From 4c23ea16c06231fa2855f149505dedc46c607eb3 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 24 Oct 2025 14:05:12 +0800 Subject: [PATCH 01/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-prod.yml | 48 +++ .../src/main/resources/application.yml | 2 +- .../controller/XgCollegeLeaderController.java | 123 +++++++ .../xg/controller/XgStudentMsgController.java | 17 + .../xg/controller/XgSysUserController.java | 2 +- .../dromara/xg/domain/XgCollegeLeader.java | 61 ++++ .../java/org/dromara/xg/domain/XgSysUser.java | 5 + .../xg/domain/bo/XgCollegeLeaderBo.java | 61 ++++ .../xg/domain/vo/XgCollegeLeaderVo.java | 88 +++++ .../xg/mapper/XgCollegeLeaderMapper.java | 15 + .../dromara/xg/mapper/XgStudentMsgMapper.java | 156 +++++---- .../xg/service/IXgCollegeLeaderService.java | 76 +++++ .../xg/service/IXgStudentMsgService.java | 8 + .../impl/XgCollegeLeaderServiceImpl.java | 163 +++++++++ .../service/impl/XgStudentMsgServiceImpl.java | 26 ++ .../mapper/ejxyfgld/XgCollegeLeaderMapper.xml | 8 + .../mapper/studentMsg/XgStudentMsgMapper.xml | 8 + .../resources/mapper/xg/XgSysUserMapper.xml | 6 +- backend/script/sql/ry_workflow.sql | 100 ++++++ target/classes/META-INF/mps/mappers | 59 ---- web/src/api/xg/college/index.ts | 47 +++ web/src/api/xg/college/types.ts | 37 ++ web/src/api/xg/ejxyfgld/index.ts | 117 +++++++ web/src/api/xg/sysUser.ts | 22 +- .../xg/xsjcxx/studentMsg/studentMsg/index.ts | 11 + web/src/views/xg/ejxyfgld/index.vue | 322 ++++++++++++++++++ web/src/views/xg/xsjcxx/xsqj/dialog.vue | 27 +- 27 files changed, 1466 insertions(+), 149 deletions(-) create mode 100644 backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgCollegeLeaderController.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgCollegeLeader.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgCollegeLeaderBo.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgCollegeLeaderVo.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgCollegeLeaderMapper.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgCollegeLeaderService.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgCollegeLeaderServiceImpl.java create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/ejxyfgld/XgCollegeLeaderMapper.xml delete mode 100644 target/classes/META-INF/mps/mappers create mode 100644 web/src/api/xg/college/index.ts create mode 100644 web/src/api/xg/college/types.ts create mode 100644 web/src/api/xg/ejxyfgld/index.ts create mode 100644 web/src/views/xg/ejxyfgld/index.vue diff --git a/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml b/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml new file mode 100644 index 00000000..108a203e --- /dev/null +++ b/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application-prod.yml @@ -0,0 +1,48 @@ +server: + port: 9090 +spring: + application: + name: ruoyi-monitor-admin + profiles: + active: @profiles.active@ + +logging: + config: classpath:logback-plus.xml + +--- # 监控中心服务端配置 +spring: + security: + user: + name: @monitor.username@ + password: @monitor.password@ + boot: + admin: + ui: + title: RuoYi-Vue-Plus服务监控中心 + context-path: /admin + +--- # Actuator 监控端点的配置项 +management: + endpoints: + web: + exposure: + include: '*' + endpoint: + health: + show-details: ALWAYS + logfile: + external-file: ./logs/ruoyi-monitor-admin.log + +--- # 监控配置 +spring.boot.admin.client: + # 增加客户端开关 + enabled: true + # 设置 Spring Boot Admin Server 地址 + url: http://172.16.0.67:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + username: @monitor.username@ + password: @monitor.password@ diff --git a/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml index 108a203e..86766d7a 100644 --- a/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml +++ b/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml @@ -38,7 +38,7 @@ spring.boot.admin.client: # 增加客户端开关 enabled: true # 设置 Spring Boot Admin Server 地址 - url: http://172.16.0.67:9090/admin + url: http://127.0.0.1:9090/admin instance: service-host-type: IP metadata: diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgCollegeLeaderController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgCollegeLeaderController.java new file mode 100644 index 00000000..a233ae10 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgCollegeLeaderController.java @@ -0,0 +1,123 @@ +package org.dromara.xg.controller; + +import java.util.List; + +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.xg.domain.vo.XgCollegeLeaderVo; +import org.dromara.xg.domain.bo.XgCollegeLeaderBo; +import org.dromara.xg.service.IXgCollegeLeaderService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 二级学院分管领导配置 + * + * @author Cursor AI + * @date 2025-01-24 + */ +@Tag(name = "二级学院分管领导配置") +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/xg/ejxyfgld") +public class XgCollegeLeaderController extends BaseController { + + private final IXgCollegeLeaderService xgCollegeLeaderService; + + /** + * 查询二级学院分管领导配置列表 + */ + @Operation(summary = "查询二级学院分管领导配置列表") + @SaCheckPermission("xg:ejxyfgld:list") + @GetMapping("/list") + public TableDataInfo list(XgCollegeLeaderBo bo, PageQuery pageQuery) { + return xgCollegeLeaderService.queryPageList(bo, pageQuery); + } + + /** + * 根据学院ID查询分管领导列表 + */ + @Operation(summary = "根据学院ID查询分管领导列表") + @SaCheckPermission("xg:ejxyfgld:list") + @GetMapping("/listByCollege/{collegeId}") + public R> listByCollege(@PathVariable Long collegeId) { + List list = xgCollegeLeaderService.queryByCollegeId(collegeId); + return R.ok(list); + } + + /** + * 导出二级学院分管领导配置列表 + */ + @Operation(summary = "导出二级学院分管领导配置列表") + @SaCheckPermission("xg:ejxyfgld:export") + @Log(title = "二级学院分管领导配置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(XgCollegeLeaderBo bo, HttpServletResponse response) { + List list = xgCollegeLeaderService.queryList(bo); + ExcelUtil.exportExcel(list, "二级学院分管领导配置", XgCollegeLeaderVo.class, response); + } + + /** + * 获取二级学院分管领导配置详细信息 + * + * @param id 主键 + */ + @Operation(summary = "获取二级学院分管领导配置详细信息") + @SaCheckPermission("xg:ejxyfgld:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(xgCollegeLeaderService.queryById(id)); + } + + /** + * 新增二级学院分管领导配置 + */ + @Operation(summary = "新增二级学院分管领导配置") + @SaCheckPermission("xg:ejxyfgld:add") + @Log(title = "二级学院分管领导配置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody XgCollegeLeaderBo bo) { + return toAjax(xgCollegeLeaderService.insertByBo(bo)); + } + + /** + * 修改二级学院分管领导配置 + */ + @Operation(summary = "修改二级学院分管领导配置") + @SaCheckPermission("xg:ejxyfgld:edit") + @Log(title = "二级学院分管领导配置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody XgCollegeLeaderBo bo) { + return toAjax(xgCollegeLeaderService.updateByBo(bo)); + } + + /** + * 删除二级学院分管领导配置 + * + * @param ids 主键串 + */ + @Operation(summary = "删除二级学院分管领导配置") + @SaCheckPermission("xg:ejxyfgld:remove") + @Log(title = "二级学院分管领导配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { + return toAjax(xgCollegeLeaderService.deleteWithValidByIds(List.of(ids), true)); + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java index a41a700e..186c7943 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java @@ -823,6 +823,23 @@ public class XgStudentMsgController extends BaseController { return R.ok(approverUserIds); } + /** + * 根据学生ID获取二级学院分管领导ID列表 + * + * @param studentId 学生ID + * @return 分管领导ID列表 + */ + @SaCheckPermission("studentMsg:studentMsg:query") + @GetMapping("/getCollegeLeaders/{studentId}") + public R> getCollegeLeaders(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) { + log.info("根据学生ID获取二级学院分管领导ID列表 - 学生ID: {}", studentId); + List leaderIds = xgStudentMsgService.getCollegeLeadersByStudentId(studentId); + if (leaderIds.isEmpty()) { + return R.fail("未找到该学生所在学院的分管领导"); + } + return R.ok(leaderIds); + } + /** * 导入数据 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgSysUserController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgSysUserController.java index ed10eac0..9997dd5f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgSysUserController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgSysUserController.java @@ -28,7 +28,7 @@ public class XgSysUserController extends BaseController { private final IXgSysUserService xgSysUserService; /** - * 查询有效用户列表 + * 查询有效用户列表(教职工account_type=1) */ @SaCheckPermission("xg:sysUser:list") @GetMapping("/activeList") diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgCollegeLeader.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgCollegeLeader.java new file mode 100644 index 00000000..08549ec2 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgCollegeLeader.java @@ -0,0 +1,61 @@ +package org.dromara.xg.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 二级学院分管领导配置对象 xg_college_leader + * + * @author Cursor AI + * @date 2025-01-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("xg_college_leader") +public class XgCollegeLeader extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @TableId(value = "id") + private Long id; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic(value = "0", delval = "1") + private Long deleted; + + /** + * 二级学院ID,关联sys_dept.dept_id + */ + private Long collegeId; + + /** + * 二级学院名称 + */ + private String collegeName; + + /** + * 分管领导ID,关联sys_user.user_id + */ + private Long leaderId; + + /** + * 分管领导姓名 + */ + private String leaderName; + + /** + * 备注信息 + */ + private String remark; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgSysUser.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgSysUser.java index cd893516..d6528aff 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgSysUser.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgSysUser.java @@ -27,6 +27,11 @@ public class XgSysUser extends TenantEntity { @TableId(value = "user_id") private Long userId; + /** + * 用户账号 + */ + private String userName; + /** * 用户昵称 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgCollegeLeaderBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgCollegeLeaderBo.java new file mode 100644 index 00000000..e94dcaf6 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgCollegeLeaderBo.java @@ -0,0 +1,61 @@ +package org.dromara.xg.domain.bo; + +import org.dromara.xg.domain.XgCollegeLeader; +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.*; + +/** + * 二级学院分管领导配置业务对象 xg_college_leader + * + * @author Cursor AI + * @date 2025-01-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = XgCollegeLeader.class, reverseConvertGenerate = false) +public class XgCollegeLeaderBo extends BaseEntity { + + /** + * 主键ID(雪花ID) + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 删除标志(0代表存在 1代表删除) + */ + private Long deleted; + + /** + * 二级学院ID,关联sys_dept.dept_id + */ + @NotNull(message = "二级学院不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long collegeId; + + /** + * 二级学院名称 + */ + private String collegeName; + + /** + * 分管领导ID,关联sys_user.user_id + */ + @NotNull(message = "分管领导不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long leaderId; + + /** + * 分管领导姓名 + */ + private String leaderName; + + /** + * 备注信息 + */ + private String remark; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgCollegeLeaderVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgCollegeLeaderVo.java new file mode 100644 index 00000000..71ad5900 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgCollegeLeaderVo.java @@ -0,0 +1,88 @@ +package org.dromara.xg.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import org.dromara.xg.domain.XgCollegeLeader; +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; + +/** + * 二级学院分管领导配置视图对象 xg_college_leader + * + * @author Cursor AI + * @date 2025-01-24 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = XgCollegeLeader.class) +public class XgCollegeLeaderVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 二级学院ID,关联sys_dept.dept_id + */ + @ExcelProperty(value = "二级学院ID") + private Long collegeId; + + /** + * 二级学院名称 + */ + @ExcelProperty(value = "二级学院名称") + private String collegeName; + + /** + * 分管领导ID,关联sys_user.user_id + */ + @ExcelProperty(value = "分管领导ID") + private Long leaderId; + + /** + * 分管领导姓名 + */ + @ExcelProperty(value = "分管领导姓名") + private String leaderName; + + /** + * 备注信息 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 创建者 + */ + 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; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgCollegeLeaderMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgCollegeLeaderMapper.java new file mode 100644 index 00000000..b2ce068a --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgCollegeLeaderMapper.java @@ -0,0 +1,15 @@ +package org.dromara.xg.mapper; + +import org.dromara.xg.domain.XgCollegeLeader; +import org.dromara.xg.domain.vo.XgCollegeLeaderVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 二级学院分管领导配置Mapper接口 + * + * @author Cursor AI + * @date 2025-01-24 + */ +public interface XgCollegeLeaderMapper extends BaseMapperPlus { + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java index 5e3bfc8f..155ef255 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java @@ -19,87 +19,95 @@ import java.util.Map; */ public interface XgStudentMsgMapper extends BaseMapperPlus { - /** - * 根据组织ID列表分页查询学生信息 用于导出 - * - * @param page 分页参数 - * @param queryWrapper 查询条件 - * @param deptIds 组织ID列表 - * @return 学生信息分页结果 - */ - List queryPageListExportByDeptId( - @Param("ew") LambdaQueryWrapper queryWrapper, - @Param("deptIds") List deptIds); + /** + * 根据组织ID列表分页查询学生信息 用于导出 + * + * @param page 分页参数 + * @param queryWrapper 查询条件 + * @param deptIds 组织ID列表 + * @return 学生信息分页结果 + */ + List queryPageListExportByDeptId( + @Param("ew") LambdaQueryWrapper queryWrapper, + @Param("deptIds") List deptIds); - /** - * 根据组织ID列表分页查询学生信息 - * - * @param page 分页参数 - * @param queryWrapper 查询条件 - * @param deptIds 组织ID列表 - * @return 学生信息分页结果 - */ - Page selectPageByDeptIds(@Param("page") Page page, - @Param("ew") LambdaQueryWrapper queryWrapper, - @Param("deptIds") List deptIds); + /** + * 根据组织ID列表分页查询学生信息 + * + * @param page 分页参数 + * @param queryWrapper 查询条件 + * @param deptIds 组织ID列表 + * @return 学生信息分页结果 + */ + Page selectPageByDeptIds(@Param("page") Page page, + @Param("ew") LambdaQueryWrapper queryWrapper, + @Param("deptIds") List deptIds); - /** - * 根据班级ID和姓名查询学生信息列表 - * - * @param classId 班级ID - * @param name 学生姓名(模糊查询) - * @return 学生信息列表 - */ - List selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name); + /** + * 根据班级ID和姓名查询学生信息列表 + * + * @param classId 班级ID + * @param name 学生姓名(模糊查询) + * @return 学生信息列表 + */ + List selectStudentsByClassId(@Param("classId") String classId, @Param("name") String name); - /** - * 根据学生ID查询班主任ID - * - * @param studentId 学生ID - * @return 班主任ID - */ - Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId); + /** + * 根据学生ID查询班主任ID + * + * @param studentId 学生ID + * @return 班主任ID + */ + Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId); - /** - * 根据学生ID查询学院ID - * - * @param studentId 学生ID - * @return 学院ID - */ - Long selectCollegeIdByStudentId(@Param("studentId") Long studentId); + /** + * 根据学生ID查询学院ID + * + * @param studentId 学生ID + * @return 学院ID + */ + Long selectCollegeIdByStudentId(@Param("studentId") Long studentId); - /** - * 根据学院ID和角色Key查询具备该角色的用户列表 - * - * @param collegeId 学院ID - * @param roleKey 角色Key - * @return 用户ID列表 - */ - List selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId, - @Param("roleKey") String roleKey); + /** + * 根据学院ID和角色Key查询具备该角色的用户列表 + * + * @param collegeId 学院ID + * @param roleKey 角色Key + * @return 用户ID列表 + */ + List selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId, + @Param("roleKey") String roleKey); - /** - * 查询二级学院下的学院、专业、班级学生统计信息 - * - * @param secondaryCollegeId 二级学院ID - * @return 学生统计信息列表 - */ - List> getCollegeStudentStats(@Param("secondaryCollegeId") Long secondaryCollegeId); + /** + * 根据学院ID查询二级学院分管领导ID列表 + * + * @param collegeId 学院ID + * @return 分管领导ID列表 + */ + List selectCollegeLeaderIdsByCollegeId(@Param("collegeId") Long collegeId); - /** - * 查询二级学院下的学院、专业、班级学生统计信息(按床位分配统计) - * - * @param secondaryCollegeId 二级学院ID - * @return 学生统计信息列表 - */ - List> getCollegeStudentStatsByClass(@Param("secondaryCollegeId") Long secondaryCollegeId); + /** + * 查询二级学院下的学院、专业、班级学生统计信息 + * + * @param secondaryCollegeId 二级学院ID + * @return 学生统计信息列表 + */ + List> getCollegeStudentStats(@Param("secondaryCollegeId") Long secondaryCollegeId); - /** - * 查询二级学院下的学院床位分配统计信息(基于college_id统计) - * - * @param secondaryCollegeId 二级学院ID - * @return 学院床位分配统计信息列表 - */ - List> getCollegeBedAssignmentStats(@Param("secondaryCollegeId") Long secondaryCollegeId); + /** + * 查询二级学院下的学院、专业、班级学生统计信息(按床位分配统计) + * + * @param secondaryCollegeId 二级学院ID + * @return 学生统计信息列表 + */ + List> getCollegeStudentStatsByClass(@Param("secondaryCollegeId") Long secondaryCollegeId); + + /** + * 查询二级学院下的学院床位分配统计信息(基于college_id统计) + * + * @param secondaryCollegeId 二级学院ID + * @return 学院床位分配统计信息列表 + */ + List> getCollegeBedAssignmentStats(@Param("secondaryCollegeId") Long secondaryCollegeId); } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgCollegeLeaderService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgCollegeLeaderService.java new file mode 100644 index 00000000..915752ea --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgCollegeLeaderService.java @@ -0,0 +1,76 @@ +package org.dromara.xg.service; + +import org.dromara.xg.domain.vo.XgCollegeLeaderVo; +import org.dromara.xg.domain.bo.XgCollegeLeaderBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 二级学院分管领导配置Service接口 + * + * @author Cursor AI + * @date 2025-01-24 + */ +public interface IXgCollegeLeaderService { + + /** + * 查询二级学院分管领导配置 + * + * @param id 主键 + * @return 二级学院分管领导配置 + */ + XgCollegeLeaderVo queryById(Long id); + + /** + * 分页查询二级学院分管领导配置列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 二级学院分管领导配置分页列表 + */ + TableDataInfo queryPageList(XgCollegeLeaderBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的二级学院分管领导配置列表 + * + * @param bo 查询条件 + * @return 二级学院分管领导配置列表 + */ + List queryList(XgCollegeLeaderBo bo); + + /** + * 新增二级学院分管领导配置 + * + * @param bo 二级学院分管领导配置 + * @return 是否新增成功 + */ + Boolean insertByBo(XgCollegeLeaderBo bo); + + /** + * 修改二级学院分管领导配置 + * + * @param bo 二级学院分管领导配置 + * @return 是否修改成功 + */ + Boolean updateByBo(XgCollegeLeaderBo bo); + + /** + * 校验并批量删除二级学院分管领导配置信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 根据学院ID查询分管领导列表 + * + * @param collegeId 学院ID + * @return 分管领导列表 + */ + List queryByCollegeId(Long collegeId); +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java index fe89c09c..030c9bd8 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java @@ -140,6 +140,14 @@ public interface IXgStudentMsgService { */ List getCollegeLeaveApproversByStudentId(Long studentId); + /** + * 根据学生ID获取二级学院分管领导ID列表 + * + * @param studentId 学生ID + * @return 分管领导ID列表 + */ + List getCollegeLeadersByStudentId(Long studentId); + /** * 批量申请床位 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgCollegeLeaderServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgCollegeLeaderServiceImpl.java new file mode 100644 index 00000000..dac2b934 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgCollegeLeaderServiceImpl.java @@ -0,0 +1,163 @@ +package org.dromara.xg.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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.xg.domain.bo.XgCollegeLeaderBo; +import org.dromara.xg.domain.vo.XgCollegeLeaderVo; +import org.dromara.xg.domain.XgCollegeLeader; +import org.dromara.xg.mapper.XgCollegeLeaderMapper; +import org.dromara.xg.service.IXgCollegeLeaderService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 二级学院分管领导配置Service业务层处理 + * + * @author Cursor AI + * @date 2025-01-24 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class XgCollegeLeaderServiceImpl implements IXgCollegeLeaderService { + + private final XgCollegeLeaderMapper baseMapper; + + /** + * 查询二级学院分管领导配置 + * + * @param id 主键 + * @return 二级学院分管领导配置 + */ + @Override + public XgCollegeLeaderVo queryById(Long id) { + return baseMapper.selectVoById(id); + } + + /** + * 分页查询二级学院分管领导配置列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 二级学院分管领导配置分页列表 + */ + @Override + public TableDataInfo queryPageList(XgCollegeLeaderBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的二级学院分管领导配置列表 + * + * @param bo 查询条件 + * @return 二级学院分管领导配置列表 + */ + @Override + public List queryList(XgCollegeLeaderBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + /** + * 根据学院ID查询分管领导列表 + * + * @param collegeId 学院ID + * @return 分管领导列表 + */ + @Override + public List queryByCollegeId(Long collegeId) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(XgCollegeLeader::getCollegeId, collegeId); + lqw.orderByDesc(XgCollegeLeader::getCreateTime); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(XgCollegeLeaderBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getCollegeId() != null, XgCollegeLeader::getCollegeId, bo.getCollegeId()); + lqw.like(StringUtils.isNotBlank(bo.getCollegeName()), XgCollegeLeader::getCollegeName, bo.getCollegeName()); + lqw.eq(bo.getLeaderId() != null, XgCollegeLeader::getLeaderId, bo.getLeaderId()); + lqw.like(StringUtils.isNotBlank(bo.getLeaderName()), XgCollegeLeader::getLeaderName, bo.getLeaderName()); + lqw.orderByDesc(XgCollegeLeader::getCreateTime); + return lqw; + } + + /** + * 新增二级学院分管领导配置 + * + * @param bo 二级学院分管领导配置 + * @return 是否新增成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean insertByBo(XgCollegeLeaderBo bo) { + XgCollegeLeader add = MapstructUtils.convert(bo, XgCollegeLeader.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改二级学院分管领导配置 + * + * @param bo 二级学院分管领导配置 + * @return 是否修改成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateByBo(XgCollegeLeaderBo bo) { + XgCollegeLeader update = MapstructUtils.convert(bo, XgCollegeLeader.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(XgCollegeLeader entity) { + // 校验是否已存在相同的配置 + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(XgCollegeLeader::getCollegeId, entity.getCollegeId()); + lqw.eq(XgCollegeLeader::getLeaderId, entity.getLeaderId()); + if (entity.getId() != null) { + lqw.ne(XgCollegeLeader::getId, entity.getId()); + } + long count = baseMapper.selectCount(lqw); + if (count > 0) { + throw new RuntimeException("该学院已配置此分管领导,不能重复配置!"); + } + } + + /** + * 校验并批量删除二级学院分管领导配置信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // 可以添加业务校验逻辑 + } + return baseMapper.deleteByIds(ids) > 0; + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java index 654d757e..af3095f1 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java @@ -1095,6 +1095,32 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService { return approverUserIds; } + /** + * 根据学生ID获取二级学院分管领导ID列表 + * + * @param studentId 学生ID + * @return 分管领导ID列表 + */ + @Override + public List getCollegeLeadersByStudentId(Long studentId) { + log.info("根据学生ID获取二级学院分管领导ID列表 - 学生ID: {}", studentId); + + // 1. 根据学生ID查询学院ID + Long collegeId = baseMapper.selectCollegeIdByStudentId(studentId); + if (collegeId == null) { + log.warn("未找到学生对应的学院信息 - 学生ID: {}", studentId); + return new ArrayList<>(); + } + + log.info("学生所在学院ID: {}", collegeId); + + // 2. 根据学院ID查询二级学院分管领导ID列表 + List leaderIds = baseMapper.selectCollegeLeaderIdsByCollegeId(collegeId); + + log.info("找到的二级学院分管领导数量: {}", leaderIds.size()); + return leaderIds; + } + /** * 批量申请床位 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/ejxyfgld/XgCollegeLeaderMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/ejxyfgld/XgCollegeLeaderMapper.xml new file mode 100644 index 00000000..91d2a198 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/ejxyfgld/XgCollegeLeaderMapper.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml index 1c7de23c..64ddfac8 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml @@ -171,6 +171,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND (d.dept_id = #{collegeId} OR FIND_IN_SET(#{collegeId}, d.ancestors)) + + + - select user_id, nick_name, phonenumber + select user_id, user_name, nick_name, phonenumber, account_type from sys_user where status = '0' and del_flag = '0' order by create_time desc @@ -26,7 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + SELECT + id, + create_time, + update_time, + tenant_id, + del_flag, + definition_id, + instance_id, + task_id, + cooperate_type, + node_code, + node_name, + node_type, + target_node_code, + target_node_name, + approver, + collaborator, + skip_type, + flow_status, + message, + signature, + ext, + form_custom, + form_path + FROM flow_his_task + WHERE instance_id = #{instanceId} + AND node_type = #{nodeType} + AND del_flag = '0' + ORDER BY create_time DESC, update_time DESC + + + + diff --git a/backend/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml b/backend/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml index f539030b..6d4aeeac 100644 --- a/backend/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml +++ b/backend/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml @@ -59,6 +59,7 @@ a.instance_id, a.flow_status flow_task_status, a.message, + a.signature, a.ext, a.create_time, a.update_time, diff --git a/web/src/components/Process/approvalRecord.vue b/web/src/components/Process/approvalRecord.vue index b13c426d..0bc459a3 100644 --- a/web/src/components/Process/approvalRecord.vue +++ b/web/src/components/Process/approvalRecord.vue @@ -33,6 +33,21 @@ + + + + + From aacdbdad8147a339a517a5733d2050b55d77a43a Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 24 Oct 2025 15:54:43 +0800 Subject: [PATCH 03/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/views/index.vue | 62 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/web/src/views/index.vue b/web/src/views/index.vue index 3c2f2643..bf4d18c9 100644 --- a/web/src/views/index.vue +++ b/web/src/views/index.vue @@ -47,7 +47,7 @@ {{ menu.title }} - {{ menu.description }} + 开发中 @@ -946,24 +946,36 @@ onMounted(async () => { } .menu-card-wrapper { - flex: 0 0 calc(20% - 16px); // 每行5个,减去间隔 - min-width: 180px; + flex: 0 0 calc((100% - 140px) / 8); // 每行8个,精确计算:(100% - 7个间隔*20px) / 8 + min-width: 100px; margin-bottom: 20px; + @media (max-width: 1600px) { + flex: 0 0 calc((100% - 120px) / 7); // 中大屏幕每行7个 + } + @media (max-width: 1400px) { - flex: 0 0 calc(25% - 15px); // 中等屏幕每行4个 + flex: 0 0 calc((100% - 100px) / 6); // 中等屏幕每行6个 } @media (max-width: 1200px) { - flex: 0 0 calc(33.333% - 14px); // 小屏幕每行3个 + flex: 0 0 calc((100% - 80px) / 5); // 小屏幕每行5个 + } + + @media (max-width: 992px) { + flex: 0 0 calc((100% - 60px) / 4); // 平板每行4个 } @media (max-width: 768px) { - flex: 0 0 calc(50% - 10px); // 移动端每行2个 + flex: 0 0 calc((100% - 40px) / 3); // 移动端每行3个 + } + + @media (max-width: 600px) { + flex: 0 0 calc((100% - 20px) / 2); // 小手机每行2个 } @media (max-width: 480px) { - flex: 0 0 100%; // 超小屏幕每行1个 + flex: 0 0 calc(100% - 20px); // 超小屏幕每行1个 } } @@ -972,7 +984,7 @@ onMounted(async () => { border: none; transition: all 0.3s ease; cursor: pointer; - height: 140px; + height: 110px; display: flex; align-items: center; justify-content: center; @@ -995,7 +1007,7 @@ onMounted(async () => { .menu-action { text-align: center; - padding: 15px 10px; + padding: 10px 8px; width: 100%; } @@ -1011,15 +1023,15 @@ onMounted(async () => { display: flex; justify-content: center; align-items: center; - margin-bottom: 12px; - height: 48px; + margin-bottom: 8px; + height: 36px; } .menu-logo { - width: 48px; - height: 48px; + width: 36px; + height: 36px; object-fit: contain; - border-radius: 8px; + border-radius: 6px; transition: all 0.3s ease; filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1)); @@ -1041,16 +1053,16 @@ onMounted(async () => { } .action-title { - font-size: 15px; + font-size: 14px; font-weight: 600; color: #333; - margin-bottom: 6px; + margin-bottom: 4px; display: block; line-height: 1.3; } .action-desc { - font-size: 12px; + font-size: 11px; color: #666; display: block; line-height: 1.4; @@ -1140,29 +1152,29 @@ onMounted(async () => { } .menu-action-card { - height: 120px; + height: 100px; } .menu-action { - padding: 10px 5px; + padding: 8px 5px; } .menu-logo-wrapper { - height: 36px; - margin-bottom: 8px; + height: 30px; + margin-bottom: 6px; } .menu-logo { - width: 36px; - height: 36px; + width: 30px; + height: 30px; } .action-title { - font-size: 14px; + font-size: 13px; } .action-desc { - font-size: 11px; + font-size: 10px; } .loading-state { From a0e5c9a024a2f1f0e1dd6540d86dfabb72a865a6 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 24 Oct 2025 22:20:29 +0800 Subject: [PATCH 04/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mobile/package.json | 1 + mobile/src/api/system/dict/data.js | 35 + mobile/src/common/README-API调用规范.md | 413 +++ mobile/src/common/config.js | 4 +- mobile/src/pages.json | 55 +- .../src/pages/xgss/wsjc/components/README.md | 210 ++ .../xgss/wsjc/components/SingleRoomCheck.vue | 2212 +++++++++++++++++ .../wsjc/components/ViolationRecordDialog.vue | 1901 ++++++++++++++ mobile/src/pages/xgss/wsjc/wsjc.vue | 2 +- mobile/src/pages/xgss/wsjc/wsjcMainAdd.vue | 971 ++++++++ mobile/src/utils/upload.js | 308 +++ 11 files changed, 6086 insertions(+), 26 deletions(-) create mode 100644 mobile/src/api/system/dict/data.js create mode 100644 mobile/src/common/README-API调用规范.md create mode 100644 mobile/src/pages/xgss/wsjc/components/README.md create mode 100644 mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue create mode 100644 mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue create mode 100644 mobile/src/pages/xgss/wsjc/wsjcMainAdd.vue create mode 100644 mobile/src/utils/upload.js diff --git a/mobile/package.json b/mobile/package.json index eedf7efa..b2d21a2a 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -73,6 +73,7 @@ "@vue/shared": "^3.0.0", "core-js": "^3.8.3", "crypto-js": "^4.2.0", + "dayjs": "^1.11.18", "flyio": "^0.6.2", "jsencrypt": "^3.3.2", "uview-ui": "2.0.38", diff --git a/mobile/src/api/system/dict/data.js b/mobile/src/api/system/dict/data.js new file mode 100644 index 00000000..8a4b10e1 --- /dev/null +++ b/mobile/src/api/system/dict/data.js @@ -0,0 +1,35 @@ +// 字典数据API - 重新导出common/dict.js中的方法以保持兼容性 +import { + getDicts, + getDict, + useDict, + getDictLabel, + getDictValue, + hasDictValue, + refreshDict, + getDictTagType, +} from '@/common/dict.js'; + +// 重新导出所有字典相关方法 +export { + getDicts, + getDict, + useDict, + getDictLabel, + getDictValue, + hasDictValue, + refreshDict, + getDictTagType, +}; + +// 默认导出 +export default { + getDicts, + getDict, + useDict, + getDictLabel, + getDictValue, + hasDictValue, + refreshDict, + getDictTagType, +}; diff --git a/mobile/src/common/README-API调用规范.md b/mobile/src/common/README-API调用规范.md new file mode 100644 index 00000000..c4b02c1b --- /dev/null +++ b/mobile/src/common/README-API调用规范.md @@ -0,0 +1,413 @@ +# 移动端API调用标准规范 + +> 📌 **重要提示**: 移动端与Web端的API调用方式完全不同,请严格遵守本规范! + +## 🚫 常见错误 + +### ❌ 不要这样做(Web端方式) +```javascript +// 错误1:使用 this.$http +const res = await this.$http.get('/api/path'); +const res = await this.$http.post('/api/path', data); +const res = await this.$http.upload('/api/upload', options); + +// 错误2:使用 getDicts +import { getDicts } from '@/api/system/dict/data.js'; +const res = await getDicts('dict_type'); +this.dictData = res.data; // Web端返回格式 + +// 错误3:使用 this.$u.toast +this.$u.toast('提示信息'); +``` + +## ✅ 正确做法(移动端方式) + +### 1. HTTP请求 + +```javascript +// 导入 +import { request } from '@/common/request.js'; + +// GET请求(无参数) +const res = await request({ + url: '/api/path', + method: 'GET', +}); + +// GET请求(带参数) +const res = await request({ + url: '/api/path', + method: 'GET', + params: { + key1: 'value1', + key2: 'value2' + }, +}); + +// POST请求 +const res = await request({ + url: '/api/path', + method: 'POST', + data: { + key1: 'value1', + key2: 'value2' + }, +}); + +// PUT请求 +const res = await request({ + url: '/api/path', + method: 'PUT', + data: { ... }, +}); + +// DELETE请求 +const res = await request({ + url: '/api/path', + method: 'DELETE', +}); +``` + +### 2. 字典数据获取 + +```javascript +// 导入 +import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js'; + +// 获取字典数据(直接返回数组) +const dictData = await getDict('dict_type'); // 返回: [{label, value, ...}, ...] + +// 批量获取字典 +const [dict1, dict2] = await Promise.all([ + getDict('dict_type_1'), + getDict('dict_type_2'), +]); + +// 获取字典标签 +const label = getDictLabel(dictData, '1'); // 返回: '标签名' + +// 获取字典Tag类型(用于u-tag组件) +const tagType = getDictTagType(dictData, '1'); // 返回: 'success', 'warning', 'error' 等 +``` + +### 3. 字典展示(u-tag) + +```vue + + + +``` + +### 4. 文件上传 + +```javascript +// 导入 +import { uploadFile } from '@/utils/upload.js'; + +// 选择并上传图片 +uni.chooseImage({ + count: 1, + success: async (res) => { + const filePath = res.tempFilePaths[0]; + + // 上传文件 + const result = await uploadFile(filePath, (progress) => { + console.log('上传进度:', progress); + // 更新进度条 + this.uploadProgress = progress; + }); + + if (result && result.code === 200 && result.data) { + // 上传成功 + this.ossId = result.data.ossId; + this.fileUrl = result.data.url; + } + } +}); +``` + +### 5. Toast提示 + +```javascript +// 普通提示 +uni.showToast({ + title: '提示信息', + icon: 'none' +}); + +// 成功提示 +uni.showToast({ + title: '操作成功', + icon: 'success' +}); + +// 失败提示 +uni.showToast({ + title: '操作失败', + icon: 'error' +}); + +// 加载中 +uni.showLoading({ + title: '加载中...', + mask: true +}); + +// 关闭加载 +uni.hideLoading(); +``` + +## 📊 完整示例 + +```vue + + + +``` + +## 🔑 核心差异对照表 + +| 功能 | Web端 | 移动端 | +|------|-------|--------| +| **HTTP请求** | `this.$http.get('/api')` | `request({ url: '/api', method: 'GET' })` | +| **字典获取** | `getDicts('type')` 返回 `{data:[]}` | `getDict('type')` 返回 `[...]` | +| **字典标签** | `selectDictLabel(dict, value)` | `getDictLabel(dict, value)` | +| **Tag类型** | 手动映射 | `getDictTagType(dict, value)` | +| **文件上传** | `this.$http.upload()` | `uploadFile(path, callback)` | +| **Toast提示** | `this.$u.toast('msg')` | `uni.showToast({ title: 'msg' })` | +| **加载提示** | `this.$u.loading.show()` | `uni.showLoading({ title: '...' })` | + +## 📝 注意事项 + +1. **导入路径固定**: + - HTTP请求: `@/common/request.js` + - 字典方法: `@/common/dict.js` + - 文件上传: `@/utils/upload.js` + +2. **字典数据格式**: + - 移动端 `getDict()` 直接返回数组,不需要 `.data` + - 字典项格式: `{ label, value, elTagType, elTagClass, remark }` + +3. **Tag类型自动转换**: + - `danger` → `error` + - `default` → `primary` + +4. **文件上传进度**: + - 进度回调返回 0-100 的数字 + - Web端可能是 `progress.progress`,移动端直接是数字 + +5. **错误处理**: + - 统一使用 `try-catch` + - 错误时使用 `uni.showToast` 提示 + +## 🔗 相关文档 + +- `mobile/src/common/request.js` - HTTP请求源码 +- `mobile/src/common/dict.js` - 字典方法源码 +- `mobile/src/utils/upload.js` - 文件上传源码 +- `.cursor/cursorrules-mobile.md` - 移动端开发规范 + +--- + +**最后更新**: 2025-01-24 +**适用范围**: 移动端工程(Vue 2.6.14 + uni-app 2.x) + diff --git a/mobile/src/common/config.js b/mobile/src/common/config.js index 85839def..af11a20f 100644 --- a/mobile/src/common/config.js +++ b/mobile/src/common/config.js @@ -12,6 +12,7 @@ export const ENCRYPT_CONFIG = { TENANT_ID: '000000', // 加密请求头 ENCRYPT_HEADER: 'encrypt-key', + //TODO 开发环境需要修改为false // 是否启用加密 ENCRYPT_ENABLED: true, }; @@ -36,8 +37,9 @@ export const REQUEST_CONFIG = { ? 'http://localhost/login?redirect=/index' : 'https://hzdyjs.camasort.com/login?redirect=/index', // 生产环境前端域名 + //TODO 生产环境需要修改为true // 是否启用加密 - encryptEnabled: true, + encryptEnabled: false, // 防重复提交间隔时间(ms) repeatSubmitInterval: 500, }; diff --git a/mobile/src/pages.json b/mobile/src/pages.json index 8db5e1ac..960c5afe 100644 --- a/mobile/src/pages.json +++ b/mobile/src/pages.json @@ -200,6 +200,13 @@ "navigationBarTextStyle": "white" } }, + { + "path": "pages/xgss/wsjc/wsjcMainAdd", + "style": { + "navigationBarTitleText": "宿舍卫生检查", + "navigationStyle": "custom" + } + }, { "path": "pages/xgss/xssswsjc/xssswsjc", "style": { @@ -303,30 +310,30 @@ "navigationBarTextStyle": "white" } }, - { - "path": "pages/xljk/reservation/index", - "style": { - "navigationBarTitleText": "心理咨询预约", - "navigationBarBackgroundColor": "#0051d9", - "navigationBarTextStyle": "white" - } - }, - { - "path": "pages/xljk/my-appointments/index", - "style": { - "navigationBarTitleText": "我的预约", - "navigationBarBackgroundColor": "#0051d9", - "navigationBarTextStyle": "white" - } - }, - { - "path": "pages/xljk/reservation/appointment-form", - "style": { - "navigationBarTitleText": "预约咨询", - "navigationBarBackgroundColor": "#0051d9", - "navigationBarTextStyle": "white" - } - } + { + "path": "pages/xljk/reservation/index", + "style": { + "navigationBarTitleText": "心理咨询预约", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/xljk/my-appointments/index", + "style": { + "navigationBarTitleText": "我的预约", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/xljk/reservation/appointment-form", + "style": { + "navigationBarTitleText": "预约咨询", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + } ], "globalStyle": { "navigationBarTextStyle": "black", diff --git a/mobile/src/pages/xgss/wsjc/components/README.md b/mobile/src/pages/xgss/wsjc/components/README.md new file mode 100644 index 00000000..403034bc --- /dev/null +++ b/mobile/src/pages/xgss/wsjc/components/README.md @@ -0,0 +1,210 @@ +# 宿舍卫生检查组件说明 + +## 组件列表 + +### 1. SingleRoomCheck.vue - 单宿舍检查组件 + +**功能说明:** +- 移动端单个宿舍的卫生检查表单组件 +- 与web端 `DormHygieneCheckSingleDialog.vue` 功能完全一致 +- 用于嵌入在弹窗中进行单个宿舍的详细检查 + +**主要功能模块:** + +#### 1.1 宿舍整体情况 +- 违纪类型选择(从API加载) +- 宿舍扣分输入(0-100分) +- 卫生等级选择(字典:`xg_dorm_hygiene_grade`) +- 检查附件上传(支持图片,最多15个) + +#### 1.2 宿舍学生情况 +- 学生基本信息展示(姓名、学号、性别、床位、班级、专业) +- 学生评分项: + - 个人扣分(0-100分) + - 出勤状态(字典:`xg_student_attendance_status`) + - 学生评级(字典:`xg_dorm_hygiene_grade`) +- 请假信息展示(如果有请假记录) +- 违纪记录录入与管理 + +**Props:** +```javascript +{ + roomInfo: Object, // 房间信息(必填) + checkInfo: Object, // 检查基础信息(必填) + existingData: Object // 编辑时的现有数据(可选) +} +``` + +**Events:** +```javascript +{ + submit: (data) => {}, // 保存检查结果 + cancel: () => {} // 取消操作 +} +``` + +**API接口:** +- `GET /disciplineType/disciplineType/listByAreaType` - 加载违纪类型 +- `GET /dormBedStudent/dormBedStudent/getRoomStudents` - 加载房间学生信息 +- `GET /studentLeave/studentLeave/queryByCheckDate` - 查询学生请假信息 +- `POST /dormHygieneCheck/dormHygieneCheck/saveCompleteData` - 保存完整检查数据 +- `POST /resource/oss/upload` - 上传附件 + +**字典类型:** +- `xg_dorm_hygiene_grade` - 卫生等级 +- `xg_student_attendance_status` - 出勤状态 + +--- + +### 2. ViolationRecordDialog.vue - 违纪记录弹窗 + +**功能说明:** +- 学生违纪记录的录入和编辑弹窗 +- 支持新增和编辑两种模式 + +**主要字段:** +- 违纪类型(从API加载) +- 扣分分数(自动填入最低扣分) +- 违纪日期 +- 违纪地点 +- 违纪描述 +- 处理意见 + +**Props:** +```javascript +{ + visible: Boolean, // 弹窗显示状态 + studentInfo: Object, // 学生信息 + violationData: Object // 违纪数据(编辑模式) +} +``` + +**Events:** +```javascript +{ + confirm: (data) => {}, // 确认保存 + close: () => {} // 关闭弹窗 +} +``` + +**API接口:** +- `GET /disciplineType/disciplineType/listByAreaType` - 加载违纪类型 + +--- + +## 使用示例 + +### 在 wsjcMainAdd.vue 中使用 SingleRoomCheck + +```vue + + + +``` + +--- + +## 开发规范 + +### 1. API调用 +- 统一使用 `mobile/src/common/request.js` 中的 `request` 方法 +- 附件上传使用 `smartUploadFile` 方法(自动适配微信环境) + +### 2. 字典数据 +- 使用 `mobile/src/common/dict.js` 中的方法 + - `getDict(dictType)` - 获取字典数据 + - `getDictLabel(dictData, value)` - 获取字典标签 + - `getDictTagType(dictData, value)` - 获取u-tag类型 + +### 3. 样式规范 +- 使用uview-ui 2.x组件 +- 字典数据用 `` 展示 +- 遵循移动端UI设计规范 + +### 4. 数据提交 +- 与web端API接口完全一致 +- 参数名和数据结构保持一致 +- 返回数据格式相同 + +--- + +## 技术栈 + +- Vue 2.6.14 +- uni-app 2.x +- uview-ui 2.0.38 +- dayjs(日期处理) + +--- + +## 注意事项 + +1. **附件上传兼容性** + - 移动端附件功能兼容微信小程序 + - 参考 `wsjcAdd.vue` 的附件实现 + - 使用 `smartUploadFile` 方法自动适配环境 + +2. **字典数据加载** + - 组件初始化时自动加载字典数据 + - 使用缓存机制,避免重复请求 + +3. **请假信息自动填充** + - 加载学生信息时自动查询请假记录 + - 有请假记录的学生自动设置出勤状态为"请假" + +4. **违纪类型自动扣分** + - 选择违纪类型后自动填入最低扣分 + - 用户可手动修改 + +5. **数据验证** + - 扣分范围:0-100分 + - 必填字段验证 + - 表单提交前统一验证 + +--- + +## 更新日志 + +**2025-01-24** +- 创建 SingleRoomCheck.vue 组件 +- 创建 ViolationRecordDialog.vue 组件 +- 实现与web端完全一致的功能 +- 适配移动端UI和交互方式 + diff --git a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue new file mode 100644 index 00000000..64eb75d9 --- /dev/null +++ b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue @@ -0,0 +1,2212 @@ + + + + + diff --git a/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue b/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue new file mode 100644 index 00000000..2a7ac3c6 --- /dev/null +++ b/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue @@ -0,0 +1,1901 @@ + + + + + diff --git a/mobile/src/pages/xgss/wsjc/wsjc.vue b/mobile/src/pages/xgss/wsjc/wsjc.vue index fe4531ce..81d5b182 100644 --- a/mobile/src/pages/xgss/wsjc/wsjc.vue +++ b/mobile/src/pages/xgss/wsjc/wsjc.vue @@ -450,7 +450,7 @@ export default { // 新增检查 handleAdd() { uni.navigateTo({ - url: '/pages/xgss/wsjc/wsjcAdd?type=add', + url: '/pages/xgss/wsjc/wsjcMainAdd', }); }, diff --git a/mobile/src/pages/xgss/wsjc/wsjcMainAdd.vue b/mobile/src/pages/xgss/wsjc/wsjcMainAdd.vue new file mode 100644 index 00000000..acecd989 --- /dev/null +++ b/mobile/src/pages/xgss/wsjc/wsjcMainAdd.vue @@ -0,0 +1,971 @@ + + + + + diff --git a/mobile/src/utils/upload.js b/mobile/src/utils/upload.js new file mode 100644 index 00000000..c9fa566a --- /dev/null +++ b/mobile/src/utils/upload.js @@ -0,0 +1,308 @@ +// 文件上传工具 - 移动端适配 +import { request } from '@/common/request.js'; +import { REQUEST_CONFIG } from '@/common/config.js'; + +/** + * 获取token + */ +function getToken() { + try { + const token = uni.getStorageSync('access_token'); + return token ? `Bearer ${token}` : ''; + } catch (e) { + console.error('❌ 获取Token失败:', e); + return ''; + } +} + +/** + * 构建完整的上传URL + */ +function buildUploadUrl(url) { + let fullUrl; + if (REQUEST_CONFIG.baseURL.startsWith('/')) { + // 开发环境,baseURL是'/api',需要使用完整的服务器URL + fullUrl = `${REQUEST_CONFIG.serverUrl}${url}`; + } else { + // 生产环境,直接拼接 + fullUrl = REQUEST_CONFIG.baseURL + url; + } + return fullUrl; +} + +/** + * 上传单个文件 + * @param {string} url - 上传接口URL + * @param {Object} options - 上传选项 + * @param {string} options.filePath - 文件路径 + * @param {string} options.name - 文件字段名,默认'file' + * @param {Object} options.formData - 额外的表单数据 + * @param {Function} options.onProgress - 进度回调 + * @returns {Promise} + */ +export function uploadFile(url, options = {}) { + return new Promise((resolve, reject) => { + const { filePath, name = 'file', formData = {}, onProgress } = options; + + if (!filePath) { + return reject(new Error('文件路径不能为空')); + } + + const token = getToken(); + const clientid = uni.getStorageSync('clientid') || REQUEST_CONFIG.clientId; + const fullUrl = buildUploadUrl(url); + + console.log('🚀 开始上传文件:', { + url: fullUrl, + filePath, + name, + }); + + const uploadTask = uni.uploadFile({ + url: fullUrl, + filePath, + name, + formData, + header: { + authorization: token, + clientid: clientid, + }, + success: (res) => { + console.log('✅ 文件上传成功:', res); + + if (res.statusCode === 200) { + try { + const data = JSON.parse(res.data); + if (data.code === 200) { + resolve(data); + } else { + uni.showToast({ + title: data.msg || '上传失败', + icon: 'none', + }); + reject(new Error(data.msg || '上传失败')); + } + } catch (error) { + console.error('❌ 解析上传响应失败:', error); + reject(new Error('解析响应数据失败')); + } + } else { + reject(new Error(`上传失败: HTTP ${res.statusCode}`)); + } + }, + fail: (err) => { + console.error('❌ 文件上传失败:', err); + uni.showToast({ + title: '上传失败', + icon: 'none', + }); + reject(new Error(err.errMsg || '上传失败')); + }, + }); + + // 监听上传进度 + if (onProgress && typeof onProgress === 'function') { + uploadTask.onProgressUpdate((res) => { + onProgress(res); + }); + } + }); +} + +/** + * 上传多个文件 + * @param {string} url - 上传接口URL + * @param {Array} files - 文件列表 + * @param {Object} options - 上传选项 + * @returns {Promise} + */ +export function uploadFiles(url, files, options = {}) { + const uploadPromises = files.map((file, index) => { + return uploadFile(url, { + ...options, + filePath: file.tempFilePath || file.path || file.url, + onProgress: (progress) => { + if (options.onProgress) { + options.onProgress({ + ...progress, + fileIndex: index, + totalFiles: files.length, + }); + } + }, + }); + }); + + return Promise.all(uploadPromises); +} + +/** + * 选择并上传图片 + * @param {string} url - 上传接口URL + * @param {Object} options - 选择和上传选项 + * @returns {Promise} + */ +export function chooseAndUploadImage(url, options = {}) { + return new Promise((resolve, reject) => { + const { + count = 1, + sizeType = ['compressed'], + sourceType = ['album', 'camera'], + ...uploadOptions + } = options; + + uni.chooseImage({ + count, + sizeType, + sourceType, + success: (chooseRes) => { + console.log('📷 选择图片成功:', chooseRes); + + if (count === 1) { + // 单张图片上传 + uploadFile(url, { + ...uploadOptions, + filePath: chooseRes.tempFilePaths[0], + }) + .then(resolve) + .catch(reject); + } else { + // 多张图片上传 + uploadFiles(url, chooseRes.tempFiles, uploadOptions) + .then(resolve) + .catch(reject); + } + }, + fail: (err) => { + console.error('❌ 选择图片失败:', err); + reject(new Error(err.errMsg || '选择图片失败')); + }, + }); + }); +} + +/** + * 选择并上传文件 + * @param {string} url - 上传接口URL + * @param {Object} options - 选择和上传选项 + * @returns {Promise} + */ +export function chooseAndUploadFile(url, options = {}) { + return new Promise((resolve, reject) => { + const { count = 1, type = 'all', extension = [], ...uploadOptions } = options; + + // #ifdef MP-WEIXIN + // 微信小程序使用 wx.chooseMessageFile + uni.chooseMessageFile({ + count, + type, + extension, + success: (chooseRes) => { + console.log('📁 选择文件成功:', chooseRes); + + if (count === 1) { + uploadFile(url, { + ...uploadOptions, + filePath: chooseRes.tempFiles[0].path, + }) + .then(resolve) + .catch(reject); + } else { + uploadFiles(url, chooseRes.tempFiles, uploadOptions) + .then(resolve) + .catch(reject); + } + }, + fail: (err) => { + console.error('❌ 选择文件失败:', err); + reject(new Error(err.errMsg || '选择文件失败')); + }, + }); + // #endif + + // #ifndef MP-WEIXIN + // H5和其他平台使用普通文件选择 + uni.chooseFile({ + count, + type, + extension, + success: (chooseRes) => { + console.log('📁 选择文件成功:', chooseRes); + + if (count === 1) { + uploadFile(url, { + ...uploadOptions, + filePath: chooseRes.tempFilePaths[0], + }) + .then(resolve) + .catch(reject); + } else { + uploadFiles(url, chooseRes.tempFiles, uploadOptions) + .then(resolve) + .catch(reject); + } + }, + fail: (err) => { + console.error('❌ 选择文件失败:', err); + reject(new Error(err.errMsg || '选择文件失败')); + }, + }); + // #endif + }); +} + +/** + * 压缩图片 + * @param {string} src - 图片路径 + * @param {Object} options - 压缩选项 + * @returns {Promise} + */ +export function compressImage(src, options = {}) { + return new Promise((resolve, reject) => { + const { quality = 80, compressedWidth, compressedHeight } = options; + + uni.compressImage({ + src, + quality, + compressedWidth, + compressedHeight, + success: (res) => { + console.log('✅ 图片压缩成功:', res); + resolve(res.tempFilePath); + }, + fail: (err) => { + console.error('❌ 图片压缩失败:', err); + reject(new Error(err.errMsg || '图片压缩失败')); + }, + }); + }); +} + +/** + * 获取图片信息 + * @param {string} src - 图片路径 + * @returns {Promise} + */ +export function getImageInfo(src) { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src, + success: (res) => { + resolve(res); + }, + fail: (err) => { + reject(new Error(err.errMsg || '获取图片信息失败')); + }, + }); + }); +} + +// 默认导出 +export default { + uploadFile, + uploadFiles, + chooseAndUploadImage, + chooseAndUploadFile, + compressImage, + getImageInfo, +}; From e97c97aa0b3164e609b769f7117545bacdfdf308 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 24 Oct 2025 22:21:01 +0800 Subject: [PATCH 05/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mobile/src/common/README-API调用规范.md | 413 ------------------------ mobile/src/common/README-dict.md | 200 ------------ 2 files changed, 613 deletions(-) delete mode 100644 mobile/src/common/README-API调用规范.md delete mode 100644 mobile/src/common/README-dict.md diff --git a/mobile/src/common/README-API调用规范.md b/mobile/src/common/README-API调用规范.md deleted file mode 100644 index c4b02c1b..00000000 --- a/mobile/src/common/README-API调用规范.md +++ /dev/null @@ -1,413 +0,0 @@ -# 移动端API调用标准规范 - -> 📌 **重要提示**: 移动端与Web端的API调用方式完全不同,请严格遵守本规范! - -## 🚫 常见错误 - -### ❌ 不要这样做(Web端方式) -```javascript -// 错误1:使用 this.$http -const res = await this.$http.get('/api/path'); -const res = await this.$http.post('/api/path', data); -const res = await this.$http.upload('/api/upload', options); - -// 错误2:使用 getDicts -import { getDicts } from '@/api/system/dict/data.js'; -const res = await getDicts('dict_type'); -this.dictData = res.data; // Web端返回格式 - -// 错误3:使用 this.$u.toast -this.$u.toast('提示信息'); -``` - -## ✅ 正确做法(移动端方式) - -### 1. HTTP请求 - -```javascript -// 导入 -import { request } from '@/common/request.js'; - -// GET请求(无参数) -const res = await request({ - url: '/api/path', - method: 'GET', -}); - -// GET请求(带参数) -const res = await request({ - url: '/api/path', - method: 'GET', - params: { - key1: 'value1', - key2: 'value2' - }, -}); - -// POST请求 -const res = await request({ - url: '/api/path', - method: 'POST', - data: { - key1: 'value1', - key2: 'value2' - }, -}); - -// PUT请求 -const res = await request({ - url: '/api/path', - method: 'PUT', - data: { ... }, -}); - -// DELETE请求 -const res = await request({ - url: '/api/path', - method: 'DELETE', -}); -``` - -### 2. 字典数据获取 - -```javascript -// 导入 -import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js'; - -// 获取字典数据(直接返回数组) -const dictData = await getDict('dict_type'); // 返回: [{label, value, ...}, ...] - -// 批量获取字典 -const [dict1, dict2] = await Promise.all([ - getDict('dict_type_1'), - getDict('dict_type_2'), -]); - -// 获取字典标签 -const label = getDictLabel(dictData, '1'); // 返回: '标签名' - -// 获取字典Tag类型(用于u-tag组件) -const tagType = getDictTagType(dictData, '1'); // 返回: 'success', 'warning', 'error' 等 -``` - -### 3. 字典展示(u-tag) - -```vue - - - -``` - -### 4. 文件上传 - -```javascript -// 导入 -import { uploadFile } from '@/utils/upload.js'; - -// 选择并上传图片 -uni.chooseImage({ - count: 1, - success: async (res) => { - const filePath = res.tempFilePaths[0]; - - // 上传文件 - const result = await uploadFile(filePath, (progress) => { - console.log('上传进度:', progress); - // 更新进度条 - this.uploadProgress = progress; - }); - - if (result && result.code === 200 && result.data) { - // 上传成功 - this.ossId = result.data.ossId; - this.fileUrl = result.data.url; - } - } -}); -``` - -### 5. Toast提示 - -```javascript -// 普通提示 -uni.showToast({ - title: '提示信息', - icon: 'none' -}); - -// 成功提示 -uni.showToast({ - title: '操作成功', - icon: 'success' -}); - -// 失败提示 -uni.showToast({ - title: '操作失败', - icon: 'error' -}); - -// 加载中 -uni.showLoading({ - title: '加载中...', - mask: true -}); - -// 关闭加载 -uni.hideLoading(); -``` - -## 📊 完整示例 - -```vue - - - -``` - -## 🔑 核心差异对照表 - -| 功能 | Web端 | 移动端 | -|------|-------|--------| -| **HTTP请求** | `this.$http.get('/api')` | `request({ url: '/api', method: 'GET' })` | -| **字典获取** | `getDicts('type')` 返回 `{data:[]}` | `getDict('type')` 返回 `[...]` | -| **字典标签** | `selectDictLabel(dict, value)` | `getDictLabel(dict, value)` | -| **Tag类型** | 手动映射 | `getDictTagType(dict, value)` | -| **文件上传** | `this.$http.upload()` | `uploadFile(path, callback)` | -| **Toast提示** | `this.$u.toast('msg')` | `uni.showToast({ title: 'msg' })` | -| **加载提示** | `this.$u.loading.show()` | `uni.showLoading({ title: '...' })` | - -## 📝 注意事项 - -1. **导入路径固定**: - - HTTP请求: `@/common/request.js` - - 字典方法: `@/common/dict.js` - - 文件上传: `@/utils/upload.js` - -2. **字典数据格式**: - - 移动端 `getDict()` 直接返回数组,不需要 `.data` - - 字典项格式: `{ label, value, elTagType, elTagClass, remark }` - -3. **Tag类型自动转换**: - - `danger` → `error` - - `default` → `primary` - -4. **文件上传进度**: - - 进度回调返回 0-100 的数字 - - Web端可能是 `progress.progress`,移动端直接是数字 - -5. **错误处理**: - - 统一使用 `try-catch` - - 错误时使用 `uni.showToast` 提示 - -## 🔗 相关文档 - -- `mobile/src/common/request.js` - HTTP请求源码 -- `mobile/src/common/dict.js` - 字典方法源码 -- `mobile/src/utils/upload.js` - 文件上传源码 -- `.cursor/cursorrules-mobile.md` - 移动端开发规范 - ---- - -**最后更新**: 2025-01-24 -**适用范围**: 移动端工程(Vue 2.6.14 + uni-app 2.x) - diff --git a/mobile/src/common/README-dict.md b/mobile/src/common/README-dict.md deleted file mode 100644 index fab362b2..00000000 --- a/mobile/src/common/README-dict.md +++ /dev/null @@ -1,200 +0,0 @@ -# 移动端字典工具使用指南 - -## 📋 简介 - -为移动端(Vue 2.6.14 + uni-app 2.x)提供完整的字典数据获取和管理功能,与web端保持一致的API设计。 - -## 🚀 快速开始 - -### 1. 基础导入 -```javascript -import { useDict, getDict, getDictLabel } from '@/common/dict.js' -``` - -### 2. 在页面中使用 -```javascript -export default { - data() { - return { - dictionaries: {}, - userInfo: { - gender: '0', - status: '1' - } - } - }, - - created() { - this.loadDictionaries() - }, - - methods: { - async loadDictionaries() { - // 批量加载字典 - const dictManager = useDict('sys_user_sex', 'sys_normal_disable') - - // Vue 2响应式更新(必须设置) - dictManager.setUpdateCallback((dictType, data) => { - this.$set(this.dictionaries, dictType, data) - }) - }, - - // 获取字典标签显示 - getDictText(dictType, value) { - const dictData = this.dictionaries[dictType] || [] - return getDictLabel(dictData, value) - } - } -} -``` - -### 3. 在模板中显示 -```vue - -``` - -## 📚 主要功能 - -| 功能 | 方法 | 说明 | -|------|------|------| -| 批量字典管理 | `useDict(...types)` | 推荐使用,支持多字典并发加载 | -| 单个字典获取 | `getDict(type, cache?)` | 获取单个字典数据 | -| 字典标签获取 | `getDictLabel(data, value)` | 根据值获取标签 | -| 字典值获取 | `getDictValue(data, label)` | 根据标签获取值 | -| 值存在检查 | `hasDictValue(data, value)` | 检查值是否存在 | -| 缓存刷新 | `refreshDict(types)` | 刷新指定字典缓存 | - -## 🎯 使用场景 - -### 场景1: uview组件集成 -```javascript -// radio-group - - - {{ item.label }} - - - -// select组件 -computed: { - selectOptions() { - return (this.dictionaries.sys_user_sex || []).map(item => ({ - value: item.value, - label: item.label - })) - } -} -``` - -### 场景2: 表格数据转换 -```javascript -computed: { - processedData() { - return this.tableData.map(row => ({ - ...row, - genderText: getDictLabel(this.dictionaries.sys_user_sex || [], row.gender), - statusText: getDictLabel(this.dictionaries.sys_normal_disable || [], row.status) - })) - } -} -``` - -### 场景3: 单独获取字典 -```javascript -async methods: { - async loadSingleDict() { - try { - const genderDict = await getDict('sys_user_sex') - console.log('性别字典:', genderDict) - } catch (error) { - console.error('加载失败:', error) - } - } -} -``` - -## ⚡ 性能特性 - -- **智能缓存**: 24小时有效期,自动管理 -- **并发加载**: 多字典同时请求,提升加载速度 -- **响应式更新**: 完美适配Vue 2响应式系统 -- **错误处理**: 完善的错误处理和兜底机制 - -## 🔧 高级用法 - -### 缓存管理 -```javascript -import { dictCache, refreshDict } from '@/common/dict.js' - -// 清除指定缓存 -dictCache.remove('sys_user_sex') - -// 清除所有缓存 -dictCache.clear() - -// 刷新缓存 -await refreshDict(['sys_user_sex', 'sys_normal_disable']) -``` - -### 错误处理 -```javascript -// 安全的字典获取(带默认值) -safeDictLabel(dictType, value, defaultLabel = '-') { - try { - const dictData = this.dictionaries[dictType] || [] - const label = getDictLabel(dictData, value) - return label || defaultLabel - } catch (error) { - console.error(`获取字典失败 ${dictType}:${value}`, error) - return defaultLabel - } -} -``` - -## 📖 相关文件 - -- **核心实现**: `/mobile/src/common/dict.js` -- **使用示例**: `/mobile/src/common/dict-usage-example.js` -- **变更记录**: `/docs/变更管理/code-adjust-record-2025-01-27-移动端字典工具实现.md` - -## ⚠️ 重要提醒 - -### Vue 2必须使用回调机制 -```javascript ❌ -// 错误:直接赋值不会触发响应式更新 -this.dictionaries = dictManager.data - -// 正确:使用回调确保响应式更新 ✅ -dictManager.setUpdateCallback((dictType, data) => { - this.$set(this.dictionaries, dictType, data) -}) -``` - -### 兼容性 -- ✅ Vue 2.6.14+ -- ✅ uni-app 2.x -- ✅ H5 / 小程序 / App - -## 🎯 最佳实践 - -1. **优先使用useDict**: 支持并发加载,性能更好 -2. **合理使用缓存**: 频繁变动的字典设置`useCache=false` -3. **添加错误处理**: 关键业务场景要有异常处理 -4. **使用callback机制**: Vue 2中必须使用setUpdateCallback - ---- -✨ **完成时间**: 2025-01-27 20:15:00 -👨‍💻 **开发者**: wuzilong -📱 **适用版本**: Vue 2.6.14 + uni-app 2.x \ No newline at end of file From 1b117de6cfefa10f484efa357232dca604042747 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 24 Oct 2025 23:34:48 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/问题跟踪/后端文件上传数据丢失问题.md | 300 ++++++++ .../xgss/wsjc/components/SingleRoomCheck.vue | 660 ++++++++++++++++-- .../wsjc/components/ViolationRecordDialog.vue | 388 ++++++++-- 3 files changed, 1246 insertions(+), 102 deletions(-) create mode 100644 docs/问题跟踪/后端文件上传数据丢失问题.md diff --git a/docs/问题跟踪/后端文件上传数据丢失问题.md b/docs/问题跟踪/后端文件上传数据丢失问题.md new file mode 100644 index 00000000..2690fc35 --- /dev/null +++ b/docs/问题跟踪/后端文件上传数据丢失问题.md @@ -0,0 +1,300 @@ +# 后端文件上传数据丢失问题 + +**发现日期**: 2025-01-24 +**问题模块**: 文件上传/OSS服务 +**影响范围**: 所有使用附件上传的业务模块 +**严重程度**: ⚠️ 中等(影响用户体验,但有前端兜底) + +--- + +## 问题描述 + +后端在保存文件时丢失了真实的文件名和扩展名信息,导致前端回显时无法正确识别文件类型。 + +### 问题表现 + +从 `/resource/oss/listByIds/{ossIds}` API 返回的数据中: + +```javascript +{ + ossId: "1981738218223079425", + originalName: "12038a7c-48bd-4978-bcbb-1119a201a6e9", // ❌ UUID,不是用户上传的真实文件名 + fileSuffix: "9", // ❌ 数字字符,不是真实扩展名(应该是 jpg/png 等) + fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139", // ❌ 没有扩展名 + url: "/resource/oss/download/1981738218223079425" +} +``` + +### 期望的数据格式 + +```javascript +{ + ossId: "1981738218223079425", + originalName: "违纪现场照片.jpg", // ✅ 用户上传时的真实文件名 + fileSuffix: "jpg", // ✅ 真实的文件扩展名 + fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139.jpg", // ✅ 包含扩展名 + url: "/resource/oss/download/1981738218223079425" +} +``` + +--- + +## 问题分析 + +### 可能的原因 + +1. **文件上传时未保存原始文件名** + - 上传接口 `/resource/oss/upload` 可能未正确提取文件名 + - `originalName` 字段被UUID覆盖 + +2. **扩展名提取错误** + - `fileSuffix` 字段提取逻辑有误 + - 从文件路径末尾提取了一个字符(如 `9`、`e`、`d`)而不是真实扩展名 + +3. **数据库保存逻辑问题** + - `sys_oss` 表中的字段未正确保存 + - 可能是某个字段映射错误 + +### 影响的业务场景 + +- ✅ **新增上传**:正常工作(文件可以上传) +- ❌ **回显展示**:文件类型识别错误,图片无法正常显示 +- ❌ **下载功能**:下载的文件名显示为UUID,用户体验差 +- ❌ **文件管理**:管理员无法识别文件内容 + +--- + +## 复现步骤 + +1. 在移动端进入"宿舍卫生检查"模块 +2. 点击"录入违纪",上传一张图片(如 `photo.jpg`) +3. 保存违纪记录 +4. 刷新页面或重新进入 +5. 点击"编辑"查看违纪记录 +6. **问题**:附件无法显示为图片,文件名显示为UUID + +--- + +## 调试信息 + +### 前端上传请求 + +```javascript +// 调用上传接口 +POST /resource/oss/upload +Content-Type: multipart/form-data + +// FormData: +file: [Blob] photo.jpg +bizType: "dorm_violation_attachment" +``` + +### 后端返回(上传成功) + +```javascript +{ + code: 200, + data: { + ossId: "1981738218223079425", + url: "/resource/oss/download/1981738218223079425", + fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139" // ⚠️ 缺少扩展名 + } +} +``` + +### 后端返回(回显查询) + +```javascript +// GET /resource/oss/listByIds/1981738218223079425 +{ + code: 200, + data: [{ + ossId: "1981738218223079425", + originalName: "12038a7c-48bd-4978-bcbb-1119a201a6e9", // ❌ UUID + fileSuffix: "9", // ❌ 错误 + fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139", + url: "/resource/oss/download/1981738218223079425" + }] +} +``` + +--- + +## 建议的修复方案 + +### 方案1:修复上传接口(推荐)⭐ + +**修改文件**:`backend/src/main/java/com/ruoyi/resource/controller/SysOssController.java` + +**修复点**: +1. 上传时正确提取并保存 `originalName`(用户上传的文件名) +2. 正确提取文件扩展名保存到 `fileSuffix` +3. 保存到OSS的文件名应包含扩展名 + +```java +@PostMapping("/upload") +public R upload(@RequestParam("file") MultipartFile file, + @RequestParam(required = false) String bizType) { + // 获取原始文件名 + String originalFilename = file.getOriginalFilename(); + + // 提取真实扩展名 + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1); + } + + // 生成OSS文件名(包含扩展名) + String ossFileName = generateOssFileName() + "." + extension; + + // 保存到数据库 + SysOss oss = new SysOss(); + oss.setOriginalName(originalFilename); // ✅ 保存真实文件名 + oss.setFileSuffix(extension); // ✅ 保存真实扩展名 + oss.setFileName(ossFileName); // ✅ 包含扩展名的OSS文件名 + + // ... 其他逻辑 +} +``` + +### 方案2:修复查询接口 + +**修改文件**:`backend/src/main/java/com/ruoyi/resource/service/impl/SysOssServiceImpl.java` + +**修复点**: +1. 从数据库正确读取 `originalName` 和 `fileSuffix` +2. 确保返回的VO类包含正确的字段值 + +```java +public List listByIds(String ossIds) { + // ... 查询逻辑 ... + + List voList = new ArrayList<>(); + for (SysOss oss : ossList) { + SysOssVo vo = new SysOssVo(); + vo.setOssId(oss.getOssId()); + vo.setOriginalName(oss.getOriginalName()); // ✅ 确保读取正确 + vo.setFileSuffix(oss.getFileSuffix()); // ✅ 确保读取正确 + vo.setFileName(oss.getFileName()); + vo.setUrl(oss.getUrl()); + voList.add(vo); + } + + return voList; +} +``` + +### 方案3:数据库表结构检查 + +**检查表**:`sys_oss` + +**需要确认的字段**: +```sql +-- 检查字段定义 +SHOW COLUMNS FROM sys_oss LIKE '%name%'; +SHOW COLUMNS FROM sys_oss LIKE '%suffix%'; + +-- 检查数据示例 +SELECT oss_id, original_name, file_suffix, file_name +FROM sys_oss +WHERE oss_id = '1981738218223079425'; +``` + +**期望的字段定义**: +- `original_name` VARCHAR(255) - 用户上传的原始文件名 +- `file_suffix` VARCHAR(10) - 文件扩展名(jpg/png/pdf等) +- `file_name` VARCHAR(500) - OSS中的文件路径(应包含扩展名) + +--- + +## 临时解决方案(前端兜底) + +**已实施**:在 `ViolationRecordDialog.vue` 中添加了兜底逻辑 + +```javascript +// 检测 fileSuffix 是否为有效扩展名 +if (item.fileSuffix && item.fileSuffix.length <= 5 && /^[a-zA-Z]+$/.test(item.fileSuffix)) { + // 使用 fileSuffix + realExtension = item.fileSuffix.toLowerCase(); + fileType = this.getFileTypeBySuffix(realExtension); +} else { + // 默认按图片处理(违纪证明材料大概率是图片) + realExtension = 'jpg'; + fullFileName = `违纪证明_${timestamp + index}.jpg`; + fileType = 'image/jpeg'; +} +``` + +**局限性**: +- ✅ 可以临时解决图片回显问题 +- ❌ 无法恢复真实的文件名 +- ❌ 非图片文件可能被错误识别 +- ❌ 用户体验不佳 + +--- + +## 验证方法 + +### 1. 后端修复后的验证 + +**步骤**: +1. 上传一张图片 `test.jpg` +2. 调用 `/resource/oss/listByIds/{ossId}` 查询 +3. 检查返回数据: + - `originalName` 应该是 `test.jpg` + - `fileSuffix` 应该是 `jpg` + - `fileName` 应该包含 `.jpg` 扩展名 + +**期望日志**: +``` +违纪记录:原始数据 - originalName=test.jpg, fileSuffix=jpg, fileName=2025/10/24/xxx.jpg +违纪记录:最终文件名=test.jpg, 扩展名=jpg, 文件类型=image/jpeg +``` + +### 2. 前端验证 + +**步骤**: +1. 上传图片后编辑违纪记录 +2. 检查附件回显是否正常 +3. 检查文件名是否正确显示 + +**期望结果**: +- ✅ 图片正常显示(不是灰白背景) +- ✅ 文件名显示为真实文件名(不是UUID) +- ✅ 下载文件名正确 + +--- + +## 相关文件 + +**后端**: +- `backend/src/main/java/com/ruoyi/resource/controller/SysOssController.java` +- `backend/src/main/java/com/ruoyi/resource/service/impl/SysOssServiceImpl.java` +- `backend/src/main/java/com/ruoyi/resource/domain/vo/SysOssVo.java` +- `backend/src/main/java/com/ruoyi/resource/domain/SysOss.java` + +**前端**: +- `mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue`(已添加兜底逻辑) + +**数据库**: +- `sys_oss` 表 + +--- + +## 优先级和影响 + +**优先级**: P2(中等) + +**影响评估**: +- **用户体验**: ⬇️ 下降(文件名显示为UUID,无法识别内容) +- **功能可用性**: ✅ 正常(有前端兜底,基本功能可用) +- **数据完整性**: ⚠️ 受影响(历史数据无法恢复真实文件名) + +**建议处理时间**: 2周内修复 + +--- + +**创建时间**: 2025-01-24 +**创建人**: Cursor AI +**状态**: 🔴 待修复 + diff --git a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue index 64eb75d9..39505066 100644 --- a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue +++ b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue @@ -519,6 +519,28 @@ @select="handleUploadActionSelect" @close="showUploadActionSheet = false" > + + + + + + @@ -590,6 +612,11 @@ export default { currentViolation: null, currentViolationIndex: -1, + // 删除违纪记录确认对话框 + showDeleteConfirm: false, + deleteViolationStudentId: null, + deleteViolationIndex: -1, + // 附件数据 attachmentList: [], @@ -601,6 +628,10 @@ export default { // 附件上传操作选择框 showUploadActionSheet: false, uploadActions: [{ name: '选择图片' }], + + // 删除附件确认弹窗 + showDeleteImageConfirm: false, + deleteImageTarget: null, }; }, computed: { @@ -712,6 +743,10 @@ export default { await this.checkWechatEnvironment(); this.initComponent(); }, + beforeDestroy() { + // 清理blob URL,防止内存泄漏 + this.cleanupBlobUrls(); + }, methods: { // 检查微信环境并自动初始化SDK async checkWechatEnvironment() { @@ -881,9 +916,425 @@ export default { }, // 加载编辑数据 - loadEditData() { - // TODO: 加载现有检查数据进行编辑 - console.log('加载编辑数据:', this.existingData); + async loadEditData() { + console.log('📝 加载编辑数据:', this.existingData); + + if (!this.existingData) { + console.warn('⚠️ 没有现有数据'); + return; + } + + try { + // 1️⃣ 回显表单基础数据 + this.formData.disciplineTypeId = this.existingData.disciplineTypeId || null; + this.formData.disciplineTypeName = this.existingData.disciplineTypeName || ''; + this.formData.totalScore = this.existingData.totalScore || 0; + this.formData.hygieneGrade = this.existingData.hygieneGrade || 'A'; + this.formData.checkRemarks = this.existingData.checkRemarks || ''; + this.formData.attachmentIds = this.existingData.attachmentIds || ''; + + console.log('✅ 表单基础数据回显完成:', this.formData); + + // 2️⃣ 回显附件数据 + if (this.existingData.attachmentIds) { + await this.loadAttachments(this.existingData.attachmentIds); + } + + // 3️⃣ 从后端接口加载学生评分数据、违纪记录和请假信息(复用Web端接口) + // 注意:loadStudentScoresFromAPI 内部会自动调用 loadViolationRecordsFromAPI 和 loadStudentLeaves + if (this.existingData.id) { + await this.loadStudentScoresFromAPI(this.existingData.id); + } + + console.log('✅ 所有数据回显完成'); + } catch (error) { + console.error('❌ 加载编辑数据失败:', error); + uni.showToast({ title: '加载数据失败', icon: 'none' }); + } + }, + + // 从后端API加载学生评分数据(复用Web端接口) + async loadStudentScoresFromAPI(checkId) { + if (!checkId) { + console.warn('⚠️ 没有检查ID,无法加载学生评分数据'); + return; + } + + this.studentsLoading = true; + try { + console.log('🔄 从API加载学生评分数据,检查ID:', checkId); + + const res = await request({ + url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${checkId}`, + method: 'GET', + }); + + if (res.code === 200 && res.data && res.data.length > 0) { + console.log('✅ 成功获取学生评分数据:', res.data.length, '条'); + + // 将后端返回的学生评分数据映射到roomStudents + this.roomStudents = res.data.map((studentScore) => ({ + // 学生基本信息 + studentId: studentScore.studentId, + studentName: studentScore.studentName, + studentNo: studentScore.studentNo, + className: studentScore.className, + majorName: studentScore.majorName, + collegeName: studentScore.collegeName, + bedId: studentScore.bedId, + bedName: studentScore.bedCode, + bedCode: studentScore.bedCode, + gender: studentScore.gender || '1', + // 评分数据 + student_score: studentScore.studentScore || 0, + attendance_status: studentScore.attendanceStatus || '1', + student_grade: studentScore.studentGrade || 'A', + isResponsiblePerson: studentScore.isResponsiblePerson || 0, + scoreRemarks: studentScore.scoreRemarks || '', + improvementNotes: studentScore.improvementNotes || '', + // UI状态 + showAttendancePicker: false, + showGradePicker: false, + })); + + console.log('✅ 学生评分数据映射完成'); + + // 🔴 同时加载违纪记录和请假信息(复用Web端逻辑) + await Promise.all([ + this.loadViolationRecordsFromAPI(checkId), + this.loadStudentLeaves(), + ]); + } else { + console.warn('⚠️ 未获取到学生评分数据,保持当前学生列表'); + } + } catch (error) { + console.error('❌ 加载学生评分数据失败:', error); + uni.showToast({ title: '加载学生评分数据失败', icon: 'none' }); + } finally { + this.studentsLoading = false; + } + }, + + // 从后端API加载违纪记录数据(复用Web端接口) + async loadViolationRecordsFromAPI(checkId) { + if (!checkId) { + console.warn('⚠️ 没有检查ID,无法加载违纪记录数据'); + return; + } + + try { + console.log('🔄 从API加载违纪记录数据,检查ID:', checkId); + + const res = await request({ + url: `/dormHygieneCheck/dormHygieneCheck/getViolations/${checkId}`, + method: 'GET', + }); + + if (res.code === 200 && res.data) { + console.log('✅ 成功获取违纪记录数据:', res.data.length, '条'); + + // 清空现有违纪记录 + this.violationRecords.clear(); + + // 按学生ID分组违纪记录 + res.data.forEach((violation) => { + const studentId = violation.studentId; + if (!studentId) return; + + const violations = this.violationRecords.get(studentId) || []; + violations.push({ + disciplineTypeId: violation.disciplineTypeId, + disciplineTypeName: violation.disciplineTypeName, + scoreDeduction: violation.scoreDeduction, + violationDescription: + violation.disciplineContent || violation.disciplineReason || '', + // 🔴 统一使用 attachmentOssIds 字段名 + attachmentOssIds: violation.attachmentOssIds || '', + }); + + this.violationRecords.set(studentId, violations); + console.log( + `✅ 学生ID ${studentId} 违纪记录加载完成,数量:`, + violations.length, + ); + }); + + console.log( + '✅ 所有违纪记录数据加载完成,总学生数:', + this.violationRecords.size, + ); + } else { + console.warn('⚠️ 未获取到违纪记录数据'); + } + } catch (error) { + console.error('❌ 加载违纪记录数据失败:', error); + // 不显示错误提示,因为可能确实没有违纪记录 + } + }, + + // 加载附件数据 + async loadAttachments(attachmentIds) { + if (!attachmentIds) { + console.log('📎 没有附件数据'); + return; + } + + try { + console.log('📎 开始加载附件数据, IDs:', attachmentIds); + + const ossIds = attachmentIds.toString().trim(); + const response = await request({ + url: `/resource/oss/listByIds/${ossIds}`, + method: 'GET', + }); + + if (response.code === 200 && response.data) { + console.log('📎 附件数据加载成功,数量:', response.data.length); + + const attachmentFiles = response.data.map((item, index) => { + // 使用fileSuffix字段判断文件类型 + const fileType = this.getFileTypeBySuffix(item.fileSuffix); + const isImage = this.isImageFile({ + name: item.originalName, + type: fileType, + }); + + // 处理图片URL - 确保有正确的API前缀 + let processedUrl = item.url; + if (isImage && item.url && item.url.startsWith('/resource/')) { + // 图片文件需要添加/api前缀 + processedUrl = `/api${item.url}`; + } + + // 构造完整的文件名(包含扩展名) + let fullFileName = item.originalName || `附件${index + 1}`; + if ( + item.fileSuffix && + !fullFileName + .toLowerCase() + .endsWith(`.${item.fileSuffix.toLowerCase()}`) + ) { + fullFileName = `${fullFileName}.${item.fileSuffix}`; + } + + const fileObj = { + uid: `existing_${index}_${Date.now()}`, + name: fullFileName, + url: processedUrl, + ossId: item.ossId, + status: 'success', + type: fileType, + size: item.fileSize || 0, + }; + + console.log(`📎 附件${index + 1}:`, fileObj); + return fileObj; + }); + + this.attachmentList = attachmentFiles; + + // 为图片文件获取blob数据以确保正确显示 + await this.processImageFiles(); + + // 强制刷新组件显示 + this.$nextTick(() => { + // 预加载图片以确保能正确显示 + this.preloadImages(); + }); + + console.log('✅ 附件数据处理完成'); + } else { + console.warn('⚠️ 获取附件信息失败:', response.msg); + this.attachmentList = []; + } + } catch (error) { + console.error('❌ 获取附件失败:', error); + this.attachmentList = []; + } + }, + + // 根据文件后缀获取文件类型 + getFileTypeBySuffix(fileSuffix) { + if (!fileSuffix) return ''; + + // 移除点号,转换为小写 + const ext = fileSuffix.replace('.', '').toLowerCase(); + + const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; + if (imageTypes.includes(ext)) { + const mimeTypeMap = { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + bmp: 'image/bmp', + webp: 'image/webp', + }; + return mimeTypeMap[ext] || `image/${ext}`; + } + + // 其他文件类型 + const docTypes = { + pdf: 'application/pdf', + doc: 'application/msword', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + xls: 'application/vnd.ms-excel', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }; + + return docTypes[ext] || `file/${ext}`; + }, + + // 处理图片文件,获取blob数据以确保正确显示 + async processImageFiles() { + console.log('=== 处理图片文件 ==='); + const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file)); + + for (const file of imageFiles) { + if (file.url && !file.blobUrl) { + try { + console.log(`处理图片文件: ${file.name}`); + const blobUrl = await this.getImageBlobUrl(file.url); + if (blobUrl) { + // 更新文件对象,添加blob URL,并标记为图片 + file.blobUrl = blobUrl; + file.url = blobUrl; // 🔴 关键:使用blob URL作为显示URL,解决灰白背景问题 + file.fileType = 'image'; + file.type = + file.type && file.type.startsWith('image') ? file.type : 'image'; + file.isImage = true; + console.log(`图片文件处理成功: ${file.name} - ${blobUrl}`); + } + } catch (error) { + console.error(`处理图片文件失败: ${file.name}`, error); + } + } + } + }, + + // 获取图片blob URL + async getImageBlobUrl(imageUrl) { + try { + console.log(`获取图片blob数据: ${imageUrl}`); + + // 处理图片URL路径 + let apiUrl = imageUrl; + if (imageUrl.startsWith('/api/')) { + // 移除/api前缀,因为request会自动添加baseURL + apiUrl = imageUrl.replace('/api', ''); + } else if (imageUrl.startsWith('/resource/')) { + // 直接使用相对路径 + apiUrl = imageUrl; + } else if (imageUrl.startsWith('http')) { + // 完整URL,需要特殊处理 + console.log('检测到完整URL,直接返回原URL'); + return imageUrl; + } + + console.log(`API请求URL: ${apiUrl}`); + + // 使用request方法获取图片数据 + const response = await request({ + url: apiUrl, + method: 'GET', + responseType: 'arraybuffer', // 设置响应类型为arraybuffer + header: { + isToken: true, + }, + }); + + // 将arraybuffer转换为blob + const blob = new Blob([response], { type: 'image/*' }); + const blobUrl = URL.createObjectURL(blob); + + console.log(`成功创建blob URL: ${blobUrl}`); + return blobUrl; + } catch (error) { + console.error(`获取图片blob数据失败: ${imageUrl}`, error); + // 如果请求失败,返回原始URL作为备用方案 + return imageUrl; + } + }, + + // 预加载图片以确保能正确显示 + async preloadImages() { + console.log('=== 预加载图片 ==='); + const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file)); + + for (const file of imageFiles) { + if (file.blobUrl || file.url) { + try { + const imgUrl = file.blobUrl || file.url; + console.log(`预加载图片: ${file.name} - ${imgUrl}`); + // 注意:在uni-app中,可能需要使用不同的预加载方式 + // 这里只是简单的日志记录,实际预加载由image标签自动完成 + } catch (error) { + console.error(`预加载图片失败: ${file.name}`, error); + } + } + } + }, + + // 回显学生评分数据 + loadStudentScores(studentScores) { + console.log('👥 回显学生评分数据:', studentScores); + + studentScores.forEach((scoreData) => { + const student = this.roomStudents.find((s) => s.studentId === scoreData.studentId); + if (student) { + student.student_score = scoreData.studentScore || 0; + student.attendance_status = scoreData.attendanceStatus || '1'; + student.student_grade = scoreData.studentGrade || 'A'; + student.scoreRemarks = scoreData.scoreRemarks || ''; + student.improvementNotes = scoreData.improvementNotes || ''; + console.log(`✅ 学生 ${student.studentName} 评分数据回显完成`); + } + }); + }, + + // 回显违纪记录数据 + loadViolationRecords(violationData) { + console.log('⚠️ 回显违纪记录数据:', violationData); + + this.violationRecords.clear(); + + violationData.forEach((violation) => { + const studentId = violation.studentId; + if (!studentId) return; + + const violations = this.violationRecords.get(studentId) || []; + violations.push({ + disciplineTypeId: violation.disciplineTypeId, + disciplineTypeName: violation.disciplineTypeName, + scoreDeduction: violation.scoreDeduction, + violationDescription: violation.violationDescription, + // 🔴 修复字段名不一致问题:统一使用 attachmentOssIds + // 兼容后端可能返回的两种字段名 + attachmentOssIds: + violation.violationAttachmentIds || violation.attachmentOssIds || '', + }); + + this.violationRecords.set(studentId, violations); + console.log( + `✅ 学生ID ${studentId} 违纪记录回显完成,附件IDs:`, + violation.violationAttachmentIds || violation.attachmentOssIds, + ); + }); + }, + + // 清理blob URL,防止内存泄漏 + cleanupBlobUrls() { + console.log('=== 清理blob URL ==='); + this.attachmentList.forEach((file) => { + if (file.blobUrl && file.blobUrl.startsWith('blob:')) { + try { + URL.revokeObjectURL(file.blobUrl); + console.log(`已清理blob URL: ${file.name}`); + } catch (error) { + console.error(`清理blob URL失败: ${file.name}`, error); + } + } + }); }, // 违纪类型确认处理 @@ -1150,39 +1601,109 @@ export default { // 上传文件到服务器 async uploadFileToServer(file) { try { + console.log('🚀 === 开始上传文件到服务器 ==='); + console.log('📁 待上传文件对象:', file); + console.log('📁 文件对象属性:', Object.keys(file)); + + // 更新文件状态 file.status = 'uploading'; // 🔴 关键修复:保存原始文件名,防止被后端UUID覆盖 const originalName = file.name; console.log('📝 上传前文件名:', originalName); + // 确定正确的文件路径 - uview2通常使用url属性 + const filePath = file.url || file.tempFilePath || file.path; + console.log('🎯 文件路径优先级选择:', { + url: file.url, + tempFilePath: file.tempFilePath, + path: file.path, + selected: filePath, + }); + + if (!filePath) { + const error = new Error('文件路径不存在'); + console.error('❌ 上传失败 - 文件路径为空:', file); + file.status = 'error'; + throw error; + } + + // 显示上传进度 + uni.showLoading({ + title: `上传${file.name}...`, + mask: true, + }); + console.log(`💫 开始上传文件: ${file.name}`); + + // 调用智能上传API(自动适配微信环境) + console.log('📡 调用智能上传API...'); const response = await smartUploadFile('/resource/oss/upload', file, { bizType: 'dorm_hygiene_check_attachment', }); - console.log('📡 上传响应:', response); - console.log('🔍 上传后file.name:', file.name); + console.log('📡 上传API响应:', response); + uni.hideLoading(); if (response && response.code === 200) { + // 上传成功 file.url = response.data.url; file.ossId = response.data.ossId; file.status = 'success'; // 🔴 恢复原始文件名,不使用后端返回的fileName(UUID) file.name = originalName; - console.log('✅ 恢复文件名为:', file.name); - uni.showToast({ title: '上传成功', icon: 'success', duration: 1500 }); - } else { - file.status = 'error'; + + console.log('✅ 文件上传成功:', { + fileName: file.name, + ossId: file.ossId, + url: file.url, + }); + uni.showToast({ - title: (response && response.msg) || '上传失败', + title: '上传成功', + icon: 'success', + duration: 1500, + }); + + return response; + } else { + // 上传失败 + file.status = 'error'; + const errorMsg = (response && response.msg) || '上传失败'; + console.error('❌ 上传失败 - API返回错误:', { + response: response, + fileName: file.name, + errorMsg: errorMsg, + }); + + uni.showToast({ + title: errorMsg, + icon: 'none', + duration: 3000, + }); + + throw new Error(errorMsg); + } + } catch (error) { + console.error('❌ 上传文件失败:', error); + file.status = 'error'; + uni.hideLoading(); + + // 如果是已知错误,直接显示 + if (error.message && error.message !== 'undefined') { + uni.showToast({ + title: error.message, + icon: 'none', + duration: 2000, + }); + } else { + uni.showToast({ + title: '上传失败,请重试', icon: 'none', duration: 2000, }); } - } catch (error) { - console.error('上传文件失败:', error); - file.status = 'error'; - uni.showToast({ title: '上传失败', icon: 'none', duration: 2000 }); + + throw error; } }, @@ -1401,41 +1922,47 @@ export default { event.preventDefault(); } - uni.showModal({ - title: '确认删除', - content: '确定要删除这张图片吗?删除后无法恢复。', - confirmColor: '#ff5722', - success: (res) => { - if (res.confirm) { - // 从附件列表中删除 - const attachmentIndex = this.attachmentList.findIndex( - (file) => - file.uid === image.uid || - (file.ossId && file.ossId === image.ossId) || - (file.url && file.url === image.url), - ); + // 🔴 修复:使用 u-modal 组件替代 uni.showModal,解决 z-index 层级冲突 + this.showDeleteImageConfirm = true; + this.deleteImageTarget = { image, index }; + }, - if (attachmentIndex >= 0) { - this.attachmentList.splice(attachmentIndex, 1); - } + // 确认删除图片 + confirmDeleteImage() { + const { image, index } = this.deleteImageTarget; - // 清理blob URL,防止内存泄漏 - if (image.blobUrl && image.blobUrl.startsWith('blob:')) { - URL.revokeObjectURL(image.blobUrl); - } + // 从附件列表中删除 + const attachmentIndex = this.attachmentList.findIndex( + (file) => + file.uid === image.uid || + (file.ossId && file.ossId === image.ossId) || + (file.url && file.url === image.url), + ); - uni.showToast({ - title: '删除成功', - icon: 'success', - }); - } - }, - fail: (err) => { - console.error('删除确认对话框显示失败:', err); - }, + if (attachmentIndex >= 0) { + this.attachmentList.splice(attachmentIndex, 1); + } + + // 清理blob URL,防止内存泄漏 + if (image.blobUrl && image.blobUrl.startsWith('blob:')) { + URL.revokeObjectURL(image.blobUrl); + } + + this.showDeleteImageConfirm = false; + this.deleteImageTarget = null; + + uni.showToast({ + title: '删除成功', + icon: 'success', }); }, + // 取消删除图片 + cancelDeleteImage() { + this.showDeleteImageConfirm = false; + this.deleteImageTarget = null; + }, + // 获取文件名短格式 getShortFileName(fileName) { if (!fileName) return ''; @@ -1574,24 +2101,39 @@ export default { // 删除违纪记录 removeViolationRecord(studentId, index) { - uni.showModal({ - title: '确认删除', - content: '确认删除这条违纪记录吗?', - success: (res) => { - if (res.confirm) { - const violations = this.violationRecords.get(studentId) || []; - violations.splice(index, 1); + // 保存要删除的记录信息 + this.deleteViolationStudentId = studentId; + this.deleteViolationIndex = index; + // 显示确认对话框 + this.showDeleteConfirm = true; + }, - if (violations.length === 0) { - this.violationRecords.delete(studentId); - } else { - this.violationRecords.set(studentId, violations); - } + // 确认删除违纪记录 + confirmDeleteViolation() { + if (this.deleteViolationStudentId !== null && this.deleteViolationIndex >= 0) { + const violations = this.violationRecords.get(this.deleteViolationStudentId) || []; + violations.splice(this.deleteViolationIndex, 1); - uni.showToast({ title: '违纪记录删除成功', icon: 'success' }); - } - }, - }); + if (violations.length === 0) { + this.violationRecords.delete(this.deleteViolationStudentId); + } else { + this.violationRecords.set(this.deleteViolationStudentId, violations); + } + + uni.showToast({ title: '违纪记录删除成功', icon: 'success' }); + } + + // 重置状态 + this.showDeleteConfirm = false; + this.deleteViolationStudentId = null; + this.deleteViolationIndex = -1; + }, + + // 取消删除违纪记录 + cancelDeleteViolation() { + this.showDeleteConfirm = false; + this.deleteViolationStudentId = null; + this.deleteViolationIndex = -1; }, // 获取学生违纪记录 diff --git a/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue b/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue index 2a7ac3c6..502a5e29 100644 --- a/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue +++ b/mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue @@ -312,6 +312,17 @@ @select="handleUploadActionSelect" @close="showUploadActionSheet = false" > + + + @@ -368,7 +379,25 @@ export default { { required: true, message: '请选择违纪类型', trigger: 'change' }, ], disciplineDate: [{ required: true, message: '请选择违纪日期', trigger: 'change' }], - scoreDeduction: [{ required: true, message: '请输入扣分分值', trigger: 'blur' }], + scoreDeduction: [ + { + required: true, + message: '请输入扣分分值', + trigger: 'blur', + validator: (rule, value, callback) => { + // 允许数字0,但不允许null、undefined、空字符串 + if (value === null || value === undefined || value === '') { + callback(new Error('请输入扣分分值')); + } else if (isNaN(value)) { + callback(new Error('扣分分值必须是数字')); + } else if (value < 0) { + callback(new Error('扣分分值不能小于0')); + } else { + callback(); + } + }, + }, + ], disciplineReason: [{ required: true, message: '请输入违纪原因', trigger: 'blur' }], }, @@ -399,6 +428,10 @@ export default { // 附件上传操作选择框 showUploadActionSheet: false, uploadActions: [{ name: '选择图片' }], + + // 删除确认弹窗 + showDeleteConfirm: false, + deleteTarget: null, }; }, computed: { @@ -458,6 +491,10 @@ export default { this.initDialog(); } }, + beforeUnmount() { + // 组件销毁前清理blob URL,防止内存泄漏 + this.cleanupBlobUrls(); + }, methods: { // === 微信SDK相关方法 === @@ -582,8 +619,8 @@ export default { this.allViolationTypes.length, ); - // 初始化表单 - this.initForm(); + // 🔴 关键修复:等待表单初始化完成(包括附件数据加载) + await this.initForm(); console.log('✅ 违纪记录弹窗初始化完成,最终数据量:', this.allViolationTypes.length); }, @@ -656,7 +693,7 @@ export default { }, // 初始化表单数据 - initForm() { + async initForm() { // 先设置默认日期(确保无论什么情况都有默认日期) this.disciplineDateValue = Number(new Date()); this.disciplineDateDisplay = dayjs().format('YYYY-MM-DD'); @@ -717,13 +754,14 @@ export default { }); } - // 如果是编辑模式,处理附件数据 + // 🔴 关键修复:如果是编辑模式,异步等待附件数据加载完成 if (this.violationData.attachmentOssIds) { console.log( '违纪记录:发现附件数据,准备回显:', this.violationData.attachmentOssIds, ); - this.handleAttachmentData(this.violationData.attachmentOssIds); + await this.handleAttachmentData(this.violationData.attachmentOssIds); + console.log('违纪记录:附件数据回显完成'); } else { console.log('违纪记录:编辑模式但无附件数据'); this.attachmentList = []; @@ -763,17 +801,94 @@ export default { if (response.code === 200 && response.data) { console.log('违纪记录:获取到的附件信息:', response.data); - const attachmentFiles = response.data.map((item, index) => ({ - uid: `existing_${index}_${Date.now()}`, - name: item.originalName || `附件${index + 1}`, - url: item.url, - ossId: item.ossId, - status: 'success', - type: this.getFileTypeByName(item.originalName), - })); + const attachmentFiles = response.data.map((item, index) => { + console.log(`违纪记录:处理第${index + 1}个附件:`, item); + console.log( + `违纪记录:原始数据 - originalName=${item.originalName}, fileSuffix=${item.fileSuffix}, fileName=${item.fileName}`, + ); + + // 处理URL路径 + let processedUrl = item.url || ''; + if ( + processedUrl && + !processedUrl.startsWith('http') && + !processedUrl.startsWith('/') + ) { + processedUrl = `/${processedUrl}`; + } + + // 🔴 核心修复:后端数据质量问题导致无法获取真实文件名和扩展名 + // 问题分析: + // 1. originalName 是 UUID(如 12038a7c-48bd-4978-bcbb-1119a201a6e9) + // 2. fileSuffix 是数字字符(如 "9"、"e"、"d") + // 3. fileName 也没有扩展名(如 2025/10/24/aa991fe4d6114995a0562d783cea56139) + // 解决方案:通过 Content-Type 检测文件类型,默认按图片处理 + + // 生成一个友好的文件名(不使用UUID) + const timestamp = new Date().getTime(); + let fullFileName = `附件_${timestamp + index}`; + let realExtension = ''; + let fileType = ''; + + // 尝试从 fileSuffix 判断(如果是有效的扩展名) + if ( + item.fileSuffix && + item.fileSuffix.length <= 5 && + /^[a-zA-Z]+$/.test(item.fileSuffix) + ) { + realExtension = item.fileSuffix.toLowerCase(); + fullFileName = `附件_${timestamp + index}.${realExtension}`; + fileType = this.getFileTypeBySuffix(realExtension); + console.log( + `违纪记录:从fileSuffix识别 - 扩展名=${realExtension}, 类型=${fileType}`, + ); + } + + // 如果fileSuffix无效,默认按图片处理(因为是违纪证明材料,大概率是图片) + if (!fileType || !fileType.startsWith('image/')) { + console.warn(`违纪记录:无法识别文件类型,默认按jpg图片处理`); + realExtension = 'jpg'; + fullFileName = `违纪证明_${timestamp + index}.jpg`; + fileType = 'image/jpeg'; + } + + console.log( + `违纪记录:最终文件名=${fullFileName}, 扩展名=${realExtension}, 文件类型=${fileType}`, + ); + + const fileObj = { + uid: `existing_${index}_${Date.now()}`, + name: fullFileName, + url: processedUrl, + ossId: item.ossId, + status: 'success', + type: fileType, + size: item.fileSize || 0, + // 为uview2组件添加必要的属性 + file: { + name: fullFileName, + type: fileType, + size: item.fileSize || 0, + path: processedUrl, + url: processedUrl, + }, + }; + + console.log(`违纪记录:构造的文件对象:`, fileObj); + return fileObj; + }); this.attachmentList = attachmentFiles; console.log('违纪记录:设置附件列表成功:', this.attachmentList); + + // 为图片文件获取blob数据以确保正确显示 + await this.processImageFiles(); + + // 强制刷新组件显示 + this.$nextTick(() => { + // 预加载图片以确保能正确显示 + this.preloadImages(); + }); } else { console.warn('违纪记录:获取附件信息失败:', response.msg); this.attachmentList = []; @@ -883,10 +998,19 @@ export default { // 扣分变化处理 handleScoreChange() { + // 确保转换为数字类型 const score = parseFloat(this.formData.scoreDeduction); - if (!isNaN(score) && score < 0) { + if (isNaN(score) || this.formData.scoreDeduction === '') { + // 如果不是有效数字或为空,保持为空字符串或设为0 + return; + } + + if (score < 0) { uni.showToast({ title: '扣分分数不能小于0', icon: 'none' }); this.formData.scoreDeduction = 0; + } else { + // 确保是数字类型 + this.formData.scoreDeduction = score; } }, @@ -1453,38 +1577,47 @@ export default { event.preventDefault(); } - uni.showModal({ - title: '确认删除', - content: '确定要删除这张图片吗?删除后无法恢复。', - confirmColor: '#ff5722', - success: (res) => { - if (res.confirm) { - // 从附件列表中查找并删除 - const attachmentIndex = this.attachmentList.findIndex( - (file) => - file.uid === image.uid || - (file.ossId && file.ossId === image.ossId) || - (file.url && file.url === image.url), - ); + // 🔴 修复:使用 u-modal 组件替代 uni.showModal,解决 z-index 层级冲突 + this.showDeleteConfirm = true; + this.deleteTarget = { image, index }; + }, - if (attachmentIndex >= 0) { - this.attachmentList.splice(attachmentIndex, 1); - } + // 确认删除图片 + confirmDelete() { + const { image, index } = this.deleteTarget; - // 释放blob URL(如果存在) - if (image.blobUrl && image.blobUrl.startsWith('blob:')) { - URL.revokeObjectURL(image.blobUrl); - } + // 从附件列表中查找并删除 + const attachmentIndex = this.attachmentList.findIndex( + (file) => + file.uid === image.uid || + (file.ossId && file.ossId === image.ossId) || + (file.url && file.url === image.url), + ); - uni.showToast({ - title: '删除成功', - icon: 'success', - }); - } - }, + if (attachmentIndex >= 0) { + this.attachmentList.splice(attachmentIndex, 1); + } + + // 释放blob URL(如果存在) + if (image.blobUrl && image.blobUrl.startsWith('blob:')) { + URL.revokeObjectURL(image.blobUrl); + } + + this.showDeleteConfirm = false; + this.deleteTarget = null; + + uni.showToast({ + title: '删除成功', + icon: 'success', }); }, + // 取消删除 + cancelDelete() { + this.showDeleteConfirm = false; + this.deleteTarget = null; + }, + // 获取文件名短格式 getShortFileName(fileName) { if (!fileName) return ''; @@ -1524,6 +1657,165 @@ export default { return `file/${ext}`; }, + // 根据文件后缀获取文件类型 + getFileTypeBySuffix(fileSuffix) { + if (!fileSuffix) return ''; + + // 移除点号,转换为小写 + const ext = fileSuffix.replace('.', '').toLowerCase(); + + // 图片类型判断 + const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; + if (imageTypes.includes(ext)) { + const mimeTypeMap = { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + bmp: 'image/bmp', + webp: 'image/webp', + }; + return mimeTypeMap[ext] || `image/${ext}`; + } + + // 文档类型判断 + const documentTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt']; + if (documentTypes.includes(ext)) { + return `file/${ext}`; + } + + return `file/${ext}`; + }, + + // 获取图片的blob数据(用于图片显示) + async getImageBlobUrl(imageUrl) { + try { + console.log(`获取图片blob数据: ${imageUrl}`); + + // 处理图片URL路径 + let apiUrl = imageUrl; + if (imageUrl.startsWith('/api/')) { + // 移除/api前缀,因为request会自动添加baseURL + apiUrl = imageUrl.replace('/api', ''); + } else if (imageUrl.startsWith('/resource/')) { + // 直接使用相对路径 + apiUrl = imageUrl; + } else if (imageUrl.startsWith('http')) { + // 完整URL,需要特殊处理 + console.log('检测到完整URL,直接返回原URL'); + return imageUrl; + } + + console.log(`API请求URL: ${apiUrl}`); + + // 使用request方法获取图片数据 + const response = await request({ + url: apiUrl, + method: 'GET', + responseType: 'arraybuffer', // 设置响应类型为arraybuffer + header: { + isToken: true, + }, + }); + + // 将arraybuffer转换为blob + const blob = new Blob([response], { type: 'image/*' }); + const blobUrl = URL.createObjectURL(blob); + + console.log(`成功创建blob URL: ${blobUrl}`); + return blobUrl; + } catch (error) { + console.error(`获取图片blob数据失败: ${imageUrl}`, error); + // 如果请求失败,返回原始URL作为备用方案 + return imageUrl; + } + }, + + // 处理图片文件,获取blob数据以确保正确显示 + async processImageFiles() { + console.log('=== 处理图片文件 ==='); + console.log('违纪记录:当前附件列表:', this.attachmentList); + + // 检查每个文件是否为图片 + this.attachmentList.forEach((file, index) => { + const isImage = this.isImageFile(file); + console.log( + `违纪记录:文件[${index}] ${file.name}, type=${file.type}, isImage=${isImage}`, + ); + }); + + const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file)); + console.log(`违纪记录:找到 ${imageFiles.length} 个图片文件`); + + for (const file of imageFiles) { + if (file.url && !file.blobUrl) { + try { + console.log(`违纪记录:开始处理图片: ${file.name}, url=${file.url}`); + const blobUrl = await this.getImageBlobUrl(file.url); + if (blobUrl) { + // 🔴 关键修复:同时更新url和blobUrl字段 + file.blobUrl = blobUrl; + file.url = blobUrl; // 使用blob URL作为显示URL,解决灰白背景问题 + file.fileType = 'image'; + file.type = + file.type && file.type.startsWith('image') ? file.type : 'image'; + file.isImage = true; + console.log(`违纪记录:图片处理成功: ${file.name} - ${blobUrl}`); + } else { + console.warn(`违纪记录:blob URL 为空: ${file.name}`); + } + } catch (error) { + console.error(`违纪记录:处理图片失败: ${file.name}`, error); + } + } else { + console.log( + `违纪记录:跳过处理: ${file.name}, url=${file.url}, blobUrl=${file.blobUrl}`, + ); + } + } + }, + + // 预加载图片以确保能正确显示 + async preloadImages() { + console.log('=== 预加载图片 ==='); + const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file)); + + for (const file of imageFiles) { + if (file.blobUrl || file.url) { + try { + const imgUrl = file.blobUrl || file.url; + console.log(`预加载图片: ${file.name} - ${imgUrl}`); + // 创建一个Image对象来预加载图片 + const img = new Image(); + img.onload = () => { + console.log(`图片预加载成功: ${file.name}`); + }; + img.onerror = (error) => { + console.error(`图片预加载失败: ${file.name}`, error); + }; + img.src = imgUrl; + } catch (error) { + console.error(`预加载图片异常: ${file.name}`, error); + } + } + } + }, + + // 清理blob URL,防止内存泄漏 + cleanupBlobUrls() { + console.log('=== 清理blob URL ==='); + this.attachmentList.forEach((file) => { + if (file.blobUrl && file.blobUrl.startsWith('blob:')) { + try { + URL.revokeObjectURL(file.blobUrl); + console.log(`已清理blob URL: ${file.name}`); + } catch (error) { + console.error(`清理blob URL失败: ${file.name}`, error); + } + } + }); + }, + // 图片加载错误处理 handleImageError(event) { console.warn('图片加载失败:', event); @@ -1532,11 +1824,21 @@ export default { // 判断是否为图片文件 isImageFile(file) { const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp']; + + // 检查 file.type if (file.type) { - return imageTypes.includes(file.type); + const isImageByType = imageTypes.includes(file.type); + console.log( + `违纪记录:通过type判断: ${file.name}, type=${file.type}, 是否图片=${isImageByType}`, + ); + return isImageByType; } + + // 通过文件名判断 const fileName = file.name || file.url || ''; - return /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(fileName); + const isImageByName = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(fileName); + console.log(`违纪记录:通过文件名判断: ${fileName}, 是否图片=${isImageByName}`); + return isImageByName; }, // 确定 From ee6bd9795e3a1319192cee14c2e8b72de744d407 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 25 Oct 2025 09:19:54 +0800 Subject: [PATCH 07/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/xgss/wsjc/components/SingleRoomCheck.vue | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue index 39505066..ad19726a 100644 --- a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue +++ b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue @@ -305,7 +305,7 @@ type="digit" placeholder="0-100分" height="40" - custom-style="height: 40rpx; line-height: 40rpx; font-size: 14px;" + custom-style="height: 40rpx; line-height: 40rpx; font-size: 14px; padding: 0;" @input="handleStudentScoreChange(student)" @blur="validateStudentScore(student)" /> @@ -1046,8 +1046,9 @@ export default { disciplineTypeId: violation.disciplineTypeId, disciplineTypeName: violation.disciplineTypeName, scoreDeduction: violation.scoreDeduction, - violationDescription: - violation.disciplineContent || violation.disciplineReason || '', + disciplineReason: violation.disciplineReason || '', // 🔴 修复:保留原始字段名 + disciplineContent: violation.disciplineContent || '', // 🔴 修复:保留原始字段名 + disciplineDate: violation.disciplineDate || '', // 违纪日期 // 🔴 统一使用 attachmentOssIds 字段名 attachmentOssIds: violation.attachmentOssIds || '', }); @@ -1307,7 +1308,9 @@ export default { disciplineTypeId: violation.disciplineTypeId, disciplineTypeName: violation.disciplineTypeName, scoreDeduction: violation.scoreDeduction, - violationDescription: violation.violationDescription, + disciplineReason: violation.disciplineReason || '', // 🔴 修复:保留原始字段名 + disciplineContent: violation.disciplineContent || '', // 🔴 修复:保留原始字段名 + disciplineDate: violation.disciplineDate || '', // 违纪日期 // 🔴 修复字段名不一致问题:统一使用 attachmentOssIds // 兼容后端可能返回的两种字段名 attachmentOssIds: From cfc83776f576a1c9de771e247a61fa4a9434f558 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 25 Oct 2025 09:41:52 +0800 Subject: [PATCH 08/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xgss/wsjc/components/SingleRoomCheck.vue | 67 ++++- mobile/src/pages/xgss/wsjc/wsjc.vue | 258 +++++++++++++++++- 2 files changed, 303 insertions(+), 22 deletions(-) diff --git a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue index ad19726a..5925b91f 100644 --- a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue +++ b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue @@ -13,18 +13,22 @@ - + - 类型说明:{{ selectedDisciplineTypeRemark }} + + 【{{ formData.disciplineTypeName }}】类型说明:{{ + selectedDisciplineTypeRemark + }} + @@ -48,24 +56,30 @@ v-model.number="formData.totalScore" type="digit" placeholder="请输入扣分(0-100分)" + :disabled="isViewMode" + :readonly="isViewMode" @input="handleTotalScoreChange" /> - + - + 宿舍学生情况 - + 出勤状态 - + 学生评级 - + 违纪情况 - + - + - + 取消 - + + 关闭 + + 保存检查结果 @@ -569,6 +602,10 @@ export default { type: Object, default: null, }, + isViewMode: { + type: Boolean, + default: false, + }, }, data() { return { diff --git a/mobile/src/pages/xgss/wsjc/wsjc.vue b/mobile/src/pages/xgss/wsjc/wsjc.vue index 81d5b182..d1854000 100644 --- a/mobile/src/pages/xgss/wsjc/wsjc.vue +++ b/mobile/src/pages/xgss/wsjc/wsjc.vue @@ -205,15 +205,77 @@ + + + + + + + {{ currentViewItem ? currentViewItem.roomName : '' }} - 查看检查 + + + + + + + + + + + + + + + {{ currentEditItem ? currentEditItem.roomName : '' }} - 修改检查 + + + + + + + + + + diff --git a/web/src/views/xg/studentDynamics/components/DormApplicationTab.vue b/web/src/views/xg/studentDynamics/components/DormApplicationTab.vue new file mode 100644 index 00000000..f5403dd5 --- /dev/null +++ b/web/src/views/xg/studentDynamics/components/DormApplicationTab.vue @@ -0,0 +1,258 @@ + + + + + diff --git a/web/src/views/xg/studentDynamics/components/HolidayStayDetail.vue b/web/src/views/xg/studentDynamics/components/HolidayStayDetail.vue new file mode 100644 index 00000000..e67443b2 --- /dev/null +++ b/web/src/views/xg/studentDynamics/components/HolidayStayDetail.vue @@ -0,0 +1,474 @@ + + + + + diff --git a/web/src/views/xg/studentDynamics/components/HolidayStayTab.vue b/web/src/views/xg/studentDynamics/components/HolidayStayTab.vue new file mode 100644 index 00000000..397aa21b --- /dev/null +++ b/web/src/views/xg/studentDynamics/components/HolidayStayTab.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/web/src/views/xg/studentDynamics/components/StudentLeaveDetail.vue b/web/src/views/xg/studentDynamics/components/StudentLeaveDetail.vue new file mode 100644 index 00000000..91b87b19 --- /dev/null +++ b/web/src/views/xg/studentDynamics/components/StudentLeaveDetail.vue @@ -0,0 +1,21 @@ + + + diff --git a/web/src/views/xg/studentDynamics/components/StudentLeaveTab.vue b/web/src/views/xg/studentDynamics/components/StudentLeaveTab.vue new file mode 100644 index 00000000..06d85cf0 --- /dev/null +++ b/web/src/views/xg/studentDynamics/components/StudentLeaveTab.vue @@ -0,0 +1,271 @@ + + + + + diff --git a/web/src/views/xg/studentDynamics/index.vue b/web/src/views/xg/studentDynamics/index.vue new file mode 100644 index 00000000..0f078ed0 --- /dev/null +++ b/web/src/views/xg/studentDynamics/index.vue @@ -0,0 +1,80 @@ + + + + + From 4c63668c1984e857625c0d0749a606f89df3ef80 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 25 Oct 2025 15:19:07 +0800 Subject: [PATCH 11/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/XgStudentDynamicsServiceImpl.java | 36 +- mobile/src/App.vue | 9 + .../src/components/Process/approvalRecord.vue | 197 +++++++- mobile/src/pages.json | 8 + mobile/src/pages/index/index.vue | 22 + .../components/DormApplicationTab.vue | 432 +++++++++++++++++ .../components/HolidayStayTab.vue | 392 ++++++++++++++++ .../components/StudentLeaveTab.vue | 435 ++++++++++++++++++ mobile/src/pages/xg/studentDynamics/index.vue | 99 ++++ .../xg/xsjcxx/xsws/stuMsgDetailDialog.vue | 2 +- 10 files changed, 1608 insertions(+), 24 deletions(-) create mode 100644 mobile/src/pages/xg/studentDynamics/components/DormApplicationTab.vue create mode 100644 mobile/src/pages/xg/studentDynamics/components/HolidayStayTab.vue create mode 100644 mobile/src/pages/xg/studentDynamics/components/StudentLeaveTab.vue create mode 100644 mobile/src/pages/xg/studentDynamics/index.vue diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentDynamicsServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentDynamicsServiceImpl.java index 77a364e3..8690fe53 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentDynamicsServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentDynamicsServiceImpl.java @@ -135,8 +135,16 @@ public class XgStudentDynamicsServiceImpl implements IXgStudentDynamicsService { lqw.in(XgDormApplication::getCurrentBuildingId, managedBuildingIds); // 添加其他查询条件 - lqw.like(bo.getStudentName() != null, XgDormApplication::getStudentName, bo.getStudentName()); - lqw.like(bo.getStudentNo() != null, XgDormApplication::getStudentNo, bo.getStudentNo()); + // 姓名或学号检索(OR关系) + if (bo.getStudentName() != null && bo.getStudentNo() != null) { + lqw.and(wrapper -> wrapper + .like(XgDormApplication::getStudentName, bo.getStudentName()) + .or() + .like(XgDormApplication::getStudentNo, bo.getStudentNo())); + } else { + lqw.like(bo.getStudentName() != null, XgDormApplication::getStudentName, bo.getStudentName()); + lqw.like(bo.getStudentNo() != null, XgDormApplication::getStudentNo, bo.getStudentNo()); + } lqw.like(bo.getCollegeName() != null, XgDormApplication::getCollegeName, bo.getCollegeName()); lqw.like(bo.getMajorName() != null, XgDormApplication::getMajorName, bo.getMajorName()); lqw.like(bo.getClassName() != null, XgDormApplication::getClassName, bo.getClassName()); @@ -169,8 +177,16 @@ public class XgStudentDynamicsServiceImpl implements IXgStudentDynamicsService { lqw.in(XgHolidayStay::getBuildingId, managedBuildingIds); // 添加其他查询条件 - lqw.like(bo.getStudentName() != null, XgHolidayStay::getStudentName, bo.getStudentName()); - lqw.like(bo.getStudentNo() != null, XgHolidayStay::getStudentNo, bo.getStudentNo()); + // 姓名或学号检索(OR关系) + if (bo.getStudentName() != null && bo.getStudentNo() != null) { + lqw.and(wrapper -> wrapper + .like(XgHolidayStay::getStudentName, bo.getStudentName()) + .or() + .like(XgHolidayStay::getStudentNo, bo.getStudentNo())); + } else { + lqw.like(bo.getStudentName() != null, XgHolidayStay::getStudentName, bo.getStudentName()); + lqw.like(bo.getStudentNo() != null, XgHolidayStay::getStudentNo, bo.getStudentNo()); + } lqw.like(bo.getCollegeName() != null, XgHolidayStay::getCollegeName, bo.getCollegeName()); lqw.like(bo.getMajorName() != null, XgHolidayStay::getMajorName, bo.getMajorName()); lqw.like(bo.getClassName() != null, XgHolidayStay::getClassName, bo.getClassName()); @@ -203,8 +219,16 @@ public class XgStudentDynamicsServiceImpl implements IXgStudentDynamicsService { lqw.in(XgStudentLeave::getStudentId, managedStudentIds); // 添加其他查询条件 - lqw.like(bo.getStudentName() != null, XgStudentLeave::getStudentName, bo.getStudentName()); - lqw.like(bo.getStudentNo() != null, XgStudentLeave::getStudentNo, bo.getStudentNo()); + // 姓名或学号检索(OR关系) + if (bo.getStudentName() != null && bo.getStudentNo() != null) { + lqw.and(wrapper -> wrapper + .like(XgStudentLeave::getStudentName, bo.getStudentName()) + .or() + .like(XgStudentLeave::getStudentNo, bo.getStudentNo())); + } else { + lqw.like(bo.getStudentName() != null, XgStudentLeave::getStudentName, bo.getStudentName()); + lqw.like(bo.getStudentNo() != null, XgStudentLeave::getStudentNo, bo.getStudentNo()); + } lqw.like(bo.getCollegeName() != null, XgStudentLeave::getCollegeName, bo.getCollegeName()); lqw.like(bo.getMajorName() != null, XgStudentLeave::getMajorName, bo.getMajorName()); lqw.like(bo.getClassName() != null, XgStudentLeave::getClassName, bo.getClassName()); diff --git a/mobile/src/App.vue b/mobile/src/App.vue index f7bcf2db..e793dbc5 100644 --- a/mobile/src/App.vue +++ b/mobile/src/App.vue @@ -150,6 +150,15 @@ page { border-color: #007dfb !important; } +/* Tabs组件主题色覆盖 - 全局统一底部方块颜色 */ +.u-tabs__wrapper__nav__line { + background-color: #0051d9 !important; +} + +.u-tabs .u-tabs__wrapper__nav__line { + background: #0051d9 !important; +} + /* ========== 全局温暖商务风格样式 ========== */ /* 全局页面背景 */ diff --git a/mobile/src/components/Process/approvalRecord.vue b/mobile/src/components/Process/approvalRecord.vue index 9f5074c4..fffb2380 100644 --- a/mobile/src/components/Process/approvalRecord.vue +++ b/mobile/src/components/Process/approvalRecord.vue @@ -73,18 +73,36 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ item.studentName || '-' }} + {{ item.studentNo || '-' }} + + + + + + + + 学院: + {{ item.collegeName || '-' }} + + + 专业: + {{ item.majorName || '-' }} + + + 班级: + {{ item.className || '-' }} + + + 当前宿舍: + + {{ item.currentBuildingName }}{{ item.currentRoomNo + }}{{ item.currentBedNo }} + + + + 目标宿舍: + + {{ item.targetBuildingName }}{{ item.targetRoomNo + }}{{ item.targetBedNo }} + + + + 审批状态: + + + + 申请时间: + {{ formatTime(item.applyTime) }} + + + + + + + + 审批记录 + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src/pages/xg/studentDynamics/components/HolidayStayTab.vue b/mobile/src/pages/xg/studentDynamics/components/HolidayStayTab.vue new file mode 100644 index 00000000..0c14da1c --- /dev/null +++ b/mobile/src/pages/xg/studentDynamics/components/HolidayStayTab.vue @@ -0,0 +1,392 @@ + + + + + diff --git a/mobile/src/pages/xg/studentDynamics/components/StudentLeaveTab.vue b/mobile/src/pages/xg/studentDynamics/components/StudentLeaveTab.vue new file mode 100644 index 00000000..99edfd39 --- /dev/null +++ b/mobile/src/pages/xg/studentDynamics/components/StudentLeaveTab.vue @@ -0,0 +1,435 @@ + + + + + diff --git a/mobile/src/pages/xg/studentDynamics/index.vue b/mobile/src/pages/xg/studentDynamics/index.vue new file mode 100644 index 00000000..4a16d080 --- /dev/null +++ b/mobile/src/pages/xg/studentDynamics/index.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/mobile/src/pages/xg/xsjcxx/xsws/stuMsgDetailDialog.vue b/mobile/src/pages/xg/xsjcxx/xsws/stuMsgDetailDialog.vue index b8b1c33e..c181ea34 100644 --- a/mobile/src/pages/xg/xsjcxx/xsws/stuMsgDetailDialog.vue +++ b/mobile/src/pages/xg/xsjcxx/xsws/stuMsgDetailDialog.vue @@ -17,7 +17,7 @@ :list="tabList" :current="safeCurrentTab" @click="handleTabClick" - active-color="#3c9cff" + active-color="#0051d9" inactive-color="#606266" :font-size="32" :bar-width="60" From 9310a7323f3a29d853fca746f93f19abdb6b6b5f Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 25 Oct 2025 15:26:11 +0800 Subject: [PATCH 12/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue | 2 +- web/src/views/xg/xgss/holidayStay/holidayStay/index.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue b/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue index 13ad355b..e78203eb 100644 --- a/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue +++ b/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue @@ -183,7 +183,7 @@ - + diff --git a/web/src/views/xg/xgss/holidayStay/holidayStay/index.vue b/web/src/views/xg/xgss/holidayStay/holidayStay/index.vue index 82eec49f..d41a6745 100644 --- a/web/src/views/xg/xgss/holidayStay/holidayStay/index.vue +++ b/web/src/views/xg/xgss/holidayStay/holidayStay/index.vue @@ -180,7 +180,7 @@ - + From 384373a2c1962f10e685c5e84d509619f3db6874 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 25 Oct 2025 17:19:54 +0800 Subject: [PATCH 13/17] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mobile/src/App.vue | 26 + mobile/src/pages.json | 8 +- mobile/src/pages/internship/index.vue | 424 ++++++------ mobile/src/pages/visit/add.vue | 646 +++++++++--------- mobile/src/pages/visit/detail.vue | 428 ++++++------ mobile/src/pages/visit/edit.vue | 527 +++++++------- mobile/src/pages/visit/index.vue | 424 +++++++----- .../src/pages/workStudy/applicationDetail.vue | 213 +++--- mobile/src/pages/workStudy/apply.vue | 445 ++++++------ mobile/src/pages/workStudy/detail.vue | 262 +++---- mobile/src/pages/workStudy/index.vue | 460 +++++++------ 11 files changed, 2053 insertions(+), 1810 deletions(-) diff --git a/mobile/src/App.vue b/mobile/src/App.vue index e793dbc5..c7cf51d8 100644 --- a/mobile/src/App.vue +++ b/mobile/src/App.vue @@ -159,6 +159,32 @@ page { background: #0051d9 !important; } +/* Navbar导航栏主题色覆盖 - 勤工俭学模块统一背景色 */ +.u-navbar { + background-color: #0051d9 !important; +} + +.u-navbar__content { + background-color: #0051d9 !important; +} + +.u-navbar__content__title { + color: #fff !important; +} + +/* uni-app 原生导航栏样式覆盖 */ +uni-page-head { + background-color: #0051d9 !important; +} + +.uni-page-head { + background-color: #0051d9 !important; +} + +.uni-page-head__title { + color: #fff !important; +} + /* ========== 全局温暖商务风格样式 ========== */ /* 全局页面背景 */ diff --git a/mobile/src/pages.json b/mobile/src/pages.json index 1c061b33..65697572 100644 --- a/mobile/src/pages.json +++ b/mobile/src/pages.json @@ -274,7 +274,7 @@ "path": "pages/workStudy/index", "style": { "navigationBarTitleText": "勤工俭学", - "navigationBarBackgroundColor": "#1890ff", + "navigationBarBackgroundColor": "#0051d9", "navigationBarTextStyle": "white" } }, @@ -282,7 +282,7 @@ "path": "pages/workStudy/detail", "style": { "navigationBarTitleText": "岗位详情", - "navigationBarBackgroundColor": "#1890ff", + "navigationBarBackgroundColor": "#0051d9", "navigationBarTextStyle": "white" } }, @@ -290,7 +290,7 @@ "path": "pages/workStudy/apply", "style": { "navigationBarTitleText": "申请岗位", - "navigationBarBackgroundColor": "#1890ff", + "navigationBarBackgroundColor": "#0051d9", "navigationBarTextStyle": "white" } }, @@ -298,7 +298,7 @@ "path": "pages/workStudy/applicationDetail", "style": { "navigationBarTitleText": "申请详情", - "navigationBarBackgroundColor": "#1890ff", + "navigationBarBackgroundColor": "#0051d9", "navigationBarTextStyle": "white" } }, diff --git a/mobile/src/pages/internship/index.vue b/mobile/src/pages/internship/index.vue index 2d82eee0..d28c3aff 100644 --- a/mobile/src/pages/internship/index.vue +++ b/mobile/src/pages/internship/index.vue @@ -1,5 +1,12 @@