wuzhen update

This commit is contained in:
wuzhen committed 2025-09-07 18:17:50 +08:00
1 parent edaf8dbacf
commit 48da578546
86 files changed
+2101 -1849

No files matched your search

+24 -1
View File
@@ -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文档
**🔄 状态**: 核心功能完成,待进一步测试和优化
**🔄 状态**: 核心功能完成,待进一步测试和优化
+1 -1
View File
@@ -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,
+132 -40
View File
@@ -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);
}
/* 表格卡片头部样式 */
+12 -2
View File
@@ -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) {
+2 -2
View File
@@ -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">
+17 -2
View File
@@ -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>
+2 -2
View File
@@ -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>
+8 -38
View File
@@ -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` - 编辑权限
+3 -5
View File
@@ -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 {
+206 -137
View File
@@ -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 -15
View File
@@ -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响应数据