smart-admin-web 10.20.1 → 10.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.d.ts +19 -1
- package/dist/api/index.js +85 -66
- package/dist/api/index.js.map +1 -1
- package/dist/api/schema.d.ts +448 -1
- package/dist/assets/icons/ph-subset.json.js +1 -1
- package/dist/components/FormContainer/index.vue.js +87 -75
- package/dist/components/FormContainer/index.vue.js.map +1 -1
- package/dist/components/UserPicker/UserAvatar.vue.d.ts +8 -0
- package/dist/components/UserPicker/UserAvatar.vue.js +8 -0
- package/dist/components/UserPicker/UserAvatar.vue.js.map +1 -0
- package/dist/components/UserPicker/UserAvatar2.vue.js +38 -0
- package/dist/components/UserPicker/UserAvatar2.vue.js.map +1 -0
- package/dist/components/UserPicker/index.vue.js +1 -1
- package/dist/components/UserPicker/index2.vue.js +328 -186
- package/dist/components/UserPicker/index2.vue.js.map +1 -1
- package/dist/composables/authMenuRoute.d.ts +10 -0
- package/dist/composables/authMenuRoute.js +39 -23
- package/dist/composables/authMenuRoute.js.map +1 -1
- package/dist/composables/useModule.d.ts +3 -1
- package/dist/composables/useModule.js +44 -32
- package/dist/composables/useModule.js.map +1 -1
- package/dist/index.js +246 -242
- package/dist/layouts/components/SideNav.vue.js +2 -2
- package/dist/layouts/components/SideNav2.vue.js.map +1 -1
- package/dist/lib/runtime.js +1 -1
- package/dist/lib/watermark.d.ts +1 -1
- package/dist/locales/en-US.d.ts +68 -3
- package/dist/locales/en-US.js +74 -8
- package/dist/locales/en-US.js.map +1 -1
- package/dist/locales/kernelMenuTitles.js +1 -0
- package/dist/locales/kernelMenuTitles.js.map +1 -1
- package/dist/locales/zh-CN.d.ts +68 -3
- package/dist/locales/zh-CN.js +74 -8
- package/dist/locales/zh-CN.js.map +1 -1
- package/dist/router/viewRegistry.js +6 -6
- package/dist/router/viewRegistry.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/theme/naive-theme.js +80 -77
- package/dist/theme/naive-theme.js.map +1 -1
- package/dist/types/api.d.ts +91 -0
- package/dist/types/api.js +17 -15
- package/dist/types/api.js.map +1 -1
- package/dist/utils/shake.d.ts +1 -0
- package/dist/utils/shake.js +30 -0
- package/dist/utils/shake.js.map +1 -0
- package/dist/utils/tree.d.ts +15 -0
- package/dist/utils/tree.js +43 -29
- package/dist/utils/tree.js.map +1 -1
- package/dist/utils/treeCard.d.ts +6 -0
- package/dist/utils/treeCard.js +9 -0
- package/dist/utils/treeCard.js.map +1 -0
- package/dist/views/error/404.vue.js +2 -2
- package/dist/views/error/4042.vue.js +81 -24
- package/dist/views/error/4042.vue.js.map +1 -1
- package/dist/views/login/LoginForm.vue.js +2 -2
- package/dist/views/login/LoginForm2.vue.js +135 -135
- package/dist/views/login/LoginForm2.vue.js.map +1 -1
- package/dist/views/login/components/LoginDesk.vue.js +2 -2
- package/dist/views/login/components/LoginDesk2.vue.js +59 -47
- package/dist/views/login/components/LoginDesk2.vue.js.map +1 -1
- package/dist/views/login/components/LoginHeroPanel.vue.js +2 -2
- package/dist/views/login/components/LoginHeroPanel2.vue.js.map +1 -1
- package/dist/views/personal/notice2.vue.js +1 -1
- package/dist/views/personal/notice2.vue.js.map +1 -1
- package/dist/views/personal/sessions.vue.js +2 -2
- package/dist/views/personal/sessions2.vue.js +41 -40
- package/dist/views/personal/sessions2.vue.js.map +1 -1
- package/dist/views/system/ai-usage/index.vue.js +3 -3
- package/dist/views/system/ai-usage/index2.vue.js +13 -12
- package/dist/views/system/ai-usage/index2.vue.js.map +1 -1
- package/dist/views/system/config/components/AdvancedSection.vue.js +3 -3
- package/dist/views/system/config/components/AdvancedSection2.vue.js +2 -1
- package/dist/views/system/config/components/AdvancedSection2.vue.js.map +1 -1
- package/dist/views/system/dict/index.vue.js +2 -2
- package/dist/views/system/dict/index2.vue.js +12 -1
- package/dist/views/system/dict/index2.vue.js.map +1 -1
- package/dist/views/system/file/index2.vue.js +1 -0
- package/dist/views/system/file/index2.vue.js.map +1 -1
- package/dist/views/system/job/index.vue.js +3 -3
- package/dist/views/system/job/index2.vue.js +182 -181
- package/dist/views/system/job/index2.vue.js.map +1 -1
- package/dist/views/system/job-log/components/AttemptTable2.vue.js +8 -3
- package/dist/views/system/job-log/components/AttemptTable2.vue.js.map +1 -1
- package/dist/views/system/job-log/index.vue.js +1 -1
- package/dist/views/system/job-log/index2.vue.js +5 -5
- package/dist/views/system/job-log/index2.vue.js.map +1 -1
- package/dist/views/system/job-monitor/index.vue.js +2 -2
- package/dist/views/system/job-monitor/index2.vue.js +46 -46
- package/dist/views/system/job-monitor/index2.vue.js.map +1 -1
- package/dist/views/system/log/exception/index.vue.js +2 -2
- package/dist/views/system/log/exception/index2.vue.js +8 -5
- package/dist/views/system/log/exception/index2.vue.js.map +1 -1
- package/dist/views/system/log/login/index2.vue.js +21 -12
- package/dist/views/system/log/login/index2.vue.js.map +1 -1
- package/dist/views/system/log/op/index.vue.js +2 -2
- package/dist/views/system/log/op/index2.vue.js +50 -40
- package/dist/views/system/log/op/index2.vue.js.map +1 -1
- package/dist/views/system/menu/components/ButtonManager.vue.js +2 -2
- package/dist/views/system/menu/components/ButtonManager2.vue.js +45 -36
- package/dist/views/system/menu/components/ButtonManager2.vue.js.map +1 -1
- package/dist/views/system/menu/index.vue.js +1 -1
- package/dist/views/system/menu/index2.vue.js +162 -151
- package/dist/views/system/menu/index2.vue.js.map +1 -1
- package/dist/views/system/module/index.vue.js +1 -1
- package/dist/views/system/module/index2.vue.js +171 -117
- package/dist/views/system/module/index2.vue.js.map +1 -1
- package/dist/views/system/notice/index.vue.js +3 -3
- package/dist/views/system/notice/index2.vue.js +1 -0
- package/dist/views/system/notice/index2.vue.js.map +1 -1
- package/dist/views/system/org/index.vue.js +2 -2
- package/dist/views/system/org/index2.vue.js +204 -195
- package/dist/views/system/org/index2.vue.js.map +1 -1
- package/dist/views/system/position/index.vue.js +2 -2
- package/dist/views/system/position/index2.vue.js +57 -48
- package/dist/views/system/position/index2.vue.js.map +1 -1
- package/dist/views/system/recycle/index.vue.js +1 -1
- package/dist/views/system/recycle/index2.vue.js +3 -1
- package/dist/views/system/recycle/index2.vue.js.map +1 -1
- package/dist/views/system/role/components/GrantMenuSheet.vue.d.ts +2 -0
- package/dist/views/system/role/components/GrantMenuSheet.vue.js +2 -2
- package/dist/views/system/role/components/GrantMenuSheet2.vue.js +54 -49
- package/dist/views/system/role/components/GrantMenuSheet2.vue.js.map +1 -1
- package/dist/views/system/role/components/GrantMenuTable.vue.js +1 -1
- package/dist/views/system/role/components/GrantMenuTable2.vue.js +159 -195
- package/dist/views/system/role/components/GrantMenuTable2.vue.js.map +1 -1
- package/dist/views/system/role/components/grantMenuGroups.d.ts +54 -0
- package/dist/views/system/role/components/grantMenuGroups.js +116 -76
- package/dist/views/system/role/components/grantMenuGroups.js.map +1 -1
- package/dist/views/system/role/index.vue.js +2 -2
- package/dist/views/system/role/index2.vue.js +152 -145
- package/dist/views/system/role/index2.vue.js.map +1 -1
- package/dist/views/system/session/index2.vue.js +39 -35
- package/dist/views/system/session/index2.vue.js.map +1 -1
- package/dist/views/system/user/components/UserGrantEffectiveTable.vue.d.ts +10 -0
- package/dist/views/system/user/components/UserGrantEffectiveTable.vue.js +5 -0
- package/dist/views/system/user/components/UserGrantEffectiveTable.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantEffectiveTable2.vue.js +96 -0
- package/dist/views/system/user/components/UserGrantEffectiveTable2.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantMenuSheet.vue.d.ts +13 -0
- package/dist/views/system/user/components/UserGrantMenuSheet.vue.js +8 -0
- package/dist/views/system/user/components/UserGrantMenuSheet.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantMenuSheet2.vue.js +216 -0
- package/dist/views/system/user/components/UserGrantMenuSheet2.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantMenuTable.vue.d.ts +13 -0
- package/dist/views/system/user/components/UserGrantMenuTable.vue.js +8 -0
- package/dist/views/system/user/components/UserGrantMenuTable.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantMenuTable2.vue.js +186 -0
- package/dist/views/system/user/components/UserGrantMenuTable2.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantNodeRow.vue.d.ts +31 -0
- package/dist/views/system/user/components/UserGrantNodeRow.vue.js +8 -0
- package/dist/views/system/user/components/UserGrantNodeRow.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantNodeRow2.vue.js +171 -0
- package/dist/views/system/user/components/UserGrantNodeRow2.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.d.ts +21 -0
- package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.js +5 -0
- package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.js.map +1 -0
- package/dist/views/system/user/components/UserGrantOverviewDrawer2.vue.js +178 -0
- package/dist/views/system/user/components/UserGrantOverviewDrawer2.vue.js.map +1 -0
- package/dist/views/system/user/components/userGrantState.d.ts +64 -0
- package/dist/views/system/user/components/userGrantState.js +117 -0
- package/dist/views/system/user/components/userGrantState.js.map +1 -0
- package/dist/views/system/user/index.vue.js +2 -2
- package/dist/views/system/user/index2.vue.js +369 -295
- package/dist/views/system/user/index2.vue.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index2.vue.js","sources":["../../../src/components/UserPicker/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, h, onMounted, reactive, ref, unref } from 'vue'\nimport { NButton, NEmpty, NInput, NTab, NTabs, NTree, NTreeSelect, useMessage } from 'naive-ui'\nimport { SmartTable, type SmartTableColumn, type SmartTableInst } from 'smart-naive-table'\nimport { useI18n } from 'vue-i18n'\nimport { useWindowSize } from '@vueuse/core'\nimport AppIcon from '#/components/AppIcon.vue'\nimport FormContainer from '#/components/FormContainer/index.vue'\nimport { useShellBreakpoint } from '#/composables/useShellBreakpoint'\nimport { orgApi, userApi } from '#/api'\nimport { buildTree } from '#/utils/tree'\nimport { translateError } from '#/utils/error'\nimport { SEARCH_ACTIONS } from '#/utils/tableFilter'\nimport type { SysOrg, UserItem } from '#/types/api'\n\nconst props = defineProps<{ excludeIds?: number[] }>()\nconst emit = defineEmits<{ (e: 'confirm', ids: number[]): void }>()\n\nconst { t } = useI18n()\nconst message = useMessage()\n\nconst show = ref(false)\nconst tableRef = ref<SmartTableInst<UserItem>>()\n\n// 屏幕形态:\n// 宽 / 中档 = 居中弹窗里的三栏(机构树 | 可选用户 | 已选);\n// 窄档(壳层 < 600)= 底部抽屉,三栏收成「可选用户 / 已选」两页,机构树换成页内的树选择;\n// 内容区 < 900 但还不到窄档时仍是弹窗,三栏同样收成两页(1100 宽的三栏在这里放不下)。\n// 三栏选择器在抽屉里太局促,所以宽 / 中档固定用弹窗,不跟随「表单形态」全局偏好。\nconst { bp } = useShellBreakpoint()\nconst { width: winW } = useWindowSize()\nconst compact = computed(() => bp.value === 'narrow' || winW.value < 900)\nconst variant = computed(() => (bp.value === 'narrow' ? 'drawer' : 'modal'))\nconst tab = ref<'users' | 'selected'>('users')\nconst btnSize = computed(() => (compact.value ? 'large' : 'small'))\n\n// ── 机构树(左面板) ──\nconst orgFlat = ref<SysOrg[]>([])\nconst orgTree = computed(() => {\n const all = { id: 0, parentId: -1, name: t('common.all'), children: buildTree(orgFlat.value) }\n return [all]\n})\nconst selectedOrgKey = ref<number | null>(null)\nconst orgId = computed(() =>\n selectedOrgKey.value && selectedOrgKey.value !== 0 ? selectedOrgKey.value : undefined,\n)\n\nonMounted(async () => {\n try {\n orgFlat.value = await orgApi.list()\n } catch {\n /* 静默 */\n }\n})\n\nfunction onOrgSelect(keys: Array<string | number>) {\n selectedOrgKey.value = (keys[0] as number) ?? null\n tableRef.value?.refresh()\n}\n// 两页形态下机构树换成树选择:没有「再点一次取消」,选「全部」(id 0)即不限机构。\nfunction onOrgPick(key: number | null) {\n selectedOrgKey.value = key ?? null\n tableRef.value?.refresh()\n}\n\n// ── 可选用户表(中间面板) ──\nconst checkedKeys = ref<(string | number)[]>([])\nconst excludeSet = computed(() => new Set(props.excludeIds ?? []))\n\nconst fetcher = (params: { page: number; pageSize: number; account?: string }) =>\n userApi.page({\n page: params.page,\n pageSize: params.pageSize,\n account: params.account,\n orgId: orgId.value,\n })\n\nconst columns: SmartTableColumn<UserItem>[] = [\n { type: 'selection' },\n { key: 'name', title: () => t('user.name'), width: 100 },\n {\n key: 'account',\n title: () => t('user.account'),\n width: 120,\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n {\n key: 'op',\n title: () => t('common.operation'),\n width: 70,\n render: r => {\n const already = selectedSet.value.has(r.id) || excludeSet.value.has(r.id)\n return h(\n NButton,\n {\n size: 'tiny',\n type: 'primary',\n quaternary: true,\n disabled: already,\n onClick: () => addUsers([r]),\n },\n () => (already ? t('userPicker.added') : t('userPicker.add')),\n )\n },\n },\n]\n\n// ── 已选用户(右面板) ──\nconst selected = reactive<UserItem[]>([])\nconst selectedSet = computed(() => new Set(selected.map(u => u.id)))\nconst selectedSearch = ref('')\nconst filteredSelected = computed(() => {\n const q = selectedSearch.value.trim().toLowerCase()\n if (!q) return selected\n return selected.filter(\n u => u.name.toLowerCase().includes(q) || u.account.toLowerCase().includes(q),\n )\n})\n\nfunction addUsers(users: UserItem[]) {\n for (const u of users) {\n if (!selectedSet.value.has(u.id) && !excludeSet.value.has(u.id)) {\n selected.push(u)\n }\n }\n}\n\nfunction addChecked() {\n // SmartTable 只 expose rows,别改用未公开的 tableData —— 恒为 undefined。\n // unref 兼容类型声明的 Ref 与运行时 expose 解包两种形态。\n const rows = (unref(tableRef.value?.rows) ?? []).filter(r => checkedKeys.value.includes(r.id))\n addUsers(rows)\n checkedKeys.value = []\n}\n\nfunction removeUser(id: number) {\n const idx = selected.findIndex(u => u.id === id)\n if (idx >= 0) selected.splice(idx, 1)\n}\n\nfunction clearSelected() {\n selected.splice(0, selected.length)\n}\n\n// ── 公共 API ──\nasync function open(ids?: number[]) {\n selected.splice(0, selected.length)\n checkedKeys.value = []\n selectedSearch.value = ''\n selectedOrgKey.value = null\n tab.value = 'users'\n show.value = true\n\n if (ids?.length) {\n try {\n // ponytail: 拉一页,靠 id 过滤。取 200 是为了在应用把 Api:MaxPageSize 调小时也不会被后端回 48001。\n // 用户超过 200 个时,排在后面的 id 解析不出名字,走下面那条退回显示 id 的分支;\n // 真要支持更大规模的用户名单,再改成分页搜索。\n const { items } = await userApi.page({ page: 1, pageSize: 200 })\n const all = items.filter(u => ids.includes(u.id))\n // 没拉到的 id(翻页之外),退回只显示 id\n const found = new Set(all.map(u => u.id))\n for (const u of all) selected.push(u)\n for (const id of ids) {\n if (!found.has(id)) selected.push({ id, account: String(id), name: String(id) } as UserItem)\n }\n } catch {\n for (const id of ids) selected.push({ id, account: String(id), name: String(id) } as UserItem)\n }\n }\n}\n\nasync function handleConfirm() {\n const ids = selected.map(u => u.id)\n emit('confirm', ids)\n}\n\ndefineExpose({ open })\n</script>\n\n<template>\n <FormContainer\n v-model:show=\"show\"\n :variant=\"variant\"\n :title=\"t('userPicker.title')\"\n :width=\"1100\"\n :on-confirm=\"handleConfirm\"\n :confirm-text=\"t('common.confirm')\"\n >\n <n-tabs v-if=\"compact\" v-model:value=\"tab\" type=\"segment\" animated class=\"picker-tabs\">\n <n-tab name=\"users\">{{ t('userPicker.available') }}</n-tab>\n <n-tab name=\"selected\">{{ t('userPicker.selected') }}({{ selected.length }})</n-tab>\n </n-tabs>\n <div class=\"user-picker\" :class=\"{ 'is-compact': compact }\">\n <!-- 左面板:机构树(两页形态下换成可选用户页顶部的树选择) -->\n <div v-if=\"!compact\" class=\"picker-tree\">\n <div class=\"panel-header\">{{ t('org.title') }}</div>\n <n-tree\n :data=\"orgTree\"\n key-field=\"id\"\n label-field=\"name\"\n children-field=\"children\"\n selectable\n block-line\n default-expand-all\n :selected-keys=\"selectedOrgKey != null ? [selectedOrgKey] : []\"\n @update:selected-keys=\"onOrgSelect\"\n />\n </div>\n\n <!-- 中间面板:可选用户 -->\n <div v-show=\"!compact || tab === 'users'\" class=\"picker-table\">\n <n-tree-select\n v-if=\"compact\"\n :value=\"selectedOrgKey ?? 0\"\n :options=\"orgTree\"\n key-field=\"id\"\n label-field=\"name\"\n children-field=\"children\"\n default-expand-all\n size=\"large\"\n class=\"picker-org-select\"\n @update:value=\"onOrgPick\"\n />\n <div class=\"panel-header\">\n <span>{{ t('userPicker.available') }}</span>\n <n-button\n :size=\"btnSize\"\n type=\"primary\"\n :disabled=\"checkedKeys.length === 0\"\n @click=\"addChecked\"\n >\n {{ t('userPicker.addChecked') }}({{ checkedKeys.length }})\n </n-button>\n </div>\n <SmartTable\n :default-page-size=\"100\"\n ref=\"tableRef\"\n :columns=\"columns\"\n :fetcher=\"fetcher\"\n :search=\"{ container: 'table' }\"\n :pagination=\"{ pageSize: 10 }\"\n size=\"small\"\n storage-key=\"user-picker\"\n :tool-button=\"false\"\n :checked-row-keys=\"checkedKeys\"\n @update:checked-row-keys=\"(keys: (string | number)[]) => (checkedKeys = keys)\"\n @error=\"e => message.error(translateError(e))\"\n />\n </div>\n\n <!-- 右面板:已选用户 -->\n <div v-show=\"!compact || tab === 'selected'\" class=\"picker-selected\">\n <div class=\"panel-header\">\n <span>{{ t('userPicker.selected') }}({{ selected.length }})</span>\n <n-button\n :size=\"btnSize\"\n quaternary\n type=\"error\"\n :disabled=\"selected.length === 0\"\n @click=\"clearSelected\"\n >\n {{ t('userPicker.clear') }}\n </n-button>\n </div>\n <n-input\n v-model:value=\"selectedSearch\"\n :size=\"compact ? 'large' : 'small'\"\n clearable\n :placeholder=\"t('common.search')\"\n style=\"margin-bottom: 8px\"\n />\n <div class=\"selected-list\">\n <div v-for=\"u in filteredSelected\" :key=\"u.id\" class=\"selected-item\">\n <span class=\"selected-name\">\n {{ u.name }}\n <span class=\"selected-account\">({{ u.account }})</span>\n </span>\n <n-button :size=\"btnSize\" quaternary type=\"error\" @click=\"removeUser(u.id)\">\n <template #icon><AppIcon icon=\"ph:x\" :size=\"14\" /></template>\n </n-button>\n </div>\n <n-empty\n v-if=\"filteredSelected.length === 0\"\n :description=\"t('common.noData')\"\n style=\"padding: 24px 0\"\n />\n </div>\n </div>\n </div>\n </FormContainer>\n</template>\n\n<style scoped>\n/* 选择器的尺寸、分隔线、字号。 */\n.user-picker {\n display: flex;\n gap: 12px;\n min-height: min(440px, calc(100vh - 250px));\n}\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 34px;\n font-size: 13.5px;\n font-weight: 600;\n padding: 0 0 8px;\n border-bottom: 1px solid var(--hairline);\n margin-bottom: 8px;\n}\n.picker-tree {\n width: 200px;\n flex-shrink: 0;\n overflow: auto;\n border-right: 1px solid var(--hairline);\n padding-right: 12px;\n}\n.picker-table {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n}\n.picker-org-select {\n margin-bottom: 8px;\n}\n.picker-selected {\n width: 220px;\n flex-shrink: 0;\n border-left: 1px solid var(--hairline);\n padding-left: 12px;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.selected-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n max-height: 380px;\n}\n.selected-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n padding: 2px 0;\n font-size: 13.5px;\n border-bottom: 1px solid var(--hairline);\n}\n.selected-item:last-child {\n border-bottom: none;\n}\n.selected-name {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.selected-account {\n color: var(--text-3);\n margin-left: 2px;\n}\n\n/* 两页形态:分段页签在上,当前页占满整行;已选页不再要左侧分隔线,条目行高放大到可点 */\n.picker-tabs {\n margin-bottom: 12px;\n}\n.user-picker.is-compact {\n min-height: 0;\n}\n.is-compact .picker-selected {\n width: auto;\n flex: 1;\n border-left: none;\n padding-left: 0;\n}\n.is-compact .selected-list {\n max-height: none;\n}\n.is-compact .selected-item {\n min-height: 44px;\n}\n</style>\n"],"names":["props","__props","emit","__emit","t","useI18n","message","useMessage","show","ref","tableRef","bp","useShellBreakpoint","winW","useWindowSize","compact","computed","variant","tab","btnSize","orgFlat","orgTree","buildTree","selectedOrgKey","orgId","onMounted","orgApi","onOrgSelect","keys","_a","onOrgPick","key","checkedKeys","excludeSet","fetcher","params","userApi","columns","SEARCH_ACTIONS","r","already","selectedSet","h","NButton","addUsers","selected","reactive","u","selectedSearch","filteredSelected","q","users","addChecked","rows","unref","removeUser","id","idx","clearSelected","open","ids","items","all","found","handleConfirm","__expose","_createBlock","FormContainer","$event","_unref","NTabs","_createVNode","NTab","_createTextVNode","_toDisplayString","_createElementVNode","_normalizeClass","_openBlock","_createElementBlock","_hoisted_1","_hoisted_2","NTree","_withDirectives","_hoisted_3","NTreeSelect","_hoisted_4","SmartTable","_cache","e","translateError","_vShow","_hoisted_5","_hoisted_6","NInput","_hoisted_7","_Fragment","_renderList","_hoisted_8","_hoisted_9","AppIcon","NEmpty"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index2.vue.js","sources":["../../../src/components/UserPicker/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onMounted, reactive, ref } from 'vue'\nimport {\n NButton,\n NDropdown,\n NEmpty,\n NInput,\n NSpin,\n NTab,\n NTabs,\n NTree,\n NTreeSelect,\n useMessage,\n} from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { useDebounceFn, useWindowSize } from '@vueuse/core'\nimport AppIcon from '#/components/AppIcon.vue'\nimport FormContainer from '#/components/FormContainer/index.vue'\nimport UserAvatar from './UserAvatar.vue'\nimport { useShellBreakpoint } from '#/composables/useShellBreakpoint'\nimport { orgApi, userApi } from '#/api'\nimport { buildTree } from '#/utils/tree'\nimport { translateError } from '#/utils/error'\nimport type { SysOrg, UserItem } from '#/types/api'\n\nconst props = defineProps<{ excludeIds?: number[] }>()\nconst emit = defineEmits<{ (e: 'confirm', ids: number[]): void }>()\n\nconst { t } = useI18n()\nconst message = useMessage()\n\nconst show = ref(false)\n\n// 屏幕形态:\n// 宽 / 中档 = 居中弹窗里的三栏(机构 | 可选用户 | 已选);\n// 窄档(壳层 < 600)= 底部抽屉,三栏收成「可选用户 / 已选」两页,机构换成页内的树选择;\n// 内容区 < 900 但还不到窄档时仍是弹窗,三栏同样收成两页(920 宽的三栏在这里放不下)。\n// 三栏选择器在抽屉里太局促,所以宽 / 中档固定用弹窗,不跟随「表单形态」全局偏好。\nconst { bp } = useShellBreakpoint()\nconst { width: winW } = useWindowSize()\nconst compact = computed(() => bp.value === 'narrow' || winW.value < 900)\nconst variant = computed(() => (bp.value === 'narrow' ? 'drawer' : 'modal'))\nconst tab = ref<'users' | 'selected'>('users')\n\n// ── 机构(左栏) ──\nconst orgFlat = ref<SysOrg[]>([])\nconst orgTree = computed(() => {\n const all = { id: 0, parentId: -1, name: t('common.all'), children: buildTree(orgFlat.value) }\n return [all]\n})\nconst selectedOrgKey = ref<number | null>(null)\nconst orgId = computed(() =>\n selectedOrgKey.value && selectedOrgKey.value !== 0 ? selectedOrgKey.value : undefined,\n)\n\nonMounted(async () => {\n try {\n orgFlat.value = await orgApi.list()\n } catch {\n /* 静默 */\n }\n})\n\nfunction onOrgSelect(keys: Array<string | number>) {\n selectedOrgKey.value = (keys[0] as number) ?? null\n reload()\n}\n// 两页形态下机构换成树选择:没有「再点一次取消」,选「全部」(id 0)即不限机构。\nfunction onOrgPick(key: number | null) {\n selectedOrgKey.value = key ?? null\n reload()\n}\n\n// ── 可选用户(中栏) ──\n// 一页 20 人;接口只有「姓名 / 账号」两个独立的模糊条件、没有联合检索,所以搜索框带一个范围切换。\nconst PAGE_SIZE = 20\ntype SearchField = 'name' | 'account'\n\nconst field = ref<SearchField>('name')\nconst keyword = ref('')\nconst page = ref(1)\nconst total = ref(0)\nconst rows = ref<UserItem[]>([])\nconst loading = ref(false)\n// 防乱序:输入停顿、翻页、换机构都会发请求,后发的先回来时,先发的结果不能覆盖它。\nlet seq = 0\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(total.value / PAGE_SIZE)))\nconst placeholder = computed(() =>\n field.value === 'name' ? t('userPicker.searchName') : t('userPicker.searchAccount'),\n)\nconst scopeOptions = computed(() => [\n { key: 'name', label: t('user.name') },\n { key: 'account', label: t('user.account') },\n])\n\nasync function load() {\n const mine = ++seq\n loading.value = true\n try {\n const kw = keyword.value.trim() || undefined\n const res = await userApi.page({\n page: page.value,\n pageSize: PAGE_SIZE,\n name: field.value === 'name' ? kw : undefined,\n account: field.value === 'account' ? kw : undefined,\n orgId: orgId.value,\n })\n if (mine !== seq) return\n rows.value = res.items\n total.value = res.total\n } catch (e) {\n if (mine !== seq) return\n rows.value = []\n total.value = 0\n message.error(translateError(e))\n } finally {\n if (mine === seq) loading.value = false\n }\n}\n\n/** 条件变了:回第 1 页重查。 */\nfunction reload() {\n page.value = 1\n void load()\n}\nconst reloadDebounced = useDebounceFn(reload, 300)\n\nfunction onSearchInput(v: string) {\n keyword.value = v\n void reloadDebounced()\n}\nfunction onScope(key: SearchField) {\n field.value = key\n if (keyword.value.trim()) reload()\n}\nfunction goPage(n: number) {\n if (n < 1 || n > pageCount.value || n === page.value) return\n page.value = n\n void load()\n}\n\n// ── 已选用户(右栏) ──\nconst selected = reactive<UserItem[]>([])\nconst selectedSet = computed(() => new Set(selected.map(u => u.id)))\nconst excludeSet = computed(() => new Set(props.excludeIds ?? []))\nconst selectedSearch = ref('')\n// 已选不多时搜索框只是噪音,超过一屏才出现。\nconst SELECTED_SEARCH_MIN = 8\nconst filteredSelected = computed(() => {\n const q = selectedSearch.value.trim().toLowerCase()\n if (!q) return selected\n return selected.filter(\n u => u.name.toLowerCase().includes(q) || u.account.toLowerCase().includes(q),\n )\n})\n\nfunction addUsers(users: UserItem[]) {\n for (const u of users) {\n if (!selectedSet.value.has(u.id) && !excludeSet.value.has(u.id)) selected.push(u)\n }\n}\nfunction removeUser(id: number) {\n const idx = selected.findIndex(u => u.id === id)\n if (idx >= 0) selected.splice(idx, 1)\n}\nfunction toggleUser(u: UserItem) {\n if (excludeSet.value.has(u.id)) return\n if (selectedSet.value.has(u.id)) removeUser(u.id)\n else addUsers([u])\n}\nfunction clearSelected() {\n selected.splice(0, selected.length)\n}\n\n// 「全选本页」:排除名单里的人不算;本页能选的都选上了,再点就是取消本页。\nconst selectable = computed(() => rows.value.filter(u => !excludeSet.value.has(u.id)))\nconst pageAllSelected = computed(\n () => selectable.value.length > 0 && selectable.value.every(u => selectedSet.value.has(u.id)),\n)\nfunction togglePage() {\n if (pageAllSelected.value) {\n for (const u of selectable.value) removeUser(u.id)\n } else {\n addUsers(selectable.value)\n }\n}\n\n/** 次行:账号(与姓名相同就不重复)· 机构。 */\nfunction subline(u: UserItem) {\n return [u.account !== u.name ? u.account : '', u.orgName].filter(Boolean).join(' · ')\n}\n\n// ── 公共 API ──\nasync function open(ids?: number[]) {\n selected.splice(0, selected.length)\n selectedSearch.value = ''\n selectedOrgKey.value = null\n keyword.value = ''\n field.value = 'name'\n page.value = 1\n rows.value = []\n total.value = 0\n tab.value = 'users'\n show.value = true\n\n void load()\n if (!ids?.length) return\n try {\n // ponytail: 拉一页,靠 id 过滤。取 200 是为了在应用把 Api:MaxPageSize 调小时也不会被后端回 48001。\n // 用户超过 200 个时,排在后面的 id 解析不出名字,走下面那条退回显示 id 的分支;\n // 真要支持更大规模的用户名单,再改成分页搜索。\n const { items } = await userApi.page({ page: 1, pageSize: 200 })\n const all = items.filter(u => ids.includes(u.id))\n // 没拉到的 id(翻页之外),退回只显示 id\n const found = new Set(all.map(u => u.id))\n for (const u of all) selected.push(u)\n for (const id of ids) {\n if (!found.has(id)) selected.push({ id, account: String(id), name: String(id) } as UserItem)\n }\n } catch {\n for (const id of ids) selected.push({ id, account: String(id), name: String(id) } as UserItem)\n }\n}\n\nasync function handleConfirm() {\n emit(\n 'confirm',\n selected.map(u => u.id),\n )\n}\n\ndefineExpose({ open })\n</script>\n\n<template>\n <FormContainer\n v-model:show=\"show\"\n :variant=\"variant\"\n :title=\"t('userPicker.title')\"\n :width=\"920\"\n :on-confirm=\"handleConfirm\"\n :confirm-text=\"t('common.confirm')\"\n >\n <n-tabs v-if=\"compact\" v-model:value=\"tab\" type=\"segment\" animated class=\"picker-tabs\">\n <n-tab name=\"users\">{{ t('userPicker.available') }}</n-tab>\n <n-tab name=\"selected\">{{ t('userPicker.selected') }} {{ selected.length }}</n-tab>\n </n-tabs>\n <div class=\"user-picker\" :class=\"{ 'is-compact': compact }\">\n <!-- 左栏:机构(侧栏式列表;两页形态下换成可选用户页顶部的树选择) -->\n <div v-if=\"!compact\" class=\"picker-org\">\n <div class=\"side-title\">{{ t('userPicker.org') }}</div>\n <n-tree\n :data=\"orgTree\"\n key-field=\"id\"\n label-field=\"name\"\n children-field=\"children\"\n selectable\n block-line\n default-expand-all\n :selected-keys=\"[selectedOrgKey ?? 0]\"\n @update:selected-keys=\"onOrgSelect\"\n />\n </div>\n\n <!-- 中栏:搜索 + 用户列表 -->\n <div v-show=\"!compact || tab === 'users'\" class=\"picker-main\">\n <n-tree-select\n v-if=\"compact\"\n :value=\"selectedOrgKey ?? 0\"\n :options=\"orgTree\"\n key-field=\"id\"\n label-field=\"name\"\n children-field=\"children\"\n default-expand-all\n size=\"large\"\n class=\"picker-org-select\"\n @update:value=\"onOrgPick\"\n />\n <div class=\"picker-toolbar\">\n <n-input\n :value=\"keyword\"\n round\n clearable\n :size=\"compact ? 'large' : 'medium'\"\n :placeholder=\"placeholder\"\n class=\"picker-search\"\n @update:value=\"onSearchInput\"\n >\n <template #prefix>\n <n-dropdown trigger=\"click\" :options=\"scopeOptions\" :value=\"field\" @select=\"onScope\">\n <span class=\"scope-trigger\" role=\"button\" tabindex=\"0\">\n <AppIcon icon=\"ph:magnifying-glass\" :size=\"15\" />\n <AppIcon icon=\"ph:caret-down\" :size=\"10\" />\n </span>\n </n-dropdown>\n </template>\n </n-input>\n <n-button\n text\n type=\"primary\"\n class=\"page-toggle\"\n :disabled=\"selectable.length === 0\"\n @click=\"togglePage\"\n >\n {{ pageAllSelected ? t('userPicker.unselectPage') : t('userPicker.selectPage') }}\n </n-button>\n </div>\n\n <n-spin :show=\"loading\" size=\"small\" class=\"picker-spin\">\n <div class=\"user-list\" role=\"group\">\n <div\n v-for=\"u in rows\"\n :key=\"u.id\"\n class=\"user-row\"\n :class=\"{\n 'is-selected': selectedSet.has(u.id),\n 'is-disabled': excludeSet.has(u.id),\n }\"\n role=\"checkbox\"\n :aria-checked=\"selectedSet.has(u.id) ? 'true' : 'false'\"\n :aria-disabled=\"excludeSet.has(u.id) ? 'true' : undefined\"\n :tabindex=\"excludeSet.has(u.id) ? -1 : 0\"\n @click=\"toggleUser(u)\"\n @keydown.enter.prevent=\"toggleUser(u)\"\n @keydown.space.prevent=\"toggleUser(u)\"\n >\n <UserAvatar :name=\"u.name\" :src=\"u.avatar\" :size=\"32\" />\n <span class=\"user-main\">\n <span class=\"user-name\">{{ u.name }}</span>\n <span v-if=\"subline(u)\" class=\"user-sub\">{{ subline(u) }}</span>\n </span>\n <span v-if=\"excludeSet.has(u.id)\" class=\"user-tag\">\n {{ t('userPicker.existing') }}\n </span>\n <span v-else class=\"user-check\"><AppIcon icon=\"ph:check-bold\" :size=\"12\" /></span>\n </div>\n <n-empty\n v-if=\"!loading && rows.length === 0\"\n :description=\"t('common.noData')\"\n class=\"user-empty\"\n />\n </div>\n </n-spin>\n\n <div class=\"picker-foot\">\n <span class=\"picker-total\">{{ t('userPicker.total', { n: total }) }}</span>\n <div v-if=\"pageCount > 1\" class=\"pager\">\n <button\n type=\"button\"\n class=\"pager-btn pager-prev\"\n :disabled=\"page <= 1\"\n :aria-label=\"t('userPicker.prevPage')\"\n @click=\"goPage(page - 1)\"\n >\n <AppIcon icon=\"ph:caret-left\" :size=\"14\" />\n </button>\n <span class=\"pager-num\">{{ page }} / {{ pageCount }}</span>\n <button\n type=\"button\"\n class=\"pager-btn pager-next\"\n :disabled=\"page >= pageCount\"\n :aria-label=\"t('userPicker.nextPage')\"\n @click=\"goPage(page + 1)\"\n >\n <AppIcon icon=\"ph:caret-right\" :size=\"14\" />\n </button>\n </div>\n </div>\n </div>\n\n <!-- 右栏:已选 -->\n <div v-show=\"!compact || tab === 'selected'\" class=\"picker-selected\">\n <div class=\"side-head\">\n <span class=\"side-title\">\n {{ t('userPicker.selected') }}\n <span class=\"side-count\">{{ selected.length }}</span>\n </span>\n <n-button\n text\n type=\"error\"\n class=\"selected-clear\"\n :disabled=\"selected.length === 0\"\n @click=\"clearSelected\"\n >\n {{ t('userPicker.clear') }}\n </n-button>\n </div>\n <n-input\n v-if=\"selected.length > SELECTED_SEARCH_MIN\"\n v-model:value=\"selectedSearch\"\n round\n clearable\n :size=\"compact ? 'large' : 'small'\"\n :placeholder=\"t('common.search')\"\n class=\"selected-search\"\n />\n <div class=\"selected-list\">\n <div v-for=\"u in filteredSelected\" :key=\"u.id\" class=\"selected-item\">\n <UserAvatar :name=\"u.name\" :src=\"u.avatar\" :size=\"28\" />\n <span class=\"user-main\">\n <span class=\"user-name\">{{ u.name }}</span>\n <span v-if=\"subline(u)\" class=\"user-sub\">{{ subline(u) }}</span>\n </span>\n <button\n type=\"button\"\n class=\"item-remove\"\n :aria-label=\"t('userPicker.remove')\"\n @click=\"removeUser(u.id)\"\n >\n <AppIcon icon=\"ph:x\" :size=\"12\" />\n </button>\n </div>\n <n-empty\n v-if=\"selected.length === 0\"\n :description=\"t('userPicker.emptySelected')\"\n class=\"user-empty\"\n />\n </div>\n </div>\n </div>\n </FormContainer>\n</template>\n\n<style scoped>\n/* 三栏:机构(浅底侧栏)| 可选用户 | 已选。整体定高,各栏自己滚,翻页和搜索时弹窗不跳。 */\n.user-picker {\n display: flex;\n gap: 16px;\n height: min(460px, calc(100vh - 260px));\n}\n\n/* ── 左栏:macOS 侧栏式机构列表 ── */\n.picker-org {\n width: 200px;\n flex-shrink: 0;\n overflow: auto;\n padding: 8px;\n border-radius: var(--radius-lg);\n background: var(--fill);\n}\n.side-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--text-3);\n}\n.picker-org .side-title {\n padding: 2px 8px 6px;\n}\n.picker-org :deep(.n-tree-node) {\n min-height: var(--sidebar-row-h);\n border-radius: var(--radius-md);\n}\n.picker-org :deep(.n-tree-node:hover) {\n background: var(--hover);\n}\n/* 灰底面板上 --sel-bg(6% 强调色)几乎看不出来,选中条用更实的 --fill-strong,文字再用强调色加粗。 */\n.picker-org :deep(.n-tree-node.n-tree-node--selected) {\n background: var(--fill-strong);\n}\n.picker-org :deep(.n-tree-node.n-tree-node--selected .n-tree-node-content__text) {\n color: var(--sel-fg);\n font-weight: 600;\n}\n\n/* ── 中栏 ── */\n.picker-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n}\n.picker-org-select {\n margin-bottom: 8px;\n}\n.picker-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 8px;\n}\n.picker-search {\n flex: 1;\n min-width: 0;\n}\n.scope-trigger {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n color: var(--text-3);\n cursor: pointer;\n}\n.page-toggle {\n flex-shrink: 0;\n font-size: 13.5px;\n}\n.picker-spin {\n flex: 1;\n min-height: 0;\n}\n.picker-spin :deep(.n-spin-content) {\n height: 100%;\n}\n.user-list {\n height: 100%;\n overflow: auto;\n margin: 0 -4px;\n padding: 0 4px;\n}\n\n.user-row {\n display: flex;\n align-items: center;\n gap: 12px;\n min-height: 48px;\n padding: 0 10px;\n border-radius: var(--radius-md);\n cursor: pointer;\n transition: background var(--transition-fast);\n outline: none;\n}\n.user-row:hover {\n background: var(--hover);\n}\n.user-row.is-selected {\n background: var(--sel-bg);\n}\n.user-row:focus-visible {\n box-shadow: inset 0 0 0 2px var(--sel-ring);\n}\n.user-row.is-disabled {\n cursor: not-allowed;\n background: none;\n opacity: 0.55;\n}\n\n.user-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n line-height: 1.35;\n}\n.user-name {\n font-size: 14px;\n color: var(--text-1);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.user-sub {\n font-size: 12.5px;\n color: var(--text-3);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.user-tag {\n flex-shrink: 0;\n font-size: 12.5px;\n color: var(--text-3);\n}\n\n/* 圆形勾选标记:空心环 → 选中后实心强调色。整行可点,它只负责告诉人「选了没」。 */\n.user-check {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 999px;\n border: 1.5px solid var(--border);\n color: transparent;\n transition:\n background var(--transition-fast),\n border-color var(--transition-fast);\n}\n.user-row.is-selected .user-check {\n background: var(--acc-solid);\n border-color: var(--acc-solid);\n color: var(--on-acc);\n}\n\n.user-empty {\n padding: 48px 0;\n}\n\n.picker-foot {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 34px;\n padding-top: 8px;\n border-top: 1px solid var(--hairline);\n font-size: 13px;\n color: var(--text-3);\n}\n.pager {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.pager-num {\n min-width: 48px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n color: var(--text-2);\n}\n.pager-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: var(--radius-sm);\n background: none;\n color: var(--text-2);\n cursor: pointer;\n transition: background var(--transition-fast);\n}\n.pager-btn:hover:not(:disabled) {\n background: var(--hover);\n}\n.pager-btn:disabled {\n color: var(--text-4);\n cursor: not-allowed;\n}\n\n/* ── 右栏 ── */\n.picker-selected {\n width: 232px;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n min-height: 0;\n padding-left: 16px;\n border-left: 1px solid var(--hairline);\n}\n.side-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 30px;\n margin-bottom: 8px;\n}\n.side-head .side-title {\n font-size: 13.5px;\n color: var(--text-1);\n}\n.side-count {\n margin-left: 4px;\n font-weight: 500;\n color: var(--text-3);\n font-variant-numeric: tabular-nums;\n}\n.selected-search {\n margin-bottom: 8px;\n}\n.selected-list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}\n.selected-item {\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 44px;\n padding: 0 6px 0 8px;\n border-radius: var(--radius-md);\n}\n.selected-item:hover {\n background: var(--hover);\n}\n.item-remove {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: 999px;\n background: none;\n color: var(--text-3);\n cursor: pointer;\n opacity: 0;\n transition:\n opacity var(--transition-fast),\n background var(--transition-fast);\n}\n.selected-item:hover .item-remove,\n.item-remove:focus-visible {\n opacity: 1;\n}\n.item-remove:hover {\n background: var(--fill-strong);\n color: var(--err);\n}\n\n/* 两页形态:分段页签在上,当前页占满整行;已选页不再要左侧分隔线,条目放大到可点;触屏没有悬停,移除钮常显 */\n.picker-tabs {\n margin-bottom: 12px;\n}\n.user-picker.is-compact {\n height: min(60vh, 520px);\n}\n.is-compact .picker-selected {\n width: auto;\n flex: 1;\n padding-left: 0;\n border-left: none;\n}\n.is-compact .selected-item {\n min-height: 52px;\n}\n.is-compact .item-remove {\n opacity: 1;\n width: 32px;\n height: 32px;\n}\n.is-compact .user-row {\n min-height: 56px;\n}\n</style>\n"],"names":["PAGE_SIZE","SELECTED_SEARCH_MIN","props","__props","emit","__emit","t","useI18n","message","useMessage","show","ref","bp","useShellBreakpoint","winW","useWindowSize","compact","computed","variant","tab","orgFlat","orgTree","buildTree","selectedOrgKey","orgId","onMounted","orgApi","onOrgSelect","keys","reload","onOrgPick","key","field","keyword","page","total","rows","loading","seq","pageCount","placeholder","scopeOptions","load","mine","kw","res","userApi","e","translateError","reloadDebounced","useDebounceFn","onSearchInput","v","onScope","goPage","n","selected","reactive","selectedSet","u","excludeSet","selectedSearch","filteredSelected","q","addUsers","users","removeUser","id","idx","toggleUser","clearSelected","selectable","pageAllSelected","togglePage","subline","open","ids","items","all","found","handleConfirm","__expose","_createBlock","FormContainer","$event","_unref","NTabs","_createVNode","NTab","_createTextVNode","_toDisplayString","_createElementVNode","_normalizeClass","_openBlock","_createElementBlock","_hoisted_1","_hoisted_2","NTree","_withDirectives","_hoisted_3","NTreeSelect","_hoisted_4","NInput","NDropdown","_hoisted_5","AppIcon","NButton","NSpin","_hoisted_6","_Fragment","_renderList","_withKeys","_withModifiers","UserAvatar","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","NEmpty","_hoisted_13","_hoisted_14","_hoisted_15","_cache","_hoisted_17","_vShow","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26"],"mappings":"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"}
|
|
@@ -30,3 +30,13 @@ export type MenuRouteDescriptor = {
|
|
|
30
30
|
* `useAuthMenu` 负责。`viewKeys` 必须来自同一张页面 glob,避免存在性判断与 loader 来源漂移。
|
|
31
31
|
*/
|
|
32
32
|
export declare function describeMenuRoute(node: MenuNode, viewKeys: ReadonlySet<string>): MenuRouteDescriptor | null;
|
|
33
|
+
/**
|
|
34
|
+
* 比对路由路径的口径:补前导斜杠、去末尾斜杠(vue-router 非严格模式下 /a/ 与 /a 是同一条路由)。
|
|
35
|
+
* 末尾斜杠用循环从后往前数:路径来自地址栏,是用户可控的输入,`/\/+$/` 这类正则遇到一长串斜杠会二次方回溯。
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeRoutePath(path: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* 这棵菜单树会不会注册出 `path` 这条路由。判据就是 describeMenuRoute 的产出,所以与真正注册时同口径:
|
|
40
|
+
* 目录、外链、没配组件的节点不算。只认整条路径相等 —— 前缀相同不代表是同一个页面。
|
|
41
|
+
*/
|
|
42
|
+
export declare function menuHasPath(tree: MenuNode[], path: string, viewKeys: ReadonlySet<string>): boolean;
|
|
@@ -1,37 +1,53 @@
|
|
|
1
|
-
import { isHttpUrl as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { isHttpUrl as u } from "../utils/url.js";
|
|
2
|
+
import { flattenTree as p } from "../utils/tree.js";
|
|
3
|
+
import { menuTitleKey as c } from "../locales/menuTitle.js";
|
|
4
|
+
import { MenuType as s } from "../types/menu.js";
|
|
5
|
+
function m(t, r) {
|
|
6
|
+
if (t.type !== s.Menu || !t.path || u(t.path)) return null;
|
|
7
|
+
const i = t.path.startsWith("/") ? t.path : `/${t.path}`, n = `menu-${t.id}`, e = t.component;
|
|
8
|
+
if (e && u(e))
|
|
8
9
|
return {
|
|
9
10
|
kind: "iframe",
|
|
10
|
-
path:
|
|
11
|
-
name:
|
|
12
|
-
title:
|
|
11
|
+
path: i,
|
|
12
|
+
name: n,
|
|
13
|
+
title: c(t.title, t.path),
|
|
13
14
|
icon: t.icon,
|
|
14
|
-
iframeSrc:
|
|
15
|
+
iframeSrc: e
|
|
15
16
|
};
|
|
16
|
-
if (!
|
|
17
|
-
const
|
|
18
|
-
return
|
|
17
|
+
if (!e) return null;
|
|
18
|
+
const a = e.replace(/^\/+/, "").replace(/\.vue$/, "");
|
|
19
|
+
return r.has(a) ? {
|
|
19
20
|
kind: "view",
|
|
20
|
-
path:
|
|
21
|
-
name:
|
|
22
|
-
title:
|
|
21
|
+
path: i,
|
|
22
|
+
name: n,
|
|
23
|
+
title: c(t.title, t.path),
|
|
23
24
|
icon: t.icon,
|
|
24
|
-
viewKey:
|
|
25
|
+
viewKey: a
|
|
25
26
|
} : {
|
|
26
27
|
kind: "missing",
|
|
27
|
-
path:
|
|
28
|
-
name:
|
|
29
|
-
title:
|
|
28
|
+
path: i,
|
|
29
|
+
name: n,
|
|
30
|
+
title: c(t.title, t.path),
|
|
30
31
|
icon: t.icon,
|
|
31
|
-
component:
|
|
32
|
+
component: e
|
|
32
33
|
};
|
|
33
34
|
}
|
|
35
|
+
function l(t) {
|
|
36
|
+
const r = t.startsWith("/") ? t : `/${t}`;
|
|
37
|
+
let i = r.length;
|
|
38
|
+
for (; i > 1 && r.charCodeAt(i - 1) === 47; ) i--;
|
|
39
|
+
return r.slice(0, i);
|
|
40
|
+
}
|
|
41
|
+
function y(t, r, i) {
|
|
42
|
+
const n = l(r);
|
|
43
|
+
return p(t).some((e) => {
|
|
44
|
+
const a = m(e, i);
|
|
45
|
+
return a !== null && l(a.path) === n;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
34
48
|
export {
|
|
35
|
-
|
|
49
|
+
m as describeMenuRoute,
|
|
50
|
+
y as menuHasPath,
|
|
51
|
+
l as normalizeRoutePath
|
|
36
52
|
};
|
|
37
53
|
//# sourceMappingURL=authMenuRoute.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authMenuRoute.js","sources":["../../src/composables/authMenuRoute.ts"],"sourcesContent":["import { isHttpUrl } from '#/utils/url'\nimport { menuTitleKey } from '#/locales/menuTitle'\nimport { MenuType, type MenuNode } from '#/types/menu'\n\n/**\n * 菜单节点的可物化结果。三种变体把 iframe/view/missing 各自所需的数据收窄,调用方无需再次\n * 解释后端的 `component` 字符串。\n */\nexport type MenuRouteDescriptor =\n | { kind: 'iframe'; path: string; name: string; title: string; icon?: string; iframeSrc: string }\n | { kind: 'view'; path: string; name: string; title: string; icon?: string; viewKey: string }\n | { kind: 'missing'; path: string; name: string; title: string; icon?: string; component: string }\n\n/**\n * 只负责菜单路由决策,让分支逻辑能脱离 vue-router 做单测;路由注册与组件物化仍由\n * `useAuthMenu` 负责。`viewKeys` 必须来自同一张页面 glob,避免存在性判断与 loader 来源漂移。\n */\nexport function describeMenuRoute(\n node: MenuNode,\n viewKeys: ReadonlySet<string>,\n): MenuRouteDescriptor | null {\n if (node.type !== MenuType.Menu || !node.path || isHttpUrl(node.path)) return null\n\n const path = node.path.startsWith('/') ? node.path : `/${node.path}`\n const name = `menu-${node.id}`\n const component = node.component\n if (component && isHttpUrl(component)) {\n return {\n kind: 'iframe',\n path,\n name,\n title: menuTitleKey(node.title, node.path),\n icon: node.icon,\n iframeSrc: component,\n }\n }\n if (!component) return null\n\n // 页面表的 key 形如 system/user/index;容忍存量配置带前导斜杠或 .vue 后缀\n const viewKey = component.replace(/^\\/+/, '').replace(/\\.vue$/, '')\n if (viewKeys.has(viewKey))\n return {\n kind: 'view',\n path,\n name,\n title: menuTitleKey(node.title, node.path),\n icon: node.icon,\n viewKey,\n }\n return {\n kind: 'missing',\n path,\n name,\n title: menuTitleKey(node.title, node.path),\n icon: node.icon,\n component,\n }\n}\n"],"names":["describeMenuRoute","node","viewKeys","MenuType","isHttpUrl","path","name","component","menuTitleKey","viewKey"],"mappings":"
|
|
1
|
+
{"version":3,"file":"authMenuRoute.js","sources":["../../src/composables/authMenuRoute.ts"],"sourcesContent":["import { isHttpUrl } from '#/utils/url'\nimport { flattenTree } from '#/utils/tree'\nimport { menuTitleKey } from '#/locales/menuTitle'\nimport { MenuType, type MenuNode } from '#/types/menu'\n\n/**\n * 菜单节点的可物化结果。三种变体把 iframe/view/missing 各自所需的数据收窄,调用方无需再次\n * 解释后端的 `component` 字符串。\n */\nexport type MenuRouteDescriptor =\n | { kind: 'iframe'; path: string; name: string; title: string; icon?: string; iframeSrc: string }\n | { kind: 'view'; path: string; name: string; title: string; icon?: string; viewKey: string }\n | { kind: 'missing'; path: string; name: string; title: string; icon?: string; component: string }\n\n/**\n * 只负责菜单路由决策,让分支逻辑能脱离 vue-router 做单测;路由注册与组件物化仍由\n * `useAuthMenu` 负责。`viewKeys` 必须来自同一张页面 glob,避免存在性判断与 loader 来源漂移。\n */\nexport function describeMenuRoute(\n node: MenuNode,\n viewKeys: ReadonlySet<string>,\n): MenuRouteDescriptor | null {\n if (node.type !== MenuType.Menu || !node.path || isHttpUrl(node.path)) return null\n\n const path = node.path.startsWith('/') ? node.path : `/${node.path}`\n const name = `menu-${node.id}`\n const component = node.component\n if (component && isHttpUrl(component)) {\n return {\n kind: 'iframe',\n path,\n name,\n title: menuTitleKey(node.title, node.path),\n icon: node.icon,\n iframeSrc: component,\n }\n }\n if (!component) return null\n\n // 页面表的 key 形如 system/user/index;容忍存量配置带前导斜杠或 .vue 后缀\n const viewKey = component.replace(/^\\/+/, '').replace(/\\.vue$/, '')\n if (viewKeys.has(viewKey))\n return {\n kind: 'view',\n path,\n name,\n title: menuTitleKey(node.title, node.path),\n icon: node.icon,\n viewKey,\n }\n return {\n kind: 'missing',\n path,\n name,\n title: menuTitleKey(node.title, node.path),\n icon: node.icon,\n component,\n }\n}\n\n/**\n * 比对路由路径的口径:补前导斜杠、去末尾斜杠(vue-router 非严格模式下 /a/ 与 /a 是同一条路由)。\n * 末尾斜杠用循环从后往前数:路径来自地址栏,是用户可控的输入,`/\\/+$/` 这类正则遇到一长串斜杠会二次方回溯。\n */\nexport function normalizeRoutePath(path: string): string {\n const withSlash = path.startsWith('/') ? path : `/${path}`\n let end = withSlash.length\n while (end > 1 && withSlash.charCodeAt(end - 1) === 47 /* '/' */) end--\n return withSlash.slice(0, end)\n}\n\n/**\n * 这棵菜单树会不会注册出 `path` 这条路由。判据就是 describeMenuRoute 的产出,所以与真正注册时同口径:\n * 目录、外链、没配组件的节点不算。只认整条路径相等 —— 前缀相同不代表是同一个页面。\n */\nexport function menuHasPath(\n tree: MenuNode[],\n path: string,\n viewKeys: ReadonlySet<string>,\n): boolean {\n const target = normalizeRoutePath(path)\n return flattenTree(tree).some(node => {\n const route = describeMenuRoute(node, viewKeys)\n return route !== null && normalizeRoutePath(route.path) === target\n })\n}\n"],"names":["describeMenuRoute","node","viewKeys","MenuType","isHttpUrl","path","name","component","menuTitleKey","viewKey","normalizeRoutePath","withSlash","end","menuHasPath","tree","target","flattenTree","route"],"mappings":";;;;AAkBO,SAASA,EACdC,GACAC,GAC4B;AAC5B,MAAID,EAAK,SAASE,EAAS,QAAQ,CAACF,EAAK,QAAQG,EAAUH,EAAK,IAAI,EAAG,QAAO;AAE9E,QAAMI,IAAOJ,EAAK,KAAK,WAAW,GAAG,IAAIA,EAAK,OAAO,IAAIA,EAAK,IAAI,IAC5DK,IAAO,QAAQL,EAAK,EAAE,IACtBM,IAAYN,EAAK;AACvB,MAAIM,KAAaH,EAAUG,CAAS;AAClC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAAF;AAAA,MACA,MAAAC;AAAA,MACA,OAAOE,EAAaP,EAAK,OAAOA,EAAK,IAAI;AAAA,MACzC,MAAMA,EAAK;AAAA,MACX,WAAWM;AAAA,IAAA;AAGf,MAAI,CAACA,EAAW,QAAO;AAGvB,QAAME,IAAUF,EAAU,QAAQ,QAAQ,EAAE,EAAE,QAAQ,UAAU,EAAE;AAClE,SAAIL,EAAS,IAAIO,CAAO,IACf;AAAA,IACL,MAAM;AAAA,IACN,MAAAJ;AAAA,IACA,MAAAC;AAAA,IACA,OAAOE,EAAaP,EAAK,OAAOA,EAAK,IAAI;AAAA,IACzC,MAAMA,EAAK;AAAA,IACX,SAAAQ;AAAA,EAAA,IAEG;AAAA,IACL,MAAM;AAAA,IACN,MAAAJ;AAAA,IACA,MAAAC;AAAA,IACA,OAAOE,EAAaP,EAAK,OAAOA,EAAK,IAAI;AAAA,IACzC,MAAMA,EAAK;AAAA,IACX,WAAAM;AAAA,EAAA;AAEJ;AAMO,SAASG,EAAmBL,GAAsB;AACvD,QAAMM,IAAYN,EAAK,WAAW,GAAG,IAAIA,IAAO,IAAIA,CAAI;AACxD,MAAIO,IAAMD,EAAU;AACpB,SAAOC,IAAM,KAAKD,EAAU,WAAWC,IAAM,CAAC,MAAM,KAAc,CAAAA;AAClE,SAAOD,EAAU,MAAM,GAAGC,CAAG;AAC/B;AAMO,SAASC,EACdC,GACAT,GACAH,GACS;AACT,QAAMa,IAASL,EAAmBL,CAAI;AACtC,SAAOW,EAAYF,CAAI,EAAE,KAAK,CAAAb,MAAQ;AACpC,UAAMgB,IAAQjB,EAAkBC,GAAMC,CAAQ;AAC9C,WAAOe,MAAU,QAAQP,EAAmBO,EAAM,IAAI,MAAMF;AAAA,EAC9D,CAAC;AACH;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AppModule } from '../types/menu';
|
|
1
2
|
type EnterResult = {
|
|
2
3
|
chooser: true;
|
|
3
4
|
} | {
|
|
@@ -10,7 +11,8 @@ declare function enter(moduleId: number): Promise<EnterResult>;
|
|
|
10
11
|
export declare function useModule(): {
|
|
11
12
|
enter: typeof enter;
|
|
12
13
|
enterInitial: () => Promise<EnterResult>;
|
|
13
|
-
switchModule: (moduleId: number) => Promise<void>;
|
|
14
|
+
switchModule: (moduleId: number, target?: string) => Promise<void>;
|
|
15
|
+
findOwnerModule: (path: string) => Promise<AppModule | null>;
|
|
14
16
|
setDefault: (moduleId: number) => Promise<void>;
|
|
15
17
|
};
|
|
16
18
|
export {};
|
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import { useAuthStore as
|
|
2
|
-
import { useTabsStore as
|
|
3
|
-
import { useUserStore as
|
|
4
|
-
import { personalApi as
|
|
5
|
-
import { buildRoutesForModule as
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { useAuthStore as g } from "../stores/auth.js";
|
|
2
|
+
import { useTabsStore as y } from "../stores/tabs.js";
|
|
3
|
+
import { useUserStore as d } from "../stores/user.js";
|
|
4
|
+
import { personalApi as i } from "../api/index.js";
|
|
5
|
+
import { buildRoutesForModule as v } from "./useAuthMenu.js";
|
|
6
|
+
import { normalizeRoutePath as c, menuHasPath as S } from "./authMenuRoute.js";
|
|
7
|
+
import { viewComponentPaths as P } from "../router/viewRegistry.js";
|
|
8
|
+
import { router as T } from "../router/index.js";
|
|
9
|
+
let m = null;
|
|
10
|
+
async function l(o) {
|
|
11
|
+
return await v(o), { chooser: !1, moduleId: o };
|
|
10
12
|
}
|
|
11
|
-
function
|
|
12
|
-
const
|
|
13
|
-
function
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
})),
|
|
13
|
+
function z() {
|
|
14
|
+
const o = g();
|
|
15
|
+
function h() {
|
|
16
|
+
return m ?? (m = p().finally(() => {
|
|
17
|
+
m = null;
|
|
18
|
+
})), m;
|
|
17
19
|
}
|
|
18
|
-
async function
|
|
19
|
-
const [{ modules:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
async function p() {
|
|
21
|
+
const [{ modules: t, defaultModuleId: r }, u, a] = await Promise.all([
|
|
22
|
+
i.modules(),
|
|
23
|
+
i.permissions().then((e) => ({ ok: !0, codes: e })).catch(() => ({ ok: !1, codes: [] })),
|
|
24
|
+
i.profile().then((e) => ({
|
|
23
25
|
sadm: e.isSuperAdmin,
|
|
24
26
|
avatar: e.avatar ?? null,
|
|
25
27
|
orgName: e.orgName ?? null,
|
|
@@ -27,28 +29,38 @@ function b() {
|
|
|
27
29
|
})).catch(() => {
|
|
28
30
|
var e;
|
|
29
31
|
return {
|
|
30
|
-
sadm: ((e =
|
|
32
|
+
sadm: ((e = d().userInfo) == null ? void 0 : e.isSuperAdmin) ?? !1,
|
|
31
33
|
avatar: null,
|
|
32
34
|
orgName: null,
|
|
33
35
|
phoneTail: null
|
|
34
36
|
};
|
|
35
37
|
})
|
|
36
38
|
]);
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
return
|
|
39
|
+
o.modules = t, o.defaultModuleId = r ?? null, o.permissionCodes = u.codes, o.permissionsLoaded = u.ok, o.isSuperAdmin = a.sadm;
|
|
40
|
+
const s = d();
|
|
41
|
+
if (s.userInfo && (s.userInfo.avatar = a.avatar, s.userInfo.orgName = a.orgName, s.userInfo.phoneTail = a.phoneTail), t.length === 0) return { chooser: !0 };
|
|
42
|
+
const f = o.currentModuleId;
|
|
43
|
+
return f && t.some((e) => e.id === f) ? l(f) : t.length === 1 ? l(t[0].id) : r && t.some((e) => e.id === r) ? l(r) : { chooser: !0 };
|
|
42
44
|
}
|
|
43
|
-
async function
|
|
44
|
-
await
|
|
45
|
+
async function I(t, r) {
|
|
46
|
+
await l(t), y().clearTabs(), T.replace(r ?? o.homePath);
|
|
45
47
|
}
|
|
46
|
-
async function
|
|
47
|
-
|
|
48
|
+
async function w(t) {
|
|
49
|
+
const r = c(t), u = o.modules.filter((n) => n.id !== o.currentModuleId), a = u.find(
|
|
50
|
+
(n) => n.defaultRoute && c(n.defaultRoute) === r
|
|
51
|
+
);
|
|
52
|
+
if (a) return a;
|
|
53
|
+
const s = new Set(P()), e = (await Promise.all(
|
|
54
|
+
u.map((n) => i.menu(n.id).catch(() => []))
|
|
55
|
+
)).findIndex((n) => S(n, r, s));
|
|
56
|
+
return e < 0 ? null : u[e];
|
|
48
57
|
}
|
|
49
|
-
|
|
58
|
+
async function M(t) {
|
|
59
|
+
await i.setDefaultModule(t), o.defaultModuleId = t;
|
|
60
|
+
}
|
|
61
|
+
return { enter: l, enterInitial: h, switchModule: I, findOwnerModule: w, setDefault: M };
|
|
50
62
|
}
|
|
51
63
|
export {
|
|
52
|
-
|
|
64
|
+
z as useModule
|
|
53
65
|
};
|
|
54
66
|
//# sourceMappingURL=useModule.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModule.js","sources":["../../src/composables/useModule.ts"],"sourcesContent":["// 多应用门户的进门/切换流程:登录后该直接进哪个应用、还是弹应用选择器,以及切换时清理旧应用的路由与标签。\n// enterInitial() 也是 F5/深链的恢复入口(动态路由只在内存里),路由守卫会调它。\n// 坑:权限码拉取失败时 permissionsLoaded 保持 false,v-auth 走 fail-closed 藏按钮 —— 宁可少显示,不谎报有权限。\nimport { useAuthStore } from '#/stores/auth'\nimport { useTabsStore } from '#/stores/tabs'\nimport { useUserStore } from '#/stores/user'\nimport { personalApi } from '#/api'\nimport { buildRoutesForModule } from './useAuthMenu'\nimport { router } from '#/router'\n\ntype EnterResult = { chooser: true } | { chooser: false; moduleId: number }\n\n// 进门单飞:F5/深链时守卫可能被并发导航各调一次 enterInitial,而 buildRoutesForModule 起手就 resetRouter()\n// —— 后一次会把前一次刚注册的动态路由整片摘掉,表现是偶发 404/白屏。模块级 promise 让并发合流成一次。\nlet entering: Promise<EnterResult> | null = null\n\n/** 进指定应用:重建它的动态路由。 */\nasync function enter(moduleId: number): Promise<EnterResult> {\n await buildRoutesForModule(moduleId)\n return { chooser: false, moduleId }\n}\n\n/** 门户:登录后决定直接进 / 进默认 / 弹选择器,以及切换应用。逻辑与 Naive 无关。 */\nexport function useModule() {\n const auth = useAuthStore()\n\n function enterInitial(): Promise<EnterResult> {\n entering ??= doEnterInitial().finally(() => {\n entering = null\n })\n return entering\n }\n\n async function doEnterInitial(): Promise<EnterResult> {\n // 并行拉模块 + 当前用户权限码。权限码喂 v-auth:成功(哪怕空集=超管)才标 loaded;\n // 失败不阻断进门户,但 permissionsLoaded 保持 false → v-auth fail-closed(藏按钮),不谎报\"有权限\"。\n // profile 拿超管标记喂 v-auth(只对超管 fail-open,普通用户空集则隐藏)+ 顺手回填顶栏头像;失败按普通用户处理(安全侧,不误放行)。\n const [{ modules, defaultModuleId }, perm, profile] = await Promise.all([\n personalApi.modules(),\n personalApi\n .permissions()\n .then(codes => ({ ok: true, codes }))\n .catch(() => ({ ok: false, codes: [] as string[] })),\n personalApi\n .profile()\n .then(p => ({\n sadm: p.isSuperAdmin,\n avatar: p.avatar ?? null,\n orgName: p.orgName ?? null,\n phoneTail: p.phone ? p.phone.slice(-4) : null,\n }))\n .catch(() => ({\n sadm: useUserStore().userInfo?.isSuperAdmin ?? false,\n avatar: null,\n orgName: null,\n phoneTail: null,\n })),\n ])\n auth.modules = modules\n auth.defaultModuleId = defaultModuleId ?? null\n auth.permissionCodes = perm.codes\n auth.permissionsLoaded = perm.ok\n auth.isSuperAdmin = profile.sadm\n // 顶栏头像:登录出参不含 avatar,这里回填(取不到按无头像处理,顶栏回落图标)\n const user = useUserStore()\n if (user.userInfo) {\n user.userInfo.avatar = profile.avatar\n user.userInfo.orgName = profile.orgName // 水印要印机构和手机尾号,同样只有 profile 里有\n user.userInfo.phoneTail = profile.phoneTail\n }\n if (modules.length === 0) return { chooser: true } // 空态:选择器里提示未分配应用\n // F5/深链优先重建\"上次所在应用\"(持久化的 currentModuleId),让其动态路由复活,跨应用深链不落 404。\n const remembered = auth.currentModuleId\n if (remembered && modules.some(m => m.id === remembered)) return enter(remembered)\n if (modules.length === 1) return enter(modules[0]!.id)\n if (defaultModuleId && modules.some(m => m.id === defaultModuleId))\n return enter(defaultModuleId)\n return { chooser: true }\n }\n\n async function switchModule(moduleId: number): Promise<void> {\n await enter(moduleId)\n useTabsStore().clearTabs() // 切应用 → 标签归零(新应用路由已重建)\n router.replace(auth.homePath) //
|
|
1
|
+
{"version":3,"file":"useModule.js","sources":["../../src/composables/useModule.ts"],"sourcesContent":["// 多应用门户的进门/切换流程:登录后该直接进哪个应用、还是弹应用选择器,以及切换时清理旧应用的路由与标签。\n// enterInitial() 也是 F5/深链的恢复入口(动态路由只在内存里),路由守卫会调它。\n// 坑:权限码拉取失败时 permissionsLoaded 保持 false,v-auth 走 fail-closed 藏按钮 —— 宁可少显示,不谎报有权限。\nimport { useAuthStore } from '#/stores/auth'\nimport { useTabsStore } from '#/stores/tabs'\nimport { useUserStore } from '#/stores/user'\nimport { personalApi } from '#/api'\nimport { buildRoutesForModule } from './useAuthMenu'\nimport { menuHasPath, normalizeRoutePath } from './authMenuRoute'\nimport { viewComponentPaths } from '#/router/viewRegistry'\nimport { router } from '#/router'\nimport type { AppModule, MenuNode } from '#/types/menu'\n\ntype EnterResult = { chooser: true } | { chooser: false; moduleId: number }\n\n// 进门单飞:F5/深链时守卫可能被并发导航各调一次 enterInitial,而 buildRoutesForModule 起手就 resetRouter()\n// —— 后一次会把前一次刚注册的动态路由整片摘掉,表现是偶发 404/白屏。模块级 promise 让并发合流成一次。\nlet entering: Promise<EnterResult> | null = null\n\n/** 进指定应用:重建它的动态路由。 */\nasync function enter(moduleId: number): Promise<EnterResult> {\n await buildRoutesForModule(moduleId)\n return { chooser: false, moduleId }\n}\n\n/** 门户:登录后决定直接进 / 进默认 / 弹选择器,以及切换应用。逻辑与 Naive 无关。 */\nexport function useModule() {\n const auth = useAuthStore()\n\n function enterInitial(): Promise<EnterResult> {\n entering ??= doEnterInitial().finally(() => {\n entering = null\n })\n return entering\n }\n\n async function doEnterInitial(): Promise<EnterResult> {\n // 并行拉模块 + 当前用户权限码。权限码喂 v-auth:成功(哪怕空集=超管)才标 loaded;\n // 失败不阻断进门户,但 permissionsLoaded 保持 false → v-auth fail-closed(藏按钮),不谎报\"有权限\"。\n // profile 拿超管标记喂 v-auth(只对超管 fail-open,普通用户空集则隐藏)+ 顺手回填顶栏头像;失败按普通用户处理(安全侧,不误放行)。\n const [{ modules, defaultModuleId }, perm, profile] = await Promise.all([\n personalApi.modules(),\n personalApi\n .permissions()\n .then(codes => ({ ok: true, codes }))\n .catch(() => ({ ok: false, codes: [] as string[] })),\n personalApi\n .profile()\n .then(p => ({\n sadm: p.isSuperAdmin,\n avatar: p.avatar ?? null,\n orgName: p.orgName ?? null,\n phoneTail: p.phone ? p.phone.slice(-4) : null,\n }))\n .catch(() => ({\n sadm: useUserStore().userInfo?.isSuperAdmin ?? false,\n avatar: null,\n orgName: null,\n phoneTail: null,\n })),\n ])\n auth.modules = modules\n auth.defaultModuleId = defaultModuleId ?? null\n auth.permissionCodes = perm.codes\n auth.permissionsLoaded = perm.ok\n auth.isSuperAdmin = profile.sadm\n // 顶栏头像:登录出参不含 avatar,这里回填(取不到按无头像处理,顶栏回落图标)\n const user = useUserStore()\n if (user.userInfo) {\n user.userInfo.avatar = profile.avatar\n user.userInfo.orgName = profile.orgName // 水印要印机构和手机尾号,同样只有 profile 里有\n user.userInfo.phoneTail = profile.phoneTail\n }\n if (modules.length === 0) return { chooser: true } // 空态:选择器里提示未分配应用\n // F5/深链优先重建\"上次所在应用\"(持久化的 currentModuleId),让其动态路由复活,跨应用深链不落 404。\n const remembered = auth.currentModuleId\n if (remembered && modules.some(m => m.id === remembered)) return enter(remembered)\n if (modules.length === 1) return enter(modules[0]!.id)\n if (defaultModuleId && modules.some(m => m.id === defaultModuleId))\n return enter(defaultModuleId)\n return { chooser: true }\n }\n\n /** `target` 缺省落新应用自己的首页;给了就落到那个地址(跨应用深链:先切过去再打开它)。 */\n async function switchModule(moduleId: number, target?: string): Promise<void> {\n await enter(moduleId)\n useTabsStore().clearTabs() // 切应用 → 标签归零(新应用路由已重建)\n router.replace(target ?? auth.homePath)\n }\n\n /**\n * 用户有权进入的其它应用里,哪个注册了 `path` 这个页面;没有则 null。\n * 动态路由只按当前应用的菜单注册,别的应用的页面地址会落到通配 404 —— 404 页靠它把\n * 「地址不存在」和「页面属于别的应用」分开。只在命中 404 时才调,所以不预拉各应用的菜单。\n * 某个应用的菜单拉取失败按「没有」处理:这是个提示,不该因它再报一层错。\n */\n async function findOwnerModule(path: string): Promise<AppModule | null> {\n const target = normalizeRoutePath(path)\n const others = auth.modules.filter(m => m.id !== auth.currentModuleId)\n // defaultRoute 是现成的索引,命中就不必拉菜单\n const byDefault = others.find(\n m => m.defaultRoute && normalizeRoutePath(m.defaultRoute) === target,\n )\n if (byDefault) return byDefault\n\n const viewKeys = new Set(viewComponentPaths())\n const trees = await Promise.all(\n others.map(m => personalApi.menu(m.id).catch(() => [] as MenuNode[])),\n )\n const hit = trees.findIndex(tree => menuHasPath(tree, target, viewKeys))\n return hit < 0 ? null : others[hit]!\n }\n\n async function setDefault(moduleId: number): Promise<void> {\n await personalApi.setDefaultModule(moduleId)\n auth.defaultModuleId = moduleId // 本地同步,选择页角标立刻转移,不必重拉 /personal/modules\n }\n\n return { enter, enterInitial, switchModule, findOwnerModule, setDefault }\n}\n"],"names":["entering","enter","moduleId","buildRoutesForModule","useModule","auth","useAuthStore","enterInitial","doEnterInitial","modules","defaultModuleId","perm","profile","personalApi","codes","p","_a","useUserStore","user","remembered","m","switchModule","target","useTabsStore","router","findOwnerModule","path","normalizeRoutePath","others","byDefault","viewKeys","viewComponentPaths","hit","tree","menuHasPath","setDefault"],"mappings":"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"}
|