From 47506bd0b94606fe947e834f3af190cbccd61870 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 8 Aug 2026 13:00:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(company):=20=E6=96=B0=E5=A2=9E=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E5=B9=B6=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=8F=9C=E5=8D=95=E7=BC=96=E8=BE=91=E5=99=A8=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 company/permission 权限管理页 - 菜单编辑器公司选择改用 EntityPicker - CrudTable 卡片查看按钮图标化,公司卡片补充父级/租户字段 --- src/components/CrudTable.vue | 8 +- src/views/company/Company.vue | 2 +- src/views/company/Permission.vue | 50 ++++++++++++ src/views/system/Menu.vue | 133 ++++++++----------------------- 4 files changed, 89 insertions(+), 104 deletions(-) create mode 100644 src/views/company/Permission.vue diff --git a/src/components/CrudTable.vue b/src/components/CrudTable.vue index 7dd8d88..9b276c9 100644 --- a/src/components/CrudTable.vue +++ b/src/components/CrudTable.vue @@ -689,7 +689,9 @@ function isCardSelected(row: Record): boolean { {{ node.children.length }} 子级 - 查看 + + 查看 +
@@ -763,7 +765,9 @@ function isCardSelected(row: Record): boolean {
{{ (row as Record)[cardTitleField] || '-' }} - 查看 + + 查看 +
diff --git a/src/views/company/Company.vue b/src/views/company/Company.vue index 8758f42..a8fe7cf 100644 --- a/src/views/company/Company.vue +++ b/src/views/company/Company.vue @@ -104,7 +104,7 @@ async function handleIconChange(event: Event): Promise { card-icon-field="CompanyIcon" card-tree-parent-field="ParentCompanyUUID" :default-page-size="1000" - :card-fields="['CompanyShort', 'CompanyStatus', 'CreateTime']" + :card-fields="['CompanyShort', 'CompanyStatus', 'ParentCompanyUUID', 'TenantUUID', 'CreateTime']" :card-value-maps="{ CompanyStatus: { 0: '禁用', 1: '启用' }, }" diff --git a/src/views/company/Permission.vue b/src/views/company/Permission.vue new file mode 100644 index 0000000..72e0eb9 --- /dev/null +++ b/src/views/company/Permission.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/views/system/Menu.vue b/src/views/system/Menu.vue index 2124333..1326145 100644 --- a/src/views/system/Menu.vue +++ b/src/views/system/Menu.vue @@ -31,12 +31,14 @@ * 注意:api/menu.ts 的 saveMenu/deleteMenu 参数与原生后端字段不一致(Id vs MenuUUID、JSON vs 表单编码), * 故此处直接使用 request 底层 post 并显式设置表单编码,以保持与原生后端契约一致。 */ -import { ref, computed, onMounted, onBeforeUnmount } from 'vue' +import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import request from '@/api/request' import { setPageTitle } from '@/utils/serverScope' import { useUserStore } from '@/stores/user' import AdminBadge from '@/components/AdminBadge.vue' +import EntityPicker from '@/components/EntityPicker.vue' +import { entityPickerPresets } from '@/api/entityPickerPresets' setPageTitle('菜单编辑器') @@ -64,13 +66,6 @@ interface MenuNode { children?: MenuNode[] } -/** 公司搜索结果项 */ -interface CompanyItem { - CompanyUUID: string - CompanyName: string - CompanyShort?: string -} - /** 标签页(每公司一个) */ interface MenuTab { companyUUID: string @@ -85,11 +80,9 @@ const tabs = ref([]) const activeTabIndex = ref(-1) const activeTab = computed(() => (activeTabIndex.value >= 0 ? tabs.value[activeTabIndex.value] : null)) -/** 公司搜索 */ -const companySearchKeyword = ref('') -const companySearchResults = ref([]) -const companySearchVisible = ref(false) -let companySearchTimer: ReturnType | null = null +/** 公司实体选择框(EntityPicker):选中公司 UUID 与回显标签 */ +const companyPickerValue = ref('') +const companyPickerLabel = ref('') /** 右键菜单 */ const ctxMenuVisible = ref(false) @@ -167,48 +160,30 @@ function buildSubtree(flatMenus: MenuNode[], rootUUID: string): MenuNode | null return result } -/** 公司搜索(防抖 300ms) */ -function onCompanySearchInput(): void { - if (companySearchTimer) clearTimeout(companySearchTimer) - const kw = companySearchKeyword.value.trim() - if (!kw) { - companySearchResults.value = [] - companySearchVisible.value = false - return - } - companySearchTimer = setTimeout(async () => { - try { - const data = await request.get('/API/FormAPI/CompanySearch', { - params: { keyword: kw, limit: 10 }, - }) - companySearchResults.value = Array.isArray(data) ? data : [] - companySearchVisible.value = companySearchResults.value.length > 0 - } catch { - companySearchResults.value = [] - companySearchVisible.value = false - } - }, 300) -} - -/** 选择公司:打开或切换标签页 */ -function selectCompany(c: CompanyItem): void { - companySearchKeyword.value = c.CompanyName - companySearchVisible.value = false - const existing = tabs.value.findIndex((t) => t.companyUUID === c.CompanyUUID) +/** 打开或切换公司标签页(EntityPicker 选择触发) */ +function openCompanyTab(uuid: string, name: string): void { + if (!uuid) return + const existing = tabs.value.findIndex((t) => t.companyUUID === uuid) if (existing >= 0) { switchTab(existing) - } else { - tabs.value.push({ - companyUUID: c.CompanyUUID, - companyName: c.CompanyName, - menus: [], - selectedUUID: null, - }) - switchTab(tabs.value.length - 1) - loadMenus() + return } + tabs.value.push({ + companyUUID: uuid, + companyName: name || uuid, + menus: [], + selectedUUID: null, + }) + switchTab(tabs.value.length - 1) + void loadMenus() } +/** 公司实体选择框选中 → 打开/切换公司标签页 */ +watch(companyPickerValue, (uuid) => { + if (!uuid) return + openCompanyTab(uuid, companyPickerLabel.value || uuid) +}) + /** 切换标签页 */ function switchTab(index: number): void { if (index < 0 || index >= tabs.value.length) return @@ -754,11 +729,10 @@ onBeforeUnmount(() => { document.removeEventListener('click', onDocClick) }) -/** 文档点击:关闭右键菜单 / 公司搜索下拉 */ +/** 文档点击:关闭右键菜单 */ function onDocClick(e: MouseEvent): void { const target = e.target as HTMLElement if (!target.closest('.menu-ctx-panel')) ctxMenuVisible.value = false - if (!target.closest('.company-search-wrap')) companySearchVisible.value = false } @@ -783,24 +757,14 @@ function onDocClick(e: MouseEvent): void { 菜单编辑器
- -
-
- {{ c.CompanyName }} - {{ c.CompanyShort || '' }} -
-
新增根菜单 @@ -1010,40 +974,7 @@ function onDocClick(e: MouseEvent): void { } .company-search-wrap { - position: relative; - - .company-search-dropdown { - position: absolute; - top: 100%; - left: 0; - right: 0; - background: var(--el-bg-color); - border: 1px solid var(--el-border-color); - border-radius: 6px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - z-index: 100; - max-height: 240px; - overflow-y: auto; - - .company-search-item { - padding: 8px 12px; - cursor: pointer; - font-size: 13px; - - &:hover { - background: var(--el-fill-color-light); - } - - strong { - display: block; - } - - span { - font-size: 11px; - color: var(--el-text-color-secondary); - } - } - } + // EntityPicker 公司实体选择框容器(头部工具区布局占位) } .menu-body {