This commit is contained in:
zlwu committed 2025-08-17 23:05:59 +08:00
commit c9f3687c70
20 files changed
+3322 -2

No files matched your search

@@ -157,7 +157,7 @@ public class InternshipInfoController extends BaseController {
}
/**
* 修改实习信息
* 33
*/
@Operation(summary = "修改实习信息", description = "修改学生实习信息")
@SaCheckPermission("zsjy:internship:edit")
@@ -202,4 +202,27 @@ public class InternshipInfoController extends BaseController {
return R.fail("删除实习信息失败:" + e.getMessage());
}
}
/**
* 获取当前学生的实习信息
*/
@Operation(summary = "获取当前学生实习信息", description = "获取当前登录学生的实习信息,用于月度总结等功能")
@SaCheckPermission("zsjy:internship:query")
@GetMapping("/current")
public R<InternshipInfoVO> getCurrentStudentInternship() {
try {
log.info("=== 获取当前学生实习信息接口被调用 ===");
InternshipInfoVO result = internshipInfoService.getCurrentStudentInternship();
if (result != null) {
log.info("成功获取当前学生实习信息,实习ID:{}", result.getInternshipId());
return R.ok(result);
} else {
log.info("当前学生没有实习信息");
return R.ok(); // 返回空数据,前端需要处理这种情况
}
} catch (Exception e) {
log.error("获取当前学生实习信息失败:{}", e.getMessage(), e);
return R.fail("获取实习信息失败:" + e.getMessage());
}
}
}
@@ -0,0 +1,238 @@
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.core.validate.EditGroup;
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.ZsjyInternshipMonthlyAddBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import org.dromara.zsjy.service.IZsjyInternshipMonthlyService;
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-03
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/zsjy/internship/monthly")
@Tag(name = "学生月度实习总结", description = "学生月度实习总结填写管理")
public class ZsjyInternshipMonthlyController extends BaseController {
private final IZsjyInternshipMonthlyService monthlyService;
/**
* 查询学生月度实习总结列表
*/
@Operation(summary = "查询学生月度实习总结列表", description = "学生查看自己的月度总结列表")
@SaCheckPermission("zsjy:internshipmonthly:list")
@GetMapping("/list")
public TableDataInfo<ZsjyInternshipMonthlyVo> list(@Validated ZsjyInternshipMonthlyQueryBo queryBo, PageQuery pageQuery) {
log.info("=== Controller层调试 ===");
log.info("接口被调用: /zsjy/internship/monthly/list");
log.info("查询参数: {}", queryBo);
log.info("分页参数: {}", pageQuery);
return monthlyService.queryPageList(queryBo, pageQuery);
}
/**
* 查询学生月度实习总结详细信息
*/
@Operation(summary = "查询学生月度实习总结详细信息", description = "根据ID查询月度总结详情")
@SaCheckPermission("zsjy:internshipmonthly:query")
@GetMapping("/{monthlyId}")
public R<ZsjyInternshipMonthlyVo> getInfo(@Parameter(description = "月度总结ID")
@PathVariable Long monthlyId) {
log.info("查询月度总结详情, ID: {}", monthlyId);
return R.ok(monthlyService.queryById(monthlyId));
}
/**
* 新增学生月度实习总结
*/
@Operation(summary = "新增学生月度实习总结", description = "学生创建新的月度总结")
@SaCheckPermission("zsjy:internshipmonthly:add")
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody ZsjyInternshipMonthlyAddBo addBo) {
log.info("新增月度总结: {}", addBo);
if (!monthlyService.insertByBo(addBo)) {
return R.fail("新增月度总结失败");
}
return R.ok();
}
/**
* 修改学生月度实习总结
*/
@Operation(summary = "修改学生月度实习总结", description = "学生编辑自己的月度总结")
@SaCheckPermission("zsjy:internshipmonthly:edit")
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ZsjyInternshipMonthlyAddBo addBo) {
log.info("修改月度总结: {}", addBo);
if (!monthlyService.updateByBo(addBo)) {
return R.fail("修改月度总结失败");
}
return R.ok();
}
/**
* 删除学生月度实习总结
*/
@Operation(summary = "删除学生月度实习总结", description = "学生删除自己的草稿状态月度总结")
@SaCheckPermission("zsjy:internshipmonthly:remove")
@DeleteMapping("/{monthlyIds}")
public R<Void> remove(@Parameter(description = "月度总结ID串")
@PathVariable Long[] monthlyIds) {
log.info("删除月度总结, IDs: {}", (Object) monthlyIds);
if (!monthlyService.deleteByIds(List.of(monthlyIds))) {
return R.fail("删除月度总结失败");
}
return R.ok();
}
/**
* 提交月度总结审批
*/
@Operation(summary = "提交月度总结审批", description = "学生提交月度总结给班主任审批")
@SaCheckPermission("zsjy:internshipmonthly:submit")
@PutMapping("/submit/{monthlyId}")
public R<Void> submit(@Parameter(description = "月度总结ID")
@PathVariable Long monthlyId) {
log.info("提交月度总结审批, ID: {}", monthlyId);
if (!monthlyService.submitForApproval(monthlyId)) {
return R.fail("提交月度总结失败");
}
return R.ok();
}
/**
* 自动保存草稿
*/
@Operation(summary = "自动保存草稿", description = "学生编辑时自动保存草稿")
@SaCheckPermission("zsjy:internshipmonthly:autosave")
@PostMapping("/autosave")
public R<Void> autoSave(@RequestBody ZsjyInternshipMonthlyAddBo addBo) {
log.info("自动保存草稿: {}", addBo);
if (!monthlyService.autoSaveDraft(addBo)) {
return R.fail("自动保存失败");
}
return R.ok();
}
/**
* 检查月份是否存在总结
*/
@Operation(summary = "检查月份是否存在总结", description = "检查学生某月是否已有总结记录")
@SaCheckPermission("zsjy:internshipmonthly:check")
@GetMapping("/check/{summaryMonth}")
public R<Boolean> checkMonth(@Parameter(description = "总结月份")
@PathVariable String summaryMonth,
@Parameter(description = "排除的总结ID")
@RequestParam(required = false) Long monthlyId) {
log.info("检查月份总结, 月份: {}, 排除ID: {}", summaryMonth, monthlyId);
Boolean exists = monthlyService.checkMonthExists(null, summaryMonth, monthlyId);
return R.ok(exists);
}
/**
* 获取月度总结模板
*/
@Operation(summary = "获取月度总结模板", description = "获取指定月份的总结模板信息")
@SaCheckPermission("zsjy:internshipmonthly:template")
@GetMapping("/template/{summaryMonth}")
public R<ZsjyInternshipMonthlyVo> getTemplate(@Parameter(description = "总结月份")
@PathVariable String summaryMonth) {
log.info("获取月度总结模板, 月份: {}", summaryMonth);
ZsjyInternshipMonthlyVo template = monthlyService.getMonthlyTemplate(summaryMonth);
return R.ok(template);
}
/**
* 查询学生指定实习的月度总结列表
*/
@Operation(summary = "查询学生指定实习的月度总结列表", description = "查询学生某个实习的所有月度总结")
@SaCheckPermission("zsjy:internshipmonthly:list")
@GetMapping("/internship/{internshipId}")
public R<List<ZsjyInternshipMonthlyVo>> listByInternship(@Parameter(description = "实习ID")
@PathVariable Long internshipId) {
log.info("查询实习月度总结列表, 实习ID: {}", internshipId);
List<ZsjyInternshipMonthlyVo> list = monthlyService.queryByStudentAndInternship(null, internshipId);
return R.ok(list);
}
/**
* 统计学生各状态的总结数量
*/
@Operation(summary = "统计学生各状态的总结数量", description = "统计学生草稿、待审核、已审核等状态的总结数量")
@SaCheckPermission("zsjy:internshipmonthly:statistics")
@GetMapping("/statistics")
public R<List<Object>> statistics() {
log.info("统计学生月度总结状态");
List<Object> statistics = monthlyService.countByStatus(null);
return R.ok(statistics);
}
/**
* 获取学生当前实习信息
*/
@Operation(summary = "获取学生当前实习信息", description = "获取当前登录学生的进行中实习信息")
@SaCheckPermission("zsjy:internshipmonthly:add")
@GetMapping("/current-internship")
public R<Map<String, Object>> getCurrentInternship() {
log.info("获取学生当前实习信息");
Map<String, Object> internshipInfo = monthlyService.getCurrentInternshipInfo();
if (internshipInfo == null || internshipInfo.isEmpty()) {
return R.fail("您当前没有进行中的实习记录");
}
return R.ok(internshipInfo);
}
/**
* 导出学生月度实习总结
*/
@Operation(summary = "导出学生月度实习总结", description = "导出学生的月度总结数据")
@SaCheckPermission("zsjy:internshipmonthly:export")
@Log(title = "学生月度实习总结", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@RequestBody ZsjyInternshipMonthlyQueryBo queryBo, HttpServletResponse response) {
log.info("导出月度总结: {}", queryBo);
List<ZsjyInternshipMonthlyVo> list = monthlyService.queryList(queryBo);
ExcelUtil.exportExcel(list, "学生月度实习总结", ZsjyInternshipMonthlyVo.class, response);
}
}
@@ -1,6 +1,7 @@
package org.dromara.zsjy.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -159,5 +160,11 @@ public class ZsjyInternshipInfo extends BaseEntity {
* 实习状态(1进行中 2已完成 3中断)
*/
private String internshipStatus;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic(value = "0", delval = "1")
private Long deleted;
}
@@ -0,0 +1,191 @@
package org.dromara.zsjy.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
import java.math.BigDecimal;
import java.util.Date;
/**
* 学生月度实习总结实体对象 zsjy_internship_monthly
*
* @author system
* @date 2025-01-03
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("zsjy_internship_monthly")
public class ZsjyInternshipMonthly extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 月度总结ID (对应数据库monthly_id字段)
*/
@TableId(value = "monthly_id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 租户ID
*/
@TableField("tenant_id")
private String tenantId;
/**
* 实习ID
*/
@TableField("internship_id")
private Long internshipId;
/**
* 学生ID
*/
@TableField("student_id")
private Long studentId;
/**
* 总结月份 (格式: 2025-01)
*/
@TableField("summary_month")
private String summaryMonth;
/**
* 月份序号 (1-12)
*/
@TableField("month_number")
private Integer monthNumber;
/**
* 工作内容 (必填,500字以内)
*/
@TableField("work_content")
private String workContent;
/**
* 主要成果
*/
@TableField("achievements")
private String achievements;
/**
* 学习收获
*/
@TableField("learning_gains")
private String learningGains;
/**
* 遇到的问题
*/
@TableField("problems_encountered")
private String problemsEncountered;
/**
* 自我评价
*/
@TableField("self_evaluation")
private String selfEvaluation;
/**
* 下月工作计划
*/
@TableField("next_month_plan")
private String nextMonthPlan;
/**
* 专业技能自评 (1-10分)
*/
@TableField("professional_skills")
private Integer professionalSkills;
/**
* 沟通技能自评 (1-10分)
*/
@TableField("communication_skills")
private Integer communicationSkills;
/**
* 团队协作自评 (1-10分) - 修正字段映射
*/
@TableField("teamwork_ability")
private Integer teamworkAbility;
/**
* 学习能力自评 (1-10分)
*/
@TableField("learning_ability")
private Integer learningAbility;
/**
* 工作态度自评 (1-10分)
*/
@TableField("work_attitude")
private Integer workAttitude;
/**
* 审核状态 (0:待审核 1:已审核 2:审核拒绝 3:草稿) - 修正字段映射
*/
@TableField("review_status")
private Integer reviewStatus;
/**
* 审核人ID - 修正字段映射
*/
@TableField("review_by")
private Long reviewBy;
/**
* 审核时间
*/
@TableField("review_time")
private Date reviewTime;
/**
* 审核评分 (百分制)
*/
@TableField("review_score")
private BigDecimal reviewScore;
/**
* 审核意见 - 修正字段映射
*/
@TableField("review_comment")
private String reviewComment;
/**
* 提交时间
*/
@TableField("submit_time")
private Date submitTime;
/**
* 字数统计
*/
@TableField("word_count")
private Integer wordCount;
/**
* 是否逾期 (0:未逾期 1:已逾期)
*/
@TableField("is_overdue")
private Integer isOverdue;
/**
* 提交截止日期 - 修正字段映射
*/
@TableField("submit_deadline")
private Date submitDeadline;
/**
* 附件信息 (JSON格式存储)
*/
@TableField("attachments")
private String attachments;
}
@@ -0,0 +1,147 @@
package org.dromara.zsjy.domain.bo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import jakarta.validation.constraints.*;
import java.util.Date;
/**
* 学生月度实习总结新增/编辑对象 ZsjyInternshipMonthlyAddBo
*
* @author system
* @date 2025-01-03
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "学生月度实习总结新增/编辑对象")
public class ZsjyInternshipMonthlyAddBo extends BaseEntity {
/**
* 月度总结ID
*/
@Schema(description = "月度总结ID")
@NotNull(message = "月度总结ID不能为空", groups = { EditGroup.class })
private Long monthlyId;
/**
* 实习ID
*/
@Schema(description = "实习ID")
@NotNull(message = "实习ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long internshipId;
/**
* 总结月份 (格式: 2025-01)
*/
@Schema(description = "总结月份")
@NotBlank(message = "总结月份不能为空", groups = { AddGroup.class, EditGroup.class })
@Pattern(regexp = "^\\d{4}-\\d{2}$", message = "月份格式应为 YYYY-MM", groups = { AddGroup.class, EditGroup.class })
private String summaryMonth;
/**
* 工作内容 (必填,500字以内)
*/
@Schema(description = "工作内容")
@NotBlank(message = "工作内容不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(max = 500, message = "工作内容不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String workContent;
/**
* 主要成果
*/
@Schema(description = "主要成果")
@Size(max = 500, message = "主要成果不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String achievements;
/**
* 学习收获
*/
@Schema(description = "学习收获")
@Size(max = 500, message = "学习收获不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String learningGains;
/**
* 遇到的问题(包含解决方案,按2-A方案合并)
*/
@Schema(description = "遇到的问题")
@Size(max = 1000, message = "遇到的问题不能超过1000字", groups = { AddGroup.class, EditGroup.class })
private String problemsEncountered;
/**
* 自我评价
*/
@Schema(description = "自我评价")
@Size(max = 500, message = "自我评价不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String selfEvaluation;
/**
* 下月工作计划
*/
@Schema(description = "下月工作计划")
@Size(max = 500, message = "下月工作计划不能超过500字", groups = { AddGroup.class, EditGroup.class })
private String nextMonthPlan;
/**
* 专业技能自评 (1-10分)
*/
@Schema(description = "专业技能自评")
@Min(value = 1, message = "专业技能自评最小值为1", groups = { AddGroup.class, EditGroup.class })
@Max(value = 10, message = "专业技能自评最大值为10", groups = { AddGroup.class, EditGroup.class })
private Integer professionalSkills;
/**
* 沟通技能自评 (1-10分)
*/
@Schema(description = "沟通技能自评")
@Min(value = 1, message = "沟通技能自评最小值为1", groups = { AddGroup.class, EditGroup.class })
@Max(value = 10, message = "沟通技能自评最大值为10", groups = { AddGroup.class, EditGroup.class })
private Integer communicationSkills;
/**
* 团队协作自评 (1-10分) - 修正字段映射
*/
@Schema(description = "团队协作自评")
@Min(value = 1, message = "团队协作自评最小值为1", groups = { AddGroup.class, EditGroup.class })
@Max(value = 10, message = "团队协作自评最大值为10", groups = { AddGroup.class, EditGroup.class })
private Integer teamworkAbility;
/**
* 学习能力自评 (1-10分)
*/
@Schema(description = "学习能力自评")
@Min(value = 1, message = "学习能力自评最小值为1", groups = { AddGroup.class, EditGroup.class })
@Max(value = 10, message = "学习能力自评最大值为10", groups = { AddGroup.class, EditGroup.class })
private Integer learningAbility;
/**
* 工作态度自评 (1-10分)
*/
@Schema(description = "工作态度自评")
@Min(value = 1, message = "工作态度自评最小值为1", groups = { AddGroup.class, EditGroup.class })
@Max(value = 10, message = "工作态度自评最大值为10", groups = { AddGroup.class, EditGroup.class })
private Integer workAttitude;
/**
* 附件信息 (JSON格式存储)
*/
@Schema(description = "附件信息")
private String attachments;
/**
* 审核状态 (0-待审核 1-已审核 2-审核拒绝 3-草稿)
*/
@Schema(description = "审核状态")
private Integer reviewStatus;
/**
* 提交时间
*/
@Schema(description = "提交时间")
private Date submitTime;
}
@@ -0,0 +1,61 @@
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;
/**
* 学生月度实习总结查询对象 ZsjyInternshipMonthlyQueryBo
*
* @author system
* @date 2025-01-03
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "学生月度实习总结查询对象")
public class ZsjyInternshipMonthlyQueryBo extends BaseEntity {
/**
* 学生ID
*/
@Schema(description = "学生ID")
private Long studentId;
/**
* 总结月份 (格式: 2025-01)
*/
@Schema(description = "总结月份")
private String summaryMonth;
/**
* 审核状态 (0:待审核 1:已审核 2:审核拒绝 3:草稿)
*/
@Schema(description = "审核状态")
private Integer status;
/**
* 实习ID
*/
@Schema(description = "实习ID")
private Long internshipId;
/**
* 是否逾期 (0:未逾期 1:已逾期)
*/
@Schema(description = "是否逾期")
private Integer isOverdue;
/**
* 开始月份
*/
@Schema(description = "开始月份")
private String startMonth;
/**
* 结束月份
*/
@Schema(description = "结束月份")
private String endMonth;
}
@@ -0,0 +1,253 @@
package org.dromara.zsjy.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 学生月度实习总结视图对象 ZsjyInternshipMonthlyVo
*
* @author system
* @date 2025-01-03
*/
@Data
@Schema(description = "学生月度实习总结视图对象")
public class ZsjyInternshipMonthlyVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 月度总结ID
*/
@Schema(description = "月度总结ID")
private Long monthlyId;
/**
* 实习ID
*/
@Schema(description = "实习ID")
private Long internshipId;
/**
* 学生ID
*/
@Schema(description = "学生ID")
private Long studentId;
/**
* 学生姓名 - 通过关联查询获取
*/
@Schema(description = "学生姓名")
private String studentName;
/**
* 学生学号 - 通过关联查询获取
*/
@Schema(description = "学生学号")
private String studentNumber;
/**
* 总结月份 (格式: 2025-01)
*/
@Schema(description = "总结月份")
private String summaryMonth;
/**
* 月份序号 (1-12)
*/
@Schema(description = "月份序号")
private Integer monthNumber;
/**
* 工作内容
*/
@Schema(description = "工作内容")
private String workContent;
/**
* 主要成果
*/
@Schema(description = "主要成果")
private String achievements;
/**
* 学习收获
*/
@Schema(description = "学习收获")
private String learningGains;
/**
* 遇到的问题(包含解决方案,按2-A方案合并)
*/
@Schema(description = "遇到的问题")
private String problemsEncountered;
/**
* 自我评价
*/
@Schema(description = "自我评价")
private String selfEvaluation;
/**
* 下月工作计划
*/
@Schema(description = "下月工作计划")
private String nextMonthPlan;
/**
* 专业技能自评 (1-10分)
*/
@Schema(description = "专业技能自评")
private Integer professionalSkills;
/**
* 沟通技能自评 (1-10分)
*/
@Schema(description = "沟通技能自评")
private Integer communicationSkills;
/**
* 团队协作自评 (1-10分) - 修正字段映射
*/
@Schema(description = "团队协作自评")
private Integer teamworkAbility;
/**
* 学习能力自评 (1-10分)
*/
@Schema(description = "学习能力自评")
private Integer learningAbility;
/**
* 工作态度自评 (1-10分)
*/
@Schema(description = "工作态度自评")
private Integer workAttitude;
/**
* 审核状态 (0:待审核 1:已审核 2:审核拒绝 3:草稿) - 修正字段映射
*/
@Schema(description = "审核状态")
private Integer reviewStatus;
/**
* 状态文本描述
*/
@Schema(description = "状态文本描述")
private String statusText;
/**
* 审核人ID - 修正字段映射
*/
@Schema(description = "审核人ID")
private Long reviewBy;
/**
* 审核人姓名 - 通过关联查询获取
*/
@Schema(description = "审核人姓名")
private String reviewerName;
/**
* 审核时间
*/
@Schema(description = "审核时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reviewTime;
/**
* 审核评分 (百分制)
*/
@Schema(description = "审核评分")
private BigDecimal reviewScore;
/**
* 审核意见 - 修正字段映射
*/
@Schema(description = "审核意见")
private String reviewComment;
/**
* 提交时间
*/
@Schema(description = "提交时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date submitTime;
/**
* 字数统计
*/
@Schema(description = "字数统计")
private Integer wordCount;
/**
* 是否逾期 (0:未逾期 1:已逾期)
*/
@Schema(description = "是否逾期")
private Integer isOverdue;
/**
* 提交截止日期 - 修正字段映射
*/
@Schema(description = "提交截止日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date submitDeadline;
/**
* 附件信息
*/
@Schema(description = "附件信息")
private String attachments;
/**
* 是否可编辑 (草稿状态才可编辑)
*/
@Schema(description = "是否可编辑")
private Boolean editable;
/**
* 是否可删除 (草稿状态才可删除)
*/
@Schema(description = "是否可删除")
private Boolean deletable;
/**
* 是否可提交 (草稿状态才可提交)
*/
@Schema(description = "是否可提交")
private Boolean submittable;
// VO类基础字段规范 - 必须包含的四个字段
/**
* 创建者
*/
@Schema(description = "创建者")
private Long createBy;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
@Schema(description = "更新者")
private Long updateBy;
/**
* 更新时间
*/
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,82 @@
package org.dromara.zsjy.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
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.vo.ZsjyInternshipMonthlyVo;
import java.util.List;
/**
* 学生月度实习总结Mapper接口
*
* @author system
* @date 2025-01-03
*/
public interface ZsjyInternshipMonthlyMapper extends BaseMapper<ZsjyInternshipMonthly> {
/**
* 查询学生月度实习总结
*
* @param monthlyId 月度总结ID
* @return 学生月度实习总结
*/
ZsjyInternshipMonthlyVo selectVoById(Long monthlyId);
/**
* 查询学生月度实习总结列表
*
* @param queryBo 查询条件
* @return 学生月度实习总结集合
*/
Page<ZsjyInternshipMonthlyVo> selectVoPage(Page<ZsjyInternshipMonthlyVo> page, @Param("bo") ZsjyInternshipMonthlyQueryBo queryBo);
/**
* 查询学生月度实习总结列表
*
* @param queryBo 查询条件
* @return 学生月度实习总结集合
*/
List<ZsjyInternshipMonthlyVo> selectVoList(@Param("bo") ZsjyInternshipMonthlyQueryBo queryBo);
/**
* 检查学生某月是否已有总结
*
* @param studentId 学生ID
* @param summaryMonth 总结月份
* @param monthlyId 排除的总结ID(用于编辑时检查)
* @return 是否存在
*/
boolean existsByStudentAndMonth(@Param("studentId") Long studentId,
@Param("summaryMonth") String summaryMonth,
@Param("monthlyId") Long monthlyId);
/**
* 批量删除学生月度实习总结
*
* @param monthlyIds 需要删除的数据ID
* @return 影响行数
*/
int deleteByIds(@Param("monthlyIds") Long[] monthlyIds);
/**
* 查询学生指定实习的月度总结列表
*
* @param studentId 学生ID
* @param internshipId 实习ID
* @return 月度总结列表
*/
List<ZsjyInternshipMonthlyVo> selectByStudentAndInternship(@Param("studentId") Long studentId,
@Param("internshipId") Long internshipId);
/**
* 统计学生各状态的总结数量
*
* @param studentId 学生ID
* @return 状态统计结果
*/
List<Object> countByStatus(@Param("studentId") Long studentId);
}
@@ -98,4 +98,11 @@ public interface IInternshipInfoService {
* @return 是否成功
*/
Boolean deleteInternshipInfo(Long[] internshipIds);
/**
* 获取当前学生的实习信息
*
* @return 当前学生的实习信息,如果没有则返回null
*/
InternshipInfoVO getCurrentStudentInternship();
}
@@ -0,0 +1,137 @@
package org.dromara.zsjy.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyAddBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 学生月度实习总结Service接口
*
* @author system
* @date 2025-01-03
*/
public interface IZsjyInternshipMonthlyService {
/**
* 查询学生月度实习总结
*
* @param monthlyId 月度总结ID
* @return 学生月度实习总结
*/
ZsjyInternshipMonthlyVo queryById(Long monthlyId);
/**
* 查询学生月度实习总结列表
*
* @param queryBo 查询条件
* @param pageQuery 分页参数
* @return 学生月度实习总结分页结果
*/
TableDataInfo<ZsjyInternshipMonthlyVo> queryPageList(ZsjyInternshipMonthlyQueryBo queryBo, PageQuery pageQuery);
/**
* 查询学生月度实习总结列表
*
* @param queryBo 查询条件
* @return 学生月度实习总结集合
*/
List<ZsjyInternshipMonthlyVo> queryList(ZsjyInternshipMonthlyQueryBo queryBo);
/**
* 新增学生月度实习总结
*
* @param addBo 学生月度实习总结
* @return 新增结果
*/
Boolean insertByBo(ZsjyInternshipMonthlyAddBo addBo);
/**
* 修改学生月度实习总结
*
* @param addBo 学生月度实习总结
* @return 修改结果
*/
Boolean updateByBo(ZsjyInternshipMonthlyAddBo addBo);
/**
* 校验并批量删除学生月度实习总结信息
*
* @param ids 需要删除的学生月度实习总结ID
* @return 删除结果
*/
Boolean deleteByIds(Collection<Long> ids);
/**
* 提交月度总结审批
*
* @param monthlyId 月度总结ID
* @return 提交结果
*/
Boolean submitForApproval(Long monthlyId);
/**
* 自动保存草稿
*
* @param addBo 学生月度实习总结
* @return 保存结果
*/
Boolean autoSaveDraft(ZsjyInternshipMonthlyAddBo addBo);
/**
* 检查学生某月是否已有总结
*
* @param studentId 学生ID
* @param summaryMonth 总结月份
* @param monthlyId 排除的总结ID(用于编辑时检查)
* @return 是否存在
*/
Boolean checkMonthExists(Long studentId, String summaryMonth, Long monthlyId);
/**
* 获取月度总结模板信息
*
* @param summaryMonth 总结月份
* @return 模板信息
*/
ZsjyInternshipMonthlyVo getMonthlyTemplate(String summaryMonth);
/**
* 查询学生指定实习的月度总结列表
*
* @param studentId 学生ID
* @param internshipId 实习ID
* @return 月度总结列表
*/
List<ZsjyInternshipMonthlyVo> queryByStudentAndInternship(Long studentId, Long internshipId);
/**
* 统计学生各状态的总结数量
*
* @param studentId 学生ID
* @return 状态统计结果
*/
List<Object> countByStatus(Long studentId);
/**
* 计算总结内容字数
*
* @param addBo 总结内容
* @return 字数统计
*/
Integer calculateWordCount(ZsjyInternshipMonthlyAddBo addBo);
/**
* 获取学生当前实习信息
*
* @return 当前实习信息
*/
Map<String, Object> getCurrentInternshipInfo();
}
@@ -459,4 +459,53 @@ public class InternshipInfoServiceImpl implements IInternshipInfoService {
}
return studentInfo;
}
/**
* 获取当前学生的实习信息
*
* @return 当前学生的实习信息,如果没有则返回null
*/
@Override
public InternshipInfoVO getCurrentStudentInternship() {
log.info("开始获取当前学生的实习信息");
// 获取当前登录用户ID
Long currentUserId = LoginHelper.getUserId();
log.info("当前登录用户ID:{}", currentUserId);
if (currentUserId == null) {
log.error("当前用户未登录");
throw new ServiceException("用户未登录");
}
// 根据用户ID查询实习信息
LambdaQueryWrapper<ZsjyInternshipInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ZsjyInternshipInfo::getStudentId, currentUserId)
.eq(ZsjyInternshipInfo::getDeleted, 0)
.orderByDesc(ZsjyInternshipInfo::getCreateTime)
.last("LIMIT 1"); // 只取最新的一条记录
ZsjyInternshipInfo internshipInfo = zsjyInternshipInfoMapper.selectOne(wrapper);
log.info("查询到的实习信息:{}", internshipInfo != null ? internshipInfo.getInternshipId() : "无");
if (internshipInfo == null) {
log.info("当前学生没有实习信息");
return null;
}
// 转换为VO
InternshipInfoVO vo = BeanUtil.toBean(internshipInfo, InternshipInfoVO.class);
// 查询学生信息并设置到VO中
XgStudentMsgVo studentInfo = getStudentInfo(currentUserId);
vo.setStudentName(studentInfo.getName());
vo.setStudentNo(studentInfo.getStudentNo());
vo.setClassName(studentInfo.getClassName());
vo.setMajorName(studentInfo.getMajorName());
vo.setCollegeName(studentInfo.getCollegeName());
vo.setGrade(studentInfo.getGrade());
log.info("成功获取当前学生实习信息,实习ID:{}", vo.getInternshipId());
return vo;
}
}
@@ -0,0 +1,465 @@
package org.dromara.zsjy.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
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.ZsjyInternshipMonthlyAddBo;
import org.dromara.zsjy.domain.bo.ZsjyInternshipMonthlyQueryBo;
import org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo;
import org.dromara.zsjy.mapper.ZsjyInternshipMonthlyMapper;
import org.dromara.zsjy.mapper.ZsjyInternshipInfoMapper;
import org.dromara.zsjy.domain.ZsjyInternshipInfo;
import org.dromara.zsjy.service.IZsjyInternshipMonthlyService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.YearMonth;
import java.util.*;
import java.time.ZoneId;
/**
* 学生月度实习总结Service业务层处理
*
* @author system
* @date 2025-01-03
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ZsjyInternshipMonthlyServiceImpl implements IZsjyInternshipMonthlyService {
private final ZsjyInternshipMonthlyMapper baseMapper;
private final ZsjyInternshipInfoMapper internshipInfoMapper;
/**
* 查询学生月度实习总结
*/
@Override
public ZsjyInternshipMonthlyVo queryById(Long monthlyId) {
ZsjyInternshipMonthlyVo vo = baseMapper.selectVoById(monthlyId);
if (ObjectUtil.isNull(vo)) {
throw new ServiceException("月度总结不存在");
}
return vo;
}
/**
* 查询学生月度实习总结分页列表
*/
@Override
public TableDataInfo<ZsjyInternshipMonthlyVo> queryPageList(ZsjyInternshipMonthlyQueryBo queryBo, PageQuery pageQuery) {
// 如果没有指定学生ID,则使用当前登录用户ID(学生端)
if (ObjectUtil.isNull(queryBo.getStudentId())) {
queryBo.setStudentId(LoginHelper.getUserId());
}
Page<ZsjyInternshipMonthlyVo> result = baseMapper.selectVoPage(pageQuery.build(), queryBo);
return TableDataInfo.build(result);
}
/**
* 查询学生月度实习总结列表
*/
@Override
public List<ZsjyInternshipMonthlyVo> queryList(ZsjyInternshipMonthlyQueryBo queryBo) {
// 如果没有指定学生ID,则使用当前登录用户ID(学生端)
if (ObjectUtil.isNull(queryBo.getStudentId())) {
queryBo.setStudentId(LoginHelper.getUserId());
}
return baseMapper.selectVoList(queryBo);
}
/**
* 新增学生月度实习总结
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(ZsjyInternshipMonthlyAddBo addBo) {
Long studentId = LoginHelper.getUserId();
// 如果前端没有传递实习ID,则自动获取当前学生的实习ID
if (ObjectUtil.isNull(addBo.getInternshipId())) {
ZsjyInternshipInfo internshipInfo = internshipInfoMapper.selectOne(
new LambdaQueryWrapper<ZsjyInternshipInfo>()
.eq(ZsjyInternshipInfo::getStudentId, studentId)
.eq(ZsjyInternshipInfo::getStatus, "1") // 查询进行中状态的实习
.eq(ZsjyInternshipInfo::getDeleted, 0)
.orderByDesc(ZsjyInternshipInfo::getCreateTime)
.last("LIMIT 1")
);
if (ObjectUtil.isNull(internshipInfo)) {
throw new ServiceException("您当前没有进行中的实习记录,无法创建月度总结");
}
addBo.setInternshipId(internshipInfo.getInternshipId());
log.info("自动获取到学生实习ID:{}", internshipInfo.getInternshipId());
}
// 检查该学生该月份是否已有总结
if (checkMonthExists(studentId, addBo.getSummaryMonth(), null)) {
throw new ServiceException("该月份已存在总结记录,请勿重复创建");
}
// 手动转换BO到实体,解决字段映射问题
ZsjyInternshipMonthly add = new ZsjyInternshipMonthly();
add.setId(addBo.getMonthlyId()); // monthlyId -> id
add.setInternshipId(addBo.getInternshipId());
add.setSummaryMonth(addBo.getSummaryMonth());
add.setWorkContent(addBo.getWorkContent());
add.setAchievements(addBo.getAchievements());
add.setLearningGains(addBo.getLearningGains());
add.setProblemsEncountered(addBo.getProblemsEncountered());
add.setSelfEvaluation(addBo.getSelfEvaluation());
add.setNextMonthPlan(addBo.getNextMonthPlan());
add.setProfessionalSkills(addBo.getProfessionalSkills());
add.setCommunicationSkills(addBo.getCommunicationSkills());
add.setTeamworkAbility(addBo.getTeamworkAbility());
add.setLearningAbility(addBo.getLearningAbility());
add.setWorkAttitude(addBo.getWorkAttitude());
add.setAttachments(addBo.getAttachments());
add.setStudentId(studentId);
add.setTenantId("000000"); // 默认租户
// 处理审核状态和提交时间
if (addBo.getReviewStatus() != null) {
add.setReviewStatus(addBo.getReviewStatus());
// 如果状态为待审核(0),设置提交时间
if (addBo.getReviewStatus() == 0) {
add.setSubmitTime(addBo.getSubmitTime() != null ? addBo.getSubmitTime() : new Date());
}
} else {
add.setReviewStatus(3); // 默认为草稿状态
}
// 解析月份序号
if (StringUtils.isNotBlank(addBo.getSummaryMonth())) {
String[] parts = addBo.getSummaryMonth().split("-");
if (parts.length == 2) {
add.setMonthNumber(Integer.parseInt(parts[1]));
}
}
// 计算字数
add.setWordCount(calculateWordCount(addBo));
// 设置默认截止日期(下月5号)
try {
YearMonth ym = YearMonth.parse(addBo.getSummaryMonth());
add.setSubmitDeadline(Date.from(ym.plusMonths(1).atDay(5).atStartOfDay(ZoneId.systemDefault()).toInstant()));
} catch (Exception e) {
log.warn("解析月份失败: {}", addBo.getSummaryMonth());
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
addBo.setMonthlyId(add.getId());
}
return flag;
}
/**
* 修改学生月度实习总结
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(ZsjyInternshipMonthlyAddBo addBo) {
Long studentId = LoginHelper.getUserId();
// 查询原记录,验证权限和状态
ZsjyInternshipMonthly original = baseMapper.selectById(addBo.getMonthlyId());
if (ObjectUtil.isNull(original)) {
throw new ServiceException("月度总结不存在");
}
// 权限检查:只能修改自己的总结
if (!Objects.equals(original.getStudentId(), studentId)) {
throw new ServiceException("您只能修改自己的月度总结");
}
// 状态检查:只有草稿状态才能修改
if (!Objects.equals(original.getReviewStatus(), 3)) {
throw new ServiceException("只有草稿状态的总结才能修改");
}
// 如果前端没有传递实习ID,则使用原记录的实习ID
if (ObjectUtil.isNull(addBo.getInternshipId())) {
addBo.setInternshipId(original.getInternshipId());
log.info("使用原记录的实习ID:{}", original.getInternshipId());
}
// 检查月份是否被其他记录占用
if (!Objects.equals(original.getSummaryMonth(), addBo.getSummaryMonth())) {
if (checkMonthExists(studentId, addBo.getSummaryMonth(), addBo.getMonthlyId())) {
throw new ServiceException("该月份已存在其他总结记录");
}
}
// 手动转换BO到实体,解决字段映射问题
ZsjyInternshipMonthly update = new ZsjyInternshipMonthly();
update.setId(addBo.getMonthlyId()); // monthlyId -> id
update.setInternshipId(addBo.getInternshipId());
update.setSummaryMonth(addBo.getSummaryMonth());
update.setWorkContent(addBo.getWorkContent());
update.setAchievements(addBo.getAchievements());
update.setLearningGains(addBo.getLearningGains());
update.setProblemsEncountered(addBo.getProblemsEncountered());
update.setSelfEvaluation(addBo.getSelfEvaluation());
update.setNextMonthPlan(addBo.getNextMonthPlan());
update.setProfessionalSkills(addBo.getProfessionalSkills());
update.setCommunicationSkills(addBo.getCommunicationSkills());
update.setTeamworkAbility(addBo.getTeamworkAbility());
update.setLearningAbility(addBo.getLearningAbility());
update.setWorkAttitude(addBo.getWorkAttitude());
update.setAttachments(addBo.getAttachments());
// 处理审核状态和提交时间
if (addBo.getReviewStatus() != null) {
update.setReviewStatus(addBo.getReviewStatus());
// 如果状态从草稿(3)变为待审核(0),设置提交时间
if (addBo.getReviewStatus() == 0 && original.getReviewStatus() == 3) {
update.setSubmitTime(addBo.getSubmitTime() != null ? addBo.getSubmitTime() : new Date());
}
// 如果状态变回草稿(3),清空提交时间
else if (addBo.getReviewStatus() == 3) {
update.setSubmitTime(null);
}
}
// 解析月份序号
if (StringUtils.isNotBlank(addBo.getSummaryMonth())) {
String[] parts = addBo.getSummaryMonth().split("-");
if (parts.length == 2) {
update.setMonthNumber(Integer.parseInt(parts[1]));
}
}
// 重新计算字数
update.setWordCount(calculateWordCount(addBo));
return baseMapper.updateById(update) > 0;
}
/**
* 批量删除学生月度实习总结
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteByIds(Collection<Long> ids) {
Long studentId = LoginHelper.getUserId();
// 验证权限:只能删除自己的草稿状态总结
for (Long id : ids) {
ZsjyInternshipMonthly monthly = baseMapper.selectById(id);
if (ObjectUtil.isNull(monthly)) {
continue;
}
if (!Objects.equals(monthly.getStudentId(), studentId)) {
throw new ServiceException("您只能删除自己的月度总结");
}
if (!Objects.equals(monthly.getReviewStatus(), 3)) {
throw new ServiceException("只有草稿状态的总结才能删除");
}
}
return baseMapper.deleteByIds(ids.toArray(new Long[0])) > 0;
}
/**
* 提交月度总结审批
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean submitForApproval(Long monthlyId) {
Long studentId = LoginHelper.getUserId();
ZsjyInternshipMonthly monthly = baseMapper.selectById(monthlyId);
if (ObjectUtil.isNull(monthly)) {
throw new ServiceException("月度总结不存在");
}
// 权限检查
if (!Objects.equals(monthly.getStudentId(), studentId)) {
throw new ServiceException("您只能提交自己的月度总结");
}
// 状态检查
if (!Objects.equals(monthly.getReviewStatus(), 3)) {
throw new ServiceException("只有草稿状态的总结才能提交");
}
// 必填字段检查
if (StringUtils.isBlank(monthly.getWorkContent())) {
throw new ServiceException("工作内容不能为空");
}
// 更新状态为待审核
ZsjyInternshipMonthly update = new ZsjyInternshipMonthly();
update.setId(monthlyId);
update.setReviewStatus(0); // 待审核
update.setSubmitTime(new Date());
return baseMapper.updateById(update) > 0;
}
/**
* 自动保存草稿
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean autoSaveDraft(ZsjyInternshipMonthlyAddBo addBo) {
try {
if (ObjectUtil.isNull(addBo.getMonthlyId())) {
// 新增草稿
return insertByBo(addBo);
} else {
// 更新草稿
return updateByBo(addBo);
}
} catch (Exception e) {
log.warn("自动保存草稿失败: {}", e.getMessage());
return false;
}
}
/**
* 检查学生某月是否已有总结
*/
@Override
public Boolean checkMonthExists(Long studentId, String summaryMonth, Long monthlyId) {
return baseMapper.existsByStudentAndMonth(studentId, summaryMonth, monthlyId);
}
/**
* 获取月度总结模板信息
*/
@Override
public ZsjyInternshipMonthlyVo getMonthlyTemplate(String summaryMonth) {
ZsjyInternshipMonthlyVo template = new ZsjyInternshipMonthlyVo();
template.setSummaryMonth(summaryMonth);
// 解析月份序号
if (StringUtils.isNotBlank(summaryMonth)) {
String[] parts = summaryMonth.split("-");
if (parts.length == 2) {
template.setMonthNumber(Integer.parseInt(parts[1]));
}
}
// 设置默认截止日期(下月5号)
try {
YearMonth ym = YearMonth.parse(summaryMonth);
template.setSubmitDeadline(Date.from(ym.plusMonths(1).atDay(5).atStartOfDay(ZoneId.systemDefault()).toInstant()));
} catch (Exception e) {
log.warn("解析月份失败: {}", summaryMonth);
}
template.setReviewStatus(3); // 草稿
template.setStatusText("草稿");
template.setEditable(true);
template.setDeletable(true);
template.setSubmittable(true);
return template;
}
/**
* 查询学生指定实习的月度总结列表
*/
@Override
public List<ZsjyInternshipMonthlyVo> queryByStudentAndInternship(Long studentId, Long internshipId) {
if (ObjectUtil.isNull(studentId)) {
studentId = LoginHelper.getUserId();
}
return baseMapper.selectByStudentAndInternship(studentId, internshipId);
}
/**
* 统计学生各状态的总结数量
*/
@Override
public List<Object> countByStatus(Long studentId) {
if (ObjectUtil.isNull(studentId)) {
studentId = LoginHelper.getUserId();
}
return baseMapper.countByStatus(studentId);
}
/**
* 计算总结内容字数
*/
@Override
public Integer calculateWordCount(ZsjyInternshipMonthlyAddBo addBo) {
int wordCount = 0;
if (StringUtils.isNotBlank(addBo.getWorkContent())) {
wordCount += addBo.getWorkContent().length();
}
if (StringUtils.isNotBlank(addBo.getAchievements())) {
wordCount += addBo.getAchievements().length();
}
if (StringUtils.isNotBlank(addBo.getLearningGains())) {
wordCount += addBo.getLearningGains().length();
}
if (StringUtils.isNotBlank(addBo.getProblemsEncountered())) {
wordCount += addBo.getProblemsEncountered().length();
}
if (StringUtils.isNotBlank(addBo.getSelfEvaluation())) {
wordCount += addBo.getSelfEvaluation().length();
}
if (StringUtils.isNotBlank(addBo.getNextMonthPlan())) {
wordCount += addBo.getNextMonthPlan().length();
}
return wordCount;
}
/**
* 获取学生当前实习信息
*/
@Override
public Map<String, Object> getCurrentInternshipInfo() {
Long currentUserId = LoginHelper.getUserId();
log.info("获取学生当前实习信息, 用户ID: {}", currentUserId);
// 查询学生当前进行中的实习记录(与insertByBo方法保持一致)
LambdaQueryWrapper<ZsjyInternshipInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ZsjyInternshipInfo::getStudentId, currentUserId)
.eq(ZsjyInternshipInfo::getStatus, "1") // 查询进行中状态的实习
.eq(ZsjyInternshipInfo::getDeleted, 0)
.orderByDesc(ZsjyInternshipInfo::getCreateTime)
.last("LIMIT 1");
ZsjyInternshipInfo internshipInfo = internshipInfoMapper.selectOne(wrapper);
if (internshipInfo == null) {
log.warn("学生{}没有找到进行中的实习记录", currentUserId);
return new HashMap<>();
}
Map<String, Object> result = new HashMap<>();
result.put("internshipId", internshipInfo.getInternshipId());
result.put("companyName", internshipInfo.getCompanyName());
result.put("position", internshipInfo.getPositionName());
result.put("startDate", internshipInfo.getStartDate());
result.put("endDate", internshipInfo.getEndDate());
log.info("找到学生实习信息: {}", result);
return result;
}
}
@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.zsjy.mapper.ZsjyInternshipMonthlyMapper">
<resultMap type="org.dromara.zsjy.domain.ZsjyInternshipMonthly" id="ZsjyInternshipMonthlyResult">
<result property="id" column="monthly_id" />
<result property="tenantId" column="tenant_id" />
<result property="internshipId" column="internship_id" />
<result property="studentId" column="student_id" />
<result property="summaryMonth" column="summary_month" />
<result property="monthNumber" column="month_number" />
<result property="workContent" column="work_content" />
<result property="achievements" column="achievements" />
<result property="learningGains" column="learning_gains" />
<result property="problemsEncountered" column="problems_encountered" />
<result property="selfEvaluation" column="self_evaluation" />
<result property="nextMonthPlan" column="next_month_plan" />
<result property="professionalSkills" column="professional_skills" />
<result property="communicationSkills" column="communication_skills" />
<result property="teamworkAbility" column="teamwork_ability" />
<result property="learningAbility" column="learning_ability" />
<result property="workAttitude" column="work_attitude" />
<result property="reviewStatus" column="review_status" />
<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="submitTime" column="submit_time" />
<result property="wordCount" column="word_count" />
<result property="isOverdue" column="is_overdue" />
<result property="submitDeadline" column="submit_deadline" />
<result property="attachments" column="attachments" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="deleted" column="deleted" />
</resultMap>
<resultMap type="org.dromara.zsjy.domain.vo.ZsjyInternshipMonthlyVo" id="ZsjyInternshipMonthlyVoResult">
<result property="monthlyId" column="monthly_id" />
<result property="internshipId" column="internship_id" />
<result property="studentId" column="student_id" />
<result property="studentName" column="student_name" />
<result property="studentNumber" column="student_number" />
<result property="summaryMonth" column="summary_month" />
<result property="monthNumber" column="month_number" />
<result property="workContent" column="work_content" />
<result property="achievements" column="achievements" />
<result property="learningGains" column="learning_gains" />
<result property="problemsEncountered" column="problems_encountered" />
<result property="selfEvaluation" column="self_evaluation" />
<result property="nextMonthPlan" column="next_month_plan" />
<result property="professionalSkills" column="professional_skills" />
<result property="communicationSkills" column="communication_skills" />
<result property="teamworkAbility" column="teamwork_ability" />
<result property="learningAbility" column="learning_ability" />
<result property="workAttitude" column="work_attitude" />
<result property="reviewStatus" column="review_status" />
<result property="statusText" column="status_text" />
<result property="reviewBy" column="review_by" />
<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="submitTime" column="submit_time" />
<result property="wordCount" column="word_count" />
<result property="isOverdue" column="is_overdue" />
<result property="submitDeadline" column="submit_deadline" />
<result property="attachments" column="attachments" />
<result property="editable" column="editable" />
<result property="deletable" column="deletable" />
<result property="submittable" column="submittable" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectZsjyInternshipMonthlyVo">
SELECT
m.monthly_id,
m.internship_id,
m.student_id,
u.nick_name as student_name,
ui.student_no as student_number,
m.summary_month,
m.month_number,
m.work_content,
m.achievements,
m.learning_gains,
m.problems_encountered,
m.self_evaluation,
m.next_month_plan,
m.professional_skills,
m.communication_skills,
m.teamwork_ability,
m.learning_ability,
m.work_attitude,
m.review_status,
CASE m.review_status
WHEN 0 THEN '待审核'
WHEN 1 THEN '已审核'
WHEN 2 THEN '审核拒绝'
WHEN 3 THEN '草稿'
ELSE '未知状态'
END as status_text,
m.review_by,
r.nick_name as reviewer_name,
m.review_time,
m.review_score,
m.review_comment as review_comments,
m.submit_time,
m.word_count,
m.is_overdue,
m.submit_deadline,
m.attachments,
CASE WHEN m.review_status = 3 THEN 1 ELSE 0 END as editable,
CASE WHEN m.review_status = 3 THEN 1 ELSE 0 END as deletable,
CASE WHEN m.review_status = 3 THEN 1 ELSE 0 END as submittable,
m.create_by,
m.create_time,
m.update_by,
m.update_time
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
LEFT JOIN sys_user r ON r.user_id = m.review_by
WHERE m.deleted = 0
</sql>
<select id="selectVoById" parameterType="Long" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
AND m.monthly_id = #{monthlyId}
</select>
<select id="selectVoPage" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
<if test="bo.studentId != null">
AND m.student_id = #{bo.studentId}
</if>
<if test="bo.internshipId != null">
AND m.internship_id = #{bo.internshipId}
</if>
<if test="bo.summaryMonth != null and bo.summaryMonth != ''">
AND m.summary_month = #{bo.summaryMonth}
</if>
<if test="bo.status != null">
AND m.review_status = #{bo.status}
</if>
<if test="bo.isOverdue != null">
AND m.is_overdue = #{bo.isOverdue}
</if>
<if test="bo.startMonth != null and bo.startMonth != ''">
AND m.summary_month &gt;= #{bo.startMonth}
</if>
<if test="bo.endMonth != null and bo.endMonth != ''">
AND m.summary_month &lt;= #{bo.endMonth}
</if>
ORDER BY m.summary_month DESC, m.create_time DESC
</select>
<select id="selectVoList" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
<if test="bo.studentId != null">
AND m.student_id = #{bo.studentId}
</if>
<if test="bo.internshipId != null">
AND m.internship_id = #{bo.internshipId}
</if>
<if test="bo.summaryMonth != null and bo.summaryMonth != ''">
AND m.summary_month = #{bo.summaryMonth}
</if>
<if test="bo.status != null">
AND m.review_status = #{bo.status}
</if>
<if test="bo.isOverdue != null">
AND m.is_overdue = #{bo.isOverdue}
</if>
<if test="bo.startMonth != null and bo.startMonth != ''">
AND m.summary_month &gt;= #{bo.startMonth}
</if>
<if test="bo.endMonth != null and bo.endMonth != ''">
AND m.summary_month &lt;= #{bo.endMonth}
</if>
ORDER BY m.summary_month DESC, m.create_time DESC
</select>
<select id="existsByStudentAndMonth" resultType="boolean">
SELECT COUNT(1) > 0
FROM zsjy_internship_monthly
WHERE deleted = 0
AND student_id = #{studentId}
AND summary_month = #{summaryMonth}
<if test="monthlyId != null">
AND monthly_id != #{monthlyId}
</if>
</select>
<delete id="deleteByIds" parameterType="Long">
UPDATE zsjy_internship_monthly SET deleted = 1
WHERE monthly_id IN
<foreach item="monthlyId" collection="monthlyIds" open="(" separator="," close=")">
#{monthlyId}
</foreach>
AND review_status = 3
</delete>
<select id="selectByStudentAndInternship" resultMap="ZsjyInternshipMonthlyVoResult">
<include refid="selectZsjyInternshipMonthlyVo"/>
<if test="studentId != null">
AND m.student_id = #{studentId}
</if>
<if test="internshipId != null">
AND m.internship_id = #{internshipId}
</if>
ORDER BY m.summary_month DESC, m.create_time DESC
</select>
<select id="countByStatus" resultType="map">
SELECT
review_status,
CASE review_status
WHEN 0 THEN '待审核'
WHEN 1 THEN '已审核'
WHEN 2 THEN '审核拒绝'
WHEN 3 THEN '草稿'
ELSE '未知状态'
END as status_text,
COUNT(1) as count
FROM zsjy_internship_monthly
WHERE deleted = 0
AND student_id = #{studentId}
GROUP BY review_status
ORDER BY review_status
</select>
</mapper>
+11
View File
@@ -28,6 +28,17 @@ export const getStudentInternshipInfo = () => {
});
};
/**
* 获取当前学生实习信息(新版API)
* @returns Promise
*/
export const getCurrentStudentInternship = () => {
return request({
url: '/zsjy/internship/current',
method: 'GET'
});
};
/**
* 学生签到打卡
* @param {Object} data 签到数据
+1 -1
View File
@@ -170,7 +170,7 @@
</ServiceItem>
<ServiceItem
v-if="hasPermi(['student:visit:list'])"
title="班主任走访1"
title="班主任走访"
@click="gotoVisitManage"
>
<template #icon>
+11
View File
@@ -33,6 +33,17 @@ export const getInternshipInfo = (studentId: string | number): AxiosPromise<R<In
});
};
/**
* 获取当前学生实习信息
* @returns 当前登录学生的实习信息
*/
export const getCurrentStudentInternship = (): AxiosPromise<R<InternshipInfoVO>> => {
return request({
url: '/zsjy/internship/current',
method: 'get'
});
};
/**
* 导出实习信息列表
* @param query 查询参数
+102
View File
@@ -0,0 +1,102 @@
import request from '@/utils/request'
// 查询学生月度总结列表
export function listInternshipMonthly(query: any) {
return request({
url: '/zsjy/internship/monthly/list',
method: 'get',
params: query
})
}
// 获取学生月度总结详细信息
export function getInternshipMonthly(monthlyId: string | number) {
return request({
url: '/zsjy/internship/monthly/' + monthlyId,
method: 'get'
})
}
// 新增学生月度总结
export function addInternshipMonthly(data: any) {
return request({
url: '/zsjy/internship/monthly',
method: 'post',
data: data
})
}
// 修改学生月度总结
export function updateInternshipMonthly(data: any) {
return request({
url: '/zsjy/internship/monthly',
method: 'put',
data: data
})
}
// 删除学生月度总结
export function delInternshipMonthly(monthlyIds: string | number | Array<string | number>) {
return request({
url: '/zsjy/internship/monthly/' + monthlyIds,
method: 'delete'
})
}
// 提交月度总结审批
export function submitInternshipMonthly(monthlyId: string | number) {
return request({
url: '/zsjy/internship/monthly/submit/' + monthlyId,
method: 'put'
})
}
// 自动保存草稿
export function autosaveInternshipMonthly(data: any) {
return request({
url: '/zsjy/internship/monthly/autosave',
method: 'post',
data: data
})
}
// 检查月份重复
export function checkSummaryMonth(summaryMonth: string) {
return request({
url: '/zsjy/internship/monthly/check/' + summaryMonth,
method: 'get'
})
}
// 获取月度总结模板
export function getMonthlyTemplate(summaryMonth: string) {
return request({
url: '/zsjy/internship/monthly/template/' + summaryMonth,
method: 'get'
})
}
// 获取状态统计
export function getMonthlyStatistics() {
return request({
url: '/zsjy/internship/monthly/statistics',
method: 'get'
})
}
// 导出月度总结数据
export function exportInternshipMonthly(query: any) {
return request({
url: '/zsjy/internship/monthly/export',
method: 'post',
params: query
})
}
// 获取学生当前实习信息
export function getCurrentInternshipInfo() {
return request({
url: '/zsjy/internship/monthly/current-internship',
method: 'get'
})
}
@@ -0,0 +1,318 @@
<template>
<div class="monthly-detail">
<el-row :gutter="20">
<!-- 左侧详情内容 -->
<el-col :span="16">
<el-card shadow="never" class="mb-4">
<template #header>
<div class="flex justify-between items-center">
<span class="font-medium">基本信息</span>
<dict-tag :options="internship_monthly_status" :value="detail.reviewStatus" />
</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="总结月份">
{{ detail.summaryMonth }}
</el-descriptions-item>
<el-descriptions-item label="技能评分" span="2">
<div class="flex gap-4 flex-wrap">
<el-tag v-if="detail.professionalSkills" :type="getScoreTagType(detail.professionalSkills)">
专业技能: {{ detail.professionalSkills }}分
</el-tag>
<el-tag v-if="detail.communicationSkills" :type="getScoreTagType(detail.communicationSkills)">
沟通技能: {{ detail.communicationSkills }}分
</el-tag>
<el-tag v-if="detail.teamworkAbility" :type="getScoreTagType(detail.teamworkAbility)">
团队协作: {{ detail.teamworkAbility }}分
</el-tag>
<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>
</div>
</el-descriptions-item>
<el-descriptions-item label="总字数">
<el-tag :type="getWordCountTagType(detail.wordCount)">{{ detail.wordCount || 0 }}字</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否逾期">
<el-tag v-if="detail.isOverdue === 1" type="danger">逾期</el-tag>
<el-tag v-else type="success">正常</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never">
<template #header>
<span class="font-medium">总结内容</span>
</template>
<div class="space-y-6">
<div v-if="detail.workContent">
<h4 class="text-base font-medium mb-2 text-gray-800">工作内容</h4>
<div class="bg-gray-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.workContent }}</p>
</div>
</div>
<div v-if="detail.achievements">
<h4 class="text-base font-medium mb-2 text-gray-800">成果成就</h4>
<div class="bg-blue-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.achievements }}</p>
</div>
</div>
<div v-if="detail.learningGains">
<h4 class="text-base font-medium mb-2 text-gray-800">学习收获</h4>
<div class="bg-green-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.learningGains }}</p>
</div>
</div>
<div v-if="detail.problemsEncountered">
<h4 class="text-base font-medium mb-2 text-gray-800">问题困难</h4>
<div class="bg-yellow-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.problemsEncountered }}</p>
</div>
</div>
<div v-if="detail.solutions">
<h4 class="text-base font-medium mb-2 text-gray-800">解决方案</h4>
<div class="bg-purple-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.solutions }}</p>
</div>
</div>
<div v-if="detail.nextMonthPlan">
<h4 class="text-base font-medium mb-2 text-gray-800">下月计划</h4>
<div class="bg-indigo-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.nextMonthPlan }}</p>
</div>
</div>
<div v-if="detail.remark">
<h4 class="text-base font-medium mb-2 text-gray-800">备注说明</h4>
<div class="bg-gray-50 p-4 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.remark }}</p>
</div>
</div>
</div>
</el-card>
</el-col>
<!-- 右侧信息面板 -->
<el-col :span="8">
<el-card shadow="never" class="mb-4">
<template #header>
<span class="font-medium">审批信息</span>
</template>
<div class="space-y-4">
<div class="flex justify-between">
<span>审核状态:</span>
<dict-tag :options="internship_monthly_status" :value="detail.status" />
</div>
<div v-if="detail.submitTime" class="flex justify-between">
<span>提交时间:</span>
<span class="text-sm">{{ parseTime(detail.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</div>
<div v-if="detail.auditTime" class="flex justify-between">
<span>审核时间:</span>
<span class="text-sm">{{ parseTime(detail.auditTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</div>
<div v-if="detail.auditBy" class="flex justify-between">
<span>审核人:</span>
<span class="text-sm">{{ detail.auditByName || detail.auditBy }}</span>
</div>
<div v-if="detail.auditScore" class="flex justify-between">
<span>审核评分:</span>
<el-tag :type="getScoreTagType(detail.auditScore)">{{ detail.auditScore }}分</el-tag>
</div>
</div>
</el-card>
<el-card v-if="detail.auditComment" shadow="never" class="mb-4">
<template #header>
<span class="font-medium">审核意见</span>
</template>
<div class="bg-blue-50 p-3 rounded-md">
<p class="text-gray-700 leading-relaxed whitespace-pre-wrap">{{ detail.auditComment }}</p>
</div>
</el-card>
<el-card shadow="never" class="mb-4">
<template #header>
<span class="font-medium">时间记录</span>
</template>
<div class="space-y-3">
<div class="flex justify-between">
<span>创建时间:</span>
<span class="text-sm">{{ parseTime(detail.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</div>
<div v-if="detail.updateTime" class="flex justify-between">
<span>最后更新:</span>
<span class="text-sm">{{ parseTime(detail.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</div>
<div v-if="detail.createByName" class="flex justify-between">
<span>创建人:</span>
<span class="text-sm">{{ detail.createByName }}</span>
</div>
</div>
</el-card>
<el-card shadow="never">
<template #header>
<span class="font-medium">统计数据</span>
</template>
<div class="space-y-3">
<div class="flex justify-between">
<span>总字数:</span>
<el-tag :type="getWordCountTagType(detail.wordCount)">{{ detail.wordCount || 0 }}字</el-tag>
</div>
<div class="flex justify-between">
<span>自评分数:</span>
<el-tag :type="getScoreTagType(detail.selfScore)">{{ detail.selfScore }}分</el-tag>
</div>
<div v-if="detail.auditScore" class="flex justify-between">
<span>审核评分:</span>
<el-tag :type="getScoreTagType(detail.auditScore)">{{ detail.auditScore }}分</el-tag>
</div>
<div class="flex justify-between">
<span>逾期状态:</span>
<el-tag v-if="detail.isOverdue === 1" type="danger">逾期</el-tag>
<el-tag v-else type="success">正常</el-tag>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 操作按钮 -->
<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-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { getInternshipMonthly, submitInternshipMonthly } from '@/api/zsjy/internshipMonthly'
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'))
const emit = defineEmits(['edit', 'close'])
// 详情数据
const detail = ref<any>({})
const loading = ref(false)
/** 重置表单 */
const resetForm = async (monthlyId: string | number) => {
loading.value = true
try {
const res = await getInternshipMonthly(monthlyId)
detail.value = res.data || {}
} catch (error) {
console.error('获取月度总结详情失败:', error)
proxy?.$modal.msgError('获取详情失败')
} finally {
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'
}
/** 获取字数标签类型 */
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'
}
/** 编辑操作 */
const handleEdit = () => {
emit('edit', detail.value.monthlyId)
}
/** 提交审核操作 */
const handleSubmit = async () => {
try {
await proxy?.$modal.confirm('确认提交该月度总结?提交后将无法再次编辑')
await submitInternshipMonthly(detail.value.monthlyId)
proxy?.$modal.msgSuccess('提交成功')
// 重新加载详情
await resetForm(detail.value.monthlyId)
} catch {}
}
/** 关闭操作 */
const handleClose = () => {
emit('close')
}
defineExpose({
resetForm
})
</script>
<style scoped>
.monthly-detail {
max-height: 70vh;
overflow-y: auto;
}
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
margin-top: 1.5rem;
}
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
margin-top: 1rem;
}
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
margin-top: 0.75rem;
}
.whitespace-pre-wrap {
white-space: pre-wrap;
}
</style>
@@ -0,0 +1,590 @@
<template>
<el-form ref="monthlyFormRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
<!-- 左侧表单 -->
<el-col :span="16">
<el-card shadow="never" class="mb-4">
<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"
>
{{ autoSaving ? '保存中...' : '保存草稿' }}
</el-button>
</div>
</template>
<el-form-item label="总结月份" prop="summaryMonth">
<el-date-picker
v-model="form.summaryMonth"
type="month"
value-format="YYYY-MM"
placeholder="请选择总结月份"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
style="width: 300px"
@change="checkMonthDuplicate"
/>
<el-text class="ml-2" size="small" type="info">选择要总结的月份</el-text>
</el-form-item>
</el-card>
<el-card shadow="never" class="mb-4">
<template #header>
<span class="font-medium">能力自评(1-10分)</span>
</template>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业技能" prop="professionalSkills">
<el-input-number
v-model="form.professionalSkills"
:min="1"
:max="10"
:precision="0"
placeholder="1-10分"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
style="width: 150px"
/>
<el-text class="ml-2" size="small" type="info">专业技能掌握程度</el-text>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="沟通技能" prop="communicationSkills">
<el-input-number
v-model="form.communicationSkills"
:min="1"
:max="10"
:precision="0"
placeholder="1-10分"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
style="width: 150px"
/>
<el-text class="ml-2" size="small" type="info">沟通表达能力</el-text>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="团队协作" prop="teamworkAbility">
<el-input-number
v-model="form.teamworkAbility"
:min="1"
:max="10"
:precision="0"
placeholder="1-10分"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
style="width: 150px"
/>
<el-text class="ml-2" size="small" type="info">团队合作能力</el-text>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学习能力" prop="learningAbility">
<el-input-number
v-model="form.learningAbility"
:min="1"
:max="10"
:precision="0"
placeholder="1-10分"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
style="width: 150px"
/>
<el-text class="ml-2" size="small" type="info">学习新知识能力</el-text>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工作态度" prop="workAttitude">
<el-input-number
v-model="form.workAttitude"
:min="1"
:max="10"
:precision="0"
placeholder="1-10分"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
style="width: 150px"
/>
<el-text class="ml-2" size="small" type="info">工作认真负责程度</el-text>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card shadow="never">
<template #header>
<span class="font-medium">工作总结内容</span>
</template>
<el-form-item label="工作内容" prop="workContent">
<el-input
v-model="form.workContent"
type="textarea"
:rows="6"
placeholder="请详细描述本月的主要工作内容和任务完成情况(必填,最多500字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="500"
@input="updateWordCount"
/>
</el-form-item>
<el-form-item label="成果成就" prop="achievements">
<el-input
v-model="form.achievements"
type="textarea"
:rows="4"
placeholder="请描述本月取得的主要成果和成就(最多300字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="300"
@input="updateWordCount"
/>
</el-form-item>
<el-form-item label="学习收获" prop="learningGains">
<el-input
v-model="form.learningGains"
type="textarea"
:rows="4"
placeholder="请分享本月的学习心得和收获(最多300字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="300"
@input="updateWordCount"
/>
</el-form-item>
<el-form-item label="问题困难" prop="problemsEncountered">
<el-input
v-model="form.problemsEncountered"
type="textarea"
:rows="6"
placeholder="请描述遇到的问题、困难以及解决方案(包含问题和解决办法,最多1000字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="1000"
@input="updateWordCount"
/>
</el-form-item>
<el-form-item label="自我评价" prop="selfEvaluation">
<el-input
v-model="form.selfEvaluation"
type="textarea"
:rows="4"
placeholder="请对本月的工作表现进行自我评价(最多300字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="300"
@input="updateWordCount"
/>
</el-form-item>
<el-form-item label="下月计划" prop="nextMonthPlan">
<el-input
v-model="form.nextMonthPlan"
type="textarea"
:rows="4"
placeholder="请制定下个月的工作计划和目标(最多300字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="300"
@input="updateWordCount"
/>
</el-form-item>
<el-form-item label="备注说明" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="其他需要说明的内容(最多200字)"
:disabled="form.reviewStatus !== undefined && form.reviewStatus !== 3"
show-word-limit
:maxlength="200"
@input="updateWordCount"
/>
</el-form-item>
</el-card>
</el-col>
<!-- 右侧信息面板 -->
<el-col :span="8">
<el-card shadow="never" class="mb-4">
<template #header>
<span class="font-medium">统计信息</span>
</template>
<div class="space-y-3">
<div class="flex justify-between">
<span>总字数:</span>
<el-tag :type="getWordCountTagType(form.wordCount)">{{ form.wordCount || 0 }}字</el-tag>
</div>
<div class="flex justify-between">
<span>状态:</span>
<dict-tag :options="internship_monthly_status" :value="form.reviewStatus" />
</div>
<div v-if="form.submitTime" class="flex justify-between">
<span>提交时间:</span>
<span class="text-sm">{{ parseTime(form.submitTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</div>
<div v-if="form.createTime" class="flex justify-between">
<span>创建时间:</span>
<span class="text-sm">{{ parseTime(form.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</div>
</div>
</el-card>
<el-card shadow="never" class="mb-4">
<template #header>
<span class="font-medium">填写提示</span>
</template>
<div class="text-sm text-gray-600 space-y-2">
<div>• 工作内容为必填项,最多500字</div>
<div>• 其他项目选填,建议填写完整</div>
<div>• 自评分数范围:1-10分</div>
<div>• 支持自动保存草稿功能</div>
<div>• 提交后将无法再次编辑</div>
</div>
</el-card>
<el-card v-if="monthDuplicateCheck.show" shadow="never">
<template #header>
<span class="font-medium">月份检查</span>
</template>
<el-alert
:type="monthDuplicateCheck.type"
:title="monthDuplicateCheck.message"
show-icon
:closable="false"
/>
</el-card>
</el-col>
</el-row>
<el-row class="mt-6">
<el-col :span="24" class="text-center">
<el-button @click="handleCancel">取消</el-button>
<el-button
v-if="form.reviewStatus === undefined || form.reviewStatus === 3"
type="primary"
@click="handleSubmit('draft')"
:loading="submitting"
>
保存草稿
</el-button>
<el-button
v-if="form.reviewStatus === undefined || form.reviewStatus === 3"
type="success"
@click="handleSubmit('submit')"
:loading="submitting"
>
提交审核
</el-button>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import {
getInternshipMonthly,
addInternshipMonthly,
updateInternshipMonthly,
autosaveInternshipMonthly,
checkSummaryMonth,
getMonthlyTemplate,
getCurrentInternshipInfo
} from '@/api/zsjy/internshipMonthly'
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { internship_monthly_status } = toRefs<any>(proxy?.useDict('internship_monthly_status'))
const emit = defineEmits(['success'])
const monthlyFormRef = ref<ElFormInstance>()
const submitting = ref(false)
const autoSaving = ref(false)
// 表单数据
const initFormData = {
monthlyId: undefined,
internshipId: undefined,
summaryMonth: '',
workContent: '',
achievements: '',
learningGains: '',
problemsEncountered: '',
selfEvaluation: '',
nextMonthPlan: '',
professionalSkills: undefined,
communicationSkills: undefined,
teamworkAbility: undefined,
learningAbility: undefined,
workAttitude: undefined,
wordCount: 0,
remark: '',
reviewStatus: undefined,
submitTime: undefined,
createTime: undefined
}
const form = ref({ ...initFormData })
// 表单验证规则
const rules = ref({
summaryMonth: [
{ required: true, message: '总结月份不能为空', trigger: 'change' }
],
workContent: [
{ required: true, message: '工作内容不能为空', trigger: 'blur' },
{ min: 10, message: '工作内容至少需要10个字符', trigger: 'blur' },
{ max: 500, message: '工作内容不能超过500个字符', trigger: 'blur' }
],
professionalSkills: [
{ required: true, message: '专业技能不能为空', trigger: 'blur' },
{ type: 'number', min: 1, max: 10, message: '专业技能必须在1-10之间', trigger: 'blur' }
],
communicationSkills: [
{ required: true, message: '沟通技能不能为空', trigger: 'blur' },
{ type: 'number', min: 1, max: 10, message: '沟通技能必须在1-10之间', trigger: 'blur' }
],
teamworkAbility: [
{ required: true, message: '团队协作不能为空', trigger: 'blur' },
{ type: 'number', min: 1, max: 10, message: '团队协作必须在1-10之间', trigger: 'blur' }
],
learningAbility: [
{ required: true, message: '学习能力不能为空', trigger: 'blur' },
{ type: 'number', min: 1, max: 10, message: '学习能力必须在1-10之间', trigger: 'blur' }
],
workAttitude: [
{ required: true, message: '工作态度不能为空', trigger: 'blur' },
{ type: 'number', min: 1, max: 10, message: '工作态度必须在1-10之间', trigger: 'blur' }
],
problemsEncountered: [
{ required: true, message: '问题困难不能为空', trigger: 'blur' },
{ min: 10, message: '问题困难至少需要10个字符', trigger: 'blur' },
{ max: 1000, message: '问题困难不能超过1000个字符', trigger: 'blur' }
],
selfEvaluation: [
{ 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' }
]
})
// 月份重复检查
const monthDuplicateCheck = ref({
show: false,
type: 'info',
message: ''
})
// 自动保存定时器
let autoSaveTimer: NodeJS.Timeout | null = null
/** 重置表单 */
const resetForm = async (monthlyId?: string | number) => {
form.value = { ...initFormData }
monthDuplicateCheck.value.show = false
if (monthlyId) {
try {
const res = await getInternshipMonthly(monthlyId)
form.value = { ...res.data }
updateWordCount()
} catch (error) {
console.error('获取月度总结详情失败:', error)
}
} else {
// 新增时获取当前月份模板和学生实习信息
const currentMonth = new Date().toISOString().substring(0, 7)
form.value.summaryMonth = currentMonth
await loadMonthlyTemplate(currentMonth)
// 获取学生当前实习信息
try {
const internshipRes = await getCurrentInternshipInfo()
if (internshipRes.code === 200 && internshipRes.data) {
form.value.internshipId = internshipRes.data.internshipId
} else {
// 如果没有进行中的实习记录,提示用户
proxy?.$modal.msgError('您当前没有进行中的实习记录,无法创建月度总结')
emit('success') // 关闭对话框
return
}
} catch (error) {
console.error('获取学生实习信息失败:', error)
proxy?.$modal.msgError('获取实习信息失败,请稍后重试')
emit('success') // 关闭对话框
return
}
}
nextTick(() => {
monthlyFormRef.value?.clearValidate()
})
}
/** 加载月份模板 */
const loadMonthlyTemplate = async (month: string) => {
try {
const res = await getMonthlyTemplate(month)
if (res.data) {
// 如果有模板数据,可以预填充一些内容
// form.value.nextMonthPlan = res.data.suggestedPlan || ''
}
} catch (error) {
console.log('获取月份模板失败,将使用空模板')
}
}
/** 检查月份重复 */
const checkMonthDuplicate = async () => {
if (!form.value.summaryMonth) return
try {
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 = () => {
const content = [
form.value.workContent,
form.value.achievements,
form.value.learningGains,
form.value.problemsEncountered,
form.value.selfEvaluation,
form.value.nextMonthPlan,
form.value.remark
].join('')
form.value.wordCount = content.length
// 设置自动保存定时器
if (form.value.monthlyId && form.value.reviewStatus === 3) {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer)
}
autoSaveTimer = setTimeout(() => {
handleAutoSave()
}, 5000) // 5秒后自动保存
}
}
/** 获取字数标签类型 */
const getWordCountTagType = (count: number) => {
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
autoSaving.value = true
try {
await autosaveInternshipMonthly(form.value)
proxy?.$message.success('草稿已自动保存')
} catch (error) {
console.error('自动保存失败:', error)
} finally {
autoSaving.value = false
}
}
/** 提交表单 */
const handleSubmit = async (type: 'draft' | 'submit') => {
if (!monthlyFormRef.value) return
const valid = await monthlyFormRef.value.validate().catch(() => false)
if (!valid) return
submitting.value = true
try {
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)
} else {
await addInternshipMonthly(formData)
}
proxy?.$modal.msgSuccess(type === 'draft' ? '保存成功' : '提交成功')
emit('success')
} catch (error) {
console.error('提交失败:', error)
} finally {
submitting.value = false
}
}
/** 取消操作 */
const handleCancel = () => {
emit('success')
}
// 清理定时器
onUnmounted(() => {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer)
}
})
defineExpose({
resetForm
})
</script>
<style scoped>
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
margin-top: 0.75rem;
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
margin-top: 0.5rem;
}
</style>
@@ -0,0 +1,388 @@
<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="reviewStatus">
<el-select v-model="queryParams.reviewStatus" placeholder="请选择审核状态" clearable style="width: 100%">
<el-option label="草稿" value="3" />
<el-option label="待审核" value="0" />
<el-option label="已审核" value="1" />
<el-option label="审核拒绝" value="2" />
</el-select>
</el-form-item>
</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-form-item>
</el-col>
<el-col :span="6">
<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>
<!-- 操作按钮区域 -->
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<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-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-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipmonthly:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="monthlyList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="总结月份" align="center" prop="summaryMonth" width="120" />
<el-table-column label="工作内容" align="center" prop="workContent" :show-overflow-tooltip="true" min-width="200" />
<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">
<dict-tag :options="internship_monthly_status" :value="scope.row.reviewStatus" />
</template>
</el-table-column>
<el-table-column label="是否逾期" align="center" prop="isOverdue" width="100">
<template #default="scope">
<el-tag v-if="scope.row.isOverdue === 1" type="danger">逾期</el-tag>
<el-tag v-else type="success">正常</el-tag>
</template>
</el-table-column>
<el-table-column label="提交时间" align="center" prop="submitTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200" fixed="right">
<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-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-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-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-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"
/>
</el-card>
<!-- 添加或修改月度总结对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
<MonthlyForm ref="monthlyFormRef" @success="getList" />
</el-dialog>
<!-- 月度总结详情对话框 -->
<el-dialog title="月度总结详情" v-model="detailDialog.visible" width="1000px" append-to-body>
<MonthlyDetail ref="monthlyDetailRef" />
</el-dialog>
</div>
</template>
<script setup name="InternshipMonthly" lang="ts">
import {
listInternshipMonthly,
getInternshipMonthly,
delInternshipMonthly,
submitInternshipMonthly,
exportInternshipMonthly
} 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 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,
pageSize: 10,
summaryMonth: undefined,
reviewStatus: undefined,
professionalSkills: undefined
}
const data = reactive<PageData<any, any>>({
form: {},
queryParams: { ...initFormData },
rules: {}
})
const { queryParams, form, rules } = toRefs(data)
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
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
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
}
/** 新增按钮操作 */
const handleAdd = () => {
dialog.visible = true
dialog.title = '添加月度总结'
nextTick(() => {
monthlyFormRef.value?.resetForm()
})
}
/** 修改按钮操作 */
const handleUpdate = async (row?: any) => {
dialog.visible = true
dialog.title = '修改月度总结'
nextTick(() => {
const monthlyId = row?.monthlyId || ids.value[0]
monthlyFormRef.value?.resetForm(monthlyId)
})
}
/** 查看详情操作 */
const handleDetail = async (row: any) => {
detailDialog.visible = true
nextTick(() => {
monthlyDetailRef.value?.resetForm(row.monthlyId)
})
}
/** 提交审批操作 */
const handleSubmit = async (row: any) => {
try {
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
try {
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`)
}
/** 计算五项技能的平均分 */
const getAverageScore = (row: any) => {
const scores = [
row.professionalSkills || 0,
row.communicationSkills || 0,
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
}
/** 根据分数获取标签类型 */
const getScoreTagType = (score: number) => {
if (score >= 9) return 'success'
if (score >= 7) return 'warning'
if (score >= 5) return ''
return 'danger'
}
onMounted(() => {
getList()
})
</script>
<style scoped>
/* 操作按钮样式优化 - 参考后勤管理页面 */
:deep(.el-table__fixed-right) {
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
}
:deep(.el-table__fixed-right .el-table__cell) {
background-color: #fff;
}
:deep(.el-table__row:hover .el-table__cell) {
background-color: #f5f7fa !important;
}
/* 操作列按钮间距优化 */
.small-padding .el-button + .el-button {
margin-left: 8px;
}
/* 确保操作按钮在固定列中居中对齐 */
.small-padding {
text-align: center;
}
.small-padding .el-tooltip {
display: inline-block;
margin-right: 8px;
}
.small-padding .el-tooltip:last-child {
margin-right: 0;
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
</style>