wuzhen update
This commit is contained in:
1 parent
edaf8dbacf
commit
48da578546
86 files changed
+2101
-1849
No files matched your search
@@ -33,6 +33,7 @@ web/src/api/cw/
|
||||
## 🚀 核心功能
|
||||
|
||||
### 1. 项目审批人配置管理 (`projectAuditor/`)
|
||||
|
||||
- **审批人池管理**: 从系统用户中选择并配置审批人员
|
||||
- **批量添加**: 通过部门树选择用户,支持批量添加审批人
|
||||
- **跨部门选择**: 支持在不同部门间选择用户,选择状态会自动保持
|
||||
@@ -40,23 +41,27 @@ web/src/api/cw/
|
||||
- **权限控制**: 基于RBAC的细粒度权限控制
|
||||
|
||||
### 2. 项目审批流程管理 (`projectApproval/`)
|
||||
|
||||
- **流程监控**: 查看所有审批流程状态和进度(待实现)
|
||||
- **流程详情**: 查看审批记录和时间轴展示(待实现)
|
||||
- **流程终止**: 管理员可终止进行中的审批流程(待实现)
|
||||
|
||||
### 3. 我的待审批 (`projectApproval/pending.vue`)
|
||||
|
||||
- **待审批列表**: 显示当前用户需要审批的项目
|
||||
- **项目详情**: 查看项目完整信息和审批历史
|
||||
- **审批操作**: 支持通过/拒绝,必填审批意见
|
||||
- **批量审批**: 支持批量通过或拒绝多个项目(待实现)
|
||||
|
||||
### 4. 审批流程选择组件 (`components/ApprovalProcessSelector.vue`)
|
||||
|
||||
- **自定义流程配置**: 支持用户从审批人池中选择审核人
|
||||
- **可视化流程预览**: 直观展示审批顺序和流程走向
|
||||
- **审核人管理**: 支持添加、删除、排序审核人
|
||||
- **流程验证**: 验证审批流程的合理性和完整性
|
||||
|
||||
### 5. 项目申报集成 (`application/form.vue` - 已修改)
|
||||
|
||||
- **流程选择对话框**: 在提交时弹出审批流程选择界面
|
||||
- **数据保存**: 先保存申报数据,再配置审批流程
|
||||
- **流程启动**: 自动启动配置的审批流程
|
||||
@@ -65,6 +70,7 @@ web/src/api/cw/
|
||||
## 📋 实现特色
|
||||
|
||||
### 🔧 技术特色
|
||||
|
||||
1. **Vue 3.5.13 Composition API**: 使用最新的组合式API,代码结构清晰
|
||||
2. **TypeScript支持**: 完整的类型定义,提供良好的开发体验
|
||||
3. **Element Plus UI**: 统一的UI组件库,符合后勤管理模块设计规范
|
||||
@@ -72,6 +78,7 @@ web/src/api/cw/
|
||||
5. **组件化开发**: 高度复用的组件设计,易于维护和扩展
|
||||
|
||||
### 🎨 用户体验特色
|
||||
|
||||
1. **跨部门选择**: 审批人选择支持在多个部门间切换,保持选择状态
|
||||
2. **可视化流程**: 审批流程以图形化方式展示,直观易懂
|
||||
3. **实时验证**: 表单验证和流程验证,及时提供反馈
|
||||
@@ -79,6 +86,7 @@ web/src/api/cw/
|
||||
5. **状态恢复**: 切换部门后自动恢复之前的选择状态
|
||||
|
||||
### 🚀 性能特色
|
||||
|
||||
1. **按需加载**: 组件和API按需加载,优化性能
|
||||
2. **智能缓存**: 合理的数据缓存策略,减少重复请求
|
||||
3. **批量操作**: 支持批量添加、删除等操作,提升效率
|
||||
@@ -87,16 +95,19 @@ web/src/api/cw/
|
||||
## 🔄 核心流程
|
||||
|
||||
### 审批人配置流程
|
||||
|
||||
```
|
||||
管理员登录 → 进入审批人配置 → 选择部门 → 批量选择用户 → 确认添加 → 配置完成
|
||||
```
|
||||
|
||||
### 项目申报审批流程
|
||||
|
||||
```
|
||||
填写申报信息 → 点击提交 → 选择审批流程 → 配置审核人 → 确认提交 → 流程启动
|
||||
```
|
||||
|
||||
### 审批执行流程
|
||||
|
||||
```
|
||||
审核人收到通知 → 查看待审批列表 → 查看项目详情 → 执行审批操作 → 填写意见 → 提交审批
|
||||
```
|
||||
@@ -104,16 +115,19 @@ web/src/api/cw/
|
||||
## 🛠️ 后端支持
|
||||
|
||||
### 数据库设计
|
||||
|
||||
1. **cw_project_auditor**: 项目审核人配置表
|
||||
2. **cw_project_approval_process**: 项目审批流程表
|
||||
3. **cw_approval_record**: 审批记录表(已扩展支持自定义流程)
|
||||
|
||||
### API接口
|
||||
|
||||
1. **审核人配置**: 16个标准API接口(CRUD + 业务功能 + 辅助功能)
|
||||
2. **审批流程**: 10个核心API接口(流程管理 + 审批执行)
|
||||
3. **权限控制**: 基于Sa-Token的权限管理
|
||||
|
||||
### 数据安全
|
||||
|
||||
1. **多租户隔离**: 严格的数据隔离机制
|
||||
2. **权限控制**: 细粒度的功能权限控制
|
||||
3. **操作日志**: 完整的操作记录和审计
|
||||
@@ -121,18 +135,21 @@ web/src/api/cw/
|
||||
## 📊 技术规范
|
||||
|
||||
### 前端规范
|
||||
|
||||
- **组件命名**: PascalCase,清晰的语义化命名
|
||||
- **文件组织**: 按功能模块组织,层次分明
|
||||
- **样式管理**: Scoped CSS,避免样式冲突
|
||||
- **状态管理**: Reactive响应式状态,简洁高效
|
||||
|
||||
### 后端规范
|
||||
|
||||
- **接口设计**: RESTful风格,统一响应格式
|
||||
- **数据验证**: Bean Validation参数校验
|
||||
- **异常处理**: 统一的异常处理机制
|
||||
- **事务管理**: 合理的事务边界控制
|
||||
|
||||
### 代码质量
|
||||
|
||||
- **TypeScript**: 完整的类型检查
|
||||
- **ESLint**: 代码风格检查
|
||||
- **Prettier**: 代码格式化
|
||||
@@ -141,18 +158,21 @@ web/src/api/cw/
|
||||
## 🎯 后续优化
|
||||
|
||||
### 功能扩展
|
||||
|
||||
1. **审批流程管理页面**: 完整的流程监控和管理界面
|
||||
2. **批量审批功能**: 支持批量处理多个项目
|
||||
3. **审批历史查询**: 完整的审批记录查询功能
|
||||
4. **移动端支持**: 基于uni-app的移动端审批应用
|
||||
|
||||
### 性能优化
|
||||
|
||||
1. **虚拟滚动**: 大数据量列表的性能优化
|
||||
2. **缓存策略**: 更智能的数据缓存机制
|
||||
3. **懒加载**: 按需加载组件和数据
|
||||
4. **CDN集成**: 静态资源CDN加速
|
||||
|
||||
### 用户体验
|
||||
|
||||
1. **快捷操作**: 键盘快捷键支持
|
||||
2. **拖拽排序**: 审核人拖拽排序功能
|
||||
3. **消息通知**: 实时消息推送
|
||||
@@ -161,18 +181,21 @@ web/src/api/cw/
|
||||
## 🚀 部署说明
|
||||
|
||||
### 开发环境
|
||||
|
||||
1. 确保Node.js 20.15.x+,npm 10.7.x+
|
||||
2. 安装依赖:`npm install`
|
||||
3. 启动开发服务器:`npm run dev`
|
||||
4. 访问:http://localhost
|
||||
|
||||
### 生产环境
|
||||
|
||||
1. 构建项目:`npm run build`
|
||||
2. 部署到Nginx或其他Web服务器
|
||||
3. 配置反向代理到后端API
|
||||
4. 确保数据库脚本已执行
|
||||
|
||||
### 数据库部署
|
||||
|
||||
1. 执行数据库脚本:`docs/数据库修复脚本.sql`
|
||||
2. 配置权限菜单和角色权限
|
||||
3. 创建测试用户和审核人数据
|
||||
@@ -187,4 +210,4 @@ web/src/api/cw/
|
||||
|
||||
**📝 维护日期**: 2025-01-03
|
||||
**📁 文件**: 完整的前后端实现 + 数据库脚本 + API文档
|
||||
**🔄 状态**: 核心功能完成,待进一步测试和优化
|
||||
**🔄 状态**: 核心功能完成,待进一步测试和优化
|
||||
@@ -1158,7 +1158,7 @@ const confirmApprovalSubmit = async () => {
|
||||
// 构建审批流程数据
|
||||
const approvalData = {
|
||||
applicationId: form.id,
|
||||
applicationNo: form.applicationNo, // 新增:申报编号
|
||||
applicationNo: form.applicationNo, // 新增:申报编号
|
||||
auditorIds: processConfig.auditorIds,
|
||||
processName: processConfig.processName || `${form.projectName}审批流程`,
|
||||
submitterId: userStore.userId,
|
||||
|
||||
@@ -122,10 +122,14 @@
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['cw:application:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:application:remove']">删除</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:application:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Upload" @click="handleSubmit" :disabled="multiple" v-hasPermi="['cw:application:submit']">提交审批</el-button>
|
||||
<el-button type="info" plain icon="Upload" @click="handleSubmit" :disabled="multiple" v-hasPermi="['cw:application:submit']"
|
||||
>提交审批</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['cw:application:export']">导出</el-button>
|
||||
@@ -135,7 +139,15 @@
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="applicationList" @selection-change="handleSelectionChange" style="width: 100%" :scroll-x="true" class="scroll-table">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="applicationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
:scroll-x="true"
|
||||
class="scroll-table"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" fixed="left" />
|
||||
<el-table-column label="申报编号" align="center" prop="applicationNo" min-width="140" />
|
||||
<el-table-column label="项目名称" align="center" prop="projectName" min-width="180" show-overflow-tooltip />
|
||||
@@ -169,31 +181,13 @@
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['cw:application:edit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="提交审批" placement="top" v-if="['draft', 'returned'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
icon="Upload"
|
||||
@click="handleSubmit(scope.row)"
|
||||
v-hasPermi="['cw:application:submit']"
|
||||
/>
|
||||
<el-button link type="warning" icon="Upload" @click="handleSubmit(scope.row)" v-hasPermi="['cw:application:submit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="撤回" placement="top" v-if="scope.row.approvalStatus === 'submitted'">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="RefreshLeft"
|
||||
@click="handleWithdraw(scope.row)"
|
||||
v-hasPermi="['cw:application:withdraw']"
|
||||
/>
|
||||
<el-button link type="info" icon="RefreshLeft" @click="handleWithdraw(scope.row)" v-hasPermi="['cw:application:withdraw']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled', 'returned'].includes(scope.row.approvalStatus)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['cw:application:remove']"
|
||||
/>
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['cw:application:remove']" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
@@ -234,6 +228,20 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批流程选择对话框 -->
|
||||
<el-dialog title="选择审批流程" v-model="approvalProcessDialog.visible" width="800px" append-to-body :close-on-click-modal="false">
|
||||
<ApprovalProcessSelector
|
||||
ref="approvalProcessSelectorRef"
|
||||
v-model="approvalProcessDialog.selectedAuditors"
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelSubmitApproval">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmSubmitApproval">确认提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -247,8 +255,11 @@ import {
|
||||
submitApplication,
|
||||
getApplicationStatistics
|
||||
} from '@/api/cw/projectApplication';
|
||||
import { startCustomApprovalProcess, type CwCustomApprovalRequest } from '@/api/cw/projectApproval';
|
||||
import { getInfo } from '@/api/login';
|
||||
import ApplicationForm from './form.vue';
|
||||
import ApplicationSummary from './summary.vue';
|
||||
import ApprovalProcessSelector from '../projectApproval/components/ApprovalProcessSelector.vue';
|
||||
import Pagination from '@/components/Pagination/index.vue';
|
||||
|
||||
export default {
|
||||
@@ -256,6 +267,7 @@ export default {
|
||||
components: {
|
||||
ApplicationForm,
|
||||
ApplicationSummary,
|
||||
ApprovalProcessSelector,
|
||||
Pagination
|
||||
},
|
||||
data() {
|
||||
@@ -300,7 +312,14 @@ export default {
|
||||
totalCount: 0,
|
||||
approvedCount: 0,
|
||||
pendingCount: 0,
|
||||
draftCount: 0
|
||||
draftCount: 0,
|
||||
// 审批流程选择对话框
|
||||
approvalProcessDialog: {
|
||||
visible: false,
|
||||
applicationId: null,
|
||||
applicationNo: null,
|
||||
selectedAuditors: []
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -476,21 +495,92 @@ export default {
|
||||
},
|
||||
/** 提交审批 */
|
||||
handleSubmit(row) {
|
||||
const applications = row?.applicationNo
|
||||
? [row.applicationNo]
|
||||
: this.applicationList.filter((item) => this.ids.includes(item.id)).map((item) => item.applicationNo);
|
||||
if (row) {
|
||||
// 单个提交
|
||||
this.approvalProcessDialog.applicationId = row.id;
|
||||
this.approvalProcessDialog.applicationNo = row.applicationNo;
|
||||
this.approvalProcessDialog.visible = true;
|
||||
this.approvalProcessDialog.selectedAuditors = [];
|
||||
} else {
|
||||
// 批量提交 - 暂不支持
|
||||
this.$modal.msgWarning('批量提交审批功能暂未开放,请单个提交');
|
||||
}
|
||||
},
|
||||
/** 确认提交审批 */
|
||||
async confirmSubmitApproval() {
|
||||
try {
|
||||
const processSelector = this.$refs.approvalProcessSelectorRef;
|
||||
if (!processSelector) {
|
||||
this.$modal.msgError('审批流程选择组件未加载');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$modal
|
||||
.confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?')
|
||||
.then(() => {
|
||||
return submitApplication(row?.id || this.ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.getList();
|
||||
this.getStatistics(); // 提交后更新统计
|
||||
this.$modal.msgSuccess('提交成功');
|
||||
})
|
||||
.catch(() => {});
|
||||
// 验证审批流程
|
||||
const validation = processSelector.validateProcess();
|
||||
if (!validation.valid) {
|
||||
this.$modal.msgError(validation.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
let userInfo = null;
|
||||
try {
|
||||
const userResponse = await getInfo();
|
||||
if (userResponse.code === 200 && userResponse.data && userResponse.data.user) {
|
||||
userInfo = userResponse.data.user;
|
||||
}
|
||||
} catch (userError) {
|
||||
console.error('获取用户信息失败:', userError);
|
||||
this.$modal.msgError('获取用户信息失败,请重新登录');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userInfo || !userInfo.userId) {
|
||||
this.$modal.msgError('用户信息不完整,请重新登录');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取审批流程配置
|
||||
const processConfig = processSelector.getProcessConfig();
|
||||
|
||||
// 构建请求数据
|
||||
const approvalData: CwCustomApprovalRequest = {
|
||||
applicationId: this.approvalProcessDialog.applicationId,
|
||||
applicationNo: this.approvalProcessDialog.applicationNo,
|
||||
processName: processConfig.processName,
|
||||
auditorIds: processConfig.auditorIds,
|
||||
submitterId: userInfo.userId,
|
||||
submitterName: userInfo.nickName || userInfo.userName
|
||||
};
|
||||
|
||||
console.log('启动审批流程数据:', approvalData);
|
||||
|
||||
// 启动审批流程
|
||||
const result = await startCustomApprovalProcess(approvalData);
|
||||
console.log('启动审批流程结果:', result);
|
||||
|
||||
// 关闭对话框
|
||||
this.approvalProcessDialog.visible = false;
|
||||
this.approvalProcessDialog.applicationId = null;
|
||||
this.approvalProcessDialog.applicationNo = null;
|
||||
this.approvalProcessDialog.selectedAuditors = [];
|
||||
|
||||
// 刷新列表和统计
|
||||
this.getList();
|
||||
this.getStatistics();
|
||||
|
||||
this.$modal.msgSuccess('审批流程启动成功');
|
||||
} catch (error) {
|
||||
console.error('启动审批流程失败:', error);
|
||||
this.$modal.msgError('启动审批流程失败:' + (error.message || '未知错误'));
|
||||
}
|
||||
},
|
||||
/** 取消提交审批 */
|
||||
cancelSubmitApproval() {
|
||||
this.approvalProcessDialog.visible = false;
|
||||
this.approvalProcessDialog.applicationId = null;
|
||||
this.approvalProcessDialog.applicationNo = null;
|
||||
this.approvalProcessDialog.selectedAuditors = [];
|
||||
},
|
||||
/** 撤回申请 */
|
||||
handleWithdraw(row) {
|
||||
@@ -642,7 +732,9 @@ export default {
|
||||
/* 卡片样式 */
|
||||
:deep(.el-card) {
|
||||
border: 1px solid #e4e7ed;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.12),
|
||||
0 0 6px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 表格卡片头部样式 */
|
||||
|
||||
@@ -98,7 +98,15 @@
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @update:page="queryParams.pageNum = $event" @update:limit="queryParams.pageSize = $event" @pagination="getList" />
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="queryParams.pageNum = $event"
|
||||
@update:limit="queryParams.pageSize = $event"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -377,7 +385,9 @@ export default {
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.12),
|
||||
0 0 6px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
|
||||
@@ -157,8 +157,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="审批决定" prop="approvalResult" required>
|
||||
<el-radio-group v-model="approvalForm.approvalResult">
|
||||
<el-radio label="approve">通过</el-radio>
|
||||
<el-radio label="reject">退回申请人</el-radio>
|
||||
<el-radio value="approve">通过</el-radio>
|
||||
<el-radio value="reject">退回申请人</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="approvalComment">
|
||||
|
||||
@@ -7,23 +7,27 @@
|
||||
## 主要功能
|
||||
|
||||
### 1. 审批流程管理
|
||||
|
||||
- 查询审批流程列表(支持分页)
|
||||
- 按申报编号、流程状态、提交人等条件筛选
|
||||
- 显示审批进度和流程状态
|
||||
- 支持终止正在进行的审批流程
|
||||
|
||||
### 2. 待我审批
|
||||
|
||||
- 查看分配给当前用户的待审批任务
|
||||
- 执行审批操作(通过/打回)
|
||||
- 填写审批意见
|
||||
- 查看申报详情和审批记录
|
||||
|
||||
### 3. 我的已审批
|
||||
|
||||
- 查看当前用户已完成的审批记录
|
||||
- 显示审批动作和意见
|
||||
- 统计审批效率和处理时长
|
||||
|
||||
### 4. 审批记录跟踪
|
||||
|
||||
- 完整的审批时间线展示
|
||||
- 每个审批节点的详细信息
|
||||
- 审批意见和处理结果记录
|
||||
@@ -39,21 +43,25 @@ web/src/views/cw/projectApproval/
|
||||
## 功能特性
|
||||
|
||||
### 1. 多标签页设计
|
||||
|
||||
- **审批流程管理**: 查看所有审批流程的整体情况
|
||||
- **待我审批**: 专注于当前用户需要处理的审批任务
|
||||
- **我的已审批**: 查看历史审批记录和统计
|
||||
|
||||
### 2. 统计卡片
|
||||
|
||||
- 总流程数、等待开始、进行中、已完成等状态统计
|
||||
- 平均处理时长、待我审批数量等关键指标
|
||||
- 本月完成数量、已终止流程等趋势数据
|
||||
|
||||
### 3. 审批操作
|
||||
|
||||
- 支持批量审批和单个审批
|
||||
- 必填审批意见,确保审批质量
|
||||
- 实时更新审批状态和流程进度
|
||||
|
||||
### 4. 流程可视化
|
||||
|
||||
- 进度条显示审批进度
|
||||
- 时间线展示审批历史
|
||||
- 状态标签清晰标识各阶段
|
||||
@@ -61,6 +69,7 @@ web/src/views/cw/projectApproval/
|
||||
## API接口
|
||||
|
||||
使用的主要API接口:
|
||||
|
||||
- `listProjectApproval` - 查询审批流程列表
|
||||
- `listPendingApprovals` - 查询待我审批列表
|
||||
- `listProcessedApprovals` - 查询我的已审批列表
|
||||
@@ -72,6 +81,7 @@ web/src/views/cw/projectApproval/
|
||||
## 权限控制
|
||||
|
||||
页面功能对应的权限标识:
|
||||
|
||||
- `cw:projectApproval:list` - 查询权限
|
||||
- `cw:projectApproval:add` - 启动流程权限
|
||||
- `cw:projectApproval:approve` - 审批权限
|
||||
@@ -81,18 +91,21 @@ web/src/views/cw/projectApproval/
|
||||
## 状态说明
|
||||
|
||||
### 流程状态
|
||||
|
||||
- `waiting` - 等待开始
|
||||
- `running` - 进行中
|
||||
- `running` - 进行中
|
||||
- `completed` - 已完成
|
||||
- `terminated` - 已终止
|
||||
|
||||
### 节点状态
|
||||
|
||||
- `waiting` - 等待审批
|
||||
- `approved` - 已通过
|
||||
- `rejected` - 已拒绝
|
||||
- `pending` - 待处理
|
||||
|
||||
### 审批动作
|
||||
|
||||
- `approve` - 审批通过
|
||||
- `reject` - 打回申请
|
||||
|
||||
@@ -114,6 +127,7 @@ web/src/views/cw/projectApproval/
|
||||
## 使用说明
|
||||
|
||||
### 审批人员操作流程
|
||||
|
||||
1. 登录系统,进入项目审批管理页面
|
||||
2. 点击"待我审批"标签页查看待处理任务
|
||||
3. 点击具体记录查看申报详情
|
||||
@@ -121,6 +135,7 @@ web/src/views/cw/projectApproval/
|
||||
5. 提交审批结果,系统自动推进流程
|
||||
|
||||
### 管理员操作流程
|
||||
|
||||
1. 在"审批流程管理"标签页查看所有流程状态
|
||||
2. 可以终止异常或不需要的审批流程
|
||||
3. 通过统计卡片监控审批效率和积压情况
|
||||
@@ -131,4 +146,4 @@ web/src/views/cw/projectApproval/
|
||||
1. 审批意见为必填项,且至少5个字符
|
||||
2. 审批操作不可撤销,请谨慎操作
|
||||
3. 终止流程会影响项目申报状态,需谨慎使用
|
||||
4. 导出功能需要相应权限,请联系管理员配置
|
||||
4. 导出功能需要相应权限,请联系管理员配置
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="approval-process-selector">
|
||||
<el-form-item label="审批流程" required>
|
||||
<el-radio-group v-model="processType" @change="handleProcessTypeChange">
|
||||
<el-radio label="custom">自定义审批流程</el-radio>
|
||||
<el-radio value="custom">自定义审批流程</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
@@ -307,11 +307,11 @@
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="120px">
|
||||
<el-form-item label="审批操作" prop="approveAction" required>
|
||||
<el-radio-group v-model="approvalForm.approveAction">
|
||||
<el-radio label="approve">
|
||||
<el-radio value="approve">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
审批通过
|
||||
</el-radio>
|
||||
<el-radio label="reject">
|
||||
<el-radio value="reject">
|
||||
<el-icon><CircleClose /></el-icon>
|
||||
打回申请
|
||||
</el-radio>
|
||||
|
||||
@@ -21,11 +21,7 @@
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="pendingList"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table v-loading="loading" :data="pendingList" @row-dblclick="handleView">
|
||||
<el-table-column label="序号" type="index" width="80" align="center" />
|
||||
<el-table-column label="申报编号" prop="applicationNo" width="160" />
|
||||
<el-table-column label="项目名称" prop="projectName" show-overflow-tooltip />
|
||||
@@ -64,13 +60,7 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 审批操作对话框 -->
|
||||
<el-dialog
|
||||
title="审批操作"
|
||||
v-model="approvalDialog.visible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
>
|
||||
<el-dialog title="审批操作" v-model="approvalDialog.visible" width="600px" append-to-body destroy-on-close>
|
||||
<div class="approval-content">
|
||||
<!-- 项目信息 -->
|
||||
<div class="project-info">
|
||||
@@ -87,25 +77,18 @@
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
|
||||
<el-form-item label="审批操作" prop="action">
|
||||
<el-radio-group v-model="approvalForm.action">
|
||||
<el-radio label="approve">
|
||||
<el-radio value="approve">
|
||||
<el-icon><Check /></el-icon>
|
||||
审批通过
|
||||
</el-radio>
|
||||
<el-radio label="reject">
|
||||
<el-radio value="reject">
|
||||
<el-icon><Close /></el-icon>
|
||||
审批拒绝
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="opinion">
|
||||
<el-input
|
||||
v-model="approvalForm.opinion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="approvalForm.opinion" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -113,27 +96,15 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approvalDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.submitting">
|
||||
确认审批
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.submitting"> 确认审批 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情查看对话框 -->
|
||||
<el-dialog
|
||||
title="项目申报详情"
|
||||
v-model="detailDialog.visible"
|
||||
width="1200px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
top="20px"
|
||||
>
|
||||
<el-dialog title="项目申报详情" v-model="detailDialog.visible" width="1200px" append-to-body destroy-on-close top="20px">
|
||||
<div v-loading="detailDialog.loading">
|
||||
<ApplicationSummary
|
||||
v-if="detailDialog.visible && currentApplicationId"
|
||||
:applicationId="currentApplicationId"
|
||||
/>
|
||||
<ApplicationSummary v-if="detailDialog.visible && currentApplicationId" :applicationId="currentApplicationId" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
@@ -283,7 +254,6 @@ const submitApproval = async () => {
|
||||
|
||||
// 重新加载列表
|
||||
getList();
|
||||
|
||||
} catch (error) {
|
||||
ElMessage.error('审批操作失败');
|
||||
} finally {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
# 项目审批人批量添加功能Bug修复报告
|
||||
|
||||
## 🐛 问题描述
|
||||
|
||||
用户反馈:批量添加审批人时,选择用户后点击提交,页面提示添加成功,但后台数据库中没有新增数据。
|
||||
|
||||
## 🔍 问题分析
|
||||
|
||||
### 根本原因
|
||||
|
||||
1. **前端使用模拟API函数**:`index.vue` 中第354-378行定义了模拟的API函数,而不是真实的API调用
|
||||
2. **缺少正确的API导入**:没有导入真实的 `batchAddProjectAuditor` 等API函数
|
||||
3. **数据格式不匹配**:前端传递的数据格式与后端期望格式存在差异
|
||||
|
||||
### 问题代码
|
||||
|
||||
```javascript
|
||||
// 错误:使用模拟函数
|
||||
const batchAddProjectAuditor = (data: any) => {
|
||||
@@ -21,18 +24,20 @@ const batchAddProjectAuditor = (data: any) => {
|
||||
## 🔧 修复方案
|
||||
|
||||
### 1. 导入真实API函数
|
||||
|
||||
```javascript
|
||||
import {
|
||||
listProjectAuditor,
|
||||
getProjectAuditor,
|
||||
updateProjectAuditor,
|
||||
delProjectAuditor,
|
||||
batchAddProjectAuditor
|
||||
import {
|
||||
listProjectAuditor,
|
||||
getProjectAuditor,
|
||||
updateProjectAuditor,
|
||||
delProjectAuditor,
|
||||
batchAddProjectAuditor
|
||||
} from '@/api/cw/projectAuditor'
|
||||
import type { CwProjectAuditorVO, CwProjectAuditorForm, CwProjectAuditorQuery, CwProjectAuditorBatchBo } from '@/api/cw/types'
|
||||
```
|
||||
|
||||
### 2. 修复数据类型定义
|
||||
|
||||
```typescript
|
||||
// 修正数据类型
|
||||
const auditorList = ref<CwProjectAuditorVO[]>([])
|
||||
@@ -43,6 +48,7 @@ const editDialog = reactive({
|
||||
```
|
||||
|
||||
### 3. 修复批量添加数据格式
|
||||
|
||||
```javascript
|
||||
const confirmBatchAdd = async () => {
|
||||
const userIds = batchDialog.selectedUsers.map((user) => user.userId as number)
|
||||
@@ -50,23 +56,25 @@ const confirmBatchAdd = async () => {
|
||||
userIds: userIds,
|
||||
remark: '批量添加'
|
||||
}
|
||||
|
||||
|
||||
const response = await batchAddProjectAuditor(data)
|
||||
// ... 处理响应
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 修复后端数据解析
|
||||
|
||||
```javascript
|
||||
const response = await listProjectAuditor(queryParams.value)
|
||||
const response = await listProjectAuditor(queryParams.value);
|
||||
// 后端返回的是TableDataInfo格式
|
||||
auditorList.value = response.data?.rows || []
|
||||
total.value = response.data?.total || 0
|
||||
auditorList.value = response.data?.rows || [];
|
||||
total.value = response.data?.total || 0;
|
||||
```
|
||||
|
||||
## ✅ 修复内容清单
|
||||
|
||||
### 前端修复
|
||||
|
||||
- [x] 移除模拟API函数,导入真实API
|
||||
- [x] 修复TypeScript类型定义
|
||||
- [x] 修复批量添加数据格式
|
||||
@@ -75,6 +83,7 @@ total.value = response.data?.total || 0
|
||||
- [x] 修复按钮事件类型问题
|
||||
|
||||
### 后端验证
|
||||
|
||||
- [x] 确认后端API接口正常工作
|
||||
- [x] 确认数据库表结构正确
|
||||
- [x] 确认权限控制配置
|
||||
@@ -82,13 +91,16 @@ total.value = response.data?.total || 0
|
||||
## 🧪 测试验证
|
||||
|
||||
### 测试用例
|
||||
|
||||
1. **批量添加功能测试**
|
||||
|
||||
- 选择部门
|
||||
- 选择多个用户
|
||||
- 点击确认添加
|
||||
- 验证数据库是否成功插入数据
|
||||
|
||||
2. **查询列表功能测试**
|
||||
|
||||
- 验证API调用正常
|
||||
- 验证数据格式解析正确
|
||||
- 验证分页功能
|
||||
@@ -99,11 +111,13 @@ total.value = response.data?.total || 0
|
||||
- 测试权限不足情况
|
||||
|
||||
### 测试脚本
|
||||
|
||||
提供了 `test-api.js` 文件用于功能验证测试。
|
||||
|
||||
## 📋 技术规范遵循
|
||||
|
||||
### 符合项目规范
|
||||
|
||||
- ✅ **前端开发规范**:符合Vue 3.5.13 + TypeScript + Element Plus架构
|
||||
- ✅ **API接口规范**:使用统一的request工具和错误处理
|
||||
- ✅ **UI设计规范**:继承后勤管理模块的样式风格和颜色主题
|
||||
@@ -111,6 +125,7 @@ total.value = response.data?.total || 0
|
||||
- ✅ **权限控制**:使用@SaCheckPermission注解控制接口权限
|
||||
|
||||
### 代码质量
|
||||
|
||||
- ✅ **类型安全**:完整的TypeScript类型定义
|
||||
- ✅ **错误处理**:完善的异常捕获和用户提示
|
||||
- ✅ **用户体验**:优化的加载状态和反馈信息
|
||||
@@ -119,12 +134,14 @@ total.value = response.data?.total || 0
|
||||
## 🚀 部署说明
|
||||
|
||||
### 验证步骤
|
||||
|
||||
1. 确保后端服务正常运行
|
||||
2. 检查数据库表 `cw_project_auditor` 是否存在
|
||||
3. 验证用户权限配置 `cw:projectAuditor:add`
|
||||
4. 测试前端页面功能
|
||||
|
||||
### 预期结果
|
||||
|
||||
- 批量添加功能正常工作
|
||||
- 数据成功写入数据库
|
||||
- 页面显示准确的成功/失败提示
|
||||
@@ -132,16 +149,17 @@ total.value = response.data?.total || 0
|
||||
|
||||
## 📝 修复日志
|
||||
|
||||
| 时间 | 修复内容 | 影响范围 |
|
||||
|------|----------|----------|
|
||||
| 2025-01-03 | 修复API调用问题 | 批量添加功能 |
|
||||
| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 |
|
||||
| 时间 | 修复内容 | 影响范围 |
|
||||
| ---------- | ---------------- | ------------------ |
|
||||
| 2025-01-03 | 修复API调用问题 | 批量添加功能 |
|
||||
| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 |
|
||||
| 2025-01-03 | 修复类型定义问题 | TypeScript类型安全 |
|
||||
| 2025-01-03 | 修复响应解析问题 | 列表查询功能 |
|
||||
| 2025-01-03 | 修复响应解析问题 | 列表查询功能 |
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
本次修复解决了批量添加审批人功能的根本问题,确保:
|
||||
|
||||
1. **功能正常**:批量添加能够真实地向后端发送请求并保存数据
|
||||
2. **类型安全**:完整的TypeScript类型定义和检查
|
||||
3. **用户体验**:准确的成功/失败提示和数据刷新
|
||||
|
||||
@@ -7,18 +7,21 @@
|
||||
## 主要功能
|
||||
|
||||
### 1. 审批人列表管理
|
||||
|
||||
- 查询审批人列表(支持分页)
|
||||
- 按用户姓名、部门、状态等条件筛选
|
||||
- 显示审批人基本信息(姓名、部门、联系方式等)
|
||||
- 支持审批人状态管理(正常/停用)
|
||||
|
||||
### 2. 批量添加审批人
|
||||
|
||||
- 通过部门树选择部门
|
||||
- 展示部门下的可用用户列表
|
||||
- 支持多选用户批量添加为审批人
|
||||
- 自动过滤已存在的审批人
|
||||
|
||||
### 3. 审批人信息维护
|
||||
|
||||
- 修改审批人联系信息
|
||||
- 更新审批人状态
|
||||
- 添加备注信息
|
||||
@@ -35,6 +38,7 @@ web/src/views/cw/projectAuditor/
|
||||
## API接口
|
||||
|
||||
使用的主要API接口:
|
||||
|
||||
- `listProjectAuditor` - 查询审批人列表
|
||||
- `getProjectAuditor` - 获取审批人详情
|
||||
- `updateProjectAuditor` - 更新审批人信息
|
||||
@@ -45,6 +49,7 @@ web/src/views/cw/projectAuditor/
|
||||
## 权限控制
|
||||
|
||||
页面功能对应的权限标识:
|
||||
|
||||
- `cw:projectAuditor:query` - 查询权限
|
||||
- `cw:projectAuditor:add` - 新增权限
|
||||
- `cw:projectAuditor:edit` - 编辑权限
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>测试审核人列表API</span>
|
||||
<el-button @click="testAuditorList" type="primary" style="float: right;">测试API</el-button>
|
||||
<el-button @click="testAuditorList" type="primary" style="float: right">测试API</el-button>
|
||||
</template>
|
||||
|
||||
<div v-if="loading">加载中...</div>
|
||||
@@ -32,10 +32,10 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card style="margin-top: 20px;">
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<span>测试部门数据API</span>
|
||||
<el-button @click="testDeptList" type="success" style="float: right;">测试部门API</el-button>
|
||||
<el-button @click="testDeptList" type="success" style="float: right">测试部门API</el-button>
|
||||
</template>
|
||||
|
||||
<div v-if="deptLoading">加载中...</div>
|
||||
@@ -101,7 +101,6 @@ const testAuditorList = async () => {
|
||||
}
|
||||
|
||||
console.log('处理后的审核人列表:', auditorList.value);
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试审核人API失败:', error);
|
||||
} finally {
|
||||
@@ -126,7 +125,6 @@ const testDeptList = async () => {
|
||||
|
||||
deptList.value = Array.isArray(deptData) ? deptData : [];
|
||||
console.log('处理后的部门列表:', deptList.value);
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试部门API失败:', error);
|
||||
} finally {
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
@@ -106,7 +105,7 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['cw:projectAuditor:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :show-search="showSearch" @update:show-search="(val) => showSearch = val" @queryTable="getList"></right-toolbar>
|
||||
<right-toolbar :show-search="showSearch" @update:show-search="(val) => (showSearch = val)" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -166,10 +165,22 @@
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['cw:projectAuditor:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="scope.row.status === '1'" content="停用" placement="top">
|
||||
<el-button link type="warning" icon="CircleClose" @click="handleDisable(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
icon="CircleClose"
|
||||
@click="handleDisable(scope.row)"
|
||||
v-hasPermi="['cw:projectAuditor:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else content="启用" placement="top">
|
||||
<el-button link type="success" icon="CircleCheck" @click="handleEnable(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="CircleCheck"
|
||||
@click="handleEnable(scope.row)"
|
||||
v-hasPermi="['cw:projectAuditor:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -181,15 +192,15 @@
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="(val) => queryParams.pageNum = val"
|
||||
@update:limit="(val) => queryParams.pageSize = val"
|
||||
@update:page="(val) => (queryParams.pageNum = val)"
|
||||
@update:limit="(val) => (queryParams.pageSize = val)"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 批量添加审核人对话框 -->
|
||||
<el-dialog title="批量添加审核人" v-model="batchAddDialog.visible" width="80%" append-to-body>
|
||||
<el-dialog title="批量添加审核人" v-model="batchAddDialog.visible" width="1200px" append-to-body>
|
||||
<div class="batch-add-container">
|
||||
<!-- 左侧部门选择 -->
|
||||
<div class="dept-select-section">
|
||||
@@ -221,24 +232,42 @@
|
||||
<el-card shadow="never" class="user-table-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>部门用户列表</span>
|
||||
<el-tag v-if="currentDeptName" type="info" size="small">{{ currentDeptName }}</el-tag>
|
||||
<div class="header-left">
|
||||
<span>部门用户列表</span>
|
||||
<el-tag v-if="currentDeptName" type="info" size="small">{{ currentDeptName }}</el-tag>
|
||||
<span v-if="deptUserList.length > 0" class="user-count">({{ deptUserList.length }}人)</span>
|
||||
</div>
|
||||
<div class="header-right" v-if="deptUserList.length > 0">
|
||||
<el-input v-model="userSearchText" placeholder="搜索用户" size="small" style="width: 180px" clearable @input="handleUserSearch">
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table
|
||||
ref="userTableRef"
|
||||
v-loading="userLoading"
|
||||
:data="deptUserList"
|
||||
:data="filteredUserList"
|
||||
@selection-change="handleUserSelectionChange"
|
||||
max-height="400"
|
||||
style="width: 100%"
|
||||
table-layout="fixed"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="姓名" prop="nickName" width="100" />
|
||||
<el-table-column label="用户名" prop="userName" width="120" />
|
||||
<el-table-column label="部门" prop="deptName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="手机号" prop="phonenumber" width="130" />
|
||||
<el-table-column label="邮箱" prop="email" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="姓名" prop="nickName" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="用户名" prop="userName" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="部门" prop="deptName" width="160" show-overflow-tooltip />
|
||||
<el-table-column label="手机号" prop="phonenumber" width="130" show-overflow-tooltip />
|
||||
<el-table-column label="邮箱" prop="email" width="200" show-overflow-tooltip />
|
||||
<el-table-column label="状态" prop="status" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ row.status === '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
@@ -261,7 +290,7 @@
|
||||
@close="removeSelectedUser(user)"
|
||||
class="selected-user-tag"
|
||||
>
|
||||
{{ user.nickName }}({{ user.userName }})
|
||||
{{ user.nickName }}({{ user.userName }}) - {{ user.deptName || '未知部门' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -271,12 +300,7 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="batchAddDialog.visible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="confirmBatchAdd"
|
||||
:disabled="batchAddDialog.selectedUsers.length === 0"
|
||||
:loading="batchAddLoading"
|
||||
>
|
||||
<el-button type="primary" @click="confirmBatchAdd" :disabled="batchAddDialog.selectedUsers.length === 0" :loading="batchAddLoading">
|
||||
确定添加 ({{ batchAddDialog.selectedUsers.length }})
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -329,8 +353,8 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="editDialog.form.status">
|
||||
<el-radio label="1">正常</el-radio>
|
||||
<el-radio label="0">停用</el-radio>
|
||||
<el-radio value="1">正常</el-radio>
|
||||
<el-radio value="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -339,14 +363,7 @@
|
||||
<el-input v-model="editDialog.form.email" placeholder="请输入邮箱地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注说明" prop="remark">
|
||||
<el-input
|
||||
v-model="editDialog.form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注说明"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="editDialog.form.remark" type="textarea" :rows="3" placeholder="请输入备注说明" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -360,7 +377,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="CwProjectAuditor">
|
||||
import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { ref, reactive, onMounted, nextTick, getCurrentInstance, computed } from 'vue';
|
||||
import { ElMessage, ElMessageBox, ElTable, ElTree, FormInstance } from 'element-plus';
|
||||
import {
|
||||
listProjectAuditor,
|
||||
@@ -369,18 +386,20 @@ import {
|
||||
updateProjectAuditor,
|
||||
delProjectAuditor,
|
||||
batchAddProjectAuditor,
|
||||
// batchAddProjectAuditorWithDetails, // 后端暂不支持此接口
|
||||
listMyConfigAuditors,
|
||||
listUserByDeptId,
|
||||
enableAuditors,
|
||||
disableAuditors,
|
||||
exportProjectAuditor,
|
||||
listUserByDeptId,
|
||||
type CwProjectAuditorVO,
|
||||
type CwProjectAuditorForm,
|
||||
type CwProjectAuditorQuery,
|
||||
type BatchAddRequest,
|
||||
type BatchAddDetailRequest,
|
||||
type DeptUserVO
|
||||
} from '@/api/cw/projectAuditor';
|
||||
import { listDept } from '@/api/system/dept/index';
|
||||
import { deptTreeSelect } from '@/api/system/user/index';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
|
||||
@@ -417,7 +436,6 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
|
||||
// 部门数据
|
||||
const deptOptions = ref([]);
|
||||
const deptTreeData = ref([]);
|
||||
@@ -433,6 +451,20 @@ const batchAddLoading = ref(false);
|
||||
const userLoading = ref(false);
|
||||
const deptUserList = ref<DeptUserVO[]>([]);
|
||||
const currentDeptName = ref('');
|
||||
const userSearchText = ref('');
|
||||
const filteredUserList = computed(() => {
|
||||
if (!userSearchText.value) {
|
||||
return deptUserList.value;
|
||||
}
|
||||
const searchText = userSearchText.value.toLowerCase();
|
||||
return deptUserList.value.filter(
|
||||
(user) =>
|
||||
user.userName?.toLowerCase().includes(searchText) ||
|
||||
user.nickName?.toLowerCase().includes(searchText) ||
|
||||
user.phonenumber?.includes(searchText) ||
|
||||
user.email?.toLowerCase().includes(searchText)
|
||||
);
|
||||
});
|
||||
|
||||
// 详情对话框
|
||||
const detailDialog = reactive({
|
||||
@@ -450,15 +482,9 @@ const editLoading = ref(false);
|
||||
|
||||
// 编辑表单验证规则
|
||||
const editRules = {
|
||||
phoneNumber: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ type: 'email' as const, message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'change' }
|
||||
]
|
||||
phoneNumber: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
|
||||
email: [{ type: 'email' as const, message: '请输入正确的邮箱地址', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 查询审核人列表
|
||||
@@ -500,7 +526,6 @@ const getList = async () => {
|
||||
|
||||
console.log('处理后的审核人列表:', auditorList.value);
|
||||
console.log('总数:', total.value);
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取审核人列表失败:', error);
|
||||
auditorList.value = [];
|
||||
@@ -510,7 +535,6 @@ const getList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 搜索
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNum = 1;
|
||||
@@ -526,7 +550,7 @@ const resetQuery = () => {
|
||||
|
||||
// 多选框选中数据
|
||||
const handleSelectionChange = (selection: CwProjectAuditorVO[]) => {
|
||||
ids.value = selection.map(item => item.id);
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
@@ -542,7 +566,7 @@ const handleUpdate = (row?: CwProjectAuditorVO) => {
|
||||
if (row) {
|
||||
editDialog.form = { ...row };
|
||||
} else if (ids.value.length === 1) {
|
||||
const selectedRow = auditorList.value.find(item => item.id === ids.value[0]);
|
||||
const selectedRow = auditorList.value.find((item) => item.id === ids.value[0]);
|
||||
if (selectedRow) {
|
||||
editDialog.form = { ...selectedRow };
|
||||
}
|
||||
@@ -573,19 +597,13 @@ const submitEdit = async () => {
|
||||
// 删除审核人
|
||||
const handleDelete = (row?: CwProjectAuditorVO) => {
|
||||
const deleteIds = row ? [row.id] : ids.value;
|
||||
const names = row ? [row.userName] : auditorList.value
|
||||
.filter((item) => deleteIds.includes(item.id))
|
||||
.map((item) => item.userName);
|
||||
const names = row ? [row.userName] : auditorList.value.filter((item) => deleteIds.includes(item.id)).map((item) => item.userName);
|
||||
|
||||
ElMessageBox.confirm(
|
||||
`是否确认删除审核人"${names.join(', ')}"?`,
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(async () => {
|
||||
ElMessageBox.confirm(`是否确认删除审核人"${names.join(', ')}"?`, '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await delProjectAuditor(deleteIds);
|
||||
ElMessage.success('删除成功');
|
||||
@@ -649,44 +667,28 @@ const handleExport = () => {
|
||||
const handleBatchAdd = () => {
|
||||
batchAddDialog.visible = true;
|
||||
batchAddDialog.selectedUsers = [];
|
||||
userSearchText.value = '';
|
||||
currentDeptName.value = '';
|
||||
deptUserList.value = [];
|
||||
loadDeptTree();
|
||||
};
|
||||
|
||||
// 加载部门树数据
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
const response = await listDept();
|
||||
const response = await deptTreeSelect();
|
||||
console.log('部门选项响应:', response);
|
||||
|
||||
let deptData;
|
||||
if (response.data) {
|
||||
deptData = response.data;
|
||||
} else {
|
||||
deptData = response;
|
||||
}
|
||||
const deptData = response.data;
|
||||
|
||||
// 验证部门数据结构
|
||||
if (Array.isArray(deptData)) {
|
||||
// 检查数据格式并打印前几个节点用于调试
|
||||
// deptTreeSelect 返回的已经是正确的 DeptTreeVO[] 格式
|
||||
// 包含 id、label、children 字段,直接使用即可
|
||||
console.log('部门数据示例:', deptData.slice(0, 3));
|
||||
|
||||
// 转换为树形结构,适配 el-tree-select 组件
|
||||
const treeData = proxy?.handleTree(deptData, 'deptId', 'parentId', 'children', '0') || [];
|
||||
|
||||
// 转换数据格式以适配 el-tree-select
|
||||
const formatTreeData = (nodes: any[]): any[] => {
|
||||
return nodes.map(node => ({
|
||||
id: node.deptId,
|
||||
label: node.deptName,
|
||||
value: node.deptId,
|
||||
children: node.children ? formatTreeData(node.children) : []
|
||||
}));
|
||||
};
|
||||
|
||||
const formattedData = formatTreeData(treeData);
|
||||
|
||||
deptOptions.value = formattedData;
|
||||
deptTreeData.value = formattedData;
|
||||
deptOptions.value = deptData;
|
||||
deptTreeData.value = deptData;
|
||||
} else {
|
||||
console.warn('部门数据格式错误,期望数组但收到:', typeof deptData);
|
||||
deptOptions.value = [];
|
||||
@@ -720,61 +722,70 @@ const handleDeptClick = async (data: any) => {
|
||||
console.log('部门树节点点击:', data);
|
||||
console.log('节点ID类型:', typeof data?.id, '值:', data?.id);
|
||||
|
||||
// 验证deptId是否为有效的数字
|
||||
if (data?.id && !isNaN(Number(data.id)) && data.id !== 'treeselect') {
|
||||
try {
|
||||
userLoading.value = true;
|
||||
currentDeptName.value = data.label || data.deptName;
|
||||
// 验证deptId是否为有效的数字,排除特殊值
|
||||
const nodeId = data?.id;
|
||||
if (!nodeId || nodeId === 'treeselect' || nodeId === '') {
|
||||
console.warn('无效的部门节点ID:', nodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保传递数字类型的ID
|
||||
const deptId = Number(data.id);
|
||||
console.log('调用API,部门ID:', deptId);
|
||||
try {
|
||||
userLoading.value = true;
|
||||
currentDeptName.value = data.label || data.deptName;
|
||||
|
||||
const response = await listUserByDeptId(deptId);
|
||||
console.log('部门用户数据响应:', response);
|
||||
// 保持字符串类型的ID,避免大整数精度丢失
|
||||
const deptId = String(nodeId);
|
||||
console.log('调用API,部门ID:', deptId, '类型:', typeof deptId);
|
||||
|
||||
let userData;
|
||||
if (response.data) {
|
||||
userData = response.data;
|
||||
} else {
|
||||
userData = response;
|
||||
}
|
||||
const response = await listUserByDeptId(deptId);
|
||||
console.log('部门用户数据响应:', response);
|
||||
|
||||
deptUserList.value = Array.isArray(userData) ? userData : [];
|
||||
console.log('处理后的用户数据:', deptUserList.value);
|
||||
|
||||
// 切换部门后,恢复之前选中的用户状态
|
||||
nextTick(() => {
|
||||
if (userTableRef.value) {
|
||||
// 清空选择
|
||||
userTableRef.value.clearSelection();
|
||||
// 恢复已选择的用户
|
||||
deptUserList.value.forEach(user => {
|
||||
const isSelected = batchAddDialog.selectedUsers.some(selected => selected.userId === user.userId);
|
||||
if (isSelected) {
|
||||
userTableRef.value?.toggleRowSelection(user, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取部门用户失败:', error);
|
||||
ElMessage.error('获取部门用户失败,请重试');
|
||||
deptUserList.value = [];
|
||||
} finally {
|
||||
userLoading.value = false;
|
||||
// 后端返回格式: { code: 200, data: [...], msg: "操作成功" }
|
||||
let userData = [];
|
||||
if (response && response.data) {
|
||||
userData = response.data;
|
||||
} else if (Array.isArray(response)) {
|
||||
userData = response;
|
||||
}
|
||||
} else {
|
||||
console.warn('无效的部门节点:', data);
|
||||
ElMessage.warning('请选择有效的部门节点');
|
||||
|
||||
// 处理用户数据,添加缺失的状态字段和部门信息
|
||||
deptUserList.value = Array.isArray(userData) ? userData.map((user: any) => ({
|
||||
...user,
|
||||
status: user.status || '0', // 默认为正常状态
|
||||
deptId: user.deptId || nodeId, // 确保有部门ID
|
||||
deptName: user.deptName || currentDeptName.value // 确保有部门名称
|
||||
})) : [];
|
||||
console.log('处理后的用户数据:', deptUserList.value);
|
||||
console.log('用户数据数量:', deptUserList.value.length);
|
||||
|
||||
// 切换部门后,恢复之前选中的用户状态
|
||||
nextTick(() => {
|
||||
if (userTableRef.value) {
|
||||
// 清空选择
|
||||
userTableRef.value.clearSelection();
|
||||
// 恢复已选择的用户
|
||||
deptUserList.value.forEach((user) => {
|
||||
const isSelected = batchAddDialog.selectedUsers.some((selected) => selected.userId === user.userId);
|
||||
if (isSelected) {
|
||||
userTableRef.value?.toggleRowSelection(user, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取部门用户失败:', error);
|
||||
ElMessage.error('获取部门用户失败,请重试');
|
||||
deptUserList.value = [];
|
||||
} finally {
|
||||
userLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 用户选择变化
|
||||
const handleUserSelectionChange = (selection: DeptUserVO[]) => {
|
||||
// 移除当前部门中未选中的用户
|
||||
batchAddDialog.selectedUsers = batchAddDialog.selectedUsers.filter(user =>
|
||||
!deptUserList.value.some(deptUser => deptUser.userId === user.userId)
|
||||
batchAddDialog.selectedUsers = batchAddDialog.selectedUsers.filter(
|
||||
(user) => !deptUserList.value.some((deptUser) => deptUser.userId === user.userId)
|
||||
);
|
||||
|
||||
// 添加新选中的用户
|
||||
@@ -782,7 +793,7 @@ const handleUserSelectionChange = (selection: DeptUserVO[]) => {
|
||||
|
||||
// 去重
|
||||
const userMap = new Map();
|
||||
batchAddDialog.selectedUsers.forEach(user => {
|
||||
batchAddDialog.selectedUsers.forEach((user) => {
|
||||
userMap.set(user.userId, user);
|
||||
});
|
||||
batchAddDialog.selectedUsers = Array.from(userMap.values());
|
||||
@@ -790,14 +801,14 @@ const handleUserSelectionChange = (selection: DeptUserVO[]) => {
|
||||
|
||||
// 移除已选择的用户
|
||||
const removeSelectedUser = (user: DeptUserVO) => {
|
||||
const index = batchAddDialog.selectedUsers.findIndex(item => item.userId === user.userId);
|
||||
const index = batchAddDialog.selectedUsers.findIndex((item) => item.userId === user.userId);
|
||||
if (index > -1) {
|
||||
batchAddDialog.selectedUsers.splice(index, 1);
|
||||
}
|
||||
|
||||
// 如果该用户在当前显示的部门用户列表中,取消选中状态
|
||||
if (userTableRef.value && deptUserList.value.some(deptUser => deptUser.userId === user.userId)) {
|
||||
const tableUser = deptUserList.value.find(deptUser => deptUser.userId === user.userId);
|
||||
if (userTableRef.value && deptUserList.value.some((deptUser) => deptUser.userId === user.userId)) {
|
||||
const tableUser = deptUserList.value.find((deptUser) => deptUser.userId === user.userId);
|
||||
if (tableUser) {
|
||||
userTableRef.value.toggleRowSelection(tableUser, false);
|
||||
}
|
||||
@@ -812,6 +823,22 @@ const clearSelectedUsers = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 用户搜索处理
|
||||
const handleUserSearch = () => {
|
||||
// 搜索时重新应用选择状态
|
||||
nextTick(() => {
|
||||
if (userTableRef.value) {
|
||||
userTableRef.value.clearSelection();
|
||||
filteredUserList.value.forEach((user) => {
|
||||
const isSelected = batchAddDialog.selectedUsers.some((selected) => selected.userId === user.userId);
|
||||
if (isSelected) {
|
||||
userTableRef.value?.toggleRowSelection(user, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 确认批量添加
|
||||
const confirmBatchAdd = async () => {
|
||||
if (batchAddDialog.selectedUsers.length === 0) {
|
||||
@@ -821,19 +848,38 @@ const confirmBatchAdd = async () => {
|
||||
|
||||
batchAddLoading.value = true;
|
||||
try {
|
||||
const userIds = batchAddDialog.selectedUsers.map(user => user.userId);
|
||||
// 验证用户数据完整性
|
||||
const invalidUsers = batchAddDialog.selectedUsers.filter(user =>
|
||||
!user.userId || !user.userName || !user.deptId || !user.deptName
|
||||
);
|
||||
|
||||
if (invalidUsers.length > 0) {
|
||||
ElMessage.warning(`发现 ${invalidUsers.length} 个用户信息不完整,请重新选择`);
|
||||
console.warn('用户信息不完整:', invalidUsers);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用标准批量添加API(后端会自动获取用户完整信息)
|
||||
const userIds = batchAddDialog.selectedUsers.map((user) => user.userId);
|
||||
const request: BatchAddRequest = {
|
||||
userIds,
|
||||
remark: '批量添加的审核人'
|
||||
};
|
||||
|
||||
console.log('批量添加请求数据:', request);
|
||||
console.log('选中的用户详情:', batchAddDialog.selectedUsers);
|
||||
|
||||
await batchAddProjectAuditor(request);
|
||||
|
||||
ElMessage.success(`成功添加 ${batchAddDialog.selectedUsers.length} 个审核人`);
|
||||
batchAddDialog.visible = false;
|
||||
batchAddDialog.selectedUsers = [];
|
||||
userSearchText.value = '';
|
||||
getList();
|
||||
|
||||
} catch (error) {
|
||||
console.error('批量添加审核人失败:', error);
|
||||
ElMessage.error('批量添加审核人失败,请检查用户信息是否完整');
|
||||
} finally {
|
||||
batchAddLoading.value = false;
|
||||
}
|
||||
@@ -882,7 +928,6 @@ onMounted(() => {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
/* 表格样式 */
|
||||
.scroll-table {
|
||||
:deep(.el-table__header-wrapper) {
|
||||
@@ -901,6 +946,8 @@ onMounted(() => {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
height: 500px;
|
||||
max-width: 1160px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.dept-select-section {
|
||||
@@ -909,7 +956,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.user-select-section {
|
||||
flex: 1;
|
||||
width: 840px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
@@ -923,12 +971,33 @@ onMounted(() => {
|
||||
|
||||
.user-table-card {
|
||||
flex: 1;
|
||||
|
||||
.el-table {
|
||||
width: 100% !important;
|
||||
table-layout: fixed !important;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.user-count {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-users-list {
|
||||
|
||||
@@ -17,13 +17,7 @@
|
||||
|
||||
<div class="test-section">
|
||||
<h3>3. 部门选择测试</h3>
|
||||
<el-tree-select
|
||||
v-model="selectedDept"
|
||||
:data="deptOptions"
|
||||
placeholder="选择部门"
|
||||
clearable
|
||||
style="width: 300px"
|
||||
/>
|
||||
<el-tree-select v-model="selectedDept" :data="deptOptions" placeholder="选择部门" clearable style="width: 300px" />
|
||||
<p>选中部门: {{ selectedDept }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,14 +36,22 @@ const apiResult = ref('');
|
||||
|
||||
// 模拟部门数据
|
||||
const mockDeptOptions = [
|
||||
{ value: '1', label: '总公司', children: [
|
||||
{ value: '2', label: '财务部' },
|
||||
{ value: '3', label: '人事部' }
|
||||
]},
|
||||
{ value: '4', label: '分公司', children: [
|
||||
{ value: '5', label: '技术部' },
|
||||
{ value: '6', label: '市场部' }
|
||||
]}
|
||||
{
|
||||
value: '1',
|
||||
label: '总公司',
|
||||
children: [
|
||||
{ value: '2', label: '财务部' },
|
||||
{ value: '3', label: '人事部' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '分公司',
|
||||
children: [
|
||||
{ value: '5', label: '技术部' },
|
||||
{ value: '6', label: '市场部' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// 模拟API响应数据
|
||||
|
||||
Reference in new issue
Block a user