This commit is contained in:
zlwu committed 2025-08-21 16:24:36 +08:00
commit f4bb5e3cdd
57 files changed
+1508 -1532

No files matched your search

+1
View File
@@ -414,6 +414,7 @@
<version>${revision}</version>
</dependency>
<!-- 社团管理模块 -->
<!-- <dependency>
<groupId>org.dromara</groupId>
+1
View File
@@ -112,6 +112,7 @@
<groupId>org.dromara</groupId>
<artifactId>ruoyi-xg</artifactId>
</dependency>
<!-- 学工管理-学生信息管理模块 -->
<!-- <dependency>
<groupId>org.dromara</groupId>
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
import java.util.List;
@@ -25,6 +27,7 @@ public class JcbtScholarshipBatch extends BaseEntity {
* 主键ID
*/
@TableId(value = "id")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
@@ -101,6 +104,7 @@ public class JcbtScholarshipBatch extends BaseEntity {
* 租户ID
*/
@TableField("tenant_id")
@JsonSerialize(using = ToStringSerializer.class)
private Long tenantId;
/**
@@ -48,12 +48,6 @@ public class JcbtWorkStudyPosition extends BaseEntity {
*/
private String workTime;
/**
* 工作地点
*/
@TableField(exist = false)
private String workLocation;
/**
* 岗位有效期开始
*/
@@ -8,6 +8,8 @@ import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
import java.util.List;
@@ -27,6 +29,7 @@ public class JcbtScholarshipBatchBo extends BaseEntity {
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
@@ -121,6 +124,7 @@ public class JcbtScholarshipBatchBo extends BaseEntity {
/**
* 租户ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long tenantId;
}
@@ -56,13 +56,6 @@ public class JcbtWorkStudyPositionBo extends BaseEntity {
@Size(max = 200, message = "工作时间描述长度不能超过200个字符")
private String workTime;
/**
* 工作地点
*/
@NotBlank(message = "工作地点不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(max = 200, message = "工作地点描述长度不能超过200个字符")
private String workLocation;
/**
* 岗位有效期开始
*/
@@ -8,6 +8,8 @@ import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
@@ -32,6 +34,7 @@ public class JcbtScholarshipBatchVo implements Serializable {
* 主键ID
*/
@ExcelProperty(value = "主键ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
/**
@@ -130,6 +133,7 @@ public class JcbtScholarshipBatchVo implements Serializable {
/**
* 创建者
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long createBy;
/**
@@ -141,6 +145,7 @@ public class JcbtScholarshipBatchVo implements Serializable {
/**
* 更新者
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long updateBy;
/**
@@ -157,6 +162,7 @@ public class JcbtScholarshipBatchVo implements Serializable {
/**
* 租户ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long tenantId;
}
@@ -3,9 +3,11 @@ package org.dromara.jcbt.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -30,6 +32,7 @@ import java.util.List;
* @author ruoyi
* @date 2025-01-27
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class JcbtScholarshipBatchServiceImpl implements IJcbtScholarshipBatchService {
@@ -112,9 +115,52 @@ public class JcbtScholarshipBatchServiceImpl implements IJcbtScholarshipBatchSer
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(JcbtScholarshipBatchBo bo) {
// 添加调试日志 - 检查BO的ID
log.info("=== 修改操作调试 ===");
log.info("输入BO的ID: {}, 类型: {}", bo.getId(), bo.getId() != null ? bo.getId().getClass().getSimpleName() : "null");
log.info("输入BO的批次编码: {}", bo.getBatchCode());
JcbtScholarshipBatch update = MapstructUtils.convert(bo, JcbtScholarshipBatch.class);
// 添加调试日志 - 检查转换后实体的ID
log.info("转换后实体的ID: {}, 类型: {}", update.getId(), update.getId() != null ? update.getId().getClass().getSimpleName() : "null");
log.info("转换后实体的批次编码: {}", update.getBatchCode());
validEntityBeforeSave(update);
boolean flag = baseMapper.updateById(update) > 0;
// 添加详细调试 - 检查updateById前的状态
log.info("=== updateById执行前检查 ===");
log.info("实体ID是否为null: {}", update.getId() == null);
log.info("实体ID值: {}", update.getId());
log.info("实体批次编码: {}", update.getBatchCode());
// 先检查数据库中是否存在该记录
JcbtScholarshipBatch existingRecord = baseMapper.selectById(update.getId());
log.info("数据库中是否存在该记录: {}", existingRecord != null);
if (existingRecord != null) {
log.info("数据库中的记录: ID={}, 批次编码={}", existingRecord.getId(), existingRecord.getBatchCode());
}
log.info("=== 开始执行updateById ===");
// 使用更安全的更新策略,避免唯一键约束冲突
boolean flag;
try {
flag = baseMapper.updateById(update) > 0;
log.info("updateById执行成功: {}", flag);
} catch (Exception e) {
log.error("updateById执行失败,错误信息: {}", e.getMessage());
// 如果是唯一键冲突,尝试使用自定义更新SQL
if (e.getMessage().contains("Duplicate entry") || e.getMessage().contains("duplicate key")) {
log.info("检测到唯一键冲突,尝试使用自定义更新策略");
flag = updateByIdSafely(update);
log.info("自定义更新策略执行结果: {}", flag);
} else {
// 其他异常直接抛出
throw e;
}
}
if (flag) {
// 更新批次类型关联关系
@@ -129,8 +175,17 @@ public class JcbtScholarshipBatchServiceImpl implements IJcbtScholarshipBatchSer
* 保存前的数据校验
*/
private void validEntityBeforeSave(JcbtScholarshipBatch entity){
// 添加调试日志 - 检查校验时的ID
log.info("=== 数据校验调试 ===");
log.info("校验时实体的ID: {}, 类型: {}", entity.getId(), entity.getId() != null ? entity.getId().getClass().getSimpleName() : "null");
log.info("校验时实体的批次编码: {}", entity.getBatchCode());
// 批次编码重复性校验
if (!checkBatchCodeUnique(entity.getBatchCode(), entity.getId())) {
boolean isUnique = checkBatchCodeUnique(entity.getBatchCode(), entity.getId());
log.info("唯一性校验结果: {}", isUnique);
if (!isUnique) {
log.error("批次编码唯一性校验失败 - 编码: {}, ID: {}", entity.getBatchCode(), entity.getId());
throw new RuntimeException("批次编码已存在");
}
@@ -208,12 +263,125 @@ public class JcbtScholarshipBatchServiceImpl implements IJcbtScholarshipBatchSer
*/
@Override
public Boolean checkBatchCodeUnique(String batchCode, Long id) {
boolean exist = baseMapper.exists(Wrappers.<JcbtScholarshipBatch>lambdaQuery()
// 添加调试日志 - 检查唯一性校验逻辑
log.info("=== 唯一性校验调试 ===");
log.info("校验参数 - 批次编码: {}, ID: {}", batchCode, id);
log.info("ID是否非空: {}", ObjectUtil.isNotNull(id));
// 获取当前租户ID
String tenantId = LoginHelper.getTenantId();
log.info("当前租户ID: {}", tenantId);
LambdaQueryWrapper<JcbtScholarshipBatch> queryWrapper = Wrappers.<JcbtScholarshipBatch>lambdaQuery()
.eq(JcbtScholarshipBatch::getBatchCode, batchCode)
.ne(ObjectUtil.isNotNull(id), JcbtScholarshipBatch::getId, id));
.eq(JcbtScholarshipBatch::getDeleted, 0); // 关键修复:只检查未删除的记录
// 如果是修改操作(ID不为空),排除当前记录
if (ObjectUtil.isNotNull(id)) {
queryWrapper.ne(JcbtScholarshipBatch::getId, id);
log.info("修改操作 - 排除当前记录ID: {}", id);
} else {
log.info("新增操作 - 不排除任何记录");
}
// 查询数量而不是存在性,以便调试
long count = baseMapper.selectCount(queryWrapper);
boolean exist = count > 0;
log.info("数据库查询结果 - 相同编码的其他未删除记录数量: {}", count);
log.info("数据库查询结果 - 存在相同编码的其他未删除记录: {}", exist);
log.info("唯一性校验最终结果: {}", !exist);
// 详细分析重复记录情况
if (count > 0) {
log.error("=== 发现重复记录,详细查询 ===");
List<JcbtScholarshipBatch> duplicateRecords = baseMapper.selectList(queryWrapper);
for (JcbtScholarshipBatch record : duplicateRecords) {
log.error("重复记录详情 - ID: {}, 批次编码: {}, 是否删除: {}",
record.getId(), record.getBatchCode(), record.getDeleted());
}
// 检查是否为ID不匹配问题
if (ObjectUtil.isNotNull(id)) {
boolean foundExactMatch = duplicateRecords.stream()
.anyMatch(record -> record.getId().equals(id));
if (!foundExactMatch) {
log.error("=== ID不匹配问题 ===");
log.error("请求修改的ID: {}", id);
log.error("数据库中实际存在的ID: {}",
duplicateRecords.stream().map(JcbtScholarshipBatch::getId).toList());
throw new RuntimeException(String.format(
"记录ID不匹配!请求ID: %s,数据库实际ID: %s。请刷新页面重新获取最新数据。",
id, duplicateRecords.get(0).getId()));
}
}
}
return !exist;
}
/**
* 安全更新方法 - 避免唯一键约束冲突
* 使用指定字段更新,绕过可能的唯一键约束问题
*/
private Boolean updateByIdSafely(JcbtScholarshipBatch update) {
log.info("=== 执行安全更新策略 ===");
// 构建更新条件
LambdaUpdateWrapper<JcbtScholarshipBatch> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(JcbtScholarshipBatch::getId, update.getId());
// 只更新非唯一键相关的字段,避免约束冲突
if (update.getBatchName() != null) {
updateWrapper.set(JcbtScholarshipBatch::getBatchName, update.getBatchName());
}
if (update.getAcademicYear() != null) {
updateWrapper.set(JcbtScholarshipBatch::getAcademicYear, update.getAcademicYear());
}
if (update.getSemester() != null) {
updateWrapper.set(JcbtScholarshipBatch::getSemester, update.getSemester());
}
if (update.getBatchType() != null) {
updateWrapper.set(JcbtScholarshipBatch::getBatchType, update.getBatchType());
}
if (update.getStartTime() != null) {
updateWrapper.set(JcbtScholarshipBatch::getStartTime, update.getStartTime());
}
if (update.getEndTime() != null) {
updateWrapper.set(JcbtScholarshipBatch::getEndTime, update.getEndTime());
}
if (update.getApplyStartTime() != null) {
updateWrapper.set(JcbtScholarshipBatch::getApplyStartTime, update.getApplyStartTime());
}
if (update.getApplyEndTime() != null) {
updateWrapper.set(JcbtScholarshipBatch::getApplyEndTime, update.getApplyEndTime());
}
if (update.getReviewStartTime() != null) {
updateWrapper.set(JcbtScholarshipBatch::getReviewStartTime, update.getReviewStartTime());
}
if (update.getReviewEndTime() != null) {
updateWrapper.set(JcbtScholarshipBatch::getReviewEndTime, update.getReviewEndTime());
}
if (update.getStatus() != null) {
updateWrapper.set(JcbtScholarshipBatch::getStatus, update.getStatus());
}
if (update.getDescription() != null) {
updateWrapper.set(JcbtScholarshipBatch::getDescription, update.getDescription());
}
if (update.getNoticeContent() != null) {
updateWrapper.set(JcbtScholarshipBatch::getNoticeContent, update.getNoticeContent());
}
// 更新基础字段
updateWrapper.set(JcbtScholarshipBatch::getUpdateBy, update.getUpdateBy());
updateWrapper.set(JcbtScholarshipBatch::getUpdateTime, update.getUpdateTime());
int result = baseMapper.update(null, updateWrapper);
log.info("安全更新策略影响行数: {}", result);
return result > 0;
}
/**
* 更改批次状态
*/
@@ -45,33 +45,63 @@ public class JcbtScholarshipBatchTypeServiceImpl implements IJcbtScholarshipBatc
return true; // 空列表视为成功
}
// 先删除原有关联关系
// 先逻辑删除原有关联关系
deleteByBatchId(batchId);
// 批量插入新的关联关系
List<JcbtScholarshipBatchType> batchTypes = new ArrayList<>();
// 智能插入新的关联关系(处理逻辑删除冲突)
int sortOrder = 1;
for (Long typeId : scholarshipTypeIds) {
insertOrRestoreBatchType(batchId, typeId, tenantId, sortOrder++);
}
return true;
}
/**
* 智能插入或恢复批次类型关联记录
* 如果存在已逻辑删除的记录,则恢复;否则插入新记录
*
* 核心逻辑:
* 1. 先查询是否存在已逻辑删除的记录(deleted=1)
* 2. 如果存在,则更新该记录为未删除状态(deleted=0),避免唯一键冲突
* 3. 如果不存在,则正常插入新记录
*/
private void insertOrRestoreBatchType(Long batchId, Long typeId, String tenantId, int sortOrder) {
// 查询是否存在已逻辑删除的记录(关键:包含deleted=1的记录)
LambdaQueryWrapper<JcbtScholarshipBatchType> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(JcbtScholarshipBatchType::getBatchId, batchId);
queryWrapper.eq(JcbtScholarshipBatchType::getScholarshipTypeId, typeId);
queryWrapper.eq(JcbtScholarshipBatchType::getTenantId, tenantId);
queryWrapper.eq(JcbtScholarshipBatchType::getDeleted, 1); // 仅查询已删除的记录
queryWrapper.last("LIMIT 1"); // 提升性能,只取一条记录
JcbtScholarshipBatchType deletedRecord = baseMapper.selectOne(queryWrapper);
if (deletedRecord != null) {
// 策略1:恢复已删除的记录,避免唯一键冲突
deletedRecord.setIsActive(1);
deletedRecord.setSortOrder(sortOrder);
deletedRecord.setUpdateBy(LoginHelper.getUserId());
deletedRecord.setUpdateTime(new Date());
deletedRecord.setDeleted(0); // 关键:恢复为未删除状态
baseMapper.updateById(deletedRecord);
} else {
// 策略2:不存在已删除记录,正常插入新记录
JcbtScholarshipBatchType batchType = new JcbtScholarshipBatchType();
batchType.setBatchId(batchId);
batchType.setScholarshipTypeId(typeId);
batchType.setIsActive(1);
batchType.setSortOrder(sortOrder++);
batchType.setSortOrder(sortOrder);
batchType.setTenantId(tenantId);
batchType.setCreateBy(LoginHelper.getUserId());
batchType.setCreateTime(new Date());
batchType.setUpdateBy(LoginHelper.getUserId());
batchType.setUpdateTime(new Date());
batchType.setDeleted(0);
batchTypes.add(batchType);
}
// 批量插入
for (JcbtScholarshipBatchType batchType : batchTypes) {
baseMapper.insert(batchType);
}
return true;
}
@Override
@@ -6,7 +6,6 @@ CREATE TABLE jcbt_work_study_position (
position_scope VARCHAR(20) DEFAULT 'COLLEGE' COMMENT '岗位发布范围(COLLEGE-本学院,UNIVERSITY-全校)',
required_count INT NOT NULL COMMENT '需求人数',
work_time VARCHAR(200) NOT NULL COMMENT '工作时间',
work_location VARCHAR(200) NOT NULL COMMENT '工作地点',
start_date DATETIME NOT NULL COMMENT '岗位有效期开始',
end_date DATETIME NOT NULL COMMENT '岗位有效期结束',
position_requirements TEXT COMMENT '岗位要求',
@@ -37,45 +36,44 @@ CREATE TABLE jcbt_work_study_position (
-- 插入测试数据
INSERT INTO jcbt_work_study_position (
position_name, position_scope, required_count, work_time, work_location,
position_name, position_scope, required_count, work_time,
start_date, end_date, position_requirements, salary_info,
contact_teacher, contact_phone, college_id, college_name, status, current_count
) VALUES
(
'图书馆助理', 'UNIVERSITY', 5, '周一至周五 14:00-17:00', '图书馆一楼服务台',
'图书馆助理', 'UNIVERSITY', 5, '周一至周五 14:00-17:00',
'2025-01-01 00:00:00', '2025-06-30 23:59:59',
'1. 熟悉图书馆基本业务流程;2. 责任心强,工作认真细致;3. 具备良好的沟通能力',
'15元/小时,按月发放',
'张老师', '13800138001', 1, '信息工程学院', 'PUBLISHED', 2
),
(
'实验室管理员', 'COLLEGE', 3, '周一、三、五 16:00-18:00', '信息楼A301实验室',
'实验室管理员', 'COLLEGE', 3, '周一、三、五 16:00-18:00',
'2025-01-01 00:00:00', '2025-06-30 23:59:59',
'1. 计算机相关专业优先;2. 熟悉实验室设备操作;3. 有相关工作经验者优先',
'18元/小时,按月发放',
'李老师', '13800138002', 1, '信息工程学院', 'RECRUITING', 1
),
(
'教学助理', 'UNIVERSITY', 8, '根据课程安排,灵活调整', '各教学楼教室',
'教学助理', 'UNIVERSITY', 8, '根据课程安排,灵活调整',
'2025-01-01 00:00:00', '2025-06-30 23:59:59',
'1. 学习成绩优良;2. 有较强的组织协调能力;3. 能够辅助教师完成教学任务',
'12元/小时,按月发放',
'王老师', '13800138003', 2, '经济管理学院', 'PUBLISHED', 5
),
(
'食堂服务员', 'UNIVERSITY', 10, '11:00-13:00, 17:00-19:00', '学生食堂各楼层',
'食堂服务员', 'UNIVERSITY', 10, '11:00-13:00, 17:00-19:00',
'2025-01-01 00:00:00', '2025-06-30 23:59:59',
'1. 身体健康,无传染性疾病;2. 工作积极主动;3. 能够承受一定的工作强度',
'14元/小时,按月发放',
'刘老师', '13800138004', 3, '后勤服务处', 'RECRUITING', 7
),
(
'宿舍管理助理', 'COLLEGE', 6, '18:00-22:00(工作日)', '学生宿舍各楼栋',
'宿舍管理助理', 'COLLEGE', 6, '18:00-22:00(工作日)',
'2025-01-01 00:00:00', '2025-06-30 23:59:59',
'1. 住校学生优先;2. 有责任心,能够处理突发情况;3. 沟通能力强',
'13元/小时,按月发放',
'陈老师', '13800138005', 4, '学生工作处', 'PUBLISHED', 3
);
-- 如果表已存在,添加工作地点字段的ALTER语句
-- ALTER TABLE jcbt_work_study_position ADD COLUMN work_location VARCHAR(200) NOT NULL COMMENT '工作地点' AFTER work_time;
-- 注意:工作地点字段已从表结构中移除,不再需要
@@ -144,16 +144,19 @@ public class ZsjyGraduateInfoBo extends BaseEntity {
/**
* 邮箱地址
*/
@Size(max = 100, message = "邮箱地址长度不能超过100个字符", groups = { AddGroup.class, EditGroup.class })
private String email;
/**
* QQ号码
*/
@Size(max = 20, message = "QQ号码长度不能超过20个字符", groups = { AddGroup.class, EditGroup.class })
private String qqNumber;
/**
* 微信号码
*/
@Size(max = 30, message = "微信号码长度不能超过30个字符", groups = { AddGroup.class, EditGroup.class })
private String wechatNumber;
/**
@@ -174,11 +177,14 @@ public class ZsjyGraduateInfoBo extends BaseEntity {
/**
* 详细地址
*/
@Size(max = 200, message = "详细地址长度不能超过200个字符", groups = { AddGroup.class, EditGroup.class })
private String detailAddress;
/**
* 邮政编码
*/
@Size(max = 10, message = "邮政编码长度不能超过10个字符", groups = { AddGroup.class, EditGroup.class })
@Pattern(regexp = "^\\d{6}$", message = "邮政编码格式不正确,应为6位数字", groups = { AddGroup.class, EditGroup.class })
private String postalCode;
/**
@@ -190,6 +196,7 @@ public class ZsjyGraduateInfoBo extends BaseEntity {
/**
* 就业单位名称
*/
@Size(max = 100, message = "就业单位名称长度不能超过100个字符", groups = { AddGroup.class, EditGroup.class })
private String employmentCompany;
/**
@@ -200,6 +207,7 @@ public class ZsjyGraduateInfoBo extends BaseEntity {
/**
* 工作职位
*/
@Size(max = 50, message = "工作职位长度不能超过50个字符", groups = { AddGroup.class, EditGroup.class })
private String jobPosition;
/**
@@ -220,11 +228,13 @@ public class ZsjyGraduateInfoBo extends BaseEntity {
/**
* 升学院校
*/
@Size(max = 100, message = "升学院校长度不能超过100个字符", groups = { AddGroup.class, EditGroup.class })
private String furtherSchool;
/**
* 升学专业
*/
@Size(max = 50, message = "升学专业长度不能超过50个字符", groups = { AddGroup.class, EditGroup.class })
private String furtherMajor;
/**
+209 -177
View File
@@ -90,74 +90,72 @@
</view>
<!-- 位置信息卡片 -->
<view class="location-card" v-if="currentLocation" style="display: none;">
<view class="location-card">
<view class="card-header">
<view class="header-left">
<u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
<text class="card-title">当前位置</text>
</view>
<u-button
type="info"
size="mini"
text="重新定位"
@click="refreshLocation"
:loading="locationLoading"
class="refresh-btn"
></u-button>
</view>
<!-- 地址信息 -->
<view class="location-address">
<u-icon name="location" size="14" color="#909399" class="address-icon"></u-icon>
<text class="address-text">{{ currentLocation.address }}</text>
</view>
<!-- 坐标信息 -->
<view class="coordinates-section">
<view class="coord-row">
<view class="coord-item">
<text class="coord-label">经度</text>
<text class="coord-value">{{ currentLocation.longitude.toFixed(6) }}</text>
</view>
<view class="coord-divider"></view>
<view class="coord-item">
<text class="coord-label">纬度</text>
<text class="coord-value">{{ currentLocation.latitude.toFixed(6) }}</text>
</view>
<text class="card-title">签到位置</text>
</view>
</view>
<!-- 精度信息 -->
<view class="accuracy-section" v-if="currentLocation.accuracy">
<view class="accuracy-info">
<u-icon name="target" size="14" color="#67c23a" class="accuracy-icon"></u-icon>
<text class="accuracy-text">定位精度 {{ currentLocation.accuracy.toFixed(0) }}米</text>
<u-tag
:text="getAccuracyStatus(currentLocation.accuracy).text"
:type="getAccuracyStatus(currentLocation.accuracy).type"
size="mini"
class="accuracy-tag"
></u-tag>
</view>
</view>
<!-- 操作按钮 -->
<view class="location-actions">
<!-- 定位按钮 -->
<view class="location-row">
<u-button
type="primary"
size="default"
plain
@click="openLocationInMap"
class="map-btn"
plain
@click="handleGetLocation"
:loading="locationLoading"
class="location-btn"
>
<u-icon name="map" size="16" color="#2979ff" />
<text class="btn-text">在地图中查看</text>
<u-icon name="map-fill" size="20" color="#2979ff" />
<text style="margin-left: 8rpx">
{{ checkIsWechatBrowser() ? '微信定位' : '定位' }}
</text>
</u-button>
</view>
<!-- 位置信息显示 -->
<view v-if="currentLocation" class="location-info-section">
<!-- 坐标信息 -->
<view class="location-coords-container">
<text class="location-coords">
经纬度:{{ currentLocation.latitude.toFixed(6) }},{{ currentLocation.longitude.toFixed(6) }}
</text>
</view>
<!-- 精度信息 -->
<view class="accuracy-section" v-if="currentLocation.accuracy">
<view class="accuracy-info">
<u-icon name="target" size="14" color="#67c23a" class="accuracy-icon"></u-icon>
<text class="accuracy-text">定位精度 {{ currentLocation.accuracy.toFixed(0) }}米</text>
<u-tag
:text="getAccuracyStatus(currentLocation.accuracy).text"
:type="getAccuracyStatus(currentLocation.accuracy).type"
size="mini"
class="accuracy-tag"
></u-tag>
</view>
</view>
<!-- 查看位置按钮 -->
<view class="location-action-container">
<u-button
size="default"
type="primary"
plain
@click="openLocationInMap"
class="view-map-btn"
>
<u-icon name="eye" size="16" color="#2979ff" />
<text>查看位置</text>
</u-button>
</view>
</view>
</view>
<!-- 位置获取失败提示 -->
<view class="location-error-card" v-else-if="locationError">
<view class="location-error-card" v-if="locationError && !currentLocation">
<view class="card-header">
<view class="header-left">
<u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
@@ -265,6 +263,7 @@ import { getInternshipInfo, getStudentInternshipInfo, checkIn, getDailyList, get
import { getCurrentLocation, checkLocationPermission, openLocationSettings } from '@/utils/location.js';
import { userManager } from '@/utils/user.js';
import weChatSDK from '@/utils/wechat.js';
import { request } from '@/common/request.js';
export default {
name: 'InternshipIndex',
@@ -289,7 +288,6 @@ export default {
// 微信SDK相关(新增)
wechatSDK: weChatSDK,
isWechatBrowser: false,
wechatInitializing: false,
// 统计数据
@@ -319,19 +317,6 @@ export default {
};
},
async onLoad() {
// 初始化微信环境检查并自动初始化SDK
await this.checkWechatEnvironment();
this.initPage();
},
onShow() {
// 页面显示时刷新数据
this.loadTodayRecord();
this.loadStatistics();
},
methods: {
// === 微信SDK相关方法 ===
@@ -339,9 +324,9 @@ export default {
async checkWechatEnvironment() {
try {
// 检查是否在微信浏览器中
this.isWechatBrowser = this.wechatSDK.isWeChatBrowser();
const isWechatBrowser = this.wechatSDK.isWeChatBrowser();
if (this.isWechatBrowser) {
if (isWechatBrowser) {
// 在微信环境中,自动初始化SDK
await this.initWechatSDK();
}
@@ -407,10 +392,10 @@ export default {
}
// 初始化微信环境检测
this.isWechatBrowser = this.wechatSDK.isWeChatBrowser();
const isWechatBrowser = this.wechatSDK.isWeChatBrowser();
// 如果在微信环境,初始化微信SDK
if (this.isWechatBrowser) {
if (isWechatBrowser) {
await this.initWechatSDK();
}
@@ -419,10 +404,14 @@ export default {
this.loadInternshipInfo(),
this.loadTodayRecord(),
this.loadRecentRecords(),
this.loadStatistics(),
this.initLocation()
this.loadStatistics()
]);
// 初始化位置信息(不阻塞页面加载)
this.initLocation().catch(error => {
console.error('位置信息初始化失败:', error);
});
} catch (error) {
console.error('初始化页面失败:', error);
uni.showToast({
@@ -608,8 +597,8 @@ export default {
return;
}
// 获取当前位置
await this.getCurrentLocationInfo();
// 不自动获取位置,等待用户手动点击定位按钮
console.log('位置权限检查完成,等待用户手动定位');
} catch (error) {
console.error('初始化位置失败:', error);
this.canCheckin = false;
@@ -645,16 +634,16 @@ export default {
},
/**
* 智能定位:根据环境自动选择最适合的定位方式(增强版)
* 智能定位:根据环境自动选择最适合的定位方式(参考请假页面实现)
*/
async getCurrentLocationInfo() {
async handleGetLocation() {
try {
this.locationLoading = true;
this.locationError = '';
uni.showLoading({ title: '定位中...', mask: true });
const envInfo = {
isWechatBrowser: this.isWechatBrowser,
isWechatBrowser: this.checkIsWechatBrowser(),
isWechatSdkReady: this.wechatSDK.isInitialized,
isH5Web: this.isH5Web(),
isBrowserGeolocationSupported: this.isBrowserGeolocationSupported(),
@@ -663,7 +652,7 @@ export default {
console.log('环境检测结果:', envInfo);
// 优先级1:微信SDK定位(如果在微信环境且SDK已就绪)
if (this.isWechatBrowser && this.wechatSDK.isInitialized) {
if (this.checkIsWechatBrowser() && this.wechatSDK.isInitialized) {
const wxLocationSuccess = await this.tryWeChatLocationWithSDK();
if (wxLocationSuccess) {
return;
@@ -671,7 +660,7 @@ export default {
}
// 优先级2:微信环境但SDK未就绪,尝试初始化后重试
if (this.isWechatBrowser && !this.wechatSDK.isInitialized) {
if (this.checkIsWechatBrowser() && !this.wechatSDK.isInitialized) {
try {
await this.initWechatSDK();
if (this.wechatSDK.isInitialized) {
@@ -710,7 +699,7 @@ export default {
success: (res) => {
if (res.confirm) {
// 用户选择重试
this.getCurrentLocationInfo();
this.handleGetLocation();
}
}
});
@@ -720,7 +709,14 @@ export default {
},
/**
* 使用微信SDK进行定位(增强版)
* 检测浏览器是否支持 Geolocation API
*/
isBrowserGeolocationSupported() {
return typeof navigator !== 'undefined' && 'geolocation' in navigator;
},
/**
* 使用微信SDK进行定位(参考请假页面实现)
*/
async tryWeChatLocationWithSDK() {
try {
@@ -768,7 +764,7 @@ export default {
},
/**
* H5 web端原生定位(增强版)
* H5 web端原生定位(参考请假页面实现)
*/
async handleH5WebLocation() {
return new Promise((resolve) => {
@@ -792,6 +788,7 @@ export default {
const { latitude, longitude, accuracy } = position.coords;
// 将WGS84坐标转换为GCJ02坐标系(中国标准坐标系)
this.currentLocation = {
latitude: latitude,
longitude: longitude,
@@ -834,12 +831,12 @@ export default {
content: `${errorMessage}。是否手动输入位置信息?`,
showCancel: true,
cancelText: '取消',
confirmText: '稍后重试',
confirmText: '手动输入',
success: (res) => {
if (res.confirm) {
// 用户选择重试
// 可以在这里添加手动输入位置的逻辑
uni.showToast({
title: '请稍后重试定位',
title: '手动输入功能开发中',
icon: 'none',
duration: 2000,
});
@@ -935,23 +932,24 @@ export default {
* 刷新位置
*/
refreshLocation() {
this.getCurrentLocationInfo();
this.handleGetLocation();
},
/**
* 打开位置信息在地图中查看
* 打开位置信息在地图中查看(参考请假页面实现)
*/
async openLocationInMap() {
if (!this.currentLocation) {
uni.showToast({
title: '无法获取位置信息',
icon: 'none'
title: '暂无位置信息',
icon: 'none',
});
return;
}
// 从坐标信息解析经纬度
const { latitude, longitude } = this.currentLocation;
if (!latitude || !longitude || isNaN(latitude) || isNaN(longitude)) {
@@ -962,26 +960,72 @@ export default {
return;
}
if (this.isWechatBrowser && this.wechatSDK.isInitialized) {
try {
await this.wechatSDK.openLocation({
latitude: latitude,
// 声明变量在外部作用域,确保finally块可以访问
let formattedAddress = '';
// 调用高德sdk逆地理编码
try {
// 调用后台API进行逆地理编码
const geocodingResponse = await request({
url: '/system/SysRegion/reverse-geocode',
method: 'POST',
data: {
longitude: longitude,
name: '实习签到位置',
address: this.currentLocation.address || '当前位置',
scale: 16,
});
} catch (error) {
console.error('微信地图打开失败:', error);
latitude: latitude,
},
});
if (geocodingResponse.code === 200 && geocodingResponse.data) {
formattedAddress = geocodingResponse.data.regeocode?.formattedAddress || '';
if (formattedAddress) {
console.log('逆地理编码成功:', formattedAddress);
uni.showToast({
title: formattedAddress,
icon: 'none',
duration: 2000,
});
}
} else {
console.warn('逆地理编码失败:', geocodingResponse.msg);
}
} catch (error) {
console.error('逆地理编码请求失败:', error);
// 逆地理编码失败不影响地图打开功能
} finally {
console.log('=== finally块开始执行 ===');
console.log('formattedAddress:', formattedAddress);
console.log('isWechatBrowser:', this.checkIsWechatBrowser());
console.log('wechatSDK.isInitialized:', this.wechatSDK?.isInitialized);
if (this.checkIsWechatBrowser() && this.wechatSDK?.isInitialized) {
try {
console.log('尝试使用微信SDK打开地图...');
await this.wechatSDK.openLocation({
latitude: latitude,
longitude: longitude,
name: formattedAddress || '实习签到位置',
address: formattedAddress || '当前位置',
scale: 16,
});
console.log('微信地图打开成功');
} catch (error) {
console.error('微信地图打开失败:', error);
this.fallbackOpenMap(latitude, longitude);
}
} else {
console.log('使用备用地图打开方式...');
this.fallbackOpenMap(latitude, longitude);
}
} else {
this.fallbackOpenMap(latitude, longitude);
console.log('=== finally块执行完成 ===');
}
},
// 备用地图打开方式
// 备用地图打开方式(参考请假页面实现)
fallbackOpenMap(latitude, longitude) {
const url = `https://apis.map.qq.com/uri/v1/marker?marker=coord:${latitude},${longitude};title:实习签到位置;addr:${encodeURIComponent(
this.currentLocation.address || '当前位置',
)}&referer=智慧校园`;
// 首先尝试使用uni.openLocation(原生地图)
if (typeof uni.openLocation === 'function') {
uni.showLoading({
@@ -1059,7 +1103,7 @@ export default {
confirmText: '重新获取',
success: (res) => {
if (res.confirm) {
this.initLocation();
this.handleGetLocation();
}
}
});
@@ -1269,6 +1313,24 @@ export default {
checkIsWechatBrowser() {
const ua = navigator.userAgent.toLowerCase();
return ua.includes('micromessenger');
},
/**
* 刷新数据
*/
async refreshData() {
try {
// 刷新位置信息
if (!this.currentLocation) {
await this.getCurrentLocationInfo();
}
// 检查今日签到状态
await this.checkTodayCheckInStatus();
} catch (error) {
console.error('数据刷新失败:', error);
}
}
},
@@ -1295,8 +1357,8 @@ export default {
// 设置当前日期
this.currentDate = this.formatDate(new Date());
// 检测微信环境
this.isWechatBrowser = this.checkIsWechatBrowser();
// 检测微信环境(在methods中有相应方法)
// this.checkIsWechatBrowser() 在需要时调用
// 获取用户信息(不阻塞)
this.loadUserInfo().catch(error => {
@@ -1308,33 +1370,15 @@ export default {
console.error('实习信息加载失败:', error);
});
// 获取位置信息(核心功能,但不阻塞页面显示)
this.getCurrentLocationInfo().catch(error => {
console.error('位置信息获取失败:', error);
});
// 初始化位置权限检查(不阻塞页面显示)
this.initLocation().catch(error => {
console.error('位置信息初始化失败:', error);
});
// 检查今日签到状态(不阻塞)
this.checkTodayCheckInStatus().catch(error => {
console.error('签到状态检查失败:', error);
});
},
/**
* 刷新数据
*/
async refreshData() {
try {
// 刷新位置信息
if (!this.currentLocation) {
await this.getCurrentLocationInfo();
}
// 检查今日签到状态
await this.checkTodayCheckInStatus();
} catch (error) {
console.error('数据刷新失败:', error);
}
}
};
</script>
@@ -1517,68 +1561,56 @@ export default {
margin-left: 4px;
}
/* 位置信息 */
.location-address {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
padding: 12px;
background: #f8f9fa;
border-radius: 8px;
border-left: 3px solid #67c23a;
}
.address-icon {
margin-right: 8px;
margin-top: 2px;
flex-shrink: 0;
}
.address-text {
font-size: 14px;
color: #303133;
line-height: 1.5;
flex: 1;
}
/* 坐标信息 */
.coordinates-section {
/* 位置信息(参考请假页面样式) */
.location-row {
margin-bottom: 16px;
}
.coord-row {
.location-btn {
width: 100%;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border: 1px solid #2979ff;
background: #fff;
color: #2979ff;
}
.location-info-section {
margin-top: 16px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
padding: 12px;
border: 1px solid #e4e7ed;
}
.coord-item {
flex: 1;
text-align: center;
.location-coords-container {
margin-bottom: 12px;
}
.coord-divider {
width: 1px;
height: 32px;
background: #e4e7ed;
margin: 0 16px;
}
.coord-label {
display: block;
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.coord-value {
display: block;
.location-coords {
font-size: 14px;
color: #303133;
font-weight: 500;
font-family: monospace;
line-height: 1.5;
word-break: break-all;
}
.location-action-container {
display: flex;
justify-content: center;
margin-top: 12px;
}
.view-map-btn {
min-width: 120px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
/* 精度信息 */
@@ -4,7 +4,7 @@ import { AssessmentIndicatorVO, AssessmentIndicatorQuery, AssessmentIndicatorFor
/**
* 查询班主任考核指标体系列表
* @param query 查询条件
* @returns
* @returns
*/
export function listBmzrAssessmentIndicator(query?: AssessmentIndicatorQuery) {
return request({
@@ -17,7 +17,7 @@ export function listBmzrAssessmentIndicator(query?: AssessmentIndicatorQuery) {
/**
* 查询班主任考核指标体系详细
* @param id 主键
* @returns
* @returns
*/
export function getBmzrAssessmentIndicator(id: string | number) {
return request({
@@ -29,7 +29,7 @@ export function getBmzrAssessmentIndicator(id: string | number) {
/**
* 新增班主任考核指标体系
* @param data 表单数据
* @returns
* @returns
*/
export function addBmzrAssessmentIndicator(data: AssessmentIndicatorForm) {
return request({
@@ -42,7 +42,7 @@ export function addBmzrAssessmentIndicator(data: AssessmentIndicatorForm) {
/**
* 修改班主任考核指标体系
* @param data 表单数据
* @returns
* @returns
*/
export function updateBmzrAssessmentIndicator(data: AssessmentIndicatorForm) {
return request({
@@ -55,7 +55,7 @@ export function updateBmzrAssessmentIndicator(data: AssessmentIndicatorForm) {
/**
* 删除班主任考核指标体系
* @param ids 主键串
* @returns
* @returns
*/
export function delBmzrAssessmentIndicator(ids: string | number | Array<string | number>) {
return request({
@@ -66,7 +66,7 @@ export function delBmzrAssessmentIndicator(ids: string | number | Array<string |
/**
* 获取指标类型选项
* @returns
* @returns
*/
export function getIndicatorTypeOptions() {
return request({
@@ -77,11 +77,11 @@ export function getIndicatorTypeOptions() {
/**
* 获取父级指标选项
* @returns
* @returns
*/
export function getParentIndicatorOptions() {
return request({
url: '/bzrkh/assessmentIndicator/treeselect',
method: 'get'
});
}
}
@@ -234,4 +234,4 @@ export interface AssessmentIndicatorTypeOption {
* 状态(0正常 1停用)
*/
status?: string;
}
}
@@ -72,4 +72,4 @@ export const delAssessmentIndicatorType = (id: string | number | Array<string |
url: '/bzrkh/assessmentIndicatorType/' + id,
method: 'delete'
});
};
};
@@ -107,4 +107,4 @@ export interface AssessmentIndicatorTypeQuery extends PageQuery {
* 日期范围参数
*/
params?: any;
}
}
+1 -1
View File
@@ -97,4 +97,4 @@ export const changeAssessmentPlanStatus = (id: string | number, status: string)
planStatus: status
}
});
};
};
+1 -1
View File
@@ -237,4 +237,4 @@ export interface AssessmentSchemeOption {
* 状态
*/
status?: string;
}
}
+1 -1
View File
@@ -71,4 +71,4 @@ export const exportBmzrAssessmentScheme = (query?: BmzrAssessmentSchemeQuery): A
method: 'post',
data: query
});
};
};
+1 -1
View File
@@ -182,4 +182,4 @@ export interface BmzrAssessmentSchemeQuery extends PageQuery {
* 参数
*/
params?: any;
}
}
@@ -4,7 +4,7 @@ import { BmzrAssessmentSchemeIndicatorVO, BmzrAssessmentSchemeIndicatorQuery, Bm
/**
* 查询班主任考核方案指标关联列表
* @param query 查询条件
* @returns
* @returns
*/
export function listBmzrAssessmentSchemeIndicator(query?: BmzrAssessmentSchemeIndicatorQuery) {
return request({
@@ -17,7 +17,7 @@ export function listBmzrAssessmentSchemeIndicator(query?: BmzrAssessmentSchemeIn
/**
* 根据方案ID查询指标关联列表
* @param schemeId 方案ID
* @returns
* @returns
*/
export function listBySchemeId(schemeId: string | number) {
return request({
@@ -29,7 +29,7 @@ export function listBySchemeId(schemeId: string | number) {
/**
* 查询班主任考核方案指标关联详细
* @param id 主键
* @returns
* @returns
*/
export function getBmzrAssessmentSchemeIndicator(id: string | number) {
return request({
@@ -41,7 +41,7 @@ export function getBmzrAssessmentSchemeIndicator(id: string | number) {
/**
* 新增班主任考核方案指标关联
* @param data 表单数据
* @returns
* @returns
*/
export function addBmzrAssessmentSchemeIndicator(data: BmzrAssessmentSchemeIndicatorForm) {
return request({
@@ -54,7 +54,7 @@ export function addBmzrAssessmentSchemeIndicator(data: BmzrAssessmentSchemeIndic
/**
* 修改班主任考核方案指标关联
* @param data 表单数据
* @returns
* @returns
*/
export function updateBmzrAssessmentSchemeIndicator(data: BmzrAssessmentSchemeIndicatorForm) {
return request({
@@ -67,7 +67,7 @@ export function updateBmzrAssessmentSchemeIndicator(data: BmzrAssessmentSchemeIn
/**
* 删除班主任考核方案指标关联
* @param ids 主键串
* @returns
* @returns
*/
export function delBmzrAssessmentSchemeIndicator(ids: string | number | Array<string | number>) {
return request({
@@ -80,7 +80,7 @@ export function delBmzrAssessmentSchemeIndicator(ids: string | number | Array<st
* 批量保存方案指标关联
* @param schemeId 方案ID
* @param indicatorList 指标列表
* @returns
* @returns
*/
export function batchSaveSchemeIndicators(schemeId: string | number, indicatorList: BmzrAssessmentSchemeIndicatorForm[]) {
return request({
@@ -88,4 +88,4 @@ export function batchSaveSchemeIndicators(schemeId: string | number, indicatorLi
method: 'post',
data: indicatorList
});
}
}
@@ -147,4 +147,4 @@ export interface BmzrAssessmentSchemeIndicatorQuery extends PageQuery {
* 参数
*/
params?: any;
}
}
+8 -8
View File
@@ -3,7 +3,7 @@ import { AxiosPromise } from 'axios';
export interface ScholarshipBatchVO {
/** 主键ID */
id?: string | number;
id?: string;
/** 批次编码 */
batchCode?: string;
/** 批次名称 */
@@ -33,7 +33,7 @@ export interface ScholarshipBatchVO {
/** 通知内容 */
noticeContent?: string;
/** 关联的奖学金类型ID列表 */
scholarshipTypeIds?: Array<string | number>;
scholarshipTypeIds?: Array<string>;
/** 创建者 */
createBy?: number;
/** 创建时间 */
@@ -46,7 +46,7 @@ export interface ScholarshipBatchVO {
export interface ScholarshipBatchForm {
/** 主键ID */
id?: string | number;
id?: string;
/** 批次编码 */
batchCode?: string;
/** 批次名称 */
@@ -76,7 +76,7 @@ export interface ScholarshipBatchForm {
/** 通知内容 */
noticeContent?: string;
/** 关联的奖学金类型ID列表 */
scholarshipTypeIds?: Array<string | number>;
scholarshipTypeIds?: Array<string>;
}
export interface ScholarshipBatchQuery extends PageQuery {
@@ -106,7 +106,7 @@ export function listScholarshipBatch(query: ScholarshipBatchQuery): AxiosPromise
}
// 查询奖学金评定批次详细
export function getScholarshipBatch(id: string | number): AxiosPromise<ScholarshipBatchVO> {
export function getScholarshipBatch(id: string): AxiosPromise<ScholarshipBatchVO> {
return request({
url: '/jcbt/scholarshipBatch/' + id,
method: 'get'
@@ -132,7 +132,7 @@ export function updateScholarshipBatch(data: ScholarshipBatchForm) {
}
// 删除奖学金评定批次
export function delScholarshipBatch(id: string | number | Array<string | number>) {
export function delScholarshipBatch(id: string | Array<string>) {
return request({
url: '/jcbt/scholarshipBatch/' + id,
method: 'delete'
@@ -177,7 +177,7 @@ export function getActiveBatches(): AxiosPromise<ScholarshipBatchVO[]> {
}
// 检查批次编码是否唯一
export function checkBatchCodeUnique(batchCode: string, id?: string | number): AxiosPromise<boolean> {
export function checkBatchCodeUnique(batchCode: string, id?: string): AxiosPromise<boolean> {
return request({
url: '/jcbt/scholarshipBatch/checkBatchCodeUnique',
method: 'get',
@@ -189,7 +189,7 @@ export function checkBatchCodeUnique(batchCode: string, id?: string | number): A
}
// 更改批次状态
export function changeScholarshipBatchStatus(id: string | number, status: string) {
export function changeScholarshipBatchStatus(id: string, status: string) {
return request({
url: '/jcbt/scholarshipBatch/changeStatus',
method: 'put',
-4
View File
@@ -27,8 +27,6 @@ export interface WorkStudyApplicationQuery extends PageQuery {
positionName?: string;
/** 岗位类型 */
positionType?: string;
/** 工作地点 */
workLocation?: string;
/** 申请状态 */
status?: string;
/** 学院审核状态 */
@@ -76,8 +74,6 @@ export interface WorkStudyApplicationForm {
positionName: string;
/** 岗位类型 */
positionType: string;
/** 工作地点 */
workLocation: string;
/** 计划工作时长 */
plannedWorkHours?: number;
/** 家庭经济情况 */
@@ -113,7 +113,6 @@ export interface DormHygieneCheckVO {
* 检查备注
*/
checkRemarks: string;
}
export interface DormHygieneCheckForm extends BaseEntity {
@@ -231,11 +230,9 @@ export interface DormHygieneCheckForm extends BaseEntity {
* 检查备注
*/
checkRemarks?: string;
}
export interface DormHygieneCheckQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -346,10 +343,10 @@ export interface DormHygieneCheckQuery extends PageQuery {
*/
checkRemarks?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
// 宿舍级联选择相关类型
@@ -643,6 +640,3 @@ export interface DormHygieneCheckCompleteForm extends DormHygieneCheckForm {
*/
studentScores?: DormHygieneStudentScoreForm[];
}
@@ -98,7 +98,6 @@ export interface DormHygieneStudentScoreVO {
* 改进意见
*/
improvementNotes: string;
}
export interface DormHygieneStudentScoreForm extends BaseEntity {
@@ -201,11 +200,9 @@ export interface DormHygieneStudentScoreForm extends BaseEntity {
* 改进意见
*/
improvementNotes?: string;
}
export interface DormHygieneStudentScoreQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -301,11 +298,8 @@ export interface DormHygieneStudentScoreQuery extends PageQuery {
*/
improvementNotes?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -138,7 +138,6 @@ export interface DormViolationVO {
* 备注
*/
remark: string;
}
export interface DormViolationForm extends BaseEntity {
@@ -281,11 +280,9 @@ export interface DormViolationForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface DormViolationQuery extends PageQuery {
/**
* 学生ID
*/
@@ -416,10 +413,10 @@ export interface DormViolationQuery extends PageQuery {
*/
deleted?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
// 学生选择相关类型
@@ -478,6 +475,3 @@ export interface BedOptionVO {
id: string | number;
bedName: string;
}
@@ -67,7 +67,9 @@ export const delHolidayStay = (id: string | number | Array<string | number>) =>
* @param query 查询参数
* @returns 学生选项列表
*/
export const getStudentOptions = (query?: StudentOptionQuery): AxiosPromise<{
export const getStudentOptions = (
query?: StudentOptionQuery
): AxiosPromise<{
total: number;
rows: StudentOptionVO[];
}> => {
@@ -173,7 +173,6 @@ export interface HolidayStayVO {
* 备注信息
*/
remark: string;
}
export interface HolidayStayForm extends BaseEntity {
@@ -351,11 +350,9 @@ export interface HolidayStayForm extends BaseEntity {
* 备注信息
*/
remark?: string;
}
export interface HolidayStayQuery extends PageQuery {
/**
* 删除标志(0代表存在 1代表删除)
*/
@@ -526,10 +523,10 @@ export interface HolidayStayQuery extends PageQuery {
*/
status?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
/**
@@ -752,6 +749,3 @@ export interface BedOptionVO {
*/
bedStatus?: string;
}
@@ -245,7 +245,6 @@ export interface DisciplineRecordForm {
}
export interface DisciplineRecordQuery extends PageQuery {
/**
* 学生ID
*/
@@ -346,10 +345,10 @@ export interface DisciplineRecordQuery extends PageQuery {
*/
approvalRemark?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
/**
@@ -38,7 +38,6 @@ export interface DisciplineTypeVO {
* 删除标志(0-未删除 1-已删除)
*/
deleted: number;
}
export interface DisciplineTypeForm extends BaseEntity {
@@ -81,11 +80,9 @@ export interface DisciplineTypeForm extends BaseEntity {
* 删除标志(0-未删除 1-已删除)
*/
deleted?: number;
}
export interface DisciplineTypeQuery extends PageQuery {
/**
* 类型编码
*/
@@ -116,11 +113,8 @@ export interface DisciplineTypeQuery extends PageQuery {
*/
deleted?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -33,7 +33,6 @@ export interface RewardTypeVO {
* 删除标志(0-未删除 1-已删除)
*/
deleted: number;
}
export interface RewardTypeForm extends BaseEntity {
@@ -71,11 +70,9 @@ export interface RewardTypeForm extends BaseEntity {
* 删除标志(0-未删除 1-已删除)
*/
deleted?: number;
}
export interface RewardTypeQuery extends PageQuery {
/**
* 类型编码
*/
@@ -106,11 +103,8 @@ export interface RewardTypeQuery extends PageQuery {
*/
deleted?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
@@ -245,7 +245,6 @@ export interface DisciplineRecordForm {
}
export interface DisciplineRecordQuery extends PageQuery {
/**
* 学生ID
*/
@@ -346,10 +345,10 @@ export interface DisciplineRecordQuery extends PageQuery {
*/
approvalRemark?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}
/**
+13 -14
View File
@@ -1,4 +1,4 @@
import request from '@/utils/request'
import request from '@/utils/request';
// 查询学生月度总结列表
export function listInternshipMonthly(query: any) {
@@ -6,7 +6,7 @@ export function listInternshipMonthly(query: any) {
url: '/zsjy/internship/monthly/list',
method: 'get',
params: query
})
});
}
// 获取学生月度总结详细信息
@@ -14,7 +14,7 @@ export function getInternshipMonthly(monthlyId: string | number) {
return request({
url: '/zsjy/internship/monthly/' + monthlyId,
method: 'get'
})
});
}
// 新增学生月度总结
@@ -23,7 +23,7 @@ export function addInternshipMonthly(data: any) {
url: '/zsjy/internship/monthly',
method: 'post',
data: data
})
});
}
// 修改学生月度总结
@@ -32,7 +32,7 @@ export function updateInternshipMonthly(data: any) {
url: '/zsjy/internship/monthly',
method: 'put',
data: data
})
});
}
// 删除学生月度总结
@@ -40,7 +40,7 @@ export function delInternshipMonthly(monthlyIds: string | number | Array<string
return request({
url: '/zsjy/internship/monthly/' + monthlyIds,
method: 'delete'
})
});
}
// 提交月度总结审批
@@ -48,7 +48,7 @@ export function submitInternshipMonthly(monthlyId: string | number) {
return request({
url: '/zsjy/internship/monthly/submit/' + monthlyId,
method: 'put'
})
});
}
// 自动保存草稿
@@ -57,7 +57,7 @@ export function autosaveInternshipMonthly(data: any) {
url: '/zsjy/internship/monthly/autosave',
method: 'post',
data: data
})
});
}
// 检查月份重复
@@ -65,7 +65,7 @@ export function checkSummaryMonth(summaryMonth: string) {
return request({
url: '/zsjy/internship/monthly/check/' + summaryMonth,
method: 'get'
})
});
}
// 获取月度总结模板
@@ -73,7 +73,7 @@ export function getMonthlyTemplate(summaryMonth: string) {
return request({
url: '/zsjy/internship/monthly/template/' + summaryMonth,
method: 'get'
})
});
}
// 获取状态统计
@@ -81,7 +81,7 @@ export function getMonthlyStatistics() {
return request({
url: '/zsjy/internship/monthly/statistics',
method: 'get'
})
});
}
// 导出月度总结数据
@@ -90,7 +90,7 @@ export function exportInternshipMonthly(query: any) {
url: '/zsjy/internship/monthly/export',
method: 'post',
params: query
})
});
}
// 获取学生当前实习信息
@@ -98,6 +98,5 @@ export function getCurrentInternshipInfo() {
return request({
url: '/zsjy/internship/monthly/current-internship',
method: 'get'
})
});
}
+13 -13
View File
@@ -1,4 +1,4 @@
import request from '@/utils/request'
import request from '@/utils/request';
/**
* 二维码管理 VO
@@ -156,7 +156,7 @@ export function listQrCode(query?: QrCodeQuery) {
url: '/zsjy/qrcode/list',
method: 'get',
params: query
})
});
}
/**
@@ -166,7 +166,7 @@ export function getQrCode(id: string | number) {
return request({
url: '/zsjy/qrcode/' + id,
method: 'get'
})
});
}
/**
@@ -177,7 +177,7 @@ export function addQrCode(data: QrCodeBO) {
url: '/zsjy/qrcode',
method: 'post',
data: data
})
});
}
/**
@@ -188,7 +188,7 @@ export function updateQrCode(data: QrCodeBO) {
url: '/zsjy/qrcode',
method: 'put',
data: data
})
});
}
/**
@@ -198,7 +198,7 @@ export function delQrCode(ids: string | number | Array<string | number>) {
return request({
url: '/zsjy/qrcode/' + ids,
method: 'delete'
})
});
}
/**
@@ -210,7 +210,7 @@ export function exportQrCode(query?: QrCodeQuery) {
method: 'post',
data: query,
responseType: 'blob'
})
});
}
/**
@@ -220,7 +220,7 @@ export function validateToken(token: string) {
return request({
url: '/zsjy/qrcode/validate/' + token,
method: 'get'
})
});
}
/**
@@ -230,7 +230,7 @@ export function getQrCodeByToken(token: string) {
return request({
url: '/zsjy/qrcode/token/' + token,
method: 'get'
})
});
}
/**
@@ -240,7 +240,7 @@ export function incrementUsage(id: string | number) {
return request({
url: '/zsjy/qrcode/increment/' + id,
method: 'post'
})
});
}
/**
@@ -250,7 +250,7 @@ export function generateToken() {
return request({
url: '/zsjy/qrcode/generate-token',
method: 'post'
})
});
}
/**
@@ -261,7 +261,7 @@ export const qrCodeTypeOptions = [
label: '招生报名',
value: 1
}
]
];
/**
* 状态选项
@@ -275,4 +275,4 @@ export const statusOptions = [
label: '启用',
value: 1
}
]
];
+8 -9
View File
@@ -140,13 +140,12 @@ service.interceptors.response.use(
code === 0 ||
code === 'SUCCESS' ||
resData.success === true ||
(res.status === 200 && (
resData.rows !== undefined ||
resData.total !== undefined ||
(resData.data && (resData.data.rows !== undefined || resData.data.total !== undefined || resData.data.list !== undefined)) ||
resData.list !== undefined
)) ||
(res.status === 200 && (res.config?.method === 'get'))
(res.status === 200 &&
(resData.rows !== undefined ||
resData.total !== undefined ||
(resData.data && (resData.data.rows !== undefined || resData.data.total !== undefined || resData.data.list !== undefined)) ||
resData.list !== undefined)) ||
(res.status === 200 && res.config?.method === 'get')
) {
return Promise.resolve(resData);
}
@@ -179,8 +178,8 @@ service.interceptors.response.use(
} else if (code === 403) {
// 特殊处理403权限错误,提供更明确的错误提示
const permissionMsg = '无访问权限,可能原因:\n1. 用户权限不足\n2. 权限配置问题\n请联系管理员检查权限配置';
ElMessage({
message: permissionMsg,
ElMessage({
message: permissionMsg,
type: 'warning',
duration: 8000,
showClose: true
@@ -41,24 +41,10 @@
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['bzrkh:assessmentIndicator:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['bzrkh:assessmentIndicator:edit']"
>
修改
</el-button>
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['bzrkh:assessmentIndicator:edit']"> 修改 </el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['bzrkh:assessmentIndicator:remove']"
>
删除
</el-button>
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['bzrkh:assessmentIndicator:remove']"> 删除 </el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
@@ -66,12 +52,7 @@
</template>
<div class="table-container">
<el-table
v-loading="loading"
border
:data="assessmentIndicatorList"
@selection-change="handleSelectionChange"
>
<el-table v-loading="loading" border :data="assessmentIndicatorList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="指标编码" align="center" prop="indicatorCode" width="140" show-overflow-tooltip />
<el-table-column label="指标名称" align="center" prop="indicatorName" min-width="180" show-overflow-tooltip />
@@ -91,10 +72,22 @@
<el-table-column label="操作" align="center" width="120" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['bzrkh:assessmentIndicator:edit']"></el-button>
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['bzrkh:assessmentIndicator:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['bzrkh:assessmentIndicator:remove']"></el-button>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['bzrkh:assessmentIndicator:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -170,8 +163,21 @@
</template>
<script setup name="AssessmentIndicator" lang="ts">
import { listBmzrAssessmentIndicator, getBmzrAssessmentIndicator, delBmzrAssessmentIndicator, addBmzrAssessmentIndicator, updateBmzrAssessmentIndicator, getIndicatorTypeOptions, getParentIndicatorOptions } from '@/api/bzrkh/assessmentIndicator';
import { AssessmentIndicatorVO, AssessmentIndicatorQuery, AssessmentIndicatorForm, AssessmentIndicatorTypeOption } from '@/api/bzrkh/assessmentIndicator/types';
import {
listBmzrAssessmentIndicator,
getBmzrAssessmentIndicator,
delBmzrAssessmentIndicator,
addBmzrAssessmentIndicator,
updateBmzrAssessmentIndicator,
getIndicatorTypeOptions,
getParentIndicatorOptions
} from '@/api/bzrkh/assessmentIndicator';
import {
AssessmentIndicatorVO,
AssessmentIndicatorQuery,
AssessmentIndicatorForm,
AssessmentIndicatorTypeOption
} from '@/api/bzrkh/assessmentIndicator/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_item_enable, sys_yes_no } = toRefs<any>(proxy?.useDict('sys_item_enable', 'sys_yes_no'));
@@ -234,7 +240,7 @@ const { queryParams, form, rules } = toRefs(data);
/** 获取指标类型名称 */
const getIndicatorTypeName = (code: string) => {
const option = indicatorTypeOptions.value.find(item => item.indicatorTypeCode === code);
const option = indicatorTypeOptions.value.find((item) => item.indicatorTypeCode === code);
return option ? option.indicatorTypeName : code;
};
@@ -359,8 +365,6 @@ const handleDelete = async (row?: AssessmentIndicatorVO) => {
await getList();
};
onMounted(() => {
getList();
getIndicatorTypeOptionsList();
@@ -400,4 +404,4 @@ onMounted(() => {
background-color: #fff;
margin-top: 0px !important;
}
</style>
</style>
@@ -44,7 +44,9 @@
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['bzrkh:assessmentIndicatorType:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['bzrkh:assessmentIndicatorType:remove']">删除</el-button>
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['bzrkh:assessmentIndicatorType:remove']"
>删除</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -69,10 +71,22 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['bzrkh:assessmentIndicatorType:edit']"></el-button>
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['bzrkh:assessmentIndicatorType:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['bzrkh:assessmentIndicatorType:remove']"></el-button>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['bzrkh:assessmentIndicatorType:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -112,7 +126,13 @@
</template>
<script setup name="AssessmentIndicatorType" lang="ts">
import { listAssessmentIndicatorType, getAssessmentIndicatorType, delAssessmentIndicatorType, addAssessmentIndicatorType, updateAssessmentIndicatorType } from '@/api/bzrkh/assessmentIndicatorType';
import {
listAssessmentIndicatorType,
getAssessmentIndicatorType,
delAssessmentIndicatorType,
addAssessmentIndicatorType,
updateAssessmentIndicatorType
} from '@/api/bzrkh/assessmentIndicatorType';
import { AssessmentIndicatorTypeVO, AssessmentIndicatorTypeQuery, AssessmentIndicatorTypeForm } from '@/api/bzrkh/assessmentIndicatorType/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -300,4 +320,4 @@ onMounted(() => {
background-color: #fff;
margin-top: 0px !important;
}
</style>
</style>
+19 -17
View File
@@ -91,7 +91,7 @@
<el-table-column label="计划编码" align="center" prop="planCode" :show-overflow-tooltip="true" />
<el-table-column label="考核方案" align="center" :show-overflow-tooltip="true">
<template #default="scope">
<span>{{ schemeOptions.find(item => item.id === scope.row.schemeId)?.schemeName || '' }}</span>
<span>{{ schemeOptions.find((item) => item.id === scope.row.schemeId)?.schemeName || '' }}</span>
</template>
</el-table-column>
<el-table-column label="学年学期" align="center" width="120">
@@ -165,7 +165,12 @@
<el-col :span="24">
<el-form-item label="考核方案" prop="schemeId">
<el-select v-model="form.schemeId" placeholder="请选择考核方案" style="width: 100%">
<el-option v-for="item in schemeOptions" :key="item.id" :label="`${item.schemeName}(${item.academicYear}${item.semester === '1' ? '上' : '下'}学期)`" :value="item.id" />
<el-option
v-for="item in schemeOptions"
:key="item.id"
:label="`${item.schemeName}(${item.academicYear}${item.semester === '1' ? '上' : '下'}学期)`"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
@@ -277,11 +282,7 @@ import {
changeAssessmentPlanStatus
} from '@/api/bzrkh/assessmentPlan';
import { listBmzrAssessmentScheme } from '@/api/bzrkh/assessmentScheme';
import {
AssessmentPlanVO,
AssessmentPlanQuery,
AssessmentPlanForm
} from '@/api/bzrkh/assessmentPlan/types';
import { AssessmentPlanVO, AssessmentPlanQuery, AssessmentPlanForm } from '@/api/bzrkh/assessmentPlan/types';
import { BmzrAssessmentSchemeVO } from '@/api/bzrkh/assessmentScheme/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -371,7 +372,7 @@ const getList = async () => {
/** 获取考核方案选项 */
const getSchemeOptions = async () => {
const res = await listBmzrAssessmentScheme({
const res = await listBmzrAssessmentScheme({
status: '0', // 只获取正常状态的方案
pageNum: 1,
pageSize: 1000 // 设置较大的pageSize以获取所有可用方案
@@ -479,18 +480,19 @@ const handleDelete = async (row?: AssessmentPlanVO) => {
});
};
/** 状态修改 */
const handleStatusChange = async (row: AssessmentPlanVO) => {
const text = row.planStatus === '1' ? '启用' : '禁用';
await proxy?.$modal.confirm('确认要"' + text + '""' + row.planName + '"考核计划吗?').then(async () => {
await changeAssessmentPlanStatus(row.id!, row.planStatus!).then(() => {
proxy?.$modal.msgSuccess(text + '成功');
await proxy?.$modal
.confirm('确认要"' + text + '""' + row.planName + '"考核计划吗?')
.then(async () => {
await changeAssessmentPlanStatus(row.id!, row.planStatus!).then(() => {
proxy?.$modal.msgSuccess(text + '成功');
});
})
.catch(() => {
row.planStatus = row.planStatus === '1' ? '0' : '1';
});
}).catch(() => {
row.planStatus = row.planStatus === '1' ? '0' : '1';
});
};
onMounted(() => {
@@ -532,4 +534,4 @@ onMounted(() => {
background-color: #fff;
margin-top: 0px !important;
}
</style>
</style>
@@ -1,12 +1,5 @@
<template>
<el-dialog
title="指标体系管理"
v-model="dialogVisible"
width="1200px"
append-to-body
:close-on-click-modal="false"
@close="handleClose"
>
<el-dialog title="指标体系管理" v-model="dialogVisible" width="1200px" append-to-body :close-on-click-modal="false" @close="handleClose">
<div class="indicator-management-container">
<!-- 检索区域 -->
<div class="search-container">
@@ -15,12 +8,7 @@
<el-row :gutter="20">
<el-col :lg="8" :md="12" :sm="24">
<el-form-item label="指标名称" prop="indicatorName">
<el-input
v-model="searchParams.indicatorName"
placeholder="请输入指标名称"
clearable
@keyup.enter="handleSearchIndicators"
/>
<el-input v-model="searchParams.indicatorName" placeholder="请输入指标名称" clearable @keyup.enter="handleSearchIndicators" />
</el-form-item>
</el-col>
<el-col :lg="16" :md="12" :sm="24">
@@ -31,15 +19,10 @@
</el-col>
</el-row>
</el-form>
<!-- 搜索结果提示 -->
<div v-if="searchResultInfo" class="search-result-info">
<el-alert
:title="searchResultInfo"
type="info"
:closable="false"
show-icon
/>
<el-alert :title="searchResultInfo" type="info" :closable="false" show-icon />
</div>
</el-card>
</div>
@@ -54,8 +37,8 @@
<!-- 没有搜索结果时显示提示 -->
<div v-else-if="filteredIndicatorList.length === 0" class="empty-state">
<div style="text-align: center; color: #909399;">
<el-icon size="48" style="margin-bottom: 16px;"><Search /></el-icon>
<div style="text-align: center; color: #909399">
<el-icon size="48" style="margin-bottom: 16px"><Search /></el-icon>
<p>未找到匹配"{{ searchParams.indicatorName }}"的指标</p>
<el-button type="text" @click="resetSearch">清除搜索条件</el-button>
</div>
@@ -71,19 +54,14 @@
</div>
<div class="row-item">
<span class="label">指标名称:</span>
<el-select
v-model="item.indicatorId"
placeholder="请选择指标名称"
<el-select
v-model="item.indicatorId"
placeholder="请选择指标名称"
style="width: 250px"
@change="handleIndicatorChange(item, getOriginalIndex(item))"
filterable
>
<el-option
v-for="indicator in availableIndicators"
:key="indicator.id"
:label="indicator.indicatorName"
:value="indicator.id"
/>
<el-option v-for="indicator in availableIndicators" :key="indicator.id" :label="indicator.indicatorName" :value="indicator.id" />
</el-select>
</div>
<div class="row-item">
@@ -153,13 +131,13 @@ const filteredIndicatorList = computed(() => {
// 没有搜索条件时,显示所有指标
return indicatorList.value;
}
// 有搜索条件时,只显示匹配的指标
return indicatorList.value.filter(item => {
return indicatorList.value.filter((item) => {
// 根据indicatorId查找对应的指标名称
const indicator = availableIndicators.value.find(ind => ind.id == item.indicatorId);
const indicator = availableIndicators.value.find((ind) => ind.id == item.indicatorId);
const indicatorName = indicator?.indicatorName || '';
// 检查指标名称是否包含搜索关键词(不区分大小写)
return indicatorName.toLowerCase().includes(keyword.toLowerCase());
});
@@ -167,10 +145,11 @@ const filteredIndicatorList = computed(() => {
// 获取指标在原始列表中的索引
const getOriginalIndex = (item: BmzrAssessmentSchemeIndicatorForm) => {
return indicatorList.value.findIndex(originalItem =>
originalItem === item ||
(originalItem.id && originalItem.id === item.id) ||
(originalItem.indicatorId && originalItem.indicatorId === item.indicatorId && originalItem.sortOrder === item.sortOrder)
return indicatorList.value.findIndex(
(originalItem) =>
originalItem === item ||
(originalItem.id && originalItem.id === item.id) ||
(originalItem.indicatorId && originalItem.indicatorId === item.indicatorId && originalItem.sortOrder === item.sortOrder)
);
};
@@ -178,10 +157,10 @@ const getOriginalIndex = (item: BmzrAssessmentSchemeIndicatorForm) => {
const open = async (id: string | number) => {
schemeId.value = id;
dialogVisible.value = true;
// 加载可选指标
await loadAvailableIndicators();
// 加载当前方案的指标
await loadSchemeIndicators();
};
@@ -194,24 +173,24 @@ const loadAvailableIndicators = async () => {
pageNum: 1,
pageSize: 1000 // 加载所有指标
};
// 如果有搜索条件,添加到查询参数中
const hasSearchCondition = !!(searchParams.value.indicatorName && searchParams.value.indicatorName.trim());
if (hasSearchCondition) {
queryParams.indicatorName = searchParams.value.indicatorName.trim();
}
const res = await listBmzrAssessmentIndicator(queryParams);
availableIndicators.value = res.rows || [];
// 调试日志
console.log('loadAvailableIndicators完成:', {
queryParams,
hasSearchCondition,
resultCount: availableIndicators.value.length,
indicatorNames: availableIndicators.value.map(i => i.indicatorName)
indicatorNames: availableIndicators.value.map((i) => i.indicatorName)
});
// 更新搜索结果信息
updateSearchResultInfo(hasSearchCondition, availableIndicators.value.length);
} catch (error) {
@@ -225,15 +204,16 @@ const loadAvailableIndicators = async () => {
const loadSchemeIndicators = async () => {
try {
const res = await listBySchemeId(schemeId.value);
indicatorList.value = res.data?.map((item: any) => ({
id: item.id,
schemeId: item.schemeId,
indicatorId: item.indicatorId,
// 优先使用score字段,如果为空则使用customMaxScore字段(兜底方案)
score: item.score || item.customMaxScore,
remark: item.remark || getIndicatorRemark(item.indicatorId),
sortOrder: item.sortOrder
})) || [];
indicatorList.value =
res.data?.map((item: any) => ({
id: item.id,
schemeId: item.schemeId,
indicatorId: item.indicatorId,
// 优先使用score字段,如果为空则使用customMaxScore字段(兜底方案)
score: item.score || item.customMaxScore,
remark: item.remark || getIndicatorRemark(item.indicatorId),
sortOrder: item.sortOrder
})) || [];
} catch (error) {
console.error('加载方案指标失败:', error);
proxy?.$modal.msgError('加载方案指标失败');
@@ -242,7 +222,7 @@ const loadSchemeIndicators = async () => {
// 根据指标ID获取指标描述
const getIndicatorRemark = (indicatorId: string | number) => {
const indicator = availableIndicators.value.find(item => item.id == indicatorId);
const indicator = availableIndicators.value.find((item) => item.id == indicatorId);
return indicator?.remark || '';
};
@@ -261,23 +241,23 @@ const updateSearchResultInfo = (hasSearchCondition: boolean, resultCount: number
const getAvailableIndicators = (currentIndex: number) => {
// 搜索功能:直接返回后端搜索结果,允许重复选择同一指标
// 这样用户可以在不同位置选择相同的指标
// 调试日志
console.log('getAvailableIndicators调用:', {
currentIndex,
totalAvailable: availableIndicators.value.length,
searchKeyword: searchParams.value.indicatorName,
availableIndicatorIds: availableIndicators.value.map(i => i.id),
availableIndicatorNames: availableIndicators.value.map(i => i.indicatorName),
availableIndicatorIds: availableIndicators.value.map((i) => i.id),
availableIndicatorNames: availableIndicators.value.map((i) => i.indicatorName),
currentSelectedIndicatorId: indicatorList.value[currentIndex]?.indicatorId
});
return availableIndicators.value;
};
// 指标选择变化
const handleIndicatorChange = (item: BmzrAssessmentSchemeIndicatorForm, index: number) => {
const selectedIndicator = availableIndicators.value.find(indicator => indicator.id == item.indicatorId);
const selectedIndicator = availableIndicators.value.find((indicator) => indicator.id == item.indicatorId);
if (selectedIndicator) {
item.score = selectedIndicator.score;
item.remark = selectedIndicator.remark;
@@ -311,7 +291,7 @@ const handleSearchIndicators = async () => {
proxy?.$modal.msgWarning('请输入搜索关键词');
return;
}
try {
await loadAvailableIndicators();
proxy?.$modal.msgSuccess('搜索完成');
@@ -337,7 +317,7 @@ const resetSearch = async () => {
// 保存
const handleSave = async () => {
// 验证必填项
const invalidItems = indicatorList.value.filter(item => !item.indicatorId);
const invalidItems = indicatorList.value.filter((item) => !item.indicatorId);
if (invalidItems.length > 0) {
proxy?.$modal.msgError('请选择所有指标名称');
return;
@@ -469,10 +449,10 @@ defineExpose({
align-items: flex-start;
gap: 12px;
}
.row-item {
width: 100%;
justify-content: space-between;
}
}
</style>
</style>
+22 -18
View File
@@ -112,14 +112,26 @@
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['bzrkh:assessmentScheme:edit']"></el-button>
</el-tooltip>
<el-tooltip content="指标体系管理" placement="top">
<el-button link type="success" icon="Management" @click="handleIndicatorManagement(scope.row)" v-hasPermi="['bzrkh:assessmentSchemeIndicator:list']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['bzrkh:assessmentScheme:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
<el-tooltip content="指标体系管理" placement="top">
<el-button
link
type="success"
icon="Management"
@click="handleIndicatorManagement(scope.row)"
v-hasPermi="['bzrkh:assessmentSchemeIndicator:list']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['bzrkh:assessmentScheme:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-container">
@@ -222,9 +234,7 @@ import { BmzrAssessmentSchemeVO, BmzrAssessmentSchemeQuery, BmzrAssessmentScheme
import IndicatorManagement from './IndicatorManagement.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, sys_yesno } = toRefs<any>(
proxy?.useDict('sys_normal_disable', 'sys_yesno')
);
const { sys_normal_disable, sys_yesno } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_yesno'));
const schemeList = ref<BmzrAssessmentSchemeVO[]>([]);
const buttonLoading = ref(false);
@@ -385,8 +395,6 @@ const handleDelete = async (row?: BmzrAssessmentSchemeVO) => {
}
};
/** 指标体系管理按钮操作 */
const handleIndicatorManagement = (row: BmzrAssessmentSchemeVO) => {
indicatorManagementRef.value?.open(row.id);
@@ -485,10 +493,6 @@ onMounted(() => {
border-bottom: none !important;
}
/* 表单样式 */
.scheme-form {
padding: 20px;
@@ -513,4 +517,4 @@ onMounted(() => {
justify-content: flex-end;
gap: 12px;
}
</style>
</style>
+30 -5
View File
@@ -720,12 +720,14 @@ function handleAdd() {
/** 修改按钮操作 */
async function handleUpdate(row?: ScholarshipBatchVO) {
reset();
const _id = row?.id || ids.value[0];
const res = await getScholarshipBatch(_id);
// 先赋值基本数据
Object.assign(form.value, res.data);
// 关键修复:不调用reset(),直接赋值数据,确保ID不会被重置为undefined
form.value = {
...res.data,
id: String(_id) // 🔧 修复:确保ID为字符串格式,避免精度丢失
};
// 确保scholarshipTypeIds正确赋值
if (res.data.scholarshipTypeIds && Array.isArray(res.data.scholarshipTypeIds)) {
@@ -734,6 +736,13 @@ async function handleUpdate(row?: ScholarshipBatchVO) {
form.value.scholarshipTypeIds = [];
}
// 清除表单验证状态,但不重置数据
batchFormRef.value?.clearValidate();
// 调试日志 - 验证ID是否正确
console.log('修改操作 - ID:', form.value.id, '类型:', typeof form.value.id);
console.log('修改操作 - 完整数据:', JSON.stringify(form.value, null, 2));
dialog.visible = true;
dialog.title = '修改奖学金评定批次';
}
@@ -753,6 +762,10 @@ function submitForm() {
buttonLoading.value = true;
try {
// 调试日志 - 提交前检查数据
console.log('提交前表单数据:', form.value);
console.log('提交前ID值:', form.value.id, '类型:', typeof form.value.id);
// 检查时间冲突
const conflictCheck = await checkTimeConflict(form.value);
if (conflictCheck.data) {
@@ -760,10 +773,22 @@ function submitForm() {
return;
}
if (form.value.id) {
await updateScholarshipBatch(form.value);
// 关键修复:更严格的ID检查逻辑
const isUpdate = form.value.id && form.value.id !== undefined && form.value.id !== '' && form.value.id !== null;
if (isUpdate) {
console.log('执行修改操作,ID:', form.value.id, '类型:', typeof form.value.id);
// 🔧 修复:保持ID为字符串格式,避免精度丢失
// 不再使用parseInt(),直接使用字符串格式的ID
const updateData = {
...form.value,
id: String(form.value.id) // 确保ID始终为字符串格式
};
console.log('发送到后端的修改数据:', JSON.stringify(updateData, null, 2));
await updateScholarshipBatch(updateData);
proxy?.$modal.msgSuccess('修改成功');
} else {
console.log('执行新增操作,ID为:', form.value.id);
await addScholarshipBatch(form.value);
proxy?.$modal.msgSuccess('新增成功');
}
@@ -16,10 +16,7 @@
<span class="preview-label">岗位名称:</span>
<el-tag type="primary" size="large">{{ position?.positionName }}</el-tag>
</div>
<div class="preview-item">
<span class="preview-label">工作地点:</span>
<span class="preview-value">{{ position?.workLocation }}</span>
</div>
<div class="preview-item">
<span class="preview-label">工作时间:</span>
<span class="preview-value">{{ position?.workTime }}</span>
@@ -196,7 +193,6 @@ const formData = reactive<WorkStudyApplicationForm>({
positionId: 0,
positionName: '',
positionType: '',
workLocation: '',
familyFinancialStatus: '',
applicationReason: '',
personalAbility: '',
+1 -7
View File
@@ -353,13 +353,7 @@
</template>
<script setup name="OverseasApproval">
import {
listOverseasApproval,
getOverseasApproval,
delOverseasApproval,
addOverseasApproval,
updateOverseasApproval
} from '@/api/rs/overseas';
import { listOverseasApproval, getOverseasApproval, delOverseasApproval, addOverseasApproval, updateOverseasApproval } from '@/api/rs/overseas';
import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue';
import { parseTime } from '@/utils/ruoyi';
+4
View File
@@ -749,6 +749,10 @@ const rules = reactive({
{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }
],
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
postalCode: [
{ max: 10, message: '邮政编码长度不能超过10个字符', trigger: 'blur' },
{ pattern: /^\d{6}$/, message: '邮政编码格式不正确,应为6位数字', trigger: 'blur' }
],
employmentStatus: [{ required: true, message: '就业状态不能为空', trigger: 'change' }]
});
+2 -2
View File
@@ -297,13 +297,13 @@
<el-tooltip content="查看详情" placement="top">
<el-button link type="info" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['zsjy:graduate:query']"></el-button>
</el-tooltip>
<el-tooltip content="编辑" placement="top" v-if="scope.row.auditStatus !== '1'">
<el-tooltip content="编辑" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['zsjy:graduate:edit']"></el-button>
</el-tooltip>
<el-tooltip content="审核" placement="top" v-if="scope.row.auditStatus === '0'">
<el-button link type="success" icon="Check" @click="handleAudit(scope.row)" v-hasPermi="['zsjy:graduate:audit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.auditStatus !== '1'">
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['zsjy:graduate:remove']"></el-button>
</el-tooltip>
</template>
@@ -168,23 +168,6 @@
</template>
</el-table-column>
<el-table-column label="签退时间" min-width="120" align="center">
<template #default="scope">
<div v-if="scope.row.checkOutTime">
<div>{{ parseTime(scope.row.checkDate, '{y}-{m}-{d}') }}</div>
<div class="time-detail">{{ scope.row.checkOutTime }}</div>
</div>
<span v-else class="text-muted">未签退</span>
</template>
</el-table-column>
<el-table-column label="工作时长" min-width="100" align="center">
<template #default="scope">
<span v-if="scope.row.workDuration">{{ scope.row.workDuration }}小时</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="签到状态" min-width="100" align="center">
<template #default="scope">
<dict-tag :options="attendanceStatusOptions" :value="scope.row.status" />
@@ -193,8 +176,6 @@
<el-table-column label="签到地点" prop="checkInLocation" min-width="150" align="left" show-overflow-tooltip />
<el-table-column label="工作内容" prop="workContent" min-width="200" align="left" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<div class="action-buttons">
@@ -29,9 +29,7 @@
<el-tag v-if="detail.learningAbility" :type="getScoreTagType(detail.learningAbility)">
学习能力: {{ detail.learningAbility }}分
</el-tag>
<el-tag v-if="detail.workAttitude" :type="getScoreTagType(detail.workAttitude)">
工作态度: {{ detail.workAttitude }}分
</el-tag>
<el-tag v-if="detail.workAttitude" :type="getScoreTagType(detail.workAttitude)"> 工作态度: {{ detail.workAttitude }}分 </el-tag>
</div>
</el-descriptions-item>
<el-descriptions-item label="总字数">
@@ -205,93 +203,81 @@
<el-row class="mt-6">
<el-col :span="24" class="text-center">
<el-button @click="handleClose">关闭</el-button>
<el-button
v-if="detail.reviewStatus === 3"
type="primary"
@click="handleEdit"
>
编辑
</el-button>
<el-button
v-if="detail.reviewStatus === 3"
type="success"
@click="handleSubmit"
>
提交审核
</el-button>
<el-button v-if="detail.reviewStatus === 3" type="primary" @click="handleEdit"> 编辑 </el-button>
<el-button v-if="detail.reviewStatus === 3" type="success" @click="handleSubmit"> 提交审核 </el-button>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { getInternshipMonthly, submitInternshipMonthly } from '@/api/zsjy/internshipMonthly'
import { getInternshipMonthly, submitInternshipMonthly } from '@/api/zsjy/internshipMonthly';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'))
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'));
const emit = defineEmits(['edit', 'close'])
const emit = defineEmits(['edit', 'close']);
// 详情数据
const detail = ref<any>({})
const loading = ref(false)
const detail = ref<any>({});
const loading = ref(false);
/** 重置表单 */
const resetForm = async (monthlyId: string | number) => {
loading.value = true
loading.value = true;
try {
const res = await getInternshipMonthly(monthlyId)
detail.value = res.data || {}
const res = await getInternshipMonthly(monthlyId);
detail.value = res.data || {};
} catch (error) {
console.error('获取月度总结详情失败:', error)
proxy?.$modal.msgError('获取详情失败')
console.error('获取月度总结详情失败:', error);
proxy?.$modal.msgError('获取详情失败');
} finally {
loading.value = false
loading.value = false;
}
}
};
/** 根据分数获取标签类型 */
const getScoreTagType = (score: number) => {
if (!score) return 'info'
if (score >= 9) return 'success'
if (score >= 7) return 'warning'
if (score >= 5) return ''
return 'danger'
}
if (!score) return 'info';
if (score >= 9) return 'success';
if (score >= 7) return 'warning';
if (score >= 5) return '';
return 'danger';
};
/** 获取字数标签类型 */
const getWordCountTagType = (count: number) => {
if (!count) return 'info'
if (count < 100) return 'info'
if (count < 500) return 'warning'
if (count < 1000) return 'success'
return 'danger'
}
if (!count) return 'info';
if (count < 100) return 'info';
if (count < 500) return 'warning';
if (count < 1000) return 'success';
return 'danger';
};
/** 编辑操作 */
const handleEdit = () => {
emit('edit', detail.value.monthlyId)
}
emit('edit', detail.value.monthlyId);
};
/** 提交审核操作 */
const handleSubmit = async () => {
try {
await proxy?.$modal.confirm('确认提交该月度总结?提交后将无法再次编辑')
await submitInternshipMonthly(detail.value.monthlyId)
proxy?.$modal.msgSuccess('提交成功')
await proxy?.$modal.confirm('确认提交该月度总结?提交后将无法再次编辑');
await submitInternshipMonthly(detail.value.monthlyId);
proxy?.$modal.msgSuccess('提交成功');
// 重新加载详情
await resetForm(detail.value.monthlyId)
await resetForm(detail.value.monthlyId);
} catch {}
}
};
/** 关闭操作 */
const handleClose = () => {
emit('close')
}
emit('close');
};
defineExpose({
resetForm
})
});
</script>
<style scoped>
@@ -316,4 +302,3 @@ defineExpose({
white-space: pre-wrap;
}
</style>
+84 -101
View File
@@ -7,13 +7,7 @@
<template #header>
<div class="flex justify-between items-center">
<span class="font-medium">基本信息</span>
<el-button
v-if="form.monthlyId && form.reviewStatus === 3"
type="primary"
size="small"
@click="handleAutoSave"
:loading="autoSaving"
>
<el-button v-if="form.monthlyId && form.reviewStatus === 3" type="primary" size="small" @click="handleAutoSave" :loading="autoSaving">
{{ autoSaving ? '保存中...' : '保存草稿' }}
</el-button>
</div>
@@ -262,12 +256,7 @@
<span class="font-medium">月份检查</span>
</template>
<el-alert
:type="monthDuplicateCheck.type"
:title="monthDuplicateCheck.message"
show-icon
:closable="false"
/>
<el-alert :type="monthDuplicateCheck.type" :title="monthDuplicateCheck.message" show-icon :closable="false" />
</el-card>
</el-col>
</el-row>
@@ -305,16 +294,16 @@ import {
checkSummaryMonth,
getMonthlyTemplate,
getCurrentInternshipInfo
} from '@/api/zsjy/internshipMonthly'
} from '@/api/zsjy/internshipMonthly';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'))
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'));
const emit = defineEmits(['success'])
const emit = defineEmits(['success']);
const monthlyFormRef = ref<ElFormInstance>()
const submitting = ref(false)
const autoSaving = ref(false)
const monthlyFormRef = ref<ElFormInstance>();
const submitting = ref(false);
const autoSaving = ref(false);
// 表单数据
const initFormData = {
@@ -337,15 +326,13 @@ const initFormData = {
reviewStatus: undefined,
submitTime: undefined,
createTime: undefined
}
};
const form = ref({ ...initFormData })
const form = ref({ ...initFormData });
// 表单验证规则
const rules = ref({
summaryMonth: [
{ required: true, message: '总结月份不能为空', trigger: 'change' }
],
summaryMonth: [{ required: true, message: '总结月份不能为空', trigger: 'change' }],
workContent: [
{ required: true, message: '工作内容不能为空', trigger: 'blur' },
{ min: 10, message: '工作内容至少需要10个字符', trigger: 'blur' },
@@ -380,107 +367,103 @@ const rules = ref({
{ required: true, message: '自我评价不能为空', trigger: 'blur' },
{ max: 300, message: '自我评价不能超过300个字符', trigger: 'blur' }
],
nextMonthPlan: [
{ max: 300, message: '下月计划不能超过300个字符', trigger: 'blur' }
],
remark: [
{ max: 200, message: '备注说明不能超过200个字符', trigger: 'blur' }
]
})
nextMonthPlan: [{ max: 300, message: '下月计划不能超过300个字符', trigger: 'blur' }],
remark: [{ max: 200, message: '备注说明不能超过200个字符', trigger: 'blur' }]
});
// 月份重复检查
const monthDuplicateCheck = ref({
show: false,
type: 'info',
message: ''
})
});
// 自动保存定时器
let autoSaveTimer: NodeJS.Timeout | null = null
let autoSaveTimer: NodeJS.Timeout | null = null;
/** 重置表单 */
const resetForm = async (monthlyId?: string | number) => {
form.value = { ...initFormData }
monthDuplicateCheck.value.show = false
form.value = { ...initFormData };
monthDuplicateCheck.value.show = false;
if (monthlyId) {
try {
const res = await getInternshipMonthly(monthlyId)
form.value = { ...res.data }
updateWordCount()
const res = await getInternshipMonthly(monthlyId);
form.value = { ...res.data };
updateWordCount();
} catch (error) {
console.error('获取月度总结详情失败:', error)
console.error('获取月度总结详情失败:', error);
}
} else {
// 新增时获取当前月份模板和学生实习信息
const currentMonth = new Date().toISOString().substring(0, 7)
form.value.summaryMonth = currentMonth
await loadMonthlyTemplate(currentMonth)
const currentMonth = new Date().toISOString().substring(0, 7);
form.value.summaryMonth = currentMonth;
await loadMonthlyTemplate(currentMonth);
// 获取学生当前实习信息
try {
const internshipRes = await getCurrentInternshipInfo()
const internshipRes = await getCurrentInternshipInfo();
if (internshipRes.code === 200 && internshipRes.data) {
form.value.internshipId = internshipRes.data.internshipId
form.value.internshipId = internshipRes.data.internshipId;
} else {
// 如果没有进行中的实习记录,提示用户
proxy?.$modal.msgError('您当前没有进行中的实习记录,无法创建月度总结')
emit('success') // 关闭对话框
return
proxy?.$modal.msgError('您当前没有进行中的实习记录,无法创建月度总结');
emit('success'); // 关闭对话框
return;
}
} catch (error) {
console.error('获取学生实习信息失败:', error)
proxy?.$modal.msgError('获取实习信息失败,请稍后重试')
emit('success') // 关闭对话框
return
console.error('获取学生实习信息失败:', error);
proxy?.$modal.msgError('获取实习信息失败,请稍后重试');
emit('success'); // 关闭对话框
return;
}
}
nextTick(() => {
monthlyFormRef.value?.clearValidate()
})
}
monthlyFormRef.value?.clearValidate();
});
};
/** 加载月份模板 */
const loadMonthlyTemplate = async (month: string) => {
try {
const res = await getMonthlyTemplate(month)
const res = await getMonthlyTemplate(month);
if (res.data) {
// 如果有模板数据,可以预填充一些内容
// form.value.nextMonthPlan = res.data.suggestedPlan || ''
}
} catch (error) {
console.log('获取月份模板失败,将使用空模板')
console.log('获取月份模板失败,将使用空模板');
}
}
};
/** 检查月份重复 */
const checkMonthDuplicate = async () => {
if (!form.value.summaryMonth) return
if (!form.value.summaryMonth) return;
try {
const res = await checkSummaryMonth(form.value.summaryMonth)
const res = await checkSummaryMonth(form.value.summaryMonth);
if (res.data.exists && (!form.value.monthlyId || res.data.monthlyId !== form.value.monthlyId)) {
monthDuplicateCheck.value = {
show: true,
type: 'warning',
message: '该月份已存在总结记录,请选择其他月份'
}
};
} else {
monthDuplicateCheck.value = {
show: true,
type: 'success',
message: '该月份可以创建总结'
}
};
}
} catch (error) {
monthDuplicateCheck.value = {
show: true,
type: 'error',
message: '检查月份失败,请重试'
}
};
}
}
};
/** 更新字数统计 */
const updateWordCount = () => {
@@ -492,91 +475,91 @@ const updateWordCount = () => {
form.value.selfEvaluation,
form.value.nextMonthPlan,
form.value.remark
].join('')
].join('');
form.value.wordCount = content.length
form.value.wordCount = content.length;
// 设置自动保存定时器
if (form.value.monthlyId && form.value.reviewStatus === 3) {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer)
clearTimeout(autoSaveTimer);
}
autoSaveTimer = setTimeout(() => {
handleAutoSave()
}, 5000) // 5秒后自动保存
handleAutoSave();
}, 5000); // 5秒后自动保存
}
}
};
/** 获取字数标签类型 */
const getWordCountTagType = (count: number) => {
if (count < 100) return 'info'
if (count < 500) return 'warning'
if (count < 1000) return 'success'
return 'danger'
}
if (count < 100) return 'info';
if (count < 500) return 'warning';
if (count < 1000) return 'success';
return 'danger';
};
/** 自动保存草稿 */
const handleAutoSave = async () => {
if (!form.value.monthlyId || form.value.reviewStatus !== 3) return
if (!form.value.monthlyId || form.value.reviewStatus !== 3) return;
autoSaving.value = true
autoSaving.value = true;
try {
await autosaveInternshipMonthly(form.value)
proxy?.$message.success('草稿已自动保存')
await autosaveInternshipMonthly(form.value);
proxy?.$message.success('草稿已自动保存');
} catch (error) {
console.error('自动保存失败:', error)
console.error('自动保存失败:', error);
} finally {
autoSaving.value = false
autoSaving.value = false;
}
}
};
/** 提交表单 */
const handleSubmit = async (type: 'draft' | 'submit') => {
if (!monthlyFormRef.value) return
if (!monthlyFormRef.value) return;
const valid = await monthlyFormRef.value.validate().catch(() => false)
if (!valid) return
const valid = await monthlyFormRef.value.validate().catch(() => false);
if (!valid) return;
submitting.value = true
submitting.value = true;
try {
updateWordCount()
updateWordCount();
const formData = {
...form.value,
reviewStatus: type === 'draft' ? 3 : 0, // 草稿=3,待审核=0
submitTime: type === 'submit' ? new Date() : undefined
}
};
if (form.value.monthlyId) {
await updateInternshipMonthly(formData)
await updateInternshipMonthly(formData);
} else {
await addInternshipMonthly(formData)
await addInternshipMonthly(formData);
}
proxy?.$modal.msgSuccess(type === 'draft' ? '保存成功' : '提交成功')
emit('success')
proxy?.$modal.msgSuccess(type === 'draft' ? '保存成功' : '提交成功');
emit('success');
} catch (error) {
console.error('提交失败:', error)
console.error('提交失败:', error);
} finally {
submitting.value = false
submitting.value = false;
}
}
};
/** 取消操作 */
const handleCancel = () => {
emit('success')
}
emit('success');
};
// 清理定时器
onUnmounted(() => {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer)
clearTimeout(autoSaveTimer);
}
})
});
defineExpose({
resetForm
})
});
</script>
<style scoped>
+97 -129
View File
@@ -31,13 +31,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="专业技能" prop="professionalSkills">
<el-input-number
v-model="queryParams.professionalSkills"
:min="1"
:max="10"
placeholder="请输入专业技能评分"
style="width: 100%"
/>
<el-input-number v-model="queryParams.professionalSkills" :min="1" :max="10" placeholder="请输入专业技能评分" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
@@ -60,10 +54,14 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internshipmonthly:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:internshipmonthly:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:internshipmonthly:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:internshipmonthly:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:internshipmonthly:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipmonthly:export']">导出</el-button>
@@ -80,20 +78,16 @@
<el-table-column label="综合评分" align="center" width="120">
<template #default="scope">
<el-tooltip content="专业/沟通/团队/学习/态度" placement="top">
<el-tag :type="getScoreTagType(getAverageScore(scope.row))">
{{ getAverageScore(scope.row).toFixed(1) }}分
</el-tag>
<el-tag :type="getScoreTagType(getAverageScore(scope.row))"> {{ getAverageScore(scope.row).toFixed(1) }}分 </el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="字数统计" align="center" width="100">
<template #default="scope">
{{ scope.row.wordCount || 0 }}字
</template>
<template #default="scope"> {{ scope.row.wordCount || 0 }}字 </template>
</el-table-column>
<el-table-column label="审核状态" align="center" prop="reviewStatus" width="100">
<template #default="scope">
<dict-tag :options="internship_monthly_status" :value="scope.row.reviewStatus" />
<dict-tag :options="internship_monthly_status" :value="scope.row.reviewStatus" />
</template>
</el-table-column>
<el-table-column label="是否逾期" align="center" prop="isOverdue" width="100">
@@ -116,59 +110,29 @@
<template #default="scope">
<!-- 查看详情 -->
<el-tooltip content="查看详情" placement="top">
<el-button
link
type="primary"
icon="View"
@click="handleDetail(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:list']"
></el-button>
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['zsjy:internshipmonthly:list']"></el-button>
</el-tooltip>
<!-- 编辑(只有草稿状态才能编辑) -->
<el-tooltip content="编辑" placement="top" v-if="scope.row.reviewStatus === 3">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:edit']"
></el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['zsjy:internshipmonthly:edit']"></el-button>
</el-tooltip>
<!-- 提交(只有草稿状态才能提交) -->
<el-tooltip content="提交" placement="top" v-if="scope.row.reviewStatus === 3">
<el-button
link
type="success"
icon="Check"
@click="handleSubmit(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:submit']"
></el-button>
<el-button link type="success" icon="Check" @click="handleSubmit(scope.row)" v-hasPermi="['zsjy:internshipmonthly:submit']"></el-button>
</el-tooltip>
<!-- 删除(只有草稿状态才能删除) -->
<el-tooltip content="删除" placement="top" v-if="scope.row.reviewStatus === 3">
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:remove']"
></el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['zsjy:internshipmonthly:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改月度总结对话框 -->
@@ -190,30 +154,30 @@ import {
delInternshipMonthly,
submitInternshipMonthly,
exportInternshipMonthly
} from '@/api/zsjy/internshipMonthly'
import MonthlyForm from './form.vue'
import MonthlyDetail from './detail.vue'
} from '@/api/zsjy/internshipMonthly';
import MonthlyForm from './form.vue';
import MonthlyDetail from './detail.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'))
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'));
const monthlyList = ref<any[]>([])
const loading = ref(true)
const showSearch = ref(true)
const ids = ref<Array<string | number>>([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const monthlyList = ref<any[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
})
});
const detailDialog = reactive<DialogOption>({
visible: false,
title: ''
})
});
const initFormData: any = {
pageNum: 1,
@@ -221,101 +185,105 @@ const initFormData: any = {
summaryMonth: undefined,
reviewStatus: undefined,
professionalSkills: undefined
}
};
const data = reactive<PageData<any, any>>({
form: {},
queryParams: { ...initFormData },
rules: {}
})
});
const { queryParams, form, rules } = toRefs(data)
const { queryParams, form, rules } = toRefs(data);
const queryFormRef = ref<ElFormInstance>()
const monthlyFormRef = ref<ComponentRef<typeof MonthlyForm>>()
const monthlyDetailRef = ref<ComponentRef<typeof MonthlyDetail>>()
const queryFormRef = ref<ElFormInstance>();
const monthlyFormRef = ref<ComponentRef<typeof MonthlyForm>>();
const monthlyDetailRef = ref<ComponentRef<typeof MonthlyDetail>>();
/** 查询月度总结列表 */
const getList = async () => {
loading.value = true
const res = await listInternshipMonthly(queryParams.value)
monthlyList.value = res.rows
total.value = res.total
loading.value = false
}
loading.value = true;
const res = await listInternshipMonthly(queryParams.value);
monthlyList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: any) => {
ids.value = selection.map((item: any) => item.monthlyId)
single.value = selection.length !== 1
multiple.value = !selection.length
}
ids.value = selection.map((item: any) => item.monthlyId);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
dialog.visible = true
dialog.title = '添加月度总结'
dialog.visible = true;
dialog.title = '添加月度总结';
nextTick(() => {
monthlyFormRef.value?.resetForm()
})
}
monthlyFormRef.value?.resetForm();
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: any) => {
dialog.visible = true
dialog.title = '修改月度总结'
dialog.visible = true;
dialog.title = '修改月度总结';
nextTick(() => {
const monthlyId = row?.monthlyId || ids.value[0]
monthlyFormRef.value?.resetForm(monthlyId)
})
}
const monthlyId = row?.monthlyId || ids.value[0];
monthlyFormRef.value?.resetForm(monthlyId);
});
};
/** 查看详情操作 */
const handleDetail = async (row: any) => {
detailDialog.visible = true
detailDialog.visible = true;
nextTick(() => {
monthlyDetailRef.value?.resetForm(row.monthlyId)
})
}
monthlyDetailRef.value?.resetForm(row.monthlyId);
});
};
/** 提交审批操作 */
const handleSubmit = async (row: any) => {
try {
await proxy?.$modal.confirm('确认提交该月度总结?提交后将无法再次编辑')
await submitInternshipMonthly(row.monthlyId)
await getList()
proxy?.$modal.msgSuccess('提交成功')
await proxy?.$modal.confirm('确认提交该月度总结?提交后将无法再次编辑');
await submitInternshipMonthly(row.monthlyId);
await getList();
proxy?.$modal.msgSuccess('提交成功');
} catch {}
}
};
/** 删除按钮操作 */
const handleDelete = async (row?: any) => {
const monthlyIds = row?.monthlyId || ids.value
const monthlyIds = row?.monthlyId || ids.value;
try {
await proxy?.$modal.confirm('是否确认删除月度总结编号为"' + monthlyIds + '"的数据项?')
await delInternshipMonthly(monthlyIds)
await getList()
proxy?.$modal.msgSuccess('删除成功')
await proxy?.$modal.confirm('是否确认删除月度总结编号为"' + monthlyIds + '"的数据项?');
await delInternshipMonthly(monthlyIds);
await getList();
proxy?.$modal.msgSuccess('删除成功');
} catch {}
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.$download('zsjy/internship/monthly/export', {
...queryParams.value
}, `月度总结_${new Date().getTime()}.xlsx`)
}
proxy?.$download(
'zsjy/internship/monthly/export',
{
...queryParams.value
},
`月度总结_${new Date().getTime()}.xlsx`
);
};
/** 计算五项技能的平均分 */
const getAverageScore = (row: any) => {
@@ -325,23 +293,23 @@ const getAverageScore = (row: any) => {
row.teamworkAbility || 0,
row.learningAbility || 0,
row.workAttitude || 0
].filter(score => score > 0)
if (scores.length === 0) return 0
return scores.reduce((sum, score) => sum + score, 0) / scores.length
}
].filter((score) => score > 0);
if (scores.length === 0) return 0;
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
};
/** 根据分数获取标签类型 */
const getScoreTagType = (score: number) => {
if (score >= 9) return 'success'
if (score >= 7) return 'warning'
if (score >= 5) return ''
return 'danger'
}
if (score >= 9) return 'success';
if (score >= 7) return 'warning';
if (score >= 5) return '';
return 'danger';
};
onMounted(() => {
getList()
})
getList();
});
</script>
<style scoped>
+156 -219
View File
@@ -14,20 +14,10 @@
/>
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input
v-model="queryParams.studentName"
placeholder="请输入学生姓名"
clearable
style="width: 150px"
/>
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="学号" prop="studentNumber">
<el-input
v-model="queryParams.studentNumber"
placeholder="请输入学号"
clearable
style="width: 150px"
/>
<el-input v-model="queryParams.studentNumber" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="审核状态" prop="reviewStatus">
<el-select v-model="queryParams.reviewStatus" placeholder="请选择状态" clearable style="width: 150px">
@@ -84,34 +74,14 @@
<div class="table-header">
<div class="table-title">月度总结审核</div>
<div class="table-actions">
<el-button
type="success"
icon="Check"
:disabled="!selections.length"
@click="handleBatchApprove"
>
批量通过
</el-button>
<el-button
type="warning"
icon="Close"
:disabled="!selections.length"
@click="handleBatchReject"
>
批量拒绝
</el-button>
<el-button type="success" icon="Check" :disabled="!selections.length" @click="handleBatchApprove"> 批量通过 </el-button>
<el-button type="warning" icon="Close" :disabled="!selections.length" @click="handleBatchReject"> 批量拒绝 </el-button>
<el-button type="info" icon="Download" @click="handleExport">导出</el-button>
</div>
</div>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="reviewList"
@selection-change="handleSelectionChange"
border
stripe
>
<el-table v-loading="loading" :data="reviewList" @selection-change="handleSelectionChange" border stripe>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="学生信息" width="200" fixed="left">
<template #default="scope">
@@ -125,10 +95,7 @@
<el-table-column prop="monthNumber" label="第几个月" width="100" align="center" />
<el-table-column label="状态" width="100" align="center">
<template #default="scope">
<el-tag
:type="getStatusType(scope.row.reviewStatus)"
size="small"
>
<el-tag :type="getStatusType(scope.row.reviewStatus)" size="small">
{{ getStatusText(scope.row.reviewStatus) }}
</el-tag>
</template>
@@ -146,48 +113,19 @@
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" link size="small" @click="handleDetail(scope.row)">
查看详情
</el-button>
<el-button
v-if="scope.row.reviewStatus === 0"
type="success"
link
size="small"
@click="handleApprove(scope.row)"
>
审核
</el-button>
<el-button
v-if="scope.row.reviewStatus !== 0"
type="warning"
link
size="small"
@click="handleReReview(scope.row)"
>
重新审核
</el-button>
<el-button type="primary" link size="small" @click="handleDetail(scope.row)"> 查看详情 </el-button>
<el-button v-if="scope.row.reviewStatus === 0" type="success" link size="small" @click="handleApprove(scope.row)"> 审核 </el-button>
<el-button v-if="scope.row.reviewStatus !== 0" type="warning" link size="small" @click="handleReReview(scope.row)"> 重新审核 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 审核对话框 -->
<el-dialog
:title="reviewForm.monthlyId ? '审核月度总结' : '批量审核'"
v-model="reviewVisible"
width="600px"
:close-on-click-modal="false"
>
<el-dialog :title="reviewForm.monthlyId ? '审核月度总结' : '批量审核'" v-model="reviewVisible" width="600px" :close-on-click-modal="false">
<el-form :model="reviewForm" :rules="reviewRules" ref="reviewFormRef" label-width="100px">
<el-form-item label="审核结果" prop="reviewStatus">
<el-radio-group v-model="reviewForm.reviewStatus">
@@ -202,25 +140,11 @@
</el-radio-group>
</el-form-item>
<el-form-item label="评分" prop="reviewScore">
<el-input-number
v-model="reviewForm.reviewScore"
:min="0"
:max="100"
:precision="1"
placeholder="请输入评分(0-100)"
style="width: 200px"
/>
<el-input-number v-model="reviewForm.reviewScore" :min="0" :max="100" :precision="1" placeholder="请输入评分(0-100)" style="width: 200px" />
<span class="ml-2 text-gray-500">分</span>
</el-form-item>
<el-form-item label="审核意见" prop="reviewComment">
<el-input
v-model="reviewForm.reviewComment"
type="textarea"
:rows="4"
placeholder="请输入审核意见"
maxlength="500"
show-word-limit
/>
<el-input v-model="reviewForm.reviewComment" type="textarea" :rows="4" placeholder="请输入审核意见" maxlength="500" show-word-limit />
</el-form-item>
<el-form-item label="改进建议" prop="reviewSuggestions">
<el-input
@@ -240,19 +164,14 @@
</el-dialog>
<!-- 详情对话框 -->
<monthly-detail
v-if="detailVisible"
:monthly-id="currentMonthlyId"
:visible="detailVisible"
@close="detailVisible = false"
/>
<monthly-detail v-if="detailVisible" :monthly-id="currentMonthlyId" :visible="detailVisible" @close="detailVisible = false" />
</div>
</template>
<script setup lang="ts" name="MonthlyReview">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Check, Close, Search, Refresh, Download } from '@element-plus/icons-vue'
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Check, Close, Search, Refresh, Download } from '@element-plus/icons-vue';
import {
getPendingReviewList,
getReviewList,
@@ -264,18 +183,18 @@ import {
type MonthlyReviewParams,
type BatchReviewParams,
type ReviewStatistics
} from '@/api/zsjy/internshipMonthlyReview'
import MonthlyDetail from './detail.vue'
} from '@/api/zsjy/internshipMonthlyReview';
import MonthlyDetail from './detail.vue';
// 响应式数据
const loading = ref(false)
const reviewLoading = ref(false)
const reviewVisible = ref(false)
const detailVisible = ref(false)
const currentMonthlyId = ref<number | null>(null)
const selections = ref<any[]>([])
const total = ref(0)
const reviewList = ref<any[]>([])
const loading = ref(false);
const reviewLoading = ref(false);
const reviewVisible = ref(false);
const detailVisible = ref(false);
const currentMonthlyId = ref<number | null>(null);
const selections = ref<any[]>([]);
const total = ref(0);
const reviewList = ref<any[]>([]);
// 查询参数
const queryParams = reactive<MonthlyReviewQueryParams & { pageNum: number; pageSize: number }>({
@@ -285,7 +204,7 @@ const queryParams = reactive<MonthlyReviewQueryParams & { pageNum: number; pageS
studentName: '',
studentNumber: '',
reviewStatus: undefined
})
});
// 统计数据
const statistics = ref<ReviewStatistics>({
@@ -295,7 +214,7 @@ const statistics = ref<ReviewStatistics>({
rejectedCount: 0,
averageScore: 0,
completionRate: 0
})
});
// 审核表单
const reviewForm = reactive<MonthlyReviewParams & { isBatch?: boolean; monthlyIds?: number[] }>({
@@ -306,143 +225,143 @@ const reviewForm = reactive<MonthlyReviewParams & { isBatch?: boolean; monthlyId
reviewSuggestions: '',
isBatch: false,
monthlyIds: []
})
});
// 表单校验规则
const reviewRules = {
reviewStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
reviewComment: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
}
};
// ref引用
const queryFormRef = ref()
const reviewFormRef = ref()
const queryFormRef = ref();
const reviewFormRef = ref();
// 获取列表数据
const getList = async () => {
loading.value = true
loading.value = true;
try {
const response = await getReviewList(queryParams)
reviewList.value = response.rows
total.value = response.total
const response = await getReviewList(queryParams);
reviewList.value = response.rows;
total.value = response.total;
} catch (error) {
console.error('获取审核列表失败:', error)
console.error('获取审核列表失败:', error);
} finally {
loading.value = false
loading.value = false;
}
}
};
// 获取统计数据
const getStatistics = async () => {
try {
const response = await getReviewStatistics(queryParams)
statistics.value = response.data
const response = await getReviewStatistics(queryParams);
statistics.value = response.data;
} catch (error) {
console.error('获取统计数据失败:', error)
console.error('获取统计数据失败:', error);
}
}
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1
getList()
getStatistics()
}
queryParams.pageNum = 1;
getList();
getStatistics();
};
// 重置搜索
const resetQuery = () => {
queryFormRef.value?.resetFields()
queryParams.pageNum = 1
getList()
getStatistics()
}
queryFormRef.value?.resetFields();
queryParams.pageNum = 1;
getList();
getStatistics();
};
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selections.value = selection
}
selections.value = selection;
};
// 查看详情
const handleDetail = (row: any) => {
currentMonthlyId.value = row.monthlyId
detailVisible.value = true
}
currentMonthlyId.value = row.monthlyId;
detailVisible.value = true;
};
// 单个审核
const handleApprove = (row: any) => {
reviewForm.monthlyId = row.monthlyId
reviewForm.isBatch = false
reviewForm.reviewStatus = 1
reviewForm.reviewScore = undefined
reviewForm.reviewComment = ''
reviewForm.reviewSuggestions = ''
reviewVisible.value = true
}
reviewForm.monthlyId = row.monthlyId;
reviewForm.isBatch = false;
reviewForm.reviewStatus = 1;
reviewForm.reviewScore = undefined;
reviewForm.reviewComment = '';
reviewForm.reviewSuggestions = '';
reviewVisible.value = true;
};
// 重新审核
const handleReReview = (row: any) => {
reviewForm.monthlyId = row.monthlyId
reviewForm.isBatch = false
reviewForm.reviewStatus = row.reviewStatus
reviewForm.reviewScore = row.reviewScore
reviewForm.reviewComment = row.reviewComment || ''
reviewForm.reviewSuggestions = row.reviewSuggestions || ''
reviewVisible.value = true
}
reviewForm.monthlyId = row.monthlyId;
reviewForm.isBatch = false;
reviewForm.reviewStatus = row.reviewStatus;
reviewForm.reviewScore = row.reviewScore;
reviewForm.reviewComment = row.reviewComment || '';
reviewForm.reviewSuggestions = row.reviewSuggestions || '';
reviewVisible.value = true;
};
// 批量通过
const handleBatchApprove = () => {
const pendingItems = selections.value.filter(item => item.reviewStatus === 0)
const pendingItems = selections.value.filter((item) => item.reviewStatus === 0);
if (pendingItems.length === 0) {
ElMessage.warning('请选择待审核的记录')
return
ElMessage.warning('请选择待审核的记录');
return;
}
reviewForm.isBatch = true
reviewForm.reviewStatus = 1
reviewForm.reviewScore = 85 // 默认分数
reviewForm.reviewComment = ''
reviewForm.reviewSuggestions = ''
reviewForm.monthlyIds = pendingItems.map(item => item.monthlyId)
reviewVisible.value = true
}
reviewForm.isBatch = true;
reviewForm.reviewStatus = 1;
reviewForm.reviewScore = 85; // 默认分数
reviewForm.reviewComment = '';
reviewForm.reviewSuggestions = '';
reviewForm.monthlyIds = pendingItems.map((item) => item.monthlyId);
reviewVisible.value = true;
};
// 批量拒绝
const handleBatchReject = () => {
const pendingItems = selections.value.filter(item => item.reviewStatus === 0)
const pendingItems = selections.value.filter((item) => item.reviewStatus === 0);
if (pendingItems.length === 0) {
ElMessage.warning('请选择待审核的记录')
return
ElMessage.warning('请选择待审核的记录');
return;
}
reviewForm.isBatch = true
reviewForm.reviewStatus = 2
reviewForm.reviewScore = undefined
reviewForm.reviewComment = ''
reviewForm.reviewSuggestions = ''
reviewForm.monthlyIds = pendingItems.map(item => item.monthlyId)
reviewVisible.value = true
}
reviewForm.isBatch = true;
reviewForm.reviewStatus = 2;
reviewForm.reviewScore = undefined;
reviewForm.reviewComment = '';
reviewForm.reviewSuggestions = '';
reviewForm.monthlyIds = pendingItems.map((item) => item.monthlyId);
reviewVisible.value = true;
};
// 提交审核
const submitReview = async () => {
try {
await reviewFormRef.value?.validate()
reviewLoading.value = true
await reviewFormRef.value?.validate();
reviewLoading.value = true;
if (reviewForm.isBatch) {
// 批量审核
const batchData: BatchReviewParams = {
reviewItems: reviewForm.monthlyIds!.map(monthlyId => ({
reviewItems: reviewForm.monthlyIds!.map((monthlyId) => ({
monthlyId,
reviewStatus: reviewForm.reviewStatus,
reviewScore: reviewForm.reviewScore,
reviewComment: reviewForm.reviewComment,
reviewSuggestions: reviewForm.reviewSuggestions
}))
}
await batchApproveMonthly(batchData)
ElMessage.success('批量审核成功')
};
await batchApproveMonthly(batchData);
ElMessage.success('批量审核成功');
} else {
// 单个审核
await approveMonthly({
@@ -451,29 +370,29 @@ const submitReview = async () => {
reviewScore: reviewForm.reviewScore,
reviewComment: reviewForm.reviewComment,
reviewSuggestions: reviewForm.reviewSuggestions
})
ElMessage.success('审核成功')
});
ElMessage.success('审核成功');
}
reviewVisible.value = false
getList()
getStatistics()
reviewVisible.value = false;
getList();
getStatistics();
} catch (error) {
console.error('审核失败:', error)
console.error('审核失败:', error);
} finally {
reviewLoading.value = false
reviewLoading.value = false;
}
}
};
// 导出数据
const handleExport = async () => {
try {
await exportReviewData(queryParams)
ElMessage.success('导出成功')
await exportReviewData(queryParams);
ElMessage.success('导出成功');
} catch (error) {
console.error('导出失败:', error)
console.error('导出失败:', error);
}
}
};
// 获取状态类型
const getStatusType = (status: number) => {
@@ -481,9 +400,9 @@ const getStatusType = (status: number) => {
0: 'warning',
1: 'success',
2: 'danger'
}
return statusMap[status] || 'info'
}
};
return statusMap[status] || 'info';
};
// 获取状态文本
const getStatusText = (status: number) => {
@@ -491,15 +410,15 @@ const getStatusText = (status: number) => {
0: '待审核',
1: '已审核',
2: '审核拒绝'
}
return statusMap[status] || '未知'
}
};
return statusMap[status] || '未知';
};
// 组件挂载
onMounted(() => {
getList()
getStatistics()
})
getList();
getStatistics();
});
</script>
<style scoped lang="scss">
@@ -527,20 +446,38 @@ onMounted(() => {
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.table-title {
font-size: 16px;
font-weight: bold;
}
}
.text-primary { color: #409eff; }
.text-success { color: #67c23a; }
.text-warning { color: #e6a23c; }
.text-danger { color: #f56c6c; }
.text-gray-500 { color: #9ca3af; }
.font-bold { font-weight: bold; }
.text-sm { font-size: 12px; }
.mb-4 { margin-bottom: 16px; }
.ml-2 { margin-left: 8px; }
</style>
.text-primary {
color: #409eff;
}
.text-success {
color: #67c23a;
}
.text-warning {
color: #e6a23c;
}
.text-danger {
color: #f56c6c;
}
.text-gray-500 {
color: #9ca3af;
}
.font-bold {
font-weight: bold;
}
.text-sm {
font-size: 12px;
}
.mb-4 {
margin-bottom: 16px;
}
.ml-2 {
margin-left: 8px;
}
</style>
@@ -22,26 +22,13 @@
</el-col>
<el-col :span="12">
<el-form-item label="统一评分">
<el-input-number
v-model="batchForm.reviewScore"
:min="0"
:max="100"
:step="5"
style="width: 100%"
/>
<el-input-number v-model="batchForm.reviewScore" :min="0" :max="100" :step="5" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="统一审核意见">
<el-input
v-model="batchForm.reviewComment"
type="textarea"
:rows="3"
placeholder="输入统一的审核意见"
maxlength="500"
show-word-limit
/>
<el-input v-model="batchForm.reviewComment" type="textarea" :rows="3" placeholder="输入统一的审核意见" maxlength="500" show-word-limit />
</el-form-item>
<el-form-item label="统一改进建议">
@@ -100,13 +87,7 @@
</el-col>
<el-col :span="8">
<el-form-item label="审核评分" :prop="`reviewItems.${index}.reviewScore`">
<el-input-number
v-model="item.reviewScore"
:min="0"
:max="100"
:step="5"
style="width: 100%"
/>
<el-input-number v-model="item.reviewScore" :min="0" :max="100" :step="5" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -119,25 +100,11 @@
</el-row>
<el-form-item label="审核意见" :prop="`reviewItems.${index}.reviewComment`" required>
<el-input
v-model="item.reviewComment"
type="textarea"
:rows="2"
placeholder="请输入审核意见"
maxlength="500"
show-word-limit
/>
<el-input v-model="item.reviewComment" type="textarea" :rows="2" placeholder="请输入审核意见" maxlength="500" show-word-limit />
</el-form-item>
<el-form-item label="改进建议" :prop="`reviewItems.${index}.reviewSuggestions`">
<el-input
v-model="item.reviewSuggestions"
type="textarea"
:rows="2"
placeholder="请输入改进建议"
maxlength="300"
show-word-limit
/>
<el-input v-model="item.reviewSuggestions" type="textarea" :rows="2" placeholder="请输入改进建议" maxlength="300" show-word-limit />
</el-form-item>
<!-- 快速操作 -->
@@ -167,25 +134,21 @@
</template>
<script setup lang="ts">
import { ref, reactive, defineProps, defineEmits } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
SuccessFilled,
CircleCloseFilled,
Check
} from '@element-plus/icons-vue'
import { batchReviewInternshipMonthly } from '@/api/zsjy/internshipMonthlyReview'
import { ref, reactive, defineProps, defineEmits } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { SuccessFilled, CircleCloseFilled, Check } from '@element-plus/icons-vue';
import { batchReviewInternshipMonthly } from '@/api/zsjy/internshipMonthlyReview';
interface Props {
selectedRows: any[]
selectedRows: any[];
}
const props = defineProps<Props>()
const emit = defineEmits(['refresh'])
const props = defineProps<Props>();
const emit = defineEmits(['refresh']);
// 状态管理
const submitting = ref(false)
const showBatchSetting = ref(true)
const submitting = ref(false);
const showBatchSetting = ref(true);
// 批量设置表单
const batchForm = reactive({
@@ -193,36 +156,38 @@ const batchForm = reactive({
reviewScore: 85,
reviewComment: '',
reviewSuggestions: ''
})
});
// 审核项目列表
const reviewItems = ref(props.selectedRows.map(row => ({
monthlyId: row.monthlyId,
studentName: row.studentName,
studentNumber: row.studentNumber,
summaryMonth: row.summaryMonth,
className: row.className,
reviewStatus: 1,
reviewScore: 85,
reviewComment: '',
reviewSuggestions: ''
})))
const reviewItems = ref(
props.selectedRows.map((row) => ({
monthlyId: row.monthlyId,
studentName: row.studentName,
studentNumber: row.studentNumber,
summaryMonth: row.summaryMonth,
className: row.className,
reviewStatus: 1,
reviewScore: 85,
reviewComment: '',
reviewSuggestions: ''
}))
);
/** 获取分数等级 */
function getScoreLevel(score: number) {
if (score >= 90) return '优秀'
if (score >= 80) return '良好'
if (score >= 70) return '中等'
if (score >= 60) return '及格'
return '不及格'
if (score >= 90) return '优秀';
if (score >= 80) return '良好';
if (score >= 70) return '中等';
if (score >= 60) return '及格';
return '不及格';
}
/** 获取分数标签类型 */
function getScoreTagType(score: number) {
if (score >= 90) return 'success'
if (score >= 80) return 'warning'
if (score >= 70) return 'primary'
return 'danger'
if (score >= 90) return 'success';
if (score >= 80) return 'warning';
if (score >= 70) return 'primary';
return 'danger';
}
/** 应用批量设置 */
@@ -232,60 +197,60 @@ function applyBatchSettings() {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
reviewItems.value.forEach(item => {
reviewItems.value.forEach((item) => {
if (batchForm.reviewStatus) {
item.reviewStatus = batchForm.reviewStatus
item.reviewStatus = batchForm.reviewStatus;
}
if (batchForm.reviewScore !== null) {
item.reviewScore = batchForm.reviewScore
item.reviewScore = batchForm.reviewScore;
}
if (batchForm.reviewComment) {
item.reviewComment = batchForm.reviewComment
item.reviewComment = batchForm.reviewComment;
}
if (batchForm.reviewSuggestions) {
item.reviewSuggestions = batchForm.reviewSuggestions
item.reviewSuggestions = batchForm.reviewSuggestions;
}
})
ElMessage.success('批量设置已应用')
})
});
ElMessage.success('批量设置已应用');
});
}
/** 清空批量设置 */
function clearBatchSettings() {
batchForm.reviewStatus = 1
batchForm.reviewScore = 85
batchForm.reviewComment = ''
batchForm.reviewSuggestions = ''
batchForm.reviewStatus = 1;
batchForm.reviewScore = 85;
batchForm.reviewComment = '';
batchForm.reviewSuggestions = '';
}
/** 快速设置审核结果 */
function setQuickReview(index: number, status: number, score: number, comment: string) {
const item = reviewItems.value[index]
item.reviewStatus = status
item.reviewScore = score
item.reviewComment = comment
const item = reviewItems.value[index];
item.reviewStatus = status;
item.reviewScore = score;
item.reviewComment = comment;
}
/** 验证表单 */
function validateForm() {
for (let i = 0; i < reviewItems.value.length; i++) {
const item = reviewItems.value[i]
const item = reviewItems.value[i];
if (!item.reviewStatus) {
ElMessage.error(`请为 ${item.studentName} 选择审核结果`)
return false
ElMessage.error(`请为 ${item.studentName} 选择审核结果`);
return false;
}
if (!item.reviewComment || item.reviewComment.trim().length < 10) {
ElMessage.error(`请为 ${item.studentName} 输入审核意见(至少10个字符)`)
return false
ElMessage.error(`请为 ${item.studentName} 输入审核意见(至少10个字符)`);
return false;
}
}
return true
return true;
}
/** 提交批量审核 */
function submitBatchReview() {
if (!validateForm()) {
return
return;
}
ElMessageBox.confirm(`确认批量审核 ${reviewItems.value.length} 条记录?`, '确认批量审核', {
@@ -293,32 +258,35 @@ function submitBatchReview() {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
submitting.value = true
submitting.value = true;
const reviewData = {
reviewItems: reviewItems.value.map(item => ({
reviewItems: reviewItems.value.map((item) => ({
monthlyId: item.monthlyId,
reviewStatus: item.reviewStatus,
reviewScore: item.reviewScore,
reviewComment: item.reviewComment,
reviewSuggestions: item.reviewSuggestions
}))
}
};
batchReviewInternshipMonthly(reviewData).then(() => {
ElMessage.success('批量审核完成')
emit('refresh')
}).catch(() => {
ElMessage.error('批量审核失败')
}).finally(() => {
submitting.value = false
})
})
batchReviewInternshipMonthly(reviewData)
.then(() => {
ElMessage.success('批量审核完成');
emit('refresh');
})
.catch(() => {
ElMessage.error('批量审核失败');
})
.finally(() => {
submitting.value = false;
});
});
}
/** 取消操作 */
function handleCancel() {
emit('refresh')
emit('refresh');
}
</script>
@@ -67,69 +67,42 @@
<el-col :span="12">
<div class="score-item">
<span class="score-label">专业技能:</span>
<el-rate
:model-value="monthlyData.professionalSkills"
disabled
show-score
text-color="#ff9900"
/>
<el-rate :model-value="monthlyData.professionalSkills" disabled show-score text-color="#ff9900" />
<span class="score-value">{{ monthlyData.professionalSkills || 0 }}分</span>
</div>
</el-col>
<el-col :span="12">
<div class="score-item">
<span class="score-label">沟通能力:</span>
<el-rate
:model-value="monthlyData.communicationSkills"
disabled
show-score
text-color="#ff9900"
/>
<el-rate :model-value="monthlyData.communicationSkills" disabled show-score text-color="#ff9900" />
<span class="score-value">{{ monthlyData.communicationSkills || 0 }}分</span>
</div>
</el-col>
<el-col :span="12">
<div class="score-item">
<span class="score-label">团队协作:</span>
<el-rate
:model-value="monthlyData.teamworkAbility"
disabled
show-score
text-color="#ff9900"
/>
<el-rate :model-value="monthlyData.teamworkAbility" disabled show-score text-color="#ff9900" />
<span class="score-value">{{ monthlyData.teamworkAbility || 0 }}分</span>
</div>
</el-col>
<el-col :span="12">
<div class="score-item">
<span class="score-label">学习能力:</span>
<el-rate
:model-value="monthlyData.learningAbility"
disabled
show-score
text-color="#ff9900"
/>
<el-rate :model-value="monthlyData.learningAbility" disabled show-score text-color="#ff9900" />
<span class="score-value">{{ monthlyData.learningAbility || 0 }}分</span>
</div>
</el-col>
<el-col :span="12">
<div class="score-item">
<span class="score-label">工作态度:</span>
<el-rate
:model-value="monthlyData.workAttitude"
disabled
show-score
text-color="#ff9900"
/>
<el-rate :model-value="monthlyData.workAttitude" disabled show-score text-color="#ff9900" />
<span class="score-value">{{ monthlyData.workAttitude || 0 }}分</span>
</div>
</el-col>
<el-col :span="12">
<div class="score-item">
<span class="score-label">综合评分:</span>
<el-tag :type="getScoreTagType(getAverageScore())" size="large">
{{ getAverageScore().toFixed(1) }}分
</el-tag>
<el-tag :type="getScoreTagType(getAverageScore())" size="large"> {{ getAverageScore().toFixed(1) }}分 </el-tag>
</div>
</el-col>
</el-row>
@@ -147,8 +120,10 @@
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核评分">
<el-tag v-if="monthlyData.reviewScore !== null && monthlyData.reviewScore !== undefined"
:type="getReviewScoreTagType(monthlyData.reviewScore)">
<el-tag
v-if="monthlyData.reviewScore !== null && monthlyData.reviewScore !== undefined"
:type="getReviewScoreTagType(monthlyData.reviewScore)"
>
{{ monthlyData.reviewScore }}分
</el-tag>
<span v-else class="text-gray-400">未评分</span>
@@ -178,14 +153,14 @@
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
import { defineProps, defineEmits } from 'vue';
interface Props {
monthlyData: any
monthlyData: any;
}
const props = defineProps<Props>()
const emit = defineEmits(['refresh', 'review'])
const props = defineProps<Props>();
const emit = defineEmits(['refresh', 'review']);
/** 获取平均分 */
function getAverageScore() {
@@ -195,50 +170,58 @@ function getAverageScore() {
props.monthlyData.teamworkAbility || 0,
props.monthlyData.learningAbility || 0,
props.monthlyData.workAttitude || 0
]
const total = scores.reduce((sum, score) => sum + score, 0)
return total / scores.length
];
const total = scores.reduce((sum, score) => sum + score, 0);
return total / scores.length;
}
/** 获取评分标签类型 */
function getScoreTagType(score: number) {
if (score >= 9) return 'success'
if (score >= 8) return 'warning'
if (score >= 7) return 'primary'
return 'danger'
if (score >= 9) return 'success';
if (score >= 8) return 'warning';
if (score >= 7) return 'primary';
return 'danger';
}
/** 获取审核状态文本 */
function getReviewStatusText(status: number) {
switch (status) {
case 0: return '待审核'
case 1: return '已通过'
case 2: return '已拒绝'
default: return '未知'
case 0:
return '待审核';
case 1:
return '已通过';
case 2:
return '已拒绝';
default:
return '未知';
}
}
/** 获取审核状态标签类型 */
function getReviewStatusTagType(status: number) {
switch (status) {
case 0: return 'warning'
case 1: return 'success'
case 2: return 'danger'
default: return 'info'
case 0:
return 'warning';
case 1:
return 'success';
case 2:
return 'danger';
default:
return 'info';
}
}
/** 获取审核评分标签类型 */
function getReviewScoreTagType(score: number) {
if (score >= 90) return 'success'
if (score >= 80) return 'warning'
if (score >= 70) return 'primary'
return 'danger'
if (score >= 90) return 'success';
if (score >= 80) return 'warning';
if (score >= 70) return 'primary';
return 'danger';
}
/** 审核按钮点击 */
function handleReview() {
emit('review', props.monthlyData)
emit('review', props.monthlyData);
}
</script>
@@ -145,27 +145,20 @@
</template>
<script setup lang="ts">
import { ref, reactive, defineProps, defineEmits } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
SuccessFilled,
CircleCloseFilled,
InfoFilled,
Check,
Refresh,
View
} from '@element-plus/icons-vue'
import { reviewInternshipMonthly } from '@/api/zsjy/internshipMonthlyReview'
import { ref, reactive, defineProps, defineEmits } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { SuccessFilled, CircleCloseFilled, InfoFilled, Check, Refresh, View } from '@element-plus/icons-vue';
import { reviewInternshipMonthly } from '@/api/zsjy/internshipMonthlyReview';
interface Props {
monthlyData: any
monthlyData: any;
}
const props = defineProps<Props>()
const emit = defineEmits(['refresh'])
const props = defineProps<Props>();
const emit = defineEmits(['refresh']);
// 表单引用
const reviewFormRef = ref()
const reviewFormRef = ref();
// 表单数据
const reviewForm = reactive({
@@ -174,13 +167,11 @@ const reviewForm = reactive({
reviewScore: 85, // 默认85分
reviewComment: '',
reviewSuggestions: ''
})
});
// 表单验证规则
const reviewRules = {
reviewStatus: [
{ required: true, message: '请选择审核结果', trigger: 'change' }
],
reviewStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
reviewScore: [
{ required: true, message: '请输入审核评分', trigger: 'blur' },
{ type: 'number', min: 0, max: 100, message: '评分范围为0-100分', trigger: 'blur' }
@@ -189,14 +180,12 @@ const reviewRules = {
{ required: true, message: '请输入审核意见', trigger: 'blur' },
{ min: 10, max: 500, message: '审核意见长度在10到500个字符', trigger: 'blur' }
],
reviewSuggestions: [
{ max: 300, message: '改进建议长度不能超过300个字符', trigger: 'blur' }
]
}
reviewSuggestions: [{ max: 300, message: '改进建议长度不能超过300个字符', trigger: 'blur' }]
};
// 状态管理
const submitting = ref(false)
const previewOpen = ref(false)
const submitting = ref(false);
const previewOpen = ref(false);
// 评语模板
const commentTemplates = [
@@ -220,86 +209,89 @@ const commentTemplates = [
label: '需改进',
content: '总结内容过于简单,缺乏具体的工作描述和深入的思考。请重新整理总结内容,确保内容的完整性和深度。'
}
]
];
/** 格式化滑块提示 */
function formatTooltip(value: number) {
return `${value}分`
return `${value}分`;
}
/** 获取分数等级 */
function getScoreLevel(score: number) {
if (score >= 90) return '优秀'
if (score >= 80) return '良好'
if (score >= 70) return '中等'
if (score >= 60) return '及格'
return '不及格'
if (score >= 90) return '优秀';
if (score >= 80) return '良好';
if (score >= 70) return '中等';
if (score >= 60) return '及格';
return '不及格';
}
/** 获取分数标签类型 */
function getScoreTagType(score: number) {
if (score >= 90) return 'success'
if (score >= 80) return 'warning'
if (score >= 70) return 'primary'
return 'danger'
if (score >= 90) return 'success';
if (score >= 80) return 'warning';
if (score >= 70) return 'primary';
return 'danger';
}
/** 插入评语模板 */
function insertTemplate(template: string) {
if (reviewForm.reviewComment) {
reviewForm.reviewComment += '\n\n' + template
reviewForm.reviewComment += '\n\n' + template;
} else {
reviewForm.reviewComment = template
reviewForm.reviewComment = template;
}
}
/** 重置表单 */
function resetForm() {
reviewFormRef.value?.resetFields()
reviewForm.reviewStatus = 1
reviewForm.reviewScore = 85
reviewForm.reviewComment = ''
reviewForm.reviewSuggestions = ''
reviewFormRef.value?.resetFields();
reviewForm.reviewStatus = 1;
reviewForm.reviewScore = 85;
reviewForm.reviewComment = '';
reviewForm.reviewSuggestions = '';
}
/** 预览审核 */
function previewReview() {
reviewFormRef.value?.validate((valid: boolean) => {
if (valid) {
previewOpen.value = true
previewOpen.value = true;
}
})
});
}
/** 提交审核 */
function submitReview() {
reviewFormRef.value?.validate((valid: boolean) => {
if (valid) {
const message = reviewForm.reviewStatus === 1 ? '确认通过此月度总结?' : '确认拒绝此月度总结?'
const message = reviewForm.reviewStatus === 1 ? '确认通过此月度总结?' : '确认拒绝此月度总结?';
ElMessageBox.confirm(message, '确认审核', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
confirmSubmit()
})
confirmSubmit();
});
}
})
});
}
/** 确认提交 */
function confirmSubmit() {
submitting.value = true
previewOpen.value = false
submitting.value = true;
previewOpen.value = false;
reviewInternshipMonthly(reviewForm).then(() => {
ElMessage.success('审核完成')
emit('refresh')
}).catch(() => {
ElMessage.error('审核失败')
}).finally(() => {
submitting.value = false
})
reviewInternshipMonthly(reviewForm)
.then(() => {
ElMessage.success('审核完成');
emit('refresh');
})
.catch(() => {
ElMessage.error('审核失败');
})
.finally(() => {
submitting.value = false;
});
}
</script>
@@ -138,7 +138,8 @@
:disabled="multiple"
@click="handleBatchReview"
v-hasPermi="['zsjy:internshipmonthly:review']"
>批量审核</el-button>
>批量审核</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -165,16 +166,12 @@
<el-table-column label="自我评分" align="center" width="120">
<template #default="scope">
<el-tooltip content="专业/沟通/团队/学习/态度" placement="top">
<el-tag :type="getScoreTagType(getAverageScore(scope.row))">
{{ getAverageScore(scope.row).toFixed(1) }}分
</el-tag>
<el-tag :type="getScoreTagType(getAverageScore(scope.row))"> {{ getAverageScore(scope.row).toFixed(1) }}分 </el-tag>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="字数统计" align="center" width="100">
<template #default="scope">
{{ scope.row.wordCount || 0 }}字
</template>
<template #default="scope"> {{ scope.row.wordCount || 0 }}字 </template>
</el-table-column>
<el-table-column label="审核状态" align="center" prop="reviewStatus" width="100">
<template #default="scope">
@@ -186,9 +183,7 @@
<el-table-column label="审核评分" align="center" prop="reviewScore" width="100">
<template #default="scope">
<span v-if="scope.row.reviewScore !== null && scope.row.reviewScore !== undefined">
<el-tag :type="getReviewScoreTagType(scope.row.reviewScore)">
{{ scope.row.reviewScore }}分
</el-tag>
<el-tag :type="getReviewScoreTagType(scope.row.reviewScore)"> {{ scope.row.reviewScore }}分 </el-tag>
</span>
<span v-else class="text-gray-400">未评分</span>
</template>
@@ -205,19 +200,14 @@
icon="Check"
@click="handleReview(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:review']"
>审核</el-button>
>审核</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 审核详情对话框 -->
@@ -238,25 +228,16 @@
</template>
<script setup name="InternshipMonthlyReview" lang="ts">
import { onMounted, reactive, ref, toRefs, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
DocumentChecked,
Clock,
SuccessFilled,
CircleCloseFilled,
Search,
Refresh,
Check,
View
} from '@element-plus/icons-vue'
import { getReviewList, getReviewStatistics } from '@/api/zsjy/internshipMonthlyReview'
import type { InternshipMonthlyVO, InternshipMonthlyReviewStatisticsVO } from '@/api/zsjy/types'
import ReviewDetail from './components/ReviewDetail.vue'
import ReviewForm from './components/ReviewForm.vue'
import BatchReviewForm from './components/BatchReviewForm.vue'
import { onMounted, reactive, ref, toRefs, getCurrentInstance } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { DocumentChecked, Clock, SuccessFilled, CircleCloseFilled, Search, Refresh, Check, View } from '@element-plus/icons-vue';
import { getReviewList, getReviewStatistics } from '@/api/zsjy/internshipMonthlyReview';
import type { InternshipMonthlyVO, InternshipMonthlyReviewStatisticsVO } from '@/api/zsjy/types';
import ReviewDetail from './components/ReviewDetail.vue';
import ReviewForm from './components/ReviewForm.vue';
import BatchReviewForm from './components/BatchReviewForm.vue';
const { proxy } = getCurrentInstance() as any
const { proxy } = getCurrentInstance() as any;
// 数据状态
const data = reactive({
@@ -289,132 +270,123 @@ const data = reactive({
averageScore: 0,
completionRate: 0
}
})
});
const {
loading,
showSearch,
reviewList,
multipleSelection,
single,
multiple,
total,
queryParams,
daterangeCreateTime,
statistics
} = toRefs(data)
const { loading, showSearch, reviewList, multipleSelection, single, multiple, total, queryParams, daterangeCreateTime, statistics } = toRefs(data);
// 对话框状态
const detailOpen = ref(false)
const reviewOpen = ref(false)
const batchReviewOpen = ref(false)
const detailTitle = ref('')
const currentMonthlyData = ref({})
const detailOpen = ref(false);
const reviewOpen = ref(false);
const batchReviewOpen = ref(false);
const detailTitle = ref('');
const currentMonthlyData = ref({});
// 表单引用
const queryFormRef = ref()
const reviewDetailRef = ref()
const reviewFormRef = ref()
const batchReviewFormRef = ref()
const queryFormRef = ref();
const reviewDetailRef = ref();
const reviewFormRef = ref();
const batchReviewFormRef = ref();
/** 查询列表 */
function getList() {
data.loading = true
data.loading = true;
// 处理时间范围
if (data.daterangeCreateTime && data.daterangeCreateTime.length === 2) {
data.queryParams.startDate = data.daterangeCreateTime[0]
data.queryParams.endDate = data.daterangeCreateTime[1]
data.queryParams.startDate = data.daterangeCreateTime[0];
data.queryParams.endDate = data.daterangeCreateTime[1];
} else {
data.queryParams.startDate = undefined
data.queryParams.endDate = undefined
data.queryParams.startDate = undefined;
data.queryParams.endDate = undefined;
}
getReviewList(data.queryParams).then((response: any) => {
data.reviewList = response.rows
data.total = response.total
data.loading = false
}).catch(() => {
data.loading = false
})
getReviewList(data.queryParams)
.then((response: any) => {
data.reviewList = response.rows;
data.total = response.total;
data.loading = false;
})
.catch(() => {
data.loading = false;
});
}
/** 获取统计数据 */
function getStatistics() {
getReviewStatistics().then((response: any) => {
data.statistics = response.data
})
data.statistics = response.data;
});
}
/** 搜索按钮操作 */
function handleQuery() {
data.queryParams.pageNum = 1
getList()
data.queryParams.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
data.daterangeCreateTime = []
queryFormRef.value?.resetFields()
handleQuery()
data.daterangeCreateTime = [];
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
function handleSelectionChange(selection: any[]) {
data.multipleSelection = selection
data.single = selection.length !== 1
data.multiple = !selection.length
data.multipleSelection = selection;
data.single = selection.length !== 1;
data.multiple = !selection.length;
}
/** 查看详情 */
function handleDetail(row: any) {
currentMonthlyData.value = { ...row }
detailTitle.value = `${row.studentName}的月度总结详情`
detailOpen.value = true
currentMonthlyData.value = { ...row };
detailTitle.value = `${row.studentName}的月度总结详情`;
detailOpen.value = true;
}
/** 单个审核 */
function handleReview(row: any) {
currentMonthlyData.value = { ...row }
reviewOpen.value = true
currentMonthlyData.value = { ...row };
reviewOpen.value = true;
}
/** 批量审核 */
function handleBatchReview() {
if (data.multipleSelection.length === 0) {
ElMessage.warning('请选择要审核的数据')
return
ElMessage.warning('请选择要审核的数据');
return;
}
// 检查是否都是待审核状态
const pendingRows = data.multipleSelection.filter(row => row.reviewStatus === 0)
const pendingRows = data.multipleSelection.filter((row) => row.reviewStatus === 0);
if (pendingRows.length === 0) {
ElMessage.warning('选择的数据中没有待审核的记录')
return
ElMessage.warning('选择的数据中没有待审核的记录');
return;
}
if (pendingRows.length !== data.multipleSelection.length) {
ElMessage.warning('只能审核状态为"待审核"的记录,请重新选择')
return
ElMessage.warning('只能审核状态为"待审核"的记录,请重新选择');
return;
}
batchReviewOpen.value = true
batchReviewOpen.value = true;
}
/** 审核成功回调 */
function handleReviewSuccess() {
reviewOpen.value = false
getList()
getStatistics()
ElMessage.success('审核完成')
reviewOpen.value = false;
getList();
getStatistics();
ElMessage.success('审核完成');
}
/** 批量审核成功回调 */
function handleBatchReviewSuccess() {
batchReviewOpen.value = false
getList()
getStatistics()
ElMessage.success('批量审核完成')
batchReviewOpen.value = false;
getList();
getStatistics();
ElMessage.success('批量审核完成');
}
/** 获取平均分 */
@@ -425,51 +397,59 @@ function getAverageScore(row: any) {
row.teamworkSkills || 0,
row.learningAbility || 0,
row.workAttitude || 0
]
const total = scores.reduce((sum, score) => sum + score, 0)
return total / scores.length
];
const total = scores.reduce((sum, score) => sum + score, 0);
return total / scores.length;
}
/** 获取评分标签类型 */
function getScoreTagType(score: number) {
if (score >= 9) return 'success'
if (score >= 8) return 'warning'
if (score >= 7) return 'primary'
return 'danger'
if (score >= 9) return 'success';
if (score >= 8) return 'warning';
if (score >= 7) return 'primary';
return 'danger';
}
/** 获取审核状态文本 */
function getReviewStatusText(status: number) {
switch (status) {
case 0: return '待审核'
case 1: return '已通过'
case 2: return '已拒绝'
default: return '未知'
case 0:
return '待审核';
case 1:
return '已通过';
case 2:
return '已拒绝';
default:
return '未知';
}
}
/** 获取审核状态标签类型 */
function getReviewStatusTagType(status: number) {
switch (status) {
case 0: return 'warning'
case 1: return 'success'
case 2: return 'danger'
default: return 'info'
case 0:
return 'warning';
case 1:
return 'success';
case 2:
return 'danger';
default:
return 'info';
}
}
/** 获取审核评分标签类型 */
function getReviewScoreTagType(score: number) {
if (score >= 90) return 'success'
if (score >= 80) return 'warning'
if (score >= 70) return 'primary'
return 'danger'
if (score >= 90) return 'success';
if (score >= 80) return 'warning';
if (score >= 70) return 'primary';
return 'danger';
}
onMounted(() => {
getList()
getStatistics()
})
getList();
getStatistics();
});
</script>
<style scoped>
+65 -68
View File
@@ -68,12 +68,24 @@
</el-col>
<el-col :span="6">
<el-form-item label="实习单位" prop="companyName">
<el-input v-model="queryParams.companyName" placeholder="请输入实习单位名称" clearable style="width: 100%" @keyup.enter="handleQuery" />
<el-input
v-model="queryParams.companyName"
placeholder="请输入实习单位名称"
clearable
style="width: 100%"
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="走访人" prop="visitorName">
<el-input v-model="queryParams.visitorName" placeholder="请输入走访人姓名" clearable style="width: 100%" @keyup.enter="handleQuery" />
<el-input
v-model="queryParams.visitorName"
placeholder="请输入走访人姓名"
clearable
style="width: 100%"
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -103,12 +115,10 @@
<el-col :span="12">
<div class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="info" plain icon="Search" @click="showSearch = !showSearch">
{{ showSearch ? '隐藏' : '显示' }}搜索
</el-button>
<el-button type="info" plain icon="Search" @click="showSearch = !showSearch"> {{ showSearch ? '隐藏' : '显示' }}搜索 </el-button>
</el-form-item>
</div>
</el-col>
@@ -126,9 +136,7 @@
<div class="button-group">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internshipVisit:add']">新增走访</el-button>
<el-button type="success" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipVisit:export']">导出</el-button>
<el-button type="info" plain icon="Search" @click="showSearch = !showSearch">
{{ showSearch ? '隐藏' : '显示' }}搜索
</el-button>
<el-button type="info" plain icon="Search" @click="showSearch = !showSearch"> {{ showSearch ? '隐藏' : '显示' }}搜索 </el-button>
<el-tooltip content="刷新" placement="top">
<el-button icon="Refresh" circle @click="getList" />
</el-tooltip>
@@ -171,8 +179,6 @@
</template>
</el-table-column>
<el-table-column label="走访状态" prop="status" min-width="100" align="center">
<template #default="scope">
<dict-tag :options="visitStatusOptions" :value="scope.row.status || ''" />
@@ -282,17 +288,10 @@
:value="student.studentId"
/>
<!-- 空数据提示 -->
<el-option
v-if="studentOptions.length === 0 && !studentLoading"
disabled
label="暂无学生数据"
value=""
/>
<el-option v-if="studentOptions.length === 0 && !studentLoading" disabled label="暂无学生数据" value="" />
</el-select>
<!-- 提示信息 -->
<div class="form-item-tip">
系统将自动显示您所管理班级的学生,支持输入姓名或学号搜索
</div>
<div class="form-item-tip">系统将自动显示您所管理班级的学生,支持输入姓名或学号搜索</div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -457,54 +456,56 @@ function getList() {
}
delete params.visitDateRange;
listInternshipVisit(params).then((response) => {
console.log('API响应数据:', response); // 用于调试
listInternshipVisit(params)
.then((response) => {
console.log('API响应数据:', response); // 用于调试
// 健壮的响应处理逻辑
if (response && typeof response === 'object') {
// 情况1:直接的TableDataInfo格式 {total: number, rows: array}
if (response.rows !== undefined && response.total !== undefined) {
visitList.value = response.rows || [];
total.value = response.total || 0;
}
// 情况2:嵌套在data中的TableDataInfo格式 {data: {total: number, rows: array}}
else if (response.data && response.data.rows !== undefined && response.data.total !== undefined) {
visitList.value = response.data.rows || [];
total.value = response.data.total || 0;
}
// 情况3:直接返回数组格式
else if (Array.isArray(response)) {
visitList.value = response;
total.value = response.length;
}
// 情况4:嵌套的数组格式
else if (response.data && Array.isArray(response.data)) {
visitList.value = response.data;
total.value = response.data.length;
}
// 其他未知格式
else {
console.warn('未识别的API响应格式:', response);
// 健壮的响应处理逻辑
if (response && typeof response === 'object') {
// 情况1:直接的TableDataInfo格式 {total: number, rows: array}
if (response.rows !== undefined && response.total !== undefined) {
visitList.value = response.rows || [];
total.value = response.total || 0;
}
// 情况2:嵌套在data中的TableDataInfo格式 {data: {total: number, rows: array}}
else if (response.data && response.data.rows !== undefined && response.data.total !== undefined) {
visitList.value = response.data.rows || [];
total.value = response.data.total || 0;
}
// 情况3:直接返回数组格式
else if (Array.isArray(response)) {
visitList.value = response;
total.value = response.length;
}
// 情况4:嵌套的数组格式
else if (response.data && Array.isArray(response.data)) {
visitList.value = response.data;
total.value = response.data.length;
}
// 其他未知格式
else {
console.warn('未识别的API响应格式:', response);
visitList.value = [];
total.value = 0;
}
} else {
console.warn('API响应不是对象类型:', response);
visitList.value = [];
total.value = 0;
}
} else {
console.warn('API响应不是对象类型:', response);
loading.value = false;
// 计算统计数据
calculateStatistics();
})
.catch((error) => {
console.error('获取走访记录列表失败:', error);
visitList.value = [];
total.value = 0;
}
loading.value = false;
// 计算统计数据
calculateStatistics();
}).catch((error) => {
console.error('获取走访记录列表失败:', error);
visitList.value = [];
total.value = 0;
loading.value = false;
// 可以添加用户友好的错误提示
proxy?.$modal.msgError('获取数据失败,请稍后重试');
});
loading.value = false;
// 可以添加用户友好的错误提示
proxy?.$modal.msgError('获取数据失败,请稍后重试');
});
}
/** 计算统计数据 */
@@ -760,17 +761,13 @@ function reset() {
function initDictData() {
// 获取走访状态字典
getDicts('zsjy_visit_status').then((response) => {
visitStatusOptions.value = response.data.map(item => ({
visitStatusOptions.value = response.data.map((item) => ({
label: item.dictLabel,
value: item.dictValue
}));
});
}
/** 加载当前班主任班级的学生列表 */
function loadCurrentTeacherStudents() {
const teacherId = userStore.userId;
+57 -83
View File
@@ -7,22 +7,17 @@
<div class="qrcode-header">
<h2 class="qrcode-title">新生报名二维码</h2>
<p class="qrcode-description">请扫码进行新生报名信息填写</p>
</div>
</div>
<!-- 二维码区域 -->
<div class="qrcode-wrapper">
<div class="qrcode-container">
<canvas ref="qrcodeCanvas" class="qrcode-canvas"></canvas>
</div>
</div>
<!-- 二维码URL显示 -->
<div class="qrcode-url">
<el-input
v-model="qrcodeUrl"
placeholder="请输入二维码链接地址"
clearable
@change="generateQrcode"
>
<el-input v-model="qrcodeUrl" placeholder="请输入二维码链接地址" clearable @change="generateQrcode">
<template #prepend>链接地址</template>
</el-input>
</div>
@@ -30,31 +25,13 @@
<!-- 操作按钮 -->
<div class="qrcode-actions">
<el-button
type="primary"
icon="Refresh"
@click="generateQrcode"
:loading="generating"
v-hasPermi="['zsjy:qrcode:view']"
>
<el-button type="primary" icon="Refresh" @click="generateQrcode" :loading="generating" v-hasPermi="['zsjy:qrcode:view']">
重新生成
</el-button>
<el-button
type="success"
icon="Download"
@click="downloadQrcode"
:disabled="!qrcodeGenerated"
v-hasPermi="['zsjy:qrcode:view']"
>
<el-button type="success" icon="Download" @click="downloadQrcode" :disabled="!qrcodeGenerated" v-hasPermi="['zsjy:qrcode:view']">
下载二维码
</el-button>
<el-button
type="info"
icon="Printer"
@click="printQrcode"
:disabled="!qrcodeGenerated"
v-hasPermi="['zsjy:qrcode:view']"
>
<el-button type="info" icon="Printer" @click="printQrcode" :disabled="!qrcodeGenerated" v-hasPermi="['zsjy:qrcode:view']">
打印二维码
</el-button>
</div>
@@ -64,37 +41,35 @@
</template>
<script setup lang="ts" name="QrCode">
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue'
import QRCode from 'qrcode'
import { ElMessage } from 'element-plus'
import { ref, onMounted, nextTick, getCurrentInstance } from 'vue';
import QRCode from 'qrcode';
import { ElMessage } from 'element-plus';
const { proxy } = getCurrentInstance() as any
const { proxy } = getCurrentInstance() as any;
// 响应式数据
const qrcodeCanvas = ref<HTMLCanvasElement>()
const qrcodeUrl = ref<string>('') // 二维码链接地址,可手动配置
const generating = ref<boolean>(false)
const qrcodeGenerated = ref<boolean>(false)
const qrcodeCanvas = ref<HTMLCanvasElement>();
const qrcodeUrl = ref<string>(''); // 二维码链接地址,可手动配置
const generating = ref<boolean>(false);
const qrcodeGenerated = ref<boolean>(false);
// 默认二维码URL(后续可手动配置)
const getDefaultUrl = () => {
// 使用 window.location 来判断环境
const isProduction = window.location.hostname !== 'localhost'
return isProduction
? 'https://domain.com/mobile/#/pages/enrollment/form'
: 'http://localhost:8081/#/pages/enrollment/form'
}
const isProduction = window.location.hostname !== 'localhost';
return isProduction ? 'https://domain.com/mobile/#/pages/enrollment/form' : 'http://localhost:8081/#/pages/enrollment/form';
};
/** 生成二维码 */
const generateQrcode = async () => {
if (!qrcodeUrl.value.trim()) {
ElMessage.warning('请输入有效的链接地址')
return
ElMessage.warning('请输入有效的链接地址');
return;
}
generating.value = true
generating.value = true;
try {
await nextTick()
await nextTick();
if (qrcodeCanvas.value) {
// 生成二维码到canvas
@@ -106,64 +81,64 @@ const generateQrcode = async () => {
light: '#FFFFFF'
},
errorCorrectionLevel: 'M'
})
});
qrcodeGenerated.value = true
ElMessage.success('二维码生成成功')
qrcodeGenerated.value = true;
ElMessage.success('二维码生成成功');
}
} catch (error) {
console.error('生成二维码失败:', error)
ElMessage.error('生成二维码失败,请检查链接地址是否正确')
console.error('生成二维码失败:', error);
ElMessage.error('生成二维码失败,请检查链接地址是否正确');
} finally {
generating.value = false
generating.value = false;
}
}
};
/** 下载二维码 */
const downloadQrcode = () => {
if (!qrcodeCanvas.value || !qrcodeGenerated.value) {
ElMessage.warning('请先生成二维码')
return
ElMessage.warning('请先生成二维码');
return;
}
try {
// 创建下载链接
const link = document.createElement('a')
link.download = `新生报到二维码_${new Date().getTime()}.png`
link.href = qrcodeCanvas.value.toDataURL('image/png')
const link = document.createElement('a');
link.download = `新生报到二维码_${new Date().getTime()}.png`;
link.href = qrcodeCanvas.value.toDataURL('image/png');
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success('二维码下载成功')
ElMessage.success('二维码下载成功');
} catch (error) {
console.error('下载二维码失败:', error)
ElMessage.error('下载失败')
console.error('下载二维码失败:', error);
ElMessage.error('下载失败');
}
}
};
/** 打印二维码 */
const printQrcode = () => {
if (!qrcodeCanvas.value || !qrcodeGenerated.value) {
ElMessage.warning('请先生成二维码')
return
ElMessage.warning('请先生成二维码');
return;
}
try {
// 创建打印窗口
const printWindow = window.open('', '_blank')
const printWindow = window.open('', '_blank');
if (!printWindow) {
ElMessage.error('无法打开打印窗口,请检查浏览器设置')
return
ElMessage.error('无法打开打印窗口,请检查浏览器设置');
return;
}
// 获取二维码图片数据
const qrcodeDataUrl = qrcodeCanvas.value.toDataURL('image/png')
const qrcodeDataUrl = qrcodeCanvas.value.toDataURL('image/png');
// 生成打印页面HTML
const scriptTag = 'script'
const scriptTag = 'script';
const printContent = [
'<!DOCTYPE html>',
'<html>',
@@ -196,27 +171,26 @@ const printQrcode = () => {
`</${scriptTag}>`,
'</body>',
'</html>'
].join('')
].join('');
// 写入打印内容
printWindow.document.write(printContent)
printWindow.document.close()
printWindow.document.write(printContent);
printWindow.document.close();
} catch (error) {
console.error('打印二维码失败:', error)
ElMessage.error('打印失败')
console.error('打印二维码失败:', error);
ElMessage.error('打印失败');
}
}
};
// 组件挂载后初始化
onMounted(() => {
// 设置默认URL
qrcodeUrl.value = getDefaultUrl()
qrcodeUrl.value = getDefaultUrl();
// 自动生成二维码
nextTick(() => {
generateQrcode()
})
})
generateQrcode();
});
});
</script>
<style lang="scss" scoped>