feat(@vben/web-antd): 项目成员管理改 Drawer + 分页 + 增量

从 Modal 多选改为 Drawer 分页表,更接近"成员管理"语义:
- 原 assign-user-form.vue 重写为 Drawer + Vxe 分页表
- 新增 add-user-modal.vue 子弹窗用于添加用户(过滤已是成员)
- 每行一个"移除"popConfirm 按钮,调 removeProjectUser 单删
- 顶部 keyword 搜索,按 username/nickname/mobile 模糊
- 底部提示:超管不在此列表(后端已过滤)
- data.ts 新增 useProjectMemberGridColumns
- api 新增 getProjectUserPage / addProjectUsers / removeProjectUser
- project/index.vue 接入点改 useVbenDrawer

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-23 15:48:43 +08:00
parent b15b6b4f4d
commit 09538b03cb
5 changed files with 342 additions and 78 deletions

View File

@@ -0,0 +1,106 @@
<script lang="ts" setup>
import type { SystemUserApi } from '#/api/system/user';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Select, Spin } from 'ant-design-vue';
import { getSimpleUserList } from '#/api/system/user';
import { addProjectUsers } from '#/api/system/user-project';
import { $t } from '#/locales';
interface AddUserModalData {
projectId: number;
projectName?: string;
excludedUserIds: number[];
}
const emit = defineEmits(['success']);
const allUsers = ref<SystemUserApi.User[]>([]);
const selectedUserIds = ref<number[]>([]);
const loading = ref(false);
const currentData = ref<AddUserModalData | null>(null);
/** 可选项:全量用户 - 已是成员的用户 */
const selectOptions = computed(() => {
const excluded = new Set(currentData.value?.excludedUserIds ?? []);
return allUsers.value
.filter((u) => u.id && !excluded.has(u.id))
.map((u) => ({
value: u.id,
label: `${u.nickname || u.username}${u.username}`,
}));
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
if (!currentData.value?.projectId) return;
if (selectedUserIds.value.length === 0) {
message.warning('请至少选择一个用户');
return;
}
modalApi.lock();
try {
await addProjectUsers({
projectId: currentData.value.projectId,
userIds: selectedUserIds.value,
});
message.success($t('ui.actionMessage.operationSuccess'));
await modalApi.close();
emit('success');
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
allUsers.value = [];
selectedUserIds.value = [];
currentData.value = null;
return;
}
const data = modalApi.getData<AddUserModalData>();
if (!data) return;
currentData.value = data;
loading.value = true;
try {
allUsers.value = await getSimpleUserList();
} finally {
loading.value = false;
}
},
});
</script>
<template>
<Modal
:title="
currentData?.projectName
? `添加用户到项目 - ${currentData.projectName}`
: '添加用户'
"
class="w-[560px]"
>
<Spin :spinning="loading">
<div class="px-4 py-2">
<div class="mb-2 text-sm">选择要加入项目的用户已在项目中的用户会自动过滤</div>
<Select
v-model:value="selectedUserIds"
mode="multiple"
placeholder="请选择用户"
:options="selectOptions"
:filter-option="
(input: string, option: any) =>
option.label.toLowerCase().includes(input.toLowerCase())
"
show-search
allow-clear
class="w-full"
/>
</div>
</Spin>
</Modal>
</template>