From b15b6b4f4d19cf728f93949af5cad4262e909b8f Mon Sep 17 00:00:00 2001 From: lzh Date: Thu, 23 Apr 2026 14:51:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(@vben/web-antd):=20=E7=94=A8=E6=88=B7-?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=BB=91=E5=AE=9A=E7=AE=A1=E7=90=86=E5=8F=8C?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用户管理页:下拉操作新增 "分配项目" 按钮 + assign-project-form.vue 弹窗 沿用现有 assign-role-form 的交互(多选 + 覆盖写入) - 项目管理页:行操作新增 "管理成员" 按钮 + assign-user-form.vue 弹窗 下拉支持搜索用户 nickname/username - 新建 api/system/user-project/ 封装 4 个接口 - api/system/project 新增 getAllProjectSimpleList: 顶栏 simple-list 已改为用户授权过滤,管理员分配场景需要全量下拉 - 空集保存二次确认:清空所有分配/成员时弹 AntModal.confirm,防误操作 - 权限点:system:user:assign-project / system:project:assign-user 设计文档:docs/design/2026-04-23-user-project-binding.md Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web-antd/src/api/system/project/index.ts | 8 ++ .../src/api/system/user-project/index.ts | 46 +++++++++ .../web-antd/src/views/system/project/data.ts | 46 +++++++++ .../src/views/system/project/index.vue | 18 ++++ .../project/modules/assign-user-form.vue | 98 +++++++++++++++++++ apps/web-antd/src/views/system/user/data.ts | 44 +++++++++ apps/web-antd/src/views/system/user/index.vue | 18 ++++ .../user/modules/assign-project-form.vue | 98 +++++++++++++++++++ 8 files changed, 376 insertions(+) create mode 100644 apps/web-antd/src/api/system/user-project/index.ts create mode 100644 apps/web-antd/src/views/system/project/modules/assign-user-form.vue create mode 100644 apps/web-antd/src/views/system/user/modules/assign-project-form.vue diff --git a/apps/web-antd/src/api/system/project/index.ts b/apps/web-antd/src/api/system/project/index.ts index 4d767fb74..524cc09b8 100644 --- a/apps/web-antd/src/api/system/project/index.ts +++ b/apps/web-antd/src/api/system/project/index.ts @@ -23,12 +23,20 @@ export function getProjectPage(params: PageParam) { ); } +/** 获取当前登录用户授权的项目精简列表(顶栏项目切换器用) */ export function getSimpleProjectList() { return requestClient.get( '/system/project/simple-list', ); } +/** 获取本租户全部启用项目(管理员分配场景下拉数据源) */ +export function getAllProjectSimpleList() { + return requestClient.get( + '/system/project/all-simple-list', + ); +} + export function getProject(id: number) { return requestClient.get( `/system/project/get?id=${id}`, diff --git a/apps/web-antd/src/api/system/user-project/index.ts b/apps/web-antd/src/api/system/user-project/index.ts new file mode 100644 index 000000000..795a6f2e1 --- /dev/null +++ b/apps/web-antd/src/api/system/user-project/index.ts @@ -0,0 +1,46 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemUserProjectApi { + export interface AssignUserProjectsReq { + userId: number; + projectIds: number[]; + } + export interface AssignProjectUsersReq { + projectId: number; + userIds: number[]; + } +} + +/** 给用户覆盖式分配项目 */ +export function assignUserProjects( + data: SystemUserProjectApi.AssignUserProjectsReq, +) { + return requestClient.post( + '/system/user-project/assign-user-projects', + data, + ); +} + +/** 给项目覆盖式分配成员 */ +export function assignProjectUsers( + data: SystemUserProjectApi.AssignProjectUsersReq, +) { + return requestClient.post( + '/system/user-project/assign-project-users', + data, + ); +} + +/** 查询某用户已绑定的项目编号集合 */ +export function getProjectIdsByUserId(userId: number) { + return requestClient.get( + `/system/user-project/list-project-ids-by-user?userId=${userId}`, + ); +} + +/** 查询某项目下绑定的用户编号集合 */ +export function getUserIdsByProjectId(projectId: number) { + return requestClient.get( + `/system/user-project/list-user-ids-by-project?projectId=${projectId}`, + ); +} diff --git a/apps/web-antd/src/views/system/project/data.ts b/apps/web-antd/src/views/system/project/data.ts index d4eb1080a..8bd1bf701 100644 --- a/apps/web-antd/src/views/system/project/data.ts +++ b/apps/web-antd/src/views/system/project/data.ts @@ -5,6 +5,7 @@ import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; import { getDictOptions } from '@vben/hooks'; import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -83,6 +84,51 @@ export function useFormSchema(): VbenFormSchema[] { ]; } +/** 管理成员的表单(项目 → 多用户) */ +export function useAssignUserFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '项目名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'code', + label: '项目编码', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'userIds', + label: '成员', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择成员(留空表示清空所有成员)', + allowClear: true, + showSearch: true, + optionFilterProp: 'label', + }, + }, + ]; +} + /** 列表的搜索表单 */ export function useGridFormSchema(): VbenFormSchema[] { return [ diff --git a/apps/web-antd/src/views/system/project/index.vue b/apps/web-antd/src/views/system/project/index.vue index dec6426bd..708a59ede 100644 --- a/apps/web-antd/src/views/system/project/index.vue +++ b/apps/web-antd/src/views/system/project/index.vue @@ -14,6 +14,7 @@ import { deleteProject, getProjectPage } from '#/api/system/project'; import { $t } from '#/locales'; import { useGridColumns, useGridFormSchema } from './data'; +import AssignUserForm from './modules/assign-user-form.vue'; import Form from './modules/form.vue'; const [FormModal, formModalApi] = useVbenModal({ @@ -21,6 +22,11 @@ const [FormModal, formModalApi] = useVbenModal({ destroyOnClose: true, }); +const [AssignUserModal, assignUserModalApi] = useVbenModal({ + connectedComponent: AssignUserForm, + destroyOnClose: true, +}); + /** 刷新表格 */ function handleRefresh() { gridApi.query(); @@ -36,6 +42,11 @@ function handleEdit(row: SystemProjectApi.Project) { formModalApi.setData(row).open(); } +/** 管理成员 */ +function handleAssignUser(row: SystemProjectApi.Project) { + assignUserModalApi.setData(row).open(); +} + /** 删除项目 */ async function handleDelete(row: SystemProjectApi.Project) { const hideLoading = message.loading({ @@ -114,6 +125,7 @@ const [Grid, gridApi] = useVbenVxeGrid({