wuzhen update

This commit is contained in:
wuzhen committed 2025-08-18 15:48:34 +08:00
1 parent 811fc00bcc
commit a600627f4c
22 files changed
+3582 -3

No files matched your search

@@ -236,3 +236,4 @@ public class ZsjyInternshipMonthlyController extends BaseController {
}
}
@@ -0,0 +1,159 @@
package org.dromara.zsjy.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyBatchReviewBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import org.dromara.zsjy.service.IZsjyInternshipMonthlyReviewService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 学生月度实习总结审核Controller
*
* @author system
* @date 2025-01-27
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/zsjy/internship/monthly/review")
@Tag(name = "学生月度实习总结审核", description = "班主任审核学生月度实习总结管理")
public class ZsjyInternshipMonthlyReviewController extends BaseController {
private final IZsjyInternshipMonthlyReviewService reviewService;
/**
* 查询待审核的学生月度实习总结列表
*/
@Operation(summary = "查询待审核的学生月度实习总结列表", description = "班主任查看待审核的月度总结列表")
@SaCheckPermission("zsjy:internshipmonthly:review")
@GetMapping("/pending")
public TableDataInfo<ZsjyInternshipMonthlyVo> listPending(@Validated ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery) {
log.info("=== 审核Controller层调试 ===");
log.info("接口被调用: /zsjy/internship/monthly/review/pending");
log.info("查询参数: {}", queryBo);
log.info("分页参数: {}", pageQuery);
return reviewService.queryPendingPageList(queryBo, pageQuery);
}
/**
* 查询所有审核记录列表 (包含已审核和待审核)
*/
@Operation(summary = "查询所有审核记录列表", description = "班主任查看所有审核记录")
@SaCheckPermission("zsjy:internshipmonthly:review")
@GetMapping("/list")
public TableDataInfo<ZsjyInternshipMonthlyVo> listAll(@Validated ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery) {
log.info("=== 审核Controller层调试 ===");
log.info("接口被调用: /zsjy/internship/monthly/review/list");
log.info("查询参数: {}", queryBo);
log.info("分页参数: {}", pageQuery);
return reviewService.queryReviewPageList(queryBo, pageQuery);
}
/**
* 查询学生月度实习总结审核详情
*/
@Operation(summary = "查询学生月度实习总结审核详情", description = "根据ID查询月度总结审核详情")
@SaCheckPermission("zsjy:internshipmonthly:review")
@GetMapping("/{monthlyId}/detail")
public R<ZsjyInternshipMonthlyVo> getReviewDetail(@Parameter(description = "月度总结ID")
@PathVariable Long monthlyId) {
log.info("查询月度总结审核详情, ID: {}", monthlyId);
return R.ok(reviewService.queryReviewDetail(monthlyId));
}
/**
* 单个审核学生月度实习总结
*/
@Operation(summary = "单个审核学生月度实习总结", description = "班主任对单个月度总结进行审核")
@SaCheckPermission("zsjy:internshipmonthly:review")
@Log(title = "学生月度实习总结审核", businessType = BusinessType.UPDATE)
@PostMapping("/approve")
public R<Void> approve(@Validated(AddGroup.class) @RequestBody ZsjyInternshipMonthlyReviewBo reviewBo) {
log.info("审核月度总结: {}", reviewBo);
Boolean result = reviewService.approveMonthly(reviewBo);
if (result) {
return R.ok("审核成功");
} else {
return R.fail("审核失败");
}
}
/**
* 批量审核学生月度实习总结
*/
@Operation(summary = "批量审核学生月度实习总结", description = "班主任批量审核多个月度总结")
@SaCheckPermission("zsjy:internshipmonthly:review")
@Log(title = "学生月度实习总结批量审核", businessType = BusinessType.UPDATE)
@PostMapping("/batch-approve")
public R<Map<String, Object>> batchApprove(@Validated(AddGroup.class) @RequestBody ZsjyInternshipMonthlyBatchReviewBo batchReviewBo) {
log.info("批量审核月度总结: {}", batchReviewBo);
Map<String, Object> result = reviewService.batchApproveMonthly(batchReviewBo);
return R.ok(result);
}
/**
* 查询审核统计信息
*/
@Operation(summary = "查询审核统计信息", description = "获取审核工作的统计数据")
@SaCheckPermission("zsjy:internshipmonthly:review")
@GetMapping("/statistics")
public R<Map<String, Object>> getStatistics(@Validated ZsjyInternshipMonthlyReviewQueryBo queryBo) {
log.info("查询审核统计信息: {}", queryBo);
Map<String, Object> statistics = reviewService.getReviewStatistics(queryBo);
return R.ok(statistics);
}
/**
* 查询审核历史记录
*/
@Operation(summary = "查询审核历史记录", description = "查看已完成的审核记录")
@SaCheckPermission("zsjy:internshipmonthly:review")
@GetMapping("/history")
public TableDataInfo<ZsjyInternshipMonthlyVo> getHistory(@Validated ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery) {
log.info("查询审核历史记录: {}", queryBo);
return reviewService.queryHistoryPageList(queryBo, pageQuery);
}
/**
* 导出审核记录
*/
@Operation(summary = "导出审核记录", description = "导出学生月度总结审核记录")
@SaCheckPermission("zsjy:internshipmonthly:review")
@Log(title = "学生月度实习总结审核记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@RequestBody ZsjyInternshipMonthlyReviewQueryBo queryBo, HttpServletResponse response) {
log.info("导出审核记录: {}", queryBo);
List<ZsjyInternshipMonthlyVo> list = reviewService.queryReviewList(queryBo);
ExcelUtil.exportExcel(list, "学生月度实习总结审核记录", ZsjyInternshipMonthlyVo.class, response);
}
}
@@ -158,6 +158,12 @@ public class ZsjyInternshipMonthly extends BaseEntity {
@TableField("review_comment")
private String reviewComment;
/**
* 改进建议
*/
@TableField("review_suggestions")
private String reviewSuggestions;
/**
* 提交时间
*/
@@ -145,3 +145,4 @@ public class ZsjyInternshipMonthlyAddBo extends BaseEntity {
private Date submitTime;
}
@@ -0,0 +1,79 @@
package org.dromara.zsjy.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.List;
/**
* 学生月度实习总结批量审核对象 ZsjyInternshipMonthlyBatchReviewBo
*
* @author system
* @date 2025-01-27
*/
@Data
@Schema(description = "学生月度实习总结批量审核对象")
public class ZsjyInternshipMonthlyBatchReviewBo {
/**
* 批量审核项目列表
*/
@Schema(description = "批量审核项目列表")
@NotEmpty(message = "批量审核列表不能为空", groups = { AddGroup.class, EditGroup.class })
@Valid
private List<BatchReviewItem> reviewItems;
/**
* 批量审核项目
*/
@Data
@Schema(description = "批量审核项目")
public static class BatchReviewItem {
/**
* 月度总结ID
*/
@Schema(description = "月度总结ID")
@NotNull(message = "月度总结ID不能为空")
private Long monthlyId;
/**
* 审核结果 (1通过/2拒绝)
*/
@Schema(description = "审核结果", allowableValues = {"1", "2"})
@NotNull(message = "审核结果不能为空")
@Min(value = 1, message = "审核结果值必须为1或2")
@Max(value = 2, message = "审核结果值必须为1或2")
private Integer reviewStatus;
/**
* 评分(百分制)
*/
@Schema(description = "评分")
@DecimalMin(value = "0", message = "评分不能小于0分")
@DecimalMax(value = "100", message = "评分不能大于100分")
@Digits(integer = 3, fraction = 1, message = "评分格式不正确")
private BigDecimal reviewScore;
/**
* 审核意见 (必填)
*/
@Schema(description = "审核意见")
@NotBlank(message = "审核意见不能为空")
@Size(max = 500, message = "审核意见不能超过500字")
private String reviewComment;
/**
* 改进建议 (选填)
*/
@Schema(description = "改进建议")
@Size(max = 500, message = "改进建议不能超过500字")
private String reviewSuggestions;
}
}
@@ -0,0 +1,61 @@
package org.dromara.zsjy.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
/**
* 学生月度实习总结审核对象 ZsjyInternshipMonthlyReviewBo
*
* @author system
* @date 2025-01-27
*/
@Data
@Schema(description = "学生月度实习总结审核对象")
public class ZsjyInternshipMonthlyReviewBo {
/**
* 月度总结ID
*/
@Schema(description = "月度总结ID")
@NotNull(message = "月度总结ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long monthlyId;
/**
* 审核结果 (1通过/2拒绝)
*/
@Schema(description = "审核结果", allowableValues = {"1", "2"})
@NotNull(message = "审核结果不能为空", groups = { AddGroup.class, EditGroup.class })
@Min(value = 1, message = "审核结果值必须为1或2", groups = { AddGroup.class, EditGroup.class })
@Max(value = 2, message = "审核结果值必须为1或2", groups = { AddGroup.class, EditGroup.class })
private Integer reviewStatus;
/**
* 评分(百分制)
*/
@Schema(description = "评分")
@DecimalMin(value = "0", message = "评分不能小于0分", groups = { AddGroup.class, EditGroup.class })
@DecimalMax(value = "100", message = "评分不能大于100分", groups = { AddGroup.class, EditGroup.class })
@Digits(integer = 3, fraction = 1, message = "评分格式不正确", groups = { AddGroup.class, EditGroup.class })
private BigDecimal reviewScore;
/**
* 审核意见 (必填)
*/
@Schema(description = "审核意见")
@NotBlank(message = "审核意见不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(max = 500, message = "审核意见不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String reviewComment;
/**
* 改进建议 (选填)
*/
@Schema(description = "改进建议")
@Size(max = 500, message = "改进建议不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String reviewSuggestions;
}
@@ -0,0 +1,79 @@
package org.dromara.zsjy.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/**
* 学生月度实习总结审核查询对象 ZsjyInternshipMonthlyReviewQueryBo
*
* @author system
* @date 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "学生月度实习总结审核查询对象")
public class ZsjyInternshipMonthlyReviewQueryBo extends BaseEntity {
/**
* 学生姓名
*/
@Schema(description = "学生姓名")
private String studentName;
/**
* 学号
*/
@Schema(description = "学号")
private String studentNo;
/**
* 班级名称
*/
@Schema(description = "班级名称")
private String className;
/**
* 总结月份 (格式: 2025-01)
*/
@Schema(description = "总结月份")
private String summaryMonth;
/**
* 审核状态 (0待审核/1已审核通过/2审核拒绝)
*/
@Schema(description = "审核状态", allowableValues = {"0", "1", "2"})
private Integer reviewStatus;
/**
* 提交开始时间
*/
@Schema(description = "提交开始时间")
private String submitTimeStart;
/**
* 提交结束时间
*/
@Schema(description = "提交结束时间")
private String submitTimeEnd;
/**
* 实习单位名称
*/
@Schema(description = "实习单位名称")
private String companyName;
/**
* 学院名称
*/
@Schema(description = "学院名称")
private String collegeName;
/**
* 专业名称
*/
@Schema(description = "专业名称")
private String majorName;
}
@@ -52,6 +52,18 @@ public class ZsjyInternshipMonthlyVo implements Serializable {
@Schema(description = "学生学号")
private String studentNumber;
/**
* 班级名称 - 通过关联查询获取
*/
@Schema(description = "班级名称")
private String className;
/**
* 专业名称 - 通过关联查询获取
*/
@Schema(description = "专业名称")
private String majorName;
/**
* 总结月份 (格式: 2025-01)
*/
@@ -173,6 +185,12 @@ public class ZsjyInternshipMonthlyVo implements Serializable {
@Schema(description = "审核意见")
private String reviewComment;
/**
* 改进建议
*/
@Schema(description = "改进建议")
private String reviewSuggestions;
/**
* 提交时间
*/
@@ -251,3 +269,4 @@ public class ZsjyInternshipMonthlyVo implements Serializable {
private Date updateTime;
}
@@ -5,9 +5,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.dromara.zsjy.domain.ZsjyInternshipMonthly;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyQueryBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import java.util.List;
import java.util.Map;
/**
* 学生月度实习总结Mapper接口
@@ -79,4 +81,56 @@ public interface ZsjyInternshipMonthlyMapper extends BaseMapper<ZsjyInternshipMo
*/
List<Object> countByStatus(@Param("studentId") Long studentId);
/**
* 查询审核分页列表
*
* @param page 分页参数
* @param queryBo 查询条件
* @param studentIds 学生ID列表
* @return 审核分页结果
*/
Page<ZsjyInternshipMonthlyVo> selectReviewPage(Page<ZsjyInternshipMonthlyVo> page,
@Param("bo") ZsjyInternshipMonthlyReviewQueryBo queryBo,
@Param("studentIds") List<Long> studentIds);
/**
* 查询审核列表
*
* @param queryBo 查询条件
* @param studentIds 学生ID列表
* @return 审核列表
*/
List<ZsjyInternshipMonthlyVo> selectReviewList(@Param("bo") ZsjyInternshipMonthlyReviewQueryBo queryBo,
@Param("studentIds") List<Long> studentIds);
/**
* 查询审核历史分页列表
*
* @param page 分页参数
* @param queryBo 查询条件
* @param studentIds 学生ID列表
* @return 审核历史分页结果
*/
Page<ZsjyInternshipMonthlyVo> selectHistoryPage(Page<ZsjyInternshipMonthlyVo> page,
@Param("bo") ZsjyInternshipMonthlyReviewQueryBo queryBo,
@Param("studentIds") List<Long> studentIds);
/**
* 查询审核统计信息
*
* @param queryBo 查询条件
* @param studentIds 学生ID列表
* @return 统计信息
*/
Map<String, Object> selectReviewStatistics(@Param("bo") ZsjyInternshipMonthlyReviewQueryBo queryBo,
@Param("studentIds") List<Long> studentIds);
/**
* 根据班主任ID查询管理的学生ID列表
*
* @param classTeacherId 班主任ID
* @return 学生ID列表
*/
List<Long> selectStudentIdsByClassTeacher(@Param("classTeacherId") Long classTeacherId);
}
@@ -0,0 +1,103 @@
package org.dromara.zsjy.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyBatchReviewBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import java.util.List;
import java.util.Map;
/**
* 学生月度实习总结审核Service接口
*
* @author system
* @date 2025-01-27
*/
public interface IZsjyInternshipMonthlyReviewService {
/**
* 查询待审核的学生月度实习总结分页列表
*
* @param queryBo 查询条件
* @param pageQuery 分页参数
* @return 待审核月度总结分页结果
*/
TableDataInfo<ZsjyInternshipMonthlyVo> queryPendingPageList(ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery);
/**
* 查询所有审核记录分页列表 (包含已审核和待审核)
*
* @param queryBo 查询条件
* @param pageQuery 分页参数
* @return 审核记录分页结果
*/
TableDataInfo<ZsjyInternshipMonthlyVo> queryReviewPageList(ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery);
/**
* 查询审核记录列表
*
* @param queryBo 查询条件
* @return 审核记录集合
*/
List<ZsjyInternshipMonthlyVo> queryReviewList(ZsjyInternshipMonthlyReviewQueryBo queryBo);
/**
* 查询学生月度实习总结审核详情
*
* @param monthlyId 月度总结ID
* @return 月度总结审核详情
*/
ZsjyInternshipMonthlyVo queryReviewDetail(Long monthlyId);
/**
* 单个审核学生月度实习总结
*
* @param reviewBo 审核参数
* @return 审核结果
*/
Boolean approveMonthly(ZsjyInternshipMonthlyReviewBo reviewBo);
/**
* 批量审核学生月度实习总结
*
* @param batchReviewBo 批量审核参数
* @return 批量审核结果
*/
Map<String, Object> batchApproveMonthly(ZsjyInternshipMonthlyBatchReviewBo batchReviewBo);
/**
* 获取审核统计信息
*
* @param queryBo 查询条件
* @return 统计信息
*/
Map<String, Object> getReviewStatistics(ZsjyInternshipMonthlyReviewQueryBo queryBo);
/**
* 查询审核历史记录分页列表
*
* @param queryBo 查询条件
* @param pageQuery 分页参数
* @return 审核历史分页结果
*/
TableDataInfo<ZsjyInternshipMonthlyVo> queryHistoryPageList(ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery);
/**
* 检查月度总结是否可以审核
*
* @param monthlyId 月度总结ID
* @return 是否可以审核
*/
Boolean checkCanReview(Long monthlyId);
/**
* 获取班主任管理的班级学生ID列表
*
* @return 学生ID集合
*/
List<Long> getClassTeacherStudentIds();
}
@@ -135,3 +135,4 @@ public interface IZsjyInternshipMonthlyService {
Map<String, Object> getCurrentInternshipInfo();
}
@@ -0,0 +1,269 @@
package org.dromara.zsjy.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.zsjy.domain.ZsjyInternshipMonthly;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyBatchReviewBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyReviewQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import org.dromara.zsjy.mapper.ZsjyInternshipMonthlyMapper;
import org.dromara.zsjy.service.IZsjyInternshipMonthlyReviewService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* 学生月度实习总结审核Service业务层处理
*
* @author system
* @date 2025-01-27
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ZsjyInternshipMonthlyReviewServiceImpl implements IZsjyInternshipMonthlyReviewService {
private final ZsjyInternshipMonthlyMapper baseMapper;
/**
* 查询待审核的学生月度实习总结分页列表
*/
@Override
public TableDataInfo<ZsjyInternshipMonthlyVo> queryPendingPageList(ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery) {
log.info("=== Service层调试 ===");
log.info("查询待审核列表,参数: {}", queryBo);
// 设置查询条件:只查询待审核状态(0)
queryBo.setReviewStatus(0);
// 获取班主任管理的学生ID列表
List<Long> studentIds = getClassTeacherStudentIds();
if (studentIds.isEmpty()) {
log.warn("当前用户不是班主任或没有管理的学生");
return TableDataInfo.build(new Page<>());
}
Page<ZsjyInternshipMonthlyVo> result = baseMapper.selectReviewPage(pageQuery.build(), queryBo, studentIds);
return TableDataInfo.build(result);
}
/**
* 查询所有审核记录分页列表 (包含已审核和待审核)
*/
@Override
public TableDataInfo<ZsjyInternshipMonthlyVo> queryReviewPageList(ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery) {
log.info("=== Service层调试 ===");
log.info("查询所有审核记录,参数: {}", queryBo);
// 获取班主任管理的学生ID列表
List<Long> studentIds = getClassTeacherStudentIds();
if (studentIds.isEmpty()) {
log.warn("当前用户不是班主任或没有管理的学生");
return TableDataInfo.build(new Page<>());
}
Page<ZsjyInternshipMonthlyVo> result = baseMapper.selectReviewPage(pageQuery.build(), queryBo, studentIds);
return TableDataInfo.build(result);
}
/**
* 查询审核记录列表
*/
@Override
public List<ZsjyInternshipMonthlyVo> queryReviewList(ZsjyInternshipMonthlyReviewQueryBo queryBo) {
// 获取班主任管理的学生ID列表
List<Long> studentIds = getClassTeacherStudentIds();
if (studentIds.isEmpty()) {
return new ArrayList<>();
}
return baseMapper.selectReviewList(queryBo, studentIds);
}
/**
* 查询学生月度实习总结审核详情
*/
@Override
public ZsjyInternshipMonthlyVo queryReviewDetail(Long monthlyId) {
ZsjyInternshipMonthlyVo vo = baseMapper.selectVoById(monthlyId);
if (ObjectUtil.isNull(vo)) {
throw new ServiceException("月度总结不存在");
}
// 检查是否有权限查看
List<Long> studentIds = getClassTeacherStudentIds();
if (!studentIds.contains(vo.getStudentId())) {
throw new ServiceException("无权查看该学生的总结");
}
return vo;
}
/**
* 单个审核学生月度实习总结
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean approveMonthly(ZsjyInternshipMonthlyReviewBo reviewBo) {
log.info("=== 单个审核Service层调试 ===");
log.info("审核参数: {}", reviewBo);
// 检查月度总结是否存在且可以审核
if (!checkCanReview(reviewBo.getMonthlyId())) {
throw new ServiceException("该月度总结不存在或不可审核");
}
// 构建更新条件
LambdaUpdateWrapper<ZsjyInternshipMonthly> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ZsjyInternshipMonthly::getId, reviewBo.getMonthlyId())
.set(ZsjyInternshipMonthly::getReviewStatus, reviewBo.getReviewStatus())
.set(ZsjyInternshipMonthly::getReviewScore, reviewBo.getReviewScore())
.set(ZsjyInternshipMonthly::getReviewComment, reviewBo.getReviewComment())
.set(ZsjyInternshipMonthly::getReviewSuggestions, reviewBo.getReviewSuggestions())
.set(ZsjyInternshipMonthly::getReviewBy, LoginHelper.getUserId())
.set(ZsjyInternshipMonthly::getReviewTime, new Date());
int result = baseMapper.update(null, updateWrapper);
log.info("审核更新结果: {}", result);
return result > 0;
}
/**
* 批量审核学生月度实习总结
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> batchApproveMonthly(ZsjyInternshipMonthlyBatchReviewBo batchReviewBo) {
log.info("=== 批量审核Service层调试 ===");
log.info("批量审核参数: {}", batchReviewBo);
Map<String, Object> result = new HashMap<>();
int successCount = 0;
int failCount = 0;
List<String> errorMessages = new ArrayList<>();
for (ZsjyInternshipMonthlyBatchReviewBo.BatchReviewItem item : batchReviewBo.getReviewItems()) {
try {
// 检查是否可以审核
if (!checkCanReview(item.getMonthlyId())) {
failCount++;
errorMessages.add("ID:" + item.getMonthlyId() + " 不存在或不可审核");
continue;
}
// 构建更新条件
LambdaUpdateWrapper<ZsjyInternshipMonthly> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ZsjyInternshipMonthly::getId, item.getMonthlyId())
.set(ZsjyInternshipMonthly::getReviewStatus, item.getReviewStatus())
.set(ZsjyInternshipMonthly::getReviewScore, item.getReviewScore())
.set(ZsjyInternshipMonthly::getReviewComment, item.getReviewComment())
.set(ZsjyInternshipMonthly::getReviewSuggestions, item.getReviewSuggestions())
.set(ZsjyInternshipMonthly::getReviewBy, LoginHelper.getUserId())
.set(ZsjyInternshipMonthly::getReviewTime, new Date());
int updateResult = baseMapper.update(null, updateWrapper);
if (updateResult > 0) {
successCount++;
} else {
failCount++;
errorMessages.add("ID:" + item.getMonthlyId() + " 更新失败");
}
} catch (Exception e) {
failCount++;
errorMessages.add("ID:" + item.getMonthlyId() + " 审核异常: " + e.getMessage());
log.error("批量审核异常,月度总结ID: {}", item.getMonthlyId(), e);
}
}
result.put("successCount", successCount);
result.put("failCount", failCount);
result.put("total", batchReviewBo.getReviewItems().size());
result.put("errorMessages", errorMessages);
log.info("批量审核结果: {}", result);
return result;
}
/**
* 获取审核统计信息
*/
@Override
public Map<String, Object> getReviewStatistics(ZsjyInternshipMonthlyReviewQueryBo queryBo) {
// 获取班主任管理的学生ID列表
List<Long> studentIds = getClassTeacherStudentIds();
if (studentIds.isEmpty()) {
return new HashMap<>();
}
return baseMapper.selectReviewStatistics(queryBo, studentIds);
}
/**
* 查询审核历史记录分页列表
*/
@Override
public TableDataInfo<ZsjyInternshipMonthlyVo> queryHistoryPageList(ZsjyInternshipMonthlyReviewQueryBo queryBo, PageQuery pageQuery) {
// 设置查询条件:只查询已审核状态(1已通过/2已拒绝)
if (ObjectUtil.isNull(queryBo.getReviewStatus())) {
// 如果没有指定状态,则查询所有已审核的记录
// 这里可以通过修改Mapper查询条件来实现,暂时不设置具体状态
}
// 获取班主任管理的学生ID列表
List<Long> studentIds = getClassTeacherStudentIds();
if (studentIds.isEmpty()) {
return TableDataInfo.build(new Page<>());
}
Page<ZsjyInternshipMonthlyVo> result = baseMapper.selectHistoryPage(pageQuery.build(), queryBo, studentIds);
return TableDataInfo.build(result);
}
/**
* 检查月度总结是否可以审核
*/
@Override
public Boolean checkCanReview(Long monthlyId) {
ZsjyInternshipMonthlyVo vo = baseMapper.selectVoById(monthlyId);
if (ObjectUtil.isNull(vo)) {
return false;
}
// 检查是否有权限审核(是否是班主任管理的学生)
List<Long> studentIds = getClassTeacherStudentIds();
if (!studentIds.contains(vo.getStudentId())) {
return false;
}
// 检查状态是否为待审核(0)
return Integer.valueOf(0).equals(vo.getReviewStatus());
}
/**
* 获取班主任管理的班级学生ID列表
* TODO: 这里需要根据实际的班主任-学生关系表来实现
* 暂时返回空列表,需要补充具体的业务逻辑
*/
@Override
public List<Long> getClassTeacherStudentIds() {
Long currentUserId = LoginHelper.getUserId();
log.info("当前登录用户ID: {}", currentUserId);
// TODO: 这里需要查询班主任管理的学生ID列表
// 可能需要关联班级表、学生表等
// 暂时返回示例数据,实际需要根据业务补充
return baseMapper.selectStudentIdsByClassTeacher(currentUserId);
}
}
@@ -26,7 +26,8 @@
<result property="reviewBy" column="review_by" />
<result property="reviewTime" column="review_time" />
<result property="reviewScore" column="review_score" />
<result property="reviewComments" column="review_comment" />
<result property="reviewComment" column="review_comment" />
<result property="reviewSuggestions" column="review_suggestions" />
<result property="submitTime" column="submit_time" />
<result property="wordCount" column="word_count" />
<result property="isOverdue" column="is_overdue" />
@@ -45,6 +46,8 @@
<result property="studentId" column="student_id" />
<result property="studentName" column="student_name" />
<result property="studentNumber" column="student_number" />
<result property="className" column="class_name" />
<result property="majorName" column="major_name" />
<result property="summaryMonth" column="summary_month" />
<result property="monthNumber" column="month_number" />
<result property="workContent" column="work_content" />
@@ -64,7 +67,8 @@
<result property="reviewerName" column="reviewer_name" />
<result property="reviewTime" column="review_time" />
<result property="reviewScore" column="review_score" />
<result property="reviewComments" column="review_comment" />
<result property="reviewComment" column="review_comment" />
<result property="reviewSuggestions" column="review_suggestions" />
<result property="submitTime" column="submit_time" />
<result property="wordCount" column="word_count" />
<result property="isOverdue" column="is_overdue" />
@@ -86,6 +90,8 @@
m.student_id,
u.nick_name as student_name,
ui.student_no as student_number,
xsm.class_name,
xsm.major_name,
m.summary_month,
m.month_number,
m.work_content,
@@ -111,7 +117,8 @@
r.nick_name as reviewer_name,
m.review_time,
m.review_score,
m.review_comment as review_comments,
m.review_comment,
m.review_suggestions,
m.submit_time,
m.word_count,
m.is_overdue,
@@ -128,6 +135,7 @@
LEFT JOIN sys_user u ON u.user_id = m.student_id
LEFT JOIN zsjy_internship_info ui ON ui.internship_id = m.internship_id AND ui.student_id = m.student_id
LEFT JOIN sys_user r ON r.user_id = m.review_by
LEFT JOIN xg_student_msg xsm ON xsm.student_no = ui.student_no
WHERE m.deleted = 0
</sql>
@@ -237,4 +245,149 @@
ORDER BY review_status
</select>
<!-- 查询审核分页列表 -->
<select id="selectReviewPage" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
AND m.review_status != 3 <!-- 过滤掉草稿状态,只显示已提交的记录 -->
<if test="studentIds != null and studentIds.size() > 0">
AND m.student_id IN
<foreach collection="studentIds" item="studentId" open="(" close=")" separator=",">
#{studentId}
</foreach>
</if>
<if test="bo.studentName != null and bo.studentName != ''">
AND u.nick_name LIKE CONCAT('%', #{bo.studentName}, '%')
</if>
<if test="bo.studentNo != null and bo.studentNo != ''">
AND ui.student_no LIKE CONCAT('%', #{bo.studentNo}, '%')
</if>
<if test="bo.summaryMonth != null and bo.summaryMonth != ''">
AND m.summary_month = #{bo.summaryMonth}
</if>
<if test="bo.reviewStatus != null">
AND m.review_status = #{bo.reviewStatus}
</if>
<if test="bo.submitTimeStart != null and bo.submitTimeStart != ''">
AND m.submit_time &gt;= #{bo.submitTimeStart}
</if>
<if test="bo.submitTimeEnd != null and bo.submitTimeEnd != ''">
AND m.submit_time &lt;= #{bo.submitTimeEnd}
</if>
ORDER BY m.review_status ASC, m.submit_time DESC, m.create_time DESC
</select>
<!-- 查询审核列表 -->
<select id="selectReviewList" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
AND m.review_status != 3 <!-- 过滤掉草稿状态,只显示已提交的记录 -->
<if test="studentIds != null and studentIds.size() > 0">
AND m.student_id IN
<foreach collection="studentIds" item="studentId" open="(" close=")" separator=",">
#{studentId}
</foreach>
</if>
<if test="bo.studentName != null and bo.studentName != ''">
AND u.nick_name LIKE CONCAT('%', #{bo.studentName}, '%')
</if>
<if test="bo.studentNo != null and bo.studentNo != ''">
AND ui.student_no LIKE CONCAT('%', #{bo.studentNo}, '%')
</if>
<if test="bo.summaryMonth != null and bo.summaryMonth != ''">
AND m.summary_month = #{bo.summaryMonth}
</if>
<if test="bo.reviewStatus != null">
AND m.review_status = #{bo.reviewStatus}
</if>
<if test="bo.submitTimeStart != null and bo.submitTimeStart != ''">
AND m.submit_time &gt;= #{bo.submitTimeStart}
</if>
<if test="bo.submitTimeEnd != null and bo.submitTimeEnd != ''">
AND m.submit_time &lt;= #{bo.submitTimeEnd}
</if>
ORDER BY m.review_status ASC, m.submit_time DESC, m.create_time DESC
</select>
<!-- 查询审核历史分页列表 -->
<select id="selectHistoryPage" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
AND m.review_status IN (1, 2)
<if test="studentIds != null and studentIds.size() > 0">
AND m.student_id IN
<foreach collection="studentIds" item="studentId" open="(" close=")" separator=",">
#{studentId}
</foreach>
</if>
<if test="bo.studentName != null and bo.studentName != ''">
AND u.nick_name LIKE CONCAT('%', #{bo.studentName}, '%')
</if>
<if test="bo.studentNo != null and bo.studentNo != ''">
AND ui.student_no LIKE CONCAT('%', #{bo.studentNo}, '%')
</if>
<if test="bo.summaryMonth != null and bo.summaryMonth != ''">
AND m.summary_month = #{bo.summaryMonth}
</if>
<if test="bo.reviewStatus != null">
AND m.review_status = #{bo.reviewStatus}
</if>
<if test="bo.submitTimeStart != null and bo.submitTimeStart != ''">
AND m.submit_time &gt;= #{bo.submitTimeStart}
</if>
<if test="bo.submitTimeEnd != null and bo.submitTimeEnd != ''">
AND m.submit_time &lt;= #{bo.submitTimeEnd}
</if>
ORDER BY m.review_time DESC, m.submit_time DESC
</select>
<!-- 查询审核统计信息 -->
<select id="selectReviewStatistics" resultType="map">
SELECT
COUNT(*) as totalCount,
COUNT(CASE WHEN m.review_status = 0 THEN 1 END) as pendingCount,
COUNT(CASE WHEN m.review_status = 1 THEN 1 END) as approvedCount,
COUNT(CASE WHEN m.review_status = 2 THEN 1 END) as rejectedCount,
ROUND(AVG(CASE WHEN m.review_score IS NOT NULL THEN m.review_score END), 2) as averageScore,
ROUND(COUNT(CASE WHEN m.review_status IN (1, 2) THEN 1 END) * 100.0 / COUNT(*), 2) as completionRate
FROM zsjy_internship_monthly m
LEFT JOIN sys_user u ON u.user_id = m.student_id
LEFT JOIN zsjy_internship_info ui ON ui.internship_id = m.internship_id AND ui.student_id = m.student_id
WHERE m.deleted = 0
<if test="studentIds != null and studentIds.size() > 0">
AND m.student_id IN
<foreach collection="studentIds" item="studentId" open="(" close=")" separator=",">
#{studentId}
</foreach>
</if>
<if test="bo.studentName != null and bo.studentName != ''">
AND u.nick_name LIKE CONCAT('%', #{bo.studentName}, '%')
</if>
<if test="bo.studentNo != null and bo.studentNo != ''">
AND ui.student_no LIKE CONCAT('%', #{bo.studentNo}, '%')
</if>
<if test="bo.summaryMonth != null and bo.summaryMonth != ''">
AND m.summary_month = #{bo.summaryMonth}
</if>
<if test="bo.reviewStatus != null">
AND m.review_status = #{bo.reviewStatus}
</if>
<if test="bo.submitTimeStart != null and bo.submitTimeStart != ''">
AND m.submit_time &gt;= #{bo.submitTimeStart}
</if>
<if test="bo.submitTimeEnd != null and bo.submitTimeEnd != ''">
AND m.submit_time &lt;= #{bo.submitTimeEnd}
</if>
</select>
<!-- 根据班主任ID查询管理的学生ID列表 -->
<select id="selectStudentIdsByClassTeacher" resultType="Long">
SELECT DISTINCT s.id
FROM xg_student_msg s
INNER JOIN xg_class_msg c ON s.class_id = c.id
WHERE s.deleted = 0
AND s.student_status = '1'
AND c.deleted = 0
AND c.class_teacher_id = #{classTeacherId}
AND s.tenant_id = '000000'
AND c.tenant_id = '000000'
</select>
</mapper>
+1
View File
@@ -100,3 +100,4 @@ export function getCurrentInternshipInfo() {
method: 'get'
})
}
+154
View File
@@ -0,0 +1,154 @@
import request from '@/utils/request';
// 审核查询参数接口
export interface MonthlyReviewQueryParams {
summaryMonth?: string;
studentName?: string;
reviewStatus?: number;
studentNumber?: string;
startDate?: string;
endDate?: string;
}
// 单个审核参数接口
export interface MonthlyReviewParams {
monthlyId: number;
reviewStatus: number; // 1:通过 2:拒绝
reviewScore?: number; // 0-100分
reviewComment: string;
reviewSuggestions?: string;
}
// 批量审核项目接口
export interface BatchReviewItem {
monthlyId: number;
reviewStatus: number;
reviewScore?: number;
reviewComment: string;
reviewSuggestions?: string;
}
// 批量审核参数接口
export interface BatchReviewParams {
reviewItems: BatchReviewItem[];
}
// 审核统计信息接口
export interface ReviewStatistics {
totalCount: number;
pendingCount: number;
approvedCount: number;
rejectedCount: number;
averageScore: number;
completionRate: number;
}
/**
* 查询待审核的月度总结列表
*/
export function getPendingReviewList(params: MonthlyReviewQueryParams) {
return request({
url: '/zsjy/internship/monthly/review/pending',
method: 'get',
params
});
}
/**
* 查询所有审核记录列表
*/
export function getReviewList(params: MonthlyReviewQueryParams) {
return request({
url: '/zsjy/internship/monthly/review/list',
method: 'get',
params
});
}
/**
* 查询审核详情
*/
export function getReviewDetail(monthlyId: number) {
return request({
url: `/zsjy/internship/monthly/review/${monthlyId}`,
method: 'get'
});
}
/**
* 单个审核月度总结
*/
export function approveMonthly(data: MonthlyReviewParams) {
return request({
url: '/zsjy/internship/monthly/review/approve',
method: 'post',
data
});
}
/**
* 单个审核月度总结 (别名)
*/
export function reviewInternshipMonthly(data: MonthlyReviewParams) {
return request({
url: '/zsjy/internship/monthly/review/approve',
method: 'post',
data
});
}
/**
* 批量审核月度总结
*/
export function batchApproveMonthly(data: BatchReviewParams) {
return request({
url: '/zsjy/internship/monthly/review/batch',
method: 'post',
data
});
}
/**
* 批量审核月度总结 (别名)
*/
export function batchReviewInternshipMonthly(data: BatchReviewParams) {
return request({
url: '/zsjy/internship/monthly/review/batch',
method: 'post',
data
});
}
/**
* 获取审核统计信息
*/
export function getReviewStatistics(params?: MonthlyReviewQueryParams) {
return request({
url: '/zsjy/internship/monthly/review/statistics',
method: 'get',
params
});
}
/**
* 查询审核历史记录
*/
export function getReviewHistory(params: MonthlyReviewQueryParams) {
return request({
url: '/zsjy/internship/monthly/review/history',
method: 'get',
params
});
}
/**
* 导出审核记录
*/
export function exportReviewData(params: MonthlyReviewQueryParams) {
return request({
url: '/zsjy/internship/monthly/review/export',
method: 'post',
data: params,
responseType: 'blob'
});
}
+195
View File
@@ -1551,3 +1551,198 @@ export interface InternshipVisitEditBO extends InternshipVisitAddBO {
*/
visitId?: string | number;
}
/**
* 实习月度总结 VO
*/
export interface InternshipMonthlyVO {
/**
* 月度总结ID
*/
id?: string | number;
/**
* 学生ID
*/
studentId?: string | number;
/**
* 学生姓名
*/
studentName?: string;
/**
* 学号
*/
studentNumber?: string;
/**
* 班级名称
*/
className?: string;
/**
* 专业名称
*/
majorName?: string;
/**
* 学院名称
*/
collegeName?: string;
/**
* 总结月份
*/
summaryMonth?: string;
/**
* 工作内容
*/
workContent?: string;
/**
* 学习收获
*/
learningGains?: string;
/**
* 问题与困难
*/
problemsEncountered?: string;
/**
* 改进计划
*/
improvementPlan?: string;
/**
* 下月计划
*/
nextMonthPlan?: string;
/**
* 专业技能评分 (1-10分)
*/
professionalSkills?: number;
/**
* 沟通能力评分 (1-10分)
*/
communicationSkills?: number;
/**
* 团队协作评分 (1-10分)
*/
teamworkAbility?: number;
/**
* 学习能力评分 (1-10分)
*/
learningAbility?: number;
/**
* 工作态度评分 (1-10分)
*/
workAttitude?: number;
/**
* 字数统计
*/
wordCount?: number;
/**
* 审核状态 (0-待审核 1-已通过 2-已拒绝 3-草稿)
*/
reviewStatus?: number;
/**
* 审核人ID
*/
reviewerId?: string | number;
/**
* 审核人姓名
*/
reviewerName?: string;
/**
* 审核评分 (0-100分)
*/
reviewScore?: number;
/**
* 审核意见
*/
reviewComment?: string;
/**
* 改进建议
*/
reviewSuggestions?: string;
/**
* 审核时间
*/
reviewTime?: string;
/**
* 是否逾期
*/
isOverdue?: boolean;
/**
* 创建时间
*/
createTime?: string;
/**
* 创建者
*/
createBy?: number;
/**
* 更新时间
*/
updateTime?: string;
/**
* 更新者
*/
updateBy?: number;
}
/**
* 实习月度总结审核统计 VO
*/
export interface InternshipMonthlyReviewStatisticsVO {
/**
* 总记录数
*/
totalCount?: number;
/**
* 待审核数量
*/
pendingCount?: number;
/**
* 已通过数量
*/
approvedCount?: number;
/**
* 已拒绝数量
*/
rejectedCount?: number;
/**
* 平均评分
*/
averageScore?: number;
/**
* 完成率(百分比)
*/
completionRate?: number;
}
@@ -316,3 +316,4 @@ defineExpose({
white-space: pre-wrap;
}
</style>
@@ -0,0 +1,546 @@
<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-card class="search-card" shadow="never">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
<el-form-item label="总结月份" prop="summaryMonth">
<el-date-picker
v-model="queryParams.summaryMonth"
type="month"
value-format="YYYY-MM"
placeholder="选择月份"
clearable
style="width: 150px"
/>
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<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-form-item>
<el-form-item label="审核状态" prop="reviewStatus">
<el-select v-model="queryParams.reviewStatus" placeholder="请选择状态" clearable style="width: 150px">
<el-option label="待审核" value="0" />
<el-option label="已审核" value="1" />
<el-option label="审核拒绝" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 统计卡片 -->
<el-row :gutter="20" class="mb-4">
<el-col :span="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-item">
<div class="stat-value text-primary">{{ statistics.totalCount || 0 }}</div>
<div class="stat-label">总数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-item">
<div class="stat-value text-warning">{{ statistics.pendingCount || 0 }}</div>
<div class="stat-label">待审核</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-item">
<div class="stat-value text-success">{{ statistics.approvedCount || 0 }}</div>
<div class="stat-label">已通过</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-item">
<div class="stat-value text-danger">{{ statistics.rejectedCount || 0 }}</div>
<div class="stat-label">已拒绝</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 操作栏 -->
<el-card shadow="never">
<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="info" icon="Download" @click="handleExport">导出</el-button>
</div>
</div>
<!-- 数据表格 -->
<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">
<div>
<div class="font-bold">{{ scope.row.studentName }}</div>
<div class="text-sm text-gray-500">{{ scope.row.studentNumber }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="summaryMonth" label="总结月份" width="120" align="center" />
<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"
>
{{ getStatusText(scope.row.reviewStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="reviewScore" label="评分" width="80" align="center">
<template #default="scope">
{{ scope.row.reviewScore || '-' }}
</template>
</el-table-column>
<el-table-column prop="submitTime" label="提交时间" width="160" align="center" />
<el-table-column prop="reviewTime" label="审核时间" width="160" align="center">
<template #default="scope">
{{ scope.row.reviewTime || '-' }}
</template>
</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>
</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"
/>
</el-card>
<!-- 审核对话框 -->
<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">
<el-radio :label="1">
<el-icon><Check /></el-icon>
通过
</el-radio>
<el-radio :label="2">
<el-icon><Close /></el-icon>
拒绝
</el-radio>
</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"
/>
<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-form-item>
<el-form-item label="改进建议" prop="reviewSuggestions">
<el-input
v-model="reviewForm.reviewSuggestions"
type="textarea"
:rows="3"
placeholder="请输入改进建议(选填)"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="reviewVisible = false">取消</el-button>
<el-button type="primary" @click="submitReview" :loading="reviewLoading">确定</el-button>
</template>
</el-dialog>
<!-- 详情对话框 -->
<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 {
getPendingReviewList,
getReviewList,
approveMonthly,
batchApproveMonthly,
getReviewStatistics,
exportReviewData,
type MonthlyReviewQueryParams,
type MonthlyReviewParams,
type BatchReviewParams,
type ReviewStatistics
} 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 queryParams = reactive<MonthlyReviewQueryParams & { pageNum: number; pageSize: number }>({
pageNum: 1,
pageSize: 10,
summaryMonth: '',
studentName: '',
studentNumber: '',
reviewStatus: undefined
})
// 统计数据
const statistics = ref<ReviewStatistics>({
totalCount: 0,
pendingCount: 0,
approvedCount: 0,
rejectedCount: 0,
averageScore: 0,
completionRate: 0
})
// 审核表单
const reviewForm = reactive<MonthlyReviewParams & { isBatch?: boolean; monthlyIds?: number[] }>({
monthlyId: 0,
reviewStatus: 1,
reviewScore: undefined,
reviewComment: '',
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 getList = async () => {
loading.value = true
try {
const response = await getReviewList(queryParams)
reviewList.value = response.rows
total.value = response.total
} catch (error) {
console.error('获取审核列表失败:', error)
} finally {
loading.value = false
}
}
// 获取统计数据
const getStatistics = async () => {
try {
const response = await getReviewStatistics(queryParams)
statistics.value = response.data
} catch (error) {
console.error('获取统计数据失败:', error)
}
}
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1
getList()
getStatistics()
}
// 重置搜索
const resetQuery = () => {
queryFormRef.value?.resetFields()
queryParams.pageNum = 1
getList()
getStatistics()
}
// 选择变化
const handleSelectionChange = (selection: any[]) => {
selections.value = selection
}
// 查看详情
const handleDetail = (row: any) => {
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
}
// 重新审核
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
}
// 批量通过
const handleBatchApprove = () => {
const pendingItems = selections.value.filter(item => item.reviewStatus === 0)
if (pendingItems.length === 0) {
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
}
// 批量拒绝
const handleBatchReject = () => {
const pendingItems = selections.value.filter(item => item.reviewStatus === 0)
if (pendingItems.length === 0) {
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
}
// 提交审核
const submitReview = async () => {
try {
await reviewFormRef.value?.validate()
reviewLoading.value = true
if (reviewForm.isBatch) {
// 批量审核
const batchData: BatchReviewParams = {
reviewItems: reviewForm.monthlyIds!.map(monthlyId => ({
monthlyId,
reviewStatus: reviewForm.reviewStatus,
reviewScore: reviewForm.reviewScore,
reviewComment: reviewForm.reviewComment,
reviewSuggestions: reviewForm.reviewSuggestions
}))
}
await batchApproveMonthly(batchData)
ElMessage.success('批量审核成功')
} else {
// 单个审核
await approveMonthly({
monthlyId: reviewForm.monthlyId,
reviewStatus: reviewForm.reviewStatus,
reviewScore: reviewForm.reviewScore,
reviewComment: reviewForm.reviewComment,
reviewSuggestions: reviewForm.reviewSuggestions
})
ElMessage.success('审核成功')
}
reviewVisible.value = false
getList()
getStatistics()
} catch (error) {
console.error('审核失败:', error)
} finally {
reviewLoading.value = false
}
}
// 导出数据
const handleExport = async () => {
try {
await exportReviewData(queryParams)
ElMessage.success('导出成功')
} catch (error) {
console.error('导出失败:', error)
}
}
// 获取状态类型
const getStatusType = (status: number) => {
const statusMap: Record<number, string> = {
0: 'warning',
1: 'success',
2: 'danger'
}
return statusMap[status] || 'info'
}
// 获取状态文本
const getStatusText = (status: number) => {
const statusMap: Record<number, string> = {
0: '待审核',
1: '已审核',
2: '审核拒绝'
}
return statusMap[status] || '未知'
}
// 组件挂载
onMounted(() => {
getList()
getStatistics()
})
</script>
<style scoped lang="scss">
.search-card {
margin-bottom: 16px;
}
.stat-card {
text-align: center;
.stat-item {
.stat-value {
font-size: 28px;
font-weight: bold;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: #666;
}
}
}
.table-header {
display: flex;
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>
@@ -0,0 +1,395 @@
<template>
<div class="batch-review-form">
<!-- 批量设置区域 -->
<el-card shadow="never" class="mb-4">
<template #header>
<span class="card-title">批量设置</span>
<el-button type="text" @click="showBatchSetting = !showBatchSetting">
{{ showBatchSetting ? '收起' : '展开' }}
</el-button>
</template>
<div v-show="showBatchSetting">
<el-form :model="batchForm" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="统一审核结果">
<el-radio-group v-model="batchForm.reviewStatus">
<el-radio :label="1">通过</el-radio>
<el-radio :label="2">拒绝</el-radio>
</el-radio-group>
</el-form-item>
</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-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-form-item>
<el-form-item label="统一改进建议">
<el-input
v-model="batchForm.reviewSuggestions"
type="textarea"
:rows="2"
placeholder="输入统一的改进建议"
maxlength="300"
show-word-limit
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="applyBatchSettings">应用到所有</el-button>
<el-button @click="clearBatchSettings">清空设置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 个别审核区域 -->
<el-card shadow="never">
<template #header>
<span class="card-title">个别审核 ({{ selectedRows.length }}项)</span>
</template>
<div class="review-list">
<div v-for="(item, index) in reviewItems" :key="item.monthlyId" class="review-item">
<el-card shadow="hover" class="item-card">
<template #header>
<div class="item-header">
<span class="student-info">
<strong>{{ item.studentName }}</strong>
({{ item.studentNumber }}) - {{ item.summaryMonth }}
</span>
<span class="class-info">{{ item.className }}</span>
</div>
</template>
<el-form :model="item" label-width="100px" size="small">
<el-row :gutter="15">
<el-col :span="8">
<el-form-item label="审核结果" :prop="`reviewItems.${index}.reviewStatus`" required>
<el-radio-group v-model="item.reviewStatus">
<el-radio :label="1">
<el-icon color="#67c23a"><SuccessFilled /></el-icon>
通过
</el-radio>
<el-radio :label="2">
<el-icon color="#f56c6c"><CircleCloseFilled /></el-icon>
拒绝
</el-radio>
</el-radio-group>
</el-form-item>
</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-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="等级">
<el-tag :type="getScoreTagType(item.reviewScore)">
{{ getScoreLevel(item.reviewScore) }}
</el-tag>
</el-form-item>
</el-col>
</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-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-form-item>
<!-- 快速操作 -->
<el-form-item label="快速设置">
<el-button-group>
<el-button size="small" @click="setQuickReview(index, 1, 95, '总结内容详实,表现优秀')">优秀</el-button>
<el-button size="small" @click="setQuickReview(index, 1, 85, '总结内容较好,符合要求')">良好</el-button>
<el-button size="small" @click="setQuickReview(index, 1, 75, '总结内容基本符合要求')">中等</el-button>
<el-button size="small" @click="setQuickReview(index, 2, 60, '总结内容需要改进')">需改进</el-button>
</el-button-group>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
<!-- 操作按钮 -->
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="submitBatchReview" :loading="submitting">
<el-icon><Check /></el-icon>
确认批量审核
</el-button>
</div>
</el-card>
</div>
</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'
interface Props {
selectedRows: any[]
}
const props = defineProps<Props>()
const emit = defineEmits(['refresh'])
// 状态管理
const submitting = ref(false)
const showBatchSetting = ref(true)
// 批量设置表单
const batchForm = reactive({
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 '不及格'
}
/** 获取分数标签类型 */
function getScoreTagType(score: number) {
if (score >= 90) return 'success'
if (score >= 80) return 'warning'
if (score >= 70) return 'primary'
return 'danger'
}
/** 应用批量设置 */
function applyBatchSettings() {
ElMessageBox.confirm('确认将批量设置应用到所有选中的记录?', '确认操作', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
reviewItems.value.forEach(item => {
if (batchForm.reviewStatus) {
item.reviewStatus = batchForm.reviewStatus
}
if (batchForm.reviewScore !== null) {
item.reviewScore = batchForm.reviewScore
}
if (batchForm.reviewComment) {
item.reviewComment = batchForm.reviewComment
}
if (batchForm.reviewSuggestions) {
item.reviewSuggestions = batchForm.reviewSuggestions
}
})
ElMessage.success('批量设置已应用')
})
}
/** 清空批量设置 */
function clearBatchSettings() {
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
}
/** 验证表单 */
function validateForm() {
for (let i = 0; i < reviewItems.value.length; i++) {
const item = reviewItems.value[i]
if (!item.reviewStatus) {
ElMessage.error(`请为 ${item.studentName} 选择审核结果`)
return false
}
if (!item.reviewComment || item.reviewComment.trim().length < 10) {
ElMessage.error(`请为 ${item.studentName} 输入审核意见(至少10个字符)`)
return false
}
}
return true
}
/** 提交批量审核 */
function submitBatchReview() {
if (!validateForm()) {
return
}
ElMessageBox.confirm(`确认批量审核 ${reviewItems.value.length} 条记录?`, '确认批量审核', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
submitting.value = true
const reviewData = {
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
})
})
}
/** 取消操作 */
function handleCancel() {
emit('refresh')
}
</script>
<style scoped>
.batch-review-form {
max-height: 70vh;
overflow-y: auto;
}
.card-title {
font-weight: bold;
font-size: 16px;
color: #303133;
}
.review-list {
max-height: 50vh;
overflow-y: auto;
}
.review-item {
margin-bottom: 15px;
}
.item-card {
border-radius: 8px;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.student-info {
font-weight: bold;
color: #303133;
}
.class-info {
color: #909399;
font-size: 12px;
}
.dialog-footer {
text-align: center;
padding-top: 20px;
border-top: 1px solid #ebeef5;
margin-top: 20px;
}
.mb-4 {
margin-bottom: 16px;
}
/* 滚动条样式 */
.review-list::-webkit-scrollbar {
width: 6px;
}
.review-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.review-list::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.review-list::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>
@@ -0,0 +1,330 @@
<template>
<div class="review-detail">
<!-- 基本信息 -->
<el-card shadow="never" class="mb-4">
<template #header>
<span class="card-title">基本信息</span>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="学生姓名">{{ monthlyData.studentName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ monthlyData.studentNumber }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ monthlyData.className }}</el-descriptions-item>
<el-descriptions-item label="专业">{{ monthlyData.majorName }}</el-descriptions-item>
<el-descriptions-item label="总结月份">{{ monthlyData.summaryMonth }}</el-descriptions-item>
<el-descriptions-item label="字数统计">{{ monthlyData.wordCount || 0 }}字</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ monthlyData.createTime }}</el-descriptions-item>
<el-descriptions-item label="审核状态">
<el-tag :type="getReviewStatusTagType(monthlyData.reviewStatus)">
{{ getReviewStatusText(monthlyData.reviewStatus) }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 总结内容 -->
<el-card shadow="never" class="mb-4">
<template #header>
<span class="card-title">总结内容</span>
</template>
<!-- 工作内容 -->
<div class="content-section">
<h4 class="section-title">工作内容</h4>
<div class="content-text">{{ monthlyData.workContent || '暂无内容' }}</div>
</div>
<!-- 学习收获 -->
<div class="content-section">
<h4 class="section-title">学习收获</h4>
<div class="content-text">{{ monthlyData.learningGains || '暂无内容' }}</div>
</div>
<!-- 问题与困难 -->
<div class="content-section">
<h4 class="section-title">问题与困难</h4>
<div class="content-text">{{ monthlyData.problemsEncountered || '暂无内容' }}</div>
</div>
<!-- 改进计划 -->
<div class="content-section">
<h4 class="section-title">改进计划</h4>
<div class="content-text">{{ monthlyData.improvementPlan || '暂无内容' }}</div>
</div>
<!-- 下月计划 -->
<div class="content-section">
<h4 class="section-title">下月计划</h4>
<div class="content-text">{{ monthlyData.nextMonthPlan || '暂无内容' }}</div>
</div>
</el-card>
<!-- 自我评分 -->
<el-card shadow="never" class="mb-4">
<template #header>
<span class="card-title">自我评分</span>
</template>
<el-row :gutter="20">
<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"
/>
<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"
/>
<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"
/>
<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"
/>
<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"
/>
<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>
</div>
</el-col>
</el-row>
</el-card>
<!-- 审核信息 -->
<el-card v-if="monthlyData.reviewStatus !== 0" shadow="never" class="mb-4">
<template #header>
<span class="card-title">审核信息</span>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="审核状态">
<el-tag :type="getReviewStatusTagType(monthlyData.reviewStatus)">
{{ getReviewStatusText(monthlyData.reviewStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核评分">
<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>
</el-descriptions-item>
<el-descriptions-item label="审核人">{{ monthlyData.reviewerName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ monthlyData.reviewTime || '暂无' }}</el-descriptions-item>
</el-descriptions>
<!-- 审核意见 -->
<div v-if="monthlyData.reviewComment" class="mt-4">
<h4 class="section-title">审核意见</h4>
<div class="content-text review-comment">{{ monthlyData.reviewComment }}</div>
</div>
<!-- 改进建议 -->
<div v-if="monthlyData.reviewSuggestions" class="mt-4">
<h4 class="section-title">改进建议</h4>
<div class="content-text review-suggestions">{{ monthlyData.reviewSuggestions }}</div>
</div>
</el-card>
<!-- 操作按钮 -->
<div class="dialog-footer" v-if="monthlyData.reviewStatus === 0">
<el-button type="success" @click="handleReview">立即审核</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
interface Props {
monthlyData: any
}
const props = defineProps<Props>()
const emit = defineEmits(['refresh', 'review'])
/** 获取平均分 */
function getAverageScore() {
const scores = [
props.monthlyData.professionalSkills || 0,
props.monthlyData.communicationSkills || 0,
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
}
/** 获取评分标签类型 */
function getScoreTagType(score: number) {
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 '未知'
}
}
/** 获取审核状态标签类型 */
function getReviewStatusTagType(status: number) {
switch (status) {
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'
}
/** 审核按钮点击 */
function handleReview() {
emit('review', props.monthlyData)
}
</script>
<style scoped>
.review-detail {
max-height: 70vh;
overflow-y: auto;
}
.card-title {
font-weight: bold;
font-size: 16px;
color: #303133;
}
.content-section {
margin-bottom: 20px;
}
.section-title {
font-size: 14px;
font-weight: bold;
color: #606266;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 2px solid #f0f0f0;
}
.content-text {
background-color: #f8f9fa;
padding: 15px;
border-radius: 6px;
line-height: 1.6;
color: #606266;
white-space: pre-wrap;
word-break: break-word;
min-height: 60px;
}
.review-comment {
background-color: #f0f9ff;
border-left: 4px solid #409eff;
}
.review-suggestions {
background-color: #f9f9f9;
border-left: 4px solid #67c23a;
}
.score-item {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 10px;
background-color: #fafafa;
border-radius: 6px;
}
.score-label {
font-weight: bold;
color: #606266;
width: 80px;
margin-right: 15px;
}
.score-value {
margin-left: 10px;
font-weight: bold;
color: #409eff;
}
.dialog-footer {
text-align: center;
padding-top: 20px;
border-top: 1px solid #ebeef5;
}
.mb-4 {
margin-bottom: 16px;
}
.mt-4 {
margin-top: 16px;
}
.text-gray-400 {
color: #909399;
}
</style>
@@ -0,0 +1,383 @@
<template>
<div class="review-form">
<!-- 学生信息展示 -->
<el-card shadow="never" class="mb-4">
<template #header>
<span class="card-title">审核对象</span>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="学生姓名">{{ monthlyData.studentName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ monthlyData.studentNumber }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ monthlyData.className }}</el-descriptions-item>
<el-descriptions-item label="总结月份">{{ monthlyData.summaryMonth }}</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 审核表单 -->
<el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="100px">
<!-- 审核结果 -->
<el-form-item label="审核结果" prop="reviewStatus" required>
<el-radio-group v-model="reviewForm.reviewStatus">
<el-radio :label="1">
<el-icon color="#67c23a"><SuccessFilled /></el-icon>
通过
</el-radio>
<el-radio :label="2">
<el-icon color="#f56c6c"><CircleCloseFilled /></el-icon>
拒绝
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 审核评分 -->
<el-form-item label="审核评分" prop="reviewScore">
<div class="score-container">
<el-slider
v-model="reviewForm.reviewScore"
:min="0"
:max="100"
show-stops
:step="5"
show-input
:format-tooltip="formatTooltip"
style="width: 80%"
/>
<span class="score-text">{{ getScoreLevel(reviewForm.reviewScore) }}</span>
</div>
<div class="score-description">
<span class="desc-item">优秀(90-100)</span>
<span class="desc-item">良好(80-89)</span>
<span class="desc-item">中等(70-79)</span>
<span class="desc-item">及格(60-69)</span>
<span class="desc-item">不及格(0-59)</span>
</div>
</el-form-item>
<!-- 审核意见 -->
<el-form-item label="审核意见" prop="reviewComment" required>
<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
v-model="reviewForm.reviewSuggestions"
type="textarea"
:rows="3"
placeholder="请输入改进建议,为学生后续的实习和总结提供指导意见..."
maxlength="300"
show-word-limit
/>
</el-form-item>
<!-- 快速评语模板 -->
<el-form-item label="快速评语">
<div class="template-container">
<el-tag
v-for="template in commentTemplates"
:key="template.type"
:type="template.type"
effect="plain"
class="template-tag"
@click="insertTemplate(template.content)"
>
{{ template.label }}
</el-tag>
</div>
<div class="template-tip">
<el-icon><InfoFilled /></el-icon>
点击上方标签可快速插入评语模板
</div>
</el-form-item>
<!-- 操作按钮 -->
<el-form-item>
<el-button type="primary" @click="submitReview" :loading="submitting">
<el-icon><Check /></el-icon>
确认审核
</el-button>
<el-button @click="resetForm">
<el-icon><Refresh /></el-icon>
重置
</el-button>
<el-button @click="previewReview">
<el-icon><View /></el-icon>
预览
</el-button>
</el-form-item>
</el-form>
<!-- 预览对话框 -->
<el-dialog v-model="previewOpen" title="审核预览" width="500px" append-to-body>
<div class="preview-content">
<el-descriptions :column="1" border>
<el-descriptions-item label="审核结果">
<el-tag :type="reviewForm.reviewStatus === 1 ? 'success' : 'danger'">
{{ reviewForm.reviewStatus === 1 ? '通过' : '拒绝' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核评分">
<el-tag :type="getScoreTagType(reviewForm.reviewScore)">
{{ reviewForm.reviewScore }}分 ({{ getScoreLevel(reviewForm.reviewScore) }})
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审核意见">
<div class="preview-text">{{ reviewForm.reviewComment || '暂无' }}</div>
</el-descriptions-item>
<el-descriptions-item label="改进建议">
<div class="preview-text">{{ reviewForm.reviewSuggestions || '暂无' }}</div>
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<el-button @click="previewOpen = false">关闭</el-button>
<el-button type="primary" @click="confirmSubmit">确认提交</el-button>
</template>
</el-dialog>
</div>
</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'
interface Props {
monthlyData: any
}
const props = defineProps<Props>()
const emit = defineEmits(['refresh'])
// 表单引用
const reviewFormRef = ref()
// 表单数据
const reviewForm = reactive({
monthlyId: props.monthlyData.monthlyId,
reviewStatus: 1, // 默认通过
reviewScore: 85, // 默认85分
reviewComment: '',
reviewSuggestions: ''
})
// 表单验证规则
const reviewRules = {
reviewStatus: [
{ required: true, message: '请选择审核结果', trigger: 'change' }
],
reviewScore: [
{ required: true, message: '请输入审核评分', trigger: 'blur' },
{ type: 'number', min: 0, max: 100, message: '评分范围为0-100分', trigger: 'blur' }
],
reviewComment: [
{ required: true, message: '请输入审核意见', trigger: 'blur' },
{ min: 10, max: 500, message: '审核意见长度在10到500个字符', trigger: 'blur' }
],
reviewSuggestions: [
{ max: 300, message: '改进建议长度不能超过300个字符', trigger: 'blur' }
]
}
// 状态管理
const submitting = ref(false)
const previewOpen = ref(false)
// 评语模板
const commentTemplates = [
{
type: 'success',
label: '优秀评语',
content: '总结内容详实,逻辑清晰,能够很好地反映实习期间的工作情况和学习收获。自我评价客观,对遇到的问题有深入思考,改进计划切实可行。'
},
{
type: 'warning',
label: '良好评语',
content: '总结内容较为完整,基本能够反映实习情况。部分内容需要进一步完善,建议在今后的总结中更加注重细节描述和深度思考。'
},
{
type: 'primary',
label: '中等评语',
content: '总结内容基本符合要求,但在深度和完整性方面还有提升空间。建议多关注工作中的具体细节和个人成长感悟。'
},
{
type: 'danger',
label: '需改进',
content: '总结内容过于简单,缺乏具体的工作描述和深入的思考。请重新整理总结内容,确保内容的完整性和深度。'
}
]
/** 格式化滑块提示 */
function formatTooltip(value: number) {
return `${value}分`
}
/** 获取分数等级 */
function getScoreLevel(score: number) {
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'
}
/** 插入评语模板 */
function insertTemplate(template: string) {
if (reviewForm.reviewComment) {
reviewForm.reviewComment += '\n\n' + template
} else {
reviewForm.reviewComment = template
}
}
/** 重置表单 */
function resetForm() {
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
}
})
}
/** 提交审核 */
function submitReview() {
reviewFormRef.value?.validate((valid: boolean) => {
if (valid) {
const message = reviewForm.reviewStatus === 1 ? '确认通过此月度总结?' : '确认拒绝此月度总结?'
ElMessageBox.confirm(message, '确认审核', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
confirmSubmit()
})
}
})
}
/** 确认提交 */
function confirmSubmit() {
submitting.value = true
previewOpen.value = false
reviewInternshipMonthly(reviewForm).then(() => {
ElMessage.success('审核完成')
emit('refresh')
}).catch(() => {
ElMessage.error('审核失败')
}).finally(() => {
submitting.value = false
})
}
</script>
<style scoped>
.review-form {
max-height: 70vh;
overflow-y: auto;
}
.card-title {
font-weight: bold;
font-size: 16px;
color: #303133;
}
.score-container {
display: flex;
align-items: center;
gap: 20px;
}
.score-text {
font-weight: bold;
color: #409eff;
min-width: 60px;
}
.score-description {
margin-top: 10px;
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.desc-item {
font-size: 12px;
color: #909399;
padding: 2px 8px;
background-color: #f5f7fa;
border-radius: 4px;
}
.template-container {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.template-tag {
cursor: pointer;
transition: all 0.3s ease;
}
.template-tag:hover {
transform: translateY(-2px);
}
.template-tip {
font-size: 12px;
color: #909399;
display: flex;
align-items: center;
gap: 5px;
}
.preview-content {
max-height: 400px;
overflow-y: auto;
}
.preview-text {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.6;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
@@ -0,0 +1,589 @@
<template>
<div class="p-2 page-container">
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<!-- 第一行:搜索条件 -->
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="总结月份" prop="summaryMonth">
<el-date-picker
v-model="queryParams.summaryMonth"
type="month"
value-format="YYYY-MM"
placeholder="请选择总结月份"
clearable
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学号" prop="studentNumber">
<el-input v-model="queryParams.studentNumber" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审核状态" prop="reviewStatus">
<el-select v-model="queryParams.reviewStatus" placeholder="请选择审核状态" clearable style="width: 100%">
<el-option label="待审核" :value="0" />
<el-option label="已通过" :value="1" />
<el-option label="已拒绝" :value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行:时间范围 + 操作按钮 -->
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="创建时间">
<el-date-picker
v-model="daterangeCreateTime"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="handleQuery"
style="width: 100%"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<!-- 统计卡片区域 -->
<div class="mb-[10px]">
<el-row :gutter="16">
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-primary">
<div class="stat-content">
<div class="stat-icon">
<el-icon><DocumentChecked /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.totalCount }}</div>
<div class="stat-label">总记录数</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-warning">
<div class="stat-content">
<div class="stat-icon">
<el-icon><Clock /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.pendingCount }}</div>
<div class="stat-label">待审核</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-success">
<div class="stat-content">
<div class="stat-icon">
<el-icon><SuccessFilled /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.approvedCount }}</div>
<div class="stat-label">已通过</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-card-danger">
<div class="stat-content">
<div class="stat-icon">
<el-icon><CircleCloseFilled /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.rejectedCount }}</div>
<div class="stat-label">已拒绝</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 表格区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Check"
:disabled="multiple"
@click="handleBatchReview"
v-hasPermi="['zsjy:internshipmonthly:review']"
>批量审核</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<div class="table-container">
<el-table
v-loading="loading"
border
:data="reviewList"
@selection-change="handleSelectionChange"
@row-dblclick="handleDetail"
style="width: 100%"
:scroll-x="true"
class="scroll-table"
>
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column label="学生姓名" align="center" prop="studentName" width="120" show-overflow-tooltip fixed="left" />
<el-table-column label="学号" align="center" prop="studentNumber" width="140" show-overflow-tooltip />
<el-table-column label="总结月份" align="center" prop="summaryMonth" width="120" />
<el-table-column label="班级" align="center" prop="className" width="150" show-overflow-tooltip />
<el-table-column label="专业" align="center" prop="majorName" width="150" show-overflow-tooltip />
<el-table-column label="工作内容" align="center" prop="workContent" min-width="200" show-overflow-tooltip />
<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-tooltip>
</template>
</el-table-column>
<el-table-column label="字数统计" align="center" width="100">
<template #default="scope">
{{ scope.row.wordCount || 0 }}字
</template>
</el-table-column>
<el-table-column label="审核状态" align="center" prop="reviewStatus" width="100">
<template #default="scope">
<el-tag :type="getReviewStatusTagType(scope.row.reviewStatus)">
{{ getReviewStatusText(scope.row.reviewStatus) }}
</el-tag>
</template>
</el-table-column>
<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>
</span>
<span v-else class="text-gray-400">未评分</span>
</template>
</el-table-column>
<el-table-column label="审核时间" align="center" prop="reviewTime" width="150" />
<el-table-column label="创建时间" align="center" prop="createTime" width="150" />
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)">查看</el-button>
<el-button
v-if="scope.row.reviewStatus === 0"
link
type="success"
icon="Check"
@click="handleReview(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:review']"
>审核</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"
/>
</el-card>
<!-- 审核详情对话框 -->
<el-dialog v-model="detailOpen" :title="detailTitle" width="1200px" append-to-body>
<ReviewDetail ref="reviewDetailRef" :monthly-data="currentMonthlyData" @refresh="getList" />
</el-dialog>
<!-- 单个审核对话框 -->
<el-dialog v-model="reviewOpen" title="审核月度总结" width="600px" append-to-body>
<ReviewForm ref="reviewFormRef" :monthly-data="currentMonthlyData" @refresh="handleReviewSuccess" />
</el-dialog>
<!-- 批量审核对话框 -->
<el-dialog v-model="batchReviewOpen" title="批量审核" width="800px" append-to-body>
<BatchReviewForm ref="batchReviewFormRef" :selected-rows="multipleSelection" @refresh="handleBatchReviewSuccess" />
</el-dialog>
</div>
</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'
const { proxy } = getCurrentInstance() as any
// 数据状态
const data = reactive({
loading: true,
showSearch: true,
reviewList: [] as any[],
multipleSelection: [] as any[],
single: true,
multiple: true,
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
summaryMonth: undefined,
studentName: undefined,
studentNumber: undefined,
reviewStatus: undefined,
startDate: undefined,
endDate: undefined
},
// 日期范围
daterangeCreateTime: [] as any[],
// 统计数据
statistics: {
totalCount: 0,
pendingCount: 0,
approvedCount: 0,
rejectedCount: 0,
averageScore: 0,
completionRate: 0
}
})
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 queryFormRef = ref()
const reviewDetailRef = ref()
const reviewFormRef = ref()
const batchReviewFormRef = ref()
/** 查询列表 */
function getList() {
data.loading = true
// 处理时间范围
if (data.daterangeCreateTime && data.daterangeCreateTime.length === 2) {
data.queryParams.startDate = data.daterangeCreateTime[0]
data.queryParams.endDate = data.daterangeCreateTime[1]
} else {
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
})
}
/** 获取统计数据 */
function getStatistics() {
getReviewStatistics().then((response: any) => {
data.statistics = response.data
})
}
/** 搜索按钮操作 */
function handleQuery() {
data.queryParams.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
data.daterangeCreateTime = []
queryFormRef.value?.resetFields()
handleQuery()
}
/** 多选框选中数据 */
function handleSelectionChange(selection: any[]) {
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
}
/** 单个审核 */
function handleReview(row: any) {
currentMonthlyData.value = { ...row }
reviewOpen.value = true
}
/** 批量审核 */
function handleBatchReview() {
if (data.multipleSelection.length === 0) {
ElMessage.warning('请选择要审核的数据')
return
}
// 检查是否都是待审核状态
const pendingRows = data.multipleSelection.filter(row => row.reviewStatus === 0)
if (pendingRows.length === 0) {
ElMessage.warning('选择的数据中没有待审核的记录')
return
}
if (pendingRows.length !== data.multipleSelection.length) {
ElMessage.warning('只能审核状态为"待审核"的记录,请重新选择')
return
}
batchReviewOpen.value = true
}
/** 审核成功回调 */
function handleReviewSuccess() {
reviewOpen.value = false
getList()
getStatistics()
ElMessage.success('审核完成')
}
/** 批量审核成功回调 */
function handleBatchReviewSuccess() {
batchReviewOpen.value = false
getList()
getStatistics()
ElMessage.success('批量审核完成')
}
/** 获取平均分 */
function getAverageScore(row: any) {
const scores = [
row.professionalSkills || 0,
row.communicationSkills || 0,
row.teamworkSkills || 0,
row.learningAbility || 0,
row.workAttitude || 0
]
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'
}
/** 获取审核状态文本 */
function getReviewStatusText(status: number) {
switch (status) {
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'
}
}
/** 获取审核评分标签类型 */
function getReviewScoreTagType(score: number) {
if (score >= 90) return 'success'
if (score >= 80) return 'warning'
if (score >= 70) return 'primary'
return 'danger'
}
onMounted(() => {
getList()
getStatistics()
})
</script>
<style scoped>
.page-container {
background-color: #f5f7fa;
min-height: calc(100vh - 50px);
}
.search-btn-container {
display: flex;
justify-content: flex-end;
}
/* 统计卡片样式 */
.stat-card {
border: none;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
}
.stat-content {
display: flex;
align-items: center;
padding: 20px;
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
font-size: 24px;
color: white;
}
.stat-info {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: bold;
line-height: 1;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: #666;
line-height: 1;
}
.stat-card-primary .stat-icon {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card-primary .stat-value {
color: #667eea;
}
.stat-card-success .stat-icon {
background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%);
}
.stat-card-success .stat-value {
color: #4ecdc4;
}
.stat-card-warning .stat-icon {
background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 100%);
}
.stat-card-warning .stat-value {
color: #e17055;
}
.stat-card-danger .stat-icon {
background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%);
}
.stat-card-danger .stat-value {
color: #e84393;
}
/* 表格样式 */
.table-card {
border-radius: 8px;
overflow: hidden;
}
.table-container {
background: white;
border-radius: 8px;
}
.scroll-table {
border-radius: 8px;
}
/* 按钮卡片样式 */
.button-card {
border: 1px solid #ebeef5;
border-radius: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>