From 7e8f2a1328cd3a9e8e80b82816c79a908f8d19fe Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 20:45:27 +0800 Subject: [PATCH] refactor: modal select --- .../select-modal/dept-select-modal.vue | 120 +++---- .../select-modal/user-select-modal.vue | 304 +++++++++--------- 2 files changed, 198 insertions(+), 226 deletions(-) diff --git a/apps/web-antd/src/components/select-modal/dept-select-modal.vue b/apps/web-antd/src/components/select-modal/dept-select-modal.vue index 8fc25665e..d3f469f58 100644 --- a/apps/web-antd/src/components/select-modal/dept-select-modal.vue +++ b/apps/web-antd/src/components/select-modal/dept-select-modal.vue @@ -9,7 +9,7 @@ import { ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { handleTree } from '@vben/utils'; -import { Button, Card, Col, Row, Tree } from 'ant-design-vue'; +import { Card, Col, Row, Tree } from 'ant-design-vue'; import { getSimpleDeptList } from '#/api/system/dept'; @@ -41,24 +41,6 @@ const emit = defineEmits<{ confirm: [deptList: SystemDeptApi.Dept[]]; }>(); -// 对话框配置 -const [Modal, modalApi] = useVbenModal({ - title: props.title, - async onOpenChange(isOpen: boolean) { - if (!isOpen) { - resetData(); - return; - } - modalApi.setState({ loading: true }); - try { - deptData.value = await getSimpleDeptList(); - deptTree.value = handleTree(deptData.value) as DataNode[]; - } finally { - modalApi.setState({ loading: false }); - } - }, - destroyOnClose: true, -}); type checkedKeys = number[] | { checked: number[]; halfChecked: number[] }; // 部门树形结构 const deptTree = ref([]); @@ -67,25 +49,56 @@ const selectedDeptIds = ref([]); // 部门数据 const deptData = ref([]); -/** 打开对话框 */ -const open = async (selectedList?: SystemDeptApi.Dept[]) => { - modalApi.open(); - // // 设置已选择的部门 - if (selectedList?.length) { - const selectedIds = selectedList - .map((dept) => dept.id) - .filter((id): id is number => id !== undefined); - selectedDeptIds.value = props.checkStrictly - ? { - checked: selectedIds, - halfChecked: [], - } - : selectedIds; - } -}; +// 对话框配置 +const [Modal, modalApi] = useVbenModal({ + async onConfirm() { + // 获取选中的部门ID + const selectedIds: number[] = Array.isArray(selectedDeptIds.value) + ? selectedDeptIds.value + : selectedDeptIds.value.checked || []; + const deptArray = deptData.value.filter((dept) => + selectedIds.includes(dept.id!), + ); + emit('confirm', deptArray); + // 关闭并提示 + await modalApi.close(); + }, + async onOpenChange(isOpen: boolean) { + if (!isOpen) { + deptTree.value = []; + selectedDeptIds.value = []; + return; + } + // 加载数据 + const data = modalApi.getData(); + if (!data) { + return; + } + modalApi.lock(); + try { + deptData.value = await getSimpleDeptList(); + deptTree.value = handleTree(deptData.value) as DataNode[]; + // // 设置已选择的部门 + if (data.selectedList?.length) { + const selectedIds = data.selectedList + .map((dept: SystemDeptApi.Dept) => dept.id) + .filter((id: number) => id !== undefined); + selectedDeptIds.value = props.checkStrictly + ? { + checked: selectedIds, + halfChecked: [], + } + : selectedIds; + } + } finally { + modalApi.unlock(); + } + }, + destroyOnClose: true, +}); /** 处理选中状态变化 */ -const handleCheck = () => { +function handleCheck() { if (!props.multiple) { // 单选模式下,只保留最后选择的节点 if (Array.isArray(selectedDeptIds.value)) { @@ -106,37 +119,10 @@ const handleCheck = () => { } } } -}; - -/** 提交选择 */ -const handleConfirm = async () => { - // 获取选中的部门ID - const selectedIds: number[] = Array.isArray(selectedDeptIds.value) - ? selectedDeptIds.value - : selectedDeptIds.value.checked || []; - const deptArray = deptData.value.filter((dept) => - selectedIds.includes(dept.id!), - ); - // 关闭并提示 - await modalApi.close(); - emit('confirm', deptArray); -}; - -const handleCancel = () => { - modalApi.close(); -}; - -/** 重置数据 */ -const resetData = () => { - deptTree.value = []; - selectedDeptIds.value = []; -}; - -/** 提供 open 方法,用于打开对话框 */ -defineExpose({ open }); +} diff --git a/apps/web-antd/src/components/select-modal/user-select-modal.vue b/apps/web-antd/src/components/select-modal/user-select-modal.vue index e55cca14c..23fce5aec 100644 --- a/apps/web-antd/src/components/select-modal/user-select-modal.vue +++ b/apps/web-antd/src/components/select-modal/user-select-modal.vue @@ -17,7 +17,6 @@ import { message, Pagination, Row, - Spin, Transfer, Tree, } from 'ant-design-vue'; @@ -66,16 +65,66 @@ const expandedKeys = ref([]); const selectedDeptId = ref(); const deptSearchKeys = ref(''); -// 加载状态 -const loading = ref(false); - // 用户数据管理 const userList = ref([]); // 存储所有已知用户 const selectedUserIds = ref([]); +// 弹窗配置 +const [Modal, modalApi] = useVbenModal({ + onCancel: handleCancel, + onClosed: handleClosed, + async onOpenChange(isOpen: boolean) { + if (!isOpen) { + resetData(); + return; + } + // 加载数据 + const data = modalApi.getData(); + if (!data) { + return; + } + modalApi.lock(); + try { + // 加载部门数据 + const deptData = await getSimpleDeptList(); + deptList.value = deptData; + const treeData = handleTree(deptData); + deptTree.value = treeData.map((node) => processDeptNode(node)); + expandedKeys.value = deptTree.value.map((node) => node.key); + + // 加载初始用户数据 + await loadUserData(1, leftListState.value.pagination.pageSize); + + // 设置已选用户 + if (data.userIds?.length) { + selectedUserIds.value = data.userIds.map(String); + // 加载已选用户的完整信息 TODO 目前接口暂不支持 多个用户ID 查询, 需要后端支持 + const { list } = await getUserPage({ + pageNo: 1, + pageSize: 100, // 临时使用固定值确保能加载所有已选用户 + userIds: data.userIds, + }); + // 使用 Map 来去重,以用户 ID 为 key + const userMap = new Map(userList.value.map((user) => [user.id, user])); + list.forEach((user) => { + if (!userMap.has(user.id)) { + userMap.set(user.id, user); + } + }); + userList.value = [...userMap.values()]; + updateRightListData(); + } + + modalApi.open(); + } finally { + modalApi.unlock(); + } + }, + destroyOnClose: true, +}); + // 左侧列表状态 const leftListState = ref({ - loading: false, searchValue: '', dataSource: [] as SystemUserApi.User[], pagination: { @@ -145,8 +194,7 @@ const filteredDeptTree = computed(() => { }); // 加载用户数据 -const loadUserData = async (pageNo: number, pageSize: number) => { - leftListState.value.loading = true; +async function loadUserData(pageNo: number, pageSize: number) { try { const { list, total } = await getUserPage({ pageNo, @@ -167,13 +215,11 @@ const loadUserData = async (pageNo: number, pageSize: number) => { if (newUsers.length > 0) { userList.value.push(...newUsers); } - } finally { - leftListState.value.loading = false; - } -}; + } finally {} +} // 更新右侧列表数据 -const updateRightListData = () => { +function updateRightListData() { // 使用 Set 来去重选中的用户ID const uniqueSelectedIds = new Set(selectedUserIds.value); @@ -202,22 +248,22 @@ const updateRightListData = () => { const endIndex = startIndex + pageSize; rightListState.value.dataSource = filteredUsers.slice(startIndex, endIndex); -}; +} // 处理左侧分页变化 -const handleLeftPaginationChange = async (page: number, pageSize: number) => { +async function handleLeftPaginationChange(page: number, pageSize: number) { await loadUserData(page, pageSize); -}; +} // 处理右侧分页变化 -const handleRightPaginationChange = (page: number, pageSize: number) => { +function handleRightPaginationChange(page: number, pageSize: number) { rightListState.value.pagination.current = page; rightListState.value.pagination.pageSize = pageSize; updateRightListData(); -}; +} // 处理用户搜索 -const handleUserSearch = async (direction: string, value: string) => { +async function handleUserSearch(direction: string, value: string) { if (direction === 'left') { leftListState.value.searchValue = value; leftListState.value.pagination.current = 1; @@ -227,18 +273,18 @@ const handleUserSearch = async (direction: string, value: string) => { rightListState.value.pagination.current = 1; updateRightListData(); } -}; +} // 处理用户选择变化 -const handleUserChange = (targetKeys: string[]) => { +function handleUserChange(targetKeys: string[]) { // 使用 Set 来去重选中的用户ID selectedUserIds.value = [...new Set(targetKeys)]; emit('update:value', selectedUserIds.value.map(Number)); updateRightListData(); -}; +} // 重置数据 -const resetData = () => { +function resetData() { userList.value = []; selectedUserIds.value = []; @@ -249,7 +295,6 @@ const resetData = () => { selectedUserIds.value = []; leftListState.value = { - loading: false, searchValue: '', dataSource: [], pagination: { @@ -268,61 +313,20 @@ const resetData = () => { total: 0, }, }; -}; - -// 打开弹窗 -const open = async (userIds: string[]) => { - resetData(); - loading.value = true; - try { - // 加载部门数据 - const deptData = await getSimpleDeptList(); - deptList.value = deptData; - const treeData = handleTree(deptData); - deptTree.value = treeData.map((node) => processDeptNode(node)); - expandedKeys.value = deptTree.value.map((node) => node.key); - - // 加载初始用户数据 - await loadUserData(1, leftListState.value.pagination.pageSize); - - // 设置已选用户 - if (userIds?.length) { - selectedUserIds.value = userIds.map(String); - // 加载已选用户的完整信息 TODO 目前接口暂不支持 多个用户ID 查询, 需要后端支持 - const { list } = await getUserPage({ - pageNo: 1, - pageSize: 100, // 临时使用固定值确保能加载所有已选用户 - userIds, - }); - // 使用 Map 来去重,以用户 ID 为 key - const userMap = new Map(userList.value.map((user) => [user.id, user])); - list.forEach((user) => { - if (!userMap.has(user.id)) { - userMap.set(user.id, user); - } - }); - userList.value = [...userMap.values()]; - updateRightListData(); - } - - modalApi.open(); - } finally { - loading.value = false; - } -}; +} // TODO 后端接口目前仅支持 username 检索, 筛选条件需要跟后端请求参数保持一致。 -const filterOption = (inputValue: string, option: any) => { +function filterOption(inputValue: string, option: any) { return option.username.toLowerCase().includes(inputValue.toLowerCase()); -}; +} // 处理部门树展开/折叠 -const handleExpand = (keys: Key[]) => { +function handleExpand(keys: Key[]) { expandedKeys.value = keys; -}; +} // 处理部门搜索 -const handleDeptSearch = (value: string) => { +function handleDeptSearch(value: string) { deptSearchKeys.value = value; // 如果有搜索结果,自动展开所有节点 @@ -342,10 +346,10 @@ const handleDeptSearch = (value: string) => { // 清空搜索时,只展开第一级节点 expandedKeys.value = deptTree.value.map((node) => node.key); } -}; +} // 处理部门选择 -const handleDeptSelect = async (selectedKeys: Key[], _info: any) => { +async function handleDeptSelect(selectedKeys: Key[], _info: any) { // 更新选中的部门ID const newDeptId = selectedKeys.length > 0 ? Number(selectedKeys[0]) : undefined; @@ -356,10 +360,10 @@ const handleDeptSelect = async (selectedKeys: Key[], _info: any) => { const { pageSize } = leftListState.value.pagination; leftListState.value.pagination.current = 1; await loadUserData(1, pageSize); -}; +} // 确认选择 -const handleConfirm = () => { +function handleConfirm() { if (selectedUserIds.value.length === 0) { message.warning('请选择用户'); return; @@ -371,115 +375,101 @@ const handleConfirm = () => { ), ); modalApi.close(); -}; +} // 取消选择 -const handleCancel = () => { +function handleCancel() { emit('cancel'); modalApi.close(); // 确保在动画结束后再重置数据 setTimeout(() => { resetData(); }, 300); -}; +} // 关闭弹窗 -const handleClosed = () => { +function handleClosed() { emit('closed'); resetData(); -}; - -// 弹窗配置 -const [ModalComponent, modalApi] = useVbenModal({ - title: props.title, - onCancel: handleCancel, - onClosed: handleClosed, - destroyOnClose: true, -}); +} // 递归处理部门树节点 -const processDeptNode = (node: any): DeptTreeNode => { +function processDeptNode(node: any): DeptTreeNode { return { key: String(node.id), title: `${node.name} (${node.id})`, name: node.name, children: node.children?.map((child: any) => processDeptNode(child)), }; -}; - -defineExpose({ - open, -}); +}