docs: add superpowers design docs and plans
This commit is contained in:
@@ -0,0 +1,501 @@
|
||||
# 用户管理接口适配实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-subagent-driven-development (recommended) or superpowers-executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 适配后端用户管理接口变更,在用户列表页增加部门/角色展示和树形筛选,详情页使用聚合接口,减少前端请求次数。
|
||||
|
||||
**Architecture:** 基于现有 Vue 3 + Element Plus 技术栈,扩展 Service 层方法,修改视图组件以利用后端返回的聚合数据(depts/roles),添加树形筛选组件支持部门和角色筛选。
|
||||
|
||||
**Tech Stack:** Vue 3, TypeScript, Element Plus, Vite
|
||||
|
||||
---
|
||||
|
||||
## 文件变更清单
|
||||
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `admin-ui/src/service/user/userService.ts` | 修改 | 添加 aggregate 方法 |
|
||||
| `admin-ui/src/views/user/info/Index.vue` | 修改 | 添加部门/角色列和树形筛选 |
|
||||
| `admin-ui/src/views/user/info/UserDetailDialog.vue` | 修改 | 使用聚合接口展示完整信息 |
|
||||
| `admin-ui/src/views/user/info/UserFormDialog.vue` | 修改 | 从 row 解析 deptIds/roleIds |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 扩展 UserService 添加聚合查询方法
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/service/user/userService.ts`
|
||||
|
||||
- [ ] **Step 1: 添加 aggregate 方法到 UserService**
|
||||
|
||||
在 `UserService` 类中,在 `assignRoles` 方法后添加:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 聚合查询用户完整信息(基础信息 + 部门列表 + 角色列表)
|
||||
*/
|
||||
async aggregate(userId: number | string): Promise<any> {
|
||||
const res: any = await request({
|
||||
url: `/user/admin/user/${userId}/aggregate`,
|
||||
method: 'get',
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证语法**
|
||||
|
||||
Run: `cd admin-ui && npx vue-tsc --noEmit --skipLibCheck src/service/user/userService.ts`
|
||||
Expected: 无错误输出
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/service/user/userService.ts
|
||||
git commit -m "feat(user): add aggregate method to UserService for fetching user with depts and roles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 用户列表页添加部门/角色列和树形筛选
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/user/info/Index.vue`
|
||||
|
||||
- [ ] **Step 1: 导入必要的依赖**
|
||||
|
||||
在 `<script setup>` 顶部添加导入:
|
||||
|
||||
```typescript
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { deptService } from '@/service/system/deptService'
|
||||
import { roleService } from '@/service/system/roleService'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 添加部门和角色列到表格配置**
|
||||
|
||||
在 `columns` 数组中,在 `createdAt` 列之前添加:
|
||||
|
||||
```typescript
|
||||
{
|
||||
prop: 'depts',
|
||||
label: '所属部门',
|
||||
minWidth: 150,
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
prop: 'roles',
|
||||
label: '角色',
|
||||
minWidth: 150,
|
||||
slot: true,
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 添加树形数据状态**
|
||||
|
||||
在 `const { t } = useI18n()` 后添加:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 部门树数据
|
||||
*/
|
||||
const deptTree = ref<any[]>([])
|
||||
|
||||
/**
|
||||
* 角色树数据
|
||||
*/
|
||||
const roleTree = ref<any[]>([])
|
||||
|
||||
/**
|
||||
* 加载部门树
|
||||
*/
|
||||
async function loadDeptTree() {
|
||||
try {
|
||||
const list = await deptService.list({ status: 1 })
|
||||
deptTree.value = list || []
|
||||
} catch {
|
||||
// 错误已由请求拦截器统一提示
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载角色列表(转换为树形结构)
|
||||
*/
|
||||
async function loadRoleTree() {
|
||||
try {
|
||||
const list = await roleService.list({ status: 1 })
|
||||
// 角色列表已经是扁平结构,直接作为树形数据使用
|
||||
roleTree.value = list || []
|
||||
} catch {
|
||||
// 错误已由请求拦截器统一提示
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载基础数据
|
||||
onMounted(() => {
|
||||
loadDeptTree()
|
||||
loadRoleTree()
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 添加部门/角色筛选条件到搜索区域**
|
||||
|
||||
在 `<template>` 的 `#search` slot 中,在状态筛选后添加:
|
||||
|
||||
```vue
|
||||
<el-form-item label="所属部门">
|
||||
<el-tree-select
|
||||
v-model="q.deptId"
|
||||
:data="deptTree"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
:props="{ label: 'deptName', children: 'children' }"
|
||||
placeholder="请选择部门"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<el-tree-select
|
||||
v-model="q.roleId"
|
||||
:data="roleTree"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
:props="{ label: 'roleName', children: 'children' }"
|
||||
placeholder="请选择角色"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
/>
|
||||
</el-form-item>
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 添加部门/角色列的自定义渲染**
|
||||
|
||||
在 `<template>` 中,在 `#column-status` slot 后添加:
|
||||
|
||||
```vue
|
||||
<!-- 自定义列:所属部门 -->
|
||||
<template #column-depts="{ row }">
|
||||
<template v-if="row.depts?.length">
|
||||
<el-tag
|
||||
v-for="dept in row.depts"
|
||||
:key="dept.deptId"
|
||||
:type="dept.main ? 'primary' : 'info'"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ dept.deptName }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<!-- 自定义列:角色 -->
|
||||
<template #column-roles="{ row }">
|
||||
<template v-if="row.roles?.length">
|
||||
<el-tag
|
||||
v-for="role in row.roles"
|
||||
:key="role.roleId"
|
||||
type="success"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ role.roleName }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 验证模板语法**
|
||||
|
||||
Run: `cd admin-ui && npx vue-tsc --noEmit --skipLibCheck src/views/user/info/Index.vue`
|
||||
Expected: 无错误输出
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/user/info/Index.vue
|
||||
git commit -m "feat(user): add dept/role columns and tree filters to user list page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 用户详情弹窗使用聚合接口
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/user/info/UserDetailDialog.vue`
|
||||
|
||||
- [ ] **Step 1: 添加导入和状态**
|
||||
|
||||
在 `<script setup>` 顶部修改:
|
||||
|
||||
```typescript
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { userService } from '@/service/user/userService'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
row: any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
}>()
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
})
|
||||
|
||||
/**
|
||||
* 用户聚合数据
|
||||
*/
|
||||
const userAggregate = ref<any>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
/**
|
||||
* 加载聚合数据
|
||||
*/
|
||||
async function loadAggregateData() {
|
||||
if (!props.row?.id) return
|
||||
loading.value = true
|
||||
try {
|
||||
userAggregate.value = await userService.aggregate(props.row.id)
|
||||
} catch {
|
||||
// 错误已由请求拦截器统一提示
|
||||
// 回退到使用 props.row
|
||||
userAggregate.value = props.row
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听弹窗显示,加载聚合数据
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
userAggregate.value = null
|
||||
loadAggregateData()
|
||||
}
|
||||
})
|
||||
|
||||
const userTypeMap: Record<number, string> = {
|
||||
1: '普通用户',
|
||||
2: '管理员',
|
||||
3: '系统用户',
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 修改模板使用聚合数据**
|
||||
|
||||
将模板中的 `row?.` 替换为 `userAggregate?.` 或回退到 `row?.`:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="用户详情"
|
||||
width="700px"
|
||||
class="rui-dialog"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-loading="loading">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="用户ID">
|
||||
{{ userAggregate?.id || row?.id || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户名">
|
||||
{{ userAggregate?.username || row?.username || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户类型">
|
||||
<el-tag :type="(userAggregate?.userType || row?.userType) === 2 ? 'warning' : (userAggregate?.userType || row?.userType) === 3 ? 'danger' : 'info'" size="small">
|
||||
{{ userTypeMap[userAggregate?.userType || row?.userType] || '未知' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag v-if="(userAggregate?.status || row?.status) === 1" type="success" size="small">启用</el-tag>
|
||||
<el-tag v-else type="danger" size="small">禁用</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">
|
||||
{{ userAggregate?.phone || row?.phone || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">
|
||||
{{ userAggregate?.email || row?.email || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">
|
||||
{{ (userAggregate?.createdAt || row?.createdAt) ? new Date(userAggregate?.createdAt || row?.createdAt).toLocaleString() : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" :span="2">
|
||||
{{ (userAggregate?.updatedAt || row?.updatedAt) ? new Date(userAggregate?.updatedAt || row?.updatedAt).toLocaleString() : '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 部门信息 -->
|
||||
<div class="mt-4">
|
||||
<h4 class="text-sm font-bold mb-2">所属部门</h4>
|
||||
<div v-if="userAggregate?.depts?.length" class="flex flex-wrap gap-2">
|
||||
<el-tag
|
||||
v-for="dept in userAggregate.depts"
|
||||
:key="dept.deptId"
|
||||
:type="dept.main ? 'primary' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ dept.deptName }}
|
||||
<el-tag v-if="dept.main" type="danger" size="small" class="ml-1">主</el-tag>
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-empty v-else description="暂无部门信息" :image-size="60" />
|
||||
</div>
|
||||
|
||||
<!-- 角色信息 -->
|
||||
<div class="mt-4">
|
||||
<h4 class="text-sm font-bold mb-2">角色</h4>
|
||||
<div v-if="userAggregate?.roles?.length" class="flex flex-wrap gap-2">
|
||||
<el-tag
|
||||
v-for="role in userAggregate.roles"
|
||||
:key="role.roleId"
|
||||
type="success"
|
||||
size="small"
|
||||
>
|
||||
{{ role.roleName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-empty v-else description="暂无角色信息" :image-size="60" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证语法**
|
||||
|
||||
Run: `cd admin-ui && npx vue-tsc --noEmit --skipLibCheck src/views/user/info/UserDetailDialog.vue`
|
||||
Expected: 无错误输出
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/user/info/UserDetailDialog.vue
|
||||
git commit -m "feat(user): use aggregate API in user detail dialog to show depts and roles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 用户表单从聚合数据解析已选部门/角色
|
||||
|
||||
**Files:**
|
||||
- Modify: `admin-ui/src/views/user/info/UserFormDialog.vue`
|
||||
|
||||
- [ ] **Step 1: 修改 watch 逻辑解析 deptIds 和 roleIds**
|
||||
|
||||
在 `watch(() => props.visible, async (val) => { ... })` 中,修改编辑时的数据处理:
|
||||
|
||||
```typescript
|
||||
watch(() => props.visible, async (val) => {
|
||||
if (val) {
|
||||
await Promise.all([loadDeptTree(), loadPostList()])
|
||||
|
||||
if (props.row) {
|
||||
// 从聚合数据解析已选部门ID和角色ID
|
||||
const deptIds = props.row.depts?.map((d: any) => d.deptId) || []
|
||||
const roleIds = props.row.roles?.map((r: any) => r.roleId) || []
|
||||
|
||||
// 先设置表单数据,确保 rules 能正确计算
|
||||
form.value = {
|
||||
...props.row,
|
||||
password: '',
|
||||
deptIds: deptIds,
|
||||
postIds: props.row.postIds || [],
|
||||
}
|
||||
}
|
||||
else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 可选 - 移除冗余的 userDeptService 导入**
|
||||
|
||||
由于不再需要在表单中调用 `userDeptService.listDeptIdsByUserId`,可以移除该导入(但保留 `assignDepts` 用于保存):
|
||||
|
||||
**注意:** 当前代码中没有直接调用 `listDeptIdsByUserId`,所以无需修改导入。`userDeptService` 仍在 `onSubmit` 中被使用。
|
||||
|
||||
- [ ] **Step 3: 验证语法**
|
||||
|
||||
Run: `cd admin-ui && npx vue-tsc --noEmit --skipLibCheck src/views/user/info/UserFormDialog.vue`
|
||||
Expected: 无错误输出
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add admin-ui/src/views/user/info/UserFormDialog.vue
|
||||
git commit -m "feat(user): parse deptIds and roleIds from aggregate data in user form"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 验证和最终检查
|
||||
|
||||
- [ ] **Step 1: 运行类型检查**
|
||||
|
||||
Run: `cd admin-ui && npx vue-tsc --noEmit --skipLibCheck`
|
||||
Expected: 无错误输出
|
||||
|
||||
- [ ] **Step 2: 运行构建**
|
||||
|
||||
Run: `cd admin-ui && npm run build`
|
||||
Expected: 构建成功,无错误
|
||||
|
||||
- [ ] **Step 3: 检查所有变更**
|
||||
|
||||
Run: `git log --oneline -5`
|
||||
Expected: 看到 4 个提交:
|
||||
- feat(user): add aggregate method to UserService...
|
||||
- feat(user): add dept/role columns and tree filters to user list page
|
||||
- feat(user): use aggregate API in user detail dialog...
|
||||
- feat(user): parse deptIds and roleIds from aggregate data in user form
|
||||
|
||||
- [ ] **Step 4: 最终提交(可选,如果需要合并)**
|
||||
|
||||
```bash
|
||||
# 如果需要,可以创建一个合并提交
|
||||
git log --oneline -5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 回滚计划
|
||||
|
||||
如果出现问题,可以按以下顺序回滚:
|
||||
|
||||
1. 回滚 Task 4: `git revert <task4-commit>`
|
||||
2. 回滚 Task 3: `git revert <task3-commit>`
|
||||
3. 回滚 Task 2: `git revert <task2-commit>`
|
||||
4. 回滚 Task 1: `git revert <task1-commit>`
|
||||
|
||||
---
|
||||
|
||||
## 测试清单
|
||||
|
||||
- [ ] 用户列表页显示部门列(主部门高亮)
|
||||
- [ ] 用户列表页显示角色列
|
||||
- [ ] 部门树形筛选正常工作
|
||||
- [ ] 角色树形筛选正常工作
|
||||
- [ ] 同时筛选部门和角色正常工作
|
||||
- [ ] 用户详情弹窗显示完整部门列表
|
||||
- [ ] 用户详情弹窗显示完整角色列表
|
||||
- [ ] 编辑用户时正确加载已选部门
|
||||
- [ ] 编辑用户时正确加载已选角色
|
||||
- [ ] 保存用户后数据正确刷新
|
||||
|
||||
---
|
||||
|
||||
**计划状态**: 待评审
|
||||
**下一步**: 用户评审通过后,使用 superpowers-subagent-driven-development 或 superpowers-executing-plans 执行
|
||||
@@ -0,0 +1,261 @@
|
||||
# 用户管理接口适配设计规范
|
||||
|
||||
**工单**: #2 - 用户管理接口变更通知
|
||||
**日期**: 2026-06-07
|
||||
**方案**: 方案 B(完整适配)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
后端已完成用户管理模块接口重构(提交 `dbd04d8`),支持部门、角色联表查询和聚合信息返回。前端需要适配以:
|
||||
- 减少请求次数(从 3 个请求合并为 1 个)
|
||||
- 支持部门/角色筛选
|
||||
- 在列表和详情中展示部门、角色信息
|
||||
|
||||
## 2. 后端变更摘要
|
||||
|
||||
### 2.1 用户实体扩展字段
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"phone": "13800138000",
|
||||
"depts": [
|
||||
{
|
||||
"deptId": 1,
|
||||
"deptCode": "TECH",
|
||||
"deptName": "技术部",
|
||||
"main": true
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"roleId": 1,
|
||||
"roleCode": "admin",
|
||||
"roleName": "管理员",
|
||||
"dataScope": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 新增接口
|
||||
|
||||
- `GET /user/admin/user/{id}/aggregate` - 聚合查询(基础信息 + 部门列表 + 角色列表)
|
||||
|
||||
### 2.3 增强接口
|
||||
|
||||
- `GET /user/admin/user/page` - 自动返回 `depts` 和 `roles`
|
||||
- `GET /user/admin/user/list` - 自动返回 `depts` 和 `roles`
|
||||
- 支持筛选参数:`deptId`、`roleId`
|
||||
|
||||
## 3. 前端适配范围
|
||||
|
||||
### 3.1 用户列表页 (`views/user/info/Index.vue`)
|
||||
|
||||
**新增列:**
|
||||
- 部门列:显示用户所属部门名称(多个部门用逗号分隔,主部门加粗)
|
||||
- 角色列:显示用户角色名称(多个角色用标签展示)
|
||||
|
||||
**新增筛选条件:**
|
||||
- 部门筛选:树形选择器(`el-tree-select`),支持多选
|
||||
- 角色筛选:树形选择器(`el-tree-select`),支持多选
|
||||
|
||||
**数据流:**
|
||||
- 列表接口自动返回 `depts` 和 `roles`,无需额外请求
|
||||
- 筛选参数通过 `queryParams` 传递给 `userService.page()`
|
||||
|
||||
### 3.2 用户详情弹窗 (`views/user/info/UserDetailDialog.vue`)
|
||||
|
||||
**改造:**
|
||||
- 使用新的聚合接口 `GET /user/admin/user/{id}/aggregate`
|
||||
- 展示部门列表(部门名称 + 是否主部门标记)
|
||||
- 展示角色列表(角色名称 + 数据范围)
|
||||
|
||||
**数据流:**
|
||||
```
|
||||
打开弹窗 → 调用 aggregate 接口 → 展示完整信息
|
||||
```
|
||||
|
||||
### 3.3 用户表单 (`views/user/info/UserFormDialog.vue`)
|
||||
|
||||
**优化:**
|
||||
- 编辑时从 `row.depts` 解析 `deptIds`(替代调用 `userDeptService.listDeptIdsByUserId`)
|
||||
- 编辑时从 `row.roles` 解析 `roleIds`(替代调用 `userService.getRoles`)
|
||||
- 保留 `userDeptService.assignDepts` 和 `userPostService.assignPosts` 用于保存
|
||||
|
||||
### 3.4 Service 层扩展 (`service/user/userService.ts`)
|
||||
|
||||
**新增方法:**
|
||||
- `aggregate(userId)` - 调用聚合查询接口
|
||||
|
||||
## 4. 组件设计
|
||||
|
||||
### 4.1 部门/角色展示组件
|
||||
|
||||
无需新增组件,直接在表格列中使用 `slot` 渲染:
|
||||
|
||||
```vue
|
||||
<!-- 部门列 -->
|
||||
<template #column-depts="{ row }">
|
||||
<el-tag
|
||||
v-for="dept in row.depts"
|
||||
:key="dept.deptId"
|
||||
:type="dept.main ? 'primary' : 'info'"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
>
|
||||
{{ dept.deptName }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<!-- 角色列 -->
|
||||
<template #column-roles="{ row }">
|
||||
<el-tag
|
||||
v-for="role in row.roles"
|
||||
:key="role.roleId"
|
||||
type="success"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
>
|
||||
{{ role.roleName }}
|
||||
</el-tag>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 4.2 筛选区域
|
||||
|
||||
```vue
|
||||
<template #search="{ query: q, search, reset }">
|
||||
<!-- 现有筛选条件... -->
|
||||
|
||||
<!-- 新增:部门筛选 -->
|
||||
<el-form-item label="所属部门">
|
||||
<el-tree-select
|
||||
v-model="q.deptId"
|
||||
:data="deptTree"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
:props="{ label: 'deptName', children: 'children' }"
|
||||
placeholder="请选择部门"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 新增:角色筛选 -->
|
||||
<el-form-item label="角色">
|
||||
<el-tree-select
|
||||
v-model="q.roleId"
|
||||
:data="roleTree"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
:props="{ label: 'roleName', children: 'children' }"
|
||||
placeholder="请选择角色"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 5. 数据类型定义
|
||||
|
||||
```typescript
|
||||
// 部门信息(嵌套在用户中)
|
||||
interface UserDept {
|
||||
deptId: number
|
||||
deptCode: string
|
||||
deptName: string
|
||||
main: boolean
|
||||
}
|
||||
|
||||
// 角色信息(嵌套在用户中)
|
||||
interface UserRole {
|
||||
roleId: number
|
||||
roleCode: string
|
||||
roleName: string
|
||||
dataScope: number
|
||||
}
|
||||
|
||||
// 扩展用户类型
|
||||
interface User {
|
||||
id: number
|
||||
username: string
|
||||
// ... 其他字段
|
||||
depts?: UserDept[]
|
||||
roles?: UserRole[]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 接口调用变更
|
||||
|
||||
### 6.1 列表页
|
||||
|
||||
**变更前:**
|
||||
- 调用 `userService.page(params)` - 仅返回基础信息
|
||||
|
||||
**变更后:**
|
||||
- 调用 `userService.page(params)` - 自动包含 `depts` 和 `roles`
|
||||
- 支持 `deptId` 和 `roleId` 筛选参数
|
||||
|
||||
### 6.2 详情弹窗
|
||||
|
||||
**变更前:**
|
||||
- 直接使用 `props.row` 数据
|
||||
|
||||
**变更后:**
|
||||
- 打开时调用 `userService.aggregate(props.row.id)`
|
||||
- 使用返回的完整数据渲染
|
||||
|
||||
### 6.3 编辑表单
|
||||
|
||||
**变更前:**
|
||||
```typescript
|
||||
// 需要额外请求获取部门和角色
|
||||
const deptIds = await userDeptService.listDeptIdsByUserId(userId)
|
||||
const roleIds = await userService.getRoles(userId)
|
||||
```
|
||||
|
||||
**变更后:**
|
||||
```typescript
|
||||
// 直接从 row 中解析
|
||||
const deptIds = props.row.depts?.map((d: any) => d.deptId) || []
|
||||
const roleIds = props.row.roles?.map((r: any) => r.roleId) || []
|
||||
```
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
- 聚合接口失败时,回退到使用 `props.row` 基础信息
|
||||
- 部门/角色数据缺失时,显示 "-" 或空标签
|
||||
- 筛选条件不影响现有查询逻辑
|
||||
|
||||
## 8. 兼容性
|
||||
|
||||
- 原有接口保持不变
|
||||
- 新增字段通过 `@TableField(exist = false)` 添加,不影响旧逻辑
|
||||
- 保留 `userDeptService` 和 `userPostService` 用于分配功能
|
||||
|
||||
## 9. 测试要点
|
||||
|
||||
1. 列表页是否正确显示部门和角色信息
|
||||
2. 部门/角色筛选是否生效
|
||||
3. 详情弹窗是否正确展示聚合数据
|
||||
4. 编辑表单是否正确解析已选部门/角色
|
||||
5. 保存后数据是否正确刷新
|
||||
|
||||
## 10. 文件变更清单
|
||||
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `views/user/info/Index.vue` | 修改 | 添加部门/角色列和筛选条件 |
|
||||
| `views/user/info/UserDetailDialog.vue` | 修改 | 使用聚合接口,展示部门/角色详情 |
|
||||
| `views/user/info/UserFormDialog.vue` | 修改 | 从 row 解析 deptIds/roleIds |
|
||||
| `service/user/userService.ts` | 修改 | 添加 aggregate 方法 |
|
||||
|
||||
---
|
||||
|
||||
**设计评审状态**: 待评审
|
||||
**下一步**: 用户评审通过后,编写实施计划
|
||||
Reference in New Issue
Block a user