删除不需要的文件及提交移动端的代码

This commit is contained in:
zlwu committed 2025-08-06 16:01:27 +08:00
1 parent 86f65df783
commit 134f711349
751 files changed
+3879 -115492

No files matched your search

+1 -2
View File
@@ -1,3 +1,2 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
temp/
test/
+7
View File
@@ -162,3 +162,10 @@ coverage/
*.p12
*.keystore/xgxt/
excel/
# 吴自龙注释
test/
script/
stgl-api-test.js
update/
本地临时文件目录/
@@ -1,2 +0,0 @@
-- 添加批次ID字段
ALTER TABLE xljk_consultation_schedule ADD COLUMN batch_id VARCHAR(50) DEFAULT NULL COMMENT '批次ID,用于标识同一批次创建的预约时段';
@@ -128,7 +128,7 @@ spring.data:
# 端口,默认为6379
port: 16379
# 数据库索引
database: 0
database: 8
# redis 密码必须配置
password: Hzdyjs@123456
# 连接超时时间
File diff suppressed because it is too large. Load diff
@@ -1,506 +1,216 @@
# 代码调整记录 - 2025-01-07 - wuzilong
# 移动端首页界面重构 - 2025-01-07
## 2025-01-07 20:15:00 - 学生信息详细对话框改为完全禁用模式
## 📋 变更概述
### 📋 变更描述
将学生信息详细对话框(stuMsgDetailDialog.vue)中所有输入控件从 `readonly` 状态改为 `disabled` 状态,实现完全禁用效果。
**时间**: 2025-01-07
**开发者**: wuzilong
**变更类型**: 界面重构、权限控制
### 🎯 变更目标
- 统一所有输入控件的禁用状态
- 从 `readonly`(可读但不可编辑)改为 `disabled`(完全禁用)
- 修复 linter 错误
## 🎯 变更目标
### 🔧 具体修改内容
完全重构移动端首页界面,实现以下目标:
1. 简化界面结构,移除底部导航,只保留单页面
2. 重新设计头部,包含用户信息和退出登录功能
3. 采用权限控制的服务卡片布局
4. 将所有emoji图标替换为u-icon组件
5. 确保界面风格与web端保持一致
#### 1. 导入错误修复
## 🔄 主要变更
### 1. 创建权限管理工具 (`mobile/src/utils/permission.js`)
**功能**: 移动端权限校验工具,参考web端实现
**核心功能**:
- `checkPermi()`: 字符权限校验
- `checkRole()`: 角色权限校验
- `hasPermission()`: 权限指令校验函数
- `hasPermi()`: Vue过滤器权限校验
- `hasRole()`: 用户角色校验
- `permissionMixin`: 权限控制混入
**使用方式**:
```javascript
// ElMessage导入修复
// 尝试过的方式:import ElMessage from 'element-plus/es/components/message';
// 最终正确方式:
import { ElMessage } from 'element-plus';
// 在组件中使用
import { hasPermi, permissionMixin } from '@/utils/permission'
// Request导入类型错误修复
// @ts-ignore
import request from '@/utils/request';
// 混入方式
mixins: [permissionMixin]
// 模板中使用
v-if="hasPermi(['system:user:list'])"
```
#### 2. 输入控件状态统一
将所有 `el-input` 的 `readonly` 属性改为 `disabled` 属性:
### 2. 重构首页界面 (`mobile/src/pages/index/index.vue`)
**涉及的输入控件**:
- 基础信息:英文姓名、曾用名、手机号码、邮政编码、户籍所在地、家庭地址、现住址等
- 扩展信息:宗教信仰、毕业院校、电子邮箱、QQ、微信、紧急联系人信息、助学贷款金额等
- 扩展文本域:特长爱好、获奖情况、违纪记录、社会活动等
- 家庭信息:父母姓名、电话、工作单位、监护人信息、家庭收入等
- 健康信息:身高、体重、健康状况、残疾等级、疾病史等
- 银行卡信息:银行卡号、持卡人姓名、开户银行、开户支行等
**变更前**:
- 复杂的仪表板界面,包含欢迎横幅、统计数据、待办任务、新闻动态等
- 使用emoji图标
- 底部tabbar导航
#### 3. 状态对比
- **readonly**:字段可被选中和复制,但不可编辑
- **disabled**:字段完全禁用,不可选中、复制或编辑,呈现灰色状态
**变更后**:
- **头部区域**: 用户头像+昵称+部门信息 + 退出登录按钮
- **内容区域**: 按业务模块分组的服务卡片
- **权限控制**: 所有服务卡片支持权限控制
- **图标系统**: 全部使用u-icon组件
### 📁 影响文件
- `web/src/views/stuMsgSupply/stuMsgDetailDialog.vue`
**头部功能**:
- 点击用户头像: 跳转到个人中心页面
- 点击退出登录: 确认后退出并清理本地数据
### ✅ 验证结果
- [x] 修复了 ElMessage 导入错误
- [x] 所有文本输入框改为 disabled 状态
- [x] 所有文本域改为 disabled 状态
- [x] select、date-picker、tree-select 保持原有 disabled 状态
- [x] 界面呈现完全禁用的灰色效果
- [x] 无 linter 错误
**服务模块分组**:
- 学工管理 (6个服务): 学生信息、宿舍管理、学生请假、心理健康、社团管理、心理咨询
- 后勤管理 (6个服务): 物业报修、场地预约、报告厅预约、满意度调查、食堂管理、物业管理
- 招生就业 (3个服务): 学生管理、毕业生信息、就业信息
- 人事管理 (3个服务): 员工管理、出国申请、培训管理
- 财务管理 (3个服务): 预算管理、费用报销、发票管理
- 系统管理 (6个服务): 用户管理、角色管理、菜单管理、部门管理、参数设置、在线用户
- 工作流 (5个服务): 流程定义、我发起的、我的待办、我的已办、抄送我的
### 🎯 用户体验
- 更明显的视觉禁用效果(灰色显示)
- 防止用户尝试选中或复制内容
- 统一的禁用状态,界面更加一致
**权限控制示例**:
```vue
<ServiceItem
v-if="hasPermi(['student:info:list'])"
title="学生信息"
@click="handleServiceClick"
>
```
---
### 3. 更新ServiceItem组件 (`mobile/src/components/ServiceItem.vue`)
## 2025-01-07 19:50:00 - 学生信息详细对话框改为只读模式
**变更内容**:
- 支持u-icon组件通过slot传入
- 添加深度选择器样式支持u-icon
- 保持向后兼容emoji图标
### 📋 变更描述
将学生信息详细对话框(stuMsgDetailDialog.vue)从编辑模式改为只读查看模式,所有输入控件不可编辑。
**使用方式**:
```vue
<ServiceItem title="学生信息" @click="handleServiceClick">
<template #icon>
<u-icon name="account" size="24" color="inherit"></u-icon>
</template>
</ServiceItem>
```
### 🎯 变更目标
- 将界面从信息完善对话框改为信息查看对话框
- 移除编辑和提交功能,仅保留查看功能
## 🎨 样式设计
### 🔧 具体修改内容
### 头部设计
- 渐变背景: `linear-gradient(135deg, $uni-color-primary, $uni-color-primary-dark)`
- 用户头像: 圆形,48px × 48px,带白色边框
- 退出按钮: 半透明圆形背景,hover效果
#### 1. 界面标题修改
- 默认标题:`学生信息完善` → `学生信息详情查看`
- Props默认值:`title: '学生信息完善'` → `title: '学生信息详情查看'`
### 服务卡片
- 每行3个卡片的自适应布局
- 卡片悬停效果: 向上移动3px + 阴影变化
- 图标容器: 圆形背景,主题色
#### 2. 底部按钮修改
- 移除:确定按钮(提交功能)
- 保留:关闭按钮(`取消` → `关闭`)
### 响应式适配
- 小屏设备(<375px): 调整padding和图标尺寸
- 保持良好的触摸交互体验
#### 3. 输入控件只读化
**基础信息Tab**:
- `el-input`:添加 `readonly` 属性
- `el-select`:添加 `disabled` 属性
- `el-date-picker`:添加 `disabled` 属性
- `el-tree-select`:添加 `disabled` 属性
- 照片上传:改为 `<div class="photo-display">` 纯显示
## 🔐 权限集成
**扩展信息Tab**:
- 所有文本输入框:添加 `readonly` 属性
- 所有下拉选择器:添加 `disabled` 属性
- 所有文本域:添加 `readonly` 属性
**家庭信息Tab**:
- 父母信息输入框:添加 `readonly` 属性
- 监护人信息输入框:添加 `readonly` 属性
- 经济状况选择器:添加 `disabled` 属性
**健康信息Tab**:
- 身高体重输入框:添加 `readonly` 属性
- 血型选择器:添加 `disabled` 属性
- 是否残疾等选择器:添加 `disabled` 属性
- 健康相关文本域:添加 `readonly` 属性
**学籍信息Tab**:
- 培养方式选择器:添加 `disabled` 属性
- 录取类别选择器:添加 `disabled` 属性
- 学籍状态选择器:添加 `disabled` 属性
- 入学日期选择器:添加 `disabled` 属性
**银行卡信息Tab**:
- 所有银行卡相关输入框:添加 `readonly` 属性
#### 4. 功能函数移除
- 移除:`submitForm()` 提交表单函数
- 移除:`beforePhotoUpload()` 照片上传校验函数
- 移除:`handlePhotoUpload()` 照片上传处理函数
#### 5. 样式优化
添加照片显示容器样式:
```css
.photo-display {
border: 1px solid var(--el-border-color);
border-radius: 6px;
overflow: hidden;
width: 120px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
### 权限检查计算属性
```javascript
computed: {
hasStudentServices() {
return this.hasPermi(['student:info:list']) ||
this.hasPermi(['student:dorm:list']) ||
// ... 其他权限
},
// ... 其他模块权限检查
}
```
### 📁 影响文件
- `web/src/views/stuMsgSupply/stuMsgDetailDialog.vue`
### 模块级权限控制
- 如果用户没有某模块的任何权限,整个模块卡片组不显示
- 单个服务卡片根据具体权限显示/隐藏
- 工作流模块始终显示基础功能
### ✅ 验证结果
- [x] 所有Tab页的输入控件均为只读状态
- [x] 照片显示正常,无法上传新照片
- [x] 底部只有关闭按钮,无提交功能
- [x] 历史记录Tab正常显示
- [x] 界面标题正确显示为"学生信息详情查看"
- [x] 样式保持一致,用户体验良好
## 📱 用户体验优化
### 🎯 使用场景
该只读对话框适用于:
- 查看学生已提交的信息详情
- 历史记录查看
- 信息确认和核实
- 管理员查看学生信息
### 交互优化
- 退出登录: 二次确认 + Loading状态
- 服务点击: 震动反馈
- 页面进入: 渐显动画效果
- 卡片交互: 平滑过渡动画
### 📝 后续计划
- 如需要编辑功能,可创建单独的编辑对话框组件
- 可根据权限控制显示编辑按钮
- 可添加打印或导出功能
### 无障碍支持
- aria-label标签
- 合适的焦点管理
- 键盘导航支持
---
## 🔧 技术实现
## 2025-01-07 18:30:00 - 学生信息批次管理收集范围功能完善
### 核心依赖
- Vue 3组件式开发
- uview-plus图标组件
- 权限校验混入
- 用户状态管理
## 历史记录Tab页样式优化及箭头逻辑修正
### 变更时间
2025-01-07 16:45:00(初次优化)
2025-01-07 17:00:00(箭头逻辑修正)
### 变更内容
优化了学生信息完善弹窗中历史记录Tab页的显示样式,统一卡片大小,添加内容省略和tooltip效果,调整箭头显示逻辑。
### 修改文件
- `web/src/views/stuMsgSupply/stuMsgDialog.vue`
### 具体变更
1. **卡片样式统一化**
- 设置固定卡片尺寸:宽度200px,高度120px
- 使用flex布局:`flex-direction: column` 和 `justify-content: space-between`
- 确保所有卡片大小一致,提升视觉效果
2. **操作内容省略优化**
- 添加 `el-tooltip` 包装操作内容文本
- 设置省略规则:最多显示2行,超出显示省略号
- 添加条件tooltip:内容长度>20字符时才显示tooltip
- CSS样式:使用 `-webkit-line-clamp: 2` 实现多行省略
3. **箭头显示逻辑调整**
- **取消第四个卡片的向下箭头**:条件从 `(index + 1) % 4 === 0` 改为 `(index + 1) % 4 === 0 && index !== 3`
- **新增每行第一个卡片前的向右箭头**:条件为 `index % 4 === 0 && index >= 4`
- **修正箭头重复问题**:避免第二行第二个卡片出现两个箭头,只在每行第一个卡片前显示向右箭头
- 添加 `row-start-arrow` 和 `right-arrow` 样式类
4. **响应式布局优化**
- 在所有断点(1200px、900px、600px)下隐藏新的向右箭头
- 确保小屏幕下的显示效果不受影响
- 保持原有的列数布局逻辑
### 样式变更详情
```css
/* 卡片固定尺寸 */
.history-card {
width: 200px;
height: 120px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 内容省略效果 */
.operate-content {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
max-height: 2.8em;
}
/* 新的向右箭头 */
.row-start-arrow {
position: absolute;
left: -25px;
z-index: 10;
}
### 代码结构
```
index.vue (534行)
├── 模板部分 (204行) - 头部 + 内容区域
├── 脚本部分 (234行) - 权限控制 + 用户管理
└── 样式部分 (96行) - 响应式设计
```
### 用户体验改进
- **视觉一致性**:所有卡片大小统一,界面更整洁
- **内容可读性**:长文本显示省略号,悬停查看完整内容
- **流程清晰度**:箭头逻辑更合理,每行第一个卡片前显示向右箭头表示换行,避免箭头重复
- **适配性**:在不同屏幕尺寸下都有良好的显示效果
### 技术要点
- 使用CSS Grid/Flexbox实现响应式布局
- -webkit-line-clamp实现多行文本省略
- Element Plus tooltip组件增强交互体验
- 媒体查询确保移动端适配
---
## 学生信息完善弹窗蒙层加载效果优化
### 变更时间
2025-01-07 15:30:00
### 变更内容
优化了学生信息完善弹窗的蒙层加载效果,解决用户看不到加载状态的问题。
### 修改文件
- `web/src/views/stuMsgSupply/stuMsgDialog.vue`
### 具体变更
1. **添加对话框级别的蒙层**
- 在 `el-dialog` 上添加 `v-loading="dialogLoading"` 和 `element-loading-text="正在加载数据,请稍候..."`
- 新增 `dialogLoading` 响应式变量控制加载状态
2. **优化加载逻辑**
- 立即显示蒙层,防止闪烁
- 分阶段加载数据:第一阶段加载基础配置,第二阶段加载业务数据
- 增加最小显示时间到1000ms,确保用户能看到加载过程
- 增强日志输出,使用emoji提升可读性
3. **修复导入错误**
- 将 `import service from '@/utils/request'` 修正为 `import request from '@/utils/request'`
- 更新所有相关的API调用从 `service` 改为 `request`
4. **异常处理优化**
- 在 try-catch-finally 结构中确保蒙层能够正确隐藏
- 对话框关闭时重置加载状态
### 技术要点
- 使用Element Plus的v-loading指令实现蒙层效果
- 分阶段加载策略避免多API并发调用的时序问题
- 最小显示时间机制确保用户体验
### 预期效果
- 弹窗打开时显示明显的蒙层加载效果
- 防止用户在数据加载期间误操作
- 提供更好的用户体验和视觉反馈
---
## 变更概述
新增学生信息批次收集模块中的学生提交完善信息接口
## 变更时间
2025-01-07 14:30:00
## 变更人员
wuzilong
---
## 🔄 变更记录(按时间倒序)
### 2025-01-07 14:30:00 - 新增学生提交信息完善接口
#### 📋 变更说明
在学生信息批次收集模块中新增接口,用于存储学生提交的完善信息。
#### 🎯 业务需求
- 学生在信息完善界面提交数据时,需要先逻辑删除历史数据,再插入新数据
- 确保每个学生在同一批次中只有一条有效记录
- 支持使用前台传递的ID进行数据插入
#### 🔧 技术实现
**1. Controller层变更**
- 文件:`backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java`
- 新增接口:`POST /studentMsgBatch/studentMsgBatch/submitStudentMsg`
- 权限要求:`studentMsgBatch:studentMsgBatch:add`
- 入参:`XgStudentMsgBatchBo`
- 返回:`R<Void>`
**2. Service层变更**
- 接口文件:`backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgBatchService.java`
- 新增方法:`Boolean submitStudentMsg(XgStudentMsgBatchBo bo)`
- 实现文件:`backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgBatchServiceImpl.java`
- 实现业务逻辑:逻辑删除 + 数据插入
**3. Mapper层变更**
- 接口文件:`backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgBatchMapper.java`
- 新增方法:`int logicalDeleteByUserId(@Param("userId") Long userId)`
- XML文件:`backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsgBatch/XgStudentMsgBatchMapper.xml`
- 新增SQL:逻辑删除操作(只更新deleted字段,不更新update_by和update_time)
#### 📝 核心业务逻辑
```java
// 第一步:逻辑删除当前登录人的历史数据
UPDATE xg_student_msg_batch
SET deleted = 1
WHERE id = #{userId} AND deleted = 0
// 第二步:插入新的学生信息数据(使用前台传递的id)
XgStudentMsgBatch add = MapstructUtils.convert(bo, XgStudentMsgBatch.class);
if (bo.getId() != null) {
add.setId(bo.getId());
}
baseMapper.insert(add);
```
#### ✅ 测试验证
- [ ] 接口功能测试
- [ ] 权限验证测试
- [ ] 数据完整性测试
- [ ] 并发操作测试
#### 📚 文档更新
- [x] 变更记录文档
- [ ] API文档更新(待补充到学工管理API文档中)
#### 🏷️ 关联标签
- 模块:学工管理
- 功能:学生信息批次收集
- 类型:新增接口
- 影响范围:后端API
---
## 📋 待办事项
1. 补充API文档到相应的学工管理API文档中
2. 编写完整的接口测试用例
3. 前端对接测试
## 🔗 相关文件
- Controller: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgBatchController.java`
- Service: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/`
- Mapper: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgBatchMapper.java`
- XML: `backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsgBatch/XgStudentMsgBatchMapper.xml`
---
# 代码调整记录 - 2025-01-07
**调整时间**: 2025-01-07
**调整人员**: wuzilong
**调整范围**: 学生信息收集批次管理 - 历史记录功能
---
## 📝 本次调整概述
在学生信息完善对话框(`stuMsgDialog.vue`)中增加历史记录Tab页功能,展示用户的历史提交记录。
---
## 🔧 具体调整内容
### 1. 新增历史记录Tab页
- **位置**: 在基础信息Tab前面增加"历史记录"Tab页
- **默认显示**: 打开对话框时默认显示历史记录Tab
- **数据来源**: 通过 `getStudentMsgHistory` 方法获取后端数据
### 2. 历史记录数据管理
- **新增变量**: `historyLoading`、`historyRecords`
- **接口调用**: `/studentMsgBatch/studentMsgBatch/history/{batchId}`
- **数据提取**: 从返回数据的 `historyMsg` 字段获取历史记录列表
### 3. 历史记录展示逻辑
- **空数据处理**: 当 `historyMsg` 为空时显示"暂无历史记录"
- **卡片展示**: 每条记录以卡片形式展示
- **布局设计**: 每行4个卡片,使用flex布局
- **连接线**: 卡片间用箭头连接,行尾显示向下箭头
### 4. 操作内容显示规则
- **infoStatus = 3**: 显示"信息填写提交"
- **infoStatus = 4 或 5**: 显示 `remark` 字段内容
- **其他状态**: 显示"未知操作"
### 5. 时间格式化
- **方法**: `formatDateTime`
- **格式**: `YYYY-MM-DD HH:mm:ss` 中文本地化格式
---
## 🎨 界面优化
### 卡片设计
- **卡片结构**: 头部(操作人+时间)+ 内容(操作内容)
- **悬停效果**: 阴影加深,轻微上移
- **边框圆角**: 8px 圆角设计
- **间距**: 16px 内边距,20px 底部外边距
### 连接线设计
- **水平箭头**: 卡片间显示 → 符号
- **换行箭头**: 每行末尾显示 ↓ 符号
- **颜色**: 主题蓝色 (#409eff)
- **字体**: 18px 粗体
### 响应式布局
- **>1200px**: 每行4列
- **900-1200px**: 每行3列
- **600-900px**: 每行2列,隐藏连接线
- **<600px**: 每行1列,隐藏连接线
---
## 🔄 数据流程
### 对话框打开流程
1. 设置默认Tab为"history"
2. 并行加载:行政区划、宿舍楼栋、历史记录、历史数据
3. 历史记录通过 `getStudentMsgHistory` 方法获取
4. 数据存储在 `historyRecords` 响应式变量中
### 关闭重置流程
1. 重置Tab为"history"
2. 清空 `historyRecords` 数组
3. 重置表单数据到默认值
---
## 📁 涉及文件
### 前端文件
- **主要文件**: `web/src/views/stuMsgSupply/stuMsgDialog.vue`
- **修改类型**: 新增功能、样式优化
### 后端接口
- **控制器**: `XgStudentMsgBatchController.java`
- **方法**: `getStudentMsgHistory(@PathVariable Long batchId)`
- **返回**: `Map<String, Object>` 包含 `historyMsg` 字段
---
## ✅ 测试要点
## ✅ 测试验证
### 功能测试
- [ ] 历史记录Tab页正常显示
- [ ] 无历史记录时显示空状态
- [ ] 有历史记录时正确显示卡片
- [ ] 操作内容根据状态正确显示
- [ ] 时间格式化正确
- [x] 用户头像点击跳转个人中心
- [x] 退出登录流程完整性
- [x] 权限控制正确性
- [x] 服务卡片交互
- [x] 响应式布局适配
### 界面测试
- [ ] 卡片布局每行4个
- [ ] 连接线和箭头正确显示
- [ ] 响应式布局在不同屏幕下正常
- [ ] 悬停效果正常
- [ ] 加载状态正常显示
### 兼容性测试
- [x] 保持ServiceItem组件向后兼容
- [x] 权限校验工具正确性
- [x] u-icon组件显示正常
### 数据测试
- [ ] 接口调用正常
- [ ] 数据解析正确
- [ ] 错误处理正常
- [ ] 空数据处理正常
## 📊 性能影响
### 优化项
- 移除了复杂的仪表板数据
- 简化了页面结构和状态管理
- 减少了不必要的API调用
### 文件大小
- index.vue: 压缩为534行 (原1323行)
- 新增permission.js: 123行
- 更新ServiceItem.vue: +12行样式
## 🚀 部署注意事项
### 权限配置
- 确保后端已配置相应的权限标识
- 验证权限数据返回格式正确
- 检查超级管理员权限(`*:*:*`)
### 图标资源
- 确保uview-plus组件库正确安装
- 验证所有u-icon名称有效性
- 测试图标在不同设备上的显示效果
## 📝 后续优化建议
1. **服务路由**: 为每个服务卡片配置具体的跳转路由
2. **图标主题**: 考虑为不同模块使用不同颜色的图标
3. **加载状态**: 添加权限数据加载时的骨架屏
4. **缓存优化**: 权限数据的本地缓存策略
5. **搜索功能**: 添加服务快速搜索功能
## 🔄 相关文档更新
- [x] 移动端开发规范 (cursorrules-mobile.md)
- [x] 权限管理文档
- [x] API文档同步更新
- [x] 组件使用说明
---
## 🎯 预期效果
1. **用户体验**: 用户可以直观查看历史提交记录
2. **界面美观**: 卡片式布局清晰美观,连接线体现时间流程
3. **响应式**: 在不同设备上都有良好的显示效果
4. **性能**: 历史记录和表单数据并行加载,提升加载速度
---
## 📋 后续优化建议
1. **分页加载**: 如果历史记录过多,考虑分页显示
2. **筛选功能**: 可按时间范围或操作类型筛选记录
3. **详情查看**: 点击卡片可查看更详细的历史信息
4. **导出功能**: 支持导出历史记录为Excel
---
**变更状态**: ✅ 已完成
**测试状态**: 🔄 待测试
**上线状态**: 📋 待上线
**变更影响评估**: 🟢 低风险
**回滚方案**: Git版本回滚
**测试状态**: ✅ 已完成基础测试
@@ -0,0 +1,156 @@
# 🔄 代码调整记录 - 2025-01-07 - 移动端图标系统
## 📋 变更总览
**变更日期**: 2025-01-07 16:45:23
**变更负责人**: Cursor AI
**变更类型**: 移动端图标系统增强
**影响范围**: 移动端首页登出功能
## 🎯 变更内容
### 1. 新增iconfont图标
**变更文件**: `mobile/src/static/fonts/iconfont.css`
**新增内容**:
```css
/* 登出图标 */
.icon-logout {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.59L17 17l5-5-5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z'/%3E%3C/svg%3E");
}
```
**图标特点**:
- ✅ 采用SVG data URI格式,无需额外字体文件
- ✅ 支持currentColor,可通过CSS修改颜色
- ✅ 响应式适配,支持所有平台
- ✅ 语义化设计,清晰表达"登出"含义
### 2. 更新移动端首页
**变更文件**: `mobile/src/pages/index/index.vue`
**修改内容**:
```vue
<!-- 变更前 -->
<text class="iconfont icon-close logout-icon"></text>
<!-- 变更后 -->
<text class="iconfont icon-logout logout-icon"></text>
```
**变更原因**:
- ❌ `icon-close` 语义不清,容易与"关闭"操作混淆
- ✅ `icon-logout` 语义明确,用户体验更佳
## 🔧 技术细节
### SVG图标设计
**原始SVG代码**:
```svg
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'>
<path d='M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.59L17 17l5-5-5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z'/>
</svg>
```
**data URI编码过程**:
1. 转换特殊字符
2. URL编码处理
3. 添加data URI前缀
### 图标系统优势
根据 `mobile/docs/图标管理系统使用指南.md`:
- **轻量级**: 无需下载字体文件
- **兼容性**: 支持所有uni-app平台
- **可定制**: 支持颜色和尺寸调整
- **性能优**: 内联SVG,加载快速
## ✅ 测试验证
### 功能测试
- [x] 图标正常显示
- [x] 点击事件响应正常
- [x] 多平台兼容性验证
- [x] 响应式适配检查
### 视觉测试
- [x] 图标清晰度符合要求
- [x] 与UI风格保持一致
- [x] 颜色继承正常工作
- [x] 不同尺寸下显示正常
## 📝 代码质量
### 符合规范
- ✅ 遵循项目图标命名规范
- ✅ 使用推荐的SVG data URI方式
- ✅ 符合移动端开发规范
- ✅ 注释清晰,便于维护
### 维护性
- 📁 图标统一管理在CSS文件中
- 📖 完整的使用文档支持
- 🔄 易于后续扩展和修改
## 🚀 后续建议
### 1. 图标库完善
考虑添加更多常用图标:
- `icon-power` - 电源/开关图标
- `icon-exit` - 退出图标
- `icon-sign-out` - 注销图标
### 2. 图标管理工具
可以开发简单的图标管理脚本:
```javascript
// 批量生成图标CSS
function generateIconCSS(iconName, svgPath) {
return `.icon-${iconName} {
background-image: url("data:image/svg+xml;charset=utf8,${encodeSVG(svgPath)}");
}`;
}
```
### 3. 设计规范
建议制定图标设计规范:
- 统一的视觉风格
- 标准的尺寸规格
- 一致的线条粗细
## 📊 影响评估
### 用户体验改善
- ✅ 图标语义更清晰
- ✅ 操作意图更明确
- ✅ 减少用户困惑
### 技术债务
- ✅ 无新增技术债务
- ✅ 遵循最佳实践
- ✅ 便于后续维护
### 性能影响
- ✅ 无性能负面影响
- ✅ 图标内联,加载更快
- ✅ 减少HTTP请求
## 🔗 相关文档
- **图标系统文档**: `mobile/docs/图标管理系统使用指南.md`
- **移动端开发规范**: `.cursor/cursorrules-mobile.md`
- **项目规范总览**: `.cursor/rules.md`
## 📞 技术支持
如需扩展图标系统或遇到问题,请参考:
1. 图标管理系统使用指南
2. 在线SVG编辑工具
3. data URI转换工具
---
**✨ 总结**: 成功创建并部署了语义化的logout图标,提升了移动端用户体验,遵循了项目最佳实践。图标采用SVG data URI格式,具备良好的兼容性和性能表现。
@@ -0,0 +1,61 @@
# 移动端控制台错误修复记录
**日期**: 2025-01-17
**操作人**: Cursor AI
**时间**: 2025-01-17 14:30:00
## 🚨 修复的问题
### 1. 修复 "Refused to set unsafe header 'referer'" 错误
**问题描述**: 移动端H5环境下,浏览器禁止JavaScript手动设置referer头,导致控制台报错。
**修复位置**: `mobile/src/utils/request.js`
- 第80行:注释掉统一请求方法中的referer设置
- 第382行:注释掉加密登录方法中的referer设置
**修复内容**:
```javascript
// 修复前
referer: options.referer || REQUEST_CONFIG.referer,
// 修复后(注释掉)
// 注意:在H5环境中,浏览器会自动设置referer,手动设置会报错
// referer: options.referer || REQUEST_CONFIG.referer,
```
### 2. 修复 UInput组件border属性类型错误
**问题描述**: uview-plus的u-input组件期望border属性为字符串类型,但传入了布尔值导致类型警告。
**修复位置**: `mobile/src/pages/login/index.vue`
- 第22行:账号输入框的border属性
- 第37行:密码输入框的border属性
**修复内容**:
```vue
<!-- 修复前 -->
:border="true"
<!-- 修复后 -->
border="true"
```
## ✅ 修复结果
- ❌ 移除了所有会导致"unsafe header"错误的referer设置
- ❌ 修复了UInput组件的属性类型问题
- ✅ 保持了功能完整性(浏览器会自动处理referer)
- ✅ 消除了控制台错误和警告
## 📝 技术说明
1. **referer处理**: H5环境下浏览器自动设置referer,无需手动指定
2. **组件属性**: uview-plus组件要求严格的属性类型匹配
3. **向后兼容**: 修复不影响小程序等其他端的功能
## 🔄 影响范围
- **前端影响**: 移动端H5登录页面控制台错误消除
- **后端影响**: 无,referer信息仍然正常传递
- **其他端影响**: 无,小程序端不受影响
@@ -0,0 +1,130 @@
# 移动端加密密钥配置修复记录
**变更时间**: 2025-01-25 11:30:00
**变更人员**: wuzilong
**变更类型**: 安全配置修复
**影响范围**: 移动端加密模块
## 🎯 变更目标
修复移动端与web端加密密钥配置不一致的问题,确保前后端加密通信正常。
## 🔍 问题发现
在检查移动端和web端的加密密钥配置时发现:
### 原始问题
1. **移动端RSA私钥缺失**: `mobile/src/utils/crypto.js` 中 `RSA_PRIVATE_KEY` 配置为空字符串
2. **解密功能异常**: 由于私钥缺失,响应解密功能无法正常工作
3. **配置不一致**: 移动端和web端配置文档中的密钥配置格式不统一
### 配置对比结果
| 组件 | RSA公钥 | RSA私钥 | 状态 |
|------|---------|---------|------|
| web端配置文档 | ✅ 已配置 | ✅ 已配置 | 正常 |
| 移动端原配置 | ✅ 已配置 | ❌ 空字符串 | 异常 |
| 移动端修复后 | ✅ 已配置 | ✅ 已配置 | 正常 |
## 🔧 修复方案
### 1. 补充RSA私钥配置
**文件**: `mobile/src/utils/crypto.js`
**修改内容**:
```javascript
// 修复前
RSA_PRIVATE_KEY: '',
// 修复后
RSA_PRIVATE_KEY: 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=',
```
### 2. 增强解密函数日志
**文件**: `mobile/src/utils/crypto.js`
**修改内容**: 为 `decrypt` 函数添加详细的日志记录,便于调试和问题定位
### 3. 新增密钥验证功能
**文件**: `mobile/src/utils/crypto.js`
**新增函数**:
- `validateKeyPair()`: 验证RSA密钥对的有效性
- `getConfigSummary()`: 获取当前加密配置摘要
### 4. 更新请求模块导出
**文件**: `mobile/src/utils/request.js`
**新增导出**:
- `validateCryptoKeys`: 密钥对验证方法
- `getCryptoConfigSummary`: 获取加密配置摘要
## ✅ 验证结果
### 密钥配置一致性检查
- ✅ RSA公钥: 移动端与web端配置文档完全一致
- ✅ RSA私钥: 已补充完整,与web端配置文档一致
- ✅ 客户端ID: `e5cd7e4891bf95d1d19206ce24a7b32e`
- ✅ 加密头字段: `encrypt-key`
### 功能验证
- ✅ RSA加密功能: 正常工作
- ✅ RSA解密功能: 已修复,可正常解密
- ✅ AES加密解密: 正常工作
- ✅ 请求响应加密流程: 完整可用
## 📋 后续建议
### 1. 安全注意事项
- 🔐 私钥配置应考虑从环境变量或安全配置文件读取
- 🔐 生产环境建议定期更换密钥对
- 🔐 考虑实现密钥轮换机制
### 2. 监控和维护
- 📊 添加加密解密成功率监控
- 📊 记录加密相关错误统计
- 📊 定期验证密钥对有效性
### 3. 文档更新
- 📚 更新API文档中的加密说明
- 📚 完善移动端开发规范中的安全配置部分
## 🎯 变更影响
### 正面影响
- ✅ 修复了移动端响应解密功能
- ✅ 统一了前后端密钥配置
- ✅ 增强了密钥配置的可维护性
- ✅ 提供了密钥验证工具
### 风险评估
- ⚠️ 无向后兼容性问题
- ⚠️ 无数据丢失风险
- ⚠️ 配置修复后需要重新部署移动端应用
## 📝 测试验证
### 建议测试项目
1. **登录加密测试**: 验证移动端登录加密流程
2. **响应解密测试**: 验证加密响应的正确解密
3. **密钥对验证测试**: 运行 `validateCryptoKeys()` 函数
4. **跨端兼容性测试**: 确保与web端和后端的加密兼容性
### 测试命令
```javascript
// 在移动端控制台执行
import { validateCryptoKeys, getCryptoConfigSummary } from '@/utils/request.js';
// 验证密钥对
console.log('密钥对验证结果:', validateCryptoKeys());
// 查看配置摘要
console.log('加密配置摘要:', getCryptoConfigSummary());
```
---
**变更完成**: ✅
**验证状态**: ✅
**文档更新**: ✅
**备注**: 此次修复确保了移动端与web端加密密钥配置的完全一致,解决了响应解密异常问题,为移动端安全通信提供了可靠保障。
@@ -0,0 +1,73 @@
# 代码调整记录 - 2025-01-27 - 移动端底部导航栏移除
## 修改概述
移除移动端底部导航栏,只保留首页,删除办公中心和我的页面。
## 修改内容
### 1. 配置文件修改
- **文件**: `mobile/src/pages.json`
- **修改类型**: 删除tabBar配置和相关页面配置
- **具体变更**:
- 删除完整的tabBar配置块
- 移除`pages/workbench/index`页面配置
- 移除`pages/profile/index`页面配置
### 2. 页面文件删除
- **删除文件**:
- `mobile/src/pages/workbench/index.vue` - 办公中心页面
- `mobile/src/pages/profile/index.vue` - 我的页面
### 3. 静态资源清理
- **删除文件**:
- `mobile/src/static/tabbar/workbench.svg` - 办公中心图标
- `mobile/src/static/tabbar/workbench-active.svg` - 办公中心激活图标
- `mobile/src/static/tabbar/profile.svg` - 我的页面图标
- `mobile/src/static/tabbar/profile-active.svg` - 我的页面激活图标
### 4. 工具类文件更新
- **文件**: `mobile/src/utils/gestures.js`
- **修改内容**:
- 更新PageSwipeGesture类,移除workbench和profile页面引用
- 简化页面滑动功能,适配单页面模式
- **文件**: `mobile/src/utils/user.js`
- **修改内容**: 从公共页面列表中移除profile页面
- **文件**: `mobile/src/utils/accessibility.js`
- **修改内容**: 移除workbench和profile页面的标题配置
### 5. 首页功能调整
- **文件**: `mobile/src/pages/index/index.vue`
- **修改内容**:
- 修改用户头像点击事件,从跳转profile页面改为显示toast提示
- 保持退出登录功能正常
## 技术影响
### 正面影响
1. **应用结构简化**: 移除了底部导航栏,界面更加简洁
2. **用户体验优化**: 专注于单一首页功能,减少页面跳转复杂度
3. **代码维护**: 减少了页面维护工作量
### 需要关注的点
1. **用户信息查看**: 原profile页面功能需要通过其他方式实现(如头部用户信息显示)
2. **办公功能**: 原workbench页面的功能已整合到首页各模块中
3. **导航模式**: 从tabBar模式改为普通页面导航模式
## 测试建议
1. 验证首页各功能模块正常显示和权限控制
2. 确认用户登录状态和退出功能正常
3. 检查页面跳转和路由功能
4. 验证各业务模块的访问入口
## 后续优化建议
1. 可考虑在首页头部增加更多用户信息展示
2. 优化首页布局,突出常用功能
3. 考虑添加搜索功能来快速定位服务
---
**修改时间**: 2025-01-27 23:45:00
**修改人员**: Cursor AI
**测试状态**: 已完成基础验证
**上线状态**: 待测试
+7
View File
@@ -0,0 +1,7 @@
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
@@ -0,0 +1,348 @@
# 📚 图标管理系统使用指南
## 🎯 概述
项目采用了 **双图标系统**:
- **iconfont 自定义图标** (推荐) - 基于 SVG data URI 的轻量级图标系统
- **uview-plus 图标** (辅助) - UI库自带图标,部分图标存在显示问题
## 📁 文件结构
```
mobile/src/static/fonts/
├── iconfont.css # 主图标文件(SVG data URI)
├── iconfont.ttf # 字体文件(可选)
└── iconfont.woff # 字体文件(可选)
mobile/src/
├── main.js # 引入图标CSS
├── pages/iconfont-demo/ # 图标演示页面
└── pages/uview-icon-test/ # uview图标问题测试页面
```
## 🚀 添加新图标 - 超级简单!
### 方法一:SVG Data URI(推荐)
**优势**:
- ✅ 无需下载字体文件
- ✅ 支持彩色图标
- ✅ 完美适配所有平台
- ✅ 加载速度最快
**操作步骤**:
#### 1. 从 iconfont.cn 下载 SVG
1. 访问 [iconfont.cn](https://www.iconfont.cn/)
2. 搜索需要的图标
3. 下载 SVG 格式
#### 2. 转换为 data URI
使用在线工具或以下代码:
```javascript
// SVG 转 data URI 工具函数
function svgToDataUri(svgContent) {
const encoded = encodeURIComponent(svgContent)
.replace(/'/g, '%27')
.replace(/"/g, '%22');
return `data:image/svg+xml;charset=utf8,${encoded}`;
}
// 示例
const svgContent = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'>
<path d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z'/>
</svg>`;
```
#### 3. 添加到 CSS 文件
在 `mobile/src/static/fonts/iconfont.css` 中添加:
```css
/* 新增图标示例 */
.icon-add-circle {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z'/%3E%3C/svg%3E");
}
```
#### 4. 在页面中使用
```vue
<template>
<text class="iconfont icon-add-circle" style="font-size: 24px; color: #1890ff;"></text>
</template>
```
### 方法二:iconfont.cn 项目同步(适合大量图标)
#### 1. 创建 iconfont 项目
1. 在 iconfont.cn 注册账号
2. 创建新项目
3. 添加需要的图标到项目
#### 2. 下载代码
1. 点击"下载至本地"
2. 选择 "Font class" 或 "Unicode" 方式
3. 下载解压得到文件
#### 3. 替换项目文件
```bash
# 将下载的文件复制到项目
cp iconfont.css mobile/src/static/fonts/
cp iconfont.ttf mobile/src/static/fonts/
cp iconfont.woff mobile/src/static/fonts/
```
#### 4. 更新字体引用(如需要)
在 `uni.scss` 或相关文件中更新字体路径:
```scss
@font-face {
font-family: 'iconfont';
src: url('./static/fonts/iconfont.woff') format('woff'),
url('./static/fonts/iconfont.ttf') format('truetype');
}
```
## 🎨 使用规范
### 基础用法
```vue
<template>
<!-- 方式1:直接使用class -->
<text class="iconfont icon-user"></text>
<!-- 方式2:自定义样式 -->
<text class="iconfont icon-setting custom-icon"></text>
<!-- 方式3:动态样式 -->
<text
class="iconfont icon-home"
:style="{
fontSize: '24px',
color: iconColor
}"
></text>
</template>
<style>
.custom-icon {
font-size: 20px;
color: #52c41a;
}
</style>
```
### 响应式尺寸
```vue
<template>
<!-- 小图标 -->
<text class="iconfont icon-user icon-sm"></text>
<!-- 中等图标 -->
<text class="iconfont icon-user icon-md"></text>
<!-- 大图标 -->
<text class="iconfont icon-user icon-lg"></text>
</template>
<style>
.icon-sm { width: 16px; height: 16px; font-size: 16px; }
.icon-md { width: 24px; height: 24px; font-size: 24px; }
.icon-lg { width: 32px; height: 32px; font-size: 32px; }
</style>
```
## 📋 当前可用图标列表
### 基础图标
- `icon-user` - 用户
- `icon-lock` - 锁定
- `icon-eye` - 显示
- `icon-eye-close` - 隐藏
- `icon-home` - 首页
- `icon-setting` - 设置
### 操作图标
- `icon-add` - 添加
- `icon-close` - 关闭
- `icon-check` - 确认
- `icon-search` - 搜索
- `icon-arrow-right` - 右箭头
### 功能图标
- `icon-message` - 消息
- `icon-phone` - 电话
- `icon-mail` - 邮件
- `icon-login` - 登录
## 🔧 管理工具
### 在线 SVG 编辑器
- [SVG Editor](https://svg-edit.github.io/svgedit/)
- [Boxy SVG](https://boxy-svg.com/)
### Data URI 转换工具
```javascript
// 工具函数:SVG 转 data URI
function createSvgDataUri(svgPath, viewBox = "0 0 24 24") {
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='${viewBox}' fill='currentColor'><path d='${svgPath}'/></svg>`;
return `url("data:image/svg+xml;charset=utf8,${encodeURIComponent(svg)}")`;
}
// 使用示例
const newIconCSS = `
.icon-new-feature {
background-image: ${createSvgDataUri("M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10...")};
}
`;
```
### 批量添加脚本
```javascript
// 批量生成图标CSS的脚本
const icons = [
{ name: 'star', path: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z' },
{ name: 'heart', path: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z' }
];
function generateIconCSS(icons) {
return icons.map(icon => `
.icon-${icon.name} {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='${icon.path}'/%3E%3C/svg%3E");
}`).join('\n');
}
```
## 🎯 最佳实践
### 1. 命名规范
```css
/* ✅ 推荐命名 */
.icon-user /* 功能性图标 */
.icon-user-add /* 带操作的图标 */
.icon-arrow-right /* 方向性图标 */
/* ❌ 避免命名 */
.icon-1 /* 无意义编号 */
.icon-temp /* 临时命名 */
```
### 2. 颜色管理
```scss
// 使用CSS变量管理图标颜色
:root {
--icon-primary: #1890ff;
--icon-success: #52c41a;
--icon-warning: #faad14;
--icon-error: #ff4d4f;
}
.icon-primary { color: var(--icon-primary); }
.icon-success { color: var(--icon-success); }
.icon-warning { color: var(--icon-warning); }
.icon-error { color: var(--icon-error); }
```
### 3. 尺寸规范
```scss
// 标准尺寸系统
.icon-xs { width: 12px; height: 12px; } // 超小
.icon-sm { width: 16px; height: 16px; } // 小
.icon-md { width: 24px; height: 24px; } // 中(默认)
.icon-lg { width: 32px; height: 32px; } // 大
.icon-xl { width: 48px; height: 48px; } // 超大
```
## 🔄 迁移指南
### 从 uview 图标迁移到 iconfont
```vue
<!-- 迁移前 -->
<u-icon name="logout" size="20" color="#fff"></u-icon>
<!-- 迁移后 -->
<text class="iconfont icon-close" style="font-size: 20px; color: #fff;"></text>
```
### 批量替换工具
```bash
# 使用正则表达式批量替换
# 将 <u-icon name="logout" 替换为 <text class="iconfont icon-close"
find ./src -name "*.vue" -exec sed -i 's/<u-icon name="logout"/<text class="iconfont icon-close"/g' {} \;
```
## 🚀 性能优化
### 1. 按需加载
```javascript
// 动态导入图标CSS(大型应用)
const loadIconFont = async () => {
if (!document.querySelector('#iconfont-css')) {
const link = document.createElement('link');
link.id = 'iconfont-css';
link.rel = 'stylesheet';
link.href = './static/fonts/iconfont.css';
document.head.appendChild(link);
}
};
```
### 2. CDN 优化
```css
/* 使用CDN加载字体文件 */
@font-face {
font-family: 'iconfont';
src: url('https://cdn.example.com/fonts/iconfont.woff') format('woff');
}
```
## 🛠️ 开发工具
### VS Code 插件推荐
- **SVG Viewer** - 预览SVG文件
- **CSS Peek** - 快速查看CSS定义
- **IconFont Helper** - iconfont图标提示
### 图标搜索工具
- [Iconify](https://iconify.design/) - 100,000+ 开源图标
- [Feather Icons](https://feathericons.com/) - 简洁线性图标
- [Heroicons](https://heroicons.com/) - Tailwind CSS图标
## ❓ 常见问题
### Q: 图标不显示怎么办?
A: 检查以下几点:
1. CSS文件是否正确引入
2. 类名是否拼写正确
3. 字体文件路径是否正确
4. 浏览器是否支持data URI
### Q: 如何修改图标颜色?
A: 使用 `color` 或 `fill` 属性:
```css
.my-icon {
color: #1890ff; /* 对于SVG data URI */
fill: #1890ff; /* 对于字体图标 */
}
```
### Q: 图标模糊怎么解决?
A: 确保图标尺寸为偶数像素:
```css
.icon-clear {
width: 24px; /* ✅ 偶数 */
height: 24px; /* ✅ 偶数 */
}
```
## 📞 技术支持
- **项目位置**: `mobile/src/static/fonts/`
- **演示页面**: `/pages/iconfont-demo/index`
- **测试页面**: `/pages/uview-icon-test/index`
- **文档更新**: 2025-01-07
---
**✨ 总结**: iconfont 图标系统让您轻松管理图标,添加新图标只需要复制粘贴几行CSS代码!推荐使用 SVG data URI 方式,兼容性最好,性能最优。
+2 -2
View File
@@ -25,9 +25,9 @@ module.exports = [
'no-console': 'warn',
'no-debugger': 'warn',
'no-unused-vars': 'warn',
indent: ['error', 2],
// indent: ['error', 2],
quotes: ['error', 'single'],
semi: ['error', 'never'],
// semi: ['error', 'never'],
},
},
];
+35 -1
View File
@@ -7,8 +7,9 @@
tabindex="0"
>
<view class="service-icon">
<slot name="icon">
<text class="icon" v-if="icon">{{ icon }}</text>
<slot v-else name="icon"></slot>
</slot>
</view>
<text class="service-title">{{ title }}</text>
<view v-if="badge" class="service-badge">
@@ -82,12 +83,33 @@ export default {
.icon {
font-size: 24px;
}
// 支持u-icon组件
:deep(.u-icon) {
font-size: 24px !important;
color: inherit !important;
}
// 支持iconfont图标
.iconfont {
width: 24px;
height: 24px;
font-size: 24px;
color: inherit;
filter: brightness(0) saturate(100%) invert(49%) sepia(98%) saturate(3332%)
hue-rotate(196deg) brightness(104%) contrast(101%);
}
}
.service-item:hover .service-icon {
background: $uni-color-primary;
color: $uni-text-color-inverse;
transform: scale(1.1);
.iconfont {
filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg)
brightness(100%) contrast(100%);
}
}
.service-title {
@@ -160,6 +182,18 @@ export default {
.icon {
font-size: 20px;
}
// 响应式u-icon支持
:deep(.u-icon) {
font-size: 20px !important;
}
// 响应式iconfont支持
.iconfont {
width: 20px;
height: 20px;
font-size: 20px;
}
}
.service-title {
+8 -44
View File
@@ -24,20 +24,6 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/workbench/index",
"style": {
"navigationBarTitleText": "办公中心",
"navigationStyle": "custom"
}
},
{
"path": "pages/profile/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/iconfont-demo/index",
"style": {
@@ -66,6 +52,14 @@
"navigationBarTitleText": "我发起的流程",
"navigationStyle": "custom"
}
},
{
"path": "pages/uview-icon-test/index",
"style": {
"navigationBarTitleText": "uview图标测试",
"navigationBarBackgroundColor": "#5d87ff",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
@@ -73,35 +67,5 @@
"navigationBarTitleText": "智慧校园",
"navigationBarBackgroundColor": "rgb(93, 135, 255)",
"backgroundColor": "#f8f9fa"
},
"tabBar": {
"color": "#999999",
"selectedColor": "rgb(93, 135, 255)",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"height": "60px",
"fontSize": "12px",
"iconWidth": "22px",
"iconHeight": "22px",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.svg",
"selectedIconPath": "static/tabbar/home-active.svg"
},
{
"pagePath": "pages/workbench/index",
"text": "办公中心",
"iconPath": "static/tabbar/workbench.svg",
"selectedIconPath": "static/tabbar/workbench-active.svg"
},
{
"pagePath": "pages/profile/index",
"text": "我的",
"iconPath": "static/tabbar/profile.svg",
"selectedIconPath": "static/tabbar/profile-active.svg"
}
]
}
}
@@ -38,9 +38,19 @@
<text class="section-title">操作图标</text>
<view class="icon-grid">
<view class="icon-item">
<text class="iconfont icon-login"></text>
<text class="icon-name">登录</text>
<text class="icon-code">icon-login</text>
<text class="iconfont icon-add"></text>
<text class="icon-name">添加</text>
<text class="icon-code">icon-add</text>
</view>
<view class="icon-item">
<text class="iconfont icon-edit"></text>
<text class="icon-name">编辑</text>
<text class="icon-code">icon-edit</text>
</view>
<view class="icon-item">
<text class="iconfont icon-delete"></text>
<text class="icon-name">删除</text>
<text class="icon-code">icon-delete</text>
</view>
<view class="icon-item">
<text class="iconfont icon-search"></text>
@@ -48,9 +58,9 @@
<text class="icon-code">icon-search</text>
</view>
<view class="icon-item">
<text class="iconfont icon-add"></text>
<text class="icon-name">添加</text>
<text class="icon-code">icon-add</text>
<text class="iconfont icon-refresh"></text>
<text class="icon-name">刷新</text>
<text class="icon-code">icon-refresh</text>
</view>
<view class="icon-item">
<text class="iconfont icon-close"></text>
@@ -60,6 +70,82 @@
</view>
</view>
<!-- 文件图标 -->
<view class="icon-section">
<text class="section-title">文件图标</text>
<view class="icon-grid">
<view class="icon-item">
<text class="iconfont icon-download"></text>
<text class="icon-name">下载</text>
<text class="icon-code">icon-download</text>
</view>
<view class="icon-item">
<text class="iconfont icon-upload"></text>
<text class="icon-name">上传</text>
<text class="icon-code">icon-upload</text>
</view>
</view>
</view>
<!-- 状态图标 -->
<view class="icon-section">
<text class="section-title">状态图标</text>
<view class="icon-grid">
<view class="icon-item">
<text class="iconfont icon-success" style="color: #52c41a"></text>
<text class="icon-name">成功</text>
<text class="icon-code">icon-success</text>
</view>
<view class="icon-item">
<text class="iconfont icon-warning" style="color: #faad14"></text>
<text class="icon-name">警告</text>
<text class="icon-code">icon-warning</text>
</view>
<view class="icon-item">
<text class="iconfont icon-info" style="color: #1890ff"></text>
<text class="icon-name">信息</text>
<text class="icon-code">icon-info</text>
</view>
<view class="icon-item">
<text class="iconfont icon-check" style="color: #52c41a"></text>
<text class="icon-name">确认</text>
<text class="icon-code">icon-check</text>
</view>
</view>
</view>
<!-- 社交图标 -->
<view class="icon-section">
<text class="section-title">社交图标</text>
<view class="icon-grid">
<view class="icon-item">
<text class="iconfont icon-star" style="color: #faad14"></text>
<text class="icon-name">星星</text>
<text class="icon-code">icon-star</text>
</view>
<view class="icon-item">
<text class="iconfont icon-heart" style="color: #ff4d4f"></text>
<text class="icon-name">喜欢</text>
<text class="icon-code">icon-heart</text>
</view>
<view class="icon-item">
<text class="iconfont icon-message"></text>
<text class="icon-name">消息</text>
<text class="icon-code">icon-message</text>
</view>
<view class="icon-item">
<text class="iconfont icon-phone"></text>
<text class="icon-name">电话</text>
<text class="icon-code">icon-phone</text>
</view>
<view class="icon-item">
<text class="iconfont icon-mail"></text>
<text class="icon-name">邮件</text>
<text class="icon-code">icon-mail</text>
</view>
</view>
</view>
<!-- 导航图标 -->
<view class="icon-section">
<text class="section-title">导航图标</text>
@@ -0,0 +1,209 @@
<template>
<view class="test-page">
<view class="test-header">
<text class="test-title">uview-plus 图标问题分析与解决</text>
</view>
<!-- 问题演示 -->
<view class="test-section">
<text class="section-title">❌ 问题演示 - uview-plus图标异常</text>
<view class="test-items">
<view class="test-item">
<u-icon name="logout" size="24" color="#ff4d4f"></u-icon>
<text class="test-desc">logout图标 (可能显示文字)</text>
</view>
<view class="test-item">
<u-icon name="power" size="24" color="#ff4d4f"></u-icon>
<text class="test-desc">power图标 (可能显示文字)</text>
</view>
<view class="test-item">
<u-icon name="exit" size="24" color="#ff4d4f"></u-icon>
<text class="test-desc">exit图标 (可能显示文字)</text>
</view>
</view>
</view>
<!-- 正常工作的uview图标 -->
<view class="test-section">
<text class="section-title">✅ 正常工作 - uview-plus图标</text>
<view class="test-items">
<view class="test-item">
<u-icon name="account" size="24" color="#52c41a"></u-icon>
<text class="test-desc">account图标 (正常)</text>
</view>
<view class="test-item">
<u-icon name="home" size="24" color="#52c41a"></u-icon>
<text class="test-desc">home图标 (正常)</text>
</view>
<view class="test-item">
<u-icon name="setting" size="24" color="#52c41a"></u-icon>
<text class="test-desc">setting图标 (正常)</text>
</view>
</view>
</view>
<!-- 解决方案 - 使用iconfont -->
<view class="test-section">
<text class="section-title">🎯 解决方案 - 使用iconfont自定义图标</text>
<view class="test-items">
<view class="test-item">
<text class="iconfont icon-close solution-icon"></text>
<text class="test-desc">关闭图标 (退出登录)</text>
</view>
<view class="test-item">
<text class="iconfont icon-user solution-icon"></text>
<text class="test-desc">用户图标</text>
</view>
<view class="test-item">
<text class="iconfont icon-setting solution-icon"></text>
<text class="test-desc">设置图标</text>
</view>
</view>
</view>
<!-- Unicode字符方案 -->
<view class="test-section">
<text class="section-title">🔤 备选方案 - Unicode字符</text>
<view class="test-items">
<view class="test-item">
<text class="unicode-icon">⏻</text>
<text class="test-desc">电源符号 (退出登录)</text>
</view>
<view class="test-item">
<text class="unicode-icon">❌</text>
<text class="test-desc">关闭符号</text>
</view>
<view class="test-item">
<text class="unicode-icon">⚙️</text>
<text class="test-desc">设置符号</text>
</view>
</view>
</view>
<!-- 解决方案说明 -->
<view class="solution-section">
<text class="section-title">💡 解决方案说明</text>
<view class="solution-content">
<text class="solution-text">
1. **根本原因**: uview-plus某些图标名称在当前版本字体文件中不存在
</text>
<text class="solution-text">
2. **推荐方案**: 使用项目中的iconfont自定义图标系统
</text>
<text class="solution-text">
3. **备选方案**: 使用Unicode字符(⏻ ❌ ⚙️),兼容性最好
</text>
<text class="solution-text">
4. **长期方案**: 更新uview-plus字体文件或使用其他图标库
</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'UviewIconTest',
data() {
return {};
},
};
</script>
<style lang="scss" scoped>
.test-page {
min-height: 100vh;
background: #f8f9fa;
padding: 20px;
}
.test-header {
text-align: center;
margin-bottom: 32px;
}
.test-title {
display: block;
font-size: 20px;
font-weight: bold;
color: #333;
}
.test-section {
background: #fff;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section-title {
display: block;
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
}
.test-items {
display: flex;
flex-direction: column;
gap: 12px;
}
.test-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: #fafbfc;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.test-desc {
font-size: 14px;
color: #666;
}
.solution-icon {
font-size: 24px;
color: #1890ff;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.unicode-icon {
font-size: 24px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.solution-section {
background: #fff7e6;
border: 1px solid #ffd591;
border-radius: 12px;
padding: 20px;
}
.solution-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.solution-text {
display: block;
font-size: 14px;
color: #333;
line-height: 1.6;
}
</style>
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -19,7 +19,7 @@
v-model="username"
placeholder="账号"
type="text"
:border="true"
border="true"
:custom-style="inputStyle"
placeholder-style="color: #999; font-size: 16px"
>
@@ -34,7 +34,7 @@
v-model="password"
placeholder="密码"
type="password"
:border="true"
border="true"
:custom-style="inputStyle"
placeholder-style="color: #999; font-size: 16px"
@confirm="onPasswordConfirm"
@@ -187,7 +187,7 @@ export default {
// 调用登录API
const loginResult = await api.login(loginData);
console.log('登录结果:', loginResult);
if (loginResult.code === 200) {
// 登录成功,先保存Token
console.log('✅ 登录成功,先保存Token');
-674
View File
@@ -1,674 +0,0 @@
<template>
<view class="page-container">
<!-- 自定义导航栏 -->
<NavBar title="我的" :show-settings="true" @settings="handleSettings" />
<!-- 主要内容区域 -->
<scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
<!-- 用户信息卡片 -->
<view class="profile-card">
<view class="profile-info">
<view class="profile-avatar">
<text class="avatar-icon">👤</text>
</view>
<view class="profile-details">
<text class="user-name">{{ userInfo.name }}</text>
<text class="user-major">{{ userInfo.major }} · {{ userInfo.grade }}</text>
<text class="user-id">学号:{{ userInfo.studentId }}</text>
</view>
</view>
<view class="profile-stats">
<view class="stat-item">
<text class="stat-number">{{ userInfo.gpa }}</text>
<text class="stat-label">GPA</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ userInfo.credits }}</text>
<text class="stat-label">学分</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ userInfo.grade }}</text>
<text class="stat-label">年级</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ userInfo.ranking }}</text>
<text class="stat-label">排名</text>
</view>
</view>
</view>
<!-- 学习状态 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">📊</text>
<text class="card-title">学习状态</text>
</view>
</template>
<GridLayout :columns="2" gap="12px">
<StatCard
v-for="stat in learningStats"
:key="stat.label"
:number="stat.number"
:label="stat.label"
@click="handleStatClick"
/>
</GridLayout>
</Card>
<!-- 我的服务 -->
<view class="menu-section">
<view
v-for="menu in myServiceMenus"
:key="menu.title"
class="menu-item touchable"
@click="handleMenuClick(menu)"
>
<view class="menu-icon">
<text class="icon">{{ menu.icon }}</text>
</view>
<view class="menu-content">
<text class="menu-title">{{ menu.title }}</text>
<text class="menu-desc">{{ menu.description }}</text>
</view>
<text class="menu-arrow">›</text>
</view>
</view>
<view class="section-divider"></view>
<!-- 校园生活 -->
<view class="menu-section">
<view
v-for="menu in campusLifeMenus"
:key="menu.title"
class="menu-item touchable"
@click="handleMenuClick(menu)"
>
<view class="menu-icon">
<text class="icon">{{ menu.icon }}</text>
</view>
<view class="menu-content">
<text class="menu-title">{{ menu.title }}</text>
<text class="menu-desc">{{ menu.description }}</text>
</view>
<text class="menu-arrow">›</text>
</view>
</view>
<view class="section-divider"></view>
<!-- 应用设置 -->
<view class="menu-section">
<view
v-for="menu in settingsMenus"
:key="menu.title"
class="menu-item touchable"
@click="handleMenuClick(menu)"
>
<view class="menu-icon">
<text class="icon">{{ menu.icon }}</text>
</view>
<view class="menu-content">
<text class="menu-title">{{ menu.title }}</text>
<text class="menu-desc">{{ menu.description }}</text>
</view>
<text class="menu-arrow">›</text>
</view>
</view>
<view class="section-divider"></view>
<!-- 帮助与反馈 -->
<view class="menu-section">
<view
v-for="menu in helpMenus"
:key="menu.title"
class="menu-item touchable"
@click="handleMenuClick(menu)"
>
<view class="menu-icon">
<text class="icon">{{ menu.icon }}</text>
</view>
<view class="menu-content">
<text class="menu-title">{{ menu.title }}</text>
<text class="menu-desc">{{ menu.description }}</text>
</view>
<text class="menu-arrow">›</text>
</view>
</view>
<!-- 版权信息 -->
<view class="footer-section">
<text class="app-version">智慧校园 v2.1.0</text>
<text class="copyright">© 2024 University. All rights reserved.</text>
</view>
<!-- 底部间距 -->
<view class="bottom-spacer"></view>
</scroll-view>
</view>
</template>
<script>
import NavBar from '@/components/NavBar.vue';
import Card from '@/components/Card.vue';
import StatCard from '@/components/StatCard.vue';
import GridLayout from '@/components/GridLayout.vue';
import { userStorage } from '@/utils/request';
import { userManager, userState } from '@/utils/user';
import { checkPageAccess } from '@/utils/route-guard';
export default {
name: 'ProfilePage',
components: {
NavBar,
Card,
StatCard,
GridLayout,
},
data() {
return {
userInfo: {
name: '用户',
major: '未设置',
grade: '未设置',
studentId: '未设置',
gpa: 0,
credits: 0,
ranking: 0,
},
learningStats: [
{ number: 5, label: '本周课程' },
{ number: 3, label: '待提交作业' },
{ number: 2, label: '即将考试' },
{ number: 8, label: '借阅图书' },
],
myServiceMenus: [
{
title: '我的课表',
description: '查看本学期课程安排',
icon: '🎓',
action: 'schedule',
},
{
title: '成绩单',
description: '查看历史成绩记录',
icon: '📊',
action: 'grades',
},
{
title: '我的待办',
description: '管理学习任务和事项',
icon: '✅',
action: 'todos',
},
{
title: '校园卡',
description: '余额:¥128.50',
icon: '💳',
action: 'card',
},
],
campusLifeMenus: [
{
title: '图书借阅',
description: '当前借阅8本,2本即将到期',
icon: '📚',
action: 'library',
},
{
title: '宿舍信息',
description: '1号楼 308室',
icon: '🛏️',
action: 'dormitory',
},
{
title: '社团活动',
description: '计算机协会、摄影社',
icon: '👥',
action: 'clubs',
},
{
title: '活动报名',
description: '查看可参与的活动',
icon: '📅',
action: 'events',
},
],
settingsMenus: [
{
title: '个人信息',
description: '编辑个人资料',
icon: '👤',
action: 'profile',
},
{
title: '消息通知',
description: '管理通知设置',
icon: '🔔',
action: 'notifications',
},
{
title: '账号安全',
description: '密码、绑定手机等',
icon: '🛡️',
action: 'security',
},
{
title: '主题设置',
description: '切换应用主题',
icon: '🎨',
action: 'theme',
},
],
helpMenus: [
{
title: '帮助中心',
description: '常见问题解答',
icon: '❓',
action: 'help',
},
{
title: '意见反馈',
description: '提交建议和问题',
icon: '💬',
action: 'feedback',
},
{
title: '关于应用',
description: '版本 v2.1.0',
icon: 'ℹ️',
action: 'about',
},
{
title: '退出登录',
description: '安全退出当前账号',
icon: '🚪',
action: 'logout',
},
],
};
},
methods: {
// 检查登录状态
checkLoginStatus() {
return userState.autoLoginCheck();
},
// 加载用户信息
loadUserInfo() {
const displayUser = userManager.formatUserForDisplay();
if (displayUser) {
this.userInfo.name = displayUser.name;
this.userInfo.avatar = displayUser.avatar;
this.userInfo.department = displayUser.department;
this.userInfo.phone = displayUser.phone;
this.userInfo.email = displayUser.email;
this.userInfo.studentId = userManager.getCurrentUser()?.userId || '未设置';
}
},
handleSettings() {
uni.showToast({ title: '设置', icon: 'none' });
},
handleStatClick(stat) {
uni.showToast({ title: `查看${stat.label}详情`, icon: 'none' });
},
handleMenuClick(menu) {
uni.vibrateShort();
switch (menu.action) {
case 'schedule':
this.openSchedule();
break;
case 'grades':
this.openGrades();
break;
case 'todos':
this.openTodos();
break;
case 'card':
this.openCampusCard();
break;
case 'library':
this.openLibrary();
break;
case 'logout':
this.handleLogout();
break;
default:
uni.showToast({
title: `${menu.title}功能开发中`,
icon: 'none',
});
}
},
openSchedule() {
uni.showToast({ title: '打开课程表', icon: 'success' });
},
openGrades() {
uni.showToast({ title: '打开成绩单', icon: 'success' });
},
openTodos() {
uni.showToast({ title: '打开待办事项', icon: 'success' });
},
openCampusCard() {
uni.showToast({ title: '打开校园卡', icon: 'success' });
},
openLibrary() {
uni.showToast({ title: '打开图书借阅', icon: 'success' });
},
handleLogout() {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.showLoading({ title: '正在退出...' });
// 清除登录信息
userStorage.clearLoginInfo();
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '已退出登录',
icon: 'success'
});
// 跳转到登录页面
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/index'
});
}, 500);
}, 1000);
}
},
});
},
},
onLoad() {
// 检查页面访问权限
if (!checkPageAccess()) {
return;
}
if (this.checkLoginStatus()) {
this.loadUserInfo();
}
},
onShow() {
// 页面显示时再次检查权限
if (!checkPageAccess()) {
return;
}
// 刷新用户信息
this.loadUserInfo();
},
};
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: $uni-bg-color-grey;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding-bottom: calc(50px + env(safe-area-inset-bottom));
}
/* 用户信息卡片 */
.profile-card {
background: linear-gradient(135deg, $uni-color-primary, $uni-color-primary-dark);
color: $uni-text-color-inverse;
margin: $uni-spacing-col-lg;
border-radius: $uni-border-radius-lg;
padding: 24px 20px;
box-shadow: $uni-shadow;
}
.profile-info {
display: flex;
align-items: center;
margin-bottom: $uni-spacing-col-lg;
}
.profile-avatar {
width: 60px;
height: 60px;
border-radius: $uni-border-radius-circle;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
margin-right: $uni-spacing-row-lg;
}
.avatar-icon {
font-size: 24px;
color: $uni-text-color-inverse;
}
.profile-details {
flex: 1;
display: flex;
flex-direction: column;
}
.user-name {
font-size: $uni-font-size-xl;
font-weight: 600;
margin-bottom: 4px;
}
.user-major {
font-size: $uni-font-size-base;
opacity: 0.9;
margin-bottom: 4px;
}
.user-id {
font-size: $uni-font-size-sm;
opacity: 0.8;
}
.profile-stats {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: $uni-spacing-col-lg;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-number {
font-size: $uni-font-size-xxl;
font-weight: 600;
display: block;
margin-bottom: 4px;
}
.stat-label {
font-size: $uni-font-size-sm;
opacity: 0.8;
}
/* 分组标题 */
.section-header {
display: flex;
align-items: center;
gap: 8px;
}
.section-icon {
font-size: $uni-font-size-lg;
color: $uni-color-primary;
}
/* 菜单列表 */
.menu-section {
background: $uni-bg-color;
border-radius: $uni-border-radius-lg;
margin: $uni-spacing-col-lg;
box-shadow: $uni-shadow;
overflow: hidden;
}
.menu-item {
display: flex;
align-items: center;
padding: $uni-spacing-col-lg $uni-spacing-row-xl;
transition: $uni-transition;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&:hover {
background: $uni-color-primary-light;
}
}
.menu-icon {
width: 40px;
height: 40px;
background: $uni-color-primary-light;
border-radius: $uni-border-radius-circle;
display: flex;
align-items: center;
justify-content: center;
margin-right: $uni-spacing-row-lg;
.icon {
font-size: 18px;
color: $uni-color-primary;
}
}
.menu-content {
flex: 1;
display: flex;
flex-direction: column;
}
.menu-title {
font-size: $uni-font-size-lg;
font-weight: 500;
color: $uni-text-color;
margin-bottom: 2px;
}
.menu-desc {
font-size: $uni-font-size-sm;
color: $uni-text-color-secondary;
}
.menu-arrow {
color: $uni-text-color-grey;
font-size: $uni-font-size-xl;
font-weight: 300;
}
/* 分隔器 */
.section-divider {
height: 8px;
background: $uni-bg-color-grey;
margin: 0 $uni-spacing-row-lg;
}
/* 版权信息 */
.footer-section {
padding: 24px;
text-align: center;
color: $uni-text-color-grey;
}
.app-version {
font-size: $uni-font-size-sm;
display: block;
margin-bottom: 8px;
}
.copyright {
font-size: 11px;
}
/* 底部间距 */
.bottom-spacer {
height: 20px;
}
/* 菜单项进入动画 */
.menu-item {
animation: slideInRight 0.6s ease-out backwards;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* 页面进入动画 */
.page-container {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.profile-card,
.menu-section {
margin: $uni-spacing-col-base;
}
.menu-item {
padding: $uni-spacing-col-base $uni-spacing-row-lg;
}
.profile-avatar {
width: 50px;
height: 50px;
.avatar-icon {
font-size: 20px;
}
}
.user-name {
font-size: $uni-font-size-lg;
}
.stat-number {
font-size: $uni-font-size-xl;
}
}
</style>
-499
View File
@@ -1,499 +0,0 @@
<template>
<view class="page-container">
<!-- 自定义导航栏 -->
<NavBar title="办公中心" :show-search="true" @search="handleSearch" />
<!-- 主要内容区域 -->
<scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-bar" @click="focusSearch">
<text class="search-icon">🔍</text>
<input
class="search-input"
placeholder="搜索服务..."
v-model="searchQuery"
@input="handleSearchInput"
@confirm="handleSearchConfirm"
:focus="searchFocused"
/>
</view>
</view>
<!-- 热门服务 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">🔥</text>
<text class="card-title">热门服务</text>
</view>
</template>
<GridLayout :columns="4" gap="12px">
<ServiceItem
v-for="service in hotServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
:badge="service.badge"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 学习教务 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">🎓</text>
<text class="card-title">学习教务</text>
</view>
</template>
<GridLayout :columns="3" gap="12px">
<ServiceItem
v-for="service in academicServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 生活服务 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">🏠</text>
<text class="card-title">生活服务</text>
</view>
</template>
<GridLayout :columns="3" gap="12px">
<ServiceItem
v-for="service in lifeServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 校园资源 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">🏢</text>
<text class="card-title">校园资源</text>
</view>
</template>
<GridLayout :columns="3" gap="12px">
<ServiceItem
v-for="service in resourceServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 办事服务 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">📋</text>
<text class="card-title">办事服务</text>
</view>
</template>
<GridLayout :columns="3" gap="12px">
<ServiceItem
v-for="service in adminServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 信息查询 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">🔍</text>
<text class="card-title">信息查询</text>
</view>
</template>
<GridLayout :columns="3" gap="12px">
<ServiceItem
v-for="service in queryServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 快捷工具 -->
<Card>
<template #header>
<view class="section-header">
<text class="section-icon">⚡</text>
<text class="card-title">快捷工具</text>
</view>
</template>
<GridLayout :columns="3" gap="12px">
<ServiceItem
v-for="service in toolServices"
:key="service.title"
:title="service.title"
:icon="service.icon"
@click="handleServiceClick"
/>
</GridLayout>
</Card>
<!-- 底部间距 -->
<view class="bottom-spacer"></view>
</scroll-view>
</view>
</template>
<script>
import NavBar from '@/components/NavBar.vue';
import Card from '@/components/Card.vue';
import ServiceItem from '@/components/ServiceItem.vue';
import GridLayout from '@/components/GridLayout.vue';
import { userStorage } from '@/utils/request';
import { checkPageAccess } from '@/utils/route-guard';
export default {
name: 'WorkbenchPage',
components: {
NavBar,
Card,
ServiceItem,
GridLayout,
},
data() {
return {
searchQuery: '',
searchFocused: false,
hotServices: [
{ title: '课程表', icon: '🎓' },
{ title: '成绩查询', icon: '📊' },
{ title: '校园卡', icon: '💳' },
{ title: '图书馆', icon: '📚' },
],
academicServices: [
{ title: '我的课表', icon: '🎓' },
{ title: '成绩查询', icon: '📊' },
{ title: '选课系统', icon: '👨‍🎓' },
{ title: '考试安排', icon: '📝' },
{ title: '课程评教', icon: '📋' },
{ title: '学分查询', icon: '🏆' },
{ title: '作业提交', icon: '📄' },
{ title: '竞赛报名', icon: '🏅' },
{ title: '教学评价', icon: '👨‍🏫' },
],
lifeServices: [
{ title: '食堂菜单', icon: '🍽️' },
{ title: '校园卡', icon: '💳' },
{ title: '宿舍管理', icon: '🛏️' },
{ title: '停车服务', icon: '🚗' },
{ title: '校车时刻', icon: '🚌' },
{ title: '校园购物', icon: '🛒' },
{ title: '医疗服务', icon: '🏥' },
{ title: '报修服务', icon: '🔧' },
{ title: '空调控制', icon: '🌡️' },
],
resourceServices: [
{ title: '图书馆', icon: '📚' },
{ title: '机房预约', icon: '💻' },
{ title: '会议室', icon: '🎤' },
{ title: '体育馆', icon: '🏃‍♂️' },
{ title: '艺术中心', icon: '🎭' },
{ title: '实验室', icon: '🧪' },
{ title: '打印服务', icon: '🖨️' },
{ title: '网络服务', icon: '📶' },
{ title: '设备租借', icon: '🔧' },
],
adminServices: [
{ title: '学费缴费', icon: '💰' },
{ title: '证明开具', icon: '📄' },
{ title: '学生证', icon: '🆔' },
{ title: '学籍管理', icon: '👤' },
{ title: '转专业', icon: '🔄' },
{ title: '请假申请', icon: '✈️' },
{ title: '毕业申请', icon: '🎓' },
{ title: '就业指导', icon: '💼' },
{ title: '国际交流', icon: '🌍' },
],
queryServices: [
{ title: '校历查询', icon: '📅' },
{ title: '校园通讯录', icon: '📞' },
{ title: '校园地图', icon: '🗺️' },
{ title: '通知公告', icon: '📢' },
{ title: '常见问题', icon: '❓' },
{ title: '意见反馈', icon: '💬' },
{ title: '在线客服', icon: '🎧' },
{ title: '数据统计', icon: '📈' },
{ title: '校园新闻', icon: '📰' },
],
toolServices: [
{ title: '扫一扫', icon: '📱' },
{ title: '计算器', icon: '🧮' },
{ title: '作息时间', icon: '⏰' },
{ title: '天气查询', icon: '☁️' },
{ title: '翻译工具', icon: '🔤' },
{ title: '分享', icon: '📤' },
],
};
},
methods: {
handleSearch() {
this.focusSearch();
},
focusSearch() {
this.searchFocused = true;
},
handleSearchInput(e) {
this.searchQuery = e.detail.value;
// 实现搜索建议逻辑
this.showSearchSuggestions();
},
handleSearchConfirm() {
if (this.searchQuery.trim()) {
this.performSearch(this.searchQuery);
}
},
showSearchSuggestions() {
// 根据输入显示搜索建议
if (this.searchQuery.length > 0) {
// 这里可以实现搜索建议逻辑
}
},
performSearch(query) {
uni.showToast({
title: `搜索: ${query}`,
icon: 'none',
duration: 2000,
});
this.searchFocused = false;
},
handleServiceClick(service) {
uni.vibrateShort();
// 根据不同服务类型进行路由跳转或功能调用
switch (service.title) {
case '课程表':
case '我的课表':
this.openCourseSchedule();
break;
case '成绩查询':
this.openGradeQuery();
break;
case '校园卡':
this.openCampusCard();
break;
case '图书馆':
this.openLibrary();
break;
case '扫一扫':
this.openScanner();
break;
default:
uni.showToast({
title: `${service.title}功能开发中`,
icon: 'none',
});
}
},
openCourseSchedule() {
uni.showToast({ title: '打开课程表', icon: 'success' });
// 这里可以跳转到课程表页面
},
openGradeQuery() {
uni.showToast({ title: '打开成绩查询', icon: 'success' });
// 这里可以跳转到成绩查询页面
},
openCampusCard() {
uni.showToast({ title: '打开校园卡', icon: 'success' });
// 这里可以跳转到校园卡页面
},
openLibrary() {
uni.showToast({ title: '打开图书馆', icon: 'success' });
// 这里可以跳转到图书馆页面
},
openScanner() {
uni.scanCode({
success: (res) => {
uni.showToast({
title: `扫描结果: ${res.result}`,
icon: 'none',
});
},
fail: () => {
uni.showToast({
title: '扫描失败',
icon: 'error',
});
},
});
},
// 检查登录状态
checkLoginStatus() {
if (!userStorage.isLoggedIn()) {
uni.reLaunch({
url: '/pages/login/index',
});
return;
}
},
},
onLoad() {
// 检查页面访问权限
if (!checkPageAccess()) {
return;
}
this.checkLoginStatus();
},
onShow() {
// 页面显示时再次检查权限
if (!checkPageAccess()) {
return;
}
},
};
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: $uni-bg-color-grey;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
padding-bottom: calc(50px + env(safe-area-inset-bottom));
}
/* 搜索栏 */
.search-section {
padding: $uni-spacing-col-lg;
}
.search-bar {
position: relative;
background: $uni-bg-color;
border-radius: $uni-border-radius-lg;
box-shadow: $uni-shadow;
display: flex;
align-items: center;
padding: 0 $uni-spacing-row-lg;
transition: $uni-transition;
&:hover {
box-shadow: $uni-shadow-hover;
}
}
.search-icon {
font-size: 16px;
color: $uni-text-color-grey;
margin-right: $uni-spacing-row-base;
}
.search-input {
flex: 1;
padding: $uni-spacing-col-base 0;
font-size: $uni-font-size-lg;
border: none;
outline: none;
background: transparent;
color: $uni-text-color;
&::placeholder {
color: $uni-text-color-placeholder;
}
}
/* 分组标题 */
.section-header {
display: flex;
align-items: center;
gap: 8px;
}
.section-icon {
font-size: $uni-font-size-lg;
}
/* 服务网格动画 */
.service-item {
animation-delay: calc(var(--item-index, 0) * 0.1s);
animation: slideInUp 0.6s ease-out backwards;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 搜索焦点效果 */
.search-bar:focus-within {
transform: translateY(-2px);
box-shadow: $uni-shadow-hover;
.search-icon {
color: $uni-color-primary;
}
}
/* 底部间距 */
.bottom-spacer {
height: 20px;
}
/* 页面进入动画 */
.page-container {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.search-section {
padding: $uni-spacing-col-base;
}
.search-bar {
padding: 0 $uni-spacing-row-base;
}
.search-input {
font-size: $uni-font-size-base;
}
}
</style>
+177
View File
@@ -73,3 +73,180 @@
.icon-mail {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z'/%3E%3C/svg%3E");
}
/* 新增常用图标 - 2025-01-07 */
/* 星星图标 */
.icon-star {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3C/svg%3E");
}
/* 心形图标 */
.icon-heart {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E");
}
/* 日历图标 */
.icon-calendar {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z'/%3E%3C/svg%3E");
}
/* 聊天图标 */
.icon-chat {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z'/%3E%3C/svg%3E");
}
/* 工具图标 */
.icon-tools {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z'/%3E%3C/svg%3E");
}
/* 地图图标 */
.icon-map {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20.5 3l-.16.03L15 5.1 9 3 3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1 5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM15 19l-6-2.11V5l6 2.11V19z'/%3E%3C/svg%3E");
}
/* 播放圆圈图标 */
.icon-play-circle {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E");
}
/* 列表图标 */
.icon-list {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z'/%3E%3C/svg%3E");
}
/* 人民币圆圈图标 */
.icon-rmb-circle {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.5 6L12 10.5 10.5 8H9.5l1.75 2.5H9v1h3v1H9v1h3V15h1v-1.5h3v-1h-3v-1h3v-1h-2.25L14.5 8h-1z'/%3E%3C/svg%3E");
}
/* 商店图标 */
.icon-shop {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27 16.18 14l1.64 7L12 17.77 6.18 21l1.64-7L2 9.27l6.91-1.01L12 2zm0 0L4 8v12h16V8l-8-6z'/%3E%3C/svg%3E");
}
/* 奖杯图标 */
.icon-trophy {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M7 4V2c0-1.1.9-2 2-2h6c1.1 0 2 .9 2 2v2h4c1.1 0 2 .9 2 2v4c0 2.21-1.79 4-4 4h-1.28c-.44 2.83-2.82 5-5.72 5s-5.28-2.17-5.72-5H5c-2.21 0-4-1.79-4-4V6c0-1.1.9-2 2-2h4zM9 4h6V2H9v2zM5 6v4c0 1.1.9 2 2 2h1.28c.42-1.82 1.89-3.26 3.72-3.26s3.3 1.44 3.72 3.26H17c1.1 0 2-.9 2-2V6H5zm7 14c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3z'/%3E%3C/svg%3E");
}
/* 朋友图标 */
.icon-friends {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M16 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm4 18v-6h2.5l-2.54-7.63A1.495 1.495 0 0 0 18.5 7.5h-3c-.57 0-1.08.32-1.34.82L11.5 15h2.5v7h6zM12.5 11.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5S11 9.17 11 10s.67 1.5 1.5 1.5zM5.5 6c1.11 0 2-.89 2-2s-.89-2-2-2-2 .89-2 2 .89 2 2 2zm2 16v-7H9l-1.46-3.12A1.495 1.495 0 0 0 6.12 11h-1.5c-.57 0-1.08.32-1.34.82L1 17h2.5v7h4z'/%3E%3C/svg%3E");
}
/* 飞机图标 */
.icon-plane {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z'/%3E%3C/svg%3E");
}
/* 书签图标 */
.icon-bookmark {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z'/%3E%3C/svg%3E");
}
/* 人民币圆圈填充图标 */
.icon-rmb-circle-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.5 6L12 10.5 10.5 8H9.5l1.75 2.5H9v1h3v1H9v1h3V15h1v-1.5h3v-1h-3v-1h3v-1h-2.25L14.5 8h-1z'/%3E%3C/svg%3E");
}
/* 金钱圆圈图标 */
.icon-money-circle {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z'/%3E%3C/svg%3E");
}
/* 文件文本图标 */
.icon-file-text {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z'/%3E%3C/svg%3E");
}
/* 订单图标 */
.icon-order {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E");
}
/* 设置填充图标 */
.icon-setting-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3A8.994 8.994 0 0 0 13 3.06V1h-2v2.06A8.994 8.994 0 0 0 3.06 11H1v2h2.06A8.994 8.994 0 0 0 11 20.94V23h2v-2.06A8.994 8.994 0 0 0 20.94 13H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z'/%3E%3C/svg%3E");
}
/* 用户圆圈填充图标 */
.icon-account-circle-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z'/%3E%3C/svg%3E");
}
/* 列表点图标 */
.icon-list-dot {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z'/%3E%3C/svg%3E");
}
/* 首页填充图标 */
.icon-home-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'/%3E%3C/svg%3E");
}
/* WiFi图标 */
.icon-wifi {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M1 9l2 2c4.97-4.97 13.03-4.97 18 0l2-2C16.93 2.93 7.07 2.93 1 9zm8 8l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zm-4-4l2 2c2.76-2.76 7.24-2.76 10 0l2-2C15.14 9.14 8.87 9.14 5 13z'/%3E%3C/svg%3E");
}
/* 分享图标 */
.icon-share {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z'/%3E%3C/svg%3E");
}
/* 分享方形图标 */
.icon-share-square {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M18 2h-3c-1.1 0-2 .9-2 2v3H7c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-6h3c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 15H7v-3h3v3zm0-5H7v-2h3v2zm0-4H7V6h3v2zm5 9h-3v-3h3v3zm0-5h-3v-2h3v2zm0-4h-3V6h3v2zm5-3h-3V4h3v1z'/%3E%3C/svg%3E");
}
/* 编辑笔图标 */
.icon-edit-pen {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E");
}
/* 时钟图标 */
.icon-clock {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z'/%3E%3C/path%3E%3Cpath d='m12.5 7-1 1v4.25l3.5 2.08.75-1.23-2.75-1.63V8z'/%3E%3C/svg%3E");
}
/* 确认圆圈图标 */
.icon-checkmark-circle {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'/%3E%3C/svg%3E");
}
/* 复制图标 */
.icon-copy {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z'/%3E%3C/svg%3E");
}
/* 用户圆圈图标 */
.icon-account-circle {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z'/%3E%3C/svg%3E");
}
/* 用户填充图标 */
.icon-account-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E");
}
/* 星星填充图标 */
.icon-star-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");
}
/* 包图标 */
.icon-bag {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 6h-2V3c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v3H7c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zM9.5 3h5v3h-5V3zM9 12v-2h2v2h2v-2h2v2c0 1.1-.9 2-2 2h-2c-1.1 0-2-.9-2-2z'/%3E%3C/svg%3E");
}
/* 学工管理填充图标 */
.icon-account-fill {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E");
}
/* 登出图标 */
.icon-logout {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.59L17 17l5-5-5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z'/%3E%3C/svg%3E");
}
@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="7" r="4" fill="rgb(93, 135, 255)"/>
<path d="M12 14c-4.42 0-8 3.58-8 8h16c0-4.42-3.58-8-8-8z" fill="rgb(93, 135, 255)"/>
</svg>

Before

Width:  |  Height:  |  Size: 256 B

-5
View File
@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="7" r="4" fill="#999999"/>
<path d="M12 14c-4.42 0-8 3.58-8 8h16c0-4.42-3.58-8-8-8z" fill="#999999"/>
</svg>

Before

Width:  |  Height:  |  Size: 236 B

@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M4 4h16v2H4V4zm0 4h16v2H4V8zm0 4h16v2H4v-2zm0 4h10v2H4v-2z" fill="rgb(93, 135, 255)"/>
<rect x="3" y="3" width="18" height="18" rx="2" stroke="rgb(93, 135, 255)" stroke-width="1.5" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 319 B

-5
View File
@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M4 4h16v2H4V4zm0 4h16v2H4V8zm0 4h16v2H4v-2zm0 4h10v2H4v-2z" fill="#999999"/>
<rect x="3" y="3" width="18" height="18" rx="2" stroke="#999999" stroke-width="1.5" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 299 B

+378
View File
@@ -0,0 +1,378 @@
# 移动端工具库使用说明
本文档说明移动端 `utils` 目录下各个工具的使用方法,特别是升级后的请求工具。
## 📁 文件结构
```
mobile/src/utils/
├── request.js # 统一请求工具(已升级)
├── crypto.js # 加密解密工具(新增)
├── config.js # 配置管理工具(新增)
├── encrypt-pure.js # 登录专用加密工具(保留)
├── encrypt-*.js # 其他加密实现(保留)
└── README.md # 本文档
```
## 🚀 核心升级功能
### 1. 统一的加密解密支持
移动端现在与 Web 端保持一致,支持:
- ✅ **入参加密**:通过 `isEncrypt: 'true'` 头部控制
- ✅ **出参解密**:自动处理后端返回的加密数据
- ✅ **AES + RSA 双重加密**:与后端完全兼容
- ✅ **防重复提交**:避免用户误操作
- ✅ **错误处理优化**:404静默处理、401自动跳转
### 2. 可配置的参数管理
所有关键参数都可以通过配置文件统一管理,方便调试和部署。
## 📖 使用方法
### 基础请求
```javascript
import { request } from '@/utils/request.js';
// 普通GET请求
const response = await request({
url: '/api/user/list',
method: 'GET'
});
// 普通POST请求
const response = await request({
url: '/api/user/create',
method: 'POST',
data: {
name: '张三',
age: 25
}
});
```
### 加密请求
```javascript
import { request } from '@/utils/request.js';
// 启用加密的POST请求
const response = await request({
url: '/api/sensitive/data',
method: 'POST',
data: {
sensitiveInfo: '敏感数据'
},
header: {
isEncrypt: 'true' // 关键:启用加密
}
});
```
### 免Token请求
```javascript
import { request } from '@/utils/request.js';
// 不需要Token的请求(如获取租户列表)
const response = await request({
url: '/auth/tenant/list',
method: 'GET',
header: {
isToken: false // 关键:不携带Token
}
});
```
### 允许重复提交
```javascript
import { request } from '@/utils/request.js';
// 允许重复提交的请求
const response = await request({
url: '/api/data/sync',
method: 'POST',
data: { syncData: 'xxx' },
header: {
repeatSubmit: false // 关键:允许重复提交
}
});
```
## ⚙️ 配置管理
### 基础配置更新
```javascript
import { updateConfig } from '@/utils/config.js';
// 更新API基础URL
updateConfig({
request: {
baseURL: {
production: 'https://new-api-domain.com'
}
}
});
// 更新客户端ID
updateConfig({
app: {
clientId: 'new-client-id-here'
}
});
```
### 加密配置更新
```javascript
import { updateConfig } from '@/utils/config.js';
// 更新RSA公钥
updateConfig({
encryption: {
rsaPublicKey: 'new-rsa-public-key-here'
}
});
// 临时禁用加密(调试用)
updateConfig({
encryption: {
enabled: false
}
});
```
### 预设配置方案
```javascript
import { applyPreset } from '@/utils/config.js';
// 应用生产环境配置
applyPreset('production');
// 应用测试环境配置
applyPreset('test');
// 应用开发环境配置
applyPreset('development');
```
### 单个配置项更新
```javascript
import { setConfigItem, getConfigItem } from '@/utils/config.js';
// 设置超时时间
setConfigItem('request.timeout', 600000); // 10分钟
// 获取当前客户端ID
const clientId = getConfigItem('app.clientId');
// 检查是否启用加密
const isEncrypted = getConfigItem('encryption.enabled');
```
## 🔐 加密工具直接使用
```javascript
import {
encryptWithAES,
decryptWithAES,
generateRandomAESKey,
encrypt,
decrypt
} from '@/utils/crypto.js';
// AES加密解密
const aesKey = generateRandomAESKey();
const encryptedData = encryptWithAES('敏感数据', aesKey);
const decryptedData = decryptWithAES(encryptedData, aesKey);
// RSA加密解密
const encryptedKey = encrypt('AES密钥');
const decryptedKey = decrypt(encryptedKey);
```
## 📦 API方法封装
### 登录
```javascript
import { api } from '@/utils/request.js';
// 用户登录(自动加密)
const loginResult = await api.login({
username: 'admin',
password: 'password123',
tenantId: '000000'
});
```
### 获取用户信息
```javascript
import { api } from '@/utils/request.js';
// 获取当前用户信息
const userInfo = await api.getUserInfo();
```
### 注销登录
```javascript
import { api } from '@/utils/request.js';
// 用户注销
await api.logout();
```
## 💾 缓存工具
### 用户信息缓存
```javascript
import { userStorage } from '@/utils/request.js';
// 保存登录信息
userStorage.saveLoginInfo(loginResult, userInfo);
// 获取用户信息
const user = userStorage.getUserInfo();
// 检查登录状态
const isLoggedIn = userStorage.isLoggedIn();
// 清除登录信息
userStorage.clearLoginInfo();
```
### 通用缓存
```javascript
import { storage } from '@/utils/request.js';
// 设置缓存(30分钟过期)
storage.set('cacheKey', { data: 'value' }, 1800);
// 获取缓存
const data = storage.get('cacheKey');
// 删除缓存
storage.remove('cacheKey');
// 清空所有缓存
storage.clear();
```
## 📥 文件下载
```javascript
import { download } from '@/utils/request.js';
// 下载文件
await download('/api/export/users', {
filters: { status: 'active' }
}, 'users.xlsx');
```
## 🔧 调试和日志
### 开启详细日志
```javascript
import { updateConfig } from '@/utils/config.js';
// 开启所有调试日志
updateConfig({
debug: {
enableVerboseLog: true,
showRequestLog: true,
showResponseLog: true,
showCryptoLog: true
}
});
```
### 错误处理配置
```javascript
import { updateConfig } from '@/utils/config.js';
// 自定义错误处理
updateConfig({
error: {
silentCodes: [404, 403], // 静默处理的状态码
toastDuration: 3000, // 错误提示显示时间
loginRedirectDelay: 1000 // 跳转登录延迟
}
});
```
## 🚨 注意事项
### 1. 加密兼容性
- 移动端加密算法与 Web 端完全一致
- RSA 公钥必须与后端保持同步
- 加密只对 POST/PUT 请求生效
### 2. 配置优先级
- 运行时配置 > 环境预设 > 默认配置
- 配置更新会立即生效,无需重启应用
### 3. Token 管理
- Token 自动从本地存储获取
- 401 错误会自动清除 Token 并跳转登录
- 支持 Token 过期时间管理
### 4. 错误处理
- 404 错误默认静默处理
- 403 错误会显示提示但不跳转
- 网络错误会显示友好提示
## 📊 性能优化
### 1. 防重复提交
- 默认 500ms 间隔防重复提交
- 可通过配置调整间隔时间
- 支持单个请求禁用防重复
### 2. 请求超时
- 普通请求:5分钟超时
- 下载请求:20分钟超时
- 可通过配置调整超时时间
### 3. 缓存策略
- 会话级缓存用于防重复提交
- 持久化缓存支持过期时间
- 登录信息缓存支持时间戳
## 🔗 与 Web 端对比
| 功能 | Web 端 | 移动端 | 说明 |
|------|--------|--------|------|
| 入参加密 | ✅ | ✅ | 完全一致 |
| 出参解密 | ✅ | ✅ | 完全一致 |
| 防重复提交 | ✅ | ✅ | 完全一致 |
| 401处理 | ✅ | ✅ | 跳转方式略有不同 |
| 404静默 | ✅ | ✅ | 完全一致 |
| 下载支持 | ✅ | ✅ | 平台实现不同 |
| 配置管理 | 环境变量 | 配置文件 | 更灵活的配置方式 |
## 🎯 最佳实践
1. **生产环境**:关闭调试日志,启用加密
2. **开发环境**:开启详细日志,保持加密测试
3. **配置管理**:使用预设配置,避免硬编码
4. **错误处理**:合理设置静默错误码
5. **性能优化**:适当设置超时和缓存策略
---
**📝 更新日志**
- 2025-01-07:完成移动端请求工具升级,与 Web 端功能对齐
- 支持入参加密、出参解密、防重复提交等完整功能
- 新增配置管理系统,提升可维护性
-2
View File
@@ -373,8 +373,6 @@ class AccessibilityUtils {
uni.switchTab = (options) => {
const pageTitles = {
'/pages/index/index': '智慧校园 - 首页',
'/pages/workbench/index': '智慧校园 - 办公中心',
'/pages/profile/index': '智慧校园 - 我的',
};
const title = pageTitles[options.url];
+353
View File
@@ -0,0 +1,353 @@
// 移动端配置文件 - 集中管理所有可配置参数
// 使用方法:import { updateConfig } from '@/utils/config.js' 然后调用 updateConfig(newConfig)
// 默认配置
const DEFAULT_CONFIG = {
// =========================
// 加密相关配置
// =========================
encryption: {
// 是否启用加密
enabled: true,
// RSA公钥 - 与后端保持一致
rsaPublicKey:
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
// RSA私钥 - 前端不建议存放,仅用于测试环境
rsaPrivateKey: '',
// 加密请求头名称
encryptHeader: 'encrypt-key',
// AES密钥长度
aesKeyLength: 32,
},
// =========================
// 请求相关配置
// =========================
request: {
// API基础URL
baseURL: {
development: '/api',
production: 'https://your-api-domain.com',
},
// 请求超时时间(毫秒)
timeout: 300000, // 5分钟
// 下载超时时间(毫秒)
downloadTimeout: 1200000, // 20分钟
// 防重复提交间隔时间(毫秒)
repeatSubmitInterval: 500,
// 默认请求头
defaultHeaders: {
'Content-Type': 'application/json',
},
},
// =========================
// 应用相关配置
// =========================
app: {
// 客户端ID
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
// 租户ID
tenantId: '000000',
// 默认referer
referer: 'http://localhost/login?redirect=/index',
// 登录页面路径
loginPath: '/pages/login/index',
},
// =========================
// 缓存相关配置
// =========================
cache: {
// 默认缓存过期时间(秒)
defaultExpire: 3600, // 1小时
// Token存储键名
tokenKey: 'access_token',
// 用户信息存储键名
userInfoKey: 'userInfo',
// 登录状态存储键名
loginStatusKey: 'isLoggedIn',
// 会话缓存前缀
sessionPrefix: 'session_',
},
// =========================
// 错误处理配置
// =========================
error: {
// 静默处理的HTTP状态码
silentCodes: [404],
// 自动跳转登录的状态码
loginRedirectCodes: [401],
// 错误提示显示时间(毫秒)
toastDuration: 2000,
// 跳转登录延迟时间(毫秒)
loginRedirectDelay: 500,
},
// =========================
// 调试相关配置
// =========================
debug: {
// 是否启用详细日志
enableVerboseLog: process.env.NODE_ENV === 'development',
// 是否显示请求日志
showRequestLog: true,
// 是否显示响应日志
showResponseLog: true,
// 是否显示加密解密日志
showCryptoLog: true,
},
};
// 当前配置 - 可被动态修改
let currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
// =========================
// 配置管理方法
// =========================
/**
* 获取完整配置
* @returns {Object} 当前配置对象
*/
export const getConfig = () => {
return currentConfig;
};
/**
* 获取特定配置项
* @param {string} path 配置路径,支持嵌套 如: 'encryption.enabled' 或 'request.timeout'
* @returns {any} 配置值
*/
export const getConfigItem = (path) => {
const keys = path.split('.');
let value = currentConfig;
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key];
} else {
console.warn(`配置项 ${path} 不存在`);
return undefined;
}
}
return value;
};
/**
* 更新配置
* @param {Object} newConfig 新配置对象(部分更新)
* @param {boolean} deep 是否深度合并,默认true
*/
export const updateConfig = (newConfig, deep = true) => {
if (deep) {
currentConfig = deepMerge(currentConfig, newConfig);
} else {
currentConfig = { ...currentConfig, ...newConfig };
}
console.log('🔧 配置已更新:', currentConfig);
// 通知其他模块配置已更新
notifyConfigUpdate();
};
/**
* 设置特定配置项
* @param {string} path 配置路径
* @param {any} value 配置值
*/
export const setConfigItem = (path, value) => {
const keys = path.split('.');
let target = currentConfig;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}
target = target[key];
}
const lastKey = keys[keys.length - 1];
target[lastKey] = value;
console.log(`🔧 配置项 ${path} 已更新为:`, value);
// 通知其他模块配置已更新
notifyConfigUpdate();
};
/**
* 重置配置为默认值
*/
export const resetConfig = () => {
currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
console.log('🔄 配置已重置为默认值');
// 通知其他模块配置已更新
notifyConfigUpdate();
};
/**
* 获取当前环境的API基础URL
* @returns {string} API基础URL
*/
export const getBaseURL = () => {
const env = process.env.NODE_ENV || 'development';
return currentConfig.request.baseURL[env] || currentConfig.request.baseURL.development;
};
// =========================
// 配置预设方案
// =========================
/**
* 开发环境配置
*/
export const developmentConfig = {
encryption: {
enabled: true, // 开发环境也启用加密测试
},
debug: {
enableVerboseLog: true,
showRequestLog: true,
showResponseLog: true,
showCryptoLog: true,
},
error: {
toastDuration: 3000, // 开发环境错误提示显示久一点
},
};
/**
* 生产环境配置
*/
export const productionConfig = {
encryption: {
enabled: true,
},
debug: {
enableVerboseLog: false,
showRequestLog: false,
showResponseLog: false,
showCryptoLog: false,
},
request: {
timeout: 300000, // 生产环境保持较长超时
repeatSubmitInterval: 1000, // 生产环境防重复提交间隔更长
},
};
/**
* 测试环境配置
*/
export const testConfig = {
encryption: {
enabled: true,
},
debug: {
enableVerboseLog: true,
showRequestLog: true,
showResponseLog: false,
showCryptoLog: true,
},
};
/**
* 应用预设配置
* @param {string} preset 预设名称: 'development' | 'production' | 'test'
*/
export const applyPreset = (preset) => {
const presets = {
development: developmentConfig,
production: productionConfig,
test: testConfig,
};
if (presets[preset]) {
updateConfig(presets[preset]);
console.log(`✅ 已应用 ${preset} 环境配置预设`);
} else {
console.error(`❌ 未知的配置预设: ${preset}`);
}
};
// =========================
// 工具方法
// =========================
/**
* 深度合并对象
* @param {Object} target 目标对象
* @param {Object} source 源对象
* @returns {Object} 合并后的对象
*/
const deepMerge = (target, source) => {
const result = { ...target };
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
};
/**
* 通知其他模块配置已更新
*/
const notifyConfigUpdate = () => {
// 这里可以实现事件通知机制
// 例如使用 uni.$emit('configUpdated', currentConfig)
if (typeof uni !== 'undefined' && uni.$emit) {
uni.$emit('configUpdated', currentConfig);
}
};
// =========================
// 初始化配置
// =========================
// 根据环境自动应用配置预设
const env = process.env.NODE_ENV || 'development';
if (env !== 'development') {
applyPreset(env);
}
// 导出常用配置项的快捷访问方法
export const isEncryptionEnabled = () => getConfigItem('encryption.enabled');
export const getClientId = () => getConfigItem('app.clientId');
export const getTenantId = () => getConfigItem('app.tenantId');
export const getTimeout = () => getConfigItem('request.timeout');
export const isDebugMode = () => getConfigItem('debug.enableVerboseLog');
console.log('📋 移动端配置初始化完成,当前环境:', env);
+291
View File
@@ -0,0 +1,291 @@
// 移动端加密解密工具 - 与web端保持一致
import CryptoJS from 'crypto-js';
import JSEncrypt from 'jsencrypt';
// 加密配置 - 可配置
export const ENCRYPT_CONFIG = {
// RSA公钥 - 与后端保持一致
RSA_PUBLIC_KEY:
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
// RSA私钥 - 与web端配置文档保持一致
RSA_PRIVATE_KEY:
'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=',
// 客户端ID
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
// 租户ID
TENANT_ID: '000000',
// 加密请求头
ENCRYPT_HEADER: 'encrypt-key',
// 是否启用加密
ENCRYPT_ENABLED: true,
};
/**
* 随机生成32位的字符串
* @returns {string}
*/
const generateRandomString = () => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < 32; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
/**
* 随机生成AES密钥
* @returns {CryptoJS.lib.WordArray}
*/
export const generateAesKey = () => {
return CryptoJS.enc.Utf8.parse(generateRandomString());
};
/**
* 加密base64
* @param {CryptoJS.lib.WordArray} str
* @returns {string}
*/
export const encryptBase64 = (str) => {
return CryptoJS.enc.Base64.stringify(str);
};
/**
* 解密base64
* @param {string} str
* @returns {CryptoJS.lib.WordArray}
*/
export const decryptBase64 = (str) => {
return CryptoJS.enc.Base64.parse(str);
};
/**
* 使用密钥对数据进行AES加密
* @param {string} message 待加密的数据
* @param {CryptoJS.lib.WordArray} aesKey AES密钥
* @returns {string} 加密后的数据
*/
export const encryptWithAes = (message, aesKey) => {
const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
};
/**
* 使用密钥对数据进行AES解密
* @param {string} message 待解密的数据
* @param {CryptoJS.lib.WordArray} aesKey AES密钥
* @returns {string} 解密后的数据
*/
export const decryptWithAes = (message, aesKey) => {
const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return decrypted.toString(CryptoJS.enc.Utf8);
};
/**
* RSA加密
* @param {string} txt 待加密的文本
* @returns {string|false} 加密后的数据
*/
export const encrypt = (txt) => {
try {
console.log('🔐 开始RSA加密,输入文本长度:', txt ? txt.length : 'null');
console.log('🔐 使用的RSA公钥:', ENCRYPT_CONFIG.RSA_PUBLIC_KEY.substring(0, 20) + '...');
const encryptor = new JSEncrypt();
// 构造完整的PEM格式RSA公钥
const publicKey = `-----BEGIN PUBLIC KEY-----
${ENCRYPT_CONFIG.RSA_PUBLIC_KEY}
-----END PUBLIC KEY-----`;
console.log('🔐 构造的完整公钥:', publicKey.substring(0, 50) + '...');
encryptor.setPublicKey(publicKey);
console.log('🔐 公钥设置完成');
const result = encryptor.encrypt(txt);
if (result) {
console.log('✅ RSA加密成功,结果长度:', result.length);
console.log('✅ 加密结果前50位:', result.substring(0, 50) + '...');
} else {
console.error('❌ RSA加密返回空结果');
}
return result;
} catch (error) {
console.error('❌ RSA加密失败:', error);
console.error('❌ 错误堆栈:', error.stack);
return false;
}
};
/**
* RSA解密
* @param {string} txt 待解密的文本
* @returns {string|false} 解密后的数据
*/
export const decrypt = (txt) => {
try {
console.log('🔓 开始RSA解密,输入文本长度:', txt ? txt.length : 'null');
console.log('🔓 使用的RSA私钥:', ENCRYPT_CONFIG.RSA_PRIVATE_KEY.substring(0, 20) + '...');
if (!ENCRYPT_CONFIG.RSA_PRIVATE_KEY) {
console.warn('❌ RSA私钥未配置,无法解密');
return false;
}
const encryptor = new JSEncrypt();
// 构造完整的PEM格式RSA私钥
const privateKey = `-----BEGIN PRIVATE KEY-----
${ENCRYPT_CONFIG.RSA_PRIVATE_KEY}
-----END PRIVATE KEY-----`;
console.log('🔓 构造的完整私钥:', privateKey.substring(0, 50) + '...');
encryptor.setPrivateKey(privateKey);
console.log('🔓 私钥设置完成');
const result = encryptor.decrypt(txt);
if (result) {
console.log('✅ RSA解密成功,结果长度:', result.length);
console.log('✅ 解密结果前50位:', result.substring(0, 50) + '...');
} else {
console.error('❌ RSA解密返回空结果');
}
return result;
} catch (error) {
console.error('❌ RSA解密失败:', error);
console.error('❌ 错误堆栈:', error.stack);
return false;
}
};
/**
* 生成随机字符串AES密钥(字符串格式)
* @param {number} length 密钥长度,默认32位
* @returns {string} AES密钥字符串
*/
export const generateRandomAESKey = (length = 32) => {
return generateRandomString();
};
/**
* 使用字符串密钥进行AES加密(兼容原有函数)
* @param {string} data 待加密的数据
* @param {string} key AES密钥字符串
* @returns {string} 加密后的数据
*/
export const encryptWithAES = (data, key) => {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
};
/**
* 使用字符串密钥进行AES解密(兼容原有函数)
* @param {string} data 待解密的数据
* @param {string} key AES密钥字符串
* @returns {string} 解密后的数据
*/
export const decryptWithAES = (data, key) => {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const decrypted = CryptoJS.AES.decrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return decrypted.toString(CryptoJS.enc.Utf8);
};
/**
* 更新加密配置
* @param {Object} config 新的配置对象
*/
export const updateEncryptConfig = (config) => {
Object.assign(ENCRYPT_CONFIG, config);
console.log('🔧 加密配置已更新:', ENCRYPT_CONFIG);
};
/**
* 验证RSA密钥对配置
* @returns {boolean} 验证结果
*/
export const validateKeyPair = () => {
try {
console.log('🔍 开始验证RSA密钥对配置...');
// 检查公钥和私钥是否存在
if (!ENCRYPT_CONFIG.RSA_PUBLIC_KEY) {
console.error('❌ RSA公钥未配置');
return false;
}
if (!ENCRYPT_CONFIG.RSA_PRIVATE_KEY) {
console.error('❌ RSA私钥未配置');
return false;
}
// 测试加密解密功能
const testMessage = 'RSA密钥对验证测试消息';
console.log('🧪 测试消息:', testMessage);
// 使用公钥加密
const encrypted = encrypt(testMessage);
if (!encrypted) {
console.error('❌ RSA加密测试失败');
return false;
}
console.log('✅ RSA加密测试成功');
// 使用私钥解密
const decrypted = decrypt(encrypted);
if (!decrypted || decrypted !== testMessage) {
console.error('❌ RSA解密测试失败');
console.error('原始消息:', testMessage);
console.error('解密结果:', decrypted);
return false;
}
console.log('✅ RSA解密测试成功');
console.log('🎉 RSA密钥对配置验证通过!');
return true;
} catch (error) {
console.error('❌ RSA密钥对验证过程中出现错误:', error);
return false;
}
};
/**
* 获取当前加密配置摘要
* @returns {Object} 配置摘要
*/
export const getConfigSummary = () => {
return {
hasPublicKey: !!ENCRYPT_CONFIG.RSA_PUBLIC_KEY,
hasPrivateKey: !!ENCRYPT_CONFIG.RSA_PRIVATE_KEY,
publicKeyLength: ENCRYPT_CONFIG.RSA_PUBLIC_KEY ? ENCRYPT_CONFIG.RSA_PUBLIC_KEY.length : 0,
privateKeyLength: ENCRYPT_CONFIG.RSA_PRIVATE_KEY
? ENCRYPT_CONFIG.RSA_PRIVATE_KEY.length
: 0,
clientId: ENCRYPT_CONFIG.CLIENT_ID,
tenantId: ENCRYPT_CONFIG.TENANT_ID,
encryptHeader: ENCRYPT_CONFIG.ENCRYPT_HEADER,
encryptEnabled: ENCRYPT_CONFIG.ENCRYPT_ENABLED,
};
};
// 导出配置以便外部修改
export { ENCRYPT_CONFIG as config };
+7 -20
View File
@@ -375,7 +375,7 @@ class GestureUtils {
class PageSwipeGesture {
constructor() {
this.currentPageIndex = 0;
this.pages = ['home', 'workbench', 'profile'];
this.pages = ['home'];
this.isAnimating = false;
}
@@ -387,29 +387,18 @@ class PageSwipeGesture {
enableDoubleTap: false,
});
gesture.on('swipeleft', () => {
this.nextPage();
});
gesture.on('swiperight', () => {
this.prevPage();
});
// 只有一个页面,禁用滑动切换
return gesture;
}
nextPage() {
if (this.isAnimating) return;
this.currentPageIndex = (this.currentPageIndex + 1) % this.pages.length;
this.switchPage(this.pages[this.currentPageIndex]);
// 只有一个页面,不执行切换
return;
}
prevPage() {
if (this.isAnimating) return;
this.currentPageIndex = (this.currentPageIndex - 1 + this.pages.length) % this.pages.length;
this.switchPage(this.pages[this.currentPageIndex]);
// 只有一个页面,不执行切换
return;
}
switchPage(pageName) {
@@ -417,12 +406,10 @@ class PageSwipeGesture {
const pageUrls = {
home: '/pages/index/index',
workbench: '/pages/workbench/index',
profile: '/pages/profile/index',
};
if (pageUrls[pageName]) {
uni.switchTab({
uni.navigateTo({
url: pageUrls[pageName],
success: () => {
setTimeout(() => {
+192
View File
@@ -0,0 +1,192 @@
/**
* iconfont 图标管理工具
* 帮助开发者快速添加和管理自定义图标
*/
/**
* SVG Path 转换为 iconfont CSS 代码
* @param {string} iconName - 图标名称 (如: star, heart)
* @param {string} svgPath - SVG path 数据
* @param {string} viewBox - SVG viewBox, 默认 "0 0 24 24"
* @returns {string} CSS 代码
*/
export function generateIconCSS(iconName, svgPath, viewBox = '0 0 24 24') {
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='${viewBox}' fill='currentColor'><path d='${svgPath}'/></svg>`;
const encodedSvg = encodeURIComponent(svg).replace(/'/g, '%27').replace(/"/g, '%22');
return `/* ${iconName}图标 */
.icon-${iconName} {
background-image: url("data:image/svg+xml;charset=utf8,${encodedSvg}");
}`;
}
/**
* 批量生成图标 CSS
* @param {Array} icons - 图标数组 [{name, path, viewBox}]
* @returns {string} 完整的 CSS 代码
*/
export function generateBatchIconCSS(icons) {
return icons.map((icon) => generateIconCSS(icon.name, icon.path, icon.viewBox)).join('\n\n');
}
/**
* 从完整SVG生成CSS
* @param {string} iconName - 图标名称
* @param {string} svgContent - 完整的SVG内容
* @returns {string} CSS 代码
*/
export function generateIconCSSFromSVG(iconName, svgContent) {
const encodedSvg = encodeURIComponent(svgContent).replace(/'/g, '%27').replace(/"/g, '%22');
return `/* ${iconName}图标 */
.icon-${iconName} {
background-image: url("data:image/svg+xml;charset=utf8,${encodedSvg}");
}`;
}
/**
* 预定义的常用图标路径
* 来源:Material Design Icons
*/
export const COMMON_ICONS = {
// 基础图标
user: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
lock: 'M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z',
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
setting:
'M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z',
// 操作图标
add: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
close: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z',
edit: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
delete: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z',
search: 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z',
check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
// 状态图标
success:
'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z',
warning: 'M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z',
info: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z',
error: 'M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z',
// 文件图标
download: 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z',
upload: 'M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z',
// 导航图标
arrowLeft: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z',
arrowRight: 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z',
arrowUp: 'M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z',
arrowDown: 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z',
// 社交图标
star: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z',
heart: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z',
message:
'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z',
phone: 'M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z',
mail: 'M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z',
};
/**
* 快速生成常用图标的CSS
* @param {string[]} iconNames - 图标名称数组
* @returns {string} CSS 代码
*/
export function generateCommonIconsCSS(iconNames) {
const selectedIcons = iconNames
.map((name) => ({
name,
path: COMMON_ICONS[name],
}))
.filter((icon) => icon.path);
return generateBatchIconCSS(selectedIcons);
}
/**
* 检查图标名称是否已存在
* @param {string} iconName - 图标名称
* @param {string} cssContent - 当前CSS文件内容
* @returns {boolean} 是否已存在
*/
export function checkIconExists(iconName, cssContent) {
return cssContent.includes(`.icon-${iconName}`);
}
/**
* 验证SVG内容
* @param {string} svgContent - SVG内容
* @returns {boolean} 是否有效
*/
export function validateSVG(svgContent) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(svgContent, 'image/svg+xml');
return !doc.querySelector('parsererror');
} catch (error) {
return false;
}
}
/**
* 使用示例
*/
export const USAGE_EXAMPLES = {
// 单个图标生成
singleIcon: {
code: `import { generateIconCSS } from '@/utils/iconHelper';
const css = generateIconCSS(
'calendar',
'M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z'
);
console.log(css);`,
output: `/* calendar图标 */
.icon-calendar {
background-image: url("data:image/svg+xml;charset=utf8,...");
}`,
},
// 批量图标生成
batchIcons: {
code: `import { generateCommonIconsCSS } from '@/utils/iconHelper';
const css = generateCommonIconsCSS(['star', 'heart', 'message']);
console.log(css);`,
output: `/* star图标 */
.icon-star { ... }
/* heart图标 */
.icon-heart { ... }
/* message图标 */
.icon-message { ... }`,
},
// 从SVG生成
fromSVG: {
code: `import { generateIconCSSFromSVG } from '@/utils/iconHelper';
const svgContent = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
</svg>\`;
const css = generateIconCSSFromSVG('circle', svgContent);
console.log(css);`,
},
};
// 导出所有工具函数
export default {
generateIconCSS,
generateBatchIconCSS,
generateIconCSSFromSVG,
generateCommonIconsCSS,
checkIconExists,
validateSVG,
COMMON_ICONS,
USAGE_EXAMPLES,
};
+122
View File
@@ -0,0 +1,122 @@
/**
* 移动端权限校验工具
* 参考web端的permission.ts实现
*/
import { userManager } from '@/utils/user'
/**
* 字符权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export const checkPermi = (value) => {
if (value && value instanceof Array && value.length > 0) {
const userInfo = userManager.getCurrentUser()
if (!userInfo || !userInfo.permissions) {
return false
}
const permissions = userInfo.permissions
const permissionDatas = value
const all_permission = '*:*:*'
const hasPermission = permissions.some((permission) => {
return all_permission === permission || permissionDatas.includes(permission)
})
if (!hasPermission) {
return false
}
return true
} else {
console.error('need roles! Like checkPermi="[\'system:user:add\',\'system:user:edit\']"')
return false
}
}
/**
* 角色权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export const checkRole = (value) => {
if (value && value instanceof Array && value.length > 0) {
const userInfo = userManager.getCurrentUser()
if (!userInfo || !userInfo.roles) {
return false
}
const roles = userInfo.roles
const permissionRoles = value
const super_admin = 'admin'
const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role)
})
if (!hasRole) {
return false
}
return true
} else {
console.error('need roles! Like checkRole="[\'admin\',\'editor\']"')
return false
}
}
/**
* 权限指令校验函数
* 用于在模板中进行权限判断
* @param {Array|String} permissions 权限标识数组或单个权限标识
* @returns {Boolean}
*/
export const hasPermission = (permissions) => {
if (!permissions) return true
// 如果传入的是字符串,转换为数组
const perms = Array.isArray(permissions) ? permissions : [permissions]
return checkPermi(perms)
}
/**
* Vue过滤器:权限校验
* 在模板中使用:v-if="hasPermi(['system:user:add'])"
*/
export const hasPermi = hasPermission
/**
* 用户角色校验
* @param {Array|String} roles 角色标识数组或单个角色标识
* @returns {Boolean}
*/
export const hasRole = (roles) => {
if (!roles) return true
// 如果传入的是字符串,转换为数组
const roleList = Array.isArray(roles) ? roles : [roles]
return checkRole(roleList)
}
/**
* 权限控制混入
* 在页面或组件中使用
*/
export const permissionMixin = {
methods: {
// 权限校验方法
hasPermi(permissions) {
return hasPermission(permissions)
},
// 角色校验方法
hasRole(roles) {
return hasRole(roles)
},
// 检查单个权限
checkPermission(permission) {
return hasPermission([permission])
},
},
}
+315 -92
View File
@@ -1,10 +1,66 @@
import { encryptLoginData } from './encrypt-pure.js';
import {
generateAesKey,
encryptBase64,
decryptBase64,
encryptWithAes,
decryptWithAes,
encrypt,
decrypt,
ENCRYPT_CONFIG,
updateEncryptConfig,
validateKeyPair,
getConfigSummary,
} from './crypto.js';
// API请求配置
const baseURL =
process.env.NODE_ENV === 'development'
? '/api' // 开发环境使用代理
: 'https://your-api-domain.com';
// API请求配置 - 可配置
const REQUEST_CONFIG = {
// 基础URL配置
baseURL: process.env.NODE_ENV === 'development' ? '/api' : 'https://your-api-domain.com',
// 请求超时时间
timeout: 300000, // 5分钟,适合大数据量导出
// 客户端ID
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
// 默认referer
referer: 'http://localhost/login?redirect=/index',
// 是否启用加密
encryptEnabled: true,
// 防重复提交间隔时间(ms)
repeatSubmitInterval: 500,
};
// 会话缓存工具
const sessionCache = {
set: (key, value) => {
try {
uni.setStorageSync(`session_${key}`, JSON.stringify(value));
} catch (e) {
console.error('设置会话缓存失败:', e);
}
},
get: (key) => {
try {
const data = uni.getStorageSync(`session_${key}`);
return data ? JSON.parse(data) : null;
} catch (e) {
console.error('获取会话缓存失败:', e);
return null;
}
},
remove: (key) => {
try {
uni.removeStorageSync(`session_${key}`);
} catch (e) {
console.error('删除会话缓存失败:', e);
}
},
};
// 统一请求方法
export const request = (options) => {
@@ -12,18 +68,157 @@ export const request = (options) => {
// 获取token
const token = getToken();
uni.request({
url: baseURL + options.url,
// 处理请求配置
const config = {
url: REQUEST_CONFIG.baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
timeout: options.timeout || REQUEST_CONFIG.timeout,
header: {
'Content-Type': 'application/json',
authorization: token,
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
referer: 'http://localhost/login?redirect=/index',
clientid: options.clientId || REQUEST_CONFIG.clientId,
// 注意:在H5环境中,浏览器会自动设置referer,手动设置会报错
// referer: options.referer || REQUEST_CONFIG.referer,
...options.header,
},
};
// 检查特殊header配置
const isToken = options.header?.isToken === false;
const isRepeatSubmit = options.header?.repeatSubmit === false;
const isEncrypt = options.header?.isEncrypt === 'true';
// 如果不需要token,移除authorization
if (isToken) {
delete config.header.authorization;
}
// 防重复提交检查
if (!isRepeatSubmit && (config.method === 'POST' || config.method === 'PUT')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime(),
};
const sessionObj = sessionCache.get('sessionObj');
if (sessionObj) {
const s_url = sessionObj.url;
const s_data = sessionObj.data;
const s_time = sessionObj.time;
const interval = REQUEST_CONFIG.repeatSubmitInterval;
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message);
uni.showToast({
title: message,
icon: 'none',
});
return reject(new Error(message));
}
}
sessionCache.set('sessionObj', requestObj);
}
// 加密处理
if (
REQUEST_CONFIG.encryptEnabled &&
isEncrypt &&
(config.method === 'POST' || config.method === 'PUT')
) {
try {
console.log('🔐 开始请求加密处理...');
// 生成AES密钥
const aesKey = generateAesKey();
// 使用RSA加密AES密钥
const encryptedKey = encrypt(encryptBase64(aesKey));
if (!encryptedKey) {
throw new Error('RSA加密AES密钥失败');
}
// 设置加密头
config.header[ENCRYPT_CONFIG.ENCRYPT_HEADER] = encryptedKey;
// 加密请求数据
const dataToEncrypt =
typeof config.data === 'object' ? JSON.stringify(config.data) : config.data;
config.data = encryptWithAes(dataToEncrypt, aesKey);
console.log('✅ 请求数据加密完成');
} catch (error) {
console.error('❌ 请求加密失败:', error);
uni.showToast({
title: '请求加密失败',
icon: 'none',
});
return reject(error);
}
}
// FormData处理
if (config.data instanceof globalThis.FormData) {
delete config.header['Content-Type'];
}
console.log('📤 发送请求:', {
url: config.url,
method: config.method,
hasToken: !!token,
isEncrypt: isEncrypt,
});
// 发送请求
uni.request({
...config,
success: (res) => {
console.log('📥 收到响应:', {
statusCode: res.statusCode,
url: config.url,
hasEncryptHeader: !!res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER],
});
// 解密处理
if (REQUEST_CONFIG.encryptEnabled && res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER]) {
try {
console.log('🔓 开始响应解密处理...');
// 获取加密的AES密钥
const keyStr = res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER];
// RSA解密获取AES密钥
const base64Str = decrypt(keyStr);
if (!base64Str) {
throw new Error('RSA解密AES密钥失败');
}
// Base64解码获取AES密钥
const aesKey = decryptBase64(base64Str);
// AES解密响应数据
const decryptedData = decryptWithAes(res.data, aesKey);
// 解析JSON
res.data = JSON.parse(decryptedData);
console.log('✅ 响应数据解密完成');
} catch (error) {
console.error('❌ 响应解密失败:', error);
uni.showToast({
title: '响应解密失败',
icon: 'none',
});
return reject(error);
}
}
// 处理响应
if (res.statusCode === 200) {
// 检查业务状态码
@@ -37,10 +232,14 @@ export const request = (options) => {
reject(res.data);
} else {
// 其他业务错误
const errorMsg = res.data.msg || '请求失败';
if (res.data.code !== 404) {
// 404错误静默处理
uni.showToast({
title: res.data.msg || '请求失败',
title: errorMsg,
icon: 'none',
});
}
reject(res.data);
}
} else {
@@ -51,18 +250,34 @@ export const request = (options) => {
console.log('🔒 HTTP 401未授权,自动跳转到登录页');
handleUnauthorized('登录已失效,请重新登录');
reject(res);
} else if (res.statusCode === 404) {
// 404错误静默处理
console.log('404错误被静默处理,不显示用户提示');
reject(res);
} else {
// 其他HTTP状态码错误
const errorMsg = `请求失败: ${res.statusCode}`;
uni.showToast({
title: `请求失败: ${res.statusCode}`,
title: errorMsg,
icon: 'none',
});
reject(res);
}
},
fail: (error) => {
console.error('❌ 网络请求失败:', error);
let message = '网络请求失败';
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
message = '请求超时,请检查网络连接';
} else if (error.errMsg.includes('fail')) {
message = '网络连接失败,请检查网络';
}
}
uni.showToast({
title: '网络请求失败',
title: message,
icon: 'none',
});
reject(error);
@@ -133,87 +348,25 @@ function handleUnauthorized(message = '登录已失效,请重新登录') {
// API方法封装
export const api = {
// 用户登录
login: async (data) => {
try {
console.log('🔐 开始加密登录流程...');
// 用户登录 - 使用统一的request方法和加密拦截器
login: (data) => {
console.log('🔐 开始登录流程...');
// 加密登录数据
const encryptResult = encryptLoginData({
return request({
url: '/auth/login',
method: 'POST',
data: {
username: data.username,
password: data.password,
tenantId: data.tenantId || '000000',
clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
clientId: data.clientId || REQUEST_CONFIG.clientId,
grantType: data.grantType || 'password',
});
// 如果encryptLoginData返回的是Promise,需要await
const actualEncryptResult = encryptResult.then ? await encryptResult : encryptResult;
if (!actualEncryptResult.success) {
console.error('❌ 登录数据加密失败:', actualEncryptResult.error);
throw new Error(actualEncryptResult.error);
}
console.log('✅ 登录数据加密成功,发送加密请求...');
// 使用加密数据和自定义header发送请求
return new Promise((resolve, reject) => {
uni.request({
url: baseURL + '/auth/login',
method: 'POST',
data: actualEncryptResult.encryptedData, // 发送加密字符串,不是JSON对象
},
header: {
...actualEncryptResult.headers,
referer: 'http://localhost/login?redirect=/index', // 添加正确格式的referer
},
success: (res) => {
console.log('🔐 加密登录响应:', res);
if (res.statusCode === 200) {
if (res.data && res.data.code !== undefined) {
if (res.data.code === 200) {
console.log('✅ 加密登录成功');
resolve(res.data);
} else {
console.log('❌ 登录失败:', res.data.msg);
uni.showToast({
title: res.data.msg || '登录失败',
icon: 'none',
});
reject(res.data);
}
} else {
resolve(res.data);
}
} else {
console.log('❌ HTTP状态错误:', res.statusCode);
uni.showToast({
title: `请求失败: ${res.statusCode}`,
icon: 'none',
});
reject(res);
}
},
fail: (error) => {
console.log('❌ 登录请求失败:', error);
uni.showToast({
title: '网络请求失败',
icon: 'none',
});
reject(error);
isToken: false, // 登录接口不需要token
isEncrypt: 'true', // 启用加密
},
});
});
} catch (error) {
console.error('❌ 登录加密处理失败:', error);
console.error('❌ 错误详情:', error.message);
console.error('❌ 错误堆栈:', error.stack);
uni.showToast({
title: '登录处理失败: ' + error.message,
icon: 'none',
});
return Promise.reject(error);
}
},
// 获取用户信息
@@ -227,11 +380,6 @@ export const api = {
return request({
url: '/system/user/getInfo',
method: 'GET',
header: {
authorization: token,
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
referer: 'http://localhost/login?redirect=/index',
},
});
},
@@ -265,7 +413,7 @@ export const userStorage = {
if (userInfo) {
const enhancedUserInfo = {
...userInfo,
loginTime: new Date().toISOString(), // 记录登录时间
loginTime: new Date().toISOString(),
tokenExpireTime: loginResult.expires_in
? new Date(Date.now() + loginResult.expires_in * 1000).toISOString()
: null,
@@ -333,6 +481,26 @@ export const userStorage = {
console.error('清除登录信息失败:', e);
}
},
// 清除所有用户相关数据(包括refresh_token等)
clearAll: () => {
try {
// 清除所有登录相关的存储
uni.removeStorageSync('access_token');
uni.removeStorageSync('userInfo');
uni.removeStorageSync('isLoggedIn');
uni.removeStorageSync('refresh_token');
// 清除可能存在的其他用户相关数据
uni.removeStorageSync('userPermissions');
uni.removeStorageSync('userRoles');
uni.removeStorageSync('tenantInfo');
console.log('所有用户相关数据已清除');
} catch (e) {
console.error('清除所有用户数据失败:', e);
}
},
};
// 数据缓存工具
@@ -390,3 +558,58 @@ export const storage = {
}
},
};
// 通用下载方法
export const download = (url, params, fileName) => {
// 显示加载提示
uni.showLoading({
title: '正在下载数据,请稍候',
mask: true,
});
return request({
url: url,
method: 'POST',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout: 1200000, // 20分钟超时
responseType: 'arraybuffer',
})
.then((resp) => {
// 处理下载响应
// 注意:uni-app中文件下载需要特殊处理
console.log('文件下载完成:', fileName);
uni.hideLoading();
// 这里需要根据平台进行不同的文件保存处理
// 具体实现可能需要平台特定的代码
})
.catch((error) => {
console.error('下载失败:', error);
uni.hideLoading();
uni.showToast({
title: '下载文件出现错误,请联系管理员!',
icon: 'none',
});
});
};
// 配置更新方法
export const updateRequestConfig = (config) => {
Object.assign(REQUEST_CONFIG, config);
console.log('🔧 请求配置已更新:', REQUEST_CONFIG);
};
// 加密配置更新方法
export const updateCryptoConfig = updateEncryptConfig;
// 密钥对验证方法
export const validateCryptoKeys = validateKeyPair;
// 获取加密配置摘要
export const getCryptoConfigSummary = getConfigSummary;
// 导出配置以便外部修改
export { REQUEST_CONFIG as config, ENCRYPT_CONFIG as cryptoConfig };
+1 -1
View File
@@ -176,7 +176,7 @@ export const permissionUtils = {
*/
checkPagePermission(pagePath) {
// 公开页面,无需权限检查
const publicPages = ['/pages/login/index', '/pages/index/index', '/pages/profile/index'];
const publicPages = ['/pages/login/index', '/pages/index/index'];
if (publicPages.includes(pagePath)) {
return true;
-15
View File
@@ -1,15 +0,0 @@
INSERT INTO sys_menu (menu_id, menu_name, path, parent_id, order_num, component, is_frame, menu_type, visible, status, perms, icon) VALUES (1948304542593663001, '企业信息', '/zsjy/enterprise/', 1948304542593662977, 1, 'zsjy/enterprise/index', 1, 'C', '0', '0', NULL, 'company');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663002, '新增企业信息', 1948304542593663001, 1, 'F', '0', '0', 'zsjy:enterprise:add', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663003, '编辑企业信息', 1948304542593663001, 2, 'F', '0', '0', 'zsjy:enterprise:edit', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663004, '删除企业信息', 1948304542593663001, 3, 'F', '0', '0', 'zsjy:enterprise:remove', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663005, '导出企业信息', 1948304542593663001, 4, 'F', '0', '0', 'zsjy:enterprise:export', '#');
INSERT INTO sys_menu (menu_id, menu_name, path, parent_id, order_num, component, is_frame, menu_type, visible, status, perms, icon) VALUES (1948304542593663011, '招聘岗位', '/zsjy/jobPosition/', 1948304542593662977, 2, 'zsjy/jobPosition/index', 1, 'C', '0', '0', NULL, 'user');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663012, '新增招聘岗位', 1948304542593663011, 1, 'F', '0', '0', 'zsjy:jobPosition:add', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663013, '编辑招聘岗位', 1948304542593663011, 2, 'F', '0', '0', 'zsjy:jobPosition:edit', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663014, '删除招聘岗位', 1948304542593663011, 3, 'F', '0', '0', 'zsjy:jobPosition:remove', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663015, '导出招聘岗位', 1948304542593663011, 4, 'F', '0', '0', 'zsjy:jobPosition:export', '#');
INSERT INTO sys_menu (menu_id, menu_name, path, parent_id, order_num, component, is_frame, menu_type, visible, status, perms, icon) VALUES (1948304542593663021, '招聘海报', '/zsjy/poster', 1948304542593662977, 3, 'zsjy/poster/index', 1, 'C', '0', '0', NULL, 'file');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663022, '新增招聘海报', 1948304542593663021, 1, 'F', '0', '0', 'zsjy:poster:add', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663023, '编辑招聘海报', 1948304542593663021, 2, 'F', '0', '0', 'zsjy:poster:edit', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663024, '删除招聘海报', 1948304542593663021, 3, 'F', '0', '0', 'zsjy:poster:remove', '#');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, icon) VALUES (1948304542593663025, '导出招聘海报', 1948304542593663021, 4, 'F', '0', '0', 'zsjy:poster:export', '#');
-321
View File
@@ -1,321 +0,0 @@
# 项目申报表单集成步骤指南
## 🎯 目标
完成项目申报表单的绩效指标组件集成和金额字段修复
## 📋 前置条件
- ✅ 已创建 `PerformanceIndicators.vue` 组件
- ✅ 已创建 `performanceIndicator.ts` API接口
- ✅ 已修复表单数据结构
## 🔧 集成步骤
### 步骤1: 修复form.vue的模板部分
需要在 `web/src/views/cw/application/form.vue` 中添加完整的金额字段表单:
```vue
<!-- 第三步:资金预算 -->
<el-card shadow="never" class="form-card" v-show="currentStep === 2">
<template #header>
<div class="card-header">
<el-icon><Money /></el-icon>
<span>资金预算安排</span>
</div>
</template>
<el-form ref="budgetFormRef" :model="form" :rules="rules" label-width="140px">
<!-- 项目总投资 -->
<div class="budget-section">
<h4 class="section-title">项目总投资(万元)</h4>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="合计" prop="totalInvestment">
<el-input-number v-model="form.totalInvestment" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="市财政资金" prop="totalMunicipalFiscal">
<el-input-number v-model="form.totalMunicipalFiscal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="单位自筹" prop="totalSelfFunding">
<el-input-number v-model="form.totalSelfFunding" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="其他资金" prop="totalOtherFunding">
<el-input-number v-model="form.totalOtherFunding" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 年度预计执行数 -->
<div class="budget-section">
<h4 class="section-title">年度预计执行数(万元)</h4>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="合计" prop="currentYearTotal">
<el-input-number v-model="form.currentYearTotal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="市财政资金" prop="currentYearMunicipal">
<el-input-number v-model="form.currentYearMunicipal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="单位自筹" prop="currentYearSelf">
<el-input-number v-model="form.currentYearSelf" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="其他资金" prop="currentYearOther">
<el-input-number v-model="form.currentYearOther" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 下年度申请资金 -->
<div class="budget-section">
<h4 class="section-title">下年度申请资金(万元)</h4>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="合计" prop="nextYearTotal">
<el-input-number v-model="form.nextYearTotal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="市财政资金" prop="nextYearMunicipal">
<el-input-number v-model="form.nextYearMunicipal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="单位自筹" prop="nextYearSelf">
<el-input-number v-model="form.nextYearSelf" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="其他资金" prop="nextYearOther">
<el-input-number v-model="form.nextYearOther" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 以后年度安排 -->
<div class="budget-section">
<h4 class="section-title">以后年度安排(万元)</h4>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="合计" prop="futureYearTotal">
<el-input-number v-model="form.futureYearTotal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="市财政资金" prop="futureYearMunicipal">
<el-input-number v-model="form.futureYearMunicipal" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="单位自筹" prop="futureYearSelf">
<el-input-number v-model="form.futureYearSelf" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="其他资金" prop="futureYearOther">
<el-input-number v-model="form.futureYearOther" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</el-card>
```
### 步骤2: 注册PerformanceIndicators组件
在 `web/src/views/cw/application/form.vue` 的script部分添加:
```javascript
// 注册组件
import PerformanceIndicators from './components/PerformanceIndicators.vue'
// 在组件定义中添加
export default {
components: {
PerformanceIndicators
},
// ... 其他配置
}
```
### 步骤3: 修复API导入错误
确保 `web/src/api/cw/projectApplication.ts` 文件存在并包含正确的API方法:
```typescript
// 项目申报相关API
export const addProjectApplication = (data: any) => {
return request({
url: '/cw/application',
method: 'post',
data: data
});
};
export const updateProjectApplication = (data: any) => {
return request({
url: '/cw/application',
method: 'put',
data: data
});
};
export const getProjectApplication = (id: string | number) => {
return request({
url: '/cw/application/' + id,
method: 'get'
});
};
export const submitApplication = (id: string | number) => {
return request({
url: '/cw/application/submit/' + id,
method: 'post'
});
};
```
### 步骤4: 更新保存逻辑
修复 `handleSaveDraft` 和 `handleSubmit` 方法:
```javascript
import { batchSavePerformanceIndicator } from '@/api/cw/performanceIndicator'
// 保存草稿
const handleSaveDraft = async () => {
try {
savingDraft.value = true
// 1. 保存申报基本信息
let result
if (form.id) {
result = await updateProjectApplication(form)
} else {
result = await addProjectApplication(form)
if (result && result.data) {
form.id = result.data.id
form.applicationNo = result.data.applicationNo
}
}
// 2. 保存绩效指标
if (form.id && form.performanceIndicators.length > 0) {
await batchSavePerformanceIndicator(form.id, form.applicationNo, form.performanceIndicators)
}
ElMessage.success('草稿保存成功')
router.push('/cw/application')
} catch (error) {
ElMessage.error('保存失败:' + error.message)
} finally {
savingDraft.value = false
}
}
// 提交申报
const handleSubmit = async () => {
try {
// 1. 验证绩效指标
const performanceValidation = performanceFormRef.value?.validate()
if (performanceValidation && !performanceValidation.valid) {
ElMessage.error(performanceValidation.message)
return
}
submitting.value = true
// 2. 先保存草稿
await handleSaveDraft()
// 3. 提交审批
if (form.id) {
await submitApplication(form.id)
ElMessage.success('提交成功,已进入审批流程')
router.push('/cw/application')
}
} catch (error) {
ElMessage.error('提交失败:' + error.message)
} finally {
submitting.value = false
}
}
```
### 步骤5: 添加表单验证
更新验证规则:
```javascript
const rules = {
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' }
],
principal: [
{ required: true, message: '请输入项目负责人', trigger: 'blur' }
],
totalInvestment: [
{ required: true, message: '请填写项目总投资', trigger: 'blur' },
{ type: 'number', min: 0.01, message: '投资金额必须大于0', trigger: 'blur' }
],
// ... 其他验证规则
}
```
### 步骤6: 测试功能
1. **新增申报测试**:
- 填写基本信息
- 填写项目内容
- 填写完整的资金预算(所有金额字段)
- 添加绩效指标(确保分值总和为100%)
- 保存草稿,检查数据库是否正确保存
2. **修改申报测试**:
- 打开已保存的申报
- 修改金额和绩效指标
- 重新保存,验证数据更新
3. **提交流程测试**:
- 完成申报填写
- 点击提交,确认审批流程创建成功
## 🚨 常见问题解决
### 问题1: API导入错误
**解决方案**: 确保API文件路径正确,检查 `@/api/cw/` 目录下的文件是否存在
### 问题2: 绩效指标组件不显示
**解决方案**: 检查组件导入路径,确保 `PerformanceIndicators.vue` 文件存在
### 问题3: 金额字段保存失败
**解决方案**: 检查前端字段名与后端BO类字段名是否严格匹配
### 问题4: 分值验证错误
**解决方案**: 检查绩效指标组件的 `validate()` 方法是否正确调用
## ✅ 验收标准
- [ ] 所有金额字段能正确显示和编辑
- [ ] 绩效指标分类显示正确(产出指标、效益指标、满意度指标)
- [ ] 绩效指标验证规则正常工作(必填项、分值总和100%)
- [ ] 保存草稿功能正常
- [ ] 提交申报功能正常
- [ ] 数据库中申报信息和绩效指标数据完整
完成这些步骤后,项目申报功能将完全满足业务需求。
-201
View File
@@ -1,201 +0,0 @@
-- 修复审批记录数据脚本
-- 问题:审批人设置错误(使用申报人作为审批人)
-- 解决:删除错误记录,为submitted和approving状态的申报重新创建审批记录
-- ============================================
-- 1. 备份现有错误数据(可选)
-- ============================================
/*
CREATE TABLE cw_approval_record_backup_20250801 AS
SELECT * FROM cw_approval_record WHERE 1=1;
*/
-- ============================================
-- 2. 删除错误的审批记录
-- ============================================
-- 删除审批人是申报人自己的错误记录
DELETE ar FROM cw_approval_record ar
INNER JOIN cw_project_application pa ON ar.application_id = pa.id
WHERE ar.approver_id = pa.create_by;
SELECT '删除错误审批记录完成' as status;
-- ============================================
-- 3. 检查需要重新创建审批记录的申报
-- ============================================
SELECT
id,
application_no,
project_name,
approval_status,
department_id,
'需要重新创建审批记录' as note
FROM cw_project_application
WHERE approval_status IN ('submitted', 'approving')
AND deleted = 0
ORDER BY submit_time DESC;
-- ============================================
-- 4. 为教务处申报创建正确的审批记录(jwc001作为部门审批人)
-- ============================================
-- 获取需要处理的申报ID
SET @dept_id = 1938484570826956801; -- 教务处
SET @dept_approver_id = 2101; -- jwc001 (教务处处长江建)
SET @finance_approver_id = 1; -- admin (临时财务审批人)
SET @leader_approver_id = 1; -- admin (临时领导审批人)
-- 为每个submitted状态的申报创建审批记录
INSERT INTO cw_approval_record (
id, application_id, application_no, node_code, node_name, node_order,
approver_id, approver_name, is_delegate, original_approver_id, original_approver_name,
node_status, approve_action, approve_opinion, start_time, end_time, expect_finish_time,
is_overtime, config_id, config_snapshot, create_by, create_time, update_by, update_time,
create_dept, tenant_id
)
SELECT
-- 为部门审批记录生成ID
CONCAT(pa.id, '01') as id,
pa.id as application_id,
pa.application_no,
'dept_approve' as node_code,
'部门审批' as node_name,
1 as node_order,
@dept_approver_id as approver_id,
'教务处处长江建' as approver_name,
0 as is_delegate,
NULL as original_approver_id,
NULL as original_approver_name,
'waiting' as node_status,
NULL as approve_action,
NULL as approve_opinion,
NOW() as start_time,
NULL as end_time,
NULL as expect_finish_time,
0 as is_overtime,
NULL as config_id,
NULL as config_snapshot,
pa.create_by as create_by,
NOW() as create_time,
pa.create_by as update_by,
NOW() as update_time,
pa.create_dept as create_dept,
'000000' as tenant_id
FROM cw_project_application pa
WHERE pa.department_id = @dept_id
AND pa.approval_status IN ('submitted', 'approving')
AND pa.deleted = 0;
-- 为每个申报创建财务审批记录(pending状态)
INSERT INTO cw_approval_record (
id, application_id, application_no, node_code, node_name, node_order,
approver_id, approver_name, is_delegate, original_approver_id, original_approver_name,
node_status, approve_action, approve_opinion, start_time, end_time, expect_finish_time,
is_overtime, config_id, config_snapshot, create_by, create_time, update_by, update_time,
create_dept, tenant_id
)
SELECT
-- 为财务审批记录生成ID
CONCAT(pa.id, '02') as id,
pa.id as application_id,
pa.application_no,
'finance_approve' as node_code,
'财务审批' as node_name,
2 as node_order,
@finance_approver_id as approver_id,
'系统管理员' as approver_name,
0 as is_delegate,
NULL as original_approver_id,
NULL as original_approver_name,
'pending' as node_status,
NULL as approve_action,
NULL as approve_opinion,
NOW() as start_time,
NULL as end_time,
NULL as expect_finish_time,
0 as is_overtime,
NULL as config_id,
NULL as config_snapshot,
pa.create_by as create_by,
NOW() as create_time,
pa.create_by as update_by,
NOW() as update_time,
pa.create_dept as create_dept,
'000000' as tenant_id
FROM cw_project_application pa
WHERE pa.department_id = @dept_id
AND pa.approval_status IN ('submitted', 'approving')
AND pa.deleted = 0;
-- 为每个申报创建领导审批记录(pending状态)
INSERT INTO cw_approval_record (
id, application_id, application_no, node_code, node_name, node_order,
approver_id, approver_name, is_delegate, original_approver_id, original_approver_name,
node_status, approve_action, approve_opinion, start_time, end_time, expect_finish_time,
is_overtime, config_id, config_snapshot, create_by, create_time, update_by, update_time,
create_dept, tenant_id
)
SELECT
-- 为领导审批记录生成ID
CONCAT(pa.id, '03') as id,
pa.id as application_id,
pa.application_no,
'leader_approve' as node_code,
'领导审批' as node_name,
3 as node_order,
@leader_approver_id as approver_id,
'系统管理员' as approver_name,
0 as is_delegate,
NULL as original_approver_id,
NULL as original_approver_name,
'pending' as node_status,
NULL as approve_action,
NULL as approve_opinion,
NOW() as start_time,
NULL as end_time,
NULL as expect_finish_time,
0 as is_overtime,
NULL as config_id,
NULL as config_snapshot,
pa.create_by as create_by,
NOW() as create_time,
pa.create_by as update_by,
NOW() as update_time,
pa.create_dept as create_dept,
'000000' as tenant_id
FROM cw_project_application pa
WHERE pa.department_id = @dept_id
AND pa.approval_status IN ('submitted', 'approving')
AND pa.deleted = 0;
-- ============================================
-- 5. 验证修复结果
-- ============================================
-- 检查审批记录是否正确创建
SELECT
ar.application_id,
ar.application_no,
ar.node_code,
ar.node_name,
ar.node_status,
ar.approver_id,
ar.approver_name,
pa.approval_status
FROM cw_approval_record ar
INNER JOIN cw_project_application pa ON ar.application_id = pa.id
WHERE pa.department_id = @dept_id
AND pa.approval_status IN ('submitted', 'approving')
ORDER BY ar.application_id, ar.node_order;
-- 统计修复结果
SELECT
'修复完成' as status,
COUNT(DISTINCT ar.application_id) as fixed_applications,
COUNT(*) as total_approval_records
FROM cw_approval_record ar
INNER JOIN cw_project_application pa ON ar.application_id = pa.id
WHERE pa.department_id = @dept_id
AND pa.approval_status IN ('submitted', 'approving');
SELECT '数据修复脚本执行完成' as result;
-155
View File
@@ -1,155 +0,0 @@
-- 财务项目申报审批状态显示问题排查和修复脚本
-- 日期: 2025-01-26
-- 说明: 解决审批状态字段显示数字而非中文描述的问题
-- ==========================================
-- 1. 问题排查:检查当前数据状态
-- ==========================================
-- 1.1 查看当前申报记录的状态值分布
SELECT
approval_status,
COUNT(*) as record_count,
GROUP_CONCAT(DISTINCT project_name SEPARATOR ', ') as sample_projects
FROM cw_project_application
GROUP BY approval_status
ORDER BY record_count DESC;
-- 1.2 查看字典类型配置是否存在
SELECT * FROM sys_dict_type WHERE dict_type = 'cw_approval_status';
-- 1.3 查看字典数据配置是否完整
SELECT
dict_sort,
dict_label,
dict_value,
css_class,
status
FROM sys_dict_data
WHERE dict_type = 'cw_approval_status'
ORDER BY dict_sort;
-- 1.4 检查是否有数字格式的状态值(问题数据)
SELECT
id,
application_no,
project_name,
approval_status,
create_time
FROM cw_project_application
WHERE approval_status IN ('1', '2', '3', '4', '5', '6', '7')
ORDER BY create_time DESC
LIMIT 10;
-- ==========================================
-- 2. 问题修复:数据状态值迁移
-- ==========================================
-- 2.1 备份当前数据(可选,建议执行)
-- CREATE TABLE cw_project_application_backup_20250126 AS
-- SELECT * FROM cw_project_application;
-- 2.2 修复数字状态值为标准字典值
UPDATE cw_project_application
SET approval_status = CASE
WHEN approval_status = '1' THEN 'draft'
WHEN approval_status = '2' THEN 'submitted'
WHEN approval_status = '3' THEN 'approving'
WHEN approval_status = '4' THEN 'approved'
WHEN approval_status = '5' THEN 'rejected'
WHEN approval_status = '6' THEN 'returned'
WHEN approval_status = '7' THEN 'cancelled'
ELSE approval_status
END
WHERE approval_status IN ('1', '2', '3', '4', '5', '6', '7');
-- 2.3 验证修复结果
SELECT
approval_status,
COUNT(*) as record_count
FROM cw_project_application
GROUP BY approval_status
ORDER BY record_count DESC;
-- ==========================================
-- 3. 字典配置检查和修复
-- ==========================================
-- 3.1 确保字典类型存在
INSERT IGNORE INTO sys_dict_type VALUES
(3000003, '审批状态', 'cw_approval_status', '0', NULL, '财务预算-审批状态', now(), NULL, NULL, '审批状态分类');
-- 3.2 确保字典数据完整
INSERT IGNORE INTO sys_dict_data VALUES
(3000031, 0, '草稿', 'draft', 'cw_approval_status', '', '', 'N', '0', '0', '', '草稿状态', now(), NULL, NULL, NULL),
(3000032, 1, '已提交', 'submitted', 'cw_approval_status', '', '', 'N', '0', '0', '', '已提交审批', now(), NULL, NULL, NULL),
(3000033, 2, '审批中', 'approving', 'cw_approval_status', '', 'blue', 'N', '0', '0', '', '审批中', now(), NULL, NULL, NULL),
(3000034, 3, '已通过', 'approved', 'cw_approval_status', '', 'green', 'N', '0', '0', '', '审批通过', now(), NULL, NULL, NULL),
(3000035, 4, '已拒绝', 'rejected', 'cw_approval_status', '', 'red', 'N', '0', '0', '', '审批拒绝', now(), NULL, NULL, NULL),
(3000036, 5, '已退回', 'returned', 'cw_approval_status', '', 'orange', 'N', '0', '0', '', '退回修改', now(), NULL, NULL, NULL),
(3000037, 6, '已取消', 'cancelled', 'cw_approval_status', '', 'gray', 'N', '0', '0', '', '取消申报', now(), NULL, NULL, NULL);
-- ==========================================
-- 4. 清理和重置缓存(应用层面)
-- ==========================================
-- 4.1 更新字典缓存标记(如果系统支持)
-- UPDATE sys_config SET config_value = now() WHERE config_key = 'sys.dict.cache.refresh';
-- ==========================================
-- 5. 验证最终结果
-- ==========================================
-- 5.1 验证所有申报记录的状态值都是标准格式
SELECT
'修复后状态分布' as check_item,
approval_status,
COUNT(*) as record_count
FROM cw_project_application
GROUP BY approval_status
ORDER BY record_count DESC;
-- 5.2 验证字典配置完整性
SELECT
'字典配置验证' as check_item,
dict_label,
dict_value
FROM sys_dict_data
WHERE dict_type = 'cw_approval_status'
AND status = '0'
ORDER BY dict_sort;
-- 5.3 检查是否还有问题数据
SELECT
'问题数据检查' as check_item,
COUNT(*) as problem_count
FROM cw_project_application
WHERE approval_status IN ('1', '2', '3', '4', '5', '6', '7');
-- ==========================================
-- 使用说明
-- ==========================================
/*
执行步骤:
1. 先执行排查SQL(第1部分),查看当前数据状态
- 如果approval_status列显示的是数字(1,2,7等),说明需要数据迁移
- 如果已经是英文(draft,submitted等),说明数据正确
2. 如果需要修复,执行第2部分的UPDATE语句
- 建议先执行备份语句
- 然后执行状态值迁移
3. 执行第3部分确保字典配置完整
4. 执行第5部分验证修复结果
5. 重启后端服务和清理前端缓存
- cd backend && mvn spring-boot:run -pl ruoyi-admin
- cd web && npm run dev
注意事项:
- 执行前请先备份数据库
- 如果有正在进行的审批流程,请谨慎操作
- 修复后需要重启服务以清理缓存
*/
-181
View File
@@ -1,181 +0,0 @@
-- 修复jwc001用户审批权限问题
-- 创建时间: 2025-01-27
-- 问题: jwc001用户无法访问"部门审批管理"功能,API调用时提示权限不足
-- =====================================================
-- 问题分析
-- =====================================================
-- jwc001用户点击"部门审批管理"页面时,前端调用以下API接口:
-- 1. /cw/approval/list (需要 cw:approval:list 权限) ✅ 已有
-- 2. /cw/approval/dept/pending (需要 cw:approval:dept 权限) ❌ 缺失
-- 3. /cw/approval/my (需要 cw:approval:list 权限) ✅ 已有
--
-- 根本原因:jwc001用户的角色缺少 cw:approval:dept 权限
-- =====================================================
-- 第一步:确认当前状态
-- =====================================================
-- 查询jwc001用户信息
SELECT
u.user_id,
u.user_name,
u.nick_name,
u.dept_id,
d.dept_name
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.user_name = 'jwc001';
-- 查询jwc001用户当前角色
SELECT
u.user_name,
r.role_id,
r.role_name,
r.role_key
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
WHERE u.user_name = 'jwc001';
-- 查询当前角色拥有的财务审批相关权限
SELECT DISTINCT
r.role_name,
m.menu_name,
m.perms
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
JOIN sys_role_menu rm ON r.role_id = rm.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'jwc001'
AND m.perms LIKE 'cw:approval:%'
ORDER BY m.perms;
-- =====================================================
-- 第二步:查找缺失的权限
-- =====================================================
-- 查找所有财务审批相关的菜单权限
SELECT
menu_id,
menu_name,
perms,
parent_id
FROM sys_menu
WHERE perms LIKE 'cw:approval:%'
ORDER BY menu_id;
-- 特别检查部门审批权限菜单
SELECT
menu_id,
menu_name,
perms,
component
FROM sys_menu
WHERE perms = 'cw:approval:dept';
-- =====================================================
-- 第三步:修复权限配置
-- =====================================================
-- 方案1:如果部门审批员角色已存在,直接分配权限
-- 查找部门审批员角色
SELECT role_id, role_name, role_key
FROM sys_role
WHERE role_key = 'dept_approver' OR role_name LIKE '%部门审批%';
-- 如果角色存在,为该角色添加缺失的权限
-- (假设部门审批员角色ID为100,需要根据实际情况调整)
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, m.menu_id
FROM sys_role r, sys_menu m
WHERE r.role_key = 'dept_approver'
AND m.perms = 'cw:approval:dept'
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu rm
WHERE rm.role_id = r.role_id AND rm.menu_id = m.menu_id
);
-- 方案2:如果部门审批员角色不存在,创建角色并分配权限
-- 创建部门审批员角色(如果不存在)
INSERT INTO sys_role (
role_id, tenant_id, role_name, role_key, role_sort,
data_scope, menu_check_strictly, dept_check_strictly,
status, del_flag, create_dept, create_by, create_time,
update_by, update_time, remark
)
SELECT
100, '000000', '部门审批员', 'dept_approver', 4,
'3', 1, 1, '0', '0', 1938484570826956801, 1, NOW(),
1, NOW(), '负责本部门的财务项目申报审批工作'
WHERE NOT EXISTS (
SELECT 1 FROM sys_role WHERE role_key = 'dept_approver'
);
-- 为部门审批员角色分配必要的菜单权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 100, menu_id FROM sys_menu WHERE perms IN (
'cw:approval:list', -- 查询审批记录列表
'cw:approval:query', -- 查询审批记录详情
'cw:approval:dept', -- 部门审批权限
'cw:approval:export' -- 导出审批记录
);
-- 为部门审批员角色分配财务管理相关的父级菜单权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 100, menu_id FROM sys_menu WHERE menu_id IN (
6000, -- 财务管理(如果存在)
6100, -- 财务项目申报管理(如果存在)
6200 -- 部门审批管理(如果存在)
);
-- 确保jwc001用户拥有部门审批员角色
INSERT IGNORE INTO sys_user_role (user_id, role_id)
SELECT u.user_id, 100
FROM sys_user u
WHERE u.user_name = 'jwc001'
AND NOT EXISTS (
SELECT 1 FROM sys_user_role ur
WHERE ur.user_id = u.user_id AND ur.role_id = 100
);
-- =====================================================
-- 第四步:验证修复结果
-- =====================================================
-- 验证jwc001用户最终的权限配置
SELECT
u.user_name AS '用户名',
r.role_name AS '角色名',
m.menu_name AS '菜单名',
m.perms AS '权限码'
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
JOIN sys_role_menu rm ON r.role_id = rm.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'jwc001'
AND m.perms LIKE 'cw:approval:%'
ORDER BY m.perms;
-- 特别验证部门审批权限
SELECT
COUNT(*) AS '是否有部门审批权限',
CASE
WHEN COUNT(*) > 0 THEN '✅ 权限配置正确'
ELSE '❌ 权限配置缺失'
END AS '配置状态'
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
JOIN sys_role_menu rm ON r.role_id = rm.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'jwc001'
AND m.perms = 'cw:approval:dept';
COMMIT;
-- =====================================================
-- 预期结果
-- =====================================================
-- 1. jwc001用户获得完整的部门审批权限
-- 2. 能够正常访问 /cw/approval/dept/pending 接口
-- 3. "部门审批管理"页面加载正常,不再报权限错误
-- 4. 可以查看待审批和已审批记录
-16
View File
@@ -1,16 +0,0 @@
-- ==================================================
-- 修复招生就业模块表,补充缺失的基础字段
-- 创建日期: 2025-01-26
-- ==================================================
-- 1. 为 `zsjy_job_position` 表添加 `create_dept` 字段
ALTER TABLE `zsjy_job_position` ADD COLUMN `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门';
-- 2. 为 `zsjy_recruitment_poster` 表添加 `create_dept` 字段
ALTER TABLE `zsjy_recruitment_poster` ADD COLUMN `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门';
-- ==================================================
-- 检查语句
-- ==================================================
DESC `zsjy_job_position`;
DESC `zsjy_recruitment_poster`;
@@ -1,55 +0,0 @@
-- 学生请假审批状态字典配置脚本
-- 日期: 2025-01-27
-- 说明: 创建学生请假审批状态字典类型和数据
-- 1. 创建字典类型
INSERT IGNORE INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (2000000000000000001, '000000', '学生请假审批状态', 'student_leave_approval_status', 1, now(), '学生请假申请审批状态字典');
-- 2. 创建字典数据
-- 已撤销
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001001, '000000', 1, '已撤销', 'cancel', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '已撤销');
-- 草稿
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001002, '000000', 2, '草稿', 'draft', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '草稿状态');
-- 待审核
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001003, '000000', 3, '待审核', 'waiting', 'student_leave_approval_status', '', 'warning', 'N', 1, now(), '待审核状态');
-- 已完成
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001004, '000000', 4, '已完成', 'finish', 'student_leave_approval_status', '', 'success', 'N', 1, now(), '已完成状态');
-- 已作废
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001005, '000000', 5, '已作废', 'invalid', 'student_leave_approval_status', '', 'danger', 'N', 1, now(), '已作废状态');
-- 已退回
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001006, '000000', 6, '已退回', 'back', 'student_leave_approval_status', '', 'warning', 'N', 1, now(), '已退回状态');
-- 已终止
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001007, '000000', 7, '已终止', 'termination', 'student_leave_approval_status', '', 'danger', 'N', 1, now(), '已终止状态');
-- 3. 验证创建结果
SELECT
'字典类型' as check_item,
dict_name,
dict_type,
create_time
FROM sys_dict_type
WHERE dict_type = 'student_leave_approval_status';
SELECT
'字典数据' as check_item,
dict_sort,
dict_label,
dict_value,
list_class
FROM sys_dict_data
WHERE dict_type = 'student_leave_approval_status'
ORDER BY dict_sort;
-15
View File
@@ -1,15 +0,0 @@
-- 调整财务项目预算审批菜单结构
-- 目标:去掉"财务管理"一级目录,让"项目预算审批"直接作为一级目录
-- 1. 将"项目预算审批"菜单设置为一级菜单(parent_id改为0)
UPDATE sys_menu SET parent_id = 0, order_num = 8 WHERE menu_id = 5100;
-- 2. 删除"财务管理"一级目录(如果没有其他子菜单的话)
DELETE FROM sys_menu WHERE menu_id = 5000;
-- 3. 确认菜单结构调整后的完整性
-- 查看调整后的菜单结构
SELECT menu_id, parent_id, menu_name, menu_type, order_num, path
FROM sys_menu
WHERE menu_id >= 5100 AND menu_id <= 5200
ORDER BY parent_id, order_num;
-46
View File
@@ -1,46 +0,0 @@
-- 财务项目申报审批功能字典数据创建脚本
-- 日期: 2025-01-26
-- 说明: 创建审批动作和审批节点状态字典数据
-- 1. 审批动作字典数据
-- 审批动作字典类型
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (1950000000000000001, '000000', '审批动作', 'cw_approve_action', 1, now(), '财务项目申报审批动作字典');
-- 审批动作字典数据
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000001001, '000000', 1, '通过', 'approve', 'cw_approve_action', '', 'success', 'N', 1, now(), '审批通过');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000001002, '000000', 2, '拒绝', 'reject', 'cw_approve_action', '', 'danger', 'N', 1, now(), '审批拒绝');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000001003, '000000', 3, '退回', 'return', 'cw_approve_action', '', 'warning', 'N', 1, now(), '退回修改');
-- 2. 审批节点状态字典数据
-- 审批节点状态字典类型
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (1950000000000000002, '000000', '审批节点状态', 'cw_node_status', 1, now(), '财务项目申报审批节点状态字典');
-- 审批节点状态字典数据
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002001, '000000', 1, '等待审批', 'waiting', 'cw_node_status', '', 'info', 'N', 1, now(), '等待审批');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002002, '000000', 2, '审批中', 'approving', 'cw_node_status', '', 'primary', 'N', 1, now(), '正在审批');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002003, '000000', 3, '已通过', 'approved', 'cw_node_status', '', 'success', 'N', 1, now(), '审批通过');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002004, '000000', 4, '已拒绝', 'rejected', 'cw_node_status', '', 'danger', 'N', 1, now(), '审批拒绝');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002005, '000000', 5, '已退回', 'returned', 'cw_node_status', '', 'warning', 'N', 1, now(), '已退回');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002006, '000000', 6, '未开始', 'pending', 'cw_node_status', '', 'info', 'N', 1, now(), '未开始');
COMMIT;
-- 字典数据创建完成
@@ -1,84 +0,0 @@
-- 财务项目申报部门审批功能菜单创建脚本(仅创建菜单,不赋权)
-- 日期: 2025-01-26
-- 说明: 在"项目申报管理"(6000)下创建部门审批功能菜单
-- 1. 部门审批管理菜单 (在项目申报管理下新增子菜单)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6200, '部门审批管理', 6000, 2, 'deptApproval', 'cw/deptApproval/index', '', 1, 0, 'C', '0', '0', 'cw:approval:dept', 'ele-Checked', 1, now(), 1, now(), '财务项目申报部门审批管理');
-- 2. 部门审批管理功能按钮
-- 查询
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6201, '部门审批查询', 6200, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:dept', '#', 1, now(), 1, now(), '');
-- 审批操作
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6202, '部门审批操作', 6200, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:process', '#', 1, now(), 1, now(), '');
-- 审批详情查看
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6203, '审批详情查看', 6200, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 1, now(), 1, now(), '');
-- 3. 审批记录管理菜单 (给管理员查看所有审批记录)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6210, '审批记录管理', 6000, 3, 'approvalRecord', 'cw/approvalRecord/index', '', 1, 0, 'C', '0', '0', 'cw:approval:list', 'ele-List', 1, now(), 1, now(), '财务项目申报审批记录管理');
-- 4. 审批记录管理功能按钮
-- 查询
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6211, '审批记录查询', 6210, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:list', '#', 1, now(), 1, now(), '');
-- 导出
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6212, '审批记录导出', 6210, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:export', '#', 1, now(), 1, now(), '');
-- 详情
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6213, '审批记录详情', 6210, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 1, now(), 1, now(), '');
-- 5. 创建审批流程权限 (系统管理员)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES (6214, '创建审批流程', 6210, 4, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:create', '#', 1, now(), 1, now(), '');
-- 6. 审批动作字典数据
-- 审批动作字典类型
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (1950000000000000001, '000000', '审批动作', 'cw_approve_action', 1, now(), '财务项目申报审批动作字典');
-- 审批动作字典数据
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000001001, '000000', 1, '通过', 'approve', 'cw_approve_action', '', 'success', 'N', 1, now(), '审批通过');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000001002, '000000', 2, '拒绝', 'reject', 'cw_approve_action', '', 'danger', 'N', 1, now(), '审批拒绝');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000001003, '000000', 3, '退回', 'return', 'cw_approve_action', '', 'warning', 'N', 1, now(), '退回修改');
-- 7. 审批节点状态字典数据
-- 审批节点状态字典类型
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (1950000000000000002, '000000', '审批节点状态', 'cw_node_status', 1, now(), '财务项目申报审批节点状态字典');
-- 审批节点状态字典数据
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002001, '000000', 1, '等待审批', 'waiting', 'cw_node_status', '', 'info', 'N', 1, now(), '等待审批');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002002, '000000', 2, '审批中', 'approving', 'cw_node_status', '', 'primary', 'N', 1, now(), '正在审批');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002003, '000000', 3, '已通过', 'approved', 'cw_node_status', '', 'success', 'N', 1, now(), '审批通过');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002004, '000000', 4, '已拒绝', 'rejected', 'cw_node_status', '', 'danger', 'N', 1, now(), '审批拒绝');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002005, '000000', 5, '已退回', 'returned', 'cw_node_status', '', 'warning', 'N', 1, now(), '已退回');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (1950000000000002006, '000000', 6, '未开始', 'pending', 'cw_node_status', '', 'info', 'N', 1, now(), '未开始');
COMMIT;
-- 菜单创建完成,等待后续手动赋权操作
@@ -1,94 +0,0 @@
-- 财务项目申报部门审批功能菜单权限配置
-- 日期: 2025-01-26
-- 1. 部门审批管理菜单 (在财务项目申报管理下新增子菜单)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6200, '部门审批管理', 6100, 2, 'deptApproval', 'cw/deptApproval/index', '', 1, 0, 'C', '0', '0', 'cw:approval:dept', 'ele-Checked', 1, sysdate(), 1, sysdate(), '财务项目申报部门审批管理', '000000');
-- 2. 部门审批管理功能按钮
-- 查询
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6201, '部门审批查询', 6200, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:dept', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 审批操作
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6202, '部门审批操作', 6200, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:process', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 审批详情查看
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6203, '审批详情查看', 6200, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 3. 审批记录管理菜单 (给管理员查看所有审批记录)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6210, '审批记录管理', 6100, 3, 'approvalRecord', 'cw/approvalRecord/index', '', 1, 0, 'C', '0', '0', 'cw:approval:list', 'ele-List', 1, sysdate(), 1, sysdate(), '财务项目申报审批记录管理', '000000');
-- 4. 审批记录管理功能按钮
-- 查询
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6211, '审批记录查询', 6210, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:list', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 导出
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6212, '审批记录导出', 6210, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:export', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 详情
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6213, '审批记录详情', 6210, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 5. 创建审批流程权限 (系统管理员)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6214, '创建审批流程', 6210, 4, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:create', '#', 1, sysdate(), 1, sysdate(), '', '000000');
-- 6. 将新菜单分配给超级管理员角色 (role_id = 1)
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6200);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6201);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6202);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6203);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6210);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6211);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6212);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6213);
INSERT INTO sys_role_menu(role_id, menu_id) VALUES (1, 6214);
-- 7. 审批动作字典数据
-- 检查是否已存在审批动作字典类型
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (nextval('sys_dict_type_seq'), '000000', '审批动作', 'cw_approve_action', 1, sysdate(), '财务项目申报审批动作字典')
ON DUPLICATE KEY UPDATE dict_name = VALUES(dict_name);
-- 审批动作字典数据
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 1, '通过', 'approve', 'cw_approve_action', '', 'success', 'N', 1, sysdate(), '审批通过');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 2, '拒绝', 'reject', 'cw_approve_action', '', 'danger', 'N', 1, sysdate(), '审批拒绝');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 3, '退回', 'return', 'cw_approve_action', '', 'warning', 'N', 1, sysdate(), '退回修改');
-- 8. 审批节点状态字典数据
-- 检查是否已存在审批节点状态字典类型
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (nextval('sys_dict_type_seq'), '000000', '审批节点状态', 'cw_node_status', 1, sysdate(), '财务项目申报审批节点状态字典')
ON DUPLICATE KEY UPDATE dict_name = VALUES(dict_name);
-- 审批节点状态字典数据
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 1, '等待审批', 'waiting', 'cw_node_status', '', 'info', 'N', 1, sysdate(), '等待审批');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 2, '审批中', 'approving', 'cw_node_status', '', 'primary', 'N', 1, sysdate(), '正在审批');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 3, '已通过', 'approved', 'cw_node_status', '', 'success', 'N', 1, sysdate(), '审批通过');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 4, '已拒绝', 'rejected', 'cw_node_status', '', 'danger', 'N', 1, sysdate(), '审批拒绝');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 5, '已退回', 'returned', 'cw_node_status', '', 'warning', 'N', 1, sysdate(), '已退回');
INSERT INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (nextval('sys_dict_data_seq'), '000000', 6, '未开始', 'pending', 'cw_node_status', '', 'info', 'N', 1, sysdate(), '未开始');
COMMIT;
@@ -1,447 +0,0 @@
-- ==========================================
-- 财务项目预算审批系统 - 数据库创建脚本
-- 版本: 1.0
-- 创建时间: 2025-01-26
-- 表名前缀: cw_ (财务模块)
-- ==========================================
-- 设置字符集
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ==========================================
-- 1. 创建核心数据表
-- ==========================================
-- 1.1 项目预算申报表
DROP TABLE IF EXISTS `cw_project_application`;
CREATE TABLE `cw_project_application` (
`id` bigint(20) NOT NULL COMMENT '主键ID(雪花ID)',
`application_no` varchar(50) NOT NULL COMMENT '申报编号(自动生成,格式:CW-YYYY-MMDD-XXXX)',
-- 项目基本信息
`project_name` varchar(200) NOT NULL COMMENT '项目名称',
`principal` varchar(50) NOT NULL COMMENT '项目负责人',
`contact_phone` varchar(20) COMMENT '联系电话',
`start_date` date COMMENT '项目实施开始时间',
`end_date` date COMMENT '项目实施结束时间',
-- 项目特性
`target_sme` tinyint(1) DEFAULT 0 COMMENT '是否面向中小企业(0-否 1-是)',
`target_micro_sme` tinyint(1) DEFAULT 0 COMMENT '是否面向小微企业(0-否 1-是)',
`import_equipment` tinyint(1) DEFAULT 0 COMMENT '是否采购进口设备(0-否 1-是)',
-- 项目详细内容
`application_reason` text COMMENT '项目申请原因(包括设立依据、可行性及必要性)',
`construction_content` text COMMENT '项目建设内容',
`total_performance_goal` text COMMENT '绩效总目标',
`current_year_goal` text COMMENT '当年绩效目标',
-- 资金来源汇总(万元)
-- 项目总投资
`total_investment` decimal(15,2) DEFAULT 0.00 COMMENT '项目总投资-合计(万元)',
`total_municipal_fiscal` decimal(15,2) DEFAULT 0.00 COMMENT '项目总投资-市财政资金(万元)',
`total_self_funding` decimal(15,2) DEFAULT 0.00 COMMENT '项目总投资-单位自筹(万元)',
`total_other_funding` decimal(15,2) DEFAULT 0.00 COMMENT '项目总投资-其他(万元)',
-- 年度预计执行数
`current_year_total` decimal(15,2) DEFAULT 0.00 COMMENT '年度预计执行数-合计(万元)',
`current_year_municipal` decimal(15,2) DEFAULT 0.00 COMMENT '年度预计执行数-市财政资金(万元)',
`current_year_self` decimal(15,2) DEFAULT 0.00 COMMENT '年度预计执行数-单位自筹(万元)',
`current_year_other` decimal(15,2) DEFAULT 0.00 COMMENT '年度预计执行数-其他(万元)',
-- 下年度申请资金
`next_year_total` decimal(15,2) DEFAULT 0.00 COMMENT '下年度申请资金-合计(万元)',
`next_year_municipal` decimal(15,2) DEFAULT 0.00 COMMENT '下年度申请资金-市财政资金(万元)',
`next_year_self` decimal(15,2) DEFAULT 0.00 COMMENT '下年度申请资金-单位自筹(万元)',
`next_year_other` decimal(15,2) DEFAULT 0.00 COMMENT '下年度申请资金-其他(万元)',
-- 以后年度安排
`future_year_total` decimal(15,2) DEFAULT 0.00 COMMENT '以后年度安排-合计(万元)',
`future_year_municipal` decimal(15,2) DEFAULT 0.00 COMMENT '以后年度安排-市财政资金(万元)',
`future_year_self` decimal(15,2) DEFAULT 0.00 COMMENT '以后年度安排-单位自筹(万元)',
`future_year_other` decimal(15,2) DEFAULT 0.00 COMMENT '以后年度安排-其他(万元)',
-- 申请人信息
`applicant_id` bigint(20) NOT NULL COMMENT '申请人ID',
`applicant_name` varchar(50) NOT NULL COMMENT '申请人姓名',
`department_id` bigint(20) NOT NULL COMMENT '所属部门ID',
`department_name` varchar(100) NOT NULL COMMENT '所属部门名称',
-- 审批相关
`approval_status` varchar(20) DEFAULT 'draft' COMMENT '审批状态(draft-草稿 submitted-已提交 approving-审批中 approved-已通过 rejected-已拒绝 returned-已退回 cancelled-已取消)',
`submit_time` datetime COMMENT '提交时间',
`approve_time` datetime COMMENT '审批完成时间',
`year` int(11) NOT NULL COMMENT '预算年度',
-- 系统字段
`remark` varchar(500) COMMENT '备注',
`create_by` bigint(20) COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_dept` bigint(20) COMMENT '创建部门',
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-正常 1-删除)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_application_no` (`application_no`, `tenant_id`, `deleted`),
KEY `idx_applicant_id` (`applicant_id`),
KEY `idx_department_id` (`department_id`),
KEY `idx_status_year` (`approval_status`, `year`),
KEY `idx_create_time` (`create_time`),
KEY `idx_submit_time` (`submit_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目预算申报表';
-- 1.2 审批人配置表
DROP TABLE IF EXISTS `cw_approver_config`;
CREATE TABLE `cw_approver_config` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`config_name` varchar(100) NOT NULL COMMENT '配置名称',
`config_type` varchar(20) NOT NULL COMMENT '配置类型(global-全局配置 dept-按部门配置 amount-按金额配置)',
-- 配置条件
`dept_id` bigint(20) COMMENT '部门ID(按部门配置时使用)',
`dept_name` varchar(100) COMMENT '部门名称(按部门配置时使用)',
`min_amount` decimal(15,2) COMMENT '最小金额(按金额配置时使用,万元)',
`max_amount` decimal(15,2) COMMENT '最大金额(按金额配置时使用,万元)',
-- 3个固定审批节点的审批人
-- 部门审批(第1节点)
`dept_approver_id` bigint(20) COMMENT '部门审批人ID',
`dept_approver_name` varchar(50) COMMENT '部门审批人姓名',
`dept_delegate_id` bigint(20) COMMENT '部门代理审批人ID',
`dept_delegate_name` varchar(50) COMMENT '部门代理审批人姓名',
`dept_delegate_start_time` datetime COMMENT '部门代理开始时间',
`dept_delegate_end_time` datetime COMMENT '部门代理结束时间',
-- 财务审批(第2节点)
`finance_approver_id` bigint(20) COMMENT '财务审批人ID',
`finance_approver_name` varchar(50) COMMENT '财务审批人姓名',
`finance_delegate_id` bigint(20) COMMENT '财务代理审批人ID',
`finance_delegate_name` varchar(50) COMMENT '财务代理审批人姓名',
`finance_delegate_start_time` datetime COMMENT '财务代理开始时间',
`finance_delegate_end_time` datetime COMMENT '财务代理结束时间',
-- 领导审批(第3节点)
`leader_approver_id` bigint(20) COMMENT '领导审批人ID',
`leader_approver_name` varchar(50) COMMENT '领导审批人姓名',
`leader_delegate_id` bigint(20) COMMENT '领导代理审批人ID',
`leader_delegate_name` varchar(50) COMMENT '领导代理审批人姓名',
`leader_delegate_start_time` datetime COMMENT '领导代理开始时间',
`leader_delegate_end_time` datetime COMMENT '领导代理结束时间',
-- 配置状态
`priority` int(11) DEFAULT 0 COMMENT '优先级(数字越大优先级越高,用于多个配置冲突时的选择)',
`status` char(1) DEFAULT '1' COMMENT '状态(0-禁用 1-启用)',
`effective_start_time` datetime COMMENT '生效开始时间',
`effective_end_time` datetime COMMENT '生效结束时间',
-- 系统字段
`remark` varchar(500) COMMENT '备注',
`create_by` bigint(20) COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_dept` bigint(20) COMMENT '创建部门',
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-正常 1-删除)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
PRIMARY KEY (`id`),
KEY `idx_config_type` (`config_type`),
KEY `idx_dept_id` (`dept_id`),
KEY `idx_amount_range` (`min_amount`, `max_amount`),
KEY `idx_status_priority` (`status`, `priority`),
KEY `idx_effective_time` (`effective_start_time`, `effective_end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批人配置表';
-- 1.3 审批记录表
DROP TABLE IF EXISTS `cw_approval_record`;
CREATE TABLE `cw_approval_record` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`application_id` bigint(20) NOT NULL COMMENT '申报ID',
`application_no` varchar(50) NOT NULL COMMENT '申报编号',
-- 节点信息
`node_code` varchar(20) NOT NULL COMMENT '节点编码(dept_approve-部门审批 finance_approve-财务审批 leader_approve-领导审批)',
`node_name` varchar(50) NOT NULL COMMENT '节点名称',
`node_order` int(11) NOT NULL COMMENT '节点顺序(1-部门审批 2-财务审批 3-领导审批)',
-- 审批人信息
`approver_id` bigint(20) NOT NULL COMMENT '审批人ID',
`approver_name` varchar(50) NOT NULL COMMENT '审批人姓名',
`is_delegate` tinyint(1) DEFAULT 0 COMMENT '是否代理审批(0-否 1-是)',
`original_approver_id` bigint(20) COMMENT '原审批人ID(代理时使用)',
`original_approver_name` varchar(50) COMMENT '原审批人姓名(代理时使用)',
-- 审批状态和结果
`node_status` varchar(20) NOT NULL COMMENT '节点状态(waiting-等待审批 approving-审批中 approved-已通过 rejected-已拒绝 returned-已退回)',
`approve_action` varchar(20) COMMENT '审批动作(approve-通过 reject-拒绝 return-退回)',
`approve_opinion` text COMMENT '审批意见',
-- 时间记录
`start_time` datetime NOT NULL COMMENT '节点开始时间',
`end_time` datetime COMMENT '节点结束时间',
`expect_finish_time` datetime COMMENT '期望完成时间',
`is_overtime` tinyint(1) DEFAULT 0 COMMENT '是否超时(0-否 1-是)',
-- 审批配置记录
`config_id` bigint(20) COMMENT '使用的审批配置ID',
`config_snapshot` json COMMENT '审批配置快照(JSON格式,记录当时的配置)',
-- 系统字段
`create_by` bigint(20) COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_dept` bigint(20) COMMENT '创建部门',
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-正常 1-删除)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
PRIMARY KEY (`id`),
KEY `idx_application_id` (`application_id`),
KEY `idx_application_no` (`application_no`),
KEY `idx_approver_id` (`approver_id`),
KEY `idx_node_status` (`node_status`),
KEY `idx_node_order` (`node_order`),
KEY `idx_start_time` (`start_time`),
KEY `idx_expect_finish_time` (`expect_finish_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批记录表';
-- 1.4 项目支出明细表
DROP TABLE IF EXISTS `cw_expenditure_detail`;
CREATE TABLE `cw_expenditure_detail` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`application_id` bigint(20) NOT NULL COMMENT '申报ID',
`application_no` varchar(50) NOT NULL COMMENT '申报编号',
-- 支出明细信息
`category_code` varchar(50) COMMENT '支出科目编码(关联字典)',
`category` varchar(100) NOT NULL COMMENT '支出科目名称',
`amount` decimal(15,2) NOT NULL COMMENT '金额(万元)',
`calculation` text COMMENT '测算依据',
`description` varchar(500) COMMENT '详细说明',
-- 时间分配
`current_year_amount` decimal(15,2) DEFAULT 0.00 COMMENT '当年执行金额(万元)',
`next_year_amount` decimal(15,2) DEFAULT 0.00 COMMENT '下年度金额(万元)',
`future_year_amount` decimal(15,2) DEFAULT 0.00 COMMENT '以后年度金额(万元)',
-- 资金来源分配
`municipal_fiscal_amount` decimal(15,2) DEFAULT 0.00 COMMENT '市财政资金(万元)',
`self_funding_amount` decimal(15,2) DEFAULT 0.00 COMMENT '单位自筹(万元)',
`other_funding_amount` decimal(15,2) DEFAULT 0.00 COMMENT '其他资金(万元)',
-- 排序和状态
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
`status` char(1) DEFAULT '1' COMMENT '状态(0-删除 1-正常)',
-- 系统字段
`create_by` bigint(20) COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_dept` bigint(20) COMMENT '创建部门',
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-正常 1-删除)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
PRIMARY KEY (`id`),
KEY `idx_application_id` (`application_id`),
KEY `idx_application_no` (`application_no`),
KEY `idx_category_code` (`category_code`),
KEY `idx_sort_order` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目支出明细表';
-- 1.5 绩效指标表
DROP TABLE IF EXISTS `cw_performance_indicator`;
CREATE TABLE `cw_performance_indicator` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`application_id` bigint(20) NOT NULL COMMENT '申报ID',
`application_no` varchar(50) NOT NULL COMMENT '申报编号',
-- 指标信息
`indicator_type` varchar(20) NOT NULL COMMENT '指标类型(quantity-数量指标 quality-质量指标 timeliness-时效指标 cost-成本指标 economic-经济效益指标 social-社会效益指标 environmental-环境效益指标 satisfaction-满意度指标)',
`indicator_type_name` varchar(50) NOT NULL COMMENT '指标类型名称',
`goal_name` varchar(200) NOT NULL COMMENT '绩效目标名称',
`target_value` varchar(100) NOT NULL COMMENT '目标值',
`weight_percentage` decimal(5,2) NOT NULL COMMENT '分值占比(%)',
`description` varchar(500) COMMENT '详细说明',
-- 指标属性
`unit` varchar(20) COMMENT '计量单位',
`measurement_method` varchar(200) COMMENT '测量方法',
`data_source` varchar(200) COMMENT '数据来源',
`is_quantitative` tinyint(1) DEFAULT 1 COMMENT '是否定量指标(0-定性 1-定量)',
-- 排序和状态
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
`status` char(1) DEFAULT '1' COMMENT '状态(0-删除 1-正常)',
-- 系统字段
`create_by` bigint(20) COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_dept` bigint(20) COMMENT '创建部门',
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-正常 1-删除)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
PRIMARY KEY (`id`),
KEY `idx_application_id` (`application_id`),
KEY `idx_application_no` (`application_no`),
KEY `idx_indicator_type` (`indicator_type`),
KEY `idx_sort_order` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='绩效指标表';
-- 1.6 申报附件表
DROP TABLE IF EXISTS `cw_application_attachment`;
CREATE TABLE `cw_application_attachment` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`application_id` bigint(20) NOT NULL COMMENT '申报ID',
`application_no` varchar(50) NOT NULL COMMENT '申报编号',
-- 文件信息
`file_name` varchar(255) NOT NULL COMMENT '文件名称',
`file_original_name` varchar(255) NOT NULL COMMENT '原始文件名',
`file_type` varchar(20) COMMENT '文件类型(pdf,doc,docx,xls,xlsx,jpg,png等)',
`file_size` bigint(20) COMMENT '文件大小(字节)',
`file_path` varchar(500) COMMENT '文件存储路径',
`file_url` varchar(500) COMMENT '文件访问URL',
-- 附件分类
`attachment_type` varchar(50) COMMENT '附件类型(feasibility_report-可行性研究报告 implementation_plan-项目实施方案 budget_calculation-资金测算明细表 other_materials-其他相关证明材料)',
`attachment_type_name` varchar(100) COMMENT '附件类型名称',
`description` varchar(500) COMMENT '文件说明',
-- 上传信息
`upload_user_id` bigint(20) NOT NULL COMMENT '上传人ID',
`upload_user_name` varchar(50) NOT NULL COMMENT '上传人姓名',
`upload_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
-- 排序和状态
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
`status` char(1) DEFAULT '1' COMMENT '状态(0-删除 1-正常)',
-- 系统字段
`create_by` bigint(20) COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint(20) COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_dept` bigint(20) COMMENT '创建部门',
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-正常 1-删除)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
PRIMARY KEY (`id`),
KEY `idx_application_id` (`application_id`),
KEY `idx_application_no` (`application_no`),
KEY `idx_attachment_type` (`attachment_type`),
KEY `idx_upload_time` (`upload_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='申报附件表';
-- ==========================================
-- 2. 插入字典数据
-- ==========================================
-- 2.1 字典类型
INSERT INTO `sys_dict_type` VALUES
(3000001, '项目特性选项', 'cw_project_feature', '0', NULL, '财务预算-项目特性选项', now(), NULL, NULL, '项目特性相关选项'),
(3000002, '绩效指标类型', 'cw_performance_type', '0', NULL, '财务预算-绩效指标类型', now(), NULL, NULL, '8大绩效指标类型'),
(3000003, '审批状态', 'cw_approval_status', '0', NULL, '财务预算-审批状态', now(), NULL, NULL, '审批状态分类'),
(3000004, '审批人配置类型', 'cw_approver_config_type', '0', NULL, '财务预算-审批人配置类型', now(), NULL, NULL, '审批人配置类型分类'),
(3000005, '审批节点', 'cw_approval_node', '0', NULL, '财务预算-审批节点', now(), NULL, NULL, '固定3节点审批流程'),
(3000006, '资金来源类型', 'cw_funding_source', '0', NULL, '财务预算-资金来源类型', now(), NULL, NULL, '资金来源分类'),
(3000007, '支出科目', 'cw_expenditure_category', '0', NULL, '财务预算-支出科目', now(), NULL, NULL, '项目支出科目分类'),
(3000008, '附件类型', 'cw_attachment_type', '0', NULL, '财务预算-附件类型', now(), NULL, NULL, '申报附件类型分类');
-- 2.2 字典数据
INSERT INTO `sys_dict_data` VALUES
-- 项目特性选项
(3000011, 0, '是', '1', 'cw_project_feature', '', '', 'N', '0', '0', '', '是', now(), NULL, NULL, NULL),
(3000012, 1, '否', '0', 'cw_project_feature', '', '', 'N', '0', '0', '', '否', now(), NULL, NULL, NULL),
-- 绩效指标类型(8大维度)
(3000021, 0, '数量指标', 'quantity', 'cw_performance_type', '', '', 'N', '0', '0', '', '数量指标', now(), NULL, NULL, NULL),
(3000022, 1, '质量指标', 'quality', 'cw_performance_type', '', '', 'N', '0', '0', '', '质量指标', now(), NULL, NULL, NULL),
(3000023, 2, '时效指标', 'timeliness', 'cw_performance_type', '', '', 'N', '0', '0', '', '时效指标', now(), NULL, NULL, NULL),
(3000024, 3, '成本指标', 'cost', 'cw_performance_type', '', '', 'N', '0', '0', '', '成本指标', now(), NULL, NULL, NULL),
(3000025, 4, '经济效益指标', 'economic', 'cw_performance_type', '', '', 'N', '0', '0', '', '经济效益指标', now(), NULL, NULL, NULL),
(3000026, 5, '社会效益指标', 'social', 'cw_performance_type', '', '', 'N', '0', '0', '', '社会效益指标', now(), NULL, NULL, NULL),
(3000027, 6, '环境效益指标', 'environmental', 'cw_performance_type', '', '', 'N', '0', '0', '', '环境效益指标', now(), NULL, NULL, NULL),
(3000028, 7, '满意度指标', 'satisfaction', 'cw_performance_type', '', '', 'N', '0', '0', '', '满意度指标', now(), NULL, NULL, NULL),
-- 审批状态
(3000031, 0, '草稿', 'draft', 'cw_approval_status', '', '', 'N', '0', '0', '', '草稿状态', now(), NULL, NULL, NULL),
(3000032, 1, '已提交', 'submitted', 'cw_approval_status', '', '', 'N', '0', '0', '', '已提交审批', now(), NULL, NULL, NULL),
(3000033, 2, '审批中', 'approving', 'cw_approval_status', '', 'blue', 'N', '0', '0', '', '审批中', now(), NULL, NULL, NULL),
(3000034, 3, '已通过', 'approved', 'cw_approval_status', '', 'green', 'N', '0', '0', '', '审批通过', now(), NULL, NULL, NULL),
(3000035, 4, '已拒绝', 'rejected', 'cw_approval_status', '', 'red', 'N', '0', '0', '', '审批拒绝', now(), NULL, NULL, NULL),
(3000036, 5, '已退回', 'returned', 'cw_approval_status', '', 'orange', 'N', '0', '0', '', '退回修改', now(), NULL, NULL, NULL),
(3000037, 6, '已取消', 'cancelled', 'cw_approval_status', '', 'gray', 'N', '0', '0', '', '取消申报', now(), NULL, NULL, NULL),
-- 审批人配置类型
(3000041, 0, '全局配置', 'global', 'cw_approver_config_type', '', '', 'N', '0', '0', '', '全局统一配置', now(), NULL, NULL, NULL),
(3000042, 1, '部门配置', 'dept', 'cw_approver_config_type', '', '', 'N', '0', '0', '', '按部门配置', now(), NULL, NULL, NULL),
(3000043, 2, '金额配置', 'amount', 'cw_approver_config_type', '', '', 'N', '0', '0', '', '按金额阈值配置', now(), NULL, NULL, NULL),
-- 审批节点(固定3节点)
(3000051, 1, '部门审批', 'dept_approve', 'cw_approval_node', '', '', 'N', '0', '0', '', '部门负责人审批', now(), NULL, NULL, NULL),
(3000052, 2, '财务审批', 'finance_approve', 'cw_approval_node', '', '', 'N', '0', '0', '', '财务部门审批', now(), NULL, NULL, NULL),
(3000053, 3, '领导审批', 'leader_approve', 'cw_approval_node', '', '', 'N', '0', '0', '', '校领导审批', now(), NULL, NULL, NULL),
-- 资金来源类型
(3000061, 0, '市财政资金', 'municipal_fiscal', 'cw_funding_source', '', '', 'N', '0', '0', '', '市财政资金', now(), NULL, NULL, NULL),
(3000062, 1, '单位自筹', 'self_funding', 'cw_funding_source', '', '', 'N', '0', '0', '', '单位自筹资金', now(), NULL, NULL, NULL),
(3000063, 2, '其他资金', 'other_funding', 'cw_funding_source', '', '', 'N', '0', '0', '', '其他来源资金', now(), NULL, NULL, NULL),
-- 支出科目
(3000071, 0, '人员经费', 'personnel_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '人员相关费用', now(), NULL, NULL, NULL),
(3000072, 1, '设备费', 'equipment_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '设备采购费用', now(), NULL, NULL, NULL),
(3000073, 2, '材料费', 'material_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '材料采购费用', now(), NULL, NULL, NULL),
(3000074, 3, '差旅费', 'travel_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '差旅相关费用', now(), NULL, NULL, NULL),
(3000075, 4, '培训费', 'training_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '培训相关费用', now(), NULL, NULL, NULL),
(3000076, 5, '咨询费', 'consulting_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '咨询服务费用', now(), NULL, NULL, NULL),
(3000077, 6, '运行维护费', 'maintenance_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '运行维护费用', now(), NULL, NULL, NULL),
(3000078, 7, '其他费用', 'other_expense', 'cw_expenditure_category', '', '', 'N', '0', '0', '', '其他类型费用', now(), NULL, NULL, NULL),
-- 附件类型
(3000081, 0, '可行性研究报告', 'feasibility_report', 'cw_attachment_type', '', '', 'N', '0', '0', '', '项目可行性研究报告', now(), NULL, NULL, NULL),
(3000082, 1, '项目实施方案', 'implementation_plan', 'cw_attachment_type', '', '', 'N', '0', '0', '', '项目实施方案文档', now(), NULL, NULL, NULL),
(3000083, 2, '资金测算明细表', 'budget_calculation', 'cw_attachment_type', '', '', 'N', '0', '0', '', '资金测算明细表', now(), NULL, NULL, NULL),
(3000084, 3, '其他相关证明材料', 'other_materials', 'cw_attachment_type', '', '', 'N', '0', '0', '', '其他相关证明材料', now(), NULL, NULL, NULL);
-- ==========================================
-- 3. 创建复合索引优化
-- ==========================================
-- 针对常用查询的复合索引
ALTER TABLE `cw_project_application`
ADD INDEX `idx_dept_status_year` (`department_id`, `approval_status`, `year`);
ALTER TABLE `cw_approval_record`
ADD INDEX `idx_approver_status_order` (`approver_id`, `node_status`, `node_order`);
ALTER TABLE `cw_approval_record`
ADD INDEX `idx_app_node_status` (`application_id`, `node_code`, `node_status`);
-- ==========================================
-- 4. 插入初始化数据
-- ==========================================
-- 插入默认全局审批人配置
INSERT INTO `cw_approver_config` (
`id`, `config_name`, `config_type`, `dept_approver_id`, `dept_approver_name`,
`finance_approver_id`, `finance_approver_name`, `leader_approver_id`, `leader_approver_name`,
`priority`, `status`, `create_by`, `create_time`, `tenant_id`
) VALUES (
1, '默认全局审批配置', 'global', 1, '系统管理员',
1, '系统管理员', 1, '系统管理员',
100, '1', 1, now(), '000000'
);
-- 恢复外键检查
SET FOREIGN_KEY_CHECKS = 1;
-- ==========================================
-- 脚本执行完成
-- ==========================================
SELECT '财务项目预算审批系统数据库创建完成!' AS result;
-131
View File
@@ -1,131 +0,0 @@
-- ==========================================
-- 财务项目预算审批系统 - 菜单配置脚本
-- 版本: 1.0
-- 创建时间: 2025-01-26
-- 模块前缀: cw_ (财务模块)
-- 菜单ID范围: 5000-5999
-- ==========================================
-- ==========================================
-- 菜单结构设计:
-- 5000 - 财务管理 (一级菜单)
-- 5100 - 项目预算审批 (二级菜单)
-- 5110 - 预算申报管理 (三级菜单)
-- 5111-5117 - 功能按钮
-- 5120 - 审批人配置 (三级菜单)
-- 5121-5125 - 功能按钮
-- 5130 - 审批记录查询 (三级菜单)
-- 5131-5135 - 功能按钮
-- 5140 - 支出明细管理 (三级菜单)
-- 5141-5145 - 功能按钮
-- 5150 - 绩效指标管理 (三级菜单)
-- 5151-5155 - 功能按钮
-- 5160 - 附件管理 (三级菜单)
-- 5161-5165 - 功能按钮
-- ==========================================
-- 1. 一级菜单:财务管理
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5000, '财务管理', 0, 10, 'finance', NULL, '', 1, 0, 'M', '0', '0', '', 'money', 103, 1, now(), NULL, NULL, '财务管理根菜单', '000000');
-- 2. 二级菜单:项目预算审批
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5100, '项目预算审批', 5000, 1, 'budget-approval', NULL, '', 1, 0, 'M', '0', '0', '', 'file-text', 103, 1, now(), NULL, NULL, '项目预算审批管理', '000000');
-- ==========================================
-- 3. 三级菜单:预算申报管理
-- ==========================================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5110, '预算申报管理', 5100, 1, 'application', 'cw/budgetApproval/application/index', '', 1, 0, 'C', '0', '0', 'cw:application:list', 'form', 103, 1, now(), NULL, NULL, '预算申报管理页面', '000000');
-- 预算申报管理 - 功能按钮
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5111, '预算申报查询', 5110, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:query', '#', 103, 1, now(), NULL, NULL, '预算申报查询权限', '000000'),
(5112, '预算申报详情', 5110, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:detail', '#', 103, 1, now(), NULL, NULL, '预算申报详情权限', '000000'),
(5113, '预算申报新增', 5110, 3, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:add', '#', 103, 1, now(), NULL, NULL, '预算申报新增权限', '000000'),
(5114, '预算申报修改', 5110, 4, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:edit', '#', 103, 1, now(), NULL, NULL, '预算申报修改权限', '000000'),
(5115, '预算申报删除', 5110, 5, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:remove', '#', 103, 1, now(), NULL, NULL, '预算申报删除权限', '000000'),
(5116, '预算申报导出', 5110, 6, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:export', '#', 103, 1, now(), NULL, NULL, '预算申报导出权限', '000000'),
(5117, '预算申报提交', 5110, 7, '', '', '', 1, 0, 'F', '0', '0', 'cw:application:submit', '#', 103, 1, now(), NULL, NULL, '预算申报提交审批权限', '000000');
-- ==========================================
-- 4. 三级菜单:审批人配置
-- ==========================================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5120, '审批人配置', 5100, 2, 'approver-config', 'cw/budgetApproval/approverConfig/index', '', 1, 0, 'C', '0', '0', 'cw:approverConfig:list', 'peoples', 103, 1, now(), NULL, NULL, '审批人配置页面', '000000');
-- 审批人配置 - 功能按钮
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5121, '审批人查询', 5120, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:approverConfig:query', '#', 103, 1, now(), NULL, NULL, '审批人配置查询权限', '000000'),
(5122, '审批人详情', 5120, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:approverConfig:detail', '#', 103, 1, now(), NULL, NULL, '审批人配置详情权限', '000000'),
(5123, '审批人新增', 5120, 3, '', '', '', 1, 0, 'F', '0', '0', 'cw:approverConfig:add', '#', 103, 1, now(), NULL, NULL, '审批人配置新增权限', '000000'),
(5124, '审批人修改', 5120, 4, '', '', '', 1, 0, 'F', '0', '0', 'cw:approverConfig:edit', '#', 103, 1, now(), NULL, NULL, '审批人配置修改权限', '000000'),
(5125, '审批人删除', 5120, 5, '', '', '', 1, 0, 'F', '0', '0', 'cw:approverConfig:remove', '#', 103, 1, now(), NULL, NULL, '审批人配置删除权限', '000000');
-- ==========================================
-- 5. 三级菜单:审批记录查询
-- ==========================================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5130, '审批记录查询', 5100, 3, 'approval-record', 'cw/budgetApproval/approvalRecord/index', '', 1, 0, 'C', '0', '0', 'cw:approvalRecord:list', 'documentation', 103, 1, now(), NULL, NULL, '审批记录查询页面', '000000');
-- 审批记录查询 - 功能按钮
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5131, '审批记录查询', 5130, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:approvalRecord:query', '#', 103, 1, now(), NULL, NULL, '审批记录查询权限', '000000'),
(5132, '审批记录详情', 5130, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:approvalRecord:detail', '#', 103, 1, now(), NULL, NULL, '审批记录详情权限', '000000'),
(5133, '审批操作', 5130, 3, '', '', '', 1, 0, 'F', '0', '0', 'cw:approvalRecord:approve', '#', 103, 1, now(), NULL, NULL, '审批操作权限', '000000'),
(5134, '退回操作', 5130, 4, '', '', '', 1, 0, 'F', '0', '0', 'cw:approvalRecord:return', '#', 103, 1, now(), NULL, NULL, '退回操作权限', '000000'),
(5135, '审批记录导出', 5130, 5, '', '', '', 1, 0, 'F', '0', '0', 'cw:approvalRecord:export', '#', 103, 1, now(), NULL, NULL, '审批记录导出权限', '000000');
-- ==========================================
-- 6. 三级菜单:支出明细管理
-- ==========================================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5140, '支出明细管理', 5100, 4, 'expenditure-detail', 'cw/budgetApproval/expenditureDetail/index', '', 1, 0, 'C', '0', '0', 'cw:expenditureDetail:list', 'money', 103, 1, now(), NULL, NULL, '支出明细管理页面', '000000');
-- 支出明细管理 - 功能按钮
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5141, '支出明细查询', 5140, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:expenditureDetail:query', '#', 103, 1, now(), NULL, NULL, '支出明细查询权限', '000000'),
(5142, '支出明细详情', 5140, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:expenditureDetail:detail', '#', 103, 1, now(), NULL, NULL, '支出明细详情权限', '000000'),
(5143, '支出明细新增', 5140, 3, '', '', '', 1, 0, 'F', '0', '0', 'cw:expenditureDetail:add', '#', 103, 1, now(), NULL, NULL, '支出明细新增权限', '000000'),
(5144, '支出明细修改', 5140, 4, '', '', '', 1, 0, 'F', '0', '0', 'cw:expenditureDetail:edit', '#', 103, 1, now(), NULL, NULL, '支出明细修改权限', '000000'),
(5145, '支出明细删除', 5140, 5, '', '', '', 1, 0, 'F', '0', '0', 'cw:expenditureDetail:remove', '#', 103, 1, now(), NULL, NULL, '支出明细删除权限', '000000');
-- ==========================================
-- 7. 三级菜单:绩效指标管理
-- ==========================================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5150, '绩效指标管理', 5100, 5, 'performance-indicator', 'cw/budgetApproval/performanceIndicator/index', '', 1, 0, 'C', '0', '0', 'cw:performanceIndicator:list', 'chart', 103, 1, now(), NULL, NULL, '绩效指标管理页面', '000000');
-- 绩效指标管理 - 功能按钮
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5151, '绩效指标查询', 5150, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:performanceIndicator:query', '#', 103, 1, now(), NULL, NULL, '绩效指标查询权限', '000000'),
(5152, '绩效指标详情', 5150, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:performanceIndicator:detail', '#', 103, 1, now(), NULL, NULL, '绩效指标详情权限', '000000'),
(5153, '绩效指标新增', 5150, 3, '', '', '', 1, 0, 'F', '0', '0', 'cw:performanceIndicator:add', '#', 103, 1, now(), NULL, NULL, '绩效指标新增权限', '000000'),
(5154, '绩效指标修改', 5150, 4, '', '', '', 1, 0, 'F', '0', '0', 'cw:performanceIndicator:edit', '#', 103, 1, now(), NULL, NULL, '绩效指标修改权限', '000000'),
(5155, '绩效指标删除', 5150, 5, '', '', '', 1, 0, 'F', '0', '0', 'cw:performanceIndicator:remove', '#', 103, 1, now(), NULL, NULL, '绩效指标删除权限', '000000');
-- ==========================================
-- 8. 三级菜单:附件管理
-- ==========================================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5160, '附件管理', 5100, 6, 'attachment', 'cw/budgetApproval/attachment/index', '', 1, 0, 'C', '0', '0', 'cw:attachment:list', 'upload', 103, 1, now(), NULL, NULL, '附件管理页面', '000000');
-- 附件管理 - 功能按钮
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark, tenant_id) VALUES
(5161, '附件查询', 5160, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:attachment:query', '#', 103, 1, now(), NULL, NULL, '附件查询权限', '000000'),
(5162, '附件详情', 5160, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:attachment:detail', '#', 103, 1, now(), NULL, NULL, '附件详情权限', '000000'),
(5163, '附件上传', 5160, 3, '', '', '', 1, 0, 'F', '0', '0', 'cw:attachment:upload', '#', 103, 1, now(), NULL, NULL, '附件上传权限', '000000'),
(5164, '附件下载', 5160, 4, '', '', '', 1, 0, 'F', '0', '0', 'cw:attachment:download', '#', 103, 1, now(), NULL, NULL, '附件下载权限', '000000'),
(5165, '附件删除', 5160, 5, '', '', '', 1, 0, 'F', '0', '0', 'cw:attachment:remove', '#', 103, 1, now(), NULL, NULL, '附件删除权限', '000000');
-- ==========================================
-- 菜单创建完成,显示创建结果
-- ==========================================
SELECT '财务项目预算审批系统菜单配置完成!' AS result;
-- 验证菜单创建情况
SELECT '=== 菜单创建验证 ===' as verification;
SELECT menu_id, menu_name, menu_type, parent_id, order_num, perms
FROM sys_menu
WHERE menu_id BETWEEN 5000 AND 5999
ORDER BY menu_id;
-34
View File
@@ -1,34 +0,0 @@
-- 财务审批管理菜单配置
-- 执行顺序:在现有财务管理模块下添加
-- 菜单ID范围:6400-6404
-- 1. 财务审批管理菜单
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (6400, '财务审批', 6000, 5, 'financeApproval', 'cw/financeApproval/index', '', 1, 0, 'C', '0', '0', 'cw:approval:finance', 'ele-Money', 103, 1, now(), 1, now(), '财务项目申报审批管理');
-- 2. 财务审批功能权限
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES
(6401, '财务审批查询', 6400, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:finance', '#', 103, 1, now(), 1, now(), '财务审批查询权限'),
(6402, '财务审批操作', 6400, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:finance', '#', 103, 1, now(), 1, now(), '财务审批操作权限'),
(6403, '财务审批详情', 6400, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 103, 1, now(), 1, now(), '财务审批详情权限'),
(6404, '财务审批导出', 6400, 4, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:export', '#', 103, 1, now(), 1, now(), '财务审批导出权限');
-- 3. 如果需要给特定角色分配权限,请执行以下SQL(可选)
-- 注意:请根据实际角色ID调整,以下使用示例角色ID 2(普通角色)
-- 插入角色菜单关联(示例:给角色ID为2的角色分配财务审批权限)
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES
-- (2, 6400), -- 财务审批主菜单
-- (2, 6401), -- 查询权限
-- (2, 6402), -- 操作权限
-- (2, 6403), -- 详情权限
-- (2, 6404); -- 导出权限
-- 4. 添加字典数据(如果需要新的审批状态字典)
-- 注意:以下字典数据可能已存在,如果已存在则跳过
INSERT IGNORE INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES
(null, '000000', 8, '审批完成', 'approved', 'cw_approval_status', '', 'success', 'N', 103, 1, now(), 1, now(), '财务审批通过,整个流程完成');
-- 执行完成后,建议重新登录系统以刷新菜单权限
@@ -1,32 +0,0 @@
-- 分管领导审批管理菜单配置(最终修正版本)
-- 执行顺序:在现有财务管理模块下添加
-- 1. 分管领导审批管理菜单
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (6300, '分管领导审批', 6000, 4, 'leaderApproval', 'cw/leaderApproval/index', '', 1, 0, 'C', '0', '0', 'cw:approval:leader', 'ele-User', 103, 1, now(), 1, now(), '财务项目申报分管领导审批管理');
-- 2. 分管领导审批功能权限
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES
(6301, '分管领导审批查询', 6300, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:leader', '#', 103, 1, now(), 1, now(), '分管领导审批查询权限'),
(6302, '分管领导审批操作', 6300, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:leader', '#', 103, 1, now(), 1, now(), '分管领导审批操作权限'),
(6303, '分管领导审批详情', 6300, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 103, 1, now(), 1, now(), '分管领导审批详情权限'),
(6304, '分管领导审批导出', 6300, 4, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:export', '#', 103, 1, now(), 1, now(), '分管领导审批导出权限');
-- 3. 如果需要给特定角色分配权限,请执行以下SQL(可选)
-- 注意:请根据实际角色ID调整,以下使用示例角色ID 2(普通角色)
-- 插入角色菜单关联(示例:给角色ID为2的角色分配分管领导审批权限)
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES
-- (2, 6300), -- 分管领导审批主菜单
-- (2, 6301), -- 查询权限
-- (2, 6302), -- 操作权限
-- (2, 6303), -- 详情权限
-- (2, 6304); -- 导出权限
-- 4. 添加字典数据(如果需要新的审批状态字典)
INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES
(null, '000000', 7, '财务审批中', 'finance_approving', 'cw_approval_status', '', 'warning', 'N', 103, 1, now(), 1, now(), '分管领导审批通过,财务审批中');
-- 执行完成后,建议重新登录系统以刷新菜单权限
@@ -1,32 +0,0 @@
-- 分管领导审批管理菜单配置(修正版本)
-- 执行顺序:在现有财务管理模块下添加
-- 1. 分管领导审批管理菜单
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (6300, '分管领导审批', 6000, 4, 'leaderApproval', 'cw/leaderApproval/index', '', 1, 0, 'C', '0', '0', 'cw:approval:leader', 'ele-User', 103, 1, now(), 1, now(), '财务项目申报分管领导审批管理');
-- 2. 分管领导审批功能权限
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES
(6301, '分管领导审批查询', 6300, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:leader', '#', 103, 1, now(), 1, now(), '分管领导审批查询权限'),
(6302, '分管领导审批操作', 6300, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:leader', '#', 103, 1, now(), 1, now(), '分管领导审批操作权限'),
(6303, '分管领导审批详情', 6300, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 103, 1, now(), 1, now(), '分管领导审批详情权限'),
(6304, '分管领导审批导出', 6300, 4, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:export', '#', 103, 1, now(), 1, now(), '分管领导审批导出权限');
-- 3. 如果需要给特定角色分配权限,请执行以下SQL(可选)
-- 注意:请根据实际角色ID调整,以下使用示例角色ID 2(普通角色)
-- 插入角色菜单关联(示例:给角色ID为2的角色分配分管领导审批权限)
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES
-- (2, 6300), -- 分管领导审批主菜单
-- (2, 6301), -- 查询权限
-- (2, 6302), -- 操作权限
-- (2, 6303), -- 详情权限
-- (2, 6304); -- 导出权限
-- 4. 添加字典数据(如果需要新的审批状态字典)
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark)
VALUES
(null, 7, '财务审批中', 'finance_approving', 'cw_approval_status', '', 'warning', 'N', '0', 1, now(), 1, now(), '分管领导审批通过,财务审批中');
-- 执行完成后,建议重新登录系统以刷新菜单权限
-32
View File
@@ -1,32 +0,0 @@
-- 分管领导审批管理菜单配置
-- 执行顺序:在现有财务管理模块下添加
-- 1. 分管领导审批管理菜单
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES (6300, '分管领导审批', 6000, 4, 'leaderApproval', 'cw/leaderApproval/index', '', 1, 0, 'C', '0', '0', 'cw:approval:leader', 'ele-User', 1, now(), 1, now(), '财务项目申报分管领导审批管理', '000000');
-- 2. 分管领导审批功能权限
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES
(6301, '分管领导审批查询', 6300, 1, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:leader', '#', 1, now(), 1, now(), '分管领导审批查询权限', '000000'),
(6302, '分管领导审批操作', 6300, 2, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:leader', '#', 1, now(), 1, now(), '分管领导审批操作权限', '000000'),
(6303, '分管领导审批详情', 6300, 3, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:query', '#', 1, now(), 1, now(), '分管领导审批详情权限', '000000'),
(6304, '分管领导审批导出', 6300, 4, '#', '', '', 1, 0, 'F', '0', '0', 'cw:approval:export', '#', 1, now(), 1, now(), '分管领导审批导出权限', '000000');
-- 3. 如果需要给特定角色分配权限,请执行以下SQL(可选)
-- 注意:请根据实际角色ID调整,以下使用示例角色ID 2(普通角色)
-- 插入角色菜单关联(示例:给角色ID为2的角色分配分管领导审批权限)
-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES
-- (2, 6300), -- 分管领导审批主菜单
-- (2, 6301), -- 查询权限
-- (2, 6302), -- 操作权限
-- (2, 6303), -- 详情权限
-- (2, 6304); -- 导出权限
-- 4. 添加字典数据(如果需要新的审批状态字典)
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark, tenant_id)
VALUES
(null, 7, '财务审批中', 'finance_approving', 'cw_approval_status', '', 'warning', 'N', '0', 1, now(), 1, now(), '分管领导审批通过,财务审批中', '000000');
-- 执行完成后,建议重新登录系统以刷新菜单权限
-60
View File
@@ -1,60 +0,0 @@
-- 项目预算数据统计菜单配置
-- 作者:Cursor AI
-- 创建时间:2025-01-27
-- 1. 添加项目预算数据统计菜单
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (7100, '项目预算统计', 6200, 4, 'statistics', 'cw/statistics/index', '', 1, 0, 'C', '0', '0', 'cw:statistics:list', 'chart', 103, 1, NOW(), 1, NOW(), '项目预算数据统计菜单');
-- 2. 添加统计相关按钮权限
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (7101, '项目预算统计查询', 7100, 1, '', '', '', 1, 0, 'F', '0', '0', 'cw:statistics:list', '#', 103, 1, NOW(), 1, NOW(), '');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (7102, '项目预算统计导出', 7100, 2, '', '', '', 1, 0, 'F', '0', '0', 'cw:statistics:export', '#', 103, 1, NOW(), 1, NOW(), '');
-- 3. 为财务管理员角色分配统计菜单权限
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, 7100
FROM sys_role r
WHERE r.role_key = 'cw_admin' AND r.status = '0';
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, 7101
FROM sys_role r
WHERE r.role_key = 'cw_admin' AND r.status = '0';
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, 7102
FROM sys_role r
WHERE r.role_key = 'cw_admin' AND r.status = '0';
-- 4. 为系统管理员角色分配统计菜单权限
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, 7100
FROM sys_role r
WHERE r.role_key = 'admin' AND r.status = '0';
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, 7101
FROM sys_role r
WHERE r.role_key = 'admin' AND r.status = '0';
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, 7102
FROM sys_role r
WHERE r.role_key = 'admin' AND r.status = '0';
-- 5. 确认菜单创建结果
SELECT
m.menu_id,
m.menu_name,
m.parent_id,
m.path,
m.component,
m.perms,
m.menu_type,
m.status
FROM sys_menu m
WHERE m.menu_id IN (7100, 7101, 7102)
ORDER BY m.menu_id;
-88
View File
@@ -1,88 +0,0 @@
-- ==========================================
-- 修复项目申报中申请人ID为空的问题
-- 创建时间: 2025-08-01
-- 描述: 检查并修复cw_project_application表中applicant_id为NULL的记录
-- ==========================================
-- 1. 检查问题记录
SELECT
id,
application_no,
project_name,
applicant_id,
applicant_name,
department_id,
department_name,
approval_status,
create_by,
create_time
FROM cw_project_application
WHERE applicant_id IS NULL OR applicant_id = 0;
-- 2. 修复方案1:将申请人ID设置为创建者ID(推荐)
UPDATE cw_project_application
SET
applicant_id = create_by,
applicant_name = (
SELECT nick_name
FROM sys_user
WHERE user_id = cw_project_application.create_by
AND deleted = 0
LIMIT 1
)
WHERE (applicant_id IS NULL OR applicant_id = 0)
AND create_by IS NOT NULL
AND create_by > 0;
-- 3. 修复方案2:如果创建者也为空,设置为系统管理员(ID=1)
UPDATE cw_project_application
SET
applicant_id = 1,
applicant_name = '系统管理员'
WHERE (applicant_id IS NULL OR applicant_id = 0)
AND (create_by IS NULL OR create_by = 0);
-- 4. 修复部门信息为空的记录
UPDATE cw_project_application
SET
department_id = (
SELECT dept_id
FROM sys_user
WHERE user_id = cw_project_application.applicant_id
AND deleted = 0
LIMIT 1
),
department_name = (
SELECT d.dept_name
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.user_id = cw_project_application.applicant_id
AND u.deleted = 0
AND d.deleted = 0
LIMIT 1
)
WHERE (department_id IS NULL OR department_id = 0)
AND applicant_id IS NOT NULL
AND applicant_id > 0;
-- 5. 验证修复结果
SELECT
'修复后检查' as check_type,
COUNT(*) as total_records,
SUM(CASE WHEN applicant_id IS NULL OR applicant_id = 0 THEN 1 ELSE 0 END) as null_applicant_count,
SUM(CASE WHEN department_id IS NULL OR department_id = 0 THEN 1 ELSE 0 END) as null_department_count
FROM cw_project_application;
-- 6. 显示修复后的记录
SELECT
id,
application_no,
project_name,
applicant_id,
applicant_name,
department_id,
department_name,
approval_status
FROM cw_project_application
ORDER BY create_time DESC
LIMIT 10;
@@ -1,85 +0,0 @@
-- 修复项目申报表中申请人ID为空的问题
-- 日期: 2025-08-01
-- 说明: 检查并修复cw_project_application表中applicant_id和department_id为空的记录
-- 1. 查看当前有问题的记录
SELECT
id,
application_no,
project_name,
applicant_id,
applicant_name,
department_id,
department_name,
create_by,
approval_status
FROM cw_project_application
WHERE applicant_id IS NULL
OR applicant_id = 0
OR department_id IS NULL
OR department_id = 0
ORDER BY create_time DESC;
-- 2. 修复applicant_id为空的记录(使用create_by作为申请人ID)
UPDATE cw_project_application
SET applicant_id = create_by
WHERE (applicant_id IS NULL OR applicant_id = 0)
AND create_by IS NOT NULL
AND create_by > 0;
-- 3. 修复applicant_name为空的记录(从sys_user表获取用户名)
UPDATE cw_project_application cpa
INNER JOIN sys_user su ON cpa.applicant_id = su.user_id
SET cpa.applicant_name = su.user_name
WHERE (cpa.applicant_name IS NULL OR cpa.applicant_name = '')
AND cpa.applicant_id IS NOT NULL;
-- 4. 修复department_id为空的记录(从sys_user表获取部门ID)
UPDATE cw_project_application cpa
INNER JOIN sys_user su ON cpa.applicant_id = su.user_id
SET cpa.department_id = su.dept_id
WHERE (cpa.department_id IS NULL OR cpa.department_id = 0)
AND cpa.applicant_id IS NOT NULL
AND su.dept_id IS NOT NULL;
-- 5. 修复department_name为空的记录(从sys_dept表获取部门名称)
UPDATE cw_project_application cpa
INNER JOIN sys_dept sd ON cpa.department_id = sd.dept_id
SET cpa.department_name = sd.dept_name
WHERE (cpa.department_name IS NULL OR cpa.department_name = '')
AND cpa.department_id IS NOT NULL;
-- 6. 对于仍然无法修复的记录,设置为系统管理员(ID=1)
UPDATE cw_project_application
SET applicant_id = 1,
applicant_name = 'admin',
department_id = 103,
department_name = '研发部门'
WHERE applicant_id IS NULL
OR applicant_id = 0
OR department_id IS NULL
OR department_id = 0;
-- 7. 验证修复结果
SELECT
COUNT(*) as total_records,
COUNT(CASE WHEN applicant_id IS NULL OR applicant_id = 0 THEN 1 END) as null_applicant_id,
COUNT(CASE WHEN department_id IS NULL OR department_id = 0 THEN 1 END) as null_department_id,
COUNT(CASE WHEN applicant_name IS NULL OR applicant_name = '' THEN 1 END) as null_applicant_name,
COUNT(CASE WHEN department_name IS NULL OR department_name = '' THEN 1 END) as null_department_name
FROM cw_project_application;
-- 8. 显示修复后的记录
SELECT
id,
application_no,
project_name,
applicant_id,
applicant_name,
department_id,
department_name,
approval_status,
create_time
FROM cw_project_application
ORDER BY create_time DESC
LIMIT 10;
@@ -1,105 +0,0 @@
-- 修复项目申报表中缺失字段的问题
-- 日期: 2025-08-01
-- 说明: 修复cw_project_application表中可能为空的必填字段
-- 1. 检查当前有问题的记录
SELECT
id,
application_no,
project_name,
application_reason,
construction_content,
total_performance_goal,
current_year_goal,
department_name,
approval_status
FROM cw_project_application
WHERE application_reason IS NULL
OR application_reason = ''
OR construction_content IS NULL
OR construction_content = ''
OR total_performance_goal IS NULL
OR total_performance_goal = ''
OR current_year_goal IS NULL
OR current_year_goal = ''
OR department_name IS NULL
OR department_name = ''
ORDER BY create_time DESC;
-- 2. 修复application_reason为空的记录
UPDATE cw_project_application
SET application_reason = CONCAT('项目申请原因:', project_name, '项目的设立基于实际业务需求,具有可行性和必要性。')
WHERE (application_reason IS NULL OR application_reason = '')
AND project_name IS NOT NULL;
-- 3. 修复construction_content为空的记录
UPDATE cw_project_application
SET construction_content = CONCAT('项目建设内容:', project_name, '项目的主要建设内容包括相关软硬件建设、人员培训等。')
WHERE (construction_content IS NULL OR construction_content = '')
AND project_name IS NOT NULL;
-- 4. 修复total_performance_goal为空的记录(基于绩效指标自动生成)
UPDATE cw_project_application cpa
LEFT JOIN (
SELECT
application_id,
COUNT(CASE WHEN indicator_type IN ('quantity', 'quality', 'timeliness', 'cost') THEN 1 END) as output_count,
COUNT(CASE WHEN indicator_type IN ('economic', 'social', 'environmental') THEN 1 END) as effect_count,
COUNT(CASE WHEN indicator_type = 'satisfaction' THEN 1 END) as satisfaction_count
FROM cw_performance_indicator
WHERE deleted = 0
GROUP BY application_id
) pi ON cpa.id = pi.application_id
SET cpa.total_performance_goal = CASE
WHEN pi.application_id IS NOT NULL THEN
CONCAT('通过本项目的实施,预期实现以下绩效目标:产出指标', IFNULL(pi.output_count, 0), '项,效益指标', IFNULL(pi.effect_count, 0), '项,满意度指标', IFNULL(pi.satisfaction_count, 0), '项,确保项目按期完成并达到预期效果。')
ELSE
CONCAT('通过', cpa.project_name, '项目的实施,预期实现既定的绩效目标,确保项目按期完成并达到预期效果。')
END
WHERE (cpa.total_performance_goal IS NULL OR cpa.total_performance_goal = '')
AND cpa.project_name IS NOT NULL;
-- 5. 修复current_year_goal为空的记录
UPDATE cw_project_application
SET current_year_goal = CONCAT(YEAR(CURDATE()), '年度绩效目标:确保项目关键节点按计划推进,各项指标达到设定标准,为项目整体目标的实现奠定坚实基础。')
WHERE (current_year_goal IS NULL OR current_year_goal = '')
AND project_name IS NOT NULL;
-- 6. 修复department_name为空的记录(从sys_dept表获取)
UPDATE cw_project_application cpa
INNER JOIN sys_dept sd ON cpa.department_id = sd.dept_id
SET cpa.department_name = sd.dept_name
WHERE (cpa.department_name IS NULL OR cpa.department_name = '')
AND cpa.department_id IS NOT NULL;
-- 7. 对于仍然缺失department_name的记录,设置默认值
UPDATE cw_project_application
SET department_name = '未指定部门'
WHERE (department_name IS NULL OR department_name = '')
AND applicant_id IS NOT NULL;
-- 8. 验证修复结果
SELECT
COUNT(*) as total_records,
COUNT(CASE WHEN application_reason IS NULL OR application_reason = '' THEN 1 END) as null_application_reason,
COUNT(CASE WHEN construction_content IS NULL OR construction_content = '' THEN 1 END) as null_construction_content,
COUNT(CASE WHEN total_performance_goal IS NULL OR total_performance_goal = '' THEN 1 END) as null_total_performance_goal,
COUNT(CASE WHEN current_year_goal IS NULL OR current_year_goal = '' THEN 1 END) as null_current_year_goal,
COUNT(CASE WHEN department_name IS NULL OR department_name = '' THEN 1 END) as null_department_name
FROM cw_project_application;
-- 9. 显示修复后的记录样例
SELECT
id,
application_no,
project_name,
SUBSTRING(application_reason, 1, 50) as application_reason_preview,
SUBSTRING(construction_content, 1, 50) as construction_content_preview,
SUBSTRING(total_performance_goal, 1, 50) as total_performance_goal_preview,
SUBSTRING(current_year_goal, 1, 50) as current_year_goal_preview,
department_name,
approval_status,
create_time
FROM cw_project_application
ORDER BY create_time DESC
LIMIT 10;
@@ -1,31 +0,0 @@
-- 补充学生请假审批状态字典数据脚本
-- 日期: 2025-01-27
-- 说明: 补充缺失的学生请假审批状态字典数据
-- 补充缺失的字典数据
-- 已撤销
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009032, '000000', 1, '已撤销', 'cancel', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '已撤销');
-- 草稿
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009033, '000000', 2, '草稿', 'draft', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '草稿状态');
-- 待审核
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009034, '000000', 3, '待审核', 'waiting', 'student_leave_approval_status', '', 'warning', 'N', 1, now(), '待审核状态');
-- 已完成
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009035, '000000', 4, '已完成', 'finish', 'student_leave_approval_status', '', 'success', 'N', 1, now(), '已完成状态');
-- 验证补充结果
SELECT
'补充后完整字典数据' as check_item,
dict_sort,
dict_label,
dict_value,
list_class
FROM sys_dict_data
WHERE dict_type = 'student_leave_approval_status'
ORDER BY dict_sort;
@@ -1,2 +0,0 @@
-- 为xljk_consultation_appointment表添加appointment_detail_time字段
ALTER TABLE xljk_consultation_appointment ADD COLUMN appointment_detail_time VARCHAR(20) DEFAULT NULL COMMENT '具体预约时间段,如"08:30-09:00"';
-76
View File
@@ -1,76 +0,0 @@
-- 测试审批流程SQL脚本
-- 用于验证 start_time 字段修复是否有效
-- 1. 清理测试数据
DELETE FROM cw_approval_record WHERE application_no LIKE 'TEST%';
DELETE FROM cw_project_application WHERE application_no LIKE 'TEST%';
-- 2. 创建测试项目申报
INSERT INTO cw_project_application (
id, application_no, project_name, principal, contact_phone,
department_id, department_name, total_investment, budget_year,
start_date, end_date, project_description, expected_benefits,
approval_status, create_by, create_time, update_by, update_time,
create_dept, tenant_id, deleted
) VALUES (
9999999999999999, 'TEST20250801001', '测试项目申报', '测试用户', '13800000000',
103, '教务处', 100000.00, '2025',
'2025-01-01', '2025-12-31', '这是一个测试项目', '预期效益很好',
'submitted', 1, NOW(), 1, NOW(),
103, '000000', 0
);
-- 3. 手动创建审批记录(模拟我们的批量插入逻辑)
INSERT INTO cw_approval_record (
id, application_id, application_no, node_code, node_name, node_order,
approver_id, approver_name, is_delegate, original_approver_id, original_approver_name,
node_status, approve_action, approve_opinion, start_time, end_time, expect_finish_time,
is_overtime, config_id, config_snapshot, create_by, create_time, update_by, update_time,
create_dept, tenant_id
) VALUES
(9999999999999991, 9999999999999999, 'TEST20250801001', 'dept_approve', '部门审批', 1,
1, 'admin_部门审批', 0, NULL, NULL,
'waiting', NULL, NULL, NOW(), NULL, NULL,
0, NULL, NULL, 1, NOW(), 1, NOW(),
103, '000000'),
(9999999999999992, 9999999999999999, 'TEST20250801001', 'finance_approve', '财务审批', 2,
1, 'admin_财务审批', 0, NULL, NULL,
'pending', NULL, NULL, NOW(), NULL, NULL,
0, NULL, NULL, 1, NOW(), 1, NOW(),
103, '000000'),
(9999999999999993, 9999999999999999, 'TEST20250801001', 'leader_approve', '领导审批', 3,
1, 'admin_领导审批', 0, NULL, NULL,
'pending', NULL, NULL, NOW(), NULL, NULL,
0, NULL, NULL, 1, NOW(), 1, NOW(),
103, '000000');
-- 4. 验证插入结果
SELECT
'=== 测试结果验证 ===' as title,
COUNT(*) as total_records,
COUNT(CASE WHEN start_time IS NOT NULL THEN 1 END) as records_with_start_time,
COUNT(CASE WHEN start_time IS NULL THEN 1 END) as records_with_null_start_time
FROM cw_approval_record
WHERE application_no = 'TEST20250801001';
-- 5. 显示详细记录
SELECT
node_code,
node_name,
node_status,
approver_name,
start_time,
CASE
WHEN start_time IS NULL THEN '❌ NULL (问题未修复)'
ELSE '✅ 有值 (修复成功)'
END as start_time_status
FROM cw_approval_record
WHERE application_no = 'TEST20250801001'
ORDER BY node_order;
-- 6. 清理测试数据
-- DELETE FROM cw_approval_record WHERE application_no = 'TEST20250801001';
-- DELETE FROM cw_project_application WHERE application_no = 'TEST20250801001';
-- 如果上述测试成功(所有记录都有start_time值),说明我们的修复有效
-- 如果测试失败,请检查是否还有其他问题
-90
View File
@@ -1,90 +0,0 @@
-- ==================================================
-- 招生就业模块 - 招聘信息管理菜单配置
-- 创建日期: 2025-01-26
-- 说明: 参考企业信息管理菜单配置
-- ==================================================
-- 1. 招聘信息管理父菜单(招聘管理)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8710, '招聘管理', 8000, 6, 'recruitment', null, null, 1, 0, 'M', '0', '0', '', 'post', 103, 1, NOW(), null, null, '招聘管理菜单');
-- 2. 招聘信息子菜单(招聘岗位)
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8711, '招聘岗位', 8710, 1, 'position', 'zsjy/jobPosition/index', null, 1, 0, 'C', '0', '0', 'zsjy:position:list', 'job', 103, 1, NOW(), null, null, '招聘岗位菜单');
-- 3. 招聘岗位按钮权限
-- 3.1 查询权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8712, '招聘岗位查询', 8711, 1, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:position:query', '#', 103, 1, NOW(), null, null, '');
-- 3.2 新增权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8713, '招聘岗位新增', 8711, 2, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:position:add', '#', 103, 1, NOW(), null, null, '');
-- 3.3 修改权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8714, '招聘岗位修改', 8711, 3, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:position:edit', '#', 103, 1, NOW(), null, null, '');
-- 3.4 删除权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8715, '招聘岗位删除', 8711, 4, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:position:remove', '#', 103, 1, NOW(), null, null, '');
-- 3.5 导出权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8716, '招聘岗位导出', 8711, 5, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:position:export', '#', 103, 1, NOW(), null, null, '');
-- 4. 招聘海报子菜单
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8720, '招聘海报', 8710, 2, 'poster', 'zsjy/recruitmentPoster/index', null, 1, 0, 'C', '0', '0', 'zsjy:poster:list', 'picture', 103, 1, NOW(), null, null, '招聘海报菜单');
-- 5. 招聘海报按钮权限
-- 5.1 查询权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8721, '招聘海报查询', 8720, 1, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:poster:query', '#', 103, 1, NOW(), null, null, '');
-- 5.2 新增权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8722, '招聘海报新增', 8720, 2, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:poster:add', '#', 103, 1, NOW(), null, null, '');
-- 5.3 修改权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8723, '招聘海报修改', 8720, 3, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:poster:edit', '#', 103, 1, NOW(), null, null, '');
-- 5.4 删除权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8724, '招聘海报删除', 8720, 4, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:poster:remove', '#', 103, 1, NOW(), null, null, '');
-- 5.5 导出权限
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
VALUES (8725, '招聘海报导出', 8720, 5, '', '', null, 1, 0, 'F', '0', '0', 'zsjy:poster:export', '#', 103, 1, NOW(), null, null, '');
-- ==================================================
-- 为管理员角色分配招聘管理相关权限
-- ==================================================
-- 为管理员角色(role_id=1)分配招聘管理菜单权限
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8710); -- 招聘管理
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8711); -- 招聘岗位
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8712); -- 招聘岗位查询
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8713); -- 招聘岗位新增
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8714); -- 招聘岗位修改
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8715); -- 招聘岗位删除
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8716); -- 招聘岗位导出
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8720); -- 招聘海报
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8721); -- 招聘海报查询
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8722); -- 招聘海报新增
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8723); -- 招聘海报修改
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8724); -- 招聘海报删除
INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 8725); -- 招聘海报导出
-- ==================================================
-- 执行前检查
-- ==================================================
-- SELECT * FROM sys_menu WHERE menu_id BETWEEN 8710 AND 8725;
-- SELECT * FROM sys_role_menu WHERE role_id = 1 AND menu_id BETWEEN 8710 AND 8725;
-- ==================================================
-- 回滚语句
-- ==================================================
-- DELETE FROM sys_role_menu WHERE role_id = 1 AND menu_id BETWEEN 8710 AND 8725;
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 8710 AND 8725;
-550
View File
@@ -1,550 +0,0 @@
/**
* 社团管理系统API测试脚本
*
* 功能说明:
* 1. 社团信息管理 - 增删改查、状态管理、权限验证
* 2. 社团成员管理 - 成员添加、角色管理、退出处理
* 3. 社团报名大厅 - 浏览社团、报名加入、我的社团
*
* 测试环境配置:
* - 服务器地址:http://localhost:8080
* - 加密登录:AES+RSA双重加密
* - 权限验证:Sa-Token
*
* 运行方法:
* npm install axios crypto
* node stgl-api-test.js
*
* @author Cursor AI
* @date 2025-01-15
* @version 1.0
*/
const axios = require('axios');
const crypto = require('crypto');
// ============================== 配置信息 ==============================
const CONFIG = {
BASE_URL: 'http://localhost:8080',
TIMEOUT: 30000,
// 测试用户配置
TEST_USER: {
username: 'admin',
password: 'admin123',
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
referer: 'http://localhost'
},
// AES加密密钥(16位)
AES_SECRET_KEY: '1234567890123456',
// RSA公钥
RSA_PUBLIC_KEY: `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7zL3w8s0LnG7yMrp9fGU+ZOwS
Wd3g0CqX8nfF5qSZQ7GNcN8L3HS9N3jQKLdFy/9bTQJb8KcV3hxGJEGZ5cV3zqJf
oX5x7N8sGZHtQzN5pG4qqGt8M2QlGbFQNQXxZ8HwJj8yGjQl7Z7gN3Z7yKd1J3M2
5fX3Q2mFtGzP8KGc8QIDAQAB
-----END PUBLIC KEY-----`
};
// ============================== 工具函数 ==============================
// AES加密函数
function aesEncrypt(text, key) {
const cipher = crypto.createCipher('aes-128-ecb', key);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
// RSA加密函数
function rsaEncrypt(text, publicKey) {
const buffer = Buffer.from(text, 'utf8');
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: crypto.constants.RSA_PKCS1_PADDING
}, buffer);
return encrypted.toString('base64');
}
// HTTP请求封装
const request = axios.create({
baseURL: CONFIG.BASE_URL,
timeout: CONFIG.TIMEOUT,
headers: {
'Content-Type': 'application/json',
'clientid': CONFIG.TEST_USER.clientid,
'referer': CONFIG.TEST_USER.referer
}
});
// 全局变量
let authToken = '';
let testData = {
clubId: null,
memberId: null,
userIds: []
};
// ============================== 认证相关 ==============================
/**
* 用户登录(AES+RSA双重加密)
*/
async function login() {
console.log('\n🔐 开始用户登录...');
try {
// 第一步:AES加密用户名和密码
const aesUsername = aesEncrypt(CONFIG.TEST_USER.username, CONFIG.AES_SECRET_KEY);
const aesPassword = aesEncrypt(CONFIG.TEST_USER.password, CONFIG.AES_SECRET_KEY);
// 第二步:RSA加密AES密钥
const rsaKey = rsaEncrypt(CONFIG.AES_SECRET_KEY, CONFIG.RSA_PUBLIC_KEY);
// 第三步:发送登录请求
const response = await request.post('/auth/login', {
username: aesUsername,
password: aesPassword,
key: rsaKey,
captcha: '',
uuid: ''
});
if (response.data.code === 200) {
authToken = response.data.data.access_token;
request.defaults.headers['Authorization'] = `Bearer ${authToken}`;
console.log('✅ 登录成功');
console.log(`🔑 Token: ${authToken.substring(0, 50)}...`);
return true;
} else {
console.error('❌ 登录失败:', response.data.msg);
return false;
}
} catch (error) {
console.error('❌ 登录请求失败:', error.message);
return false;
}
}
// ============================== 社团信息管理API测试 ==============================
/**
* 测试社团信息管理功能
*/
async function testClubManagement() {
console.log('\n📋 ========== 社团信息管理API测试 ==========');
// 1. 查询社团列表
await testAPI('GET', '/stgl/club/list', {
pageNum: 1,
pageSize: 10
}, '查询社团列表');
// 2. 新增社团
const clubData = {
clubName: '智能科技社团',
clubType: '学术科技类',
establishDate: '2025-01-15',
description: '专注于人工智能、机器学习等前沿科技领域的学术研究和实践交流社团',
email: 'aitech@example.com',
phone: '028-12345678',
presidentName: '张三',
presidentStudentNo: '20250001',
presidentDept: '计算机学院',
presidentMajor: '软件工程',
presidentClass: '软工2501班',
presidentPhone: '13800138001',
advisorName: '李教授',
advisorDept: '计算机学院',
memberLimit: 50,
recruitCycle: '每学期',
recruitTime: '9月、3月',
activityFrequency: '每周1-2次',
fundingSources: '学校拨款,社团自筹',
status: 1
};
const createResult = await testAPI('POST', '/stgl/club', clubData, '新增社团');
if (createResult) {
// 获取创建的社团ID
const listResult = await testAPI('GET', '/stgl/club/list', {
clubName: clubData.clubName
}, '查询新建社团ID');
if (listResult && listResult.data.rows.length > 0) {
testData.clubId = listResult.data.rows[0].id;
console.log(`🆔 获取到社团ID: ${testData.clubId}`);
}
}
// 3. 查询社团详情
if (testData.clubId) {
await testAPI('GET', `/stgl/club/${testData.clubId}`, {}, '查询社团详情');
}
// 4. 修改社团信息
if (testData.clubId) {
await testAPI('PUT', '/stgl/club', {
id: testData.clubId,
clubName: '智能科技社团(更新版)',
memberLimit: 60
}, '修改社团信息');
}
// 5. 查询可用社团选项
await testAPI('GET', '/stgl/club/options', {}, '查询可用社团选项');
// 6. 检查社团名称唯一性
await testAPI('GET', '/stgl/club/checkClubNameUnique', {
clubName: '不存在的社团名称'
}, '检查社团名称唯一性');
// 7. 查询我管理的社团
await testAPI('GET', '/stgl/club/myClubs', {}, '查询我管理的社团');
// 8. 启用/禁用社团
if (testData.clubId) {
await testAPI('PUT', '/stgl/club/changeStatus', {}, '启用/禁用社团', {
id: testData.clubId,
status: 0
});
await testAPI('PUT', '/stgl/club/changeStatus', {}, '重新启用社团', {
id: testData.clubId,
status: 1
});
}
}
// ============================== 社团成员管理API测试 ==============================
/**
* 测试社团成员管理功能
*/
async function testMemberManagement() {
console.log('\n👥 ========== 社团成员管理API测试 ==========');
// 1. 查询成员列表
await testAPI('GET', '/stgl/member/list', {
pageNum: 1,
pageSize: 10,
memberType: 1
}, '查询成员列表');
// 2. 新增成员
if (testData.clubId) {
const memberData = {
memberType: 1,
relationId: testData.clubId,
userId: 1001,
studentNo: '20250002',
studentName: '李四',
roleType: 3,
status: 1
};
const createResult = await testAPI('POST', '/stgl/member', memberData, '新增成员');
if (createResult) {
// 获取创建的成员ID
const listResult = await testAPI('GET', '/stgl/member/list', {
studentNo: memberData.studentNo
}, '查询新建成员ID');
if (listResult && listResult.data.rows.length > 0) {
testData.memberId = listResult.data.rows[0].id;
console.log(`🆔 获取到成员ID: ${testData.memberId}`);
}
}
}
// 3. 查询成员详情
if (testData.memberId) {
await testAPI('GET', `/stgl/member/${testData.memberId}`, {}, '查询成员详情');
}
// 4. 修改成员信息
if (testData.memberId) {
await testAPI('PUT', '/stgl/member', {
id: testData.memberId,
studentName: '李四(更新)',
roleType: 2,
remark: '表现优秀,晋升为副社长'
}, '修改成员信息');
}
// 5. 根据社团ID查询成员
if (testData.clubId) {
await testAPI('GET', `/stgl/member/club/${testData.clubId}`, {}, '根据社团ID查询成员');
}
// 6. 批量添加成员
if (testData.clubId) {
await testAPI('POST', '/stgl/member/batchAdd', {}, '批量添加成员', {
memberType: 1,
relationId: testData.clubId,
userIds: [1002, 1003, 1004],
roleType: 3
});
}
// 7. 更新成员角色
if (testData.memberId) {
await testAPI('PUT', '/stgl/member/updateRole', {}, '更新成员角色', {
id: testData.memberId,
roleType: 3
});
}
// 8. 检查用户是否已是成员
if (testData.clubId) {
await testAPI('GET', '/stgl/member/checkMemberExists', {
memberType: 1,
relationId: testData.clubId,
userId: 1001
}, '检查用户是否已是成员');
}
}
// ============================== 社团报名大厅API测试 ==============================
/**
* 测试社团报名大厅功能
*/
async function testClubRegister() {
console.log('\n🎯 ========== 社团报名大厅API测试 ==========');
// 1. 查询社团报名大厅列表
await testAPI('GET', '/stgl/register/list', {
pageNum: 1,
pageSize: 10,
clubType: '学术科技类'
}, '查询社团报名大厅列表');
// 2. 查询社团报名大厅列表(不分页)
await testAPI('GET', '/stgl/register/listAll', {
clubType: '学术科技类'
}, '查询社团报名大厅列表(不分页)');
// 3. 查询社团详细信息
if (testData.clubId) {
await testAPI('GET', `/stgl/register/${testData.clubId}`, {}, '查询社团详细信息');
}
// 4. 报名加入社团
if (testData.clubId) {
await testAPI('POST', `/stgl/register/join/${testData.clubId}`, {}, '报名加入社团');
}
// 5. 检查报名状态
if (testData.clubId) {
await testAPI('GET', `/stgl/register/checkRegistered/${testData.clubId}`, {}, '检查报名状态');
}
// 6. 查询我参与的社团
await testAPI('GET', '/stgl/register/myClubs', {}, '查询我参与的社团');
// 7. 查询推荐社团
await testAPI('GET', '/stgl/register/recommend', {
clubType: '学术科技类',
limit: 5
}, '查询推荐社团');
// 8. 退出社团
if (testData.clubId) {
await testAPI('POST', `/stgl/register/quit/${testData.clubId}`, {}, '退出社团');
}
}
// ============================== 通用API测试函数 ==============================
/**
* 通用API测试函数
* @param {string} method HTTP方法
* @param {string} url 请求URL
* @param {object} data 请求数据
* @param {string} description 测试描述
* @param {object} params 查询参数
*/
async function testAPI(method, url, data = {}, description = '', params = {}) {
console.log(`\n${getMethodIcon(method)} 测试${description}...`);
try {
let response;
const config = {
params: Object.keys(params).length > 0 ? params : (method === 'GET' ? data : {})
};
switch (method.toUpperCase()) {
case 'GET':
response = await request.get(url, config);
break;
case 'POST':
response = await request.post(url, data, config);
break;
case 'PUT':
response = await request.put(url, data, config);
break;
case 'DELETE':
response = await request.delete(url, config);
break;
default:
throw new Error(`不支持的HTTP方法: ${method}`);
}
if (response.data.code === 200) {
console.log(`✅ ${description}成功`);
// 根据响应类型输出不同信息
if (response.data.data) {
if (Array.isArray(response.data.data)) {
console.log(`📊 返回数据数量: ${response.data.data.length}`);
} else if (response.data.data.rows) {
console.log(`📊 总记录数: ${response.data.data.total}`);
console.log(`📄 当前页数据: ${response.data.data.rows.length} 条`);
} else if (typeof response.data.data === 'object') {
console.log(`📝 返回数据类型: ${Object.keys(response.data.data).join(', ')}`);
} else {
console.log(`📝 返回数据: ${response.data.data}`);
}
}
return response.data;
} else {
console.error(`❌ ${description}失败:`, response.data.msg);
return null;
}
} catch (error) {
console.error(`❌ ${description}请求失败:`, error.response?.data?.msg || error.message);
return null;
}
}
/**
* 获取HTTP方法对应的图标
*/
function getMethodIcon(method) {
const icons = {
'GET': '🔍',
'POST': '➕',
'PUT': '✏️',
'DELETE': '🗑️'
};
return icons[method.toUpperCase()] || '📡';
}
// ============================== 清理测试数据 ==============================
/**
* 清理测试数据
*/
async function cleanupTestData() {
console.log('\n🧹 ========== 清理测试数据 ==========');
// 删除测试社团(会级联删除相关成员)
if (testData.clubId) {
await testAPI('DELETE', `/stgl/club/${testData.clubId}`, {}, '删除测试社团');
}
console.log('🎯 测试数据清理完成');
}
// ============================== 主测试流程 ==============================
/**
* 主测试函数
*/
async function runTests() {
console.log('🚀 ========== 社团管理系统API测试开始 ==========');
console.log(`📅 测试时间: ${new Date().toLocaleString()}`);
console.log(`🌐 服务器地址: ${CONFIG.BASE_URL}`);
console.log(`👤 测试用户: ${CONFIG.TEST_USER.username}`);
const startTime = Date.now();
let successCount = 0;
let totalCount = 0;
try {
// 1. 用户认证
console.log('\n🔐 ========== 用户认证测试 ==========');
const loginSuccess = await login();
if (!loginSuccess) {
console.log('❌ 登录失败,退出测试');
return;
}
// 获取用户信息
const userInfo = await testAPI('GET', '/system/user/getInfo', {}, '获取用户信息');
if (userInfo && userInfo.data.user) {
console.log(`👤 当前用户: ${userInfo.data.user.userName} (${userInfo.data.user.nickName})`);
}
// 2. 社团信息管理测试
await testClubManagement();
// 3. 社团成员管理测试
await testMemberManagement();
// 4. 社团报名大厅测试
await testClubRegister();
// 5. 清理测试数据
await cleanupTestData();
} catch (error) {
console.error('❌ 测试过程中发生错误:', error.message);
}
const endTime = Date.now();
const duration = (endTime - startTime) / 1000;
console.log('\n🎯 ========== 社团管理系统API测试完成 ==========');
console.log(`⏱️ 测试耗时: ${duration.toFixed(2)} 秒`);
console.log('📊 测试结果总结:');
console.log(' ✅ 社团信息管理: 8个API接口');
console.log(' ✅ 社团成员管理: 8个API接口');
console.log(' ✅ 社团报名大厅: 8个API接口');
console.log(' 📝 总计: 24个API接口测试完成');
console.log('\n📋 API接口列表:');
console.log('【社团信息管理】');
console.log(' - GET /stgl/club/list 查询社团列表');
console.log(' - POST /stgl/club 新增社团');
console.log(' - GET /stgl/club/{id} 查询社团详情');
console.log(' - PUT /stgl/club 修改社团信息');
console.log(' - GET /stgl/club/options 查询可用社团选项');
console.log(' - GET /stgl/club/checkClubNameUnique 检查社团名称唯一性');
console.log(' - GET /stgl/club/myClubs 查询我管理的社团');
console.log(' - PUT /stgl/club/changeStatus 启用/禁用社团');
console.log('\n【社团成员管理】');
console.log(' - GET /stgl/member/list 查询成员列表');
console.log(' - POST /stgl/member 新增成员');
console.log(' - GET /stgl/member/{id} 查询成员详情');
console.log(' - PUT /stgl/member 修改成员信息');
console.log(' - GET /stgl/member/club/{clubId} 根据社团ID查询成员');
console.log(' - POST /stgl/member/batchAdd 批量添加成员');
console.log(' - PUT /stgl/member/updateRole 更新成员角色');
console.log(' - GET /stgl/member/checkMemberExists 检查用户是否已是成员');
console.log('\n【社团报名大厅】');
console.log(' - GET /stgl/register/list 查询社团报名大厅列表');
console.log(' - GET /stgl/register/listAll 查询社团报名大厅列表(不分页)');
console.log(' - GET /stgl/register/{clubId} 查询社团详细信息');
console.log(' - POST /stgl/register/join/{clubId} 报名加入社团');
console.log(' - GET /stgl/register/checkRegistered/{clubId} 检查报名状态');
console.log(' - GET /stgl/register/myClubs 查询我参与的社团');
console.log(' - GET /stgl/register/recommend 查询推荐社团');
console.log(' - POST /stgl/register/quit/{clubId} 退出社团');
}
// 导出模块
module.exports = {
runTests,
CONFIG,
testAPI,
login
};
// 如果直接运行此文件,则执行测试
if (require.main === module) {
runTests().catch(console.error);
}
-411
View File
@@ -1,411 +0,0 @@
const axios = require('axios');
const colors = require('colors');
// 测试配置
const CONFIG = {
baseURL: 'http://localhost/dev-api',
orderNo: 'RO20250126008',
username: 'zwc007',
password: 'admin123', // 需要根据实际密码调整
timeout: 10000,
};
/**
* 获取Token的通用函数
*/
async function getToken(username, password) {
const { getToken } = require('./quick-token-getter');
return await getToken(username, password);
}
/**
* 测试API调用的通用函数
*/
async function testAPI(endpoint, data, options = {}) {
try {
const response = await axios({
method: options.method || 'GET',
url: `${CONFIG.baseURL}${endpoint}`,
data: data,
headers: {
'Content-Type': 'application/json;charset=utf-8',
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
referer: 'http://localhost/login?redirect=%252Findex',
Authorization: options.token ? `Bearer ${options.token}` : undefined,
...options.headers,
},
timeout: CONFIG.timeout,
});
return {
success: true,
data: response.data,
status: response.status,
};
} catch (error) {
return {
success: false,
error: error.response?.data || error.message,
status: error.response?.status,
};
}
}
/**
* 测试工单RO20250126008的更新进度按钮
*/
async function testRO20250126008UpdateProgressButton() {
console.log('\n' + '='.repeat(80));
console.log(colors.cyan.bold(' 工单RO20250126008更新进度按钮测试'));
console.log('='.repeat(80));
console.log(colors.yellow(`测试时间: ${new Date().toLocaleString()}`));
console.log(colors.yellow(`目标工单: ${CONFIG.orderNo}`));
console.log(colors.yellow(`测试用户: ${CONFIG.username}`));
console.log('='.repeat(80));
const results = {
login: null,
userInfo: null,
targetOrder: null,
permissionCheck: null,
buttonDisplayConditions: null,
apiTest: null,
};
try {
// 步骤1:用户登录
console.log(colors.blue('\n【步骤1】用户登录验证'));
console.log('----------------------------------------');
try {
const token = await getToken(CONFIG.username, CONFIG.password);
results.login = { success: true, token: token.substring(0, 50) + '...' };
console.log(colors.green('✓ 登录成功'));
console.log(`Token: ${token.substring(0, 50)}...`);
} catch (error) {
results.login = { success: false, error: error.message };
console.log(colors.red('✗ 登录失败: ' + error.message));
return results;
}
// 步骤2:获取用户信息和权限
console.log(colors.blue('\n【步骤2】用户权限检查'));
console.log('----------------------------------------');
const userInfoResult = await testAPI('/system/user/getInfo', null, {
method: 'GET',
token: await getToken(CONFIG.username, CONFIG.password),
});
if (userInfoResult.success) {
const userInfo = userInfoResult.data.data || userInfoResult.data;
results.userInfo = {
userId: userInfo.user?.userId,
userName: userInfo.user?.userName,
nickName: userInfo.user?.nickName,
roles: userInfo.roles?.map((r) => r.roleKey) || [],
permissions: userInfo.permissions || [],
};
console.log(colors.green('✓ 用户信息获取成功'));
console.log(`用户ID: ${results.userInfo.userId}`);
console.log(`用户名: ${results.userInfo.userName}`);
console.log(`昵称: ${results.userInfo.nickName}`);
// 检查关键权限
const hasMaintenanceRole = results.userInfo.roles.includes('maintenance_worker');
const hasUpdateProgressPerm = results.userInfo.permissions.includes(
'pm:repairOrder:updateProgress',
);
console.log(
`维修人员角色: ${
hasMaintenanceRole ? colors.green('✓ 已配置') : colors.red('✗ 缺少')
}`,
);
console.log(
`更新进度权限: ${
hasUpdateProgressPerm ? colors.green('✓ 已配置') : colors.red('✗ 缺少')
}`,
);
results.permissionCheck = {
hasMaintenanceRole,
hasUpdateProgressPerm,
};
} else {
console.log(colors.red('✗ 用户信息获取失败: ' + JSON.stringify(userInfoResult.error)));
}
// 步骤3:查找目标工单
console.log(colors.blue('\n【步骤3】查找目标工单RO20250126008'));
console.log('----------------------------------------');
const orderListResult = await testAPI('/pm/repairOrder/list', null, {
method: 'GET',
token: await getToken(CONFIG.username, CONFIG.password),
});
if (orderListResult.success) {
const orderData = orderListResult.data;
const orders = orderData.rows || orderData.data || [];
console.log(colors.green('✓ 工单列表获取成功'));
console.log(`可见工单总数: ${orderData.total || orders.length}`);
// 查找目标工单
const targetOrder = orders.find((order) => order.orderNo === CONFIG.orderNo);
if (targetOrder) {
results.targetOrder = {
id: targetOrder.id,
orderNo: targetOrder.orderNo,
title: targetOrder.title,
orderStatus: targetOrder.orderStatus,
assigneeId: targetOrder.assigneeId,
assigneeName: targetOrder.assigneeName,
repairLocation: targetOrder.repairLocation,
createTime: targetOrder.createTime,
};
console.log(colors.green(`✓ 找到目标工单: ${CONFIG.orderNo}`));
console.log(`工单标题: ${targetOrder.title}`);
console.log(`工单状态: ${getStatusText(targetOrder.orderStatus)}`);
console.log(
`维修负责人: ${targetOrder.assigneeName || '未分派'} (ID: ${
targetOrder.assigneeId || 'null'
})`,
);
console.log(`报修地点: ${targetOrder.repairLocation}`);
// 检查按钮显示条件
const isStatus2 = targetOrder.orderStatus === '2' || targetOrder.orderStatus === 2;
const isAssignedToZwc007 = targetOrder.assigneeId === results.userInfo?.userId;
console.log('\n按钮显示条件检查:');
console.log(
`工单状态为已分派(2): ${
isStatus2 ? colors.green('✓ 满足') : colors.red('✗ 不满足')
} (当前: ${targetOrder.orderStatus})`,
);
console.log(
`zwc007是负责人: ${
isAssignedToZwc007 ? colors.green('✓ 满足') : colors.red('✗ 不满足')
} (负责人ID: ${targetOrder.assigneeId})`,
);
console.log(
`维修人员角色: ${
results.permissionCheck?.hasMaintenanceRole
? colors.green('✓ 满足')
: colors.red('✗ 不满足')
}`,
);
console.log(
`更新进度权限: ${
results.permissionCheck?.hasUpdateProgressPerm
? colors.green('✓ 满足')
: colors.red('✗ 不满足')
}`,
);
const allConditionsMet =
isStatus2 &&
isAssignedToZwc007 &&
results.permissionCheck?.hasMaintenanceRole &&
results.permissionCheck?.hasUpdateProgressPerm;
console.log(
`\n更新进度按钮应该显示: ${
allConditionsMet ? colors.green('是 🎉') : colors.red('否 ❌')
}`,
);
results.buttonDisplayConditions = {
isStatus2,
isAssignedToZwc007,
allConditionsMet,
};
} else {
console.log(colors.red(`✗ 未找到工单${CONFIG.orderNo}`));
console.log(colors.yellow('可能原因:'));
console.log(colors.yellow('1. 工单不存在'));
console.log(colors.yellow('2. zwc007没有查看该工单的权限'));
console.log(colors.yellow('3. 数据权限过滤导致工单不可见'));
results.targetOrder = { found: false };
}
} else {
console.log(colors.red('✗ 工单列表获取失败: ' + JSON.stringify(orderListResult.error)));
}
// 步骤4:测试更新进度API(如果工单存在且条件满足)
console.log(colors.blue('\n【步骤4】更新进度API测试'));
console.log('----------------------------------------');
if (results.targetOrder?.found !== false && results.targetOrder?.id) {
console.log(`测试工单: ${results.targetOrder.orderNo} (ID: ${results.targetOrder.id})`);
// 构造测试用的进度数据
const progressData = {
orderId: results.targetOrder.id,
progressType: '5', // 维修进度
progressStatus: '测试进度状态',
progressDetail: `工单${
CONFIG.orderNo
}进度更新测试 - ${new Date().toLocaleString()}`,
progressTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
locationInfo: results.targetOrder.repairLocation || '测试位置',
estimatedDuration: 60,
nextStep: '继续测试验证',
remark: 'API权限验证测试',
};
const apiResult = await testAPI('/pm/repairOrder/updateProgress', progressData, {
method: 'POST',
token: await getToken(CONFIG.username, CONFIG.password),
});
if (apiResult.success) {
console.log(colors.green('✓ 更新进度API调用成功'));
console.log('响应数据:', JSON.stringify(apiResult.data, null, 2));
results.apiTest = {
success: true,
response: apiResult.data,
};
} else {
console.log(colors.red('✗ 更新进度API调用失败'));
console.log('错误信息:', JSON.stringify(apiResult.error, null, 2));
results.apiTest = {
success: false,
error: apiResult.error,
};
}
} else {
console.log(colors.yellow('⚠ 跳过API测试:工单不存在或条件不满足'));
results.apiTest = { skipped: true, reason: '工单不存在或条件不满足' };
}
// 步骤5:生成测试总结
console.log(colors.blue('\n【步骤5】测试结果总结'));
console.log('----------------------------------------');
console.log(colors.cyan('测试结果汇总:'));
console.log(
`1. 用户登录: ${
results.login?.success ? colors.green('✓ 成功') : colors.red('✗ 失败')
}`,
);
console.log(
`2. 权限配置: ${
results.permissionCheck?.hasMaintenanceRole &&
results.permissionCheck?.hasUpdateProgressPerm
? colors.green('✓ 完整')
: colors.red('✗ 缺失')
}`,
);
console.log(
`3. 目标工单: ${
results.targetOrder?.found !== false
? colors.green('✓ 找到')
: colors.red('✗ 未找到')
}`,
);
console.log(
`4. 按钮显示: ${
results.buttonDisplayConditions?.allConditionsMet
? colors.green('✓ 应显示')
: colors.red('✗ 不显示')
}`,
);
console.log(
`5. API权限: ${
results.apiTest?.success
? colors.green('✓ 正常')
: results.apiTest?.skipped
? colors.yellow('⚠ 跳过')
: colors.red('✗ 失败')
}`,
);
// 提供修复建议
if (!results.buttonDisplayConditions?.allConditionsMet) {
console.log(colors.yellow('\n修复建议:'));
if (!results.permissionCheck?.hasMaintenanceRole) {
console.log(colors.yellow('- 需要为zwc007分配maintenance_worker角色'));
}
if (!results.permissionCheck?.hasUpdateProgressPerm) {
console.log(
colors.yellow('- 需要为维修人员角色分配pm:repairOrder:updateProgress权限'),
);
}
if (!results.buttonDisplayConditions?.isStatus2) {
console.log(colors.yellow(`- 需要将工单${CONFIG.orderNo}状态设置为已分派(2)`));
}
if (!results.buttonDisplayConditions?.isAssignedToZwc007) {
console.log(colors.yellow(`- 需要将工单${CONFIG.orderNo}分派给zwc007`));
}
console.log(
colors.yellow('- 执行 test/RO20250126008-update-progress-fix.sql 脚本进行修复'),
);
}
} catch (error) {
console.log(colors.red('测试过程中发生错误: ' + error.message));
results.error = error.message;
}
console.log('\n' + '='.repeat(80));
console.log(colors.cyan.bold(' 测试完成'));
console.log('='.repeat(80));
return results;
}
/**
* 获取状态文本
*/
function getStatusText(status) {
const statusMap = {
1: '待分派',
2: '已分派',
3: '进行中',
4: '已完成',
5: '已评价',
6: '已归档',
9: '已取消',
};
return statusMap[status] || `状态${status}`;
}
// 执行测试
if (require.main === module) {
testRO20250126008UpdateProgressButton()
.then((results) => {
console.log('\n测试结果已保存');
// 输出最终结论
const success =
results.buttonDisplayConditions?.allConditionsMet && results.apiTest?.success;
if (success) {
console.log(
colors.green.bold(
'\n🎉 测试通过!工单RO20250126008的更新进度按钮应该可以正常显示和使用。',
),
);
} else {
console.log(colors.red.bold('\n❌ 测试未通过!需要执行修复脚本解决问题。'));
}
})
.catch((error) => {
console.error(colors.red('测试执行失败:'), error);
});
}
module.exports = {
testRO20250126008UpdateProgressButton,
};
-276
View File
@@ -1,276 +0,0 @@
-- ==========================================
-- 工单RO20250126008更新进度按钮修复脚本
-- 功能:针对具体工单RO20250126008和维修人员zwc007的权限问题修复
-- 时间:2025-06-30 18:45:00
-- 说明:精准定位和修复特定工单的显示问题
-- ==========================================
-- 第一部分:工单RO20250126008诊断查询
-- ==========================================
-- 1. 检查工单RO20250126008的基本信息和当前状态
SELECT '=== 工单RO20250126008基本信息 ===' as message;
SELECT
ro.id,
ro.order_no,
ro.title,
ro.order_status,
CASE ro.order_status
WHEN '1' THEN '待分派'
WHEN '2' THEN '已分派 ✓ (应显示更新进度按钮)'
WHEN '3' THEN '进行中'
WHEN '4' THEN '已完成'
WHEN '5' THEN '已评价'
WHEN '6' THEN '已归档'
WHEN '9' THEN '已取消'
ELSE CONCAT('未知状态: ', ro.order_status)
END as status_description,
ro.assignee_id,
u_assignee.user_name as assignee_name,
u_assignee.nick_name as assignee_nick_name,
ro.assign_time,
ro.create_time,
ro.repair_location,
ro.priority_level
FROM pm_repair_order ro
LEFT JOIN sys_user u_assignee ON ro.assignee_id = u_assignee.user_id
WHERE ro.order_no = 'RO20250126008';
-- 2. 检查zwc007用户信息
SELECT '=== 用户zwc007信息检查 ===' as message;
SELECT
u.user_id,
u.user_name,
u.nick_name,
u.status as user_status,
CASE u.status WHEN '0' THEN '正常' WHEN '1' THEN '停用' ELSE '未知' END as status_text
FROM sys_user u
WHERE u.user_name = 'zwc007';
-- 3. 检查当前分派情况匹配性
SELECT '=== 工单分派匹配性检查 ===' as message;
SELECT
ro.order_no,
ro.assignee_id as current_assignee_id,
u_current.user_name as current_assignee_name,
u_zwc.user_id as zwc007_user_id,
u_zwc.user_name as zwc007_name,
CASE
WHEN ro.assignee_id = u_zwc.user_id THEN '✓ zwc007是负责人'
WHEN ro.assignee_id IS NULL THEN '✗ 工单未分派'
ELSE CONCAT('✗ 工单分派给其他人: ', u_current.user_name)
END as assignment_status,
CASE
WHEN ro.order_status = '2' AND ro.assignee_id = u_zwc.user_id THEN '✅ 应显示更新进度按钮'
WHEN ro.order_status != '2' THEN CONCAT('❌ 工单状态不是已分派: ', ro.order_status)
WHEN ro.assignee_id != u_zwc.user_id THEN '❌ zwc007不是负责人'
ELSE '❌ 不满足显示条件'
END as button_display_status
FROM pm_repair_order ro
LEFT JOIN sys_user u_current ON ro.assignee_id = u_current.user_id
CROSS JOIN (SELECT user_id, user_name FROM sys_user WHERE user_name = 'zwc007') u_zwc
WHERE ro.order_no = 'RO20250126008';
-- 4. 检查zwc007的角色和权限
SELECT '=== zwc007角色和权限检查 ===' as message;
SELECT
u.user_name,
r.role_name,
r.role_key,
CASE WHEN r.role_key = 'maintenance_worker' THEN '✓ 维修人员角色' ELSE '其他角色' END as role_type
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
WHERE u.user_name = 'zwc007'
ORDER BY r.role_key;
-- 检查关键权限
SELECT '=== zwc007更新进度权限检查 ===' as message;
SELECT
m.menu_name,
m.perms,
m.menu_type,
CASE WHEN m.perms = 'pm:repairOrder:updateProgress' THEN '*** 关键权限 ***' ELSE '' END as importance
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
JOIN sys_role_menu rm ON r.role_id = rm.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'zwc007'
AND m.perms = 'pm:repairOrder:updateProgress';
-- 第二部分:问题修复操作
-- ==========================================
-- 修复1:确保zwc007有维修人员角色(如果没有)
INSERT IGNORE INTO sys_user_role (user_id, role_id)
SELECT u.user_id, r.role_id
FROM sys_user u, sys_role r
WHERE u.user_name = 'zwc007'
AND r.role_key = 'maintenance_worker'
AND NOT EXISTS (
SELECT 1 FROM sys_user_role ur2
WHERE ur2.user_id = u.user_id AND ur2.role_id = r.role_id
);
-- 修复2:确保updateProgress权限存在
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
)
SELECT
2117, '更新进度', 2110, 6, '', '',
'', 1, 0, 'F', '0', '0',
'pm:repairOrder:updateProgress', '#', 1, now(), 1, now(), '更新工单进度权限'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'pm:repairOrder:updateProgress');
-- 修复3:为维修人员角色分配权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT r.role_id, m.menu_id
FROM sys_role r, sys_menu m
WHERE r.role_key = 'maintenance_worker'
AND m.perms = 'pm:repairOrder:updateProgress';
-- 修复4:将工单RO20250126008分派给zwc007(关键修复)
UPDATE pm_repair_order
SET
assignee_id = (SELECT user_id FROM sys_user WHERE user_name = 'zwc007'),
assign_time = CASE WHEN assign_time IS NULL THEN now() ELSE assign_time END,
order_status = '2', -- 设置为已分派状态
update_time = now(),
update_by = 1
WHERE order_no = 'RO20250126008'
AND EXISTS (SELECT 1 FROM sys_user WHERE user_name = 'zwc007');
-- 修复5:记录分派操作到进度表
INSERT INTO pm_repair_progress (
order_id, progress_type, progress_status, progress_detail,
progress_time, location_info, remark,
tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted
)
SELECT
ro.id,
'2', -- 分派类型
'已分派给维修人员',
CONCAT('工单已分派给维修人员zwc007,负责处理报修问题'),
now(),
ro.repair_location,
'系统自动分派 - 修复更新进度按钮显示问题',
'000000', 103, 1, now(), 1, now(), 0
FROM pm_repair_order ro
WHERE ro.order_no = 'RO20250126008'
AND NOT EXISTS (
SELECT 1 FROM pm_repair_progress pp
WHERE pp.order_id = ro.id
AND pp.progress_type = '2'
AND pp.progress_detail LIKE '%zwc007%'
);
-- 第三部分:修复后验证
-- ==========================================
-- 验证1:确认工单状态和分派
SELECT '=== 修复后:工单状态验证 ===' as message;
SELECT
ro.order_no,
ro.title,
ro.order_status,
CASE ro.order_status
WHEN '2' THEN '✅ 已分派状态 - 应显示更新进度按钮'
ELSE CONCAT('❌ 状态异常: ', ro.order_status)
END as status_check,
ro.assignee_id,
u.user_name as assignee_name,
u.nick_name as assignee_nick_name,
CASE
WHEN u.user_name = 'zwc007' THEN '✅ zwc007是负责人'
ELSE '❌ 负责人不是zwc007'
END as assignee_check,
ro.assign_time
FROM pm_repair_order ro
LEFT JOIN sys_user u ON ro.assignee_id = u.user_id
WHERE ro.order_no = 'RO20250126008';
-- 验证2:确认zwc007权限配置
SELECT '=== 修复后:zwc007权限验证 ===' as message;
SELECT
'维修人员角色' as permission_type,
CASE
WHEN COUNT(*) > 0 THEN '✅ 已配置'
ELSE '❌ 未配置'
END as status
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
WHERE u.user_name = 'zwc007' AND r.role_key = 'maintenance_worker'
UNION ALL
SELECT
'更新进度权限' as permission_type,
CASE
WHEN COUNT(*) > 0 THEN '✅ 已配置'
ELSE '❌ 未配置'
END as status
FROM sys_user u
JOIN sys_user_role ur ON u.user_id = ur.user_id
JOIN sys_role r ON ur.role_id = r.role_id
JOIN sys_role_menu rm ON r.role_id = rm.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = 'zwc007'
AND m.perms = 'pm:repairOrder:updateProgress';
-- 验证3:确认按钮显示条件
SELECT '=== 修复后:按钮显示条件验证 ===' as message;
SELECT
ro.order_no,
'工单状态 = 2 (已分派)' as condition_1,
CASE WHEN ro.order_status = '2' THEN '✅ 满足' ELSE '❌ 不满足' END as check_1,
'用户是维修负责人' as condition_2,
CASE WHEN ro.assignee_id = u.user_id THEN '✅ 满足' ELSE '❌ 不满足' END as check_2,
'用户有maintenance_worker角色' as condition_3,
CASE WHEN role_count.cnt > 0 THEN '✅ 满足' ELSE '❌ 不满足' END as check_3,
'用户有updateProgress权限' as condition_4,
CASE WHEN perm_count.cnt > 0 THEN '✅ 满足' ELSE '❌ 不满足' END as check_4,
CASE
WHEN ro.order_status = '2'
AND ro.assignee_id = u.user_id
AND role_count.cnt > 0
AND perm_count.cnt > 0
THEN '🎉 所有条件满足 - 应显示更新进度按钮'
ELSE '❌ 条件不满足 - 按钮不会显示'
END as final_result
FROM pm_repair_order ro
JOIN sys_user u ON u.user_name = 'zwc007'
LEFT JOIN (
SELECT ur.user_id, COUNT(*) as cnt
FROM sys_user_role ur
JOIN sys_role r ON ur.role_id = r.role_id
WHERE r.role_key = 'maintenance_worker'
GROUP BY ur.user_id
) role_count ON role_count.user_id = u.user_id
LEFT JOIN (
SELECT ur.user_id, COUNT(*) as cnt
FROM sys_user_role ur
JOIN sys_role_menu rm ON ur.role_id = rm.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE m.perms = 'pm:repairOrder:updateProgress'
GROUP BY ur.user_id
) perm_count ON perm_count.user_id = u.user_id
WHERE ro.order_no = 'RO20250126008';
-- 第四部分:操作指导和确认
-- ==========================================
SELECT '=== 修复完成提示 ===' as message,
'1. 工单RO20250126008已分派给zwc007' as step1,
'2. 工单状态已设置为已分派(状态码2)' as step2,
'3. zwc007已配置维修人员角色和权限' as step3,
'4. 请重启前后端服务清除权限缓存' as step4,
'5. 使用zwc007账号登录查看该工单' as step5,
'6. 确认更新进度按钮是否显示' as step6;
-- 显示需要重启服务的提醒
SELECT '⚠️ 重要提醒' as alert_title,
'权限配置修改后必须重启前后端服务' as alert_message,
'用户需要重新登录以刷新权限缓存' as additional_note;
-251
View File
@@ -1,251 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
学生信息管理SQL修复最终验证脚本
测试修复后的XML映射文件是否能正确生成SQL
"""
import requests
import json
import time
from datetime import datetime
# 配置
BASE_URL = "http://localhost:8080"
LOGIN_URL = f"{BASE_URL}/auth/login"
STUDENT_LIST_URL = f"{BASE_URL}/xg-xx/studentInfo/list"
# 测试数据
LOGIN_DATA = {
"clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
"username": "admin",
"password": "admin123",
"grant_type": "password",
"tenantId": "000000"
}
def test_server_connection():
"""测试服务器连接"""
print("🔍 测试服务器连接...")
try:
response = requests.get(f"{BASE_URL}/health", timeout=5)
if response.status_code == 200:
print("✅ 服务器连接正常")
return True
else:
print(f"❌ 服务器响应异常: {response.status_code}")
return False
except Exception as e:
print(f"❌ 服务器连接失败: {e}")
return False
def get_auth_token():
"""获取认证Token"""
print("🔐 获取认证Token...")
try:
response = requests.post(LOGIN_URL, json=LOGIN_DATA, timeout=10)
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
token = result['data']['access_token']
print("✅ Token获取成功")
return token
else:
print(f"❌ 登录失败: {result.get('msg', '未知错误')}")
return None
else:
print(f"❌ 登录请求失败: {response.status_code}")
return None
except Exception as e:
print(f"❌ 登录异常: {e}")
return None
def test_student_list_without_params(token):
"""测试不带参数的学生列表查询"""
print("📋 测试不带参数的学生列表查询...")
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
'referer': 'http://localhost',
'tenantId': '000000'
}
params = {
'pageNum': 1,
'pageSize': 10
}
try:
response = requests.get(STUDENT_LIST_URL, params=params, headers=headers, timeout=10)
print(f"响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
print("✅ 基础查询成功")
data = result.get('data', {})
total = data.get('total', 0)
rows = data.get('rows', [])
print(f"📊 查询结果: 总数={total}, 当前页记录数={len(rows)}")
return True
else:
print(f"❌ 查询失败: {result.get('msg', '未知错误')}")
return False
else:
print(f"❌ 请求失败: {response.status_code}")
print(f"响应内容: {response.text}")
return False
except Exception as e:
print(f"❌ 查询异常: {e}")
return False
def test_student_list_with_gender_filter(token):
"""测试带性别过滤的学生列表查询(这是之前报错的场景)"""
print("🔍 测试带性别过滤的学生列表查询...")
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
'referer': 'http://localhost',
'tenantId': '000000'
}
params = {
'pageNum': 1,
'pageSize': 10,
'gender': '1' # 这是之前导致SQL重复WHERE的参数
}
try:
response = requests.get(STUDENT_LIST_URL, params=params, headers=headers, timeout=10)
print(f"响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
print("✅ 性别过滤查询成功 - SQL重复WHERE问题已修复!")
data = result.get('data', {})
total = data.get('total', 0)
rows = data.get('rows', [])
print(f"📊 查询结果: 总数={total}, 当前页记录数={len(rows)}")
return True
else:
print(f"❌ 查询失败: {result.get('msg', '未知错误')}")
return False
else:
print(f"❌ 请求失败: {response.status_code}")
print(f"响应内容: {response.text}")
return False
except Exception as e:
print(f"❌ 查询异常: {e}")
return False
def test_student_list_with_multiple_filters(token):
"""测试多条件过滤查询"""
print("🔍 测试多条件过滤查询...")
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
'referer': 'http://localhost',
'tenantId': '000000'
}
params = {
'pageNum': 1,
'pageSize': 10,
'gender': '1',
'studentStatus': '1',
'name': '张'
}
try:
response = requests.get(STUDENT_LIST_URL, params=params, headers=headers, timeout=10)
print(f"响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
print("✅ 多条件过滤查询成功")
data = result.get('data', {})
total = data.get('total', 0)
rows = data.get('rows', [])
print(f"📊 查询结果: 总数={total}, 当前页记录数={len(rows)}")
return True
else:
print(f"❌ 查询失败: {result.get('msg', '未知错误')}")
return False
else:
print(f"❌ 请求失败: {response.status_code}")
print(f"响应内容: {response.text}")
return False
except Exception as e:
print(f"❌ 查询异常: {e}")
return False
def main():
"""主函数"""
print("="*80)
print("🎯 学生信息管理SQL修复最终验证")
print("="*80)
print(f"⏰ 测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# 1. 测试服务器连接
if not test_server_connection():
print("❌ 服务器未启动,请先启动后端服务")
return
print()
# 2. 获取认证Token
token = get_auth_token()
if not token:
print("❌ 无法获取认证Token,测试中止")
return
print()
# 3. 测试基础查询
if not test_student_list_without_params(token):
print("❌ 基础查询失败")
return
print()
# 4. 测试性别过滤查询(关键测试)
if not test_student_list_with_gender_filter(token):
print("❌ 性别过滤查询失败 - SQL重复WHERE问题可能仍存在")
return
print()
# 5. 测试多条件过滤查询
if not test_student_list_with_multiple_filters(token):
print("❌ 多条件过滤查询失败")
return
print()
print("="*80)
print("🎉 所有测试通过!SQL重复WHERE问题已完全修复!")
print("="*80)
print("📋 修复总结:")
print(" 1. ✅ 修复了XML映射文件中的SQL语句结构")
print(" 2. ✅ 使用条件判断避免重复WHERE子句")
print(" 3. ✅ 验证了单条件和多条件查询都正常工作")
print(" 4. ✅ 确认MyBatis-Plus的customSqlSegment正确集成")
print()
print("🔧 技术细节:")
print(" - 修改前: WHERE deleted = 0 AND tenant_id = '000000' ${ew.customSqlSegment}")
print(" - 修改后: WHERE deleted = 0 AND tenant_id = '000000'")
print(" <if test=\"ew != null and ew.sqlSegment != null\">")
print(" AND ${ew.sqlSegment}")
print(" </if>")
print()
if __name__ == "__main__":
main()
-195
View File
@@ -1,195 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SQL修复验证测试
直接测试学生信息管理SQL语句是否修复了重复WHERE子句问题
"""
import time
import requests
import json
from datetime import datetime
# 配置
BASE_URL = "http://localhost:8080"
HEADERS = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
def test_service_health():
"""测试服务健康状态"""
print("=== 测试服务健康状态 ===")
try:
response = requests.get(f"{BASE_URL}/", headers=HEADERS, timeout=5)
print(f"服务状态码: {response.status_code}")
print(f"服务响应: {response.text[:100]}...")
if response.status_code == 200:
print("✅ 服务正常运行")
return True
else:
print(f"⚠️ 服务状态异常: {response.status_code}")
return False
except Exception as e:
print(f"❌ 服务连接失败: {e}")
return False
def test_sql_error_pattern():
"""测试SQL错误模式"""
print("\n=== 测试SQL错误模式 ===")
# 测试会触发SQL错误的请求
search_params = {
'pageNum': 1,
'pageSize': 10,
'gender': '男', # 这个参数会触发QueryWrapper生成WHERE子句
}
try:
response = requests.get(f"{BASE_URL}/xg/studentInfo/list",
params=search_params,
headers=HEADERS,
timeout=10)
print(f"请求状态码: {response.status_code}")
print(f"响应内容: {response.text[:400]}...")
if response.status_code == 200:
data = response.json()
if data.get('code') == 401:
print("✅ 正常返回401认证错误(预期行为)")
return True
elif data.get('code') == 500:
error_msg = data.get('msg', '')
if "WHERE deleted = 0 AND tenant_id = '000000' WHERE" in error_msg:
print("❌ 仍然存在重复WHERE子句错误")
print("🔧 XML映射文件修复可能没有生效")
return False
else:
print("❌ 其他500错误")
print(f"错误信息: {error_msg}")
return False
else:
print(f"⚠️ 意外的响应码: {data.get('code')}")
return False
elif response.status_code == 500:
# 直接500错误
response_text = response.text
if "WHERE deleted = 0 AND tenant_id = '000000' WHERE" in response_text:
print("❌ 仍然存在重复WHERE子句错误")
print("🔧 XML映射文件修复可能没有生效")
return False
else:
print("❌ 其他500错误")
print(f"错误详情: {response_text[:200]}...")
return False
else:
print(f"⚠️ 预期200状态码,实际: {response.status_code}")
return False
except Exception as e:
print(f"❌ 请求异常: {e}")
return False
def test_xml_file_content():
"""验证XML文件内容"""
print("\n=== 验证XML文件内容 ===")
xml_file_path = "../backend/ruoyi-modules/ruoyi-xg-xx/src/main/resources/mapper/xg/XgStudentInfoMapper.xml"
try:
with open(xml_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查是否包含正确的SQL结构
if "WHERE deleted = 0 AND tenant_id = '000000'\n ${ew.customSqlSegment}" in content:
print("✅ XML文件包含正确的SQL结构")
return True
elif "<where>" in content and "${ew.customSqlSegment}" in content:
print("❌ XML文件仍然使用<where>标签包裹customSqlSegment")
print("🔧 需要手动修复XML文件")
return False
else:
print("⚠️ XML文件结构不明确")
print("🔍 让我检查具体内容...")
# 查找selectVoPage方法
if "selectVoPage" in content:
lines = content.split('\n')
for i, line in enumerate(lines):
if "selectVoPage" in line:
print(f"找到selectVoPage方法,周围内容:")
start = max(0, i-2)
end = min(len(lines), i+10)
for j in range(start, end):
print(f"{j+1:3d}: {lines[j]}")
break
return False
except Exception as e:
print(f"❌ 读取XML文件失败: {e}")
return False
def show_correct_vs_incorrect():
"""显示正确与错误的SQL结构对比"""
print("\n=== SQL结构对比 ===")
print("✅ 正确的XML结构:")
print("""
<select id="selectVoPage" resultMap="XgStudentInfoResult">
SELECT ... FROM xg_student_info
WHERE deleted = 0 AND tenant_id = '000000'
${ew.customSqlSegment}
</select>
""")
print("❌ 错误的XML结构:")
print("""
<select id="selectVoPage" resultMap="XgStudentInfoResult">
SELECT ... FROM xg_student_info
<where>
deleted = 0 AND tenant_id = '000000'
${ew.customSqlSegment}
</where>
</select>
""")
print("🔍 错误原因:")
print("- MyBatis-Plus的${ew.customSqlSegment}可能已经包含WHERE关键字")
print("- 使用<where>标签包裹会导致重复的WHERE子句")
print("- 正确做法是直接在SQL中写WHERE条件,然后跟${ew.customSqlSegment}")
def main():
"""主函数"""
print("🚀 SQL修复验证测试")
print("=" * 50)
# 测试服务健康状态
if not test_service_health():
print("❌ 服务未正常运行,无法进行测试")
return
# 验证XML文件内容
xml_ok = test_xml_file_content()
# 测试SQL错误模式
sql_ok = test_sql_error_pattern()
# 显示结构对比
show_correct_vs_incorrect()
print("\n" + "=" * 50)
print("📊 测试结果总结:")
print(f"XML文件结构: {'✅ 正确' if xml_ok else '❌ 错误'}")
print(f"SQL执行结果: {'✅ 正常' if sql_ok else '❌ 异常'}")
if xml_ok and sql_ok:
print("🎉 SQL修复验证通过!")
elif xml_ok and not sql_ok:
print("⚠️ XML文件已修复,但可能需要重启服务")
else:
print("❌ 需要检查XML文件修复")
if __name__ == "__main__":
main()
-198
View File
@@ -1,198 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
学生信息管理SQL语法错误修复验证脚本
验证修复后的XML映射文件是否能正确处理查询请求
日期: 2025-01-15
修复内容: 解决 WHERE...AND ORDER BY 语法错误
第二次修复: 正确区分WHERE条件和ORDER BY子句的处理
"""
import requests
import json
import sys
from datetime import datetime
# 配置信息
BASE_URL = "http://localhost:8080"
LOGIN_URL = f"{BASE_URL}/auth/login"
STUDENT_LIST_URL = f"{BASE_URL}/xg-xx/studentInfo/list"
# 登录配置
LOGIN_DATA = {
"username": "admin",
"password": "admin123",
"clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
"grantType": "password"
}
# 请求头配置
HEADERS = {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"referer": "http://localhost",
"clientid": "e5cd7e4891bf95d1d19206ce24a7b32e"
}
def print_test_header(title):
"""打印测试标题"""
print(f"\n{'='*60}")
print(f"🧪 {title}")
print(f"{'='*60}")
def print_result(success, message, details=None):
"""打印测试结果"""
status = "✅ 成功" if success else "❌ 失败"
print(f"{status}: {message}")
if details:
print(f"详情: {details}")
def login():
"""登录获取token"""
print_test_header("步骤1: 用户登录")
try:
response = requests.post(LOGIN_URL, json=LOGIN_DATA, headers=HEADERS, timeout=10)
print(f"登录请求状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
if data.get("code") == 200:
token = data.get("data", {}).get("access_token")
if token:
print_result(True, "登录成功", f"Token: {token[:20]}...")
return token
else:
print_result(False, "登录响应中没有token", data)
else:
print_result(False, "登录失败", data.get("msg", "未知错误"))
else:
print_result(False, f"登录请求失败", f"状态码: {response.status_code}")
except Exception as e:
print_result(False, "登录异常", str(e))
return None
def test_student_list_query(token, test_name, params=None):
"""测试学生列表查询"""
print_test_header(f"步骤2: {test_name}")
# 设置认证头
test_headers = HEADERS.copy()
test_headers["Authorization"] = f"Bearer {token}"
try:
# 发送查询请求
response = requests.get(
STUDENT_LIST_URL,
params=params or {},
headers=test_headers,
timeout=10
)
print(f"查询请求状态码: {response.status_code}")
print(f"查询参数: {params or '无'}")
if response.status_code == 200:
data = response.json()
if data.get("code") == 200:
rows = data.get("rows", [])
total = data.get("total", 0)
print_result(True, f"查询成功", f"返回 {len(rows)} 条记录,总计 {total} 条")
return True, data
else:
print_result(False, "查询失败", data.get("msg", "未知错误"))
return False, data
else:
print_result(False, f"查询请求失败", f"状态码: {response.status_code}")
try:
error_data = response.json()
print(f"错误详情: {error_data}")
except:
print(f"响应内容: {response.text[:200]}...")
return False, None
except Exception as e:
print_result(False, "查询异常", str(e))
return False, None
def main():
"""主函数"""
print("🔍 学生信息管理SQL语法错误修复验证")
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"目标: 验证 WHERE...AND ORDER BY 语法错误修复")
print(f"修复版本: 第二次修复 - 正确区分WHERE条件和ORDER BY子句")
# 1. 登录获取token
token = login()
if not token:
print("\n❌ 登录失败,无法继续测试")
sys.exit(1)
# 2. 测试无查询条件的列表查询(最容易触发SQL语法错误)
success1, data1 = test_student_list_query(
token,
"无查询条件的列表查询(最容易触发SQL语法错误)",
{"pageNum": 1, "pageSize": 10}
)
# 3. 测试有查询条件的搜索
success2, data2 = test_student_list_query(
token,
"有查询条件的搜索",
{"pageNum": 1, "pageSize": 10, "name": "测试"}
)
# 4. 测试排序功能
success3, data3 = test_student_list_query(
token,
"排序功能测试",
{"pageNum": 1, "pageSize": 10, "orderByColumn": "create_time", "isAsc": "desc"}
)
# 5. 测试复合查询
success4, data4 = test_student_list_query(
token,
"复合查询测试",
{
"pageNum": 1,
"pageSize": 10,
"gender": "1",
"orderByColumn": "student_no",
"isAsc": "asc"
}
)
# 总结测试结果
print_test_header("测试结果总结")
tests = [
("无查询条件列表查询", success1),
("有查询条件搜索", success2),
("排序功能测试", success3),
("复合查询测试", success4)
]
passed = sum(1 for _, success in tests if success)
total = len(tests)
print(f"📊 测试统计: {passed}/{total} 通过")
for test_name, success in tests:
status = "✅" if success else "❌"
print(f"{status} {test_name}")
if passed == total:
print(f"\n🎉 所有测试通过!SQL语法错误修复成功!")
print("✅ WHERE...AND ORDER BY 语法错误已解决")
print("✅ 学生信息管理页面搜索功能恢复正常")
else:
print(f"\n⚠️ 部分测试失败,需要进一步排查问题")
return passed == total
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
-331
View File
@@ -1,331 +0,0 @@
const axios = require('axios');
const colors = require('colors');
// API格式验证测试脚本
// 用于验证所有接口是否符合统一响应格式规范
// 获取Token
async function getToken() {
try {
const { getToken } = require('./quick-token-getter');
return await getToken();
} catch (error) {
console.log('❌ 获取Token失败'.red);
return null;
}
}
// 验证响应格式
function validateResponseFormat(response, apiName, expectedType) {
const issues = [];
// 检查基础字段
if (typeof response.code !== 'number') {
issues.push('❌ code字段缺失或类型错误(应为number)');
}
if (typeof response.msg !== 'string') {
issues.push('❌ msg字段缺失或类型错误(应为string)');
}
if (!response.hasOwnProperty('data')) {
issues.push('❌ data字段缺失');
}
// 检查成功状态码
if (response.code === 200) {
// 根据期望类型验证字段
switch (expectedType) {
case 'paginated':
// 分页接口直接在顶层包含total和rows字段
if (typeof response.total !== 'number') {
issues.push('❌ 分页接口缺少total字段(应为number)');
}
if (!Array.isArray(response.rows)) {
issues.push('❌ 分页接口缺少rows字段(应为array)');
}
break;
case 'array':
if (!Array.isArray(response.data)) {
issues.push('❌ 列表接口data应为数组');
}
break;
case 'object':
if (
response.data === null ||
typeof response.data !== 'object' ||
Array.isArray(response.data)
) {
issues.push('❌ 单条数据接口data应为对象(非null)');
}
break;
case 'null':
if (response.data !== null) {
issues.push('❌ 操作类接口data应为null');
}
break;
}
}
return issues;
}
// API测试用例
const apiTestCases = [
{
name: '报修工单列表(分页)',
url: '/pm/repairOrder/list',
method: 'GET',
data: { pageNum: 1, pageSize: 10 },
expectedType: 'paginated',
permission: 'pm:repairOrder:list',
},
{
name: '维修分类下拉选项',
url: '/pm/repairCategory/options',
method: 'GET',
data: null,
expectedType: 'array',
permission: 'pm:category:options',
},
{
name: '报修统计数据',
url: '/pm/repairOrder/statistics',
method: 'GET',
data: null,
expectedType: 'object',
permission: 'pm:repairOrder:statistics',
},
{
name: '系统用户信息',
url: '/system/user/getInfo',
method: 'GET',
data: null,
expectedType: 'object',
permission: 'system:user:query',
},
{
name: '系统字典数据',
url: '/system/dict/data/list',
method: 'GET',
data: { pageNum: 1, pageSize: 10 },
expectedType: 'paginated',
permission: 'system:dict:list',
},
];
// 执行API格式验证测试
async function runApiFormatValidation() {
console.log('🔍 API格式验证测试'.rainbow.bold);
console.log('='.repeat(80).rainbow);
console.log('📋 验证规范:统一响应格式 {code, msg, data}');
console.log('🎯 测试目标:确保所有接口符合新的格式规范');
console.log('');
// 获取Token
console.log('🔐 获取认证Token...'.magenta);
const token = await getToken();
if (!token) {
console.log('❌ 无法获取Token,测试终止'.red);
return;
}
console.log('✅ Token获取成功'.green);
console.log('');
const results = [];
let totalTests = 0;
let passedTests = 0;
// 逐个测试API
for (const testCase of apiTestCases) {
totalTests++;
console.log(`📝 测试 ${totalTests}: ${testCase.name}`.cyan.bold);
console.log(` 📍 接口: ${testCase.method} ${testCase.url}`);
console.log(` 🔑 权限: ${testCase.permission}`);
console.log(` 📊 期望格式: ${testCase.expectedType}`);
try {
// 构建请求配置
const config = {
method: testCase.method.toLowerCase(),
url: `http://localhost/dev-api${testCase.url}`,
headers: {
'Content-Type': 'application/json;charset=utf-8',
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
referer: 'http://localhost/login?redirect=%252Findex',
Authorization: `Bearer ${token}`,
},
timeout: 10000,
};
if (testCase.data) {
if (testCase.method === 'GET') {
config.params = testCase.data;
} else {
config.data = testCase.data;
}
}
// 发送请求
const response = await axios(config);
// 验证响应格式
const issues = validateResponseFormat(
response.data,
testCase.name,
testCase.expectedType,
);
if (issues.length === 0) {
console.log(' ✅ 格式验证通过'.green);
passedTests++;
// 显示响应数据统计
if (response.data.code === 200) {
if (testCase.expectedType === 'paginated') {
console.log(
` 📊 数据统计: total=${response.data.total}, rows=${
response.data.rows?.length || 0
}`,
);
} else if (
testCase.expectedType === 'array' &&
Array.isArray(response.data.data)
) {
console.log(` 📊 数据统计: 数组长度=${response.data.data.length}`);
} else if (testCase.expectedType === 'object' && response.data.data) {
const keys = Object.keys(response.data.data);
console.log(` 📊 数据统计: 对象字段=${keys.length}个`);
}
}
} else {
console.log(' ❌ 格式验证失败'.red);
issues.forEach((issue) => {
console.log(` ${issue}`.red);
});
}
results.push({
name: testCase.name,
url: testCase.url,
method: testCase.method,
success: issues.length === 0,
issues: issues,
response: {
code: response.data.code,
msg: response.data.msg,
hasData: response.data.hasOwnProperty('data'),
dataType:
testCase.expectedType === 'paginated'
? 'paginated'
: Array.isArray(response.data.data)
? 'array'
: response.data.data === null
? 'null'
: typeof response.data.data,
total: response.data.total,
rowsCount: response.data.rows?.length,
},
});
} catch (error) {
console.log(' ❌ 请求失败'.red);
console.log(` 错误信息: ${error.response?.data?.msg || error.message}`.red);
results.push({
name: testCase.name,
url: testCase.url,
method: testCase.method,
success: false,
issues: [`请求失败: ${error.response?.data?.msg || error.message}`],
response: null,
});
}
console.log('');
}
// 输出测试总结
console.log('📊 测试结果汇总'.rainbow.bold);
console.log('='.repeat(80).rainbow);
console.log(`🎯 总测试数: ${totalTests}`);
console.log(`✅ 通过测试: ${passedTests}`.green);
console.log(`❌ 失败测试: ${totalTests - passedTests}`.red);
console.log(`📈 通过率: ${((passedTests / totalTests) * 100).toFixed(1)}%`);
console.log('');
// 详细结果表格
console.log('📋 详细测试结果'.blue.bold);
console.log('─'.repeat(100));
results.forEach((result, index) => {
const status = result.success ? '✅ 通过'.green : '❌ 失败'.red;
console.log(
`${(index + 1).toString().padStart(2)}. ${result.name.padEnd(
20,
)} ${result.method.padEnd(6)} ${status}`,
);
if (!result.success && result.issues.length > 0) {
result.issues.forEach((issue) => {
console.log(` ${issue}`.red);
});
}
if (result.response) {
console.log(
` 响应: code=${result.response.code}, msg="${result.response.msg}", data类型=${result.response.dataType}`,
);
}
console.log('');
});
// 规范建议
if (passedTests < totalTests) {
console.log('💡 规范建议'.yellow.bold);
console.log('─'.repeat(50));
console.log('1. 检查请求头是否包含Bearer Token格式的Authorization');
console.log('2. 分页接口必须直接返回TableDataInfo格式(total、rows、code、msg)');
console.log('3. 非分页接口必须使用R<T>类型包装返回结果');
console.log('4. 确保包含clientid、Content-Type等必要请求头');
console.log('5. 验证响应格式是否符合新的统一规范');
console.log('');
}
// 成功提示
if (passedTests === totalTests) {
console.log('🎉 恭喜!所有接口都符合统一响应格式规范!'.green.bold);
}
return {
total: totalTests,
passed: passedTests,
failed: totalTests - passedTests,
successRate: ((passedTests / totalTests) * 100).toFixed(1),
};
}
// 主函数
async function main() {
try {
const result = await runApiFormatValidation();
console.log('\n🏁 API格式验证测试完成'.rainbow.bold);
// 返回测试结果供其他脚本使用
return result;
} catch (error) {
console.log('❌ 测试执行失败:'.red, error.message);
process.exit(1);
}
}
// 如果直接运行此脚本
if (require.main === module) {
main().then((result) => {
if (result.failed > 0) {
process.exit(1); // 有失败用例时退出码为1
}
});
}
module.exports = { runApiFormatValidation };
-103
View File
@@ -1,103 +0,0 @@
# 出国审批页面问题诊断指南
## 🔍 问题排查步骤
### 1. 检查浏览器控制台错误
**请按以下步骤操作:**
1. 打开浏览器访问:`http://localhost/rs/overseasApproval`
2. 按 **F12** 打开开发者工具
3. 点击 **Console** 标签
4. 刷新页面,查看是否有红色错误信息
5. 点击 **Network** 标签,查看API请求状态
### 2. 检查API请求状态
在Network标签中查找以下请求:
- `pending/list` - 待审批申请列表API
- `my/list` - 我的审批记录API
**状态码说明:**
- `200` - 正常
- `401` - 未登录或token过期
- `403` - 权限不足
- `404` - 接口不存在
- `500` - 服务器错误
### 3. 常见问题及解决方案
#### 问题1: API返回401未授权
**原因**: 用户未登录或登录状态过期
**解决**: 重新登录系统
#### 问题2: API返回403权限不足
**原因**: 当前用户没有审批相关权限
**解决**: 联系管理员分配审批权限
#### 问题3: 页面完全空白
**原因**: Vue组件加载失败或API调用异常
**解决**: 查看控制台具体错误信息
#### 问题4: API返回空数据
**原因**: 数据库中没有待审批的数据
**解决**: 这是正常情况,数据库已包含测试数据
### 4. 快速验证方法
**打开浏览器控制台,输入以下代码进行测试:**
```javascript
// 测试API连通性
fetch('/rs/overseas/application/pending/list?pageNum=1&pageSize=10')
.then(response => response.json())
.then(data => {
console.log('待审批API响应:', data);
if (data.code === 200) {
console.log('✅ API正常,数据条数:', data.total);
} else {
console.log('❌ API异常,错误:', data.msg);
}
})
.catch(error => console.log('🔴 请求失败:', error));
fetch('/rs/overseas/approval/my/list?pageNum=1&pageSize=10')
.then(response => response.json())
.then(data => {
console.log('审批记录API响应:', data);
if (data.code === 200) {
console.log('✅ API正常,数据条数:', data.total);
} else {
console.log('❌ API异常,错误:', data.msg);
}
})
.catch(error => console.log('🔴 请求失败:', error));
```
### 5. 数据验证
**预期数据:**
- 待审批申请:应该显示2条记录(张测试、李测试)
- 审批记录:应该显示5条历史审批记录
## 📋 请提供的信息
为了进一步协助诊断,请提供以下信息:
1. **浏览器控制台的完整错误信息**
2. **Network标签中API请求的状态码和响应**
3. **页面显示的具体情况**(完全空白 or 有加载动画 or 显示错误信息)
## 🚀 临时解决方案
如果问题仍然存在,可以尝试:
1. **清除浏览器缓存和Cookie**
2. **使用无痕模式重新登录**
3. **检查是否有浏览器插件干扰**
4. **尝试不同的浏览器**
---
**创建时间**: 2025-01-28 14:30:00
**状态**: 等待用户反馈具体错误信息
-83
View File
@@ -1,83 +0,0 @@
-- ==========================================
-- 维修角色权限分配脚本
-- 功能:为维修人员和物业维修管理员分配适当的功能权限
-- 时间:2025-06-30 13:50:00
-- 说明:基于业务需求分配差异化权限
-- ==========================================
-- 第一步:为维修人员(role_id: 5)分配权限
-- 维修人员需要:查看工单、处理自己的维修任务、查看维修分类
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) VALUES
-- 报修管理基础权限
(5, 2110), -- 报修管理页面
(5, 2111), -- 报修查询
(5, 2117), -- 开始维修
(5, 2118), -- 完成维修
(5, 2121), -- 生成工单号
-- 维修分类查看权限
(5, 2130), -- 维修分类页面
(5, 2131), -- 分类查询
(5, 2136), -- 分类树查询
(5, 2137); -- 分类选项
-- 第二步:为物业维修管理员(role_id: 6)分配权限
-- 物业维修管理员需要:管理所有工单、分配任务、查看统计、管理分类
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) VALUES
-- 报修管理全权限(除删除外)
(6, 2110), -- 报修管理页面
(6, 2111), -- 报修查询
(6, 2112), -- 报修新增
(6, 2113), -- 报修修改
(6, 2115), -- 报修导出
(6, 2116), -- 工单分派
(6, 2117), -- 开始维修
(6, 2118), -- 完成维修
(6, 2119), -- 取消工单
(6, 2120), -- 统计数据
(6, 2121), -- 生成工单号
-- 维修分类管理权限
(6, 2130), -- 维修分类页面
(6, 2131), -- 分类查询
(6, 2132), -- 分类新增
(6, 2133), -- 分类修改
(6, 2135), -- 分类导出
(6, 2136), -- 分类树查询
(6, 2137); -- 分类选项
-- 第三步:为两个角色分配物业管理主菜单权限(如果存在)
-- 确保用户能看到物业管理菜单入口
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 5, menu_id FROM sys_menu WHERE menu_name = '物业管理' AND menu_type = 'M'
UNION ALL
SELECT 6, menu_id FROM sys_menu WHERE menu_name = '物业管理' AND menu_type = 'M';
-- 第四步:验证权限分配结果
SELECT '权限分配验证' as message,
'维修人员权限数' as role_type,
(SELECT COUNT(*) FROM sys_role_menu WHERE role_id = 5) as permission_count
UNION ALL
SELECT '权限分配验证' as message,
'物业维修管理员权限数' as role_type,
(SELECT COUNT(*) FROM sys_role_menu WHERE role_id = 6) as permission_count;
-- 详细权限分配情况
SELECT
r.role_name as '角色名称',
m.menu_name as '菜单/功能名称',
m.perms as '权限标识',
CASE m.menu_type
WHEN 'M' THEN '目录'
WHEN 'C' THEN '菜单'
WHEN 'F' THEN '按钮'
END as '权限类型'
FROM sys_role_menu rm
JOIN sys_role r ON rm.role_id = r.role_id
JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE rm.role_id IN (5, 6)
AND m.perms LIKE 'pm:%'
ORDER BY r.role_id, m.menu_id;
-- 第五步:权限差异化说明
SELECT '权限设计说明' as info_type, '维修人员:只能处理维修任务,无管理权限' as description
UNION ALL
SELECT '权限设计说明' as info_type, '物业维修管理员:可管理工单、分配任务、查看统计' as description;
-466
View File
@@ -1,466 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量创建学生信息脚本
为每个班级创建30个学生的测试数据
"""
import requests
import json
import random
import time
from datetime import datetime, timedelta
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class SimpleDataGenerator:
"""简单的数据生成器,替代faker"""
def __init__(self):
# 姓氏
self.surnames = ['李', '王', '张', '刘', '陈', '杨', '黄', '赵', '周', '吴',
'徐', '孙', '朱', '马', '胡', '郭', '林', '何', '高', '梁']
# 男性名字
self.male_names = ['伟', '强', '磊', '军', '勇', '涛', '明', '超', '辉', '华',
'建', '文', '健', '云', '峰', '浩', '鹏', '飞', '宇', '杰']
# 女性名字
self.female_names = ['丽', '娜', '敏', '静', '佳', '雅', '莉', '萍', '红', '梅',
'芳', '燕', '玲', '霞', '琳', '晶', '欣', '宁', '蕾', '洁']
# 省份
self.provinces = ['北京市', '上海市', '天津市', '重庆市', '河北省', '山西省', '辽宁省',
'吉林省', '黑龙江省', '江苏省', '浙江省', '安徽省', '福建省', '江西省',
'山东省', '河南省', '湖北省', '湖南省', '广东省', '海南省', '四川省',
'贵州省', '云南省', '陕西省', '甘肃省', '青海省', '台湾省', '内蒙古自治区',
'广西壮族自治区', '西藏自治区', '宁夏回族自治区', '新疆维吾尔自治区']
def generate_name(self, gender):
"""生成姓名"""
surname = random.choice(self.surnames)
if gender == '0': # 男性
given_name = random.choice(self.male_names)
else: # 女性
given_name = random.choice(self.female_names)
# 有时候生成双字名
if random.random() < 0.3:
if gender == '0':
given_name += random.choice(self.male_names)
else:
given_name += random.choice(self.female_names)
return surname + given_name
def generate_phone(self):
"""生成手机号"""
prefixes = ['130', '131', '132', '133', '134', '135', '136', '137', '138', '139',
'150', '151', '152', '153', '155', '156', '157', '158', '159',
'180', '181', '182', '183', '184', '185', '186', '187', '188', '189']
prefix = random.choice(prefixes)
suffix = ''.join([str(random.randint(0, 9)) for _ in range(8)])
return prefix + suffix
def generate_id_card(self, birth_date):
"""生成身份证号"""
# 地区代码(简化版)
area_codes = ['110101', '110102', '110105', '110106', '110107', '110108',
'310101', '310104', '310105', '310106', '310107', '310109',
'440101', '440103', '440104', '440105', '440106', '440111']
area_code = random.choice(area_codes)
# 出生日期(YYYYMMDD格式)
birth_str = birth_date.replace('-', '')
# 顺序码(3位)+ 校验码(1位)
sequence = str(random.randint(100, 999))
check_digit = str(random.randint(0, 9))
return area_code + birth_str + sequence + check_digit
def generate_address(self):
"""生成地址"""
province = random.choice(self.provinces)
cities = ['市', '县']
city = f"某{random.choice(cities)}"
district = f"某区某街道{random.randint(1, 999)}号"
return province + city + district
def generate_province(self):
"""生成省份"""
return random.choice(self.provinces)
class StudentBatchCreator:
def __init__(self, base_url="http://localhost:8080", token=None):
"""
初始化学生批量创建器
Args:
base_url: 后端API基础URL
token: 认证token
"""
self.base_url = base_url
self.token = token
self.session = requests.Session()
self.data_gen = SimpleDataGenerator()
# 设置请求头
if token:
self.session.headers.update({
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
})
def set_token(self, token):
"""设置认证token"""
self.token = token
self.session.headers.update({
'Authorization': f'Bearer {token}'
})
def get_class_list(self):
"""获取班级列表"""
try:
url = f"{self.base_url}/classMsg/classMsg/list"
params = {
'pageNum': 1,
'pageSize': 1000 # 获取所有班级
}
response = self.session.get(url, params=params)
response.raise_for_status()
data = response.json()
if data.get('code') == 200:
classes = data.get('rows', [])
logger.info(f"获取到 {len(classes)} 个班级")
return classes
else:
logger.error(f"获取班级列表失败: {data.get('msg')}")
return []
except Exception as e:
logger.error(f"获取班级列表异常: {str(e)}")
return []
def generate_birth_date(self):
"""生成出生日期(18-22岁)"""
today = datetime.now()
min_age = 18
max_age = 22
# 计算出生年份范围
birth_year = today.year - random.randint(min_age, max_age)
birth_month = random.randint(1, 12)
birth_day = random.randint(1, 28) # 使用28避免月份天数问题
return f"{birth_year:04d}-{birth_month:02d}-{birth_day:02d}"
def generate_student_data(self, class_info, student_index):
"""
生成单个学生的测试数据
Args:
class_info: 班级信息
student_index: 学生编号(1-30)
Returns:
dict: 学生数据
"""
# 生成基础信息
gender = random.choice(['0', '1']) # 0-男,1-女
name = self.data_gen.generate_name(gender)
# 生成学号:班级代码 + 学生编号
class_code = class_info.get('classCode', class_info.get('class_code', ''))
if not class_code:
# 如果没有班级代码,使用班级ID
class_code = f"CLS{class_info.get('id', '')}"
student_no = f"{class_code}{student_index:02d}"
# 生成出生日期
birth_date = self.generate_birth_date()
# 生成身份证号
id_card = self.data_gen.generate_id_card(birth_date)
# 生成手机号
phone = self.data_gen.generate_phone()
# 民族选择
nations = ['01', '02', '03', '04', '05'] # 汉族、蒙古族、回族、藏族、维吾尔族等
nationality = random.choice(nations)
# 政治面貌选择
political_statuses = ['01', '02', '03', '13'] # 群众、团员、党员等
political_status = random.choice(political_statuses)
# 构建学生数据
student_data = {
# 基础信息
"studentNo": student_no,
"idCard": id_card,
"name": name,
"gender": gender,
"birthDate": birth_date,
"nationality": nationality,
"politicalStatus": political_status,
"phone": phone,
"email": f"{student_no}@student.edu.cn",
# 学籍信息(从班级信息继承)
"classId": class_info.get('id'),
"className": class_info.get('className', class_info.get('class_name', '')),
"majorId": class_info.get('majorId', class_info.get('major_id')),
"majorName": class_info.get('majorName', class_info.get('major_name', '')),
"collegeId": class_info.get('collegeId', class_info.get('college_id')),
"collegeName": class_info.get('collegeName', class_info.get('college_name', '')),
"grade": class_info.get('grade', ''),
"enrollmentYear": class_info.get('enrollmentYear', class_info.get('enrollment_year')),
"academicLevel": class_info.get('classType', class_info.get('class_type', 1)),
"classTeacherId": class_info.get('classTeacherId', class_info.get('class_teacher_id')),
"classTeacherName": class_info.get('classTeacherName', class_info.get('class_teacher_name', '')),
"schoolSystem": class_info.get('schoolSystem', class_info.get('school_system', 3)),
# 默认状态
"studentStatus": "1", # 在籍
"trainingType": "全日制",
"admissionCategory": "统招",
"householdRegisterType": "非农业",
"educationLevel": "2", # 高中
"maritalStatus": "1", # 未婚
# 地址信息
"nativePlace": self.data_gen.generate_province(),
"sourceRegion": self.data_gen.generate_province(),
"homeAddress": self.data_gen.generate_address(),
"currentAddress": "学校宿舍",
# 家庭信息
"fatherName": self.data_gen.generate_name('0'),
"fatherPhone": self.data_gen.generate_phone(),
"motherName": self.data_gen.generate_name('1'),
"motherPhone": self.data_gen.generate_phone(),
# 健康信息
"height": random.randint(155, 185),
"weight": random.randint(45, 80),
"bloodType": random.choice(['A', 'B', 'AB', 'O']),
"healthStatus": "良好",
# 布尔字段
"isBoarder": 1, # 住校
"isPoorStudent": random.randint(0, 1),
"isMinority": 1 if nationality != '01' else 0, # 非汉族为少数民族
"isDisabled": 0,
"isOrphan": 0,
"isLeftBehindChild": 0,
"isSingleChild": random.randint(0, 1),
"isLowIncome": 0,
"hasLoan": random.randint(0, 1),
# 其他字段
"remark": f"批量生成的测试学生数据 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
}
return student_data
def create_student(self, student_data):
"""
创建单个学生
Args:
student_data: 学生数据
Returns:
tuple: (success, result)
"""
try:
url = f"{self.base_url}/studentMsg/studentMsg"
response = self.session.post(url, json=student_data)
response.raise_for_status()
result = response.json()
if result.get('code') == 200:
logger.info(f"成功创建学生: {student_data['name']} ({student_data['studentNo']})")
return True, result
else:
logger.error(f"创建学生失败: {student_data['name']} - {result.get('msg')}")
return False, result
except Exception as e:
logger.error(f"创建学生异常: {student_data['name']} - {str(e)}")
return False, str(e)
def batch_create_students_for_class(self, class_info, student_count=30):
"""
为指定班级批量创建学生
Args:
class_info: 班级信息
student_count: 要创建的学生数量
Returns:
dict: 创建结果统计
"""
class_name = class_info.get('className', class_info.get('class_name', f"班级{class_info.get('id')}"))
logger.info(f"开始为班级 [{class_name}] 创建 {student_count} 个学生...")
success_count = 0
failed_count = 0
failed_students = []
for i in range(1, student_count + 1):
try:
# 生成学生数据
student_data = self.generate_student_data(class_info, i)
# 创建学生
success, result = self.create_student(student_data)
if success:
success_count += 1
else:
failed_count += 1
failed_students.append({
'student_no': student_data['studentNo'],
'name': student_data['name'],
'error': result
})
# 添加短暂延迟,避免请求过于频繁
time.sleep(0.1)
except Exception as e:
failed_count += 1
logger.error(f"处理第 {i} 个学生时发生异常: {str(e)}")
failed_students.append({
'student_no': f"未知_{i}",
'name': '未知',
'error': str(e)
})
result = {
'class_name': class_name,
'total': student_count,
'success': success_count,
'failed': failed_count,
'failed_students': failed_students
}
logger.info(f"班级 [{class_name}] 学生创建完成: 成功 {success_count},失败 {failed_count}")
return result
def batch_create_students_for_all_classes(self, student_count_per_class=30):
"""
为所有班级批量创建学生
Args:
student_count_per_class: 每个班级创建的学生数量
Returns:
list: 所有班级的创建结果
"""
# 获取班级列表
classes = self.get_class_list()
if not classes:
logger.error("没有获取到班级信息,无法创建学生")
return []
logger.info(f"开始为 {len(classes)} 个班级批量创建学生,每个班级 {student_count_per_class} 个学生")
all_results = []
total_success = 0
total_failed = 0
for class_info in classes:
try:
result = self.batch_create_students_for_class(class_info, student_count_per_class)
all_results.append(result)
total_success += result['success']
total_failed += result['failed']
# 班级间添加延迟
time.sleep(0.5)
except Exception as e:
logger.error(f"处理班级 {class_info} 时发生异常: {str(e)}")
all_results.append({
'class_name': f"班级{class_info.get('id', '未知')}",
'total': student_count_per_class,
'success': 0,
'failed': student_count_per_class,
'error': str(e)
})
total_failed += student_count_per_class
# 输出总结
logger.info("="*50)
logger.info("批量创建学生完成!")
logger.info(f"总班级数: {len(classes)}")
logger.info(f"总学生数: {len(classes) * student_count_per_class}")
logger.info(f"成功创建: {total_success}")
logger.info(f"创建失败: {total_failed}")
logger.info("="*50)
return all_results
def main():
"""主函数"""
print("学生批量创建工具")
print("="*50)
# 配置参数
base_url = input("请输入后端API地址 (默认: http://localhost:8080): ").strip()
if not base_url:
base_url = "http://localhost:8080"
token = input("请输入认证Token (可为空): ").strip()
student_count = input("请输入每个班级创建的学生数量 (默认: 30): ").strip()
if not student_count:
student_count = 30
else:
try:
student_count = int(student_count)
except ValueError:
print("输入的学生数量无效,使用默认值 30")
student_count = 30
# 创建批量创建器
creator = StudentBatchCreator(base_url, token)
# 确认操作
print(f"\n配置信息:")
print(f"API地址: {base_url}")
print(f"每个班级学生数: {student_count}")
confirm = input("\n确认开始批量创建学生?(y/N): ").strip().lower()
if confirm not in ['y', 'yes']:
print("操作已取消")
return
# 开始批量创建
start_time = time.time()
results = creator.batch_create_students_for_all_classes(student_count)
end_time = time.time()
# 保存结果到文件
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
result_file = f"student_creation_result_{timestamp}.json"
with open(result_file, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"\n创建结果已保存到: {result_file}")
print(f"总耗时: {end_time - start_time:.2f} 秒")
if __name__ == "__main__":
main()
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
检查系统状态和可用的API端点
"""
import requests
import json
def check_system_status():
"""检查系统状态"""
base_urls = [
"http://localhost:8080",
"http://localhost/dev-api",
"http://localhost"
]
print("🔍 检查系统状态...")
for base_url in base_urls:
print(f"\n🌐 测试地址: {base_url}")
# 测试基本连接
try:
response = requests.get(f"{base_url}/", timeout=5)
print(f" ✅ 基础连接: {response.status_code}")
except Exception as e:
print(f" ❌ 基础连接失败: {str(e)}")
continue
# 测试登录端点
try:
login_endpoints = ["/auth/login", "/login"]
for endpoint in login_endpoints:
try:
response = requests.post(f"{base_url}{endpoint}", json={}, timeout=5)
print(f" 🔑 登录端点 {endpoint}: {response.status_code}")
if response.status_code in [200, 400, 401, 403]:
print(f" 响应: {response.text[:200]}...")
except Exception as e:
print(f" ❌ 登录端点 {endpoint} 失败: {str(e)}")
except Exception as e:
print(f" ❌ 登录测试失败: {str(e)}")
# 测试API端点
try:
api_endpoints = [
"/classMsg/classMsg/list",
"/studentMsg/studentMsg/list",
"/api/classMsg/classMsg/list"
]
for endpoint in api_endpoints:
try:
response = requests.get(f"{base_url}{endpoint}", timeout=5)
print(f" 📊 API端点 {endpoint}: {response.status_code}")
if response.text:
data = response.json()
if data.get('code'):
print(f" 响应码: {data.get('code')}, 消息: {data.get('msg', '')}")
except Exception as e:
print(f" ❌ API端点 {endpoint} 失败: {str(e)}")
except Exception as e:
print(f" ❌ API测试失败: {str(e)}")
if __name__ == "__main__":
check_system_status()
-102
View File
@@ -1,102 +0,0 @@
-- ==========================================
-- 物业报修管理完整权限配置脚本(修复版)
-- 功能:为报修管理模块添加完整的权限配置到sys_menu表
-- 时间:2025-06-30
-- 说明:解决报修管理接口权限访问问题
-- ==========================================
-- 清理可能存在的旧数据(谨慎使用)
-- DELETE FROM sys_role_menu WHERE menu_id BETWEEN 2100 AND 2199;
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199;
-- ==========================================
-- 第一部分:主菜单 - 物业管理
-- ==========================================
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES (
2100, '物业管理', 0, 6, 'pm', NULL,
'', 1, 0, 'M', '0', '0',
'', 'tool', 1, now(), 1, now(), '物业管理模块'
);
-- ==========================================
-- 第二部分:报修管理菜单和权限
-- ==========================================
-- 报修管理主菜单
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES (
2110, '报修管理', 2100, 1, 'repairOrder', 'pm/repairOrder/index',
'', 1, 0, 'C', '0', '0',
'pm:repairOrder:list', 'form', 1, now(), 1, now(), '报修工单管理'
);
-- 报修管理功能权限按钮
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES
-- 基础CRUD权限
(2111, '报修查询', 2110, 1, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:query', '#', 1, now(), 1, now(), '查询报修工单详情'),
(2112, '报修新增', 2110, 2, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:add', '#', 1, now(), 1, now(), '新增报修工单'),
(2113, '报修修改', 2110, 3, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:edit', '#', 1, now(), 1, now(), '修改报修工单'),
(2114, '报修删除', 2110, 4, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:remove', '#', 1, now(), 1, now(), '删除报修工单'),
(2115, '报修导出', 2110, 5, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:export', '#', 1, now(), 1, now(), '导出报修工单'),
-- 业务流程权限
(2116, '工单分派', 2110, 6, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:assign', '#', 1, now(), 1, now(), '分派维修工单'),
(2117, '开始维修', 2110, 7, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:start', '#', 1, now(), 1, now(), '开始维修工单'),
(2118, '完成维修', 2110, 8, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:finish', '#', 1, now(), 1, now(), '完成维修工单'),
(2119, '取消工单', 2110, 9, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:cancel', '#', 1, now(), 1, now(), '取消维修工单'),
-- 统计和工具权限
(2120, '统计数据', 2110, 10, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:statistics', '#', 1, now(), 1, now(), '获取报修统计数据'),
(2121, '生成工单号', 2110, 11, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:generateOrderNo', '#', 1, now(), 1, now(), '生成工单编号');
-- ==========================================
-- 第三部分:维修分类菜单和权限
-- ==========================================
-- 维修分类主菜单
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES (
2130, '维修分类', 2100, 2, 'repairCategory', 'pm/repairCategory/index',
'', 1, 0, 'C', '0', '0',
'pm:category:list', 'tree', 1, now(), 1, now(), '维修分类管理'
);
-- 维修分类功能权限按钮
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES
-- 基础CRUD权限
(2131, '分类查询', 2130, 1, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:query', '#', 1, now(), 1, now(), '查询维修分类详情'),
(2132, '分类新增', 2130, 2, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:add', '#', 1, now(), 1, now(), '新增维修分类'),
(2133, '分类修改', 2130, 3, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:edit', '#', 1, now(), 1, now(), '修改维修分类'),
(2134, '分类删除', 2130, 4, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:remove', '#', 1, now(), 1, now(), '删除维修分类'),
(2135, '分类导出', 2130, 5, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:export', '#', 1, now(), 1, now(), '导出维修分类'),
-- 特殊功能权限
(2136, '分类树查询', 2130, 6, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:tree', '#', 1, now(), 1, now(), '获取维修分类树形结构'),
(2137, '分类选项', 2130, 7, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:options', '#', 1, now(), 1, now(), '获取维修分类下拉选项');
-- ==========================================
-- 第四部分:为admin角色分配权限
-- ==========================================
-- 为admin角色(role_id=1)分配所有物业管理权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 1, menu_id FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199;
-161
View File
@@ -1,161 +0,0 @@
-- ==========================================
-- 物业报修管理完整权限配置脚本
-- 功能:为报修管理模块添加完整的权限配置到sys_menu表
-- 时间:2025-06-30
-- 说明:解决报修管理接口权限访问问题
-- ==========================================
-- 清理可能存在的旧数据(谨慎使用)
-- DELETE FROM sys_role_menu WHERE menu_id BETWEEN 2100 AND 2199;
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199;
-- ==========================================
-- 第一部分:主菜单 - 物业管理
-- ==========================================
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES (
2100, '物业管理', 0, 6, 'pm', NULL,
'', 1, 0, 'M', '0', '0',
'', 'tool', 1, sysdate(), 1, sysdate(), '物业管理模块'
);
-- ==========================================
-- 第二部分:报修管理菜单和权限
-- ==========================================
-- 报修管理主菜单
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES (
2110, '报修管理', 2100, 1, 'repairOrder', 'pm/repairOrder/index',
'', 1, 0, 'C', '0', '0',
'pm:repairOrder:list', 'form', 1, sysdate(), 1, sysdate(), '报修工单管理'
);
-- 报修管理功能权限按钮
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES
-- 基础CRUD权限
(2111, '报修查询', 2110, 1, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:query', '#', 1, sysdate(), 1, sysdate(), '查询报修工单详情'),
(2112, '报修新增', 2110, 2, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:add', '#', 1, sysdate(), 1, sysdate(), '新增报修工单'),
(2113, '报修修改', 2110, 3, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:edit', '#', 1, sysdate(), 1, sysdate(), '修改报修工单'),
(2114, '报修删除', 2110, 4, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:remove', '#', 1, sysdate(), 1, sysdate(), '删除报修工单'),
(2115, '报修导出', 2110, 5, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:export', '#', 1, sysdate(), 1, sysdate(), '导出报修工单'),
-- 业务流程权限
(2116, '工单分派', 2110, 6, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:assign', '#', 1, sysdate(), 1, sysdate(), '分派维修工单'),
(2117, '开始维修', 2110, 7, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:start', '#', 1, sysdate(), 1, sysdate(), '开始维修工单'),
(2118, '完成维修', 2110, 8, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:finish', '#', 1, sysdate(), 1, sysdate(), '完成维修工单'),
(2119, '取消工单', 2110, 9, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:cancel', '#', 1, sysdate(), 1, sysdate(), '取消维修工单'),
-- 统计和工具权限
(2120, '统计数据', 2110, 10, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:statistics', '#', 1, sysdate(), 1, sysdate(), '获取报修统计数据'),
(2121, '生成工单号', 2110, 11, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:generateOrderNo', '#', 1, sysdate(), 1, sysdate(), '生成工单编号');
-- ==========================================
-- 第三部分:维修分类菜单和权限
-- ==========================================
-- 维修分类主菜单
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES (
2130, '维修分类', 2100, 2, 'repairCategory', 'pm/repairCategory/index',
'', 1, 0, 'C', '0', '0',
'pm:category:list', 'tree', 1, sysdate(), 1, sysdate(), '维修分类管理'
);
-- 维修分类功能权限按钮
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
) VALUES
-- 基础CRUD权限
(2131, '分类查询', 2130, 1, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:query', '#', 1, sysdate(), 1, sysdate(), '查询维修分类详情'),
(2132, '分类新增', 2130, 2, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:add', '#', 1, sysdate(), 1, sysdate(), '新增维修分类'),
(2133, '分类修改', 2130, 3, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:edit', '#', 1, sysdate(), 1, sysdate(), '修改维修分类'),
(2134, '分类删除', 2130, 4, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:remove', '#', 1, sysdate(), 1, sysdate(), '删除维修分类'),
(2135, '分类导出', 2130, 5, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:export', '#', 1, sysdate(), 1, sysdate(), '导出维修分类'),
-- 特殊功能权限
(2136, '分类树查询', 2130, 6, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:tree', '#', 1, sysdate(), 1, sysdate(), '获取维修分类树形结构'),
(2137, '分类选项', 2130, 7, '', '', '', 1, 0, 'F', '0', '0', 'pm:category:options', '#', 1, sysdate(), 1, sysdate(), '获取维修分类下拉选项');
-- ==========================================
-- 第四部分:为admin角色分配权限
-- ==========================================
-- 为admin角色(role_id=1)分配所有物业管理权限
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, menu_id FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199
ON DUPLICATE KEY UPDATE role_id = role_id; -- 避免重复插入
-- ==========================================
-- 第五部分:验证和清理
-- ==========================================
-- 验证权限配置是否正确
SELECT
'=== 菜单权限配置验证 ===' AS section,
NULL AS menu_id, NULL AS menu_name, NULL AS menu_type, NULL AS perms, NULL AS has_role_permission
UNION ALL
SELECT
CASE
WHEN m.menu_type = 'M' THEN '📁 主菜单'
WHEN m.menu_type = 'C' THEN '📄 页面菜单'
WHEN m.menu_type = 'F' THEN '🔘 功能按钮'
ELSE '❓ 未知类型'
END AS section,
m.menu_id,
m.menu_name,
m.menu_type,
m.perms,
CASE WHEN rm.role_id IS NOT NULL THEN '✅ 已分配' ELSE '❌ 未分配' END AS has_role_permission
FROM sys_menu m
LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id AND rm.role_id = 1
WHERE m.menu_id BETWEEN 2100 AND 2199
ORDER BY m.menu_id;
-- 统计权限配置结果
SELECT
'权限配置统计' AS summary,
COUNT(*) AS total_menus,
SUM(CASE WHEN menu_type = 'M' THEN 1 ELSE 0 END) AS main_menus,
SUM(CASE WHEN menu_type = 'C' THEN 1 ELSE 0 END) AS page_menus,
SUM(CASE WHEN menu_type = 'F' THEN 1 ELSE 0 END) AS function_buttons,
SUM(CASE WHEN perms IS NOT NULL AND perms != '' THEN 1 ELSE 0 END) AS with_permissions
FROM sys_menu
WHERE menu_id BETWEEN 2100 AND 2199;
-- 检查admin角色权限分配
SELECT
'权限分配统计' AS summary,
COUNT(rm.menu_id) AS assigned_permissions,
(SELECT COUNT(*) FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199) AS total_permissions,
CONCAT(
ROUND(COUNT(rm.menu_id) * 100.0 / (SELECT COUNT(*) FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199), 2),
'%'
) AS assignment_rate
FROM sys_role_menu rm
WHERE rm.role_id = 1 AND rm.menu_id BETWEEN 2100 AND 2199;
-- 重要提示信息
SELECT
'🎉 物业报修管理权限配置完成!' AS message,
'请执行以下步骤完成配置:' AS next_steps,
'1. 重新登录系统刷新权限缓存' AS step1,
'2. 修改Controller权限注解' AS step2,
'3. 重启后端服务' AS step3,
'4. 测试API接口访问' AS step4;
@@ -1,257 +0,0 @@
"""
cooperation经费来源显示问题调试脚本
问题:修复后出国记录的查看详情中的经费来源显示的还是cooperation
调试步骤:
1. 登录系统获取token
2. 获取出国记录列表,找到cooperation经费来源的记录
3. 查看记录详情,检查返回的数据结构
4. 分析budgetSource和budgetSourceText字段的值
5. 确定问题的具体原因
执行时间: 2025-01-07 22:50:00
"""
import requests
import json
from typing import Dict, Any, Optional, List
class CooperationBudgetSourceDebugger:
def __init__(self):
self.base_url = "http://localhost:8080"
self.token = None
self.headers = {
'Content-Type': 'application/json',
'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e',
'referer': 'http://localhost'
}
def login(self) -> bool:
"""登录获取token"""
try:
login_url = f"{self.base_url}/login"
login_data = {
"username": "admin",
"password": "admin123",
"code": "1234",
"uuid": "test-uuid"
}
response = requests.post(login_url, json=login_data, headers=self.headers)
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
self.token = result['data']['access_token']
self.headers['Authorization'] = f'Bearer {self.token}'
print("✅ 登录成功")
return True
else:
print(f"❌ 登录失败: {result.get('msg', '未知错误')}")
return False
else:
print(f"❌ 登录请求失败: HTTP {response.status_code}")
return False
except Exception as e:
print(f"❌ 登录异常: {str(e)}")
return False
def get_record_list(self) -> Optional[List[Dict[str, Any]]]:
"""获取出国记录列表"""
try:
url = f"{self.base_url}/rs/overseas/record/list"
params = {
'pageNum': 1,
'pageSize': 20
}
response = requests.get(url, params=params, headers=self.headers)
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
records = result['data']['rows']
print(f"✅ 获取到 {len(records)} 条出国记录")
return records
else:
print(f"❌ 获取记录列表失败: {result.get('msg')}")
return None
else:
print(f"❌ 请求失败: HTTP {response.status_code}")
return None
except Exception as e:
print(f"❌ 获取记录列表异常: {str(e)}")
return None
def get_record_detail(self, record_id: str) -> Optional[Dict[str, Any]]:
"""获取出国记录详情"""
try:
url = f"{self.base_url}/rs/overseas/record/{record_id}"
response = requests.get(url, headers=self.headers)
if response.status_code == 200:
result = response.json()
if result.get('code') == 200:
record_detail = result['data']
print(f"✅ 获取到记录详情: {record_id}")
return record_detail
else:
print(f"❌ 获取记录详情失败: {result.get('msg')}")
return None
else:
print(f"❌ 请求失败: HTTP {response.status_code}")
return None
except Exception as e:
print(f"❌ 获取记录详情异常: {str(e)}")
return None
def debug_cooperation_records(self):
"""调试cooperation经费来源记录"""
print("🔍 开始调试cooperation经费来源显示问题...")
# 获取记录列表
records = self.get_record_list()
if not records:
print("⚠️ 没有找到出国记录数据")
return
print("\n📋 分析记录列表中的经费来源:")
cooperation_records = []
for i, record in enumerate(records):
record_id = record.get('recordId')
budget_source = record.get('budgetSource')
budget_source_text = record.get('budgetSourceText')
print(f" 记录 {i+1} (ID: {record_id}): budgetSource='{budget_source}', budgetSourceText='{budget_source_text}'")
if budget_source == 'cooperation':
cooperation_records.append({
'record_id': record_id,
'budget_source': budget_source,
'budget_source_text': budget_source_text
})
if not cooperation_records:
print("\n⚠️ 列表中没有找到经费来源为cooperation的记录")
# 检查是否有其他经费来源
all_sources = set(record.get('budgetSource') for record in records if record.get('budgetSource'))
print(f" 当前存在的经费来源: {list(all_sources)}")
return
print(f"\n🎯 找到 {len(cooperation_records)} 条cooperation经费来源记录")
# 详细分析每条cooperation记录
for coop_record in cooperation_records:
record_id = coop_record['record_id']
print(f"\n📊 详细分析记录 ID: {record_id}")
print(f" 列表中显示 - budgetSource: '{coop_record['budget_source']}'")
print(f" 列表中显示 - budgetSourceText: '{coop_record['budget_source_text']}'")
# 获取详情页面数据
record_detail = self.get_record_detail(str(record_id))
if record_detail:
detail_budget_source = record_detail.get('budgetSource')
detail_budget_source_text = record_detail.get('budgetSourceText')
print(f" 详情页显示 - budgetSource: '{detail_budget_source}'")
print(f" 详情页显示 - budgetSourceText: '{detail_budget_source_text}'")
# 分析问题
print(f" 🔍 问题分析:")
if detail_budget_source_text == 'cooperation':
print(f" ❌ 问题确认: 详情页budgetSourceText显示为'{detail_budget_source_text}'")
print(f" 🔧 应该显示: '合作单位'")
# 检查可能的原因
if detail_budget_source == 'cooperation':
print(f" 💡 分析: budgetSource值正确为'cooperation'")
print(f" 💡 问题: getBudgetSourceText()方法可能未生效")
else:
print(f" 💡 问题: budgetSource值异常为'{detail_budget_source}'")
elif detail_budget_source_text == '合作单位':
print(f" ✅ 正常: 详情页budgetSourceText正确显示为'{detail_budget_source_text}'")
else:
print(f" ⚠️ 异常: 详情页budgetSourceText显示为'{detail_budget_source_text}'")
# 显示完整的详情数据结构(用于调试)
print(f" 📋 完整详情数据结构:")
self.print_relevant_fields(record_detail)
else:
print(f" ❌ 无法获取记录详情")
def print_relevant_fields(self, record: Dict[str, Any]):
"""打印相关字段用于调试"""
relevant_fields = [
'recordId', 'budgetSource', 'budgetSourceText',
'purpose', 'purposeText', 'completionStatus', 'completionStatusText'
]
for field in relevant_fields:
value = record.get(field)
print(f" {field}: '{value}'")
def test_backend_restart_needed(self):
"""检查是否需要重启后端服务"""
print("\n🔄 检查后端服务状态:")
print(" 💡 如果代码修改后问题仍然存在,可能需要:")
print(" 1. 重启后端服务 (Spring Boot)")
print(" 2. 清除应用缓存")
print(" 3. 确认代码编译生效")
try:
# 简单的健康检查
health_url = f"{self.base_url}/actuator/health"
response = requests.get(health_url, headers=self.headers, timeout=5)
if response.status_code == 200:
print(" ✅ 后端服务运行正常")
else:
print(" ⚠️ 后端服务状态异常")
except:
print(" ⚠️ 无法访问后端健康检查接口")
def suggest_solutions(self):
"""建议解决方案"""
print("\n💡 可能的解决方案:")
print(" 1. 🔄 重启后端服务:")
print(" cd backend && mvn spring-boot:run")
print(" 2. 🔧 检查代码编译:")
print(" 确认RsOverseasRecordVo.java的修改已编译生效")
print(" 3. 🧹 清除缓存:")
print(" 检查是否有Redis或其他缓存影响")
print(" 4. 📊 检查数据库:")
print(" 确认数据库中budgetSource字段的实际值")
print(" 5. 🔍 调试模式:")
print(" 在getBudgetSourceText()方法中添加日志输出")
def run_debug(self):
"""运行完整调试流程"""
print("🚀 开始cooperation经费来源显示问题调试")
print("="*60)
# 登录
if not self.login():
return
# 调试cooperation记录
self.debug_cooperation_records()
# 检查后端服务状态
self.test_backend_restart_needed()
# 建议解决方案
self.suggest_solutions()
print("="*60)
print("🎯 调试完成")
print("💡 请根据上述分析结果采取相应的解决措施")
if __name__ == "__main__":
debugger = CooperationBudgetSourceDebugger()
debugger.run_debug()
-66
View File
@@ -1,66 +0,0 @@
-- ==========================================
-- 维修角色创建和用户分配脚本
-- 功能:创建维修人员和物业维修管理员角色,并为总务处用户分配角色
-- 时间:2025-06-30 13:45:00
-- 说明:使用UPSERT逻辑,支持重复执行
-- ==========================================
-- 第一步:创建维修人员角色
INSERT INTO sys_role (
role_id, role_name, role_key, role_sort, data_scope,
menu_check_strictly, dept_check_strictly, status,
del_flag, create_by, create_time, update_by, update_time, remark
)
SELECT
5, '维修人员', 'maintenance_worker', 5, '5',
1, 1, '0',
'0', 1, now(), 1, now(), '负责具体维修工作的人员,可以查看和处理分配给自己的维修工单'
WHERE NOT EXISTS (SELECT 1 FROM sys_role WHERE role_id = 5);
-- 第二步:创建物业维修管理员角色
INSERT INTO sys_role (
role_id, role_name, role_key, role_sort, data_scope,
menu_check_strictly, dept_check_strictly, status,
del_flag, create_by, create_time, update_by, update_time, remark
)
SELECT
6, '物业维修管理员', 'property_maintenance_admin', 6, '3',
1, 1, '0',
'0', 1, now(), 1, now(), '负责物业维修管理工作,可以管理维修工单、分配维修任务、查看维修统计'
WHERE NOT EXISTS (SELECT 1 FROM sys_role WHERE role_id = 6);
-- 第三步:为总务处用户分配维修人员角色
-- 选择用户:zwc005(陆雄) 和 zwc007(邹凯)
INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES
(2125, 5), -- zwc005(陆雄) -> 维修人员
(2127, 5); -- zwc007(邹凯) -> 维修人员
-- 第四步:为总务处用户分配物业维修管理员角色
-- 选择用户:zwc003(舒刚) 和 zwc004(晏茹)
INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES
(2123, 6), -- zwc003(舒刚) -> 物业维修管理员
(2124, 6); -- zwc004(晏茹) -> 物业维修管理员
-- 第五步:验证创建结果
SELECT '角色创建验证' as message,
(SELECT COUNT(*) FROM sys_role WHERE role_id IN (5, 6)) as created_roles,
(SELECT COUNT(*) FROM sys_user_role WHERE role_id IN (5, 6)) as assigned_users;
-- 详细验证信息
SELECT
'维修角色分配验证' as verification_type,
r.role_name as '角色名称',
u.user_name as '用户名',
u.nick_name as '昵称',
d.dept_name as '部门'
FROM sys_user_role ur
JOIN sys_role r ON ur.role_id = r.role_id
JOIN sys_user u ON ur.user_id = u.user_id
JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE ur.role_id IN (5, 6)
ORDER BY r.role_id, u.user_id;
-- 第六步:为这两个角色分配基础权限(可根据需要调整)
-- 注意:这里暂时不分配具体权限,后续可以通过管理界面或额外脚本配置
SELECT '权限配置提醒' as message,
'新创建的角色需要在系统管理->角色管理中配置具体的菜单权限' as reminder;
-66
View File
@@ -1,66 +0,0 @@
-- ==========================================
-- 物业报修管理测试数据脚本
-- 功能:插入测试数据到数据库
-- 时间:2025-06-30
-- 说明:为测试报修管理功能创建必要的数据
-- ==========================================
-- 1. 插入维修分类数据(如果不存在)
INSERT IGNORE INTO `pm_repair_category` (
`id`, `tenant_id`, `category_name`, `category_code`, `category_description`,
`parent_id`, `level_depth`, `required_skills`, `skill_level`,
`estimated_duration`, `icon_url`, `color_code`, `sort_order`, `status`,
`create_time`, `create_by`
) VALUES
(1, '000000', '电路维修', 'ELECTRIC', '电路、照明、开关、插座等电气设备维修', 0, 1, '电工技能', '2', 60, '/icons/electric.svg', '#ff6b35', 1, '1', NOW(), 1),
(2, '000000', '水暖维修', 'PLUMBING', '水管、暖气、卫生间、厨房设备维修', 0, 1, '水暖技能', '2', 90, '/icons/plumbing.svg', '#1890ff', 2, '1', NOW(), 1),
(3, '000000', '土建维修', 'CONSTRUCTION', '墙面、地面、天花板、门窗等土建维修', 0, 1, '泥工技能,木工技能', '2', 120, '/icons/construction.svg', '#52c41a', 3, '1', NOW(), 1),
(4, '000000', '设备维修', 'EQUIPMENT', '空调、电梯、监控等设备维修', 0, 1, '设备维修技能', '3', 90, '/icons/equipment.svg', '#722ed1', 4, '1', NOW(), 1),
(5, '000000', '园林维修', 'GARDEN', '绿化、草坪、树木修剪等园林维护', 0, 1, '园林技能', '1', 45, '/icons/garden.svg', '#13c2c2', 5, '1', NOW(), 1),
(6, '000000', '清洁维护', 'CLEANING', '日常清洁、垃圾处理、环境维护', 0, 1, '清洁技能', '1', 30, '/icons/cleaning.svg', '#fa8c16', 6, '1', NOW(), 1);
-- 2. 插入测试报修工单数据
INSERT IGNORE INTO `pm_repair_order` (
`id`, `tenant_id`, `order_no`, `title`, `description`, `repair_location`,
`category_id`, `priority_level`, `expected_finish_time`,
`reporter_id`, `reporter_name`, `reporter_phone`, `reporter_dept`,
`order_status`, `create_time`, `create_by`
) VALUES
(1001, '000000', 'RO20250630001', '办公室空调不制冷', '302办公室空调制冷效果差,温度显示异常,需要检查维修', '行政楼3楼302办公室', 4, 2, DATE_ADD(NOW(), INTERVAL 1 DAY), 1, '张三', '13900001001', '行政办公室', '1', NOW(), 1),
(1002, '000000', 'RO20250630002', '教室投影仪故障', '多媒体教室投影仪无法正常显示,有色彩偏差', '教学楼A座201教室', 4, 3, DATE_ADD(NOW(), INTERVAL 2 DAY), 1, '李四', '13900001002', '教务处', '1', NOW(), 1),
(1003, '000000', 'RO20250630003', '宿舍楼水管漏水', '学生宿舍3楼卫生间水管接头漏水,影响正常使用', '学生宿舍楼3楼卫生间', 2, 1, DATE_ADD(NOW(), INTERVAL 3 HOUR), 1, '王五', '13900001003', '后勤处', '2', NOW(), 1),
(1004, '000000', 'RO20250630004', '图书馆照明灯具损坏', '图书馆二楼阅览区多个LED灯管损坏,照明不足', '图书馆二楼阅览区', 1, 3, DATE_ADD(NOW(), INTERVAL 1 WEEK), 1, '赵六', '13900001004', '图书馆', '1', NOW(), 1),
(1005, '000000', 'RO20250630005', '食堂地面瓷砖松动', '第二食堂入口处地面瓷砖松动,存在安全隐患', '第二食堂入口', 3, 2, DATE_ADD(NOW(), INTERVAL 2 DAY), 1, '孙七', '13900001005', '餐饮服务中心', '3', NOW(), 1);
-- 3. 为已分派的工单添加分派信息
UPDATE `pm_repair_order` SET
`assign_time` = NOW(),
`assignee_id` = 2,
`assignee_name` = '维修工A',
`assign_remark` = '紧急维修,请尽快处理'
WHERE `id` = 1003;
-- 4. 为进行中的工单添加开始时间
UPDATE `pm_repair_order` SET
`start_time` = DATE_SUB(NOW(), INTERVAL 30 MINUTE)
WHERE `id` = 1005;
-- 5. 验证插入的数据
SELECT
'数据插入完成' AS message,
(SELECT COUNT(*) FROM pm_repair_category WHERE tenant_id = '000000') AS category_count,
(SELECT COUNT(*) FROM pm_repair_order WHERE tenant_id = '000000' AND id >= 1001) AS order_count;
-- 6. 显示插入的报修工单数据
SELECT
id,
order_no,
title,
repair_location,
priority_level,
order_status,
reporter_name,
create_time
FROM pm_repair_order
WHERE tenant_id = '000000' AND id >= 1001
ORDER BY create_time DESC;
-119
View File
@@ -1,119 +0,0 @@
-- ==========================================
-- 为每个部门新增10个用户脚本
-- 创建时间:2025-06-30 13:30:00
-- 说明:为系统中的每个部门添加10个测试用户
-- ==========================================
-- 智能控制学院 (dept_id: 103) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2001, '000000', 103, 'zkxy001', '智控学院张三', 'sys_user', 'zkxy001@hzdyjs.edu.cn', '13800138001', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2002, '000000', 103, 'zkxy002', '智控学院李四', 'sys_user', 'zkxy002@hzdyjs.edu.cn', '13800138002', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2003, '000000', 103, 'zkxy003', '智控学院王五', 'sys_user', 'zkxy003@hzdyjs.edu.cn', '13800138003', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2004, '000000', 103, 'zkxy004', '智控学院赵六', 'sys_user', 'zkxy004@hzdyjs.edu.cn', '13800138004', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2005, '000000', 103, 'zkxy005', '智控学院孙七', 'sys_user', 'zkxy005@hzdyjs.edu.cn', '13800138005', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2006, '000000', 103, 'zkxy006', '智控学院周八', 'sys_user', 'zkxy006@hzdyjs.edu.cn', '13800138006', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2007, '000000', 103, 'zkxy007', '智控学院吴九', 'sys_user', 'zkxy007@hzdyjs.edu.cn', '13800138007', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2008, '000000', 103, 'zkxy008', '智控学院郑十', 'sys_user', 'zkxy008@hzdyjs.edu.cn', '13800138008', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2009, '000000', 103, 'zkxy009', '智控学院王小明', 'sys_user', 'zkxy009@hzdyjs.edu.cn', '13800138009', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师'),
(2010, '000000', 103, 'zkxy010', '智控学院李小红', 'sys_user', 'zkxy010@hzdyjs.edu.cn', '13800138010', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能控制学院教师');
-- 智能制造学院 (dept_id: 104) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2011, '000000', 104, 'znzz001', '智造学院陈刚', 'sys_user', 'znzz001@hzdyjs.edu.cn', '13800138011', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2012, '000000', 104, 'znzz002', '智造学院刘敏', 'sys_user', 'znzz002@hzdyjs.edu.cn', '13800138012', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2013, '000000', 104, 'znzz003', '智造学院黄伟', 'sys_user', 'znzz003@hzdyjs.edu.cn', '13800138013', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2014, '000000', 104, 'znzz004', '智造学院张娟', 'sys_user', 'znzz004@hzdyjs.edu.cn', '13800138014', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2015, '000000', 104, 'znzz005', '智造学院李强', 'sys_user', 'znzz005@hzdyjs.edu.cn', '13800138015', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2016, '000000', 104, 'znzz006', '智造学院王丽', 'sys_user', 'znzz006@hzdyjs.edu.cn', '13800138016', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2017, '000000', 104, 'znzz007', '智造学院赵军', 'sys_user', 'znzz007@hzdyjs.edu.cn', '13800138017', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2018, '000000', 104, 'znzz008', '智造学院孙燕', 'sys_user', 'znzz008@hzdyjs.edu.cn', '13800138018', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2019, '000000', 104, 'znzz009', '智造学院周涛', 'sys_user', 'znzz009@hzdyjs.edu.cn', '13800138019', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师'),
(2020, '000000', 104, 'znzz010', '智造学院吴静', 'sys_user', 'znzz010@hzdyjs.edu.cn', '13800138020', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '智能制造学院教师');
-- 医药健康学院 (dept_id: 105) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2021, '000000', 105, 'yyjk001', '医药学院郑华', 'sys_user', 'yyjk001@hzdyjs.edu.cn', '13800138021', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2022, '000000', 105, 'yyjk002', '医药学院冯芳', 'sys_user', 'yyjk002@hzdyjs.edu.cn', '13800138022', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2023, '000000', 105, 'yyjk003', '医药学院卫峰', 'sys_user', 'yyjk003@hzdyjs.edu.cn', '13800138023', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2024, '000000', 105, 'yyjk004', '医药学院褚宁', 'sys_user', 'yyjk004@hzdyjs.edu.cn', '13800138024', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2025, '000000', 105, 'yyjk005', '医药学院蒋凯', 'sys_user', 'yyjk005@hzdyjs.edu.cn', '13800138025', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2026, '000000', 105, 'yyjk006', '医药学院蒋雪', 'sys_user', 'yyjk006@hzdyjs.edu.cn', '13800138026', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2027, '000000', 105, 'yyjk007', '医药学院韩彬', 'sys_user', 'yyjk007@hzdyjs.edu.cn', '13800138027', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2028, '000000', 105, 'yyjk008', '医药学院朱琴', 'sys_user', 'yyjk008@hzdyjs.edu.cn', '13800138028', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2029, '000000', 105, 'yyjk009', '医药学院秦亮', 'sys_user', 'yyjk009@hzdyjs.edu.cn', '13800138029', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师'),
(2030, '000000', 105, 'yyjk010', '医药学院尤娜', 'sys_user', 'yyjk010@hzdyjs.edu.cn', '13800138030', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '医药健康学院教师');
-- 餐饮休闲学院 (dept_id: 106) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2031, '000000', 106, 'cyxx001', '餐饮学院许冰', 'sys_user', 'cyxx001@hzdyjs.edu.cn', '13800138031', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2032, '000000', 106, 'cyxx002', '餐饮学院葛玲', 'sys_user', 'cyxx002@hzdyjs.edu.cn', '13800138032', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2033, '000000', 106, 'cyxx003', '餐饮学院戴锋', 'sys_user', 'cyxx003@hzdyjs.edu.cn', '13800138033', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2034, '000000', 106, 'cyxx004', '餐饮学院薛婷', 'sys_user', 'cyxx004@hzdyjs.edu.cn', '13800138034', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2035, '000000', 106, 'cyxx005', '餐饮学院谭勇', 'sys_user', 'cyxx005@hzdyjs.edu.cn', '13800138035', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2036, '000000', 106, 'cyxx006', '餐饮学院吕萍', 'sys_user', 'cyxx006@hzdyjs.edu.cn', '13800138036', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2037, '000000', 106, 'cyxx007', '餐饮学院范超', 'sys_user', 'cyxx007@hzdyjs.edu.cn', '13800138037', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2038, '000000', 106, 'cyxx008', '餐饮学院史瑶', 'sys_user', 'cyxx008@hzdyjs.edu.cn', '13800138038', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2039, '000000', 106, 'cyxx009', '餐饮学院伍龙', 'sys_user', 'cyxx009@hzdyjs.edu.cn', '13800138039', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师'),
(2040, '000000', 106, 'cyxx010', '餐饮学院叶惠', 'sys_user', 'cyxx010@hzdyjs.edu.cn', '13800138040', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '餐饮休闲学院教师');
-- 数字信息学院 (dept_id: 107) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2041, '000000', 107, 'szxx001', '数信学院廖洋', 'sys_user', 'szxx001@hzdyjs.edu.cn', '13800138041', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2042, '000000', 107, 'szxx002', '数信学院马悦', 'sys_user', 'szxx002@hzdyjs.edu.cn', '13800138042', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2043, '000000', 107, 'szxx003', '数信学院常杰', 'sys_user', 'szxx003@hzdyjs.edu.cn', '13800138043', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2044, '000000', 107, 'szxx004', '数信学院乐希', 'sys_user', 'szxx004@hzdyjs.edu.cn', '13800138044', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2045, '000000', 107, 'szxx005', '数信学院金浩', 'sys_user', 'szxx005@hzdyjs.edu.cn', '13800138045', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2046, '000000', 107, 'szxx006', '数信学院夏蕾', 'sys_user', 'szxx006@hzdyjs.edu.cn', '13800138046', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2047, '000000', 107, 'szxx007', '数信学院侯斌', 'sys_user', 'szxx007@hzdyjs.edu.cn', '13800138047', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2048, '000000', 107, 'szxx008', '数信学院潘莎', 'sys_user', 'szxx008@hzdyjs.edu.cn', '13800138048', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2049, '000000', 107, 'szxx009', '数信学院万兵', 'sys_user', 'szxx009@hzdyjs.edu.cn', '13800138049', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师'),
(2050, '000000', 107, 'szxx010', '数信学院梅曼', 'sys_user', 'szxx010@hzdyjs.edu.cn', '13800138050', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '数字信息学院教师');
-- 院领导 (dept_id: 108) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2051, '000000', 108, 'yuanld001', '院长王建国', 'sys_user', 'yuanld001@hzdyjs.edu.cn', '13800138051', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '院长'),
(2052, '000000', 108, 'yuanld002', '副院长李红梅', 'sys_user', 'yuanld002@hzdyjs.edu.cn', '13800138052', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '副院长'),
(2053, '000000', 108, 'yuanld003', '副院长张志强', 'sys_user', 'yuanld003@hzdyjs.edu.cn', '13800138053', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '副院长'),
(2054, '000000', 108, 'yuanld004', '副院长赵文静', 'sys_user', 'yuanld004@hzdyjs.edu.cn', '13800138054', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '副院长'),
(2055, '000000', 108, 'yuanld005', '纪委书记孙建华', 'sys_user', 'yuanld005@hzdyjs.edu.cn', '13800138055', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '纪委书记'),
(2056, '000000', 108, 'yuanld006', '工会主席周雅丽', 'sys_user', 'yuanld006@hzdyjs.edu.cn', '13800138056', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '工会主席'),
(2057, '000000', 108, 'yuanld007', '顾问吴德成', 'sys_user', 'yuanld007@hzdyjs.edu.cn', '13800138057', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '顾问'),
(2058, '000000', 108, 'yuanld008', '顾问郑爱华', 'sys_user', 'yuanld008@hzdyjs.edu.cn', '13800138058', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '顾问'),
(2059, '000000', 108, 'yuanld009', '助理冯小明', 'sys_user', 'yuanld009@hzdyjs.edu.cn', '13800138059', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '院长助理'),
(2060, '000000', 108, 'yuanld010', '助理卫小娟', 'sys_user', 'yuanld010@hzdyjs.edu.cn', '13800138060', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '院长助理');
-- 办公室 (dept_id: 109) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2061, '000000', 109, 'bangong001', '办公室主任褚宁华', 'sys_user', 'bangong001@hzdyjs.edu.cn', '13800138061', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室主任'),
(2062, '000000', 109, 'bangong002', '办公室蒋小雪', 'sys_user', 'bangong002@hzdyjs.edu.cn', '13800138062', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2063, '000000', 109, 'bangong003', '办公室韩小彬', 'sys_user', 'bangong003@hzdyjs.edu.cn', '13800138063', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2064, '000000', 109, 'bangong004', '办公室朱小琴', 'sys_user', 'bangong004@hzdyjs.edu.cn', '13800138064', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2065, '000000', 109, 'bangong005', '办公室秦小亮', 'sys_user', 'bangong005@hzdyjs.edu.cn', '13800138065', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2066, '000000', 109, 'bangong006', '办公室尤小娜', 'sys_user', 'bangong006@hzdyjs.edu.cn', '13800138066', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2067, '000000', 109, 'bangong007', '办公室许小冰', 'sys_user', 'bangong007@hzdyjs.edu.cn', '13800138067', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2068, '000000', 109, 'bangong008', '办公室葛小玲', 'sys_user', 'bangong008@hzdyjs.edu.cn', '13800138068', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2069, '000000', 109, 'bangong009', '办公室戴小锋', 'sys_user', 'bangong009@hzdyjs.edu.cn', '13800138069', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员'),
(2070, '000000', 109, 'bangong010', '办公室薛小婷', 'sys_user', 'bangong010@hzdyjs.edu.cn', '13800138070', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '办公室工作人员');
-- 为所有新增用户分配基础角色(role_id: 3 - 普通用户角色)
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(2001, 3), (2002, 3), (2003, 3), (2004, 3), (2005, 3), (2006, 3), (2007, 3), (2008, 3), (2009, 3), (2010, 3),
(2011, 3), (2012, 3), (2013, 3), (2014, 3), (2015, 3), (2016, 3), (2017, 3), (2018, 3), (2019, 3), (2020, 3),
(2021, 3), (2022, 3), (2023, 3), (2024, 3), (2025, 3), (2026, 3), (2027, 3), (2028, 3), (2029, 3), (2030, 3),
(2031, 3), (2032, 3), (2033, 3), (2034, 3), (2035, 3), (2036, 3), (2037, 3), (2038, 3), (2039, 3), (2040, 3),
(2041, 3), (2042, 3), (2043, 3), (2044, 3), (2045, 3), (2046, 3), (2047, 3), (2048, 3), (2049, 3), (2050, 3),
(2051, 3), (2052, 3), (2053, 3), (2054, 3), (2055, 3), (2056, 3), (2057, 3), (2058, 3), (2059, 3), (2060, 3),
(2061, 3), (2062, 3), (2063, 3), (2064, 3), (2065, 3), (2066, 3), (2067, 3), (2068, 3), (2069, 3), (2070, 3);
-- 为管理岗位用户分配额外权限(role_id: 4 - 学院管理员)
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(2051, 1), -- 院长分配超级管理员权限
(2052, 4), (2053, 4), (2054, 4), -- 副院长分配学院管理员权限
(2055, 4), (2056, 4), -- 纪委书记和工会主席分配学院管理员权限
(2061, 4); -- 办公室主任分配学院管理员权限
-- 记录执行结果
SELECT
'用户创建完成' AS message,
(SELECT COUNT(*) FROM sys_user WHERE user_id >= 2001 AND user_id <= 2070) AS created_users,
(SELECT COUNT(*) FROM sys_user_role WHERE user_id >= 2001 AND user_id <= 2070) AS assigned_roles;
-109
View File
@@ -1,109 +0,0 @@
-- ==========================================
-- 为剩余部门新增10个用户脚本 (补充部分)
-- 创建时间:2025-06-30 13:35:00
-- 说明:为人文学院、生态园林学院和各行政部门新增用户
-- ==========================================
-- 人文学院 (dept_id: 1938483954134245378) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2071, '000000', 1938483954134245378, 'rwxy001', '人文学院谭老师', 'sys_user', 'rwxy001@hzdyjs.edu.cn', '13800138071', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2072, '000000', 1938483954134245378, 'rwxy002', '人文学院吕老师', 'sys_user', 'rwxy002@hzdyjs.edu.cn', '13800138072', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2073, '000000', 1938483954134245378, 'rwxy003', '人文学院范老师', 'sys_user', 'rwxy003@hzdyjs.edu.cn', '13800138073', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2074, '000000', 1938483954134245378, 'rwxy004', '人文学院史老师', 'sys_user', 'rwxy004@hzdyjs.edu.cn', '13800138074', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2075, '000000', 1938483954134245378, 'rwxy005', '人文学院伍老师', 'sys_user', 'rwxy005@hzdyjs.edu.cn', '13800138075', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2076, '000000', 1938483954134245378, 'rwxy006', '人文学院叶老师', 'sys_user', 'rwxy006@hzdyjs.edu.cn', '13800138076', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2077, '000000', 1938483954134245378, 'rwxy007', '人文学院廖老师', 'sys_user', 'rwxy007@hzdyjs.edu.cn', '13800138077', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2078, '000000', 1938483954134245378, 'rwxy008', '人文学院马老师', 'sys_user', 'rwxy008@hzdyjs.edu.cn', '13800138078', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2079, '000000', 1938483954134245378, 'rwxy009', '人文学院常老师', 'sys_user', 'rwxy009@hzdyjs.edu.cn', '13800138079', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师'),
(2080, '000000', 1938483954134245378, 'rwxy010', '人文学院乐老师', 'sys_user', 'rwxy010@hzdyjs.edu.cn', '13800138080', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人文学院教师');
-- 生态园林学院 (dept_id: 1938484049621770242) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2081, '000000', 1938484049621770242, 'styy001', '生态园林金老师', 'sys_user', 'styy001@hzdyjs.edu.cn', '13800138081', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2082, '000000', 1938484049621770242, 'styy002', '生态园林夏老师', 'sys_user', 'styy002@hzdyjs.edu.cn', '13800138082', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2083, '000000', 1938484049621770242, 'styy003', '生态园林侯老师', 'sys_user', 'styy003@hzdyjs.edu.cn', '13800138083', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2084, '000000', 1938484049621770242, 'styy004', '生态园林潘老师', 'sys_user', 'styy004@hzdyjs.edu.cn', '13800138084', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2085, '000000', 1938484049621770242, 'styy005', '生态园林万老师', 'sys_user', 'styy005@hzdyjs.edu.cn', '13800138085', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2086, '000000', 1938484049621770242, 'styy006', '生态园林梅老师', 'sys_user', 'styy006@hzdyjs.edu.cn', '13800138086', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2087, '000000', 1938484049621770242, 'styy007', '生态园林任老师', 'sys_user', 'styy007@hzdyjs.edu.cn', '13800138087', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2088, '000000', 1938484049621770242, 'styy008', '生态园林柳老师', 'sys_user', 'styy008@hzdyjs.edu.cn', '13800138088', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2089, '000000', 1938484049621770242, 'styy009', '生态园林花老师', 'sys_user', 'styy009@hzdyjs.edu.cn', '13800138089', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师'),
(2090, '000000', 1938484049621770242, 'styy010', '生态园林草老师', 'sys_user', 'styy010@hzdyjs.edu.cn', '13800138090', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '生态园林学院教师');
-- 人事处 (dept_id: 1938484510047297538) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2091, '000000', 1938484510047297538, 'rsc001', '人事处处长林明', 'sys_user', 'rsc001@hzdyjs.edu.cn', '13800138091', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处处长'),
(2092, '000000', 1938484510047297538, 'rsc002', '人事处副处长何琳', 'sys_user', 'rsc002@hzdyjs.edu.cn', '13800138092', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处副处长'),
(2093, '000000', 1938484510047297538, 'rsc003', '人事处科长方刚', 'sys_user', 'rsc003@hzdyjs.edu.cn', '13800138093', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处科长'),
(2094, '000000', 1938484510047297538, 'rsc004', '人事处干事邓妮', 'sys_user', 'rsc004@hzdyjs.edu.cn', '13800138094', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事'),
(2095, '000000', 1938484510047297538, 'rsc005', '人事处干事曾磊', 'sys_user', 'rsc005@hzdyjs.edu.cn', '13800138095', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事'),
(2096, '000000', 1938484510047297538, 'rsc006', '人事处干事彭蕊', 'sys_user', 'rsc006@hzdyjs.edu.cn', '13800138096', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事'),
(2097, '000000', 1938484510047297538, 'rsc007', '人事处干事白帆', 'sys_user', 'rsc007@hzdyjs.edu.cn', '13800138097', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事'),
(2098, '000000', 1938484510047297538, 'rsc008', '人事处干事邬菊', 'sys_user', 'rsc008@hzdyjs.edu.cn', '13800138098', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事'),
(2099, '000000', 1938484510047297538, 'rsc009', '人事处干事贺威', 'sys_user', 'rsc009@hzdyjs.edu.cn', '13800138099', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事'),
(2100, '000000', 1938484510047297538, 'rsc010', '人事处干事齐艳', 'sys_user', 'rsc010@hzdyjs.edu.cn', '13800138100', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '人事处干事');
-- 教务处 (dept_id: 1938484570826956801) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2101, '000000', 1938484570826956801, 'jwc001', '教务处处长江建', 'sys_user', 'jwc001@hzdyjs.edu.cn', '13800138101', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处处长'),
(2102, '000000', 1938484570826956801, 'jwc002', '教务处副处长易霞', 'sys_user', 'jwc002@hzdyjs.edu.cn', '13800138102', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处副处长'),
(2103, '000000', 1938484570826956801, 'jwc003', '教务处科长时健', 'sys_user', 'jwc003@hzdyjs.edu.cn', '13800138103', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处科长'),
(2104, '000000', 1938484570826956801, 'jwc004', '教务处干事查瑜', 'sys_user', 'jwc004@hzdyjs.edu.cn', '13800138104', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事'),
(2105, '000000', 1938484570826956801, 'jwc005', '教务处干事宫强', 'sys_user', 'jwc005@hzdyjs.edu.cn', '13800138105', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事'),
(2106, '000000', 1938484570826956801, 'jwc006', '教务处干事宁兰', 'sys_user', 'jwc006@hzdyjs.edu.cn', '13800138106', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事'),
(2107, '000000', 1938484570826956801, 'jwc007', '教务处干事米超', 'sys_user', 'jwc007@hzdyjs.edu.cn', '13800138107', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事'),
(2108, '000000', 1938484570826956801, 'jwc008', '教务处干事习莉', 'sys_user', 'jwc008@hzdyjs.edu.cn', '13800138108', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事'),
(2109, '000000', 1938484570826956801, 'jwc009', '教务处干事欧翔', 'sys_user', 'jwc009@hzdyjs.edu.cn', '13800138109', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事'),
(2110, '000000', 1938484570826956801, 'jwc010', '教务处干事练芬', 'sys_user', 'jwc010@hzdyjs.edu.cn', '13800138110', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '教务处干事');
-- 学生处 (dept_id: 1938484642188845057) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2111, '000000', 1938484642188845057, 'xsc001', '学生处处长游新', 'sys_user', 'xsc001@hzdyjs.edu.cn', '13800138111', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处处长'),
(2112, '000000', 1938484642188845057, 'xsc002', '学生处副处长廉美', 'sys_user', 'xsc002@hzdyjs.edu.cn', '13800138112', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处副处长'),
(2113, '000000', 1938484642188845057, 'xsc003', '学生处科长郝坚', 'sys_user', 'xsc003@hzdyjs.edu.cn', '13800138113', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处科长'),
(2114, '000000', 1938484642188845057, 'xsc004', '学生处干事殷慧', 'sys_user', 'xsc004@hzdyjs.edu.cn', '13800138114', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事'),
(2115, '000000', 1938484642188845057, 'xsc005', '学生处干事连涛', 'sys_user', 'xsc005@hzdyjs.edu.cn', '13800138115', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事'),
(2116, '000000', 1938484642188845057, 'xsc006', '学生处干事罗霞', 'sys_user', 'xsc006@hzdyjs.edu.cn', '13800138116', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事'),
(2117, '000000', 1938484642188845057, 'xsc007', '学生处干事苏刚', 'sys_user', 'xsc007@hzdyjs.edu.cn', '13800138117', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事'),
(2118, '000000', 1938484642188845057, 'xsc008', '学生处干事苗晨', 'sys_user', 'xsc008@hzdyjs.edu.cn', '13800138118', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事'),
(2119, '000000', 1938484642188845057, 'xsc009', '学生处干事汤波', 'sys_user', 'xsc009@hzdyjs.edu.cn', '13800138119', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事'),
(2120, '000000', 1938484642188845057, 'xsc010', '学生处干事牟爽', 'sys_user', 'xsc010@hzdyjs.edu.cn', '13800138120', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '学生处干事');
-- 总务处 (dept_id: 1938484793632579586) - 10个用户
INSERT INTO `sys_user` (`user_id`, `tenant_id`, `dept_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `del_flag`, `login_ip`, `login_date`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES
(2121, '000000', 1938484793632579586, 'zwc001', '总务处处长曲建', 'sys_user', 'zwc001@hzdyjs.edu.cn', '13800138121', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处处长'),
(2122, '000000', 1938484793632579586, 'zwc002', '总务处副处长温丽', 'sys_user', 'zwc002@hzdyjs.edu.cn', '13800138122', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处副处长'),
(2123, '000000', 1938484793632579586, 'zwc003', '总务处科长舒刚', 'sys_user', 'zwc003@hzdyjs.edu.cn', '13800138123', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处科长'),
(2124, '000000', 1938484793632579586, 'zwc004', '总务处干事晏茹', 'sys_user', 'zwc004@hzdyjs.edu.cn', '13800138124', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事'),
(2125, '000000', 1938484793632579586, 'zwc005', '总务处干事陆雄', 'sys_user', 'zwc005@hzdyjs.edu.cn', '13800138125', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事'),
(2126, '000000', 1938484793632579586, 'zwc006', '总务处干事湛红', 'sys_user', 'zwc006@hzdyjs.edu.cn', '13800138126', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事'),
(2127, '000000', 1938484793632579586, 'zwc007', '总务处干事邹凯', 'sys_user', 'zwc007@hzdyjs.edu.cn', '13800138127', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事'),
(2128, '000000', 1938484793632579586, 'zwc008', '总务处干事农玲', 'sys_user', 'zwc008@hzdyjs.edu.cn', '13800138128', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事'),
(2129, '000000', 1938484793632579586, 'zwc009', '总务处干事包龙', 'sys_user', 'zwc009@hzdyjs.edu.cn', '13800138129', '0', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事'),
(2130, '000000', 1938484793632579586, 'zwc010', '总务处干事步芸', 'sys_user', 'zwc010@hzdyjs.edu.cn', '13800138130', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '', NULL, 103, 1, now(), 1, now(), '总务处干事');
-- 为所有新增用户分配基础角色(role_id: 3 - 普通用户角色)
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(2071, 3), (2072, 3), (2073, 3), (2074, 3), (2075, 3), (2076, 3), (2077, 3), (2078, 3), (2079, 3), (2080, 3),
(2081, 3), (2082, 3), (2083, 3), (2084, 3), (2085, 3), (2086, 3), (2087, 3), (2088, 3), (2089, 3), (2090, 3),
(2091, 3), (2092, 3), (2093, 3), (2094, 3), (2095, 3), (2096, 3), (2097, 3), (2098, 3), (2099, 3), (2100, 3),
(2101, 3), (2102, 3), (2103, 3), (2104, 3), (2105, 3), (2106, 3), (2107, 3), (2108, 3), (2109, 3), (2110, 3),
(2111, 3), (2112, 3), (2113, 3), (2114, 3), (2115, 3), (2116, 3), (2117, 3), (2118, 3), (2119, 3), (2120, 3),
(2121, 3), (2122, 3), (2123, 3), (2124, 3), (2125, 3), (2126, 3), (2127, 3), (2128, 3), (2129, 3), (2130, 3);
-- 为管理岗位用户分配额外权限(role_id: 4 - 学院管理员)
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(2091, 4), -- 人事处处长
(2092, 4), -- 人事处副处长
(2101, 4), -- 教务处处长
(2102, 4), -- 教务处副处长
(2111, 4), -- 学生处处长
(2112, 4), -- 学生处副处长
(2121, 4), -- 总务处处长
(2122, 4); -- 总务处副处长
-- 记录执行结果
SELECT
'补充用户创建完成' AS message,
(SELECT COUNT(*) FROM sys_user WHERE user_id >= 2071 AND user_id <= 2130) AS created_users,
(SELECT COUNT(*) FROM sys_user_role WHERE user_id >= 2071 AND user_id <= 2130) AS assigned_roles;
-344
View File
@@ -1,344 +0,0 @@
const axios = require('axios');
const CryptoJS = require('crypto-js');
const NodeRSA = require('node-rsa');
// 配置信息
const CONFIG = {
BASE_URL: 'http://localhost/dev-api',
LOGIN_ENDPOINT: '/auth/login',
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
TENANT_ID: '000000',
REFERER: 'http://localhost/login?redirect=%252Findex',
// RSA公钥
RSA_PUBLIC_KEY:
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
};
// 默认登录凭据
const CREDENTIALS = {
username: 'admin',
password: 'admin123',
};
/**
* 生成随机AES密钥
*/
function generateRandomAESKey(length = 32) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
/**
* AES加密数据
*/
function encryptWithAES(data, key) {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
}
/**
* RSA加密AES密钥
*/
function encryptAESKeyWithRSA(aesKey) {
try {
const pemKey = `-----BEGIN PUBLIC KEY-----\n${CONFIG.RSA_PUBLIC_KEY}\n-----END PUBLIC KEY-----`;
const rsaKey = new NodeRSA(pemKey);
rsaKey.setOptions({ encryptionScheme: 'pkcs1' });
// 将AES密钥转为Base64
const aesKeyBase64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(aesKey));
// RSA加密
return rsaKey.encrypt(aesKeyBase64, 'base64');
} catch (error) {
console.error('RSA加密失败:', error.message);
return null;
}
}
/**
* 自定义Header登录 - 按照用户要求
*/
async function customHeaderLogin(credentials = CREDENTIALS) {
console.log('🔐 开始自定义Header登录...');
console.log('📍 接口地址:', CONFIG.BASE_URL + CONFIG.LOGIN_ENDPOINT);
try {
// 1. 生成AES密钥
const aesKey = generateRandomAESKey(32);
console.log('🔑 生成AES密钥:', aesKey);
// 2. 准备登录数据
const loginData = {
tenantId: CONFIG.TENANT_ID,
username: credentials.username,
password: credentials.password,
clientId: CONFIG.CLIENT_ID,
grantType: 'password',
};
console.log('📋 登录数据:', JSON.stringify(loginData, null, 2));
// 3. AES加密登录数据
const encryptedData = encryptWithAES(JSON.stringify(loginData), aesKey);
console.log('🔐 AES加密后数据长度:', encryptedData.length);
// 4. RSA加密AES密钥
const encryptedKey = encryptAESKeyWithRSA(aesKey);
if (!encryptedKey) {
throw new Error('RSA加密AES密钥失败');
}
console.log('🔑 RSA加密后密钥长度:', encryptedKey.length);
// 5. 按照用户要求设置Header
const headers = {
'Content-Type': 'application/json;charset=utf-8',
clientid: CONFIG.CLIENT_ID, // 用户要求的clientid
'encrypt-key': encryptedKey, // 用户要求的encrypt-key
referer: CONFIG.REFERER, // 用户要求的referer
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
isToken: 'false',
isEncrypt: 'true',
repeatSubmit: 'false',
};
console.log('📤 请求头设置:');
console.log(' - clientid:', headers.clientid);
console.log(' - encrypt-key:', encryptedKey.substring(0, 50) + '...');
console.log(' - referer:', headers.referer);
console.log(' - Content-Type:', headers['Content-Type']);
// 6. 发送登录请求
console.log('\n🚀 发送登录请求...');
const response = await axios.post(CONFIG.BASE_URL + CONFIG.LOGIN_ENDPOINT, encryptedData, {
headers: headers,
timeout: 10000,
});
// 7. 处理响应
console.log('✅ 请求成功!');
console.log('📊 响应状态:', response.status);
console.log('📄 响应数据:', JSON.stringify(response.data, null, 2));
// 8. 提取Token
if (response.data.code === 200 && response.data.data?.access_token) {
const token = response.data.data.access_token;
console.log('\n🎯 成功获取Token!');
console.log('🎫 Token:', token);
console.log('🎫 Token长度:', token.length);
console.log('🎫 Token前50字符:', token.substring(0, 50) + '...');
// 测试Token有效性
await testTokenValidity(token);
return {
success: true,
token: token,
data: response.data,
};
} else {
console.log('⚠️ 登录请求成功,但未获取到Token');
console.log('❌ 错误信息:', response.data.msg);
return {
success: false,
error: response.data.msg || 'Unknown error',
data: response.data,
};
}
} catch (error) {
console.log('❌ 登录失败!');
if (error.response) {
console.log('📊 错误状态码:', error.response.status);
console.log('📄 错误响应:', JSON.stringify(error.response.data, null, 2));
// 特殊错误处理
if (error.response.status === 500) {
console.log('💡 提示: 这可能是服务器端加密处理问题');
} else if (error.response.status === 403) {
console.log('💡 提示: 这是权限配置问题,需要联系管理员');
}
} else if (error.request) {
console.log('🌐 网络错误,请检查服务器是否启动');
console.log('📡 请求详情:', error.message);
} else {
console.log('⚙️ 请求配置错误:', error.message);
}
return {
success: false,
error: error.response?.data || error.message,
};
}
}
/**
* 测试Token有效性
*/
async function testTokenValidity(token) {
console.log('\n🧪 测试Token有效性...');
try {
const response = await axios.get(CONFIG.BASE_URL + '/system/user/getInfo', {
headers: {
Authorization: `Bearer ${token}`,
clientid: CONFIG.CLIENT_ID,
referer: CONFIG.REFERER,
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
},
timeout: 10000,
});
console.log('✅ Token有效! 用户信息获取成功');
console.log('👤 用户信息:', JSON.stringify(response.data, null, 2));
return true;
} catch (error) {
console.log('❌ Token测试失败');
if (error.response) {
console.log('📊 错误状态码:', error.response.status);
console.log('📄 错误信息:', error.response.data);
} else {
console.log('🌐 网络错误:', error.message);
}
return false;
}
}
/**
* 标准非加密登录 (备用方案)
*/
async function standardLogin(credentials = CREDENTIALS) {
console.log('\n🔓 尝试标准非加密登录 (备用方案)...');
const loginData = {
tenantId: CONFIG.TENANT_ID,
username: credentials.username,
password: credentials.password,
clientId: CONFIG.CLIENT_ID,
grantType: 'password',
};
const headers = {
'Content-Type': 'application/json;charset=utf-8',
clientid: CONFIG.CLIENT_ID,
referer: CONFIG.REFERER,
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
};
try {
const response = await axios.post(CONFIG.BASE_URL + CONFIG.LOGIN_ENDPOINT, loginData, {
headers,
timeout: 10000,
});
console.log('✅ 标准登录请求成功');
console.log('📄 响应数据:', JSON.stringify(response.data, null, 2));
if (response.data.code === 200 && response.data.data?.access_token) {
const token = response.data.data.access_token;
console.log('🎫 获取到Token:', token.substring(0, 50) + '...');
return { success: true, token: token, data: response.data };
} else {
return { success: false, error: response.data.msg, data: response.data };
}
} catch (error) {
console.log('❌ 标准登录失败');
if (error.response) {
console.log('📊 状态码:', error.response.status);
console.log('📄 错误信息:', error.response.data);
}
return { success: false, error: error.response?.data || error.message };
}
}
/**
* 主函数 - 智能登录策略
*/
async function smartLogin(credentials) {
console.log('🌟'.repeat(25));
console.log('🚀 智能登录脚本 - 自定义Header版本');
console.log('🌟'.repeat(25));
let result = null;
// 策略1: 首先尝试加密登录
console.log('\n📋 策略1: 尝试加密登录 (用户指定Header格式)');
result = await customHeaderLogin(credentials);
if (result.success) {
console.log('\n🎉 加密登录成功!');
return result;
}
// 策略2: 如果加密登录失败,尝试标准登录
console.log('\n📋 策略2: 尝试标准非加密登录 (备用方案)');
result = await standardLogin(credentials);
if (result.success) {
console.log('\n🎉 标准登录成功!');
return result;
}
// 所有策略都失败
console.log('\n❌ 所有登录策略都失败了');
console.log('🔧 建议检查:');
console.log(' 1. 服务器是否正常运行');
console.log(' 2. 用户权限是否正确配置');
console.log(' 3. 网络连接是否正常');
return result;
}
// 导出函数
module.exports = {
customHeaderLogin,
standardLogin,
testTokenValidity,
smartLogin,
CONFIG,
CREDENTIALS,
};
// 直接运行脚本
if (require.main === module) {
console.log('🔥 检查依赖包...');
// 检查依赖
try {
require('axios');
require('crypto-js');
require('node-rsa');
console.log('✅ 所有依赖包检查完成');
} catch (error) {
console.error('❌ 缺少依赖包,请运行: npm install axios crypto-js node-rsa');
process.exit(1);
}
// 运行智能登录
smartLogin(CREDENTIALS)
.then((result) => {
console.log('\n' + '🌟'.repeat(25));
console.log('📊 最终结果:');
if (result.success) {
console.log('✅ 登录成功!');
console.log('🎫 Token:', result.token);
console.log('💡 您可以使用这个Token进行后续API调用');
} else {
console.log('❌ 登录失败!');
console.log('📄 错误信息:', result.error);
}
console.log('🌟'.repeat(25));
})
.catch((error) => {
console.error('💥 脚本运行出错:', error.message);
process.exit(1);
});
}
-120
View File
@@ -1,120 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>出国审批页面API调试</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.error { color: red; }
.success { color: green; }
.info { color: blue; }
pre { background: #f5f5f5; padding: 10px; overflow-x: auto; }
button { margin: 5px; padding: 8px 16px; }
</style>
</head>
<body>
<h1>出国审批页面API调试工具</h1>
<div id="status">正在检查...</div>
<h2>测试结果</h2>
<div id="results"></div>
<h2>控制台日志</h2>
<pre id="console-log"></pre>
<script>
const baseURL = 'http://localhost:8080';
const results = document.getElementById('results');
const consoleLog = document.getElementById('console-log');
const status = document.getElementById('status');
// 劫持console.log来显示在页面上
const originalLog = console.log;
const originalError = console.error;
console.log = function(...args) {
originalLog.apply(console, args);
consoleLog.innerHTML += '[LOG] ' + args.join(' ') + '\n';
};
console.error = function(...args) {
originalError.apply(console, args);
consoleLog.innerHTML += '[ERROR] ' + args.join(' ') + '\n';
};
async function testAPI() {
status.innerHTML = '<span class="info">开始测试API...</span>';
// 测试1: 检查基础连接
try {
console.log('测试1: 检查后端服务连接...');
const response = await fetch(`${baseURL}/actuator/health`);
const data = await response.text();
addResult('✅ 后端服务连接', 'success', `状态码: ${response.status}, 响应: ${data.substring(0, 100)}`);
} catch (error) {
addResult('❌ 后端服务连接失败', 'error', error.message);
return;
}
// 测试2: 检查待审批申请API(需要token)
try {
console.log('测试2: 检查待审批申请API...');
const response = await fetch(`${baseURL}/rs/overseas/application/pending/list?pageNum=1&pageSize=10`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.status === 401) {
addResult('⚠️ 待审批申请API需要认证', 'info', `状态码: ${response.status}, 消息: ${data.msg}`);
} else if (response.ok) {
addResult('✅ 待审批申请API正常', 'success', `返回数据: ${JSON.stringify(data, null, 2)}`);
} else {
addResult('❌ 待审批申请API异常', 'error', `状态码: ${response.status}, 响应: ${JSON.stringify(data, null, 2)}`);
}
} catch (error) {
addResult('❌ 待审批申请API请求失败', 'error', error.message);
}
// 测试3: 检查审批记录API(需要token)
try {
console.log('测试3: 检查审批记录API...');
const response = await fetch(`${baseURL}/rs/overseas/approval/my/list?pageNum=1&pageSize=10`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.status === 401) {
addResult('⚠️ 审批记录API需要认证', 'info', `状态码: ${response.status}, 消息: ${data.msg}`);
} else if (response.ok) {
addResult('✅ 审批记录API正常', 'success', `返回数据: ${JSON.stringify(data, null, 2)}`);
} else {
addResult('❌ 审批记录API异常', 'error', `状态码: ${response.status}, 响应: ${JSON.stringify(data, null, 2)}`);
}
} catch (error) {
addResult('❌ 审批记录API请求失败', 'error', error.message);
}
status.innerHTML = '<span class="success">测试完成!</span>';
}
function addResult(title, type, detail) {
const div = document.createElement('div');
div.innerHTML = `<h3 class="${type}">${title}</h3><pre>${detail}</pre>`;
results.appendChild(div);
}
// 页面加载后自动执行测试
window.onload = function() {
testAPI();
};
</script>
</body>
</html>
-115
View File
@@ -1,115 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>字典数据调试</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 800px; margin: 0 auto; }
.test-section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.success { color: green; }
.error { color: red; }
.warning { color: orange; }
pre { background: #f5f5f5; padding: 10px; border-radius: 3px; overflow-x: auto; }
</style>
</head>
<body>
<div class="container">
<h1>字典数据调试页面</h1>
<div class="test-section">
<h3>测试说明</h3>
<p>此页面用于调试学生信息管理页面的字典数据问题</p>
<p>请确保:</p>
<ul>
<li>后端服务正在运行在 http://localhost:8080</li>
<li>已经登录管理后台</li>
<li>打开浏览器开发者工具查看详细日志</li>
</ul>
</div>
<div class="test-section">
<h3>测试结果</h3>
<div id="results"></div>
</div>
<div class="test-section">
<h3>字典数据详情</h3>
<div id="dictDetails"></div>
</div>
</div>
<script>
const results = document.getElementById('results');
const dictDetails = document.getElementById('dictDetails');
function log(message, type = 'info') {
const div = document.createElement('div');
div.className = type;
div.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
results.appendChild(div);
console.log(`[DICT-DEBUG] ${message}`);
}
function showDictData(dictType, data) {
const section = document.createElement('div');
section.innerHTML = `
<h4>${dictType} (${data.length} 条记录)</h4>
<pre>${JSON.stringify(data, null, 2)}</pre>
`;
dictDetails.appendChild(section);
}
async function testDictAPI() {
log('开始测试字典API...');
const dictTypes = ['xg_gender', 'xg_student_status', 'xg_info_status'];
for (const dictType of dictTypes) {
try {
log(`正在测试字典类型: ${dictType}`);
const response = await fetch(`http://localhost:8080/system/dict/data/type/${dictType}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
credentials: 'include' // 包含cookies
});
const result = await response.json();
if (result.code === 200) {
log(`✅ ${dictType}: 成功获取 ${result.data.length} 条数据`, 'success');
showDictData(dictType, result.data);
} else if (result.code === 401) {
log(`❌ ${dictType}: 认证失败,请先登录管理后台`, 'error');
} else {
log(`❌ ${dictType}: ${result.msg}`, 'error');
}
} catch (error) {
log(`❌ ${dictType}: 网络错误 - ${error.message}`, 'error');
}
}
}
// 页面加载完成后执行测试
window.addEventListener('load', () => {
log('页面加载完成,开始测试...');
testDictAPI();
});
// 添加手动重试按钮
const retryButton = document.createElement('button');
retryButton.textContent = '重新测试';
retryButton.onclick = () => {
results.innerHTML = '';
dictDetails.innerHTML = '';
testDictAPI();
};
document.querySelector('.container').appendChild(retryButton);
</script>
</body>
</html>
-172
View File
@@ -1,172 +0,0 @@
-- ==========================================
-- 维修人员权限修复脚本
-- 功能:确保维修人员角色正确配置,能正确过滤只查看自己相关的订单
-- 时间:2025-06-30 18:45:00
-- 说明:使用UPSERT逻辑,支持重复执行
-- ==========================================
-- 第一步:创建维修人员和管理员角色(如果不存在)
INSERT INTO sys_role (
role_id, role_name, role_key, role_sort, data_scope,
menu_check_strictly, dept_check_strictly, status,
del_flag, create_by, create_time, update_by, update_time, remark
)
SELECT
5, '维修人员', 'maintenance_worker', 5, '5',
1, 1, '0',
'0', 1, now(), 1, now(), '负责具体维修工作的人员,只能查看和处理分配给自己的维修工单或自己创建的工单'
WHERE NOT EXISTS (SELECT 1 FROM sys_role WHERE role_id = 5);
INSERT INTO sys_role (
role_id, role_name, role_key, role_sort, data_scope,
menu_check_strictly, dept_check_strictly, status,
del_flag, create_by, create_time, update_by, update_time, remark
)
SELECT
6, '物业维修管理员', 'property_maintenance_admin', 6, '3',
1, 1, '0',
'0', 1, now(), 1, now(), '负责物业维修管理工作,可以管理所有维修工单、分配维修任务、查看维修统计'
WHERE NOT EXISTS (SELECT 1 FROM sys_role WHERE role_id = 6);
-- 第二步:确保维修人员角色有基础菜单权限
-- 为维修人员分配物业管理主菜单权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 5, menu_id FROM sys_menu WHERE menu_id = 2100; -- 物业管理主菜单
-- 为维修人员分配报修管理子菜单权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 5, menu_id FROM sys_menu WHERE menu_id = 2110; -- 报修管理菜单
-- 为维修人员分配基础功能权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) VALUES
(5, 2111), -- 查询权限
(5, 2113), -- 修改权限
(5, 2117), -- 开始维修权限
(5, 2118), -- 完成维修权限
(5, 2120); -- 生成工单编号权限
-- 第三步:为物业维修管理员分配完整权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 6, menu_id FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199;
-- 第四步:创建测试用户(如果不存在)
-- 创建一个专门的维修人员测试账号
INSERT INTO sys_user (
user_id, user_name, nick_name, email, phone, sex, avatar, password, status,
del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark
)
SELECT
999, 'maintenance_test', '测试维修员', 'maintenance@test.com', '13800000001', '0', '',
'$2a$10$7JB720yubVSOnlhHOY5aDuAEaG9MXOAG7Ap3CfvzDuR8YYnQbmLqK', -- 密码:admin123
'0', '0', '', now(), 1, now(), 1, now(), '维修人员权限测试账号'
WHERE NOT EXISTS (SELECT 1 FROM sys_user WHERE user_id = 999);
-- 创建一个物业管理员测试账号
INSERT INTO sys_user (
user_id, user_name, nick_name, email, phone, sex, avatar, password, status,
del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark
)
SELECT
998, 'property_admin_test', '测试物业管理员', 'property@test.com', '13800000002', '0', '',
'$2a$10$7JB720yubVSOnlhHOY5aDuAEaG9MXOAG7Ap3CfvzDuR8YYnQbmLqK', -- 密码:admin123
'0', '0', '', now(), 1, now(), 1, now(), '物业管理员权限测试账号'
WHERE NOT EXISTS (SELECT 1 FROM sys_user WHERE user_id = 998);
-- 第五步:为测试用户分配角色
INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES
(999, 5), -- 测试维修员 -> 维修人员角色
(998, 6); -- 测试物业管理员 -> 物业维修管理员角色
-- 第六步:为现有总务处用户分配维修人员角色(如果用户存在)
INSERT IGNORE INTO sys_user_role (user_id, role_id)
SELECT user_id, 5 FROM sys_user
WHERE user_name IN ('zwc005', 'zwc007') AND user_id IS NOT NULL;
-- 为现有总务处用户分配物业管理员角色(如果用户存在)
INSERT IGNORE INTO sys_user_role (user_id, role_id)
SELECT user_id, 6 FROM sys_user
WHERE user_name IN ('zwc003', 'zwc004') AND user_id IS NOT NULL;
-- 第七步:创建测试数据 - 创建一些报修订单用于测试权限
-- 为测试维修员创建的报修订单
INSERT INTO pm_repair_order (
id, order_no, title, description, repair_location, category_id, priority_level,
reporter_id, reporter_name, reporter_phone, order_status, assignee_id, assignee_name,
attachment_count, progress_count, create_by, create_time, update_by, update_time,
tenant_id, create_dept, deleted
)
SELECT
9901, 'RO20251230001', '测试报修-维修员自己创建', '这是维修员自己创建的报修订单,用于测试权限',
'测试楼101室', 1, 2, 999, '测试维修员', '13800000001', '1', NULL, NULL,
0, 0, 999, now(), 999, now(), '000000', 103, 0
WHERE NOT EXISTS (SELECT 1 FROM pm_repair_order WHERE id = 9901);
-- 分配给测试维修员的报修订单
INSERT INTO pm_repair_order (
id, order_no, title, description, repair_location, category_id, priority_level,
reporter_id, reporter_name, reporter_phone, order_status, assignee_id, assignee_name,
attachment_count, progress_count, create_by, create_time, update_by, update_time,
tenant_id, create_dept, deleted
)
SELECT
9902, 'RO20251230002', '测试报修-分配给维修员', '这是分配给维修员的报修订单,用于测试权限',
'测试楼102室', 1, 3, 1, '管理员', '13900000000', '2', 999, '测试维修员',
0, 0, 1, now(), 1, now(), '000000', 103, 0
WHERE NOT EXISTS (SELECT 1 FROM pm_repair_order WHERE id = 9902);
-- 其他用户创建的报修订单(维修员不应该看到)
INSERT INTO pm_repair_order (
id, order_no, title, description, repair_location, category_id, priority_level,
reporter_id, reporter_name, reporter_phone, order_status, assignee_id, assignee_name,
attachment_count, progress_count, create_by, create_time, update_by, update_time,
tenant_id, create_dept, deleted
)
SELECT
9903, 'RO20251230003', '测试报修-其他用户创建', '这是其他用户创建的报修订单,维修员不应该看到',
'测试楼103室', 1, 1, 998, '测试物业管理员', '13800000002', '1', NULL, NULL,
0, 0, 998, now(), 998, now(), '000000', 103, 0
WHERE NOT EXISTS (SELECT 1 FROM pm_repair_order WHERE id = 9903);
-- 第八步:验证配置结果
SELECT '=== 角色配置验证 ===' as section;
SELECT
role_id, role_name, role_key, status, remark
FROM sys_role
WHERE role_id IN (5, 6)
ORDER BY role_id;
SELECT '=== 用户角色分配验证 ===' as section;
SELECT
u.user_id, u.user_name, u.nick_name, r.role_name, r.role_key
FROM sys_user_role ur
JOIN sys_user u ON ur.user_id = u.user_id
JOIN sys_role r ON ur.role_id = r.role_id
WHERE ur.role_id IN (5, 6)
ORDER BY ur.role_id, u.user_id;
SELECT '=== 维修人员权限验证 ===' as section;
SELECT
COUNT(*) as menu_permission_count
FROM sys_role_menu
WHERE role_id = 5;
SELECT '=== 测试数据验证 ===' as section;
SELECT
id, order_no, title, reporter_id, reporter_name, assignee_id, assignee_name, order_status
FROM pm_repair_order
WHERE id BETWEEN 9901 AND 9903
ORDER BY id;
-- 第九步:权限测试说明
SELECT '=== 权限测试说明 ===' as section;
SELECT
'1. 使用maintenance_test账号登录,应该只能看到自己创建(9901)和分配给自己(9902)的工单' as test_case_1,
'2. 使用property_admin_test账号登录,应该能看到所有工单(9901,9902,9903)' as test_case_2,
'3. 使用admin账号登录,作为超级管理员应该能看到所有工单' as test_case_3;
-- 第十步:完成提示
SELECT
'权限修复完成' as status,
'maintenance_test / admin123 - 维修人员测试账号' as test_account_1,
'property_admin_test / admin123 - 物业管理员测试账号' as test_account_2,
'请使用这些账号测试权限是否正确' as next_step;
-93
View File
@@ -1,93 +0,0 @@
-- ==========================================
-- 统计权限修复脚本
-- 功能:确保只有维护管理员和超级管理员才能查看统计卡片
-- 时间:2025-06-30 18:58:00
-- 说明:修复统计权限分配,移除维修人员的统计权限
-- ==========================================
-- 第一步:确保统计权限项存在
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
query_param, is_frame, is_cache, menu_type, visible, status,
perms, icon, create_by, create_time, update_by, update_time, remark
)
SELECT
2120, '统计数据', 2110, 10, '', '',
'', 1, 0, 'F', '0', '0',
'pm:repairOrder:statistics', '#', 1, now(), 1, now(), '获取报修统计数据'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2120);
-- 第二步:移除维修人员的统计权限(如果存在)
DELETE FROM sys_role_menu
WHERE role_id = 5 AND menu_id = 2120;
-- 第三步:确保超级管理员有统计权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) VALUES
(1, 2120); -- admin角色
-- 第四步:确保物业维修管理员有统计权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) VALUES
(6, 2120); -- property_maintenance_admin角色
-- 第五步:验证权限分配结果
SELECT '=== 统计权限分配验证 ===' as section;
-- 检查统计权限项是否存在
SELECT
menu_id, menu_name, perms, menu_type, status
FROM sys_menu
WHERE menu_id = 2120;
-- 检查哪些角色有统计权限
SELECT
'当前拥有统计权限的角色' as info_type,
rm.role_id,
r.role_name,
r.role_key,
r.status as role_status
FROM sys_role_menu rm
JOIN sys_role r ON rm.role_id = r.role_id
WHERE rm.menu_id = 2120
ORDER BY rm.role_id;
-- 检查维修人员是否还有统计权限(应该为空)
SELECT
'维修人员统计权限检查' as info_type,
CASE
WHEN COUNT(*) = 0 THEN '✓ 正确:维修人员没有统计权限'
ELSE '✗ 错误:维修人员仍有统计权限'
END as result
FROM sys_role_menu
WHERE role_id = 5 AND menu_id = 2120;
-- 检查物业维修管理员是否有统计权限
SELECT
'物业维修管理员统计权限检查' as info_type,
CASE
WHEN COUNT(*) > 0 THEN '✓ 正确:物业维修管理员有统计权限'
ELSE '✗ 错误:物业维修管理员缺少统计权限'
END as result
FROM sys_role_menu
WHERE role_id = 6 AND menu_id = 2120;
-- 第六步:权限分配总结
SELECT '=== 权限分配总结 ===' as section;
SELECT
'统计权限分配已修复' as status,
'只有超级管理员和物业维修管理员可以查看统计卡片' as permission_rule,
'维修人员和其他角色无法查看统计功能' as restriction;
-- 第七步:测试指导
SELECT '=== 测试指导 ===' as section;
SELECT
'使用以下账号测试权限是否正确' as test_guide,
'admin/admin123 - 应该能看到统计卡片' as test_case_1,
'property_admin_test/admin123 - 应该能看到统计卡片' as test_case_2,
'maintenance_test/admin123 - 不应该看到统计卡片' as test_case_3,
'前端会根据用户角色自动隐藏/显示统计区域' as frontend_behavior;
-- 第八步:完成提示
SELECT
'统计权限修复完成' as completion_status,
'需要重启前端和后端服务以确保权限生效' as next_step,
'运行 test/statistics-permission-test.js 验证权限配置' as verification;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 953 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Loaded 100 of 751 files, more files were not shown because too many files have changed in this diff. Show more