This commit is contained in:
李旭明 committed 2025-07-24 10:18:35 +08:00
1 parent 225a268e32
commit b49b6f8ec2
45 files changed
+11407 -13

No files matched your search

+6
View File
@@ -400,6 +400,12 @@
<artifactId>ruoyi-aqbw</artifactId>
<version>${revision}</version>
</dependency>
<!-- 心理健康管理模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-xljk</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
+6
View File
@@ -128,6 +128,12 @@
<groupId>org.dromara</groupId>
<artifactId>ruoyi-aqbw</artifactId>
</dependency>
<!-- 心理健康管理模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-xljk</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
@@ -72,9 +72,9 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
url: jdbc:mysql://localhost:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: hn02le.34lkdLKD
password: 'DP@xp117'
# # 从库数据源
# slave:
# lazy: true
@@ -137,6 +137,8 @@ tenant:
- sys_user_role
- sys_client
- sys_oss_config
- xljk_evaluation
- xljk_evaluation_detail
# MyBatisPlus配置
# https://baomidou.com/config/
@@ -2,9 +2,9 @@ spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
url: jdbc:mysql://localhost:3306/hzdyjs_xg?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: hn02le.34lkdLKD
password: "DP@xp117"
hikari:
connection-timeout: 30000
validation-timeout: 5000
+8 -7
View File
@@ -9,18 +9,19 @@
<modelVersion>4.0.0</modelVersion>
<modules>
<module>ruoyi-demo</module>
<module>ruoyi-system</module>
<module>ruoyi-generator</module>
<module>ruoyi-job</module>
<module>ruoyi-system</module>
<module>ruoyi-demo</module>
<module>ruoyi-workflow</module>
<module>ruoyi-hq</module>
<module>ruoyi-rs</module>
<module>ruoyi-cw</module>
<module>ruoyi-aqbw</module>
<module>ruoyi-zsjy</module>
<module>ruoyi-xg</module>
<module>ruoyi-xg-xx</module>
<module>ruoyi-zsjy</module>
<module>ruoyi-hq</module>
<module>ruoyi-aqbw</module>
<module>ruoyi-cw</module>
<module>ruoyi-rs</module>
<module>ruoyi-xljk</module>
</modules>
<artifactId>ruoyi-modules</artifactId>
+81
View File
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-modules</artifactId>
<groupId>org.dromara</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-xljk</artifactId>
<description>
心理健康管理模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- 安全模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<!-- web服务 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<!-- excel -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<!-- 多租户 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<!-- 幂等性 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<!-- 日志记录 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-oss</artifactId>
</dependency>
<!-- 翻译模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-translation</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,217 @@
package org.dromara.xljk.controller;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
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.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.xljk.domain.vo.XljkEvaluationVo;
import org.dromara.xljk.domain.bo.XljkEvaluationBo;
import org.dromara.xljk.service.IXljkEvaluationService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 心理健康测评Controller
*
* @author Cursor AI
* @date 2025-07-02
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/xljk/evaluation")
public class XljkEvaluationController extends BaseController {
private final IXljkEvaluationService evaluationService;
/**
* 查询心理健康测评列表
*/
@SaCheckPermission("xljk:evaluation:list")
@GetMapping("/list")
public TableDataInfo<XljkEvaluationVo> list(XljkEvaluationBo bo, PageQuery pageQuery) {
return evaluationService.queryPageList(bo, pageQuery);
}
/**
* 导出心理健康测评列表
*/
@SaCheckPermission("xljk:evaluation:export")
@Log(title = "心理健康测评", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(XljkEvaluationBo bo, HttpServletResponse response) {
List<XljkEvaluationVo> list = evaluationService.queryList(bo);
ExcelUtil.exportExcel(list, "心理健康测评", XljkEvaluationVo.class, response);
}
/**
* 获取心理健康测评详细信息
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/{id}")
public R<XljkEvaluationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(evaluationService.queryById(id));
}
/**
* 新增心理健康测评
*/
@SaCheckPermission("xljk:evaluation:add")
@Log(title = "心理健康测评", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Long> add(@Validated(AddGroup.class) @RequestBody XljkEvaluationBo bo) {
boolean success = evaluationService.insertByBo(bo);
if (success) {
return R.ok(bo.getId());
} else {
return R.fail("新增心理健康测评失败");
}
}
/**
* 修改心理健康测评
*/
@SaCheckPermission("xljk:evaluation:edit")
@Log(title = "心理健康测评", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody XljkEvaluationBo bo) {
return toAjax(evaluationService.updateByBo(bo));
}
/**
* 删除心理健康测评
*/
@SaCheckPermission("xljk:evaluation:remove")
@Log(title = "心理健康测评", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(evaluationService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 获取院系选项
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/options/department")
public R<List<Map<String, Object>>> getDepartmentOptions() {
return R.ok(evaluationService.getDepartmentOptions());
}
/**
* 获取专业和班级选项
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/options/majorAndClass")
public R<Map<String, List<Map<String, Object>>>> getMajorAndClassOptions(
@RequestParam(required = false) Long departmentId
) {
return R.ok(evaluationService.getMajorAndClassOptions(departmentId));
}
/**
* 获取学期选项
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/options/semester")
public R<List<String>> getSemesterOptions() {
return R.ok(evaluationService.getSemesterOptions());
}
/**
* 发布测评
*/
@SaCheckPermission("xljk:evaluation:publish")
@Log(title = "发布测评", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/{id}/publish")
public R<Void> publishEvaluation(@NotNull(message = "测评ID不能为空") @PathVariable Long id) {
return toAjax(evaluationService.publishEvaluation(id));
}
/**
* 完成测评
*/
@SaCheckPermission("xljk:evaluation:complete")
@Log(title = "完成测评", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/{id}/complete")
public R<Void> completeEvaluation(@NotNull(message = "测评ID不能为空") @PathVariable Long id) {
return toAjax(evaluationService.completeEvaluation(id));
}
/**
* 获取测评统计数据
*/
@SaCheckPermission("xljk:evaluation:statistics")
@GetMapping("/{id}/statistics")
public R<Map<String, Object>> getEvaluationStatistics(@NotNull(message = "测评ID不能为空") @PathVariable Long id) {
return R.ok(evaluationService.getEvaluationStatistics(id));
}
/**
* 获取重点关注学生统计
*/
@SaCheckPermission("xljk:evaluation:statistics")
@GetMapping("/statistics/focus")
public R<Map<String, Object>> getFocusStudentsStatistics(XljkEvaluationBo bo) {
return R.ok(evaluationService.getFocusStudentsStatistics(bo));
}
/**
* 获取院系列表
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/options/colleges")
public R<List<Map<String, Object>>> getCollegeOptions() {
return R.ok(evaluationService.getCollegeOptions());
}
/**
* 获取专业列表
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/options/majors")
public R<List<Map<String, Object>>> getMajorOptions(@RequestParam(required = false) String collegeName) {
return R.ok(evaluationService.getMajorOptions(collegeName));
}
/**
* 获取班级列表
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/options/classes")
public R<List<Map<String, Object>>> getClassOptions(
@RequestParam(required = false) String collegeName,
@RequestParam(required = false) String majorName
) {
return R.ok(evaluationService.getClassOptions(collegeName, majorName));
}
/**
* 获取班级学生列表
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/students")
public R<List<Map<String, Object>>> getStudentsByClass(
@RequestParam(required = false) String collegeName,
@RequestParam(required = false) String majorName,
@RequestParam(required = false) String className
) {
return R.ok(evaluationService.getStudentsByClass(collegeName, majorName, className));
}
}
@@ -0,0 +1,154 @@
package org.dromara.xljk.controller;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
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.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.xljk.domain.vo.XljkEvaluationDetailVo;
import org.dromara.xljk.domain.bo.XljkEvaluationDetailBo;
import org.dromara.xljk.service.IXljkEvaluationDetailService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 心理健康测评明细Controller
*
* @author Cursor AI
* @date 2025-07-02
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/xljk/evaluation/detail")
public class XljkEvaluationDetailController extends BaseController {
private final IXljkEvaluationDetailService detailService;
/**
* 查询心理健康测评明细列表
*/
@SaCheckPermission("xljk:evaluation:list")
@GetMapping("/list")
public TableDataInfo<XljkEvaluationDetailVo> list(XljkEvaluationDetailBo bo, PageQuery pageQuery) {
return detailService.queryPageList(bo, pageQuery);
}
/**
* 导出心理健康测评明细列表
*/
@SaCheckPermission("xljk:evaluation:export")
@Log(title = "心理健康测评明细", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(XljkEvaluationDetailBo bo, HttpServletResponse response) {
List<XljkEvaluationDetailVo> list = detailService.queryList(bo);
ExcelUtil.exportExcel(list, "心理健康测评明细", XljkEvaluationDetailVo.class, response);
}
/**
* 获取心理健康测评明细详细信息
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/{id}")
public R<XljkEvaluationDetailVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(detailService.queryById(id));
}
/**
* 新增心理健康测评明细
*/
@SaCheckPermission("xljk:evaluation:add")
@Log(title = "心理健康测评明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Long> add(@Validated(AddGroup.class) @RequestBody XljkEvaluationDetailBo bo) {
boolean success = detailService.insertByBo(bo);
if (success) {
return R.ok(bo.getId());
} else {
return R.fail("新增心理健康测评明细失败");
}
}
/**
* 批量新增心理健康测评明细
*/
@SaCheckPermission("xljk:evaluation:add")
@Log(title = "批量新增心理健康测评明细", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/batch")
public R<Void> addBatch(@RequestBody List<XljkEvaluationDetailBo> bos) {
return toAjax(detailService.insertBatch(bos));
}
/**
* 修改心理健康测评明细
*/
@SaCheckPermission("xljk:evaluation:edit")
@Log(title = "心理健康测评明细", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody XljkEvaluationDetailBo bo) {
return toAjax(detailService.updateByBo(bo));
}
/**
* 删除心理健康测评明细
*/
@SaCheckPermission("xljk:evaluation:remove")
@Log(title = "心理健康测评明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(detailService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 根据测评ID查询明细列表
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/evaluation/{evaluationId}")
public R<List<XljkEvaluationDetailVo>> getDetailsByEvaluationId(@NotNull(message = "测评ID不能为空") @PathVariable Long evaluationId) {
return R.ok(detailService.queryByEvaluationId(evaluationId));
}
/**
* 获取学生信息
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/students/{classId}")
public R<List<Map<String, Object>>> getStudentsByClassId(@NotNull(message = "班级ID不能为空") @PathVariable Long classId) {
return R.ok(detailService.getStudentsByClassId(classId));
}
/**
* 批量更新学生心理状态
*/
@SaCheckPermission("xljk:evaluation:edit")
@Log(title = "批量更新心理状态", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/batch/mentalStatus")
public R<Void> batchUpdateMentalStatus(@RequestBody List<XljkEvaluationDetailBo> bos) {
return toAjax(detailService.batchUpdateMentalStatus(bos));
}
/**
* 获取重点关注学生列表
*/
@SaCheckPermission("xljk:evaluation:query")
@GetMapping("/focus")
public TableDataInfo<XljkEvaluationDetailVo> getFocusStudents(XljkEvaluationDetailBo bo, PageQuery pageQuery) {
return detailService.queryFocusStudents(bo, pageQuery);
}
}
@@ -0,0 +1,85 @@
package org.dromara.xljk.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 心理健康测评对象 xljk_evaluation
*
* @author Cursor AI
* @date 2025-07-02
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("xljk_evaluation")
public class XljkEvaluation extends TenantEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 院系ID
*/
private Long departmentId;
/**
* 院系名称
*/
private String departmentName;
/**
* 测评名称
*/
private String evaluationName;
/**
* 专业ID
*/
private Long majorId;
/**
* 专业名称
*/
private String majorName;
/**
* 班级ID
*/
private Long classId;
/**
* 班级名称
*/
private String className;
/**
* 学期(如2024上学期)
*/
private String semester;
/**
* 状态(0-草稿,1-已发布,2-已完成)
*/
private String status;
/**
* 创建者姓名
*/
private String createName;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,80 @@
package org.dromara.xljk.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 心理健康测评明细对象 xljk_evaluation_detail
*
* @author Cursor AI
* @date 2025-07-02
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("xljk_evaluation_detail")
public class XljkEvaluationDetail extends TenantEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 测评表ID
*/
private Long evaluationId;
/**
* 学生ID
*/
private Long studentId;
/**
* 学号
*/
private String studentNo;
/**
* 学生姓名
*/
private String studentName;
/**
* 性别(0-男,1-女,2-未知)
*/
private String gender;
/**
* 出生日期
*/
private Date birthDate;
/**
* 联系电话
*/
private String phone;
/**
* 心理状态(1-健康,2-良好,3-重点关注)
*/
private String mentalStatus;
/**
* 分数(仅重点关注时填写)
*/
private Integer score;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,108 @@
package org.dromara.xljk.domain.bo;
import org.dromara.xljk.domain.XljkEvaluation;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 心理健康测评业务对象 xljk_evaluation
*
* @author Cursor AI
* @date 2025-07-02
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = XljkEvaluation.class, reverseConvertGenerate = false)
public class XljkEvaluationBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 测评名称
*/
@NotBlank(message = "测评名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String evaluationName;
/**
* 专业ID
*/
@NotNull(message = "专业ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long majorId;
/**
* 专业名称
*/
@NotBlank(message = "专业名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String majorName;
/**
* 班级ID
*/
@NotNull(message = "班级ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long classId;
/**
* 班级名称
*/
@NotBlank(message = "班级名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String className;
/**
* 学期(如2024上学期)
*/
@NotBlank(message = "学期不能为空", groups = { AddGroup.class, EditGroup.class })
private String semester;
/**
* 状态(0-草稿,1-已发布,2-已完成)
*/
private String status;
/**
* 创建者姓名
*/
private String createName;
/**
* 备注
*/
private String remark;
/**
* 状态IN查询(用于状态过滤)
*/
private List<String> statusIn;
/**
* 专业ID IN查询(用于专业过滤)
*/
private List<Long> majorIdIn;
/**
* 班级ID IN查询(用于班级过滤)
*/
private List<Long> classIdIn;
/**
* 学期IN查询(用于学期过滤)
*/
private List<String> semesterIn;
/**
* 创建者ID IN查询(用于权限过滤)
*/
private List<Long> createByIn;
}
@@ -0,0 +1,108 @@
package org.dromara.xljk.domain.bo;
import org.dromara.xljk.domain.XljkEvaluationDetail;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 心理健康测评明细业务对象 xljk_evaluation_detail
*
* @author Cursor AI
* @date 2025-07-02
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = XljkEvaluationDetail.class, reverseConvertGenerate = false)
public class XljkEvaluationDetailBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 测评表ID
*/
@NotNull(message = "测评表ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long evaluationId;
/**
* 学生ID
*/
@NotNull(message = "学生ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long studentId;
/**
* 学号
*/
@NotBlank(message = "学号不能为空", groups = { AddGroup.class, EditGroup.class })
private String studentNo;
/**
* 学生姓名
*/
@NotBlank(message = "学生姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String studentName;
/**
* 性别(0-男,1-女,2-未知)
*/
private String gender;
/**
* 出生日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
/**
* 联系电话
*/
private String phone;
/**
* 心理状态(1-健康,2-良好,3-重点关注)
*/
@NotBlank(message = "心理状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String mentalStatus;
/**
* 分数(仅重点关注时填写)
*/
private Integer score;
/**
* 备注
*/
private String remark;
/**
* 测评ID IN查询(用于批量查询)
*/
private List<Long> evaluationIdIn;
/**
* 学生ID IN查询(用于批量查询)
*/
private List<Long> studentIdIn;
/**
* 心理状态IN查询(用于状态过滤)
*/
private List<String> mentalStatusIn;
/**
* 批量学生数据(用于批量保存)
*/
private List<XljkEvaluationDetailBo> studentList;
}
@@ -0,0 +1,144 @@
package org.dromara.xljk.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.xljk.domain.XljkEvaluationDetail;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 心理健康测评明细视图对象 xljk_evaluation_detail
*
* @author Cursor AI
* @date 2025-07-02
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = XljkEvaluationDetail.class)
public class XljkEvaluationDetailVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 测评表ID
*/
@ExcelProperty(value = "测评表ID")
private Long evaluationId;
/**
* 学生ID
*/
@ExcelProperty(value = "学生ID")
private Long studentId;
/**
* 学号
*/
@ExcelProperty(value = "学号")
private String studentNo;
/**
* 学生姓名
*/
@ExcelProperty(value = "学生姓名")
private String studentName;
/**
* 性别(0-男,1-女,2-未知)
*/
@ExcelProperty(value = "性别", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_user_sex")
private String gender;
/**
* 出生日期
*/
@ExcelProperty(value = "出生日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
/**
* 联系电话
*/
@ExcelProperty(value = "联系电话")
private String phone;
/**
* 心理状态(1-健康,2-良好,3-重点关注)
*/
@ExcelProperty(value = "心理状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "xljk_mental_status")
private String mentalStatus;
/**
* 分数(仅重点关注时填写)
*/
@ExcelProperty(value = "分数")
private Integer score;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 测评名称(非数据库字段)
*/
@ExcelProperty(value = "测评名称")
private String evaluationName;
/**
* 班级名称(非数据库字段)
*/
@ExcelProperty(value = "班级名称")
private String className;
/**
* 专业名称(非数据库字段)
*/
@ExcelProperty(value = "专业名称")
private String majorName;
/**
* 学期(非数据库字段)
*/
@ExcelProperty(value = "学期")
private String semester;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,136 @@
package org.dromara.xljk.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.xljk.domain.XljkEvaluation;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 心理健康测评视图对象 xljk_evaluation
*
* @author Cursor AI
* @date 2025-07-02
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = XljkEvaluation.class)
public class XljkEvaluationVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 院系ID
*/
@ExcelProperty(value = "院系ID")
private Long departmentId;
/**
* 院系名称
*/
@ExcelProperty(value = "院系名称")
private String departmentName;
/**
* 测评名称
*/
@ExcelProperty(value = "测评名称")
private String evaluationName;
/**
* 专业ID
*/
@ExcelProperty(value = "专业ID")
private Long majorId;
/**
* 专业名称
*/
@ExcelProperty(value = "专业名称")
private String majorName;
/**
* 班级ID
*/
@ExcelProperty(value = "班级ID")
private Long classId;
/**
* 班级名称
*/
@ExcelProperty(value = "班级名称")
private String className;
/**
* 学期(如2024上学期)
*/
@ExcelProperty(value = "学期")
private String semester;
/**
* 状态(0-草稿,1-已发布,2-已完成)
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "xljk_evaluation_status")
private String status;
/**
* 创建者姓名
*/
@ExcelProperty(value = "创建者姓名")
private String createName;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 学生数量(非数据库字段)
*/
@ExcelProperty(value = "学生数量")
private Integer studentCount;
/**
* 重点关注数量(非数据库字段)
*/
@ExcelProperty(value = "重点关注数量")
private Integer focusCount;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,15 @@
package org.dromara.xljk.mapper;
import org.dromara.xljk.domain.XljkEvaluationDetail;
import org.dromara.xljk.domain.vo.XljkEvaluationDetailVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 心理健康测评明细Mapper接口
*
* @author Cursor AI
* @date 2025-07-02
*/
public interface XljkEvaluationDetailMapper extends BaseMapperPlus<XljkEvaluationDetail, XljkEvaluationDetailVo> {
}
@@ -0,0 +1,15 @@
package org.dromara.xljk.mapper;
import org.dromara.xljk.domain.XljkEvaluation;
import org.dromara.xljk.domain.vo.XljkEvaluationVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 心理健康测评Mapper接口
*
* @author Cursor AI
* @date 2025-07-02
*/
public interface XljkEvaluationMapper extends BaseMapperPlus<XljkEvaluation, XljkEvaluationVo> {
}
@@ -0,0 +1,119 @@
package org.dromara.xljk.service;
import org.dromara.xljk.domain.vo.XljkEvaluationDetailVo;
import org.dromara.xljk.domain.bo.XljkEvaluationDetailBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 心理健康测评明细Service接口
*
* @author Cursor AI
* @date 2025-07-02
*/
public interface IXljkEvaluationDetailService {
/**
* 查询心理健康测评明细
*
* @param id 主键
* @return 心理健康测评明细
*/
XljkEvaluationDetailVo queryById(Long id);
/**
* 分页查询心理健康测评明细列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 心理健康测评明细分页列表
*/
TableDataInfo<XljkEvaluationDetailVo> queryPageList(XljkEvaluationDetailBo bo, PageQuery pageQuery);
/**
* 查询符合条件的心理健康测评明细列表
*
* @param bo 查询条件
* @return 心理健康测评明细列表
*/
List<XljkEvaluationDetailVo> queryList(XljkEvaluationDetailBo bo);
/**
* 新增心理健康测评明细
*
* @param bo 心理健康测评明细
* @return 是否新增成功
*/
Boolean insertByBo(XljkEvaluationDetailBo bo);
/**
* 批量新增心理健康测评明细
*
* @param bos 心理健康测评明细列表
* @return 是否新增成功
*/
Boolean insertBatch(List<XljkEvaluationDetailBo> bos);
/**
* 修改心理健康测评明细
*
* @param bo 心理健康测评明细
* @return 是否修改成功
*/
Boolean updateByBo(XljkEvaluationDetailBo bo);
/**
* 校验并批量删除心理健康测评明细信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据测评ID查询明细列表
*
* @param evaluationId 测评ID
* @return 明细列表
*/
List<XljkEvaluationDetailVo> queryByEvaluationId(Long evaluationId);
/**
* 根据测评ID删除明细
*
* @param evaluationId 测评ID
* @return 是否删除成功
*/
Boolean deleteByEvaluationId(Long evaluationId);
/**
* 获取学生信息(从学生信息管理模块)
*
* @param classId 班级ID
* @return 学生信息列表
*/
List<Map<String, Object>> getStudentsByClassId(Long classId);
/**
* 批量更新学生心理状态
*
* @param bos 学生心理状态数据
* @return 是否更新成功
*/
Boolean batchUpdateMentalStatus(List<XljkEvaluationDetailBo> bos);
/**
* 获取重点关注学生列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 重点关注学生列表
*/
TableDataInfo<XljkEvaluationDetailVo> queryFocusStudents(XljkEvaluationDetailBo bo, PageQuery pageQuery);
}
@@ -0,0 +1,167 @@
package org.dromara.xljk.service;
import org.dromara.xljk.domain.vo.XljkEvaluationVo;
import org.dromara.xljk.domain.bo.XljkEvaluationBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 心理健康测评Service接口
*
* @author Cursor AI
* @date 2025-07-02
*/
public interface IXljkEvaluationService {
/**
* 查询心理健康测评
*
* @param id 主键
* @return 心理健康测评
*/
XljkEvaluationVo queryById(Long id);
/**
* 分页查询心理健康测评列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 心理健康测评分页列表
*/
TableDataInfo<XljkEvaluationVo> queryPageList(XljkEvaluationBo bo, PageQuery pageQuery);
/**
* 查询符合条件的心理健康测评列表
*
* @param bo 查询条件
* @return 心理健康测评列表
*/
List<XljkEvaluationVo> queryList(XljkEvaluationBo bo);
/**
* 新增心理健康测评
*
* @param bo 心理健康测评
* @return 是否新增成功
*/
Boolean insertByBo(XljkEvaluationBo bo);
/**
* 修改心理健康测评
*
* @param bo 心理健康测评
* @return 是否修改成功
*/
Boolean updateByBo(XljkEvaluationBo bo);
/**
* 校验并批量删除心理健康测评信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取院系选项
*
* @return 院系选项数据
*/
List<Map<String, Object>> getCollegeOptions();
/**
* 获取专业选项
*
* @param collegeName 院系名称,可为空
* @return 专业选项数据
*/
List<Map<String, Object>> getMajorOptions(String collegeName);
/**
* 获取班级选项
*
* @param collegeName 院系名称,可为空
* @param majorName 专业名称,可为空
* @return 班级选项数据
*/
List<Map<String, Object>> getClassOptions(String collegeName, String majorName);
/**
* 获取学生列表
*
* @param collegeName 院系名称,可为空
* @param majorName 专业名称,可为空
* @param className 班级名称,可为空
* @return 学生列表数据
*/
List<Map<String, Object>> getStudentsByClass(String collegeName, String majorName, String className);
/**
* 获取院系选项
*
* @return 院系选项数据
*/
List<Map<String, Object>> getDepartmentOptions();
/**
* 获取专业和班级选项
*
* @param departmentId 院系ID,可为空
* @return 专业和班级选项数据
*/
Map<String, List<Map<String, Object>>> getMajorAndClassOptions(Long departmentId);
/**
* 获取专业和班级选项(兼容旧方法)
*
* @return 专业和班级选项数据
*/
default Map<String, List<Map<String, Object>>> getMajorAndClassOptions() {
return getMajorAndClassOptions(null);
}
/**
* 获取学期选项
*
* @return 学期选项列表
*/
List<String> getSemesterOptions();
/**
* 发布测评
*
* @param id 测评ID
* @return 是否成功
*/
Boolean publishEvaluation(Long id);
/**
* 完成测评
*
* @param id 测评ID
* @return 是否成功
*/
Boolean completeEvaluation(Long id);
/**
* 获取测评统计数据
*
* @param id 测评ID
* @return 统计数据
*/
Map<String, Object> getEvaluationStatistics(Long id);
/**
* 获取重点关注学生统计
*
* @param bo 查询条件
* @return 统计数据
*/
Map<String, Object> getFocusStudentsStatistics(XljkEvaluationBo bo);
}
@@ -0,0 +1,460 @@
package org.dromara.xljk.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.xljk.domain.bo.XljkEvaluationDetailBo;
import org.dromara.xljk.domain.vo.XljkEvaluationDetailVo;
import org.dromara.xljk.domain.XljkEvaluationDetail;
import org.dromara.xljk.domain.XljkEvaluation;
import org.dromara.xljk.mapper.XljkEvaluationDetailMapper;
import org.dromara.xljk.mapper.XljkEvaluationMapper;
import org.dromara.xljk.service.IXljkEvaluationDetailService;
import org.dromara.common.core.exception.ServiceException;
import java.util.*;
/**
* 心理健康测评明细Service业务层处理
*
* @author Cursor AI
* @date 2025-07-02
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class XljkEvaluationDetailServiceImpl implements IXljkEvaluationDetailService {
private final XljkEvaluationDetailMapper baseMapper;
private final XljkEvaluationMapper evaluationMapper;
/**
* 查询心理健康测评明细
*
* @param id 主键
* @return 心理健康测评明细
*/
@Override
public XljkEvaluationDetailVo queryById(Long id) {
XljkEvaluationDetailVo vo = baseMapper.selectVoById(id);
if (vo != null) {
// 查询测评信息
XljkEvaluation evaluation = evaluationMapper.selectById(vo.getEvaluationId());
if (evaluation != null) {
vo.setEvaluationName(evaluation.getEvaluationName());
vo.setClassName(evaluation.getClassName());
vo.setMajorName(evaluation.getMajorName());
vo.setSemester(evaluation.getSemester());
}
}
return vo;
}
/**
* 分页查询心理健康测评明细列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 心理健康测评明细分页列表
*/
@Override
public TableDataInfo<XljkEvaluationDetailVo> queryPageList(XljkEvaluationDetailBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<XljkEvaluationDetail> lqw = buildQueryWrapper(bo);
Page<XljkEvaluationDetailVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
// 补充测评信息
if (result.getRecords().size() > 0) {
// 获取所有测评ID
Set<Long> evaluationIds = new HashSet<>();
for (XljkEvaluationDetailVo vo : result.getRecords()) {
evaluationIds.add(vo.getEvaluationId());
}
// 批量查询测评信息
LambdaQueryWrapper<XljkEvaluation> evalLqw = Wrappers.lambdaQuery();
evalLqw.in(XljkEvaluation::getId, evaluationIds);
List<XljkEvaluation> evaluations = evaluationMapper.selectList(evalLqw);
Map<Long, XljkEvaluation> evaluationMap = new HashMap<>();
for (XljkEvaluation evaluation : evaluations) {
evaluationMap.put(evaluation.getId(), evaluation);
}
// 填充测评信息
for (XljkEvaluationDetailVo vo : result.getRecords()) {
XljkEvaluation evaluation = evaluationMap.get(vo.getEvaluationId());
if (evaluation != null) {
vo.setEvaluationName(evaluation.getEvaluationName());
vo.setClassName(evaluation.getClassName());
vo.setMajorName(evaluation.getMajorName());
vo.setSemester(evaluation.getSemester());
}
}
}
return TableDataInfo.build(result);
}
/**
* 查询符合条件的心理健康测评明细列表
*
* @param bo 查询条件
* @return 心理健康测评明细列表
*/
@Override
public List<XljkEvaluationDetailVo> queryList(XljkEvaluationDetailBo bo) {
LambdaQueryWrapper<XljkEvaluationDetail> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<XljkEvaluationDetail> buildQueryWrapper(XljkEvaluationDetailBo bo) {
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getEvaluationId() != null, XljkEvaluationDetail::getEvaluationId, bo.getEvaluationId());
lqw.eq(bo.getStudentId() != null, XljkEvaluationDetail::getStudentId, bo.getStudentId());
lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XljkEvaluationDetail::getStudentNo, bo.getStudentNo());
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XljkEvaluationDetail::getStudentName, bo.getStudentName());
lqw.eq(StringUtils.isNotBlank(bo.getGender()), XljkEvaluationDetail::getGender, bo.getGender());
lqw.eq(StringUtils.isNotBlank(bo.getMentalStatus()), XljkEvaluationDetail::getMentalStatus, bo.getMentalStatus());
lqw.in(CollUtil.isNotEmpty(bo.getEvaluationIdIn()), XljkEvaluationDetail::getEvaluationId, bo.getEvaluationIdIn());
lqw.in(CollUtil.isNotEmpty(bo.getStudentIdIn()), XljkEvaluationDetail::getStudentId, bo.getStudentIdIn());
lqw.in(CollUtil.isNotEmpty(bo.getMentalStatusIn()), XljkEvaluationDetail::getMentalStatus, bo.getMentalStatusIn());
return lqw;
}
/**
* 新增心理健康测评明细
*
* @param bo 心理健康测评明细
* @return 是否新增成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(XljkEvaluationDetailBo bo) {
XljkEvaluationDetail add = MapstructUtils.convert(bo, XljkEvaluationDetail.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 批量新增心理健康测评明细
*
* @param bos 心理健康测评明细列表
* @return 是否新增成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertBatch(List<XljkEvaluationDetailBo> bos) {
if (CollUtil.isEmpty(bos)) {
return false;
}
List<XljkEvaluationDetail> entities = new ArrayList<>();
for (XljkEvaluationDetailBo bo : bos) {
XljkEvaluationDetail entity = MapstructUtils.convert(bo, XljkEvaluationDetail.class);
validEntityBeforeSave(entity);
entities.add(entity);
}
return baseMapper.insertBatch(entities);
}
/**
* 修改心理健康测评明细
*
* @param bo 心理健康测评明细
* @return 是否修改成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(XljkEvaluationDetailBo bo) {
XljkEvaluationDetail update = MapstructUtils.convert(bo, XljkEvaluationDetail.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*
* @param entity 实体类数据
*/
private void validEntityBeforeSave(XljkEvaluationDetail entity) {
// 校验评测状态
if (entity.getEvaluationId() != null) {
XljkEvaluation evaluation = evaluationMapper.selectById(entity.getEvaluationId());
if (evaluation != null && "2".equals(evaluation.getStatus())) {
throw new ServiceException("测评已完成,不能修改");
}
}
// 校验分数
if ("3".equals(entity.getMentalStatus()) && entity.getScore() == null) {
throw new ServiceException("重点关注状态必须填写分数");
}
}
/**
* 校验并批量删除心理健康测评明细信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 校验是否允许删除
for (Long id : ids) {
XljkEvaluationDetail detail = baseMapper.selectById(id);
if (detail != null) {
XljkEvaluation evaluation = evaluationMapper.selectById(detail.getEvaluationId());
if (evaluation != null && !"0".equals(evaluation.getStatus())) {
throw new ServiceException("测评已发布或已完成,不能删除");
}
}
}
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 根据测评ID查询明细列表
*
* @param evaluationId 测评ID
* @return 明细列表
*/
@Override
public List<XljkEvaluationDetailVo> queryByEvaluationId(Long evaluationId) {
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, evaluationId);
return baseMapper.selectVoList(lqw);
}
/**
* 根据测评ID删除明细
*
* @param evaluationId 测评ID
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteByEvaluationId(Long evaluationId) {
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, evaluationId);
return baseMapper.delete(lqw) >= 0;
}
/**
* 获取学生信息(从学生信息管理模块)
*
* @param classId 班级ID
* @return 学生信息列表
*/
@Override
public List<Map<String, Object>> getStudentsByClassId(Long classId) {
// 模拟数据,实际项目中应该从学生信息管理模块获取
List<Map<String, Object>> students = new ArrayList<>();
if (classId == 1001) {
// 计算机2301班
for (int i = 1; i <= 5; i++) {
Map<String, Object> student = new HashMap<>();
student.put("id", 10000 + i);
student.put("studentNo", "2024" + String.format("%03d", i));
student.put("studentName", getStudentName(i));
student.put("gender", i % 2 == 0 ? "1" : "0");
student.put("birthDate", "2005-" + (6 + i % 7) + "-" + (10 + i % 20));
student.put("phone", "1380013800" + i);
students.add(student);
}
} else if (classId == 1002) {
// 电子2302班
for (int i = 1; i <= 5; i++) {
Map<String, Object> student = new HashMap<>();
student.put("id", 20000 + i);
student.put("studentNo", "2302" + String.format("%03d", i));
student.put("studentName", getStudentName(i + 5));
student.put("gender", i % 2 == 0 ? "1" : "0");
student.put("birthDate", "2005-" + (i % 12 + 1) + "-" + (i % 28 + 1));
student.put("phone", "1390013900" + i);
students.add(student);
}
} else if (classId == 1003) {
// 会计2303班
for (int i = 1; i <= 5; i++) {
Map<String, Object> student = new HashMap<>();
student.put("id", 30000 + i);
student.put("studentNo", "2303" + String.format("%03d", i));
student.put("studentName", getStudentName(i + 10));
student.put("gender", i % 2 == 0 ? "1" : "0");
student.put("birthDate", "2005-" + (i % 12 + 1) + "-" + (i % 28 + 1));
student.put("phone", "1370013700" + i);
students.add(student);
}
}
return students;
}
/**
* 获取学生姓名
*/
private String getStudentName(int index) {
String[] names = {"张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十",
"郑一", "王二", "陈三", "林四", "黄五", "赵六", "吴七"};
return names[(index - 1) % names.length];
}
/**
* 批量更新学生心理状态
*
* @param bos 批量更新请求
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchUpdateMentalStatus(List<XljkEvaluationDetailBo> bos) {
if (CollUtil.isEmpty(bos)) {
return false;
}
// 获取测评ID
Long evaluationId = bos.get(0).getEvaluationId();
log.info("批量更新心理状态 - 接收到的测评ID: {}, 类型: {}", evaluationId, evaluationId != null ? evaluationId.getClass().getName() : "null");
// 获取测评信息
XljkEvaluation evaluation = evaluationMapper.selectById(evaluationId);
if (evaluation == null) {
log.error("测评不存在 - 查询ID: {}", evaluationId);
throw new ServiceException("测评不存在");
}
log.info("成功找到测评记录 - ID: {}, 名称: {}", evaluation.getId(), evaluation.getEvaluationName());
if (!"0".equals(evaluation.getStatus())) {
throw new ServiceException("只能修改草稿状态的测评");
}
try {
// 分类处理
for (XljkEvaluationDetailBo bo : bos) {
// 校验数据
if ("3".equals(bo.getMentalStatus()) && bo.getScore() == null) {
throw new ServiceException("重点关注状态必须填写分数");
}
// 判断是新增还是更新
if (bo.getId() != null) {
// 更新
XljkEvaluationDetail entity = MapstructUtils.convert(bo, XljkEvaluationDetail.class);
baseMapper.updateById(entity);
} else {
// 新增
XljkEvaluationDetail entity = MapstructUtils.convert(bo, XljkEvaluationDetail.class);
baseMapper.insert(entity);
}
}
// 更新测评主表的学生总数和重点关注学生数
updateEvaluationStatistics(evaluationId);
return true;
} catch (Exception e) {
log.error("批量更新学生心理状态异常", e);
throw new ServiceException("批量更新失败:" + e.getMessage());
}
}
/**
* 更新测评主表的统计信息
*
* @param evaluationId 测评ID
*/
private void updateEvaluationStatistics(Long evaluationId) {
try {
// 查询总学生数
LambdaQueryWrapper<XljkEvaluationDetail> countQuery = Wrappers.lambdaQuery();
countQuery.eq(XljkEvaluationDetail::getEvaluationId, evaluationId);
long studentCount = baseMapper.selectCount(countQuery);
// 查询重点关注学生数
LambdaQueryWrapper<XljkEvaluationDetail> focusQuery = Wrappers.lambdaQuery();
focusQuery.eq(XljkEvaluationDetail::getEvaluationId, evaluationId)
.eq(XljkEvaluationDetail::getMentalStatus, "3");
long focusCount = baseMapper.selectCount(focusQuery);
// 更新测评主表
XljkEvaluation evaluation = new XljkEvaluation();
evaluation.setId(evaluationId);
// 使用额外字段存储统计信息
// 注意:需要确保XljkEvaluation实体类中有这些字段
// TODO: 如果XljkEvaluation实体类中没有这些字段,需要通过其他方式更新
// 例如:使用自定义SQL更新或者在XljkEvaluation实体类中添加这些字段
evaluationMapper.updateById(evaluation);
log.info("更新测评统计信息成功 - 测评ID: {}, 学生总数: {}, 重点关注学生数: {}", evaluationId, studentCount, focusCount);
} catch (Exception e) {
log.error("更新测评统计信息失败", e);
}
}
/**
* 获取重点关注学生列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 重点关注学生列表
*/
@Override
public TableDataInfo<XljkEvaluationDetailVo> queryFocusStudents(XljkEvaluationDetailBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<XljkEvaluationDetail> lqw = buildQueryWrapper(bo);
// 只查询重点关注的学生
lqw.eq(XljkEvaluationDetail::getMentalStatus, "3");
Page<XljkEvaluationDetailVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
// 补充测评信息
if (result.getRecords().size() > 0) {
// 获取所有测评ID
Set<Long> evaluationIds = new HashSet<>();
for (XljkEvaluationDetailVo vo : result.getRecords()) {
evaluationIds.add(vo.getEvaluationId());
}
// 批量查询测评信息
LambdaQueryWrapper<XljkEvaluation> evalLqw = Wrappers.lambdaQuery();
evalLqw.in(XljkEvaluation::getId, evaluationIds);
List<XljkEvaluation> evaluations = evaluationMapper.selectList(evalLqw);
Map<Long, XljkEvaluation> evaluationMap = new HashMap<>();
for (XljkEvaluation evaluation : evaluations) {
evaluationMap.put(evaluation.getId(), evaluation);
}
// 填充测评信息
for (XljkEvaluationDetailVo vo : result.getRecords()) {
XljkEvaluation evaluation = evaluationMap.get(vo.getEvaluationId());
if (evaluation != null) {
vo.setEvaluationName(evaluation.getEvaluationName());
vo.setClassName(evaluation.getClassName());
vo.setMajorName(evaluation.getMajorName());
vo.setSemester(evaluation.getSemester());
}
}
}
return TableDataInfo.build(result);
}
}
@@ -0,0 +1,953 @@
package org.dromara.xljk.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.xljk.domain.bo.XljkEvaluationBo;
import org.dromara.xljk.domain.vo.XljkEvaluationVo;
import org.dromara.xljk.domain.XljkEvaluation;
import org.dromara.xljk.domain.XljkEvaluationDetail;
import org.dromara.xljk.mapper.XljkEvaluationMapper;
import org.dromara.xljk.mapper.XljkEvaluationDetailMapper;
import org.dromara.xljk.service.IXljkEvaluationService;
import org.dromara.xljk.service.IXljkEvaluationDetailService;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.UserService;
import java.util.*;
import java.sql.*;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 心理健康测评Service业务层处理
*
* @author Cursor AI
* @date 2025-07-02
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class XljkEvaluationServiceImpl implements IXljkEvaluationService {
private final XljkEvaluationMapper baseMapper;
private final XljkEvaluationDetailMapper detailMapper;
private final IXljkEvaluationDetailService detailService;
private final UserService userService;
@Autowired
private DataSource dataSource;
/**
* 查询心理健康测评
*
* @param id 主键
* @return 心理健康测评
*/
@Override
public XljkEvaluationVo queryById(Long id) {
XljkEvaluationVo vo = baseMapper.selectVoById(id);
if (vo != null) {
// 查询学生数量和重点关注数量
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, id);
int studentCount = detailMapper.selectCount(lqw).intValue();
vo.setStudentCount(studentCount);
lqw.clear();
lqw.eq(XljkEvaluationDetail::getEvaluationId, id)
.eq(XljkEvaluationDetail::getMentalStatus, "3");
int focusCount = detailMapper.selectCount(lqw).intValue();
vo.setFocusCount(focusCount);
}
return vo;
}
/**
* 分页查询心理健康测评列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 心理健康测评分页列表
*/
@Override
public TableDataInfo<XljkEvaluationVo> queryPageList(XljkEvaluationBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<XljkEvaluation> lqw = buildQueryWrapper(bo);
Page<XljkEvaluationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
// 查询学生数量和重点关注数量
for (XljkEvaluationVo vo : result.getRecords()) {
LambdaQueryWrapper<XljkEvaluationDetail> detailLqw = Wrappers.lambdaQuery();
detailLqw.eq(XljkEvaluationDetail::getEvaluationId, vo.getId());
int studentCount = detailMapper.selectCount(detailLqw).intValue();
vo.setStudentCount(studentCount);
detailLqw.clear();
detailLqw.eq(XljkEvaluationDetail::getEvaluationId, vo.getId())
.eq(XljkEvaluationDetail::getMentalStatus, "3");
int focusCount = detailMapper.selectCount(detailLqw).intValue();
vo.setFocusCount(focusCount);
}
return TableDataInfo.build(result);
}
/**
* 查询符合条件的心理健康测评列表
*
* @param bo 查询条件
* @return 心理健康测评列表
*/
@Override
public List<XljkEvaluationVo> queryList(XljkEvaluationBo bo) {
LambdaQueryWrapper<XljkEvaluation> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<XljkEvaluation> buildQueryWrapper(XljkEvaluationBo bo) {
LambdaQueryWrapper<XljkEvaluation> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getEvaluationName()), XljkEvaluation::getEvaluationName, bo.getEvaluationName());
lqw.eq(bo.getMajorId() != null, XljkEvaluation::getMajorId, bo.getMajorId());
lqw.eq(bo.getClassId() != null, XljkEvaluation::getClassId, bo.getClassId());
lqw.eq(StringUtils.isNotBlank(bo.getSemester()), XljkEvaluation::getSemester, bo.getSemester());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XljkEvaluation::getStatus, bo.getStatus());
lqw.in(CollUtil.isNotEmpty(bo.getStatusIn()), XljkEvaluation::getStatus, bo.getStatusIn());
lqw.in(CollUtil.isNotEmpty(bo.getMajorIdIn()), XljkEvaluation::getMajorId, bo.getMajorIdIn());
lqw.in(CollUtil.isNotEmpty(bo.getClassIdIn()), XljkEvaluation::getClassId, bo.getClassIdIn());
lqw.in(CollUtil.isNotEmpty(bo.getSemesterIn()), XljkEvaluation::getSemester, bo.getSemesterIn());
lqw.in(CollUtil.isNotEmpty(bo.getCreateByIn()), XljkEvaluation::getCreateBy, bo.getCreateByIn());
// 数据权限过滤
addDataPermissionFilter(lqw);
return lqw;
}
/**
* 添加数据权限过滤
*
* @param lqw 查询条件
*/
private void addDataPermissionFilter(LambdaQueryWrapper<XljkEvaluation> lqw) {
try {
// 获取当前登录用户信息
var loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
return;
}
Long userId = loginUser.getUserId();
// 超级管理员不过滤数据
if (LoginHelper.isSuperAdmin()) {
return;
}
// 班主任只能看到自己创建的测评
lqw.eq(XljkEvaluation::getCreateBy, userId);
} catch (Exception e) {
log.error("数据权限过滤异常", e);
}
}
/**
* 填充创建者信息
*/
private void fillCreatorInfo(XljkEvaluation evaluation) {
try {
// 获取当前登录用户信息
var loginUser = LoginHelper.getLoginUser();
if (loginUser != null) {
evaluation.setCreateName(loginUser.getUsername());
// 设置所属部门
Long deptId = loginUser.getDeptId();
if (deptId != null) {
evaluation.setCreateDept(deptId);
} else {
// 如果没有部门信息,使用默认值
evaluation.setCreateDept(100L); // 使用杭州第一技师学院作为默认值
}
} else {
// 如果无法获取用户信息,提供默认值
evaluation.setCreateDept(100L); // 使用杭州第一技师学院作为默认值
}
} catch (Exception e) {
log.error("获取用户信息异常", e);
// 确保在异常情况下也设置默认值
evaluation.setCreateDept(100L); // 使用杭州第一技师学院作为默认值
}
}
/**
* 新增心理健康测评
*
* @param bo 心理健康测评
* @return 是否新增成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(XljkEvaluationBo bo) {
XljkEvaluation add = MapstructUtils.convert(bo, XljkEvaluation.class);
// 设置初始状态为草稿
if (StringUtils.isEmpty(add.getStatus())) {
add.setStatus("0");
}
// 确保department_id字段有值,如果前端传入0则使用对应院系的ID
if (add.getDepartmentId() == null || add.getDepartmentId() == 0) {
// 查找是否有对应院系的部门ID
String collegeName = add.getDepartmentName();
if (StringUtils.isNotEmpty(collegeName)) {
// 尝试从部门表中查找对应的ID
Long deptId = getDeptIdByName(collegeName);
if (deptId != null) {
add.setDepartmentId(deptId);
} else {
// 如果找不到,使用默认值
add.setDepartmentId(103L); // 默认使用智能控制学院的ID
}
} else {
// 如果院系名称为空,使用默认值
add.setDepartmentId(103L); // 默认使用智能控制学院的ID
add.setDepartmentName("智能控制学院"); // 确保department_name有值
}
}
// 确保departmentName字段有值
if (StringUtils.isEmpty(add.getDepartmentName())) {
// 如果departmentName为空但departmentId有值,尝试查询对应的名称
if (add.getDepartmentId() != null && add.getDepartmentId() > 0) {
String deptName = getDeptNameById(add.getDepartmentId());
if (StringUtils.isNotEmpty(deptName)) {
add.setDepartmentName(deptName);
} else {
add.setDepartmentName("智能控制学院"); // 默认值
}
} else {
add.setDepartmentName("智能控制学院"); // 默认值
}
}
// 设置创建者姓名
fillCreatorInfo(add);
// 验证是否重复
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 根据院系名称查找对应的部门ID
* @param collegeName 院系名称
* @return 部门ID,如果找不到则返回null
*/
private Long getDeptIdByName(String collegeName) {
try (Connection conn = dataSource.getConnection()) {
String sql = "SELECT dept_id FROM sys_dept WHERE dept_name = ? AND status = '0' LIMIT 1";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, collegeName);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getLong("dept_id");
}
}
}
} catch (Exception e) {
log.error("查询部门ID异常", e);
}
return null;
}
/**
* 根据部门ID查找对应的部门名称
* @param deptId 部门ID
* @return 部门名称,如果找不到则返回null
*/
private String getDeptNameById(Long deptId) {
try (Connection conn = dataSource.getConnection()) {
String sql = "SELECT dept_name FROM sys_dept WHERE dept_id = ? AND status = '0' LIMIT 1";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setLong(1, deptId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getString("dept_name");
}
}
}
} catch (Exception e) {
log.error("查询部门名称异常", e);
}
return null;
}
/**
* 修改心理健康测评
*
* @param bo 心理健康测评
* @return 是否修改成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(XljkEvaluationBo bo) {
XljkEvaluation update = MapstructUtils.convert(bo, XljkEvaluation.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*
* @param entity 实体类数据
*/
private void validEntityBeforeSave(XljkEvaluation entity) {
// 校验是否存在相同的测评
LambdaQueryWrapper<XljkEvaluation> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluation::getClassId, entity.getClassId())
.eq(XljkEvaluation::getSemester, entity.getSemester())
.eq(XljkEvaluation::getEvaluationName, entity.getEvaluationName());
// 排除自身
if (entity.getId() != null) {
lqw.ne(XljkEvaluation::getId, entity.getId());
}
long count = baseMapper.selectCount(lqw);
if (count > 0) {
throw new ServiceException("已存在相同班级、学期的测评");
}
}
/**
* 校验并批量删除心理健康测评信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 校验是否允许删除
for (Long id : ids) {
XljkEvaluation evaluation = baseMapper.selectById(id);
if (evaluation != null && !"0".equals(evaluation.getStatus())) {
throw new ServiceException("只能删除草稿状态的测评");
}
}
}
// 删除测评明细
for (Long id : ids) {
detailService.deleteByEvaluationId(id);
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 获取院系选项
*
* @return 院系选项数据
*/
@Override
public List<Map<String, Object>> getDepartmentOptions() {
// 重定向到新方法
return getCollegeOptions();
}
/**
* 获取院系选项
*
* @return 院系选项数据
*/
@Override
public List<Map<String, Object>> getCollegeOptions() {
// 从学生信息表中查询唯一院系列表
String sql = "SELECT DISTINCT college_name as name FROM xg_student_info WHERE college_name IS NOT NULL";
List<Map<String, Object>> result = new ArrayList<>();
try {
// 使用原生SQL查询
List<XljkEvaluation> list = baseMapper.selectList(new LambdaQueryWrapper<XljkEvaluation>().last("LIMIT 1"));
// 执行SQL查询院系数据
Connection conn = dataSource.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Map<String, Object> option = new HashMap<>();
option.put("name", rs.getString("name"));
result.add(option);
}
}
conn.close();
} catch (Exception e) {
log.error("获取院系选项异常", e);
}
return result;
}
/**
* 获取专业选项
*
* @param collegeName 院系名称,可为空
* @return 专业选项数据
*/
@Override
public List<Map<String, Object>> getMajorOptions(String collegeName) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT major_name as name FROM xg_student_info WHERE major_name IS NOT NULL");
List<Object> params = new ArrayList<>();
if (StringUtils.isNotEmpty(collegeName)) {
sql.append(" AND college_name = ?");
params.add(collegeName);
}
List<Map<String, Object>> result = new ArrayList<>();
try {
// 执行SQL查询专业数据
Connection conn = dataSource.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
// 设置参数
for (int i = 0; i < params.size(); i++) {
stmt.setObject(i + 1, params.get(i));
}
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Map<String, Object> option = new HashMap<>();
option.put("name", rs.getString("name"));
result.add(option);
}
}
}
conn.close();
} catch (Exception e) {
log.error("获取专业选项异常", e);
}
return result;
}
/**
* 获取班级选项
*
* @param collegeName 院系名称,可为空
* @param majorName 专业名称,可为空
* @return 班级选项数据
*/
@Override
public List<Map<String, Object>> getClassOptions(String collegeName, String majorName) {
StringBuilder sql = new StringBuilder("SELECT DISTINCT class_name as name FROM xg_student_info WHERE class_name IS NOT NULL");
List<Object> params = new ArrayList<>();
if (StringUtils.isNotEmpty(collegeName)) {
sql.append(" AND college_name = ?");
params.add(collegeName);
}
if (StringUtils.isNotEmpty(majorName)) {
sql.append(" AND major_name = ?");
params.add(majorName);
}
List<Map<String, Object>> result = new ArrayList<>();
try {
// 执行SQL查询班级数据
Connection conn = dataSource.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
// 设置参数
for (int i = 0; i < params.size(); i++) {
stmt.setObject(i + 1, params.get(i));
}
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Map<String, Object> option = new HashMap<>();
option.put("name", rs.getString("name"));
result.add(option);
}
}
}
conn.close();
} catch (Exception e) {
log.error("获取班级选项异常", e);
}
return result;
}
/**
* 获取学生列表
*
* @param collegeName 院系名称,可为空
* @param majorName 专业名称,可为空
* @param className 班级名称,可为空
* @return 学生列表数据
*/
@Override
public List<Map<String, Object>> getStudentsByClass(String collegeName, String majorName, String className) {
StringBuilder sql = new StringBuilder("SELECT id as studentId, student_no as studentNo, name as studentName, gender, birth_date as birthDate, phone " +
"FROM xg_student_info WHERE 1=1");
List<Object> params = new ArrayList<>();
// 主要匹配班级名称,因为不同的表可能使用不同的院系名称
if (StringUtils.isNotEmpty(className)) {
sql.append(" AND class_name = ?");
params.add(className);
} else {
// 如果没有班级名称,才使用院系和专业筛选
if (StringUtils.isNotEmpty(collegeName)) {
sql.append(" AND (college_name = ? OR college_name LIKE ?)");
params.add(collegeName);
params.add("%" + collegeName + "%");
}
if (StringUtils.isNotEmpty(majorName)) {
sql.append(" AND major_name = ?");
params.add(majorName);
}
}
log.info("获取学生列表SQL: {}, 参数: {}", sql.toString(), params);
List<Map<String, Object>> result = new ArrayList<>();
try {
// 执行SQL查询学生数据
Connection conn = dataSource.getConnection();
try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
// 设置参数
for (int i = 0; i < params.size(); i++) {
stmt.setObject(i + 1, params.get(i));
}
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Map<String, Object> student = new HashMap<>();
student.put("studentId", rs.getLong("studentId"));
student.put("studentNo", rs.getString("studentNo"));
student.put("studentName", rs.getString("studentName"));
student.put("gender", rs.getString("gender"));
student.put("birthDate", rs.getDate("birthDate"));
student.put("phone", rs.getString("phone"));
result.add(student);
}
}
}
conn.close();
} catch (Exception e) {
log.error("获取学生列表异常", e);
}
log.info("获取学生列表结果数量: {}", result.size());
return result;
}
/**
* 获取专业和班级选项
*
* @param departmentId 院系ID,可为空
* @return 专业和班级选项数据
*/
@Override
public Map<String, List<Map<String, Object>>> getMajorAndClassOptions(Long departmentId) {
// 模拟数据,实际项目中应该从学生信息管理模块获取
Map<String, List<Map<String, Object>>> result = new HashMap<>();
// 专业选项
List<Map<String, Object>> majors = new ArrayList<>();
// 按院系过滤专业
if (departmentId != null) {
if (departmentId.equals(103L)) { // 智能控制学院
Map<String, Object> major1 = new HashMap<>();
major1.put("id", 101);
major1.put("name", "计算机应用技术");
major1.put("departmentId", 103);
majors.add(major1);
Map<String, Object> major2 = new HashMap<>();
major2.put("id", 104);
major2.put("name", "自动化技术");
major2.put("departmentId", 103);
majors.add(major2);
} else if (departmentId.equals(104L)) { // 智能制造学院
Map<String, Object> major2 = new HashMap<>();
major2.put("id", 102);
major2.put("name", "电子商务");
major2.put("departmentId", 104);
majors.add(major2);
Map<String, Object> major3 = new HashMap<>();
major3.put("id", 105);
major3.put("name", "机械设计");
major3.put("departmentId", 104);
majors.add(major3);
} else if (departmentId.equals(105L)) { // 医药健康学院
Map<String, Object> major3 = new HashMap<>();
major3.put("id", 103);
major3.put("name", "会计");
major3.put("departmentId", 105);
majors.add(major3);
Map<String, Object> major4 = new HashMap<>();
major4.put("id", 106);
major4.put("name", "护理");
major4.put("departmentId", 105);
majors.add(major4);
}
} else {
// 如果没有选择院系,返回所有专业
Map<String, Object> major1 = new HashMap<>();
major1.put("id", 101);
major1.put("name", "计算机应用技术");
major1.put("departmentId", 103);
majors.add(major1);
Map<String, Object> major2 = new HashMap<>();
major2.put("id", 102);
major2.put("name", "电子商务");
major2.put("departmentId", 104);
majors.add(major2);
Map<String, Object> major3 = new HashMap<>();
major3.put("id", 103);
major3.put("name", "会计");
major3.put("departmentId", 105);
majors.add(major3);
}
// 班级选项
List<Map<String, Object>> classes = new ArrayList<>();
for (Map<String, Object> major : majors) {
Long majorId = Long.valueOf(major.get("id").toString());
if (majorId.equals(101L)) {
Map<String, Object> class1 = new HashMap<>();
class1.put("id", 1001);
class1.put("name", "计算机2301班");
class1.put("majorId", 101);
classes.add(class1);
Map<String, Object> class2 = new HashMap<>();
class2.put("id", 1004);
class2.put("name", "计算机2302班");
class2.put("majorId", 101);
classes.add(class2);
} else if (majorId.equals(102L)) {
Map<String, Object> class2 = new HashMap<>();
class2.put("id", 1002);
class2.put("name", "电子2301班");
class2.put("majorId", 102);
classes.add(class2);
Map<String, Object> class3 = new HashMap<>();
class3.put("id", 1005);
class3.put("name", "电子2302班");
class3.put("majorId", 102);
classes.add(class3);
} else if (majorId.equals(103L)) {
Map<String, Object> class3 = new HashMap<>();
class3.put("id", 1003);
class3.put("name", "会计2301班");
class3.put("majorId", 103);
classes.add(class3);
Map<String, Object> class4 = new HashMap<>();
class4.put("id", 1006);
class4.put("name", "会计2302班");
class4.put("majorId", 103);
classes.add(class4);
}
}
result.put("majors", majors);
result.put("classes", classes);
return result;
}
/**
* 获取学期选项
*
* @return 学期选项列表
*/
@Override
public List<String> getSemesterOptions() {
List<String> options = new ArrayList<>();
options.add("2023上学期");
options.add("2023下学期");
options.add("2024上学期");
options.add("2024下学期");
options.add("2025上学期");
options.add("2025下学期");
return options;
}
/**
* 发布测评
*
* @param id 测评ID
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean publishEvaluation(Long id) {
XljkEvaluation evaluation = baseMapper.selectById(id);
if (evaluation == null) {
throw new ServiceException("测评不存在");
}
if (!"0".equals(evaluation.getStatus())) {
throw new ServiceException("只能发布草稿状态的测评");
}
// 检查是否有学生数据
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, id);
long count = detailMapper.selectCount(lqw);
if (count == 0) {
throw new ServiceException("测评没有学生数据,无法发布");
}
// 更新状态为已发布
evaluation.setStatus("1");
return baseMapper.updateById(evaluation) > 0;
}
/**
* 完成测评
*
* @param id 测评ID
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean completeEvaluation(Long id) {
XljkEvaluation evaluation = baseMapper.selectById(id);
if (evaluation == null) {
throw new ServiceException("测评不存在");
}
if (!"1".equals(evaluation.getStatus())) {
throw new ServiceException("只能完成已发布状态的测评");
}
// 检查是否所有学生都已评估
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, id)
.isNull(XljkEvaluationDetail::getMentalStatus);
long count = detailMapper.selectCount(lqw);
if (count > 0) {
throw new ServiceException("还有" + count + "名学生未完成评估");
}
// 更新状态为已完成
evaluation.setStatus("2");
return baseMapper.updateById(evaluation) > 0;
}
/**
* 获取测评统计数据
*
* @param id 测评ID
* @return 统计数据
*/
@Override
public Map<String, Object> getEvaluationStatistics(Long id) {
Map<String, Object> result = new HashMap<>();
// 获取测评信息
XljkEvaluationVo evaluation = queryById(id);
if (evaluation == null) {
throw new ServiceException("测评不存在");
}
result.put("evaluationName", evaluation.getEvaluationName());
result.put("className", evaluation.getClassName());
result.put("semester", evaluation.getSemester());
result.put("createName", evaluation.getCreateName());
result.put("createTime", evaluation.getCreateTime());
// 统计各状态人数
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, id);
List<XljkEvaluationDetail> details = detailMapper.selectList(lqw);
int totalCount = details.size();
int healthyCount = 0;
int goodCount = 0;
int focusCount = 0;
for (XljkEvaluationDetail detail : details) {
if ("1".equals(detail.getMentalStatus())) {
healthyCount++;
} else if ("2".equals(detail.getMentalStatus())) {
goodCount++;
} else if ("3".equals(detail.getMentalStatus())) {
focusCount++;
}
}
result.put("totalCount", totalCount);
result.put("healthyCount", healthyCount);
result.put("goodCount", goodCount);
result.put("focusCount", focusCount);
if (totalCount > 0) {
result.put("healthyPercent", Math.round(healthyCount * 100.0 / totalCount * 10) / 10.0);
result.put("goodPercent", Math.round(goodCount * 100.0 / totalCount * 10) / 10.0);
result.put("focusPercent", Math.round(focusCount * 100.0 / totalCount * 10) / 10.0);
} else {
result.put("healthyPercent", 0);
result.put("goodPercent", 0);
result.put("focusPercent", 0);
}
// 性别分布
int maleCount = 0;
int femaleCount = 0;
int maleFocusCount = 0;
int femaleFocusCount = 0;
for (XljkEvaluationDetail detail : details) {
if ("0".equals(detail.getGender())) {
maleCount++;
if ("3".equals(detail.getMentalStatus())) {
maleFocusCount++;
}
} else if ("1".equals(detail.getGender())) {
femaleCount++;
if ("3".equals(detail.getMentalStatus())) {
femaleFocusCount++;
}
}
}
result.put("maleCount", maleCount);
result.put("femaleCount", femaleCount);
result.put("maleFocusCount", maleFocusCount);
result.put("femaleFocusCount", femaleFocusCount);
return result;
}
/**
* 获取重点关注学生统计
*
* @param bo 查询条件
* @return 统计数据
*/
@Override
public Map<String, Object> getFocusStudentsStatistics(XljkEvaluationBo bo) {
Map<String, Object> result = new HashMap<>();
// 查询所有符合条件的测评
List<XljkEvaluationVo> evaluations = queryList(bo);
int totalStudentCount = 0;
int totalFocusCount = 0;
// 按班级统计
Map<String, Integer> classFocusMap = new HashMap<>();
Map<String, Integer> classStudentMap = new HashMap<>();
// 按学期统计
Map<String, Integer> semesterFocusMap = new HashMap<>();
Map<String, Integer> semesterStudentMap = new HashMap<>();
for (XljkEvaluationVo evaluation : evaluations) {
// 查询该测评的学生数和重点关注数
LambdaQueryWrapper<XljkEvaluationDetail> lqw = Wrappers.lambdaQuery();
lqw.eq(XljkEvaluationDetail::getEvaluationId, evaluation.getId());
int studentCount = detailMapper.selectCount(lqw).intValue();
lqw.clear();
lqw.eq(XljkEvaluationDetail::getEvaluationId, evaluation.getId())
.eq(XljkEvaluationDetail::getMentalStatus, "3");
int focusCount = detailMapper.selectCount(lqw).intValue();
totalStudentCount += studentCount;
totalFocusCount += focusCount;
// 按班级统计
String className = evaluation.getClassName();
classStudentMap.put(className, classStudentMap.getOrDefault(className, 0) + studentCount);
classFocusMap.put(className, classFocusMap.getOrDefault(className, 0) + focusCount);
// 按学期统计
String semester = evaluation.getSemester();
semesterStudentMap.put(semester, semesterStudentMap.getOrDefault(semester, 0) + studentCount);
semesterFocusMap.put(semester, semesterFocusMap.getOrDefault(semester, 0) + focusCount);
}
// 计算总体关注率
double totalFocusRate = totalStudentCount > 0 ?
Math.round(totalFocusCount * 100.0 / totalStudentCount * 10) / 10.0 : 0;
result.put("totalStudentCount", totalStudentCount);
result.put("totalFocusCount", totalFocusCount);
result.put("totalFocusRate", totalFocusRate);
// 班级统计数据
List<Map<String, Object>> classStats = new ArrayList<>();
for (String className : classStudentMap.keySet()) {
Map<String, Object> classStat = new HashMap<>();
int studentCount = classStudentMap.get(className);
int focusCount = classFocusMap.getOrDefault(className, 0);
double focusRate = studentCount > 0 ?
Math.round(focusCount * 100.0 / studentCount * 10) / 10.0 : 0;
classStat.put("className", className);
classStat.put("studentCount", studentCount);
classStat.put("focusCount", focusCount);
classStat.put("focusRate", focusRate);
classStats.add(classStat);
}
result.put("classStats", classStats);
// 学期统计数据
List<Map<String, Object>> semesterStats = new ArrayList<>();
for (String semester : semesterStudentMap.keySet()) {
Map<String, Object> semesterStat = new HashMap<>();
int studentCount = semesterStudentMap.get(semester);
int focusCount = semesterFocusMap.getOrDefault(semester, 0);
double focusRate = studentCount > 0 ?
Math.round(focusCount * 100.0 / studentCount * 10) / 10.0 : 0;
semesterStat.put("semester", semester);
semesterStat.put("studentCount", studentCount);
semesterStat.put("focusCount", focusCount);
semesterStat.put("focusRate", focusRate);
semesterStats.add(semesterStat);
}
result.put("semesterStats", semesterStats);
return result;
}
}
@@ -0,0 +1,28 @@
<?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.xljk.mapper.XljkEvaluationDetailMapper">
<resultMap type="org.dromara.xljk.domain.XljkEvaluationDetail" id="XljkEvaluationDetailResult">
<result property="id" column="id"/>
<result property="evaluationId" column="evaluation_id"/>
<result property="studentId" column="student_id"/>
<result property="studentNo" column="student_no"/>
<result property="studentName" column="student_name"/>
<result property="gender" column="gender"/>
<result property="birthDate" column="birth_date"/>
<result property="phone" column="phone"/>
<result property="mentalStatus" column="mental_status"/>
<result property="score" column="score"/>
<result property="remark" column="remark"/>
<result property="createDept" column="create_dept"/>
<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"/>
<result property="tenantId" column="tenant_id"/>
</resultMap>
</mapper>
@@ -0,0 +1,29 @@
<?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.xljk.mapper.XljkEvaluationMapper">
<resultMap type="org.dromara.xljk.domain.XljkEvaluation" id="XljkEvaluationResult">
<result property="id" column="id"/>
<result property="departmentId" column="department_id"/>
<result property="departmentName" column="department_name"/>
<result property="evaluationName" column="evaluation_name"/>
<result property="majorId" column="major_id"/>
<result property="majorName" column="major_name"/>
<result property="classId" column="class_id"/>
<result property="className" column="class_name"/>
<result property="semester" column="semester"/>
<result property="status" column="status"/>
<result property="createName" column="create_name"/>
<result property="remark" column="remark"/>
<result property="createDept" column="create_dept"/>
<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"/>
<result property="tenantId" column="tenant_id"/>
</resultMap>
</mapper>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,946 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>心理咨询预约管理 - 杭州第一技师学院</title>
<!-- Element Plus 图标字体 -->
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
<script src="https://unpkg.com/@element-plus/icons-vue/dist/index.iife.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f5f5;
color: #606266;
font-size: 14px;
line-height: 1.5;
}
.p-2 {
padding: 8px;
}
.page-container {
padding: 8px;
}
.mb-10 {
margin-bottom: 10px;
}
.mb-10px {
margin-bottom: 10px;
}
/* 卡片样式 */
.el-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 10px;
}
.el-card.hover {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.el-card-body {
padding: 20px;
}
.el-card-header {
padding: 18px 20px;
border-bottom: 1px solid #ebeef5;
background-color: #fafafa;
}
/* 表单样式 */
.el-form {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.el-form-item {
display: flex;
flex-direction: column;
margin-bottom: 18px;
min-width: 200px;
}
.el-form-item label {
font-size: 14px;
color: #606266;
line-height: 1.5;
padding: 0 12px 0 0;
box-sizing: border-box;
margin-bottom: 5px;
font-weight: 400;
}
.el-input {
position: relative;
font-size: 14px;
display: inline-block;
width: 100%;
}
.el-input input {
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: 14px;
height: 32px;
line-height: 32px;
outline: none;
padding: 0 11px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
}
.el-input input:focus {
border-color: #409eff;
outline: none;
}
.el-select {
display: inline-block;
position: relative;
width: 100%;
}
.el-select select {
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: 14px;
height: 32px;
line-height: 32px;
outline: none;
padding: 0 11px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
}
.el-select select:focus {
border-color: #409eff;
outline: none;
}
/* 按钮样式 */
.el-button {
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
transition: 0.1s;
font-weight: 400;
padding: 8px 15px;
font-size: 14px;
border-radius: 6px;
text-decoration: none;
margin-right: 8px;
min-height: 32px;
}
.el-button:hover {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
.el-button--primary {
color: #fff;
background-color: #409eff;
border-color: #409eff;
}
.el-button--primary:hover {
background: #66b1ff;
border-color: #66b1ff;
color: #fff;
}
.el-button--primary.is-plain {
color: #409eff;
background: #ecf5ff;
border-color: #b3d8ff;
}
.el-button--primary.is-plain:hover {
background: #409eff;
border-color: #409eff;
color: #fff;
}
/* 搜索和重置按钮样式 */
.el-button {
font-size: 14px;
padding: 8px 15px;
border-radius: 4px;
}
.el-button:disabled {
color: #c0c4cc;
cursor: not-allowed;
background-image: none;
background-color: #fff;
border-color: #ebeef5;
}
.el-button i {
margin-right: 5px;
}
/* 表格样式 */
.el-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
background-color: #fff;
font-size: 14px;
color: #606266;
margin-bottom: 0;
}
.el-table th,
.el-table td {
padding: 12px 0;
min-width: 0;
box-sizing: border-box;
text-overflow: ellipsis;
vertical-align: middle;
position: relative;
text-align: left;
border-bottom: 1px solid #ebeef5;
}
.el-table th {
background-color: #fafafa;
color: #909399;
font-weight: 500;
border-bottom: 1px solid #ebeef5;
}
.el-table tr:hover {
background-color: #f5f7fa;
}
.el-table--border {
border: 1px solid #ebeef5;
}
.el-table--border th,
.el-table--border td {
border-right: 1px solid #ebeef5;
padding-left: 10px;
padding-right: 10px;
}
.el-table-column--selection {
width: 55px;
text-align: center;
}
/* 状态标签 */
.el-tag {
background-color: #ecf5ff;
border: 1px solid #d9ecff;
color: #409eff;
display: inline-block;
height: 32px;
padding: 0 10px;
line-height: 30px;
font-size: 12px;
border-radius: 4px;
box-sizing: border-box;
white-space: nowrap;
}
.el-tag--success {
background-color: #f0f9ff;
border-color: #e1f3d8;
color: #67c23a;
}
.el-tag--warning {
background-color: #fdf6ec;
border-color: #faecd8;
color: #e6a23c;
}
.el-tag--danger {
background-color: #fef0f0;
border-color: #fde2e2;
color: #f56c6c;
}
.el-tag--info {
background-color: #f4f4f5;
border-color: #e9e9eb;
color: #909399;
}
/* 分页样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0;
background-color: #fff;
margin-top: 0px;
}
.el-pagination {
white-space: nowrap;
padding: 2px 5px;
color: #303133;
font-weight: 700;
}
.el-pagination button {
display: inline-block;
font-size: 13px;
min-width: 35.5px;
height: 28px;
line-height: 28px;
vertical-align: top;
box-sizing: border-box;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 4px;
text-align: center;
margin: 0 5px;
cursor: pointer;
color: #606266;
transition: all 0.3s;
}
.el-pagination button:hover {
color: #409eff;
}
/* 操作按钮 */
.el-button--text {
border-color: transparent;
color: #606266;
background: transparent;
padding: 4px 8px;
margin-right: 4px;
border-radius: 4px;
min-height: 28px;
width: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.el-button--text:hover {
background-color: #f5f7fa;
border-color: transparent;
}
.el-button--text.el-button--primary {
color: #409eff;
}
.el-button--text.el-button--primary:hover {
color: #409eff;
background-color: #ecf5ff;
}
.el-button--text.el-button--danger {
color: #f56c6c;
}
.el-button--text.el-button--danger:hover {
color: #f56c6c;
background-color: #fef0f0;
}
.el-button--text.el-button--info {
color: #909399;
}
.el-button--text.el-button--info:hover {
color: #909399;
background-color: #f4f4f5;
}
/* 工具提示 */
.el-tooltip {
display: inline-block;
}
/* 行和列布局 */
.el-row {
display: flex;
flex-wrap: wrap;
}
.el-col {
flex: 0 0 auto;
}
.el-col-1-5 {
width: 12.5%;
}
.mb8 {
margin-bottom: 8px;
}
/* Element Plus 图标样式 */
.el-icon {
width: 1em;
height: 1em;
display: inline-block;
vertical-align: middle;
fill: currentColor;
margin-right: 6px;
}
/* 操作按钮图标样式 */
.el-button--text .el-icon {
margin-right: 0;
font-size: 16px;
}
/* 图标样式 */
.icon {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 5px;
vertical-align: middle;
}
/* 模态框样式 */
.el-dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: none;
z-index: 1000;
align-items: center;
justify-content: center;
}
.el-dialog__wrapper {
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
width: 90%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
}
.el-dialog__header {
padding: 20px 20px 10px;
border-bottom: 1px solid #ebeef5;
}
.el-dialog__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.el-dialog__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.el-dialog__footer {
padding: 10px 20px 20px;
text-align: right;
border-top: 1px solid #ebeef5;
}
.el-dialog__close {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
font-size: 16px;
color: #909399;
}
.el-dialog__close:hover {
color: #409eff;
}
/* 表单行样式 */
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.form-row .el-form-item {
flex: 1;
}
/* 表格单元格样式 */
.el-table td {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
/* 右侧工具栏 */
.right-toolbar {
float: right;
}
.toolbar-item {
display: inline-block;
margin-left: 10px;
cursor: pointer;
color: #606266;
font-size: 16px;
}
.toolbar-item:hover {
color: #409eff;
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
}
.table-card .el-card-body {
padding: 0px 20px 20px 20px !important;
}
/* 移除按钮栏底部边框 */
.el-card-header {
border-bottom: none !important;
}
/* 表格横向滚动容器 */
.table-container {
overflow-x: auto;
overflow-y: hidden;
width: 100%;
}
.table-container::-webkit-scrollbar {
height: 8px;
}
.table-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.table-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.table-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>
</head>
<body>
<div class="p-2 page-container">
<!-- 搜索动画过渡 -->
<transition name="search-animate">
<!-- 搜索区域 -->
<div class="mb-10px">
<div class="el-card hover" style="box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);">
<div class="el-card-body">
<form class="el-form" label-width="100px">
<div class="el-form-item">
<label>学生姓名</label>
<div class="el-input">
<input type="text" placeholder="请输入学生姓名" />
</div>
</div>
<div class="el-form-item">
<label>学号</label>
<div class="el-input">
<input type="text" placeholder="请输入学号" />
</div>
</div>
<div class="el-form-item">
<label>专业</label>
<div class="el-input">
<input type="text" placeholder="请输入专业" />
</div>
</div>
<div class="el-form-item">
<label>班级</label>
<div class="el-input">
<input type="text" placeholder="请输入班级" />
</div>
</div>
<div class="el-form-item">
<label>咨询师</label>
<div class="el-select">
<select>
<option value="">请选择咨询师</option>
<option value="1">张心理老师</option>
<option value="2">李心理老师</option>
<option value="3">王心理老师</option>
</select>
</div>
</div>
<div class="el-form-item">
<label>预约状态</label>
<div class="el-select">
<select>
<option value="">请选择预约状态</option>
<option value="pending">待处理</option>
<option value="confirmed">已确认</option>
<option value="completed">已完成</option>
<option value="cancelled">已取消</option>
<option value="no_show">未到访</option>
</select>
</div>
</div>
<div class="el-form-item">
<label>预约日期</label>
<div class="el-input">
<input type="date" />
</div>
</div>
<div class="el-form-item">
<label></label>
<div>
<button type="button" class="el-button el-button--primary">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M795.904 750.72l124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704z"></path></svg>
搜索
</button>
<button type="button" class="el-button">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64H800a32 32 0 0 1 32 32v145.28a32 32 0 1 1-64 0v-46.848zM276.288 295.616A384 384 0 0 1 896 512h-64a320 320 0 0 0-555.712-216.448H369.28a32 32 0 1 1 0 64H224a32 32 0 0 1-32-32V182.272a32 32 0 1 1 64 0v113.344z"></path></svg>
重置
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</transition>
<!-- 表格区域 -->
<div class="el-card">
<div class="el-card-header">
<div class="el-row mb8">
<div class="el-col el-col-1-5">
<button class="el-button el-button--primary is-plain">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64h352z"></path></svg>
新增
</button>
</div>
<div class="el-col el-col-1-5">
<button class="el-button" disabled>
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640V512z"></path><path d="m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z"></path></svg>
修改
</button>
</div>
<div class="el-col el-col-1-5">
<button class="el-button">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M160 256H96a32 32 0 0 1 0-64h256V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64h-64v672a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32V256zm448-64v-64H416v64h192zM224 896h576V256H224v640zm192-128a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32zm192 0a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32z"></path></svg>
删除
</button>
</div>
<div class="el-col el-col-1-5">
<button class="el-button">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M336 176h352a48 48 0 0 1 48 48v224a48 48 0 0 1-48 48H336a48 48 0 0 1-48-48V224a48 48 0 0 1 48-48zm368 96a16 16 0 0 0-16-16H336a16 16 0 0 0-16 16v224a16 16 0 0 0 16 16h352a16 16 0 0 0 16-16V272z"></path><path d="M112 80h192v48a112 112 0 0 0 112 112h192a112 112 0 0 0 112-112V80h192a32 32 0 1 1 0 64H112a32 32 0 0 1 0-64zm0 144h192v176a144 144 0 0 0 144 144h128a144 144 0 0 0 144-144V224h192a32 32 0 1 1 0 64H112a32 32 0 0 1 0-64zm688 0v176a144 144 0 0 1-144 144H368a144 144 0 0 1-144-144V224h576z"></path></svg>
导出
</button>
</div>
<div class="right-toolbar">
<span class="toolbar-item" onclick="toggleSearch()">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M795.904 750.72l124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704z"></path></svg>
</span>
<span class="toolbar-item" onclick="refreshTable()">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64H800a32 32 0 0 1 32 32v145.28a32 32 0 1 1-64 0v-46.848zM276.288 295.616A384 384 0 0 1 896 512h-64a320 320 0 0 0-555.712-216.448H369.28a32 32 0 1 1 0 64H224a32 32 0 0 1-32-32V182.272a32 32 0 1 1 64 0v113.344z"></path></svg>
</span>
</div>
</div>
</div>
<div class="table-container">
<table class="el-table el-table--border" style="margin-bottom: 0; min-width: 1400px;">
<thead>
<tr>
<th class="el-table-column--selection">
<input type="checkbox" />
</th>
<th style="min-width: 80px;">学生姓名</th>
<th style="min-width: 100px;">学号</th>
<th style="min-width: 120px;">手机号</th>
<th style="min-width: 140px;">专业</th>
<th style="min-width: 80px;">班级</th>
<th style="min-width: 100px;">咨询师</th>
<th style="min-width: 100px;">预约日期</th>
<th style="min-width: 140px;">预约时段</th>
<th style="min-width: 80px;">预约状态</th>
<th style="min-width: 80px;">到访情况</th>
<th style="min-width: 140px;">申请时间</th>
<th style="min-width: 120px;">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td class="el-table-column--selection">
<input type="checkbox" />
</td>
<td>张三</td>
<td>2024001</td>
<td>13800138001</td>
<td>计算机应用技术</td>
<td>2024-1班</td>
<td>张心理老师</td>
<td>2025-01-10</td>
<td>上午 09:00-11:00</td>
<td><span class="el-tag el-tag--warning">待处理</span></td>
<td>-</td>
<td>2025-01-08 14:30</td>
<td>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--info" title="查看">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448zm0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"></path></svg>
</button>
</div>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--primary" title="处理">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640V512z"></path><path d="m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z"></path></svg>
</button>
</div>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--danger" title="取消">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M160 256H96a32 32 0 0 1 0-64h256V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64h-64v672a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32V256zm448-64v-64H416v64h192zM224 896h576V256H224v640zm192-128a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32zm192 0a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32z"></path></svg>
</button>
</div>
</td>
</tr>
<tr>
<td class="el-table-column--selection">
<input type="checkbox" />
</td>
<td>李四</td>
<td>2024002</td>
<td>13800138002</td>
<td>电子商务</td>
<td>2024-2班</td>
<td>李心理老师</td>
<td>2025-01-11</td>
<td>下午 14:00-17:00</td>
<td><span class="el-tag el-tag--success">已确认</span></td>
<td>-</td>
<td>2025-01-07 16:20</td>
<td>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--info" title="查看">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448zm0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"></path></svg>
</button>
</div>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--primary" title="记录">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640V512z"></path><path d="m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z"></path></svg>
</button>
</div>
</td>
</tr>
<tr>
<td class="el-table-column--selection">
<input type="checkbox" />
</td>
<td>王五</td>
<td>2024003</td>
<td>13800138003</td>
<td>机械制造与自动化</td>
<td>2024-3班</td>
<td>王心理老师</td>
<td>2025-01-08</td>
<td>上午 09:00-11:00</td>
<td><span class="el-tag el-tag--success">已完成</span></td>
<td><span class="el-tag el-tag--success">已到访</span></td>
<td>2025-01-06 10:15</td>
<td>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--info" title="查看">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448zm0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"></path></svg>
</button>
</div>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--primary" title="编辑记录">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640V512z"></path><path d="m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z"></path></svg>
</button>
</div>
</td>
</tr>
<tr>
<td class="el-table-column--selection">
<input type="checkbox" />
</td>
<td>赵六</td>
<td>2024004</td>
<td>13800138004</td>
<td>汽车检测与维修</td>
<td>2024-1班</td>
<td>张心理老师</td>
<td>2025-01-07</td>
<td>下午 14:00-17:00</td>
<td><span class="el-tag el-tag--info">已完成</span></td>
<td><span class="el-tag el-tag--danger">未到访</span></td>
<td>2025-01-05 09:30</td>
<td>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--info" title="查看">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448zm0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"></path></svg>
</button>
</div>
<div class="el-tooltip">
<button class="el-button el-button--text el-button--primary" title="重新预约">
<svg class="el-icon" viewBox="0 0 1024 1024"><path d="M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64H800a32 32 0 0 1 32 32v145.28a32 32 0 1 1-64 0v-46.848zM276.288 295.616A384 384 0 0 1 896 512h-64a320 320 0 0 0-555.712-216.448H369.28a32 32 0 1 1 0 64H224a32 32 0 0 1-32-32V182.272a32 32 0 1 1 64 0v113.344z"></path></svg>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination-container">
<div class="el-pagination">
<button>上一页</button>
<span style="margin: 0 10px; color: #606266;">第 1 页,共 5 页</span>
<button>下一页</button>
</div>
</div>
</div>
</div>
<!-- 处理预约模态框 -->
<div id="processModal" class="el-dialog">
<div class="el-dialog__wrapper">
<div class="el-dialog__header">
<h3 class="el-dialog__title">处理预约申请</h3>
<span class="el-dialog__close" onclick="closeProcessModal()">&times;</span>
</div>
<div class="el-dialog__body">
<div class="form-row">
<div class="el-form-item">
<label>学生姓名</label>
<div class="el-input">
<input type="text" value="张三" readonly />
</div>
</div>
<div class="el-form-item">
<label>预约时间</label>
<div class="el-input">
<input type="text" value="2025-01-10 09:00-11:00" readonly />
</div>
</div>
</div>
<div class="form-row">
<div class="el-form-item">
<label>处理结果</label>
<div class="el-select">
<select>
<option value="confirmed">确认预约</option>
<option value="rejected">拒绝预约</option>
</select>
</div>
</div>
<div class="el-form-item">
<label>咨询师</label>
<div class="el-select">
<select>
<option value="1">张心理老师</option>
<option value="2">李心理老师</option>
<option value="3">王心理老师</option>
</select>
</div>
</div>
</div>
<div class="el-form-item">
<label>备注</label>
<div class="el-input">
<textarea style="width: 100%; height: 80px; padding: 10px; border: 1px solid #dcdfe6; border-radius: 4px; resize: vertical;" placeholder="请输入处理备注"></textarea>
</div>
</div>
</div>
<div class="el-dialog__footer">
<button class="el-button" onclick="closeProcessModal()">取消</button>
<button class="el-button el-button--primary" onclick="submitProcess()">确定</button>
</div>
</div>
</div>
<script>
// 切换搜索区域显示
function toggleSearch() {
const searchArea = document.querySelector('.mb-10');
searchArea.style.display = searchArea.style.display === 'none' ? 'block' : 'none';
}
// 刷新表格
function refreshTable() {
alert('刷新表格数据');
}
// 打开处理模态框
function openProcessModal() {
document.getElementById('processModal').style.display = 'flex';
}
// 关闭处理模态框
function closeProcessModal() {
document.getElementById('processModal').style.display = 'none';
}
// 提交处理结果
function submitProcess() {
alert('处理结果已提交');
closeProcessModal();
}
// 点击模态框外部关闭
window.onclick = function(event) {
const modal = document.getElementById('processModal');
if (event.target === modal) {
closeProcessModal();
}
}
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
@@ -0,0 +1,227 @@
# 心理健康测评模块数据库表字段调整记录
**日期**:2025-07-02
**操作人**:lixuming
## 变更内容
### 1. 问题描述
在访问心理健康测评模块时,系统报错:
```
Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'create_dept' in 'field list'
```
经分析,问题是数据库表字段与实体类定义不一致。实体类通过继承 `BaseEntity` 包含 `createDept` 字段,但数据库表中不存在对应的 `create_dept` 列。
### 2. 解决方案
在数据库表中添加缺失的 `create_dept` 字段,并设置初始值。
### 3. 执行的SQL语句
```sql
-- 向xljk_evaluation表添加create_dept字段
ALTER TABLE xljk_evaluation ADD COLUMN create_dept bigint NOT NULL COMMENT '创建部门' AFTER tenant_id;
-- 向xljk_evaluation_detail表添加create_dept字段
ALTER TABLE xljk_evaluation_detail ADD COLUMN create_dept bigint NOT NULL COMMENT '创建部门' AFTER tenant_id;
-- 设置初始值:使用create_by字段的值作为create_dept的值
UPDATE xljk_evaluation SET create_dept = create_by;
UPDATE xljk_evaluation_detail SET create_dept = create_by;
```
### 4. 变更结果
两个表都成功添加了 `create_dept` 字段,并设置了初始值。
### 5. 影响范围
此变更使数据库表结构与实体类定义一致,解决了因字段缺失导致的SQL查询错误,使心理健康测评模块可以正常访问和使用。
## 相关文档
- 实体类: `org.dromara.xljk.domain.XljkEvaluation` 继承自 `TenantEntity`,后者继承自 `BaseEntity`
- `BaseEntity` 类包含 `createDept` 字段,对应数据库中的 `create_dept` 列
---
# 心理健康测评模块添加院系字段
**日期**:2025-07-02
**操作人**:lixuming
## 变更内容
### 1. 需求描述
在心理健康测评新增界面中,需要在专业字段上方增加一个院系字段,实现院系-专业的两级联动。
### 2. 解决方案
在数据库表中添加院系ID和院系名称字段,并为已有数据设置默认值。
### 3. 执行的SQL语句
```sql
-- 在心理健康测评表中添加院系ID和院系名称字段
ALTER TABLE xljk_evaluation
ADD COLUMN department_id bigint NOT NULL COMMENT '院系ID' AFTER id,
ADD COLUMN department_name varchar(100) NOT NULL COMMENT '院系名称' AFTER department_id;
-- 更新已有数据,设置默认值
UPDATE xljk_evaluation SET department_id = 103, department_name = '智能控制学院' WHERE 1=1;
-- 为院系ID字段添加索引以提高查询效率
ALTER TABLE xljk_evaluation ADD INDEX idx_department_id (department_id);
```
### 4. 变更结果
成功添加了院系字段,并为已有数据设置了默认值。
### 5. 后续工作
需要更新以下组件以支持院系字段:
1. 前端类型定义和API接口
2. 新增和编辑表单界面
3. 列表和详情页面
4. 后端实体类、VO类和Mapper文件
## 相关文档
- SQL脚本: `docs/设计文档/database/心理健康测评/xljk_add_department_fields.sql`
- 参考院系数据: 来自系统 `sys_dept` 表,其中二级学院ID为101
---
# 心理健康测评功能增强:院系-专业联动实现
**日期**:2025-07-02
**操作人**:lixuming
## 变更内容
### 1. 需求实现
完成了心理健康测评模块的院系-专业两级联动功能,使得用户可以先选择院系,再选择该院系下的专业和班级。主要变更内容如下:
### 2. 后端实现
1. 在实体类和VO类中添加院系ID和院系名称字段
2. 添加获取院系列表的API接口
3. 修改获取专业班级API接口,支持按院系过滤
4. 在XML映射文件中添加院系字段映射
### 3. 前端实现
1. 添加院系相关的类型定义
- 添加`DepartmentOption`接口
- 在`XljkEvaluationVO`和`XljkEvaluationForm`中添加院系字段
2. 添加院系相关的API接口
- 添加`getDepartmentOptions`函数获取院系列表
- 更新`getMajorClassOptions`函数支持院系过滤
3. 界面实现
- 在新增和编辑页面的专业字段上方添加院系选择
- 在列表页面查询条件和表格中添加院系字段
- 实现院系选择后联动刷新专业选项
### 4. 数据关联和联动
- 院系选择变化时,清空专业和班级选项,并重新加载该院系下的专业
- 专业选择变化时,清空班级选项,并加载该专业下的班级
- 列表查询时可按院系、专业、班级多级筛选
## 文件变更
1. **后端文件**:
- `XljkEvaluation.java` - 添加院系字段
- `XljkEvaluationVo.java` - 添加院系字段
- `XljkEvaluationMapper.xml` - 添加字段映射
- `XljkEvaluationController.java` - 添加院系API
- `IXljkEvaluationService.java` - 添加院系服务接口
- `XljkEvaluationServiceImpl.java` - 实现院系数据获取
2. **前端文件**:
- `types.ts` - 添加院系相关类型
- `index.ts` - 添加院系API接口
- `add.vue` - 添加院系选择和联动
- `edit.vue` - 添加院系字段
- `index.vue` - 添加院系查询和显示
## 效果总结
通过添加院系字段和实现联动,使心理健康测评的数据组织更符合学校管理结构,提高了数据管理的层次性和使用体验。用户可以按院系-专业-班级的自然组织结构进行操作,提升了系统易用性。
---
# 心理健康测评模块与学生信息管理关联适配
**日期**:2025-07-02
**操作人**:lixuming
## 变更内容
### 1. 需求背景
由于学生信息管理模块尚未完全开发完成,需要关联现有的xg_student_info表数据用于心理健康测评的业务验证。主要调整内容包括:
- 将院系字段关联至xg_student_info.college_name
- 将专业字段关联至xg_student_info.major_name
- 将班级字段关联至xg_student_info.class_name
- 学期字段由下拉选择改为手动填写
### 2. 后端实现
1. 添加获取学生信息数据的API接口:
- GET /xljk/evaluation/options/colleges - 获取院系列表
- GET /xljk/evaluation/options/majors - 获取专业列表
- GET /xljk/evaluation/options/classes - 获取班级列表
- GET /xljk/evaluation/students - 获取学生列表
2. 实现数据查询方法:
- 通过SQL直接查询xg_student_info表,获取所需数据
- 实现层级过滤:根据院系过滤专业,根据院系和专业过滤班级,根据院系、专业、班级获取学生信息
### 3. 前端实现
1. 类型定义调整:
- 替换原有的院系、专业、班级相关类型定义
- 新增CollegeOption、MajorOption、ClassOption类型
- 调整StudentInfo类型适配学生信息表结构
2. 前端API调整:
- 替换原有API接口调用
- 实现新的数据获取和联动机制
3. 界面交互调整:
- 使用院系、专业、班级的名称进行数据交互
- 学期字段改为手动输入方式
- 修改学生性别显示规则,适配M/F字段
### 4. 核心调整点
1. **数据源切换**:从系统自建模拟数据切换到直接查询xg_student_info表
2. **字段匹配**:根据xg_student_info表结构对接对应字段
3. **查询策略**:使用动态SQL实现多级联动查询
4. **ID处理**:兼容保留原有ID字段,但实际使用名称字段进行数据关联
5. **前端适配**:调整前端代码使其适配新的数据结构和交互方式
## 文件变更
1. **后端文件**:
- XljkEvaluationController.java - 添加新API接口
- IXljkEvaluationService.java - 添加新服务方法
- XljkEvaluationServiceImpl.java - 实现学生信息查询逻辑
2. **前端文件**:
- types.ts - 更新数据类型定义
- index.ts - 更新API调用接口
- add.vue - 更新表单处理逻辑
## 效果总结
通过此次调整,实现了心理健康测评功能与现有学生信息表的对接,使得测试过程中可以使用真实学生数据进行业务验证。此方案作为临时适配方案,待学生信息管理模块完全开发完成后,可考虑采用更规范的数据服务调用方式进行优化。
@@ -0,0 +1,41 @@
# 代码调整记录 - 2025-07-03 - lixuming
## 变更时间:2025-07-03 14:30:00
## 变更内容
### 数据库连接配置修改
1. 修改了应用配置文件中的数据库连接,从阿里云数据库改为本地数据库
2. 修改文件:`backend/ruoyi-admin/src/main/resources/application-dev.yml`
3. 具体修改:
```yaml
# 修改前
url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: hn02le.34lkdLKD
# 修改后
url: jdbc:mysql://localhost:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 'DP@xp117'
```
## 变更原因
1. 将应用数据库连接从阿里云改为本地开发环境,方便本地开发和调试
2. Redis连接保持不变,继续使用阿里云Redis服务
## 变更影响
1. 应用将从本地数据库读取数据,确保本地数据库中存在相应的表结构和数据
2. 多租户配置需要注意,确保表 `xljk_evaluation` 包含在排除表列表中或数据有正确的租户ID
## 相关文件
- `backend/ruoyi-admin/src/main/resources/application-dev.yml`
## 备注
使用本地数据库后,需确保本地数据库中的表结构和初始数据与原阿里云数据库一致。
特别注意多租户配置,心理健康测评表中的租户ID需设置为 `000000`。
@@ -0,0 +1,803 @@
# 心理健康测评API文档
## 1. API概述
心理健康测评模块提供班主任对班级学生进行心理健康状况评估的功能,包括测评创建、学生数据加载、心理状态评估、测评统计等功能。
### 1.1 基本信息
- **模块名称**:心理健康测评模块
- **模块代码**:ruoyi-xljk
- **API前缀**:/xljk
- **数据表**:xljk_evaluation, xljk_evaluation_detail
- **开发负责人**:Cursor AI
- **创建日期**:2025-07-02
### 1.2 功能列表
| 功能 | 描述 |
| ---- | ---- |
| 测评管理 | 创建、查询、修改、删除测评 |
| 测评明细管理 | 查询、修改学生心理状态 |
| 测评流程 | 发布测评、完成测评 |
| 数据统计 | 测评统计、重点关注学生统计 |
## 2. API列表
### 2.1 测评管理API
#### 2.1.1 查询测评列表
- **接口URL**:/xljk/evaluation/list
- **请求方式**:GET
- **接口权限**:xljk:evaluation:list
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| pageNum | int | 否 | 当前页码,默认为1 |
| pageSize | int | 否 | 每页记录数,默认为10 |
| evaluationName | string | 否 | 测评名称,支持模糊查询 |
| majorId | long | 否 | 专业ID |
| classId | long | 否 | 班级ID |
| semester | string | 否 | 学期 |
| status | string | 否 | 状态(0-草稿,1-已发布,2-已完成) |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 3,
"rows": [
{
"id": 1,
"evaluationName": "2024上学期计算机2301班心理健康测评",
"majorId": 101,
"majorName": "计算机应用技术",
"classId": 1001,
"className": "计算机2301班",
"semester": "2024上学期",
"status": "2",
"createName": "张班主任",
"remark": "期末测评",
"createBy": 2001,
"createTime": "2025-01-10 09:00:00",
"updateBy": 2001,
"updateTime": "2025-01-10 10:30:00",
"studentCount": 5,
"focusCount": 2
},
{
"id": 2,
"evaluationName": "2024上学期电子2302班心理健康测评",
"majorId": 102,
"majorName": "电子商务",
"classId": 1002,
"className": "电子2302班",
"semester": "2024上学期",
"status": "0",
"createName": "李班主任",
"remark": "期末测评草稿",
"createBy": 2002,
"createTime": "2025-01-11 14:00:00",
"updateBy": 2002,
"updateTime": "2025-01-11 14:30:00",
"studentCount": 0,
"focusCount": 0
}
]
}
}
```
#### 2.1.2 获取测评详情
- **接口URL**:/xljk/evaluation/{id}
- **请求方式**:GET
- **接口权限**:xljk:evaluation:query
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| id | long | 是 | 测评ID |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"evaluationName": "2024上学期计算机2301班心理健康测评",
"majorId": 101,
"majorName": "计算机应用技术",
"classId": 1001,
"className": "计算机2301班",
"semester": "2024上学期",
"status": "2",
"createName": "张班主任",
"remark": "期末测评",
"createBy": 2001,
"createTime": "2025-01-10 09:00:00",
"updateBy": 2001,
"updateTime": "2025-01-10 10:30:00",
"studentCount": 5,
"focusCount": 2
}
}
```
#### 2.1.3 新增测评
- **接口URL**:/xljk/evaluation
- **请求方式**:POST
- **接口权限**:xljk:evaluation:add
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| evaluationName | string | 是 | 测评名称 |
| majorId | long | 是 | 专业ID |
| majorName | string | 是 | 专业名称 |
| classId | long | 是 | 班级ID |
| className | string | 是 | 班级名称 |
| semester | string | 是 | 学期 |
| remark | string | 否 | 备注 |
- **请求示例**:
```json
{
"evaluationName": "2024下学期计算机2301班心理健康测评",
"majorId": 101,
"majorName": "计算机应用技术",
"classId": 1001,
"className": "计算机2301班",
"semester": "2024下学期",
"remark": "期末测评"
}
```
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": 3
}
```
#### 2.1.4 修改测评
- **接口URL**:/xljk/evaluation
- **请求方式**:PUT
- **接口权限**:xljk:evaluation:edit
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| id | long | 是 | 测评ID |
| evaluationName | string | 是 | 测评名称 |
| majorId | long | 是 | 专业ID |
| majorName | string | 是 | 专业名称 |
| classId | long | 是 | 班级ID |
| className | string | 是 | 班级名称 |
| semester | string | 是 | 学期 |
| remark | string | 否 | 备注 |
- **请求示例**:
```json
{
"id": 3,
"evaluationName": "2024下学期计算机2301班心理健康测评(修改)",
"majorId": 101,
"majorName": "计算机应用技术",
"classId": 1001,
"className": "计算机2301班",
"semester": "2024下学期",
"remark": "期末测评(修改)"
}
```
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
#### 2.1.5 删除测评
- **接口URL**:/xljk/evaluation/{ids}
- **请求方式**:DELETE
- **接口权限**:xljk:evaluation:remove
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| ids | long[] | 是 | 测评ID数组 |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
#### 2.1.6 获取专业和班级选项
- **接口URL**:/xljk/evaluation/options/majorAndClass
- **请求方式**:GET
- **接口权限**:xljk:evaluation:query
- **请求参数**:无
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"majors": [
{
"id": 101,
"name": "计算机应用技术"
},
{
"id": 102,
"name": "电子商务"
},
{
"id": 103,
"name": "会计"
}
],
"classes": [
{
"id": 1001,
"name": "计算机2301班",
"majorId": 101
},
{
"id": 1002,
"name": "电子2302班",
"majorId": 102
},
{
"id": 1003,
"name": "会计2303班",
"majorId": 103
}
]
}
}
```
#### 2.1.7 获取学期选项
- **接口URL**:/xljk/evaluation/options/semester
- **请求方式**:GET
- **接口权限**:xljk:evaluation:query
- **请求参数**:无
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
"2023上学期",
"2023下学期",
"2024上学期",
"2024下学期",
"2025上学期",
"2025下学期"
]
}
```
#### 2.1.8 发布测评
- **接口URL**:/xljk/evaluation/{id}/publish
- **请求方式**:PUT
- **接口权限**:xljk:evaluation:publish
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| id | long | 是 | 测评ID |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
#### 2.1.9 完成测评
- **接口URL**:/xljk/evaluation/{id}/complete
- **请求方式**:PUT
- **接口权限**:xljk:evaluation:complete
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| id | long | 是 | 测评ID |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
#### 2.1.10 导出测评
- **接口URL**:/xljk/evaluation/export
- **请求方式**:POST
- **接口权限**:xljk:evaluation:export
- **请求参数**:同查询测评列表接口
- **返回结果**:Excel文件流
### 2.2 测评明细API
#### 2.2.1 查询测评明细列表
- **接口URL**:/xljk/evaluation/detail/list
- **请求方式**:GET
- **接口权限**:xljk:evaluation:list
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| pageNum | int | 否 | 当前页码,默认为1 |
| pageSize | int | 否 | 每页记录数,默认为10 |
| evaluationId | long | 否 | 测评ID |
| studentName | string | 否 | 学生姓名,支持模糊查询 |
| studentNo | string | 否 | 学号,支持模糊查询 |
| gender | string | 否 | 性别(0-男,1-女,2-未知) |
| mentalStatus | string | 否 | 心理状态(1-健康,2-良好,3-重点关注) |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 5,
"rows": [
{
"id": 101,
"evaluationId": 1,
"studentId": 10001,
"studentNo": "2024001",
"studentName": "张三",
"gender": "0",
"birthDate": "2005-06-15",
"phone": "13800138001",
"mentalStatus": "1",
"score": null,
"remark": null,
"createBy": 2001,
"createTime": "2025-01-10 09:10:00",
"updateBy": 2001,
"updateTime": "2025-01-10 09:10:00",
"evaluationName": "2024上学期计算机2301班心理健康测评",
"className": "计算机2301班",
"majorName": "计算机应用技术",
"semester": "2024上学期"
},
{
"id": 103,
"evaluationId": 1,
"studentId": 10003,
"studentNo": "2024003",
"studentName": "王五",
"gender": "0",
"birthDate": "2005-09-10",
"phone": "13800138003",
"mentalStatus": "3",
"score": 75,
"remark": "近期学习压力大,需要关注",
"createBy": 2001,
"createTime": "2025-01-10 09:20:00",
"updateBy": 2001,
"updateTime": "2025-01-10 09:20:00",
"evaluationName": "2024上学期计算机2301班心理健康测评",
"className": "计算机2301班",
"majorName": "计算机应用技术",
"semester": "2024上学期"
}
]
}
}
```
#### 2.2.2 根据测评ID查询明细列表
- **接口URL**:/xljk/evaluation/detail/evaluation/{evaluationId}
- **请求方式**:GET
- **接口权限**:xljk:evaluation:query
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| evaluationId | long | 是 | 测评ID |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 101,
"evaluationId": 1,
"studentId": 10001,
"studentNo": "2024001",
"studentName": "张三",
"gender": "0",
"birthDate": "2005-06-15",
"phone": "13800138001",
"mentalStatus": "1",
"score": null,
"remark": null,
"createBy": 2001,
"createTime": "2025-01-10 09:10:00",
"updateBy": 2001,
"updateTime": "2025-01-10 09:10:00"
},
{
"id": 102,
"evaluationId": 1,
"studentId": 10002,
"studentNo": "2024002",
"studentName": "李四",
"gender": "0",
"birthDate": "2005-03-22",
"phone": "13800138002",
"mentalStatus": "2",
"score": null,
"remark": null,
"createBy": 2001,
"createTime": "2025-01-10 09:15:00",
"updateBy": 2001,
"updateTime": "2025-01-10 09:15:00"
}
]
}
```
#### 2.2.3 获取学生信息
- **接口URL**:/xljk/evaluation/detail/students/{classId}
- **请求方式**:GET
- **接口权限**:xljk:evaluation:query
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| classId | long | 是 | 班级ID |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 10001,
"studentNo": "2024001",
"studentName": "张三",
"gender": "0",
"birthDate": "2005-06-15",
"phone": "13800138001"
},
{
"id": 10002,
"studentNo": "2024002",
"studentName": "李四",
"gender": "0",
"birthDate": "2005-03-22",
"phone": "13800138002"
}
]
}
```
#### 2.2.4 批量新增测评明细
- **接口URL**:/xljk/evaluation/detail/batch
- **请求方式**:POST
- **接口权限**:xljk:evaluation:add
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| 数组 | array | 是 | 测评明细数组 |
- **请求示例**:
```json
[
{
"evaluationId": 3,
"studentId": 10001,
"studentNo": "2024001",
"studentName": "张三",
"gender": "0",
"birthDate": "2005-06-15",
"phone": "13800138001"
},
{
"evaluationId": 3,
"studentId": 10002,
"studentNo": "2024002",
"studentName": "李四",
"gender": "0",
"birthDate": "2005-03-22",
"phone": "13800138002"
}
]
```
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
#### 2.2.5 批量更新学生心理状态
- **接口URL**:/xljk/evaluation/detail/batch/mentalStatus
- **请求方式**:PUT
- **接口权限**:xljk:evaluation:edit
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| 数组 | array | 是 | 学生心理状态数组 |
- **请求示例**:
```json
[
{
"id": 101,
"mentalStatus": "1"
},
{
"id": 102,
"mentalStatus": "2"
},
{
"id": 103,
"mentalStatus": "3",
"score": 75,
"remark": "近期学习压力大,需要关注"
}
]
```
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功"
}
```
#### 2.2.6 获取重点关注学生列表
- **接口URL**:/xljk/evaluation/detail/focus
- **请求方式**:GET
- **接口权限**:xljk:evaluation:query
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| pageNum | int | 否 | 当前页码,默认为1 |
| pageSize | int | 否 | 每页记录数,默认为10 |
| evaluationId | long | 否 | 测评ID |
| studentName | string | 否 | 学生姓名,支持模糊查询 |
| studentNo | string | 否 | 学号,支持模糊查询 |
| gender | string | 否 | 性别(0-男,1-女,2-未知) |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 2,
"rows": [
{
"id": 103,
"evaluationId": 1,
"studentId": 10003,
"studentNo": "2024003",
"studentName": "王五",
"gender": "0",
"birthDate": "2005-09-10",
"phone": "13800138003",
"mentalStatus": "3",
"score": 75,
"remark": "近期学习压力大,需要关注",
"createBy": 2001,
"createTime": "2025-01-10 09:20:00",
"updateBy": 2001,
"updateTime": "2025-01-10 09:20:00",
"evaluationName": "2024上学期计算机2301班心理健康测评",
"className": "计算机2301班",
"majorName": "计算机应用技术",
"semester": "2024上学期"
},
{
"id": 105,
"evaluationId": 1,
"studentId": 10005,
"studentNo": "2024005",
"studentName": "钱七",
"gender": "1",
"birthDate": "2005-07-20",
"phone": "13800138005",
"mentalStatus": "3",
"score": 80,
"remark": "家庭情况特殊,需要关注",
"createBy": 2001,
"createTime": "2025-01-10 09:30:00",
"updateBy": 2001,
"updateTime": "2025-01-10 09:30:00",
"evaluationName": "2024上学期计算机2301班心理健康测评",
"className": "计算机2301班",
"majorName": "计算机应用技术",
"semester": "2024上学期"
}
]
}
}
```
### 2.3 统计分析API
#### 2.3.1 获取测评统计数据
- **接口URL**:/xljk/evaluation/{id}/statistics
- **请求方式**:GET
- **接口权限**:xljk:evaluation:statistics
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| id | long | 是 | 测评ID |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"evaluationName": "2024上学期计算机2301班心理健康测评",
"className": "计算机2301班",
"semester": "2024上学期",
"createName": "张班主任",
"createTime": "2025-01-10 09:00:00",
"totalCount": 5,
"healthyCount": 2,
"goodCount": 1,
"focusCount": 2,
"healthyPercent": 40.0,
"goodPercent": 20.0,
"focusPercent": 40.0,
"maleCount": 3,
"femaleCount": 2,
"maleFocusCount": 1,
"femaleFocusCount": 1
}
}
```
#### 2.3.2 获取重点关注学生统计
- **接口URL**:/xljk/evaluation/statistics/focus
- **请求方式**:GET
- **接口权限**:xljk:evaluation:statistics
- **请求参数**:
| 参数名 | 类型 | 是否必须 | 描述 |
| ------ | ---- | -------- | ---- |
| majorId | long | 否 | 专业ID |
| classId | long | 否 | 班级ID |
| semester | string | 否 | 学期 |
- **返回示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"totalStudentCount": 10,
"totalFocusCount": 4,
"totalFocusRate": 40.0,
"classStats": [
{
"className": "计算机2301班",
"studentCount": 5,
"focusCount": 2,
"focusRate": 40.0
},
{
"className": "电子2302班",
"studentCount": 5,
"focusCount": 2,
"focusRate": 40.0
}
],
"semesterStats": [
{
"semester": "2023下学期",
"studentCount": 5,
"focusCount": 2,
"focusRate": 40.0
},
{
"semester": "2024上学期",
"studentCount": 5,
"focusCount": 2,
"focusRate": 40.0
}
]
}
}
```
## 3. 数据字典
### 3.1 测评状态(xljk_evaluation_status)
| 编码 | 标签 | 描述 |
| ---- | ---- | ---- |
| 0 | 草稿 | 测评创建后的初始状态 |
| 1 | 已发布 | 测评发布后的状态 |
| 2 | 已完成 | 测评完成后的状态 |
### 3.2 心理状态(xljk_mental_status)
| 编码 | 标签 | 描述 |
| ---- | ---- | ---- |
| 1 | 健康 | 心理状态健康 |
| 2 | 良好 | 心理状态良好 |
| 3 | 重点关注 | 需要重点关注的心理状态 |
## 4. 注意事项
1. 测评状态流转:草稿 -> 已发布 -> 已完成,不能跳过中间状态
2. 草稿状态的测评可以修改和删除,已发布和已完成状态的测评不能删除
3. 已完成状态的测评不能修改学生心理状态
4. 重点关注状态的学生必须填写分数
5. 测评完成后,会统计各状态学生人数和比例
6. 班主任只能查看和管理自己创建的测评
@@ -0,0 +1,27 @@
-- 心理健康测评表增加院系字段脚本
-- 日期:2025-07-02
-- 作者:lixuming
-- 在心理健康测评表中添加院系ID和院系名称字段
ALTER TABLE xljk_evaluation
ADD COLUMN department_id bigint NOT NULL COMMENT '院系ID' AFTER id,
ADD COLUMN department_name varchar(100) NOT NULL COMMENT '院系名称' AFTER department_id;
-- 更新已有数据,设置默认值
-- 这里使用智能控制学院(103)作为示例默认值
UPDATE xljk_evaluation SET department_id = 103, department_name = '智能控制学院' WHERE 1=1;
-- 为院系ID字段添加索引以提高查询效率
ALTER TABLE xljk_evaluation ADD INDEX idx_department_id (department_id);
-- 系统院系参考数据(来自sys_dept表)
-- 101 是二级学院的父级ID
/*
智能控制学院(103)
智能制造学院(104)
医药健康学院(105)
餐饮休闲学院(106)
数字信息学院(107)
人文学院(1938483954134245378)
生态园林学院(1938484049621770242)
*/
@@ -0,0 +1,81 @@
-- ----------------------------
-- 心理健康测评模块数据表
-- ----------------------------
-- ----------------------------
-- 心理健康测评表
-- ----------------------------
DROP TABLE IF EXISTS `xljk_evaluation`;
CREATE TABLE `xljk_evaluation` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`evaluation_name` varchar(100) NOT NULL COMMENT '测评名称',
`major_id` bigint(20) NOT NULL COMMENT '专业ID',
`major_name` varchar(100) NOT NULL COMMENT '专业名称',
`class_id` bigint(20) NOT NULL COMMENT '班级ID',
`class_name` varchar(100) NOT NULL COMMENT '班级名称',
`semester` varchar(50) NOT NULL COMMENT '学期(如2024上学期)',
`status` char(1) NOT NULL DEFAULT '0' COMMENT '状态(0-草稿,1-已发布,2-已完成)',
`create_by` bigint(20) NOT NULL COMMENT '创建者ID(班主任ID)',
`create_name` varchar(50) NOT NULL COMMENT '创建者姓名',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`tenant_id` varchar(20) DEFAULT NULL COMMENT '租户ID',
`deleted` char(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0-存在,1-删除)',
PRIMARY KEY (`id`),
KEY `idx_class_id` (`class_id`),
KEY `idx_semester` (`semester`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心理健康测评表';
-- ----------------------------
-- 心理健康测评明细表
-- ----------------------------
DROP TABLE IF EXISTS `xljk_evaluation_detail`;
CREATE TABLE `xljk_evaluation_detail` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`evaluation_id` bigint(20) NOT NULL COMMENT '测评表ID',
`student_id` bigint(20) NOT NULL COMMENT '学生ID',
`student_no` varchar(50) NOT NULL COMMENT '学号',
`student_name` varchar(50) NOT NULL COMMENT '学生姓名',
`gender` char(1) DEFAULT NULL COMMENT '性别(0-男,1-女,2-未知)',
`birth_date` date DEFAULT NULL COMMENT '出生日期',
`phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`mental_status` char(1) NOT NULL COMMENT '心理状态(1-健康,2-良好,3-重点关注)',
`score` int(11) DEFAULT NULL COMMENT '分数(仅重点关注时填写)',
`create_by` bigint(20) NOT NULL COMMENT '创建者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`tenant_id` varchar(20) DEFAULT NULL COMMENT '租户ID',
`deleted` char(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0-存在,1-删除)',
PRIMARY KEY (`id`),
KEY `idx_evaluation_id` (`evaluation_id`),
KEY `idx_student_id` (`student_id`),
KEY `idx_mental_status` (`mental_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心理健康测评明细表';
-- ----------------------------
-- 示例数据
-- ----------------------------
-- 插入测评表示例数据
INSERT INTO `xljk_evaluation` VALUES (1, '2024上学期计算机2301班心理健康测评', 101, '计算机应用技术', 1001, '计算机2301班', '2024上学期', '2', 2001, '张班主任', '2025-01-10 09:00:00', 2001, '2025-01-10 10:30:00', '期末测评', '000000', '0');
INSERT INTO `xljk_evaluation` VALUES (2, '2024上学期电子2302班心理健康测评', 102, '电子商务', 1002, '电子2302班', '2024上学期', '0', 2002, '李班主任', '2025-01-11 14:00:00', 2002, '2025-01-11 14:30:00', '期末测评草稿', '000000', '0');
INSERT INTO `xljk_evaluation` VALUES (3, '2023下学期计算机2301班心理健康测评', 101, '计算机应用技术', 1001, '计算机2301班', '2023下学期', '2', 2001, '张班主任', '2024-12-15 09:00:00', 2001, '2024-12-15 11:00:00', '期末测评', '000000', '0');
-- 插入测评明细示例数据
INSERT INTO `xljk_evaluation_detail` VALUES (101, 1, 10001, '2024001', '张三', '0', '2005-06-15', '13800138001', '1', NULL, 2001, '2025-01-10 09:10:00', 2001, '2025-01-10 09:10:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (102, 1, 10002, '2024002', '李四', '0', '2005-03-22', '13800138002', '2', NULL, 2001, '2025-01-10 09:15:00', 2001, '2025-01-10 09:15:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (103, 1, 10003, '2024003', '王五', '0', '2005-09-10', '13800138003', '3', 75, 2001, '2025-01-10 09:20:00', 2001, '2025-01-10 09:20:00', '近期学习压力大,需要关注', '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (104, 1, 10004, '2024004', '赵六', '1', '2005-11-05', '13800138004', '1', NULL, 2001, '2025-01-10 09:25:00', 2001, '2025-01-10 09:25:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (105, 1, 10005, '2024005', '钱七', '1', '2005-07-20', '13800138005', '3', 80, 2001, '2025-01-10 09:30:00', 2001, '2025-01-10 09:30:00', '家庭情况特殊,需要关注', '000000', '0');
-- 插入第三个测评的明细示例数据
INSERT INTO `xljk_evaluation_detail` VALUES (201, 3, 10001, '2024001', '张三', '0', '2005-06-15', '13800138001', '1', NULL, 2001, '2024-12-15 09:10:00', 2001, '2024-12-15 09:10:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (202, 3, 10002, '2024002', '李四', '0', '2005-03-22', '13800138002', '1', NULL, 2001, '2024-12-15 09:15:00', 2001, '2024-12-15 09:15:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (203, 3, 10003, '2024003', '王五', '0', '2005-09-10', '13800138003', '3', 70, 2001, '2024-12-15 09:20:00', 2001, '2024-12-15 09:20:00', '期末考试压力大', '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (204, 3, 10004, '2024004', '赵六', '1', '2005-11-05', '13800138004', '2', NULL, 2001, '2024-12-15 09:25:00', 2001, '2024-12-15 09:25:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (205, 3, 10005, '2024005', '钱七', '1', '2005-07-20', '13800138005', '3', 85, 2001, '2024-12-15 09:30:00', 2001, '2024-12-15 09:30:00', '家庭情况特殊,需要关注', '000000', '0');
@@ -0,0 +1,26 @@
-- 心理健康测评数据库字段更新脚本
-- 日期:2025-07-02
-- 作者:lixuming
-- 在心理健康测评表中添加create_dept字段
ALTER TABLE xljk_evaluation ADD COLUMN create_dept bigint NOT NULL COMMENT '创建部门' AFTER tenant_id;
-- 在心理健康测评明细表中添加create_dept字段
ALTER TABLE xljk_evaluation_detail ADD COLUMN create_dept bigint NOT NULL COMMENT '创建部门' AFTER tenant_id;
-- 初始化create_dept字段,将create_by的值作为create_dept的初始值
UPDATE xljk_evaluation SET create_dept = create_by;
UPDATE xljk_evaluation_detail SET create_dept = create_by;
-- 更新心理健康测评表的XML映射文件:
-- 在backend/ruoyi-modules/ruoyi-xljk/src/main/resources/mapper/XljkEvaluationMapper.xml文件中添加:
-- <result property="createDept" column="create_dept"/>
-- 更新心理健康测评明细表的XML映射文件:
-- 在backend/ruoyi-modules/ruoyi-xljk/src/main/resources/mapper/XljkEvaluationDetailMapper.xml文件中添加:
-- <result property="createDept" column="create_dept"/>
-- 说明:
-- 此脚本用于修复心理健康测评模块中因字段缺失导致的SQL错误
-- 错误:Unknown column 'create_dept' in 'field list'
-- 原因:实体类通过继承BaseEntity获得了createDept字段,但数据库表中不存在对应的create_dept列
@@ -0,0 +1,89 @@
-- ==========================================
-- 心理健康测评模块菜单权限配置脚本
-- 版本:V1.0.1
-- 功能:心理健康测评模块菜单权限配置
-- 作者:lixuming
-- 时间:2025-07-02
-- 依据文档:docs/需求文档/心理测评需求/心理健康测评系统设计说明.md
-- 更新:将心理健康设置为一级菜单,与首页、系统管理平级
-- ==========================================
-- 设置字符集
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- 心理健康测评模块菜单结构(二级层级)
-- ----------------------------
-- 1. 心理健康一级菜单(与首页、系统管理平级)
INSERT INTO `sys_menu` (
`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`
) VALUES
(2300, '心理健康', 0, 8, 'xljk', NULL, NULL, 1, 0, 'M', '0', '0', NULL, 'heart', 103, 1, NOW(), 1, NOW(), '心理健康一级菜单');
-- 2. 心理健康测评二级功能菜单
INSERT INTO `sys_menu` (
`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`
) VALUES
(2310, '心理健康测评', 2300, 1, 'evaluation', 'xljk/evaluation/index', NULL, 1, 0, 'C', '0', '0', 'xljk:evaluation:list', 'form', 103, 1, NOW(), 1, NOW(), '心理健康测评页面');
-- 3. 心理健康测评功能按钮
INSERT INTO `sys_menu` (
`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`
) VALUES
-- 心理健康测评按钮
(2311, '测评查询', 2310, 1, '', NULL, NULL, 1, 0, 'F', '0', '0', 'xljk:evaluation:query', '#', 103, 1, NOW(), 1, NOW(), '测评查询'),
(2312, '测评新增', 2310, 2, '', NULL, NULL, 1, 0, 'F', '0', '0', 'xljk:evaluation:add', '#', 103, 1, NOW(), 1, NOW(), '测评新增'),
(2313, '测评修改', 2310, 3, '', NULL, NULL, 1, 0, 'F', '0', '0', 'xljk:evaluation:edit', '#', 103, 1, NOW(), 1, NOW(), '测评修改'),
(2314, '测评删除', 2310, 4, '', NULL, NULL, 1, 0, 'F', '0', '0', 'xljk:evaluation:remove', '#', 103, 1, NOW(), 1, NOW(), '测评删除'),
(2315, '测评导出', 2310, 5, '', NULL, NULL, 1, 0, 'F', '0', '0', 'xljk:evaluation:export', '#', 103, 1, NOW(), 1, NOW(), '测评导出');
-- ----------------------------
-- 角色菜单权限配置
-- ----------------------------
-- 为超级管理员角色分配所有权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 1, menu_id FROM `sys_menu` WHERE menu_id = 2300 OR (menu_id >= 2310 AND menu_id <= 2315);
-- 为学生处管理员角色分配全部权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 2, menu_id FROM `sys_menu` WHERE menu_id = 2300 OR (menu_id >= 2310 AND menu_id <= 2315);
-- 为二级学院管理员角色分配权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 3, menu_id FROM `sys_menu` WHERE menu_id = 2300 OR (menu_id >= 2310 AND menu_id <= 2315);
-- 为辅导员角色分配权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(4, 2300), -- 心理健康一级菜单
(4, 2310), -- 心理健康测评
(4, 2311), -- 测评查询
(4, 2312), -- 测评新增
(4, 2313), -- 测评修改
(4, 2315); -- 测评导出
-- 为班级教师角色分配基础权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(5, 2300), -- 心理健康一级菜单
(5, 2310), -- 心理健康测评
(5, 2311); -- 测评查询
-- 为学生角色分配查看权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(6, 2300), -- 心理健康一级菜单
(6, 2310), -- 心理健康测评
(6, 2311); -- 测评查询
-- 添加心理健康测评状态字典
INSERT INTO `sys_dict_type` VALUES(1000, '心理测评状态', 'evaluation_status', '0', 1, NOW(), NULL, NULL, '心理健康测评状态');
INSERT INTO `sys_dict_data` VALUES(1001, 0, '草稿', '0', 'evaluation_status', '', 'warning', 'N', '0', 1, NOW(), 1, NOW(), '草稿状态');
INSERT INTO `sys_dict_data` VALUES(1002, 1, '已发布', '1', 'evaluation_status', '', 'primary', 'N', '0', 1, NOW(), 1, NOW(), '已发布状态');
INSERT INTO `sys_dict_data` VALUES(1003, 2, '已完成', '2', 'evaluation_status', '', 'success', 'N', '0', 1, NOW(), 1, NOW(), '已完成状态');
SET FOREIGN_KEY_CHECKS = 1;
-- 验证菜单配置结果
SELECT '心理健康测评菜单权限配置完成!' AS result;
SELECT CONCAT('心理健康测评菜单:', COUNT(*), '个') AS menu_info FROM sys_menu WHERE menu_id BETWEEN 2300 AND 2315;
@@ -0,0 +1,198 @@
# 心理健康测评系统设计说明
## 设计目标
设计一个简单实用的心理健康测评系统,由班主任为班级学生进行心理健康状况评估,并记录重点关注的学生,为学校心理健康管理提供有效支持。
## 数据表设计
### 1. 心理健康测评表 (`xljk_evaluation`)
**主要功能**: 管理班级心理健康测评的基本信息
**字段说明**:
- **标准字段**: 按要求包含 `id`、`tenant_id`、`create_by`、`create_time`、`update_by`、`update_time`、`deleted`
- **业务字段**:
- `evaluation_name`: 测评名称
- `major_id`: 专业ID
- `major_name`: 专业名称
- `class_id`: 班级ID
- `class_name`: 班级名称
- `semester`: 学期(如2024上学期)
- `status`: 状态(0-草稿,1-已发布,2-已完成)
- `create_name`: 创建者姓名(班主任姓名)
- `remark`: 备注
### 2. 心理健康测评明细表 (`xljk_evaluation_detail`)
**主要功能**: 记录每个学生的心理健康评估结果
**字段说明**:
- **标准字段**: 按要求包含 `id`、`tenant_id`、`create_by`、`create_time`、`update_by`、`update_time`、`deleted`
- **学生信息**:
- `evaluation_id`: 关联测评表ID
- `student_id`: 学生ID
- `student_no`: 学号
- `student_name`: 学生姓名
- `gender`: 性别(0-男,1-女,2-未知)
- `birth_date`: 出生日期
- `phone`: 联系电话
- **评估信息**:
- `mental_status`: 心理状态(1-健康,2-良好,3-重点关注)
- `score`: 分数(仅重点关注时填写)
- `remark`: 备注
## 核心功能流程
### 1. 测评创建
- 班主任创建新测评表
- 选择专业、班级、学年(如2024上学期)
- 系统自动加载该班级学生信息(专业、班级、学号、姓名、性别、出生日期、联系电话)
- 初始状态为"草稿"
### 2. 测评填写
- 班主任为每个学生选择心理状态:健康、良好、重点关注
- 选择"重点关注"时,需要填写分数
- 无固定分数标准,重点关注由班主任自行判断
- 可保存为草稿,随时继续编辑
### 3. 测评提交
- 班主任确认所有学生评估完成后提交
- 提交后状态改为"已完成"
- 提交后保存历史测评记录
- 无需流转审批
### 4. 测评统计
- 统计重点关注学生数量和比例
- 生成重点关注学生名单
- 支持按学期、专业、班级查看统计数据
## 设计特点
### 简化原则
- 无固定测评周期,由班主任自行发起测评
- 测评直接提交完成,无需流转审批
- 重点关注无固定分数标准,由班主任自行判断
### 数据关联
- 测评明细表通过 `evaluation_id` 关联测评主表
- 学生信息从学生信息管理模块同步获取
- 保存历史测评记录,支持趋势分析
### 数据完整性
- 使用雪花ID作为主键,保证唯一性
- 外键约束确保测评明细表与测评表的关联
- 必填字段设置 NOT NULL 约束
- 合理的索引设计提高查询效率
## 角色权限
### 班主任
- 创建和填写心理健康测评表
- 查看本班学生的心理测评历史记录
- 查看本班学生的心理咨询预约情况
### 心理健康管理员
- 查看所有班级的测评统计数据
- 查看重点关注学生名单
- 无权修改测评数据
### 心理咨询师
- 查看重点关注学生名单
- 查看学生的测评历史
- 无权修改测评数据
## SQL建表语句
```sql
-- 心理健康测评表
CREATE TABLE `xljk_evaluation` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`evaluation_name` varchar(100) NOT NULL COMMENT '测评名称',
`major_id` bigint(20) NOT NULL COMMENT '专业ID',
`major_name` varchar(100) NOT NULL COMMENT '专业名称',
`class_id` bigint(20) NOT NULL COMMENT '班级ID',
`class_name` varchar(100) NOT NULL COMMENT '班级名称',
`semester` varchar(50) NOT NULL COMMENT '学期(如2024上学期)',
`status` char(1) NOT NULL DEFAULT '0' COMMENT '状态(0-草稿,1-已发布,2-已完成)',
`create_by` bigint(20) NOT NULL COMMENT '创建者ID(班主任ID)',
`create_name` varchar(50) NOT NULL COMMENT '创建者姓名',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`tenant_id` varchar(20) DEFAULT NULL COMMENT '租户ID',
`deleted` char(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0-存在,1-删除)',
PRIMARY KEY (`id`),
KEY `idx_class_id` (`class_id`),
KEY `idx_semester` (`semester`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心理健康测评表';
-- 心理健康测评明细表
CREATE TABLE `xljk_evaluation_detail` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`evaluation_id` bigint(20) NOT NULL COMMENT '测评表ID',
`student_id` bigint(20) NOT NULL COMMENT '学生ID',
`student_no` varchar(50) NOT NULL COMMENT '学号',
`student_name` varchar(50) NOT NULL COMMENT '学生姓名',
`gender` char(1) DEFAULT NULL COMMENT '性别(0-男,1-女,2-未知)',
`birth_date` date DEFAULT NULL COMMENT '出生日期',
`phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`mental_status` char(1) NOT NULL COMMENT '心理状态(1-健康,2-良好,3-重点关注)',
`score` int(11) DEFAULT NULL COMMENT '分数(仅重点关注时填写)',
`create_by` bigint(20) NOT NULL COMMENT '创建者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`tenant_id` varchar(20) DEFAULT NULL COMMENT '租户ID',
`deleted` char(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0-存在,1-删除)',
PRIMARY KEY (`id`),
KEY `idx_evaluation_id` (`evaluation_id`),
KEY `idx_student_id` (`student_id`),
KEY `idx_mental_status` (`mental_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心理健康测评明细表';
```
## 示例数据
```sql
-- 插入测评表示例数据
INSERT INTO `xljk_evaluation` VALUES (1, '2024上学期计算机2301班心理健康测评', 101, '计算机应用技术', 1001, '计算机2301班', '2024上学期', '2', 2001, '张班主任', '2025-01-10 09:00:00', 2001, '2025-01-10 10:30:00', '期末测评', '000000', '0');
INSERT INTO `xljk_evaluation` VALUES (2, '2024上学期电子2302班心理健康测评', 102, '电子商务', 1002, '电子2302班', '2024上学期', '0', 2002, '李班主任', '2025-01-11 14:00:00', 2002, '2025-01-11 14:30:00', '期末测评草稿', '000000', '0');
INSERT INTO `xljk_evaluation` VALUES (3, '2023下学期计算机2301班心理健康测评', 101, '计算机应用技术', 1001, '计算机2301班', '2023下学期', '2', 2001, '张班主任', '2024-12-15 09:00:00', 2001, '2024-12-15 11:00:00', '期末测评', '000000', '0');
-- 插入测评明细示例数据
INSERT INTO `xljk_evaluation_detail` VALUES (101, 1, 10001, '2024001', '张三', '0', '2005-06-15', '13800138001', '1', NULL, 2001, '2025-01-10 09:10:00', 2001, '2025-01-10 09:10:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (102, 1, 10002, '2024002', '李四', '0', '2005-03-22', '13800138002', '2', NULL, 2001, '2025-01-10 09:15:00', 2001, '2025-01-10 09:15:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (103, 1, 10003, '2024003', '王五', '0', '2005-09-10', '13800138003', '3', 75, 2001, '2025-01-10 09:20:00', 2001, '2025-01-10 09:20:00', '近期学习压力大,需要关注', '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (104, 1, 10004, '2024004', '赵六', '1', '2005-11-05', '13800138004', '1', NULL, 2001, '2025-01-10 09:25:00', 2001, '2025-01-10 09:25:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (105, 1, 10005, '2024005', '钱七', '1', '2005-07-20', '13800138005', '3', 80, 2001, '2025-01-10 09:30:00', 2001, '2025-01-10 09:30:00', '家庭情况特殊,需要关注', '000000', '0');
-- 插入第三个测评的明细示例数据
INSERT INTO `xljk_evaluation_detail` VALUES (201, 3, 10001, '2024001', '张三', '0', '2005-06-15', '13800138001', '1', NULL, 2001, '2024-12-15 09:10:00', 2001, '2024-12-15 09:10:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (202, 3, 10002, '2024002', '李四', '0', '2005-03-22', '13800138002', '1', NULL, 2001, '2024-12-15 09:15:00', 2001, '2024-12-15 09:15:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (203, 3, 10003, '2024003', '王五', '0', '2005-09-10', '13800138003', '3', 70, 2001, '2024-12-15 09:20:00', 2001, '2024-12-15 09:20:00', '期末考试压力大', '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (204, 3, 10004, '2024004', '赵六', '1', '2005-11-05', '13800138004', '2', NULL, 2001, '2024-12-15 09:25:00', 2001, '2024-12-15 09:25:00', NULL, '000000', '0');
INSERT INTO `xljk_evaluation_detail` VALUES (205, 3, 10005, '2024005', '钱七', '1', '2005-07-20', '13800138005', '3', 85, 2001, '2024-12-15 09:30:00', 2001, '2024-12-15 09:30:00', '家庭情况特殊,需要关注', '000000', '0');
```
## 使用说明
1. 执行上述SQL脚本创建数据表和示例数据
2. 系统会从学生信息管理模块同步学生基础信息
3. 班主任可以根据需要随时发起心理健康测评
4. 测评完成后,可以查看重点关注学生名单和统计数据
+613
View File
@@ -0,0 +1,613 @@
/**
* 心理健康测评API测试脚本
* 使用Node.js和axios进行API测试
*
* @author Cursor AI
* @date 2025-07-02
*/
const axios = require('axios');
const fs = require('fs');
// 配置信息
const config = {
baseURL: 'http://localhost:8080',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
};
// 测试结果记录
const testResults = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
details: []
};
// 创建axios实例
const api = axios.create(config);
// 存储登录后的token
let token = '';
// 存储测试过程中创建的测评ID
let evaluationId = null;
// 存储测试过程中创建的测评明细ID
let detailIds = [];
/**
* 登录获取token
*/
async function login() {
try {
console.log('===== 登录获取token =====');
const loginData = {
username: 'admin',
password: 'admin123',
code: '1234',
uuid: 'test-uuid'
};
const response = await api.post('/login', loginData);
if (response.data.code === 200) {
token = response.data.data.token;
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
console.log('登录成功,获取token成功');
return true;
} else {
console.error('登录失败:', response.data.msg);
return false;
}
} catch (error) {
console.error('登录异常:', error.message);
return false;
}
}
/**
* 记录测试结果
* @param {string} name 测试名称
* @param {boolean} passed 是否通过
* @param {string} message 测试消息
* @param {object} response 响应数据
*/
function recordTestResult(name, passed, message, response) {
testResults.total++;
if (passed) {
testResults.passed++;
console.log(`✅ ${name}: ${message}`);
} else {
testResults.failed++;
console.error(`❌ ${name}: ${message}`);
}
testResults.details.push({
name,
passed,
message,
response: response ? JSON.stringify(response) : null,
timestamp: new Date().toISOString()
});
}
/**
* 测试获取专业和班级选项
*/
async function testGetMajorAndClassOptions() {
try {
console.log('\n===== 测试获取专业和班级选项 =====');
const response = await api.get('/xljk/evaluation/options/majorAndClass');
const passed = response.data.code === 200 &&
response.data.data &&
response.data.data.majors &&
response.data.data.classes;
recordTestResult('获取专业和班级选项', passed,
passed ? '成功获取专业和班级选项' : '获取专业和班级选项失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('获取专业和班级选项', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试获取学期选项
*/
async function testGetSemesterOptions() {
try {
console.log('\n===== 测试获取学期选项 =====');
const response = await api.get('/xljk/evaluation/options/semester');
const passed = response.data.code === 200 &&
Array.isArray(response.data.data) &&
response.data.data.length > 0;
recordTestResult('获取学期选项', passed,
passed ? '成功获取学期选项' : '获取学期选项失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('获取学期选项', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试创建测评
*/
async function testCreateEvaluation(options) {
try {
console.log('\n===== 测试创建测评 =====');
if (!options || !options.majors || !options.classes || !options.semesters) {
recordTestResult('创建测评', false, '缺少必要的选项数据', null);
return null;
}
// 选择第一个专业和班级
const major = options.majors[0];
const clazz = options.classes.find(c => c.majorId === major.id) || options.classes[0];
const semester = options.semesters[0];
const evaluationData = {
evaluationName: `${semester}${clazz.name}心理健康测评(测试)`,
majorId: major.id,
majorName: major.name,
classId: clazz.id,
className: clazz.name,
semester: semester,
remark: '自动化测试创建'
};
const response = await api.post('/xljk/evaluation', evaluationData);
const passed = response.data.code === 200 && response.data.data;
if (passed) {
evaluationId = response.data.data;
}
recordTestResult('创建测评', passed,
passed ? `成功创建测评,ID: ${evaluationId}` : '创建测评失败',
response.data);
return passed ? {
id: evaluationId,
majorId: major.id,
classId: clazz.id
} : null;
} catch (error) {
recordTestResult('创建测评', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试获取学生信息
*/
async function testGetStudents(classId) {
try {
console.log('\n===== 测试获取学生信息 =====');
if (!classId) {
recordTestResult('获取学生信息', false, '缺少班级ID', null);
return null;
}
const response = await api.get(`/xljk/evaluation/detail/students/${classId}`);
const passed = response.data.code === 200 &&
Array.isArray(response.data.data) &&
response.data.data.length > 0;
recordTestResult('获取学生信息', passed,
passed ? `成功获取班级(${classId})学生信息,共${response.data.data.length}条` : '获取学生信息失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('获取学生信息', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试批量添加学生
*/
async function testBatchAddStudents(evaluationId, students) {
try {
console.log('\n===== 测试批量添加学生 =====');
if (!evaluationId || !students || !Array.isArray(students) || students.length === 0) {
recordTestResult('批量添加学生', false, '缺少测评ID或学生数据', null);
return false;
}
// 构造学生数据
const studentData = students.map(student => ({
evaluationId: evaluationId,
studentId: student.id,
studentNo: student.studentNo,
studentName: student.studentName,
gender: student.gender,
birthDate: student.birthDate,
phone: student.phone
}));
const response = await api.post('/xljk/evaluation/detail/batch', studentData);
const passed = response.data.code === 200;
recordTestResult('批量添加学生', passed,
passed ? `成功批量添加${studentData.length}个学生` : '批量添加学生失败',
response.data);
return passed;
} catch (error) {
recordTestResult('批量添加学生', false, `异常: ${error.message}`, null);
return false;
}
}
/**
* 测试查询测评详情
*/
async function testGetEvaluationDetail(evaluationId) {
try {
console.log('\n===== 测试查询测评详情 =====');
if (!evaluationId) {
recordTestResult('查询测评详情', false, '缺少测评ID', null);
return null;
}
const response = await api.get(`/xljk/evaluation/${evaluationId}`);
const passed = response.data.code === 200 &&
response.data.data &&
response.data.data.id === evaluationId;
recordTestResult('查询测评详情', passed,
passed ? `成功查询测评详情,名称: ${response.data.data.evaluationName}` : '查询测评详情失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('查询测评详情', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试查询测评明细列表
*/
async function testGetEvaluationDetailList(evaluationId) {
try {
console.log('\n===== 测试查询测评明细列表 =====');
if (!evaluationId) {
recordTestResult('查询测评明细列表', false, '缺少测评ID', null);
return null;
}
const response = await api.get(`/xljk/evaluation/detail/evaluation/${evaluationId}`);
const passed = response.data.code === 200 &&
Array.isArray(response.data.data);
if (passed && response.data.data.length > 0) {
detailIds = response.data.data.map(item => item.id);
}
recordTestResult('查询测评明细列表', passed,
passed ? `成功查询测评明细列表,共${response.data.data.length}条` : '查询测评明细列表失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('查询测评明细列表', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试批量更新学生心理状态
*/
async function testBatchUpdateMentalStatus(details) {
try {
console.log('\n===== 测试批量更新学生心理状态 =====');
if (!details || !Array.isArray(details) || details.length === 0) {
recordTestResult('批量更新学生心理状态', false, '缺少测评明细数据', null);
return false;
}
// 构造更新数据,随机分配心理状态
const updateData = details.map((detail, index) => {
const status = (index % 3 + 1).toString();
return {
id: detail.id,
mentalStatus: status,
score: status === '3' ? Math.floor(Math.random() * 30) + 70 : null,
remark: status === '3' ? '需要重点关注' : null
};
});
const response = await api.put('/xljk/evaluation/detail/batch/mentalStatus', updateData);
const passed = response.data.code === 200;
recordTestResult('批量更新学生心理状态', passed,
passed ? `成功批量更新${updateData.length}个学生心理状态` : '批量更新学生心理状态失败',
response.data);
return passed;
} catch (error) {
recordTestResult('批量更新学生心理状态', false, `异常: ${error.message}`, null);
return false;
}
}
/**
* 测试发布测评
*/
async function testPublishEvaluation(evaluationId) {
try {
console.log('\n===== 测试发布测评 =====');
if (!evaluationId) {
recordTestResult('发布测评', false, '缺少测评ID', null);
return false;
}
const response = await api.put(`/xljk/evaluation/${evaluationId}/publish`);
const passed = response.data.code === 200;
recordTestResult('发布测评', passed,
passed ? '成功发布测评' : '发布测评失败',
response.data);
return passed;
} catch (error) {
recordTestResult('发布测评', false, `异常: ${error.message}`, null);
return false;
}
}
/**
* 测试完成测评
*/
async function testCompleteEvaluation(evaluationId) {
try {
console.log('\n===== 测试完成测评 =====');
if (!evaluationId) {
recordTestResult('完成测评', false, '缺少测评ID', null);
return false;
}
const response = await api.put(`/xljk/evaluation/${evaluationId}/complete`);
const passed = response.data.code === 200;
recordTestResult('完成测评', passed,
passed ? '成功完成测评' : '完成测评失败',
response.data);
return passed;
} catch (error) {
recordTestResult('完成测评', false, `异常: ${error.message}`, null);
return false;
}
}
/**
* 测试获取测评统计数据
*/
async function testGetEvaluationStatistics(evaluationId) {
try {
console.log('\n===== 测试获取测评统计数据 =====');
if (!evaluationId) {
recordTestResult('获取测评统计数据', false, '缺少测评ID', null);
return null;
}
const response = await api.get(`/xljk/evaluation/${evaluationId}/statistics`);
const passed = response.data.code === 200 &&
response.data.data &&
response.data.data.totalCount > 0;
recordTestResult('获取测评统计数据', passed,
passed ? `成功获取测评统计数据,总人数: ${response.data.data.totalCount}` : '获取测评统计数据失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('获取测评统计数据', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试获取重点关注学生列表
*/
async function testGetFocusStudents() {
try {
console.log('\n===== 测试获取重点关注学生列表 =====');
const response = await api.get('/xljk/evaluation/detail/focus');
const passed = response.data.code === 200;
recordTestResult('获取重点关注学生列表', passed,
passed ? `成功获取重点关注学生列表,共${response.data.data.total}条` : '获取重点关注学生列表失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('获取重点关注学生列表', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试获取重点关注学生统计
*/
async function testGetFocusStudentsStatistics() {
try {
console.log('\n===== 测试获取重点关注学生统计 =====');
const response = await api.get('/xljk/evaluation/statistics/focus');
const passed = response.data.code === 200 &&
response.data.data;
recordTestResult('获取重点关注学生统计', passed,
passed ? `成功获取重点关注学生统计,总关注率: ${response.data.data.totalFocusRate}%` : '获取重点关注学生统计失败',
response.data);
return passed ? response.data.data : null;
} catch (error) {
recordTestResult('获取重点关注学生统计', false, `异常: ${error.message}`, null);
return null;
}
}
/**
* 测试删除测评
*/
async function testDeleteEvaluation(evaluationId) {
try {
console.log('\n===== 测试删除测评 =====');
if (!evaluationId) {
recordTestResult('删除测评', false, '缺少测评ID', null);
return false;
}
const response = await api.delete(`/xljk/evaluation/${evaluationId}`);
// 注意:由于测评已完成,删除应该会失败,这是符合预期的
const expectedFail = response.data.code !== 200;
recordTestResult('删除测评', expectedFail,
expectedFail ? '测评已完成,无法删除,符合预期' : '错误地成功删除了已完成的测评',
response.data);
return expectedFail;
} catch (error) {
// 如果是因为业务规则导致的错误(如已完成的测评不能删除),也算测试通过
const isBusinessError = error.response && error.response.data && error.response.data.code !== 200;
recordTestResult('删除测评', isBusinessError,
isBusinessError ? '测评已完成,无法删除,符合预期' : `异常: ${error.message}`,
error.response ? error.response.data : null);
return isBusinessError;
}
}
/**
* 输出测试报告
*/
function printTestReport() {
console.log('\n===== 测试报告 =====');
console.log(`总测试数: ${testResults.total}`);
console.log(`通过数: ${testResults.passed}`);
console.log(`失败数: ${testResults.failed}`);
console.log(`跳过数: ${testResults.skipped}`);
console.log(`通过率: ${Math.round(testResults.passed / testResults.total * 100)}%`);
// 将测试结果写入文件
const reportFile = `test_report_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
fs.writeFileSync(reportFile, JSON.stringify(testResults, null, 2));
console.log(`测试报告已保存到: ${reportFile}`);
}
/**
* 主测试流程
*/
async function runTests() {
console.log('===== 心理健康测评API测试开始 =====');
// 登录获取token
const loginSuccess = await login();
if (!loginSuccess) {
console.error('登录失败,无法继续测试');
return;
}
// 获取选项数据
const majorAndClassOptions = await testGetMajorAndClassOptions();
const semesterOptions = await testGetSemesterOptions();
if (!majorAndClassOptions || !semesterOptions) {
console.error('获取基础数据失败,无法继续测试');
return;
}
const options = {
majors: majorAndClassOptions.majors,
classes: majorAndClassOptions.classes,
semesters: semesterOptions
};
// 创建测评
const evaluationInfo = await testCreateEvaluation(options);
if (!evaluationInfo) {
console.error('创建测评失败,无法继续测试');
return;
}
// 获取学生信息
const students = await testGetStudents(evaluationInfo.classId);
if (!students) {
console.error('获取学生信息失败,无法继续测试');
return;
}
// 批量添加学生
const addSuccess = await testBatchAddStudents(evaluationInfo.id, students);
if (!addSuccess) {
console.error('批量添加学生失败,无法继续测试');
return;
}
// 查询测评详情
await testGetEvaluationDetail(evaluationInfo.id);
// 查询测评明细列表
const details = await testGetEvaluationDetailList(evaluationInfo.id);
if (!details) {
console.error('查询测评明细列表失败,无法继续测试');
return;
}
// 批量更新学生心理状态
const updateSuccess = await testBatchUpdateMentalStatus(details);
if (!updateSuccess) {
console.error('批量更新学生心理状态失败,无法继续测试');
return;
}
// 发布测评
const publishSuccess = await testPublishEvaluation(evaluationInfo.id);
if (!publishSuccess) {
console.error('发布测评失败,无法继续测试');
return;
}
// 完成测评
const completeSuccess = await testCompleteEvaluation(evaluationInfo.id);
if (!completeSuccess) {
console.error('完成测评失败,无法继续测试');
return;
}
// 获取测评统计数据
await testGetEvaluationStatistics(evaluationInfo.id);
// 获取重点关注学生列表
await testGetFocusStudents();
// 获取重点关注学生统计
await testGetFocusStudentsStatistics();
// 测试删除测评(应该失败,因为已完成)
await testDeleteEvaluation(evaluationInfo.id);
// 输出测试报告
printTestReport();
console.log('===== 心理健康测评API测试结束 =====');
}
// 运行测试
runTests().catch(error => {
console.error('测试过程中发生异常:', error);
printTestReport();
});
+191
View File
@@ -0,0 +1,191 @@
import request from '@/utils/request';
import {
XljkEvaluationVO,
XljkEvaluationForm,
XljkEvaluationQuery,
XljkEvaluationDetailVO,
XljkEvaluationDetailForm,
XljkEvaluationDetailQuery,
CollegeOption,
MajorOption,
ClassOption,
StudentInfo
} from './types';
// 查询心理健康测评列表
export const listEvaluation = (query: XljkEvaluationQuery) => {
return request({
url: '/xljk/evaluation/list',
method: 'get',
params: query
});
};
// 查询心理健康测评详细信息
export const getEvaluation = (id: string | number) => {
return request({
url: `/xljk/evaluation/${id}`,
method: 'get'
});
};
// 新增心理健康测评
export const addEvaluation = (data: XljkEvaluationForm) => {
// 确保必填字段都有值,避免数据库插入错误
if (data.departmentName && !data.departmentId) {
data.departmentId = 0; // 使用临时ID,后端会根据名称处理
}
if (data.majorName && !data.majorId) {
data.majorId = 0; // 使用临时ID,后端会根据名称处理
}
if (data.className && !data.classId) {
data.classId = 0; // 使用临时ID,后端会根据名称处理
}
return request({
url: '/xljk/evaluation',
method: 'post',
data: data
});
};
// 修改心理健康测评
export const updateEvaluation = (data: XljkEvaluationForm) => {
return request({
url: '/xljk/evaluation',
method: 'put',
data: data
});
};
// 删除心理健康测评
export const delEvaluation = (ids: string | number | Array<string | number>) => {
return request({
url: `/xljk/evaluation/${typeof ids === 'object' ? ids.join(',') : ids}`,
method: 'delete'
});
};
// 获取院系选项
export const getDepartmentOptions = () => {
return request({
url: '/xljk/evaluation/options/department',
method: 'get'
});
};
// 获取院系选项(新接口)
export const getCollegeOptions = () => {
return request({
url: '/xljk/evaluation/options/colleges',
method: 'get'
});
};
// 获取专业选项
export const getMajorOptions = (collegeName?: string) => {
return request({
url: '/xljk/evaluation/options/majors',
method: 'get',
params: { collegeName }
});
};
// 获取班级选项
export const getClassOptions = (collegeName?: string, majorName?: string) => {
return request({
url: '/xljk/evaluation/options/classes',
method: 'get',
params: { collegeName, majorName }
});
};
// 获取班级学生列表
export const getStudentsByClass = (collegeName?: string, majorName?: string, className?: string) => {
return request({
url: '/xljk/evaluation/students',
method: 'get',
params: { collegeName, majorName, className }
});
};
// 获取学期选项
export const getSemesterOptions = () => {
return request({
url: '/xljk/evaluation/options/semester',
method: 'get'
});
};
// 发布测评
export const publishEvaluation = (id: string | number) => {
return request({
url: `/xljk/evaluation/${id}/publish`,
method: 'put'
});
};
// 完成测评
export const completeEvaluation = (id: string | number) => {
return request({
url: `/xljk/evaluation/${id}/complete`,
method: 'put'
});
};
// 获取测评统计数据
export const getEvaluationStatistics = (id: string | number) => {
return request({
url: `/xljk/evaluation/${id}/statistics`,
method: 'get'
});
};
// 获取重点关注学生统计
export const getFocusStudentsStatistics = (query: XljkEvaluationQuery) => {
return request({
url: '/xljk/evaluation/statistics/focus',
method: 'get',
params: query
});
};
// 查询测评明细列表
export const listEvaluationDetail = (query: XljkEvaluationDetailQuery) => {
return request({
url: '/xljk/evaluation/detail/list',
method: 'get',
params: query
});
};
// 获取测评明细列表
export const getEvaluationDetailsByEvaluationId = (evaluationId: string | number) => {
return request({
url: `/xljk/evaluation/detail/evaluation/${evaluationId}`,
method: 'get'
});
};
// 批量新增测评明细
export const batchAddEvaluationDetails = (data: XljkEvaluationDetailForm[]) => {
return request({
url: '/xljk/evaluation/detail/batch',
method: 'post',
data: data
});
};
// 批量更新学生心理状态
export const batchUpdateMentalStatus = (data: XljkEvaluationDetailForm[]) => {
// 确保数据中的ID字段保持原有格式,不做Number转换
console.log('批量更新数据 - 第一条记录evaluationId:', data[0]?.evaluationId);
return request({
url: '/xljk/evaluation/detail/batch/mentalStatus',
method: 'put',
data: data
});
};
+513
View File
@@ -0,0 +1,513 @@
export interface XljkEvaluationVO {
/**
* 主键ID
*/
id: string | number;
/**
* 院系ID
*/
departmentId: number;
/**
* 院系名称
*/
departmentName: string;
/**
* 测评名称
*/
evaluationName: string;
/**
* 专业ID
*/
majorId: number;
/**
* 专业名称
*/
majorName: string;
/**
* 班级ID
*/
classId: number;
/**
* 班级名称
*/
className: string;
/**
* 学期
*/
semester: string;
/**
* 状态(0-草稿,1-已发布,2-已完成)
*/
status: string;
/**
* 创建者ID(班主任ID)
*/
createBy: number;
/**
* 创建者姓名
*/
createName: string;
/**
* 创建时间
*/
createTime: string;
/**
* 更新者
*/
updateBy: number;
/**
* 更新时间
*/
updateTime: string;
/**
* 备注
*/
remark: string;
/**
* 学生总数
*/
studentCount?: number;
/**
* 重点关注学生数
*/
focusCount?: number;
}
export interface XljkEvaluationForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number;
/**
* 院系ID
*/
departmentId?: number;
/**
* 院系名称
*/
departmentName?: string;
/**
* 测评名称
*/
evaluationName?: string;
/**
* 专业ID
*/
majorId?: number;
/**
* 专业名称
*/
majorName?: string;
/**
* 班级ID
*/
classId?: number;
/**
* 班级名称
*/
className?: string;
/**
* 学期
*/
semester?: string;
/**
* 状态(0-草稿,1-已发布,2-已完成)
*/
status?: string;
/**
* 创建者姓名
*/
createName?: string;
/**
* 备注
*/
remark?: string;
}
export interface XljkEvaluationQuery extends PageQuery {
/**
* 院系ID
*/
departmentId?: number;
/**
* 院系名称
*/
departmentName?: string;
/**
* 测评名称
*/
evaluationName?: string;
/**
* 专业ID
*/
majorId?: number;
/**
* 专业名称
*/
majorName?: string;
/**
* 班级ID
*/
classId?: number;
/**
* 班级名称
*/
className?: string;
/**
* 学期
*/
semester?: string;
/**
* 状态
*/
status?: string;
/**
* 日期范围参数
*/
params?: any;
}
export interface XljkEvaluationDetailVO {
/**
* 主键ID
*/
id: string | number;
/**
* 测评表ID
*/
evaluationId: number;
/**
* 测评名称
*/
evaluationName?: string;
/**
* 学生ID
*/
studentId: number;
/**
* 学号
*/
studentNo: string;
/**
* 学生姓名
*/
studentName: string;
/**
* 性别(M-男,F-女)
*/
gender: string;
/**
* 出生日期
*/
birthDate: string;
/**
* 联系电话
*/
phone: string;
/**
* 心理状态(1-健康,2-良好,3-重点关注)
*/
mentalStatus: string;
/**
* 分数
*/
score?: number;
/**
* 创建者
*/
createBy: number;
/**
* 创建时间
*/
createTime: string;
/**
* 更新者
*/
updateBy: number;
/**
* 更新时间
*/
updateTime: string;
/**
* 备注
*/
remark: string;
/**
* 班级名称
*/
className?: string;
/**
* 专业名称
*/
majorName?: string;
/**
* 学期
*/
semester?: string;
}
export interface XljkEvaluationDetailForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number;
/**
* 测评表ID
*/
evaluationId?: string | number; // 支持字符串类型的ID
/**
* 学生ID
*/
studentId?: number;
/**
* 学号
*/
studentNo?: string;
/**
* 学生姓名
*/
studentName?: string;
/**
* 性别(M-男,F-女)
*/
gender?: string;
/**
* 出生日期
*/
birthDate?: string;
/**
* 联系电话
*/
phone?: string;
/**
* 心理状态(1-健康,2-良好,3-重点关注)
*/
mentalStatus?: string;
/**
* 分数
*/
score?: number;
/**
* 备注
*/
remark?: string;
}
export interface XljkEvaluationDetailQuery extends PageQuery {
/**
* 测评表ID
*/
evaluationId?: number;
/**
* 学生ID
*/
studentId?: number;
/**
* 学号
*/
studentNo?: string;
/**
* 学生姓名
*/
studentName?: string;
/**
* 心理状态
*/
mentalStatus?: string;
/**
* 日期范围参数
*/
params?: any;
}
export interface StudentInfo {
/**
* 学生ID
*/
studentId: number;
/**
* 学号
*/
studentNo: string;
/**
* 学生姓名
*/
studentName: string;
/**
* 性别(M-男,F-女)
*/
gender: string;
/**
* 出生日期
*/
birthDate: string;
/**
* 联系电话
*/
phone: string;
/**
* 心理状态(1-健康,2-良好,3-重点关注)
*/
mentalStatus?: string;
/**
* 分数,当心理状态为"重点关注"时需要填写
*/
score?: number;
/**
* 测评明细ID,已有测评数据时存在
*/
detailId?: number;
/**
* 备注
*/
remark?: string;
}
export interface CollegeOption {
/**
* 院系名称
*/
name: string;
}
export interface MajorOption {
/**
* 专业名称
*/
name: string;
}
export interface ClassOption {
/**
* 班级名称
*/
name: string;
}
export interface SemesterOption {
/**
* 学期值
*/
value: string;
/**
* 学期名称
*/
label: string;
}
export interface EvaluationStatistics {
/**
* 学生总数
*/
totalStudents: number;
/**
* 健康学生数
*/
healthyCount: number;
/**
* 良好学生数
*/
goodCount: number;
/**
* 重点关注学生数
*/
focusCount: number;
/**
* 健康学生百分比
*/
healthyPercent: number;
/**
* 良好学生百分比
*/
goodPercent: number;
/**
* 重点关注学生百分比
*/
focusPercent: number;
}
+47 -1
View File
@@ -89,10 +89,56 @@ export const constantRoutes: RouteRecordRaw[] = [
}
]
},
{
path: '/xljk',
component: Layout,
hidden: true,
children: [
{
path: 'evaluation/detail/:id',
component: () => import('@/views/xljk/evaluation/detail.vue'),
name: 'XljkEvaluationDetail',
meta: { title: '心理健康测评详情', icon: 'form', activeMenu: '/xljk/evaluation' }
},
{
path: 'evaluation/add',
component: () => import('@/views/xljk/evaluation/add.vue'),
name: 'XljkEvaluationAdd',
meta: { title: '新增心理健康测评', icon: 'form', activeMenu: '/xljk/evaluation' }
},
{
path: 'evaluation/edit/:id',
component: () => import('@/views/xljk/evaluation/edit.vue'),
name: 'XljkEvaluationEdit',
meta: { title: '编辑心理健康测评', icon: 'form', activeMenu: '/xljk/evaluation' }
},
{
path: 'evaluation/fill/:id',
component: () => import('@/views/xljk/evaluation/fill.vue'),
name: 'XljkEvaluationFill',
meta: { title: '填写心理健康测评', icon: 'form', activeMenu: '/xljk/evaluation' }
}
]
}
];
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes: RouteRecordRaw[] = [];
export const dynamicRoutes: RouteRecordRaw[] = [
{
path: '/xljk',
component: Layout,
name: 'Xljk',
meta: { title: '学工管理', icon: 'education' },
children: [
{
path: 'evaluation',
component: () => import('@/views/xljk/evaluation/index.vue'),
name: 'XljkEvaluation',
meta: { title: '心理健康测评', icon: 'heart', permissions: ['xljk:evaluation:list'] }
}
]
}
];
/**
* 创建路由
+457
View File
@@ -0,0 +1,457 @@
<template>
<div class="p-2 page-container">
<el-card shadow="hover" class="mb-[10px]">
<template #header>
<div class="card-header">
<span class="header-title">新增心理健康测评</span>
</div>
</template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="测评名称" prop="evaluationName">
<el-input
v-model="form.evaluationName"
placeholder="请输入测评名称"
style="width: 500px"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="院系" prop="departmentName">
<el-select
v-model="form.departmentName"
placeholder="请选择院系"
style="width: 500px"
@change="handleCollegeChange"
>
<el-option
v-for="college in collegeOptions"
:key="college.name"
:label="college.name"
:value="college.name"
/>
</el-select>
</el-form-item>
<el-form-item label="专业" prop="majorName">
<el-select
v-model="form.majorName"
placeholder="请选择专业"
style="width: 500px"
@change="handleMajorChange"
:disabled="!form.departmentName"
>
<el-option
v-for="major in majorOptions"
:key="major.name"
:label="major.name"
:value="major.name"
/>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="className">
<el-select
v-model="form.className"
placeholder="请选择班级"
style="width: 500px"
@change="handleClassChange"
:disabled="!form.majorName"
>
<el-option
v-for="cls in classOptions"
:key="cls.name"
:label="cls.name"
:value="cls.name"
/>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="semester">
<el-input
v-model="form.semester"
placeholder="请输入学期,如:2024上学期"
style="width: 500px"
maxlength="50"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注信息"
style="width: 500px"
:rows="4"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item>
<div class="form-buttons">
<el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="hover" class="mb-[10px]" v-if="form.className">
<template #header>
<div class="card-header">
<span class="header-title">班级学生信息</span>
<div class="header-buttons">
<el-tag type="success">创建测评后将自动加载这些学生信息</el-tag>
</div>
</div>
</template>
<div v-loading="studentsLoading">
<el-alert
v-if="studentList.length === 0 && !studentsLoading"
title="未找到该班级的学生信息"
type="warning"
show-icon
:closable="false"
/>
<!-- 学生列表表格 -->
<el-table
v-else
:data="studentList"
border
stripe
style="width: 100%"
>
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
<el-table-column label="姓名" align="center" prop="studentName" width="100" />
<el-table-column label="性别" align="center" prop="gender" width="80">
<template #default="scope">
<el-tag type="info" v-if="scope.row.gender === 'M'">男</el-tag>
<el-tag type="danger" v-else-if="scope.row.gender === 'F'">女</el-tag>
<span v-else>未知</span>
</template>
</el-table-column>
<el-table-column label="出生日期" align="center" prop="birthDate" width="120">
<template #default="scope">
{{ parseTime(scope.row.birthDate, '{y}-{m}-{d}') }}
</template>
</el-table-column>
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
<!-- 心理状态选择 -->
<el-table-column label="心理状态" align="center" width="180">
<template #default="scope">
<el-select
v-model="scope.row.mentalStatus"
placeholder="请选择心理状态"
@change="handleMentalStatusChange(scope.row)"
>
<el-option label="健康" value="1" />
<el-option label="良好" value="2" />
<el-option label="重点关注" value="3" />
</el-select>
</template>
</el-table-column>
<!-- 分数输入,仅当心理状态为"重点关注"时显示 -->
<el-table-column label="分数" align="center" width="120">
<template #default="scope">
<el-input-number
v-if="scope.row.mentalStatus === '3'"
v-model="scope.row.score"
:min="0"
:max="100"
size="small"
/>
<span v-else>-</span>
</template>
</el-table-column>
<!-- 备注 -->
<el-table-column label="备注" align="center">
<template #default="scope">
<el-input
v-model="scope.row.remark"
type="text"
placeholder="备注信息"
size="small"
/>
</template>
</el-table-column>
</el-table>
<div class="student-count" v-if="studentList.length > 0">
共 {{ studentList.length }} 名学生
</div>
</div>
</el-card>
</div>
</template>
<script setup name="XljkEvaluationAdd" lang="ts">
import { addEvaluation, getCollegeOptions, getMajorOptions, getClassOptions, getStudentsByClass, batchAddEvaluationDetails } from '@/api/xljk/evaluation';
import { XljkEvaluationForm, StudentInfo, CollegeOption, MajorOption, ClassOption } from '@/api/xljk/evaluation/types';
import { useRouter } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const formRef = ref<ElFormInstance>();
const submitLoading = ref(false);
const studentsLoading = ref(false);
// 表单数据
const form = ref<XljkEvaluationForm>({
evaluationName: undefined,
departmentName: undefined,
departmentId: undefined, // 这里保留departmentId字段,但实际使用departmentName
majorName: undefined,
majorId: undefined, // 这里保留majorId字段,但实际使用majorName
className: undefined,
classId: undefined, // 这里保留classId字段,但实际使用className
semester: undefined,
status: '0', // 默认为草稿状态
remark: undefined
});
// 表单验证规则
const rules = {
evaluationName: [{ required: true, message: '测评名称不能为空', trigger: 'blur' }],
departmentName: [{ required: true, message: '院系不能为空', trigger: 'change' }],
majorName: [{ required: true, message: '专业不能为空', trigger: 'change' }],
className: [{ required: true, message: '班级不能为空', trigger: 'change' }],
semester: [{ required: true, message: '学期不能为空', trigger: 'blur' }]
};
// 选项数据
const collegeOptions = ref<CollegeOption[]>([]);
const majorOptions = ref<MajorOption[]>([]);
const classOptions = ref<ClassOption[]>([]);
const studentList = ref<StudentInfo[]>([]);
/** 获取院系选项 */
const getColleges = async () => {
try {
const res = await getCollegeOptions();
collegeOptions.value = res.data || [];
} catch (error) {
console.error('获取院系选项失败', error);
}
};
/** 获取专业选项 */
const getMajors = async (collegeName: string) => {
try {
const res = await getMajorOptions(collegeName);
majorOptions.value = res.data || [];
} catch (error) {
console.error('获取专业选项失败', error);
}
};
/** 获取班级选项 */
const getClasses = async (collegeName: string, majorName: string) => {
try {
const res = await getClassOptions(collegeName, majorName);
classOptions.value = res.data || [];
} catch (error) {
console.error('获取班级选项失败', error);
}
};
/** 院系选择变化时更新专业选项 */
const handleCollegeChange = async (collegeName: string) => {
// 清空专业和班级选择
form.value.majorName = undefined;
form.value.className = undefined;
majorOptions.value = [];
classOptions.value = [];
studentList.value = [];
if (collegeName) {
// 获取该院系下的专业
await getMajors(collegeName);
}
};
/** 专业选择变化时更新班级选项 */
const handleMajorChange = async (majorName: string) => {
// 清空班级选择
form.value.className = undefined;
classOptions.value = [];
studentList.value = [];
if (majorName && form.value.departmentName) {
// 获取专业下的班级
await getClasses(form.value.departmentName, majorName);
}
};
/** 班级选择变化时获取学生列表 */
const handleClassChange = async (className: string) => {
if (!className) {
studentList.value = [];
return;
}
// 获取班级学生列表
try {
studentsLoading.value = true;
const res = await getStudentsByClass(form.value.departmentName, form.value.majorName, className);
studentList.value = res.data || [];
} catch (error) {
console.error('获取班级学生列表失败', error);
studentList.value = [];
} finally {
studentsLoading.value = false;
}
};
/** 处理心理状态变化 */
const handleMentalStatusChange = (student: StudentInfo) => {
// 如果心理状态不是"重点关注",清空分数
if (student.mentalStatus !== '3') {
student.score = undefined;
} else {
// 设置默认分数
student.score = student.score || 70;
}
};
/** 统计重点关注学生数量 */
const countFocusStudents = () => {
return studentList.value.filter(student => student.mentalStatus === '3').length;
};
/** 提交表单 */
const submitForm = () => {
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true;
try {
// 确保departmentName、majorName和className有值
if (!form.value.departmentName && form.value.departmentId) {
// 找不到departmentName但有departmentId的情况处理
proxy?.$modal.msgError('院系名称不能为空');
submitLoading.value = false;
return;
}
if (!form.value.majorName && form.value.majorId) {
// 找不到majorName但有majorId的情况处理
proxy?.$modal.msgError('专业名称不能为空');
submitLoading.value = false;
return;
}
if (!form.value.className && form.value.classId) {
// 找不到className但有classId的情况处理
proxy?.$modal.msgError('班级名称不能为空');
submitLoading.value = false;
return;
}
// 确保ID字段有值,即使是临时值
if (form.value.departmentName && !form.value.departmentId) {
form.value.departmentId = 0; // 临时ID,后端会基于departmentName处理
}
if (form.value.majorName && !form.value.majorId) {
form.value.majorId = 0; // 临时ID,后端会基于majorName处理
}
if (form.value.className && !form.value.classId) {
form.value.classId = 0; // 临时ID,后端会基于className处理
}
// 先创建测评
const res = await addEvaluation(form.value);
const evaluationId = res.data;
if (evaluationId) {
// 统计学生人数和重点关注学生数量
const studentCount = studentList.value.length;
const focusCount = countFocusStudents();
// 准备学生测评明细数据
const detailList = studentList.value.map(student => {
return {
evaluationId,
studentId: student.studentId,
studentNo: student.studentNo,
studentName: student.studentName,
gender: student.gender,
birthDate: student.birthDate,
phone: student.phone,
mentalStatus: student.mentalStatus || '1', // 默认健康
score: student.mentalStatus === '3' ? student.score : undefined,
remark: student.remark
};
});
// 批量创建测评明细
await batchAddEvaluationDetails(detailList);
// 更新测评主表的学生数量和重点关注学生数量
// 注:这部分逻辑可能需要在后端自动处理
proxy?.$modal.msgSuccess('新增成功');
router.push('/xljk/evaluation');
} else {
proxy?.$modal.msgError('新增测评失败');
}
} catch (error) {
console.error('新增测评失败', error);
} finally {
submitLoading.value = false;
}
}
});
};
/** 取消按钮 */
const cancel = () => {
router.push('/xljk/evaluation');
};
onMounted(() => {
getColleges();
});
</script>
<style scoped>
.page-container {
padding: 8px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.header-buttons {
display: flex;
gap: 10px;
}
.form-buttons {
margin-top: 10px;
}
.student-count {
margin-top: 10px;
text-align: right;
color: #606266;
font-size: 14px;
}
</style>
+446
View File
@@ -0,0 +1,446 @@
<template>
<div class="p-2 page-container">
<el-card shadow="hover" class="mb-[10px]">
<template #header>
<div class="card-header">
<span class="header-title">测评基本信息</span>
<div class="header-buttons">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="handleExport" v-hasPermi="['xljk:evaluation:export']">导出结果</el-button>
</div>
</div>
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="测评名称">{{ evaluationInfo.evaluationName }}</el-descriptions-item>
<el-descriptions-item label="专业">{{ evaluationInfo.majorName }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ evaluationInfo.className }}</el-descriptions-item>
<el-descriptions-item label="学期">{{ evaluationInfo.semester }}</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :options="evaluation_status" :value="evaluationInfo.status" />
</el-descriptions-item>
<el-descriptions-item label="创建人">{{ evaluationInfo.createName }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(evaluationInfo.createTime) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ evaluationInfo.remark || '无' }}</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- 统计信息卡片 -->
<el-card shadow="hover" class="mb-[10px]" v-loading="statisticsLoading">
<template #header>
<div class="card-header">
<span class="header-title">测评统计</span>
</div>
</template>
<div class="statistics-container">
<div class="statistics-row">
<el-statistic title="学生总数" :value="statistics.totalStudents">
<template #suffix>
<span class="suffix-label">人</span>
</template>
</el-statistic>
<el-statistic title="健康学生" :value="statistics.healthyCount">
<template #suffix>
<span class="suffix-label">人 ({{ formatPercent(statistics.healthyPercent) }})</span>
</template>
</el-statistic>
<el-statistic title="良好学生" :value="statistics.goodCount">
<template #suffix>
<span class="suffix-label">人 ({{ formatPercent(statistics.goodPercent) }})</span>
</template>
</el-statistic>
<el-statistic title="重点关注" :value="statistics.focusCount" value-style="color: #f56c6c;">
<template #suffix>
<span class="suffix-label" style="color: #f56c6c;">人 ({{ formatPercent(statistics.focusPercent) }})</span>
</template>
</el-statistic>
</div>
<!-- 图表展示 -->
<div class="chart-container" v-if="statistics.totalStudents > 0">
<div ref="pieChartRef" class="pie-chart"></div>
</div>
</div>
</el-card>
<!-- 学生测评结果表格 -->
<el-card shadow="hover" class="mb-[10px]">
<template #header>
<div class="card-header">
<span class="header-title">学生测评结果</span>
<div class="header-right">
<el-radio-group v-model="filterStatus" size="small" @change="handleFilterChange">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="1">健康</el-radio-button>
<el-radio-button label="2">良好</el-radio-button>
<el-radio-button label="3">重点关注</el-radio-button>
</el-radio-group>
<el-input
v-model="searchKeyword"
placeholder="搜索学号/姓名"
clearable
style="width: 200px; margin-left: 10px"
@keyup.enter="handleSearch"
@clear="handleSearch"
>
<template #suffix>
<el-icon class="el-input__icon" @click="handleSearch"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
</template>
<el-table
v-loading="loading"
border
:data="detailList"
stripe
style="width: 100%"
>
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
<el-table-column label="姓名" align="center" prop="studentName" width="100" />
<el-table-column label="性别" align="center" prop="gender" width="80">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
<el-table-column label="心理状态" align="center" prop="mentalStatus" width="100">
<template #default="scope">
<el-tag
:type="scope.row.mentalStatus === '1' ? 'success' :
scope.row.mentalStatus === '2' ? 'warning' : 'danger'"
>
{{ scope.row.mentalStatus === '1' ? '健康' :
scope.row.mentalStatus === '2' ? '良好' : '重点关注' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="分数" align="center" prop="score" width="80">
<template #default="scope">
<span v-if="scope.row.score">{{ scope.row.score }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="备注" align="left" prop="remark" min-width="200" show-overflow-tooltip />
</el-table>
<div class="pagination-container">
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getDetailList"
/>
</div>
</el-card>
</div>
</template>
<script setup name="XljkEvaluationDetail" lang="ts">
import { getEvaluation, getEvaluationStatistics, listEvaluationDetail, getEvaluationDetailsByEvaluationId } from '@/api/xljk/evaluation';
import { XljkEvaluationVO, XljkEvaluationDetailVO, XljkEvaluationDetailQuery, EvaluationStatistics } from '@/api/xljk/evaluation/types';
import { useRoute, useRouter } from 'vue-router';
import * as echarts from 'echarts/core';
import { PieChart } from 'echarts/charts';
import { TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
// 注册必要的组件
echarts.use([PieChart, TooltipComponent, LegendComponent, TitleComponent, CanvasRenderer]);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { evaluation_status, sys_user_sex } = toRefs<any>(proxy?.useDict('evaluation_status', 'sys_user_sex'));
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const loading = ref(false);
const statisticsLoading = ref(false);
const evaluationInfo = ref<XljkEvaluationVO>({} as XljkEvaluationVO);
const statistics = ref<EvaluationStatistics>({
totalStudents: 0,
healthyCount: 0,
goodCount: 0,
focusCount: 0,
healthyPercent: 0,
goodPercent: 0,
focusPercent: 0
});
const detailList = ref<XljkEvaluationDetailVO[]>([]);
const total = ref(0);
const filterStatus = ref('');
const searchKeyword = ref('');
const pieChartRef = ref<HTMLElement | null>(null);
let pieChart: echarts.ECharts | null = null;
const queryParams = ref<XljkEvaluationDetailQuery>({
pageNum: 1,
pageSize: 10,
evaluationId: Number(evaluationId.value),
mentalStatus: '',
studentName: '',
studentNo: '',
params: {}
});
/** 获取测评基本信息 */
const getEvaluationInfo = async () => {
try {
const res = await getEvaluation(evaluationId.value);
evaluationInfo.value = res.data;
} catch (error) {
console.error('获取测评信息失败', error);
}
};
/** 获取测评统计信息 */
const getStatistics = async () => {
try {
statisticsLoading.value = true;
const res = await getEvaluationStatistics(evaluationId.value);
statistics.value = res.data;
// 初始化饼图
initPieChart();
} catch (error) {
console.error('获取测评统计信息失败', error);
} finally {
statisticsLoading.value = false;
}
};
/** 获取测评明细列表 */
const getDetailList = async () => {
try {
loading.value = true;
// 使用getEvaluationDetailsByEvaluationId替代listEvaluationDetail
// 因为listEvaluationDetail需要分页参数,而我们需要的是获取指定测评ID的所有明细
const res = await getEvaluationDetailsByEvaluationId(evaluationId.value);
if (res.data) {
detailList.value = res.data;
total.value = res.data.length;
} else {
detailList.value = [];
total.value = 0;
}
} catch (error) {
console.error('获取测评明细失败', error);
detailList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/** 初始化饼图 */
const initPieChart = () => {
if (!pieChartRef.value) return;
// 销毁之前的图表实例
if (pieChart) {
pieChart.dispose();
}
// 创建新的图表实例
pieChart = echarts.init(pieChartRef.value);
const option = {
title: {
text: '心理状态分布',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: ['健康', '良好', '重点关注']
},
color: ['#67C23A', '#E6A23C', '#F56C6C'],
series: [
{
name: '心理状态',
type: 'pie',
radius: '60%',
center: ['50%', '50%'],
data: [
{ value: statistics.value.healthyCount, name: '健康' },
{ value: statistics.value.goodCount, name: '良好' },
{ value: statistics.value.focusCount, name: '重点关注' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
pieChart.setOption(option);
// 监听窗口大小变化,重新调整图表大小
window.addEventListener('resize', () => {
pieChart?.resize();
});
};
/** 格式化百分比 */
const formatPercent = (value: number) => {
return value ? `${value.toFixed(1)}%` : '0%';
};
/** 返回列表页 */
const goBack = () => {
router.push('/xljk/evaluation');
};
/** 导出结果 */
const handleExport = () => {
proxy?.download(
'xljk/evaluationDetail/export',
{
evaluationId: evaluationId.value
},
`evaluation_detail_${evaluationId.value}_${new Date().getTime()}.xlsx`
);
};
/** 处理筛选变化 */
const handleFilterChange = () => {
queryParams.value.pageNum = 1;
queryParams.value.mentalStatus = filterStatus.value;
getDetailList();
};
/** 处理搜索 */
const handleSearch = () => {
queryParams.value.pageNum = 1;
// 判断输入是否为数字(学号)或文本(姓名)
const isNumeric = /^\d+$/.test(searchKeyword.value);
if (isNumeric) {
queryParams.value.studentNo = searchKeyword.value;
queryParams.value.studentName = '';
} else {
queryParams.value.studentName = searchKeyword.value;
queryParams.value.studentNo = '';
}
getDetailList();
};
onMounted(() => {
getEvaluationInfo();
getStatistics();
getDetailList();
});
onBeforeUnmount(() => {
// 销毁图表实例
if (pieChart) {
pieChart.dispose();
pieChart = null;
}
// 移除事件监听
window.removeEventListener('resize', () => {
pieChart?.resize();
});
});
</script>
<style scoped>
.page-container {
padding: 8px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.header-buttons {
display: flex;
gap: 10px;
}
.header-right {
display: flex;
align-items: center;
}
/* 统计样式 */
.statistics-container {
padding: 10px 0;
}
.statistics-row {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
gap: 20px;
margin-bottom: 20px;
}
.suffix-label {
font-size: 12px;
color: #606266;
margin-left: 4px;
}
.chart-container {
display: flex;
justify-content: center;
margin-top: 20px;
margin-bottom: 10px;
}
.pie-chart {
width: 600px;
height: 400px;
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 10px !important;
}
@media (max-width: 768px) {
.statistics-row {
flex-direction: column;
align-items: center;
}
.pie-chart {
width: 100%;
height: 300px;
}
}
</style>
+373
View File
@@ -0,0 +1,373 @@
<template>
<div class="p-2 page-container">
<el-card shadow="hover" class="mb-[10px]">
<template #header>
<div class="card-header">
<span class="header-title">编辑心理健康测评</span>
</div>
</template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="测评名称" prop="evaluationName">
<el-input
v-model="form.evaluationName"
placeholder="请输入测评名称"
style="width: 500px"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="专业" prop="majorId">
<el-input
v-model="form.majorName"
disabled
style="width: 500px"
/>
</el-form-item>
<el-form-item label="班级" prop="classId">
<el-input
v-model="form.className"
disabled
style="width: 500px"
/>
</el-form-item>
<el-form-item label="学期" prop="semester">
<el-input
v-model="form.semester"
disabled
style="width: 500px"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注信息"
style="width: 500px"
:rows="4"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item>
<div class="form-buttons">
<el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="hover" class="mb-[10px]">
<template #header>
<div class="card-header">
<span class="header-title">班级学生信息</span>
<div class="header-buttons">
<el-tag type="warning">专业、班级和学期不可修改</el-tag>
</div>
</div>
</template>
<div v-loading="studentsLoading">
<el-alert
v-if="studentList.length === 0 && !studentsLoading"
title="未找到该班级的学生信息"
type="warning"
show-icon
:closable="false"
/>
<el-table
v-else
:data="studentList"
border
stripe
style="width: 100%"
>
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
<el-table-column label="姓名" align="center" prop="studentName" width="100" />
<el-table-column label="性别" align="center" prop="gender" width="80">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="出生日期" align="center" prop="birthDate" width="120">
<template #default="scope">
{{ parseTime(scope.row.birthDate, '{y}-{m}-{d}') }}
</template>
</el-table-column>
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
<!-- 心理状态选择 -->
<el-table-column label="心理状态" align="center" width="180">
<template #default="scope">
<el-select
v-model="scope.row.mentalStatus"
placeholder="请选择心理状态"
@change="handleMentalStatusChange(scope.row)"
>
<el-option label="健康" value="1" />
<el-option label="良好" value="2" />
<el-option label="重点关注" value="3" />
</el-select>
</template>
</el-table-column>
<!-- 分数输入,仅当心理状态为"重点关注"时显示 -->
<el-table-column label="分数" align="center" width="120">
<template #default="scope">
<el-input-number
v-if="scope.row.mentalStatus === '3'"
v-model="scope.row.score"
:min="0"
:max="100"
size="small"
/>
<span v-else>-</span>
</template>
</el-table-column>
<!-- 备注 -->
<el-table-column label="备注" align="center">
<template #default="scope">
<el-input
v-model="scope.row.remark"
type="text"
placeholder="备注信息"
size="small"
/>
</template>
</el-table-column>
</el-table>
<div class="student-count" v-if="studentList.length > 0">
共 {{ studentList.length }} 名学生
</div>
</div>
</el-card>
</div>
</template>
<script setup name="XljkEvaluationEdit" lang="ts">
import { getEvaluation, updateEvaluation, getStudentsByClass, getEvaluationDetailsByEvaluationId, batchUpdateMentalStatus } from '@/api/xljk/evaluation';
import { XljkEvaluationForm, StudentInfo, XljkEvaluationDetailForm } from '@/api/xljk/evaluation/types';
import { useRoute, useRouter } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const formRef = ref<ElFormInstance>();
const submitLoading = ref(false);
const studentsLoading = ref(false);
const detailsLoaded = ref(false); // 标记是否已加载测评明细
// 表单数据
const form = ref<XljkEvaluationForm>({
id: undefined,
evaluationName: undefined,
departmentId: undefined,
departmentName: undefined,
majorId: undefined,
majorName: undefined,
classId: undefined,
className: undefined,
semester: undefined,
status: undefined,
remark: undefined
});
// 表单验证规则
const rules = {
evaluationName: [{ required: true, message: '测评名称不能为空', trigger: 'blur' }]
};
// 学生列表
const studentList = ref<StudentInfo[]>([]);
// 测评明细列表
const detailList = ref<XljkEvaluationDetailForm[]>([]);
/** 获取测评信息 */
const getEvaluationInfo = async () => {
try {
const res = await getEvaluation(evaluationId.value);
form.value = res.data;
// 检查状态是否为草稿
if (form.value.status !== '0') {
proxy?.$modal.msgError('只能编辑草稿状态的测评');
setTimeout(() => {
router.push('/xljk/evaluation');
}, 1500);
} else {
// 获取班级学生列表
if (form.value.className) {
await getStudents();
// 加载测评明细数据
await getEvaluationDetails();
}
}
} catch (error) {
console.error('获取测评信息失败', error);
}
};
/** 获取班级学生列表 */
const getStudents = async () => {
try {
studentsLoading.value = true;
// 打印参数信息
console.log('获取学生列表的参数:', {
className: form.value.className
});
// 只使用班级名称作为参数,忽略院系和专业,因为数据可能不匹配
const res = await getStudentsByClass(null, null, form.value.className);
// 打印返回结果
console.log('获取学生列表的结果:', res);
studentList.value = res.data || [];
// 为每个学生初始化心理状态
studentList.value.forEach(student => {
student.mentalStatus = student.mentalStatus || '1'; // 默认健康
});
} catch (error) {
console.error('获取班级学生列表失败', error);
studentList.value = [];
} finally {
studentsLoading.value = false;
}
};
/** 获取测评明细数据 */
const getEvaluationDetails = async () => {
try {
const res = await getEvaluationDetailsByEvaluationId(evaluationId.value);
detailList.value = res.data || [];
// 将测评明细数据合并到学生列表中
if (detailList.value.length > 0) {
studentList.value.forEach(student => {
const detail = detailList.value.find(d => d.studentId === student.studentId);
if (detail) {
student.mentalStatus = detail.mentalStatus;
student.score = detail.score;
student.remark = detail.remark;
student.detailId = detail.id as number;
}
});
detailsLoaded.value = true;
}
} catch (error) {
console.error('获取测评明细失败', error);
}
};
/** 处理心理状态变化 */
const handleMentalStatusChange = (student: StudentInfo) => {
// 如果心理状态不是"重点关注",清空分数
if (student.mentalStatus !== '3') {
student.score = undefined;
} else {
// 设置默认分数
student.score = student.score || 70;
}
};
/** 统计重点关注学生数量 */
const countFocusStudents = () => {
return studentList.value.filter(student => student.mentalStatus === '3').length;
};
/** 提交表单 */
const submitForm = () => {
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true;
try {
// 更新测评基本信息
await updateEvaluation(form.value);
// 准备学生测评明细数据
const detailsToUpdate = studentList.value.map(student => {
return {
id: student.detailId, // 已有记录的ID
evaluationId: form.value.id, // 保持ID为原始类型,不做Number转换
studentId: student.studentId,
studentNo: student.studentNo,
studentName: student.studentName,
gender: student.gender,
birthDate: student.birthDate,
phone: student.phone,
mentalStatus: student.mentalStatus || '1', // 默认健康
score: student.mentalStatus === '3' ? student.score : undefined,
remark: student.remark
} as XljkEvaluationDetailForm;
});
// 批量更新学生心理状态
await batchUpdateMentalStatus(detailsToUpdate);
proxy?.$modal.msgSuccess('修改成功');
router.push('/xljk/evaluation');
} catch (error) {
console.error('修改测评失败', error);
} finally {
submitLoading.value = false;
}
}
});
};
/** 取消按钮 */
const cancel = () => {
router.push('/xljk/evaluation');
};
onMounted(() => {
getEvaluationInfo();
});
</script>
<style scoped>
.page-container {
padding: 8px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.header-buttons {
display: flex;
gap: 10px;
}
.form-buttons {
margin-top: 10px;
}
.student-count {
margin-top: 10px;
text-align: right;
color: #606266;
font-size: 14px;
}
</style>
+343
View File
@@ -0,0 +1,343 @@
<template>
<div class="p-2 page-container">
<el-card shadow="hover" class="mb-[10px]">
<template #header>
<div class="card-header">
<span class="header-title">测评填写:{{ evaluationInfo.evaluationName }}</span>
<div class="header-buttons">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="saveEvaluation" :loading="saveLoading">保存测评结果</el-button>
</div>
</div>
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="专业">{{ evaluationInfo.majorName }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ evaluationInfo.className }}</el-descriptions-item>
<el-descriptions-item label="学期">{{ evaluationInfo.semester }}</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :options="evaluation_status" :value="evaluationInfo.status" />
</el-descriptions-item>
<el-descriptions-item label="学生人数">{{ detailList.length }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="hover" v-loading="loading">
<template #header>
<div class="card-header">
<span class="header-title">学生测评结果</span>
<div class="header-right">
<span class="fill-stats">
已填写:<el-tag type="success">{{ filledCount }}</el-tag> / {{ detailList.length }}
</span>
<el-input
v-model="searchKeyword"
placeholder="搜索学号/姓名"
clearable
style="width: 200px; margin-left: 10px"
@keyup.enter="searchStudents"
@clear="searchStudents"
>
<template #suffix>
<el-icon class="el-input__icon" @click="searchStudents"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
</template>
<el-table
ref="tableRef"
:data="filteredDetailList"
border
stripe
:max-height="tableMaxHeight"
:header-cell-style="{ background: '#f5f7fa' }"
>
<el-table-column label="学号" prop="studentNo" width="120" align="center" fixed />
<el-table-column label="姓名" prop="studentName" width="100" align="center" fixed />
<el-table-column label="性别" prop="gender" width="60" align="center">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="联系电话" prop="phone" width="120" align="center" />
<el-table-column label="心理状态" width="200" align="center">
<template #default="scope">
<el-radio-group v-model="scope.row.mentalStatus" @change="handleStatusChange(scope.row)">
<el-radio label="1">健康</el-radio>
<el-radio label="2">良好</el-radio>
<el-radio label="3">重点关注</el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column label="分数" width="150" align="center">
<template #default="scope">
<el-input-number
v-model="scope.row.score"
:disabled="scope.row.mentalStatus !== '3'"
:min="1"
:max="100"
size="small"
placeholder="分数"
@change="handleScoreChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template #default="scope">
<el-input
v-model="scope.row.remark"
type="textarea"
:rows="1"
placeholder="请输入备注信息"
@blur="handleRemarkChange(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<div class="fixed-bottom-bar" v-if="detailList.length > 0">
<el-button type="primary" size="large" @click="saveEvaluation" :loading="saveLoading">保存测评结果</el-button>
</div>
</el-card>
</div>
</template>
<script setup name="XljkEvaluationFill" lang="ts">
import { getEvaluation, listEvaluationDetail, batchUpdateEvaluationDetail } from '@/api/xljk/evaluation';
import { XljkEvaluationVO, XljkEvaluationDetailVO, XljkEvaluationDetailForm } from '@/api/xljk/evaluation/types';
import { useRoute, useRouter } from 'vue-router';
import { throttle } from 'lodash-es';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { evaluation_status, sys_user_sex } = toRefs<any>(proxy?.useDict('evaluation_status', 'sys_user_sex'));
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const loading = ref(false);
const saveLoading = ref(false);
const evaluationInfo = ref<XljkEvaluationVO>({} as XljkEvaluationVO);
const detailList = ref<XljkEvaluationDetailVO[]>([]);
const filteredDetailList = ref<XljkEvaluationDetailVO[]>([]);
const searchKeyword = ref('');
const tableMaxHeight = ref(500);
const tableRef = ref();
// 监听搜索关键字变化
// watch(searchKeyword, searchStudents);
/** 获取测评基本信息 */
const getEvaluationInfo = async () => {
try {
const res = await getEvaluation(evaluationId.value);
evaluationInfo.value = res.data;
// 检查状态是否为"已发布"
if (evaluationInfo.value.status !== '1') {
proxy?.$modal.msgError('只能填写"已发布"状态的测评');
setTimeout(() => {
router.push('/xljk/evaluation');
}, 1500);
}
} catch (error) {
console.error('获取测评信息失败', error);
}
};
/** 获取测评明细列表 */
const getDetailList = async () => {
try {
loading.value = true;
const res = await listEvaluationDetail({
pageNum: 1,
pageSize: 1000, // 获取所有学生
evaluationId: Number(evaluationId.value)
});
detailList.value = res.rows;
filteredDetailList.value = [...detailList.value]; // 初始显示所有数据
} catch (error) {
console.error('获取测评明细失败', error);
} finally {
loading.value = false;
}
};
/** 已填写的人数 */
const filledCount = computed(() => {
return detailList.value.filter(item => item.mentalStatus).length;
});
/** 返回列表页 */
const goBack = () => {
router.push('/xljk/evaluation');
};
/** 处理心理状态变更 */
const handleStatusChange = (row: XljkEvaluationDetailVO) => {
// 如果状态不是重点关注,则分数设为空
if (row.mentalStatus !== '3') {
row.score = undefined;
}
};
/** 处理分数变更 */
const handleScoreChange = (row: XljkEvaluationDetailVO) => {
// 空实现,仅用于触发视图更新
};
/** 处理备注变更 */
const handleRemarkChange = (row: XljkEvaluationDetailVO) => {
// 空实现,仅用于触发视图更新
};
/** 处理搜索 */
const searchStudents = () => {
if (!searchKeyword.value) {
// 如果搜索关键字为空,显示所有数据
filteredDetailList.value = [...detailList.value];
return;
}
const keyword = searchKeyword.value.toLowerCase();
filteredDetailList.value = detailList.value.filter(item => {
return item.studentNo?.toLowerCase().includes(keyword) ||
item.studentName?.toLowerCase().includes(keyword);
});
};
/** 保存测评结果 */
const saveEvaluation = async () => {
// 检查是否有未填写的记录
const unfilledRecords = detailList.value.filter(item => !item.mentalStatus);
// 如果有未填写的记录,询问是否继续
if (unfilledRecords.length > 0) {
try {
await proxy?.$modal.confirm(`还有 ${unfilledRecords.length} 名学生未填写心理状态,是否确认保存?`);
} catch {
return; // 用户取消保存
}
}
// 检查重点关注学生是否都有分数
const noScoreItems = detailList.value.filter(item =>
item.mentalStatus === '3' && (!item.score || item.score <= 0)
);
if (noScoreItems.length > 0) {
proxy?.$modal.msgError(`有 ${noScoreItems.length} 名重点关注学生未填写分数,请填写`);
return;
}
// 准备提交数据
const updateData: XljkEvaluationDetailForm[] = detailList.value.map(item => ({
id: item.id,
evaluationId: Number(evaluationId.value),
mentalStatus: item.mentalStatus,
score: item.score,
remark: item.remark
}));
// 提交数据
try {
saveLoading.value = true;
await batchUpdateEvaluationDetail(updateData);
proxy?.$modal.msgSuccess('保存成功');
} catch (error) {
console.error('保存失败', error);
} finally {
saveLoading.value = false;
}
};
/** 设置表格高度 */
const setTableHeight = () => {
// 预留底部操作栏和边距空间
const windowHeight = window.innerHeight;
const headerHeight = 160; // 估计上方卡片和间距的高度
const footerHeight = 80; // 底部按钮区域高度
const padding = 40; // 额外的边距
tableMaxHeight.value = windowHeight - headerHeight - footerHeight - padding;
};
/** 窗口大小变化时重设表格高度 */
const handleResize = throttle(() => {
setTableHeight();
}, 200);
onMounted(() => {
getEvaluationInfo();
getDetailList();
setTableHeight();
// 添加窗口大小变化监听
window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
// 移除事件监听
window.removeEventListener('resize', handleResize);
});
</script>
<style scoped>
.page-container {
padding: 8px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.header-buttons {
display: flex;
gap: 10px;
}
.header-right {
display: flex;
align-items: center;
}
.fill-stats {
display: flex;
align-items: center;
font-size: 14px;
color: #606266;
}
.fixed-bottom-bar {
position: sticky;
bottom: 0;
left: 0;
width: 100%;
padding: 15px 0;
background-color: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
z-index: 100;
margin-top: 20px;
}
.fixed-bottom-bar .el-button {
min-width: 150px;
}
</style>
+499
View File
@@ -0,0 +1,499 @@
<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="evaluationName">
<el-input v-model="queryParams.evaluationName" placeholder="请输入测评名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="院系" prop="departmentName">
<el-select v-model="queryParams.departmentName" placeholder="请选择院系" clearable style="width: 100%" @change="handleDepartmentChange">
<el-option
v-for="dept in departmentOptions"
:key="dept.name"
:label="dept.name"
:value="dept.name"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="专业" prop="majorName">
<el-select v-model="queryParams.majorName" placeholder="请选择专业" clearable style="width: 100%" @change="handleMajorChange">
<el-option
v-for="major in majorOptions"
:key="major.name"
:label="major.name"
:value="major.name"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="班级" prop="className">
<el-select v-model="queryParams.className" placeholder="请选择班级" clearable style="width: 100%">
<el-option
v-for="cls in classOptions"
:key="cls.name"
:label="cls.name"
:value="cls.name"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="学期" prop="semester">
<el-select v-model="queryParams.semester" placeholder="请选择学期" clearable style="width: 100%">
<el-option
v-for="semester in semesterOptions"
:key="semester.value"
:label="semester.label"
:value="semester.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
<el-option v-for="dict in evaluation_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<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="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xljk:evaluation:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['xljk:evaluation:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xljk:evaluation:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Download" @click="handleExport" v-hasPermi="['xljk:evaluation:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="evaluationList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="测评名称" align="center" prop="evaluationName" min-width="160" show-overflow-tooltip />
<el-table-column label="院系" align="center" prop="departmentName" min-width="120" show-overflow-tooltip />
<el-table-column label="专业" align="center" prop="majorName" min-width="120" show-overflow-tooltip />
<el-table-column label="班级" align="center" prop="className" min-width="140" show-overflow-tooltip />
<el-table-column label="学期" align="center" prop="semester" width="80" />
<el-table-column label="状态" align="center" prop="status" width="80">
<template #default="scope">
<dict-tag :options="evaluation_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="学生人数" align="center" prop="studentCount" width="90">
<template #default="scope">
{{ scope.row.studentCount || 0 }}
</template>
</el-table-column>
<el-table-column label="重点关注人数" align="center" prop="focusCount" width="110">
<template #default="scope">
{{ scope.row.focusCount || 0 }}
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createName" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="150">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
link
type="info"
icon="View"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleView(scope.row)"
v-hasPermi="['xljk:evaluation:query']"
></el-button>
</el-tooltip>
<!-- 仅草稿状态可修改 -->
<el-tooltip content="修改" placement="top" v-if="scope.row.status === '0'">
<el-button
link
type="primary"
icon="Edit"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleUpdate(scope.row)"
v-hasPermi="['xljk:evaluation:edit']"
></el-button>
</el-tooltip>
<!-- 仅草稿状态可删除 -->
<el-tooltip content="删除" placement="top" v-if="scope.row.status === '0'">
<el-button
link
type="danger"
icon="Delete"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleDelete(scope.row)"
v-hasPermi="['xljk:evaluation:remove']"
></el-button>
</el-tooltip>
<!-- 草稿状态可发布 -->
<el-tooltip content="发布" placement="top" v-if="scope.row.status === '0'">
<el-button
link
type="success"
icon="Check"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handlePublish(scope.row)"
v-hasPermi="['xljk:evaluation:edit']"
></el-button>
</el-tooltip>
<!-- 已发布状态可填写 -->
<el-tooltip content="填写" placement="top" v-if="scope.row.status === '1'">
<el-button
link
type="warning"
icon="Edit"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleEdit(scope.row)"
v-hasPermi="['xljk:evaluation:edit']"
></el-button>
</el-tooltip>
<!-- 已发布状态可完成 -->
<el-tooltip content="完成" placement="top" v-if="scope.row.status === '1'">
<el-button
link
type="success"
icon="CircleCheck"
size="default"
style="font-size: 16px"
@click="handleComplete(scope.row)"
v-hasPermi="['xljk:evaluation:edit']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { listEvaluation, getEvaluation, delEvaluation, addEvaluation, updateEvaluation, publishEvaluation, completeEvaluation, getCollegeOptions, getMajorOptions, getClassOptions, getSemesterOptions } from '@/api/xljk/evaluation';
import { XljkEvaluationVO, XljkEvaluationForm, XljkEvaluationQuery, CollegeOption, MajorOption, ClassOption, SemesterOption } from '@/api/xljk/evaluation/types';
// 基本变量
const { proxy } = getCurrentInstance() as any;
const { evaluation_status } = toRefs(proxy?.useDict('evaluation_status'));
const evaluationList = ref<XljkEvaluationVO[]>([]);
const buttonLoading = ref(false);
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 departmentOptions = ref<CollegeOption[]>([]);
const majorOptions = ref<MajorOption[]>([]);
const classOptions = ref<ClassOption[]>([]);
const semesterOptions = ref<SemesterOption[]>([]);
const queryFormRef = ref();
// 查询参数
const queryParams = ref<XljkEvaluationQuery>({
pageNum: 1,
pageSize: 10,
departmentName: undefined,
evaluationName: undefined,
majorName: undefined,
className: undefined,
semester: undefined,
status: undefined,
params: {}
});
/** 获取院系选项 */
const getDepartments = async () => {
try {
const res = await getCollegeOptions();
departmentOptions.value = res.data || [];
} catch (error) {
console.error('获取院系选项失败', error);
}
};
/** 查询专业选项 */
const getMajors = async (collegeName?: string) => {
try {
const res = await getMajorOptions(collegeName);
majorOptions.value = res.data || [];
} catch (error) {
console.error('获取专业选项失败', error);
}
};
/** 查询班级选项 */
const getClasses = async (collegeName?: string, majorName?: string) => {
try {
const res = await getClassOptions(collegeName, majorName);
classOptions.value = res.data || [];
} catch (error) {
console.error('获取班级选项失败', error);
}
};
/** 获取学期选项 */
const getSemesters = async () => {
try {
const res = await getSemesterOptions();
semesterOptions.value = res.data || [];
} catch (error) {
console.error('获取学期选项失败', error);
}
};
/** 院系选择变化时更新专业选项 */
const handleDepartmentChange = async (collegeName: string) => {
// 清空专业和班级选择
queryParams.value.majorName = undefined;
queryParams.value.className = undefined;
majorOptions.value = [];
classOptions.value = [];
if (collegeName) {
// 获取该院系下的专业
await getMajors(collegeName);
} else {
// 如果清空院系,则获取所有专业
await getMajors();
}
};
/** 专业选择变化时更新班级选项 */
const handleMajorChange = async (majorName: string) => {
queryParams.value.className = undefined; // 清空班级选择
classOptions.value = [];
if (queryParams.value.departmentName && majorName) {
// 获取该院系和专业下的班级
await getClasses(queryParams.value.departmentName, majorName);
}
};
/** 查询心理健康测评列表 */
const getList = async () => {
loading.value = true;
try {
const res = await listEvaluation(queryParams.value);
evaluationList.value = res.rows;
total.value = res.total;
} catch (error) {
console.error('获取测评列表失败', error);
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: XljkEvaluationVO[]) => {
ids.value = selection.map((item) => item.id);
// 判断是否有非草稿状态
const hasNonDraft = selection.some(item => item.status !== '0');
single.value = selection.length !== 1 || hasNonDraft;
multiple.value = !selection.length || hasNonDraft;
};
/** 新增按钮操作 */
const handleAdd = async () => {
proxy?.$router.push({ path: '/xljk/evaluation/add' });
};
/** 查看按钮操作 */
const handleView = (row: XljkEvaluationVO) => {
proxy?.$router.push({ path: `/xljk/evaluation/detail/${row.id}` });
};
/** 修改按钮操作 */
const handleUpdate = (row?: XljkEvaluationVO) => {
const id = row?.id || ids.value[0];
proxy?.$router.push({ path: `/xljk/evaluation/edit/${id}` });
};
/** 填写评测 */
const handleEdit = (row: XljkEvaluationVO) => {
proxy?.$router.push({ path: `/xljk/evaluation/fill/${row.id}` });
};
/** 删除按钮操作 */
const handleDelete = async (row?: XljkEvaluationVO) => {
const _ids = row?.id || ids.value;
// 检查是否有非草稿状态的记录
if (!row) {
const selectedRecords = evaluationList.value.filter(item => ids.value.includes(item.id));
if (selectedRecords.some(item => item.status !== '0')) {
proxy?.$modal.msgError('只能删除草稿状态的测评');
return;
}
} else if (row.status !== '0') {
proxy?.$modal.msgError('只能删除草稿状态的测评');
return;
}
const confirmMessage = row
? `是否确认删除测评"${row.evaluationName}"?`
: `是否确认删除选中的${ids.value.length}条数据?`;
await proxy?.$modal.confirm(confirmMessage);
try {
loading.value = true;
await delEvaluation(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
} finally {
loading.value = false;
}
};
/** 发布按钮操作 */
const handlePublish = async (row: XljkEvaluationVO) => {
await proxy?.$modal.confirm(`是否确认发布测评"${row.evaluationName}"?发布后将无法修改测评基本信息。`);
try {
loading.value = true;
await publishEvaluation(row.id);
proxy?.$modal.msgSuccess('发布成功');
await getList();
} finally {
loading.value = false;
}
};
/** 完成按钮操作 */
const handleComplete = async (row: XljkEvaluationVO) => {
await proxy?.$modal.confirm(`是否确认完成测评"${row.evaluationName}"?完成后将无法再修改测评结果。`);
try {
loading.value = true;
await completeEvaluation(row.id);
proxy?.$modal.msgSuccess('操作成功');
await getList();
} finally {
loading.value = false;
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'xljk/evaluation/export',
{
...queryParams.value
},
`evaluation_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
getDepartments();
getMajors();
getSemesters();
});
</script>
<style scoped>
/* 页面容器样式 */
.page-container {
padding: 8px;
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
/* 移除按钮栏底部边框 */
.button-card :deep(.el-card__header) {
border-bottom: none !important;
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
/* 搜索按钮容器样式 */
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
.el-form-item {
margin-bottom: 0 !important;
}
}
</style>
+1 -1
View File
@@ -232,7 +232,7 @@ cd backend
#### 3. 启动主应用服务
```bash
./hot-start.sh
./start-main.sh
```
**等待提示**: 看到 "主应用启动成功" 或访问 `http://localhost:8080` 正常返回