This commit is contained in:
zlwu committed 2025-10-25 22:33:47 +08:00
1 parent 774e8d1137
commit f85b9b258a
13 files changed
+2468 -522

No files matched your search

@@ -112,6 +112,18 @@ public class XskhDisciplineRecordController extends BaseController {
return R.ok(xskhDisciplineRecordService.getStudentOptions(keyword));
}
/**
* 分页查询学生选项列表(用于移动端)
*/
@SaCheckPermission("disciplineRecord:disciplineRecord:query")
@GetMapping("/studentOptionsPage")
public R<List<XskhStudentOptionVo>> getStudentOptionsPage(
@RequestParam(required = false) String keyword,
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "20") Integer pageSize) {
return R.ok(xskhDisciplineRecordService.getStudentOptionsPage(keyword, pageNum, pageSize));
}
/**
* 查询当前登录学生的违纪处分记录列表
*/
@@ -166,4 +178,14 @@ public class XskhDisciplineRecordController extends BaseController {
@RequestParam String endTime) {
return R.ok(xskhDisciplineRecordService.getMaxScoreByTypeAndPeriod(typeId, startTime, endTime));
}
/**
* 查询当前用户创建的违纪记录列表(支持按学生姓名或学号搜索)
*/
@SaCheckPermission("disciplineRecord:disciplineRecord:query")
@GetMapping("/myCreated")
public TableDataInfo<XskhDisciplineRecordVo> getMyCreatedDisciplineRecords(XskhDisciplineRecordBo bo,
PageQuery pageQuery) {
return xskhDisciplineRecordService.queryMyCreatedPageList(bo, pageQuery);
}
}
@@ -23,9 +23,13 @@ public interface XskhDisciplineRecordMapper extends BaseMapperPlus<XskhDisciplin
* 查询学生选项列表(用于下拉框)
*
* @param keyword 搜索关键词(学号或姓名)
* @param pageNum 页码
* @param pageSize 每页数量
* @return 学生选项列表
*/
List<XskhStudentOptionVo> selectStudentOptions(String keyword);
List<XskhStudentOptionVo> selectStudentOptions(@Param("keyword") String keyword,
@Param("pageNum") Integer pageNum,
@Param("pageSize") Integer pageSize);
/**
* 查询班主任班级的违纪处分记录列表(班主任专用)
@@ -50,9 +50,22 @@ public interface IXskhDisciplineRecordService {
/**
* 查询学生选项列表(用于下拉框)
*
* @param keyword 搜索关键词
* @return 学生选项列表
*/
List<XskhStudentOptionVo> getStudentOptions(String keyword);
/**
* 分页查询学生选项列表(用于移动端)
*
* @param keyword 搜索关键词
* @param pageNum 页码
* @param pageSize 每页数量
* @return 学生选项列表
*/
List<XskhStudentOptionVo> getStudentOptionsPage(String keyword, Integer pageNum, Integer pageSize);
/**
* 查询当前登录学生的违纪处分记录列表
*/
@@ -101,4 +114,13 @@ public interface IXskhDisciplineRecordService {
* @return 最高总扣分
*/
java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime);
/**
* 查询当前用户创建的违纪记录列表(支持按学生姓名或学号搜索)
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 违纪记录分页列表
*/
TableDataInfo<XskhDisciplineRecordVo> queryMyCreatedPageList(XskhDisciplineRecordBo bo, PageQuery pageQuery);
}
@@ -165,7 +165,15 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer
*/
@Override
public List<XskhStudentOptionVo> getStudentOptions(String keyword) {
return baseMapper.selectStudentOptions(keyword);
return baseMapper.selectStudentOptions(keyword, null, null);
}
/**
* 分页查询学生选项列表(用于移动端)
*/
@Override
public List<XskhStudentOptionVo> getStudentOptionsPage(String keyword, Integer pageNum, Integer pageSize) {
return baseMapper.selectStudentOptions(keyword, pageNum, pageSize);
}
/**
@@ -286,4 +294,46 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer
log.info("违纪类型 {} 的最高总扣分为: {}", typeId, maxScore);
return maxScore;
}
/**
* 查询当前用户创建的违纪记录列表(支持按学生姓名或学号搜索)
*/
@Override
public TableDataInfo<XskhDisciplineRecordVo> queryMyCreatedPageList(XskhDisciplineRecordBo bo,
PageQuery pageQuery) {
// 获取当前登录用户ID
Long currentUserId = LoginHelper.getUserId();
log.info("查询用户 {} 创建的违纪记录列表", currentUserId);
// 构建查询条件,强制只查询当前用户创建的记录
LambdaQueryWrapper<XskhDisciplineRecord> lqw = Wrappers.lambdaQuery();
// 核心条件:只查询当前用户创建的记录
lqw.eq(XskhDisciplineRecord::getCreateBy, currentUserId);
// 支持按学生姓名或学号模糊搜索(使用OR连接)
if (StringUtils.isNotBlank(bo.getStudentName()) || StringUtils.isNotBlank(bo.getStudentNo())) {
String keyword = StringUtils.isNotBlank(bo.getStudentName()) ? bo.getStudentName() : bo.getStudentNo();
lqw.and(wrapper -> wrapper
.like(XskhDisciplineRecord::getStudentName, keyword)
.or()
.like(XskhDisciplineRecord::getStudentNo, keyword));
}
// 其他可选查询条件
lqw.eq(bo.getStudentId() != null, XskhDisciplineRecord::getStudentId, bo.getStudentId());
lqw.eq(bo.getCollegeId() != null, XskhDisciplineRecord::getCollegeId, bo.getCollegeId());
lqw.eq(bo.getMajorId() != null, XskhDisciplineRecord::getMajorId, bo.getMajorId());
lqw.eq(bo.getClassId() != null, XskhDisciplineRecord::getClassId, bo.getClassId());
lqw.eq(bo.getDisciplineTypeId() != null, XskhDisciplineRecord::getDisciplineTypeId, bo.getDisciplineTypeId());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XskhDisciplineRecord::getStatus, bo.getStatus());
// 按创建时间倒序排列
lqw.orderByDesc(XskhDisciplineRecord::getCreateTime);
// 执行分页查询
Page<XskhDisciplineRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
log.info("查询到 {} 条记录", result.getTotal());
return TableDataInfo.build(result);
}
}
@@ -49,7 +49,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select id, student_id, student_no, student_name, college_id, college_name, major_id, major_name, class_id, class_name, discipline_type_id, discipline_type_name, discipline_date, discipline_reason, discipline_content, score_deduction, status, violation_source, attachment_oss_ids, create_by, create_time, create_dept, update_by, update_time, deleted, tenant_id from xskh_discipline_record
</sql>
<select id="selectStudentOptions" parameterType="String" resultMap="StudentOptionResult">
<select id="selectStudentOptions" resultMap="StudentOptionResult">
select
id, student_no, name, college_id, college_name,
major_id, major_name, class_id, class_name
@@ -61,7 +61,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
or name like concat('%', #{keyword}, '%'))
</if>
order by student_no
<choose>
<when test="pageNum != null and pageSize != null">
limit ${(pageNum - 1) * pageSize}, #{pageSize, jdbcType=INTEGER}
</when>
<otherwise>
limit 50
</otherwise>
</choose>
</select>
<select id="selectClassTeacherVoPage" resultType="XskhDisciplineRecordVo">
@@ -1,75 +0,0 @@
# 代码调整记录 - 2025-01-08 - Cursor AI
## 📝 变更概述
**变更时间**: 2025-01-08 15:30:00
**变更人员**: Cursor AI
**变更类型**: 数据库结构修复
**影响模块**: 社团管理 - 活动报名功能
## 🔴 **问题描述**
**问题现象**: 活动报名接口数据插入失败,报错缺少字段
**根本原因**: `stgl_activity_registration` 数据库表结构与实体类不匹配,缺失多个字段
**缺失字段清单**:
- `remark` - 申请理由(非必填)
- `gender` - 性别
- `phone` - 联系电话
- `email` - 邮箱
- `department` - 院系
- `major` - 专业
- `class_name` - 班级
## ✅ **解决方案**
### 1. 创建修复SQL脚本
**文件**: `docs/设计文档/database/社团管理/修复活动报名表字段.sql`
- 添加7个缺失字段到 `stgl_activity_registration` 表
- 新增性能优化索引 (`idx_gender`, `idx_phone`, `idx_department`)
- 包含验证和检查语句
### 2. 更新建表脚本
**文件**: `docs/设计文档/database/社团管理/社团管理建表脚本.sql`
- 在 `stgl_activity_registration` 表中补充完整字段定义
- 调整字段顺序,符合逻辑分组(基本信息、审核信息、成绩信息、学生个人信息)
- 新增对应索引
## 📊 **影响范围**
- ✅ **后端**: StglActivityRegistration 实体类字段完整匹配
- ✅ **数据库**: 表结构字段补全,支持完整业务数据
- ✅ **API**: 活动报名相关接口正常工作
- ✅ **前端**: 报名表单数据可正常提交和保存
## 🧪 **验证方法**
```sql
-- 1. 执行修复脚本
source docs/设计文档/database/社团管理/修复活动报名表字段.sql
-- 2. 验证表结构
DESCRIBE stgl_activity_registration;
-- 3. 测试插入数据
-- 使用活动报名API接口测试完整数据提交
```
## 📋 **待执行任务**
- [x] 分析问题原因
- [x] 创建修复SQL脚本
- [x] 更新建表脚本
- [x] 记录变更日志
- [ ] **需要执行**: 在数据库中运行修复脚本
- [ ] **需要测试**: 验证活动报名功能正常
## 🔗 **相关文件**
- `backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivityRegistration.java`
- `backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityRegistrationMapper.xml`
- `docs/设计文档/database/社团管理/社团管理建表脚本.sql`
- `docs/设计文档/database/社团管理/修复活动报名表字段.sql` (新增)
## 💡 **经验总结**
1. **数据库与实体类一致性**: 开发时必须确保数据库表结构与实体类字段完全匹配
2. **渐进式开发问题**: 实体类后续添加字段时,必须同步更新数据库表结构
3. **文档同步重要性**: 建表脚本必须与实际代码保持同步,避免部署时出现问题
4. **字段分组设计**: 表字段应该按业务逻辑分组,提高可读性和维护性
---
*本记录严格按照项目规范要求,代码变更与文档同步更新*
@@ -1,241 +0,0 @@
# 代码调整记录 - 2025-01-14 - cursor
## 📝 变更概述
**变更日期**: 2025-01-14 15:30:00
**处理人员**: cursor
**变更类型**: 前端功能修复 + UI优化
## 🎯 变更内容
### 1. 修复预约时段状态切换功能 (XljkConsultationSchedule)
**问题描述**:
- 心理健康咨询预约时段管理页面,点击编辑按钮后状态切换异常
- 无论当前状态如何,都会变成停用状态
- 显示双重状态标签,界面异常
**根本原因分析**:
1. **状态值类型不一致**: 前端接收的`status`是字符串类型(`"0"`, `"1"`),但切换逻辑使用数字比较(`=== 1`)
2. **字典数据缺失**: `schedule_status`字典未正确配置,导致显示异常
3. **状态更新接口数据类型错误**: 发送数字状态但接收端可能期望字符串
**修复方案**:
#### 1.1 修复状态切换逻辑
**文件**: `web/src/views/xljk/consultation/schedule/index.vue`
**修改前**:
```typescript
const handleUpdate = async (row?: XljkConsultationScheduleVO) => {
if (row && row.id) {
try {
await updateXljkSchedule({
id: row.id,
status: row.status === 1 ? 0 : 1 // 错误:字符串与数字比较
} as XljkConsultationScheduleForm);
ElMessage.success('状态更新成功');
getList();
} catch (error) {
console.error('更新时段状态失败', error);
ElMessage.error('状态更新失败');
}
}
};
```
**修改后**:
```typescript
const handleUpdate = async (row?: XljkConsultationScheduleVO) => {
if (row && row.id) {
try {
// 修复:将字符串状态正确转换为数字进行比较
const currentStatus = parseInt(row.status as string);
const newStatus = currentStatus === 1 ? 0 : 1;
console.log(`状态切换调试 - 当前状态: ${row.status} (${typeof row.status}), 转换后: ${currentStatus}, 新状态: ${newStatus}`);
await updateXljkSchedule({
id: row.id,
status: newStatus.toString()
} as XljkConsultationScheduleForm);
ElMessage.success(`状态已${newStatus === 0 ? '启用' : '停用'}`);
getList();
} catch (error) {
console.error('更新时段状态失败', error);
ElMessage.error('状态更新失败');
}
}
};
```
**修复要点**:
- ✅ 使用 `parseInt()` 将字符串状态转换为数字进行比较
- ✅ 添加调试日志输出,便于问题排查
- ✅ 改进用户提示消息,明确显示状态变更结果
- ✅ 确保发送的状态值类型一致性
#### 1.2 创建字典数据配置
**文件**: `backend/script/sql/xljk-dict-data.sql`
**新增内容**:
```sql
-- 预约时段状态字典
INSERT INTO sys_dict_type VALUES (102, '000000', '预约时段状态', 'schedule_status', 103, 1, sysdate(), NULL, NULL, '预约时段状态列表');
-- 预约时段状态字典数据
INSERT INTO sys_dict_data VALUES (300, '000000', 1, '正常', '0', 'schedule_status', '', 'success', 'Y', 103, 1, sysdate(), NULL, NULL, '正常状态');
INSERT INTO sys_dict_data VALUES (301, '000000', 2, '停用', '1', 'schedule_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '停用状态');
```
**配置要点**:
- ✅ 创建 `schedule_status` 字典类型
- ✅ 配置状态值:`0-正常`(success样式)、`1-停用`(danger样式)
- ✅ 同时创建相关的预约状态和时段类型字典
### 2. 优化预约时段管理列表列宽 (2025-01-14 15:45:00)
**调整目标**: 缩小上午时段、下午时段、创建时间3个列的列宽,优化页面布局
**列宽调整对比**:
| 字段名称 | 调整前 | 调整后 | 变化 |
|----------|--------|--------|------|
| **上午时段** | `min-width="150"` | `min-width="120"` | -30px |
| **下午时段** | `min-width="150"` | `min-width="120"` | -30px |
| **创建时间** | `width="150"` | `width="120"` | -30px |
**修改位置**: `web/src/views/xljk/consultation/schedule/index.vue` (第84-102行)
**修改内容**:
```vue
<!-- 上午时段列 -->
<el-table-column label="上午时段" align="center" min-width="120">
<!-- 下午时段列 -->
<el-table-column label="下午时段" align="center" min-width="120">
<!-- 创建时间列 -->
<el-table-column label="创建时间" align="center" prop="createTime" width="120">
```
**优化效果**:
- ✅ 总体节省空间:90px (30px × 3列)
- ✅ 上午/下午时段列保持响应式 (`min-width`)
- ✅ 创建时间列固定宽度更紧凑
- ✅ 为其他列提供更多显示空间
- ✅ 提升页面整体布局效率
**表格布局更新**:
- **原总最小宽度**: 1115px
- **新总最小宽度**: 1025px
- **空间优化**: 节省90px宽度
### 3. 移除预约限制管理页面创建时间字段 (2025-01-14 16:00:00)
**调整目标**: 移除预约限制管理(未到访记录)列表界面的创建时间字段显示
**页面位置**: `web/src/views/xljk/consultation/noshow/index.vue`
**移除字段**:
```vue
<!-- 已移除的创建时间列 -->
<el-table-column label="创建时间" align="center" prop="createTime" width="150">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
```
**修改位置**: 第92-96行
**优化效果**:
- ✅ **简化界面**: 移除不必要的时间信息显示
- ✅ **节省空间**: 释放150px列宽空间
- ✅ **聚焦核心**: 突出学生信息、限制状态等关键信息
- ✅ **提升体验**: 减少信息冗余,提高页面简洁度
**当前保留字段**:
- 学生姓名、学号、院系、专业、班级
- 未到访日期、限制到期日、状态
- 操作(查看、解除限制)
**表格布局优化**:
- **移除列宽**: 150px
- **界面更简洁**: 专注于业务核心信息
- **操作便捷**: 保留查看详情和解除限制功能
## 🔧 技术细节
### 问题原因深度分析
1. **JavaScript类型转换问题**:
```javascript
// 问题代码
"1" === 1 // false (字符串与数字比较)
// 修复代码
parseInt("1") === 1 // true (转换后比较)
```
2. **数据流类型不一致**:
```
后端返回 → status: "0" (字符串)
前端比较 → status === 1 (数字)
结果 → 永远返回false,总是执行 : 1 分支
```
3. **字典配置缺失**:
```vue
<dict-tag :options="schedule_status" :value="scope.row.status" />
```
当 `schedule_status` 字典为空时,`dict-tag` 组件无法正确渲染,可能显示原始值或双重标签。
### 测试验证
**验证步骤**:
1. ✅ 启动前端开发服务器
2. ✅ 导航至心理健康 → 咨询管理 → 预约时段
3. ✅ 检查状态列显示是否正常(应显示"正常"或"停用")
4. ✅ 点击编辑按钮测试状态切换
5. ✅ 观察控制台调试日志
6. ✅ 验证状态在正常/停用之间正确切换
**预期结果**:
- 状态列正确显示"正常"/"停用"标签
- 点击编辑按钮后状态正确切换
- 用户提示消息明确显示变更结果
- 控制台输出调试信息供问题排查
## 📊 影响范围
**直接影响**:
- ✅ 心理健康咨询预约时段管理功能恢复正常
- ✅ 状态切换功能可正确启用/停用时段
**间接影响**:
- ✅ 改善用户体验,操作反馈更清晰
- ✅ 为类似的状态切换功能提供标准修复模式
- ✅ 完善了心理健康模块的字典数据配置
## 🚀 后续优化建议
1. **统一类型处理**: 考虑在前端建立统一的状态值处理工具函数
2. **完善错误处理**: 添加更详细的错误处理和用户提示
3. **字典数据管理**: 建立字典数据的统一管理和维护机制
4. **测试用例**: 为状态切换功能添加单元测试和集成测试
## 📋 变更文件清单
| 文件路径 | 变更类型 | 说明 |
|----------|----------|------|
| `web/src/views/xljk/consultation/schedule/index.vue` | 修改 | 修复状态切换逻辑 + 优化列宽布局 |
| `web/src/views/xljk/consultation/noshow/index.vue` | 修改 | 移除创建时间字段显示 |
| `backend/script/sql/xljk-dict-data.sql` | 新增 | 创建心理健康模块字典数据配置 |
| `docs/变更管理/code-adjust-record-2025-01-14-cursor.md` | 新增 | 本变更记录文档 |
---
**验证完成**: ✅ 功能测试通过
**文档更新**: ✅ 已同步更新
**代码审查**: ✅ 自检完成
@@ -1,66 +0,0 @@
# 代码调整记录 - 2025-01-15 - cursor
## 🔧 数据库字段修复
### 📅 变更时间
2025-01-15 14:30:00
### 🎯 变更目标
修复 `stgl_activity` 表缺失的 `remark` 字段
### ⚠️ 问题描述
在社团活动管理功能中出现以下错误:
```
Unknown column 'sa.remark' in 'field list'
```
**错误原因分析:**
1. 实体类 `StglActivity.java` 中定义了 `remark` 字段
2. MyBatis映射文件 `StglActivityMapper.xml` 中的SQL查询包含了 `sa.remark` 字段
3. 但数据库表 `stgl_activity` 中实际缺少 `remark` 字段
### 🔍 修复过程
#### 1. 问题定位
- 检查实体类:`StglActivity.java` 第216-217行定义了remark字段
- 检查映射文件:`StglActivityMapper.xml` 第59行和第40行引用了remark字段
- 检查数据库表:`stgl_activity` 表确实缺少remark字段
#### 2. 修复方案
为 `stgl_activity` 表添加缺失的 `remark` 字段
#### 3. 执行的SQL语句
```sql
ALTER TABLE `stgl_activity`
ADD COLUMN `remark` varchar(500) DEFAULT NULL COMMENT '备注' AFTER `import_source`;
```
#### 4. 修复结果验证
- ✅ remark字段成功添加到stgl_activity表
- ✅ 字段类型:varchar(500)
- ✅ 字段注释:备注
- ✅ 字段位置:import_source字段之后
### 📁 相关文件
- **实体类**: `backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java`
- **映射文件**: `backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml`
- **修复脚本**: `backend/script/sql/fix-stgl-activity-remark-field.sql`
- **建表脚本**: `docs/设计文档/database/社团管理/社团管理建表脚本.sql`
### 🎯 影响范围
- **功能模块**: 社团活动管理
- **影响接口**: 所有涉及stgl_activity表查询的接口
- **数据完整性**: 修复后代码与数据库结构保持一致
### ✅ 验证结果
修复完成后,社团活动管理相关功能应该能正常运行,不再出现字段缺失错误。
### 📝 经验教训
1. **数据库同步**: 确保实体类、映射文件与数据库表结构完全一致
2. **建表脚本**: 需要检查建表脚本是否包含所有必要字段
3. **字段映射**: MyBatis映射文件中的字段必须在数据库表中实际存在
---
**修复人员**: Cursor AI
**修复时间**: 2025-01-15 14:30:00
**状态**: ✅ 已完成
@@ -1,137 +0,0 @@
# 代码调整记录 - 2025-01-21 - Cursor AI
## 📋 变更概述
**时间**: 2025-01-21 10:30:00
**操作人**: Cursor AI
**变更类型**: 🐛 Bug修复
**影响范围**: 志愿者成员管理模块
## 🔍 问题描述
**问题现象**: 当前登录用户`2025LY23117`是志愿者马拉松工作组的队长,该队伍总共有3个人,但在志愿者成员管理页面只能看到一个成员。
**根本原因**: 志愿者角色类型(roleType)在前后端定义不一致,导致权限过滤逻辑失效。
### 问题详情
| 组件 | 队长值 | 队员值 | 状态 |
|------|--------|--------|------|
| 前端字典(修复前) | 1 | 2 | ❌ 错误 |
| 后端权限过滤 | 0 | 1 | ✅ 正确 |
| 数据库SQL映射 | 0 | 1 | ✅ 正确 |
| 字典表(修复前) | 1 | 2 | ❌ 错误 |
**影响**:
- 权限过滤方法`getCaptainTeamIds`查找`roleType=0`时找不到队长记录
- 查询条件被设置为`relationId=-1L`,导致返回空结果
- 用户只能看到第一个队伍的数据
## 🛠️ 解决方案
**采用方案A**: 统一使用数据库SQL的定义(队长=0,队员=1)
### 修复内容
#### 1. 修改字典表数据
**文件**: `backend/script/sql/stgl_volunteer_role_dict.sql`
- 队长的`dict_value`从'1'改为'0'
- 队员的`dict_value`从'2'改为'1'
#### 2. 修改前端字典定义
**文件**: `web/src/views/stgl/volunteer/member/index.vue`
- 第358-361行:更新手动定义的字典
- 第432行:修改默认角色值为'1'(队员)
- 第242行:移除parseInt转换,保持字符串类型
- 第632行:修改类型转换为String()
#### 3. 创建数据库更新脚本
**文件**: `script/update-volunteer-role-dict.sql`
- 创建线上数据库更新脚本
- 包含验证查询和完成提示
## 📝 修改细节
### 代码变更
```javascript
// 修复前
const stgl_volunteer_role = ref([
{ value: '1', label: '队长' },
{ value: '2', label: '队员' }
]);
// 修复后
const stgl_volunteer_role = ref([
{ value: '0', label: '队长' },
{ value: '1', label: '队员' }
]);
```
```sql
-- 修复前字典数据
VALUES (..., '队长', '1', ...)
VALUES (..., '队员', '2', ...)
-- 修复后字典数据
VALUES (..., '队长', '0', ...)
VALUES (..., '队员', '1', ...)
```
### 权限过滤逻辑验证
后端权限过滤逻辑保持不变(已正确):
```java
captainBo.setRoleType(0); // 队长角色 = 0
```
数据库SQL映射保持不变(已正确):
```xml
WHEN 0 THEN '队长' <!-- 队长 = 0 -->
WHEN 1 THEN '队员' <!-- 队员 = 1 -->
```
## 🎯 预期效果
修复完成后:
1. ✅ 志愿者队长能够正确查看所属队伍的所有成员
2. ✅ 权限过滤逻辑正常工作
3. ✅ 前后端角色类型定义统一
4. ✅ 字典数据显示正确
## 📋 部署步骤
1. **执行数据库更新脚本**:
```bash
mysql -u username -p database < script/update-volunteer-role-dict.sql
```
2. **重新构建前端**:
```bash
cd web
npm run build
```
3. **重启后端服务**(如有缓存)
4. **验证功能**:
- 使用队长账号登录
- 访问志愿者成员管理页面
- 确认能看到所有队伍成员
## 🔍 验证要点
- [ ] 队长能查看所属队伍的所有成员
- [ ] 角色显示正确(队长/队员)
- [ ] 新增成员时角色选择正常
- [ ] 权限过滤按预期工作
## 📚 相关文档
- **问题分析**: 本文档"问题描述"部分
- **技术规范**: `.cursor/cursorrules-backend.md` - 权限管理规范
- **API文档**: `docs/开发文档/API/志愿者管理API.md`
## 🏷️ 标签
`#志愿者管理` `#权限过滤` `#角色类型` `#前后端统一` `#字典数据` `#Bug修复`
+16
View File
@@ -246,6 +246,22 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/xskh/wjjlEntry/index",
"style": {
"navigationBarTitleText": "违纪记录录入",
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/xskh/wjjlEntry/disciplineRecordForm",
"style": {
"navigationBarTitleText": "违纪记录",
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/xskh/khsq/khsq",
"style": {
+22
View File
@@ -263,6 +263,20 @@
</template>
</ServiceItem>
<ServiceItem
v-if="hasPermi(['student:violation:entry'])"
title="违纪记录录入"
@click="gotoViolationEntry"
>
<template #icon>
<image
src="/static/00125.png"
mode="aspectFit"
style="width: 24px; height: 24px"
/>
</template>
</ServiceItem>
<ServiceItem
v-if="hasPermi(['student:assessment:applyapp'])"
title="考核申请"
@@ -739,6 +753,7 @@ export default {
this.hasPermi(['student:dynamics:list']) ||
this.hasPermi(['student:workstudy:list']) ||
this.hasPermi(['student:violation:listapp']) ||
this.hasPermi(['student:violation:entry']) ||
this.hasPermi(['student:assessment:applyapp']) ||
this.hasPermi(['student:counsel:reservation'])
);
@@ -989,6 +1004,13 @@ export default {
});
},
// 跳转到违纪记录录入页面
gotoViolationEntry() {
uni.navigateTo({
url: '/pages/xskh/wjjlEntry/index',
});
},
// 跳转到考核申请页面
gotoAssessmentApply() {
uni.navigateTo({
File diff suppressed because it is too large. Load diff
+511
View File
@@ -0,0 +1,511 @@
<template>
<view class="discipline-record-container">
<!-- 顶部导航 -->
<u-navbar title="违纪记录录入" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
<view slot="left" @click="goBack">
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
</view>
</u-navbar>
<!-- 搜索栏 -->
<view class="search-bar">
<u-search
v-model="searchKeyword"
placeholder="搜索学生姓名或学号"
:show-action="false"
@search="handleSearch"
@custom="handleSearch"
bg-color="#f6f6f6"
shape="round"
/>
</view>
<!-- 加载状态 -->
<u-loading-page :loading="loading" loading-text="加载中..."></u-loading-page>
<!-- 违纪记录列表 -->
<view v-if="!loading" class="content-wrapper">
<!-- 空状态 -->
<view v-if="disciplineRecordList.length === 0" class="empty-container">
<u-empty text="暂无违纪记录" mode="data"></u-empty>
</view>
<!-- 记录卡片列表 -->
<view v-else class="record-list">
<view
v-for="(item, index) in disciplineRecordList"
:key="item.id || index"
class="record-card"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="student-info">
<text class="student-name">{{ item.studentName || '-' }}</text>
<text class="student-no">({{ item.studentNo || '-' }})</text>
</view>
<view class="record-status">
<u-tag
:text="getDictLabel(item.status)"
:type="getStatusType(item.status)"
size="mini"
></u-tag>
</view>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="info-item">
<text class="label">违纪类型:</text>
<text class="value">{{ item.disciplineTypeName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">违纪日期:</text>
<text class="value">{{ item.disciplineDate || '-' }}</text>
</view>
<view class="info-item">
<text class="label">扣分分值:</text>
<text class="value value-score">-{{ item.scoreDeduction || '0' }}</text>
</view>
<view class="info-item">
<text class="label">学院班级:</text>
<text class="value">
{{ item.collegeName || '-' }} / {{ item.className || '-' }}
</text>
</view>
<view v-if="item.disciplineReason" class="info-item">
<text class="label">违纪原因:</text>
<text class="value">{{ item.disciplineReason }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="card-actions">
<!-- 查看按钮 -->
<u-button
type="info"
size="small"
class="action-btn"
@click="handleView(item)"
>
<u-icon name="eye" size="16" color="#323233"></u-icon>
<text>查看</text>
</u-button>
<!-- 编辑按钮 -->
<u-button
type="primary"
size="small"
class="action-btn"
@click="handleEdit(item)"
>
<u-icon name="edit-pen" size="16" color="#ffffff"></u-icon>
<text class="btn-text">编辑</text>
</u-button>
<!-- 删除按钮 -->
<u-button
type="error"
size="small"
class="action-btn"
@click="handleDelete(item)"
>
<u-icon name="trash" size="16" color="#ffffff"></u-icon>
<text class="btn-text">删除</text>
</u-button>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="disciplineRecordList.length > 0" class="load-more">
<u-loadmore :status="loadMoreStatus" @loadmore="loadMore" />
</view>
</view>
<!-- 浮动新增按钮 -->
<view class="float-add-button" @click="handleAdd">
<u-icon name="plus" color="#fff" size="28"></u-icon>
</view>
</view>
</template>
<script>
import { request } from '@/common/request.js';
import { getDictLabel, getDictTagType, getDict } from '@/common/dict.js';
export default {
name: 'DisciplineRecordEntry',
components: {},
data() {
return {
loading: false,
disciplineRecordList: [],
total: 0,
// 搜索关键词
searchKeyword: '',
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
},
// 加载更多状态
loadMoreStatus: 'loadmore',
// 字典数据
statusDict: [],
};
},
async created() {
await this.loadDictData();
await this.getList();
// 监听页面刷新事件
uni.$on('refreshDisciplineRecordList', () => {
this.queryParams.pageNum = 1;
this.getList();
});
},
onUnload() {
// 移除监听
uni.$off('refreshDisciplineRecordList');
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
},
// 加载字典数据
async loadDictData() {
try {
this.statusDict = await getDict('student_discipline_status');
} catch (error) {
console.error('加载字典数据失败:', error);
}
},
// 获取字典标签
getDictLabel(value) {
return getDictLabel(this.statusDict, value) || value;
},
// 获取状态类型
getStatusType(status) {
const fallbackMap = {
1: 'success', // 生效
2: 'info', // 已撤销
};
return getDictTagType(this.statusDict, status, fallbackMap);
},
// 获取违纪记录列表(仅显示当前用户创建的)
async getList() {
this.loading = true;
try {
const res = await request({
url: '/disciplineRecord/disciplineRecord/myCreated',
method: 'GET',
params: this.queryParams,
});
if (res && res.code === 200) {
const newList = res.rows || [];
if (this.queryParams.pageNum === 1) {
this.disciplineRecordList = newList;
} else {
this.disciplineRecordList = [...this.disciplineRecordList, ...newList];
}
this.total = res.total || 0;
// 更新加载更多状态
if (this.disciplineRecordList.length >= this.total) {
this.loadMoreStatus = 'nomore';
} else {
this.loadMoreStatus = 'loadmore';
}
} else {
uni.showToast({
title: res?.msg || '获取数据失败',
icon: 'none',
});
}
} catch (error) {
console.error('获取违纪记录列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none',
});
} finally {
this.loading = false;
}
},
// 搜索处理
handleSearch() {
// 解析搜索关键词
const keyword = this.searchKeyword.trim();
// 重置查询参数
this.queryParams.pageNum = 1;
// 如果关键词为空,清空搜索条件
if (!keyword) {
this.queryParams.studentName = '';
this.queryParams.studentNo = '';
} else {
// 同时按学生姓名和学号搜索
this.queryParams.studentName = keyword;
this.queryParams.studentNo = keyword;
}
// 重新加载列表
this.getList();
},
// 加载更多
loadMore() {
if (this.loadMoreStatus === 'loading' || this.loadMoreStatus === 'nomore') {
return;
}
this.loadMoreStatus = 'loading';
this.queryParams.pageNum += 1;
this.getList();
},
// 刷新列表
handleSuccess() {
this.queryParams.pageNum = 1;
this.getList();
},
// 新增按钮操作
handleAdd() {
uni.navigateTo({
url: '/pages/xskh/wjjlEntry/disciplineRecordForm?mode=add',
});
},
// 查看按钮操作
handleView(row) {
uni.navigateTo({
url: `/pages/xskh/wjjlEntry/disciplineRecordForm?mode=view&id=${row.id}`,
});
},
// 编辑按钮操作
handleEdit(row) {
uni.navigateTo({
url: `/pages/xskh/wjjlEntry/disciplineRecordForm?mode=edit&id=${row.id}`,
});
},
// 删除按钮操作
handleDelete(row) {
uni.showModal({
title: '提示',
content: `确定要删除学生"${row.studentName}"的违纪记录吗?`,
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({ title: '删除中...' });
await request({
url: `/disciplineRecord/disciplineRecord/${row.id}`,
method: 'DELETE',
});
uni.hideLoading();
uni.showToast({
title: '删除成功',
icon: 'success',
});
this.handleSuccess();
} catch (error) {
uni.hideLoading();
console.error('删除失败:', error);
uni.showToast({
title: '删除失败',
icon: 'error',
});
}
}
},
});
},
},
};
</script>
<style lang="scss" scoped>
.discipline-record-container {
background-color: #f5f6fa;
min-height: 100vh;
}
.search-bar {
padding: 20rpx;
background-color: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
}
.content-wrapper {
padding: 20rpx;
padding-bottom: 120rpx;
}
.empty-container {
padding: 100rpx 20rpx;
}
.record-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.record-card {
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-bottom: 1px solid #f0f0f0;
background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
}
.student-info {
flex: 1;
display: flex;
align-items: center;
gap: 8rpx;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.student-no {
font-size: 26rpx;
color: #909399;
}
.record-status {
margin-left: 16rpx;
}
.card-content {
padding: 24rpx;
}
.info-item {
display: flex;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.label {
font-size: 28rpx;
color: #606266;
min-width: 140rpx;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #303133;
flex: 1;
word-break: break-all;
}
.value-score {
color: #f56c6c;
font-weight: bold;
}
// 操作按钮
.card-actions {
display: flex;
justify-content: flex-end;
padding: 20rpx;
border-top: 1px solid #f0f0f0;
gap: 16rpx;
}
.action-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
min-width: 120rpx;
border-radius: 20rpx;
::v-deep .u-button {
border-radius: 20rpx;
padding: 16rpx 20rpx;
min-width: 120rpx;
height: auto;
line-height: 1.2;
}
}
.btn-text {
font-size: 24rpx;
margin-left: 8rpx;
font-weight: 500;
color: #ffffff;
}
.load-more {
padding: 20rpx 0;
}
/* 浮动新增按钮 */
.float-add-button {
position: fixed;
right: 40rpx;
bottom: 100rpx;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: linear-gradient(135deg, #3c9cff, #0051d9);
box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
transition: all 0.3s;
&:active {
transform: scale(0.95);
}
}
// uview组件样式调整
::v-deep .u-tag {
margin: 0;
}
::v-deep .u-button {
border-radius: 8rpx;
}
</style>