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.
Files changed (165) hide show
  1. package/dist/api/index.d.ts +19 -1
  2. package/dist/api/index.js +85 -66
  3. package/dist/api/index.js.map +1 -1
  4. package/dist/api/schema.d.ts +448 -1
  5. package/dist/assets/icons/ph-subset.json.js +1 -1
  6. package/dist/components/FormContainer/index.vue.js +87 -75
  7. package/dist/components/FormContainer/index.vue.js.map +1 -1
  8. package/dist/components/UserPicker/UserAvatar.vue.d.ts +8 -0
  9. package/dist/components/UserPicker/UserAvatar.vue.js +8 -0
  10. package/dist/components/UserPicker/UserAvatar.vue.js.map +1 -0
  11. package/dist/components/UserPicker/UserAvatar2.vue.js +38 -0
  12. package/dist/components/UserPicker/UserAvatar2.vue.js.map +1 -0
  13. package/dist/components/UserPicker/index.vue.js +1 -1
  14. package/dist/components/UserPicker/index2.vue.js +328 -186
  15. package/dist/components/UserPicker/index2.vue.js.map +1 -1
  16. package/dist/composables/authMenuRoute.d.ts +10 -0
  17. package/dist/composables/authMenuRoute.js +39 -23
  18. package/dist/composables/authMenuRoute.js.map +1 -1
  19. package/dist/composables/useModule.d.ts +3 -1
  20. package/dist/composables/useModule.js +44 -32
  21. package/dist/composables/useModule.js.map +1 -1
  22. package/dist/index.js +246 -242
  23. package/dist/layouts/components/SideNav.vue.js +2 -2
  24. package/dist/layouts/components/SideNav2.vue.js.map +1 -1
  25. package/dist/lib/runtime.js +1 -1
  26. package/dist/lib/watermark.d.ts +1 -1
  27. package/dist/locales/en-US.d.ts +68 -3
  28. package/dist/locales/en-US.js +74 -8
  29. package/dist/locales/en-US.js.map +1 -1
  30. package/dist/locales/kernelMenuTitles.js +1 -0
  31. package/dist/locales/kernelMenuTitles.js.map +1 -1
  32. package/dist/locales/zh-CN.d.ts +68 -3
  33. package/dist/locales/zh-CN.js +74 -8
  34. package/dist/locales/zh-CN.js.map +1 -1
  35. package/dist/router/viewRegistry.js +6 -6
  36. package/dist/router/viewRegistry.js.map +1 -1
  37. package/dist/style.css +1 -1
  38. package/dist/theme/naive-theme.js +80 -77
  39. package/dist/theme/naive-theme.js.map +1 -1
  40. package/dist/types/api.d.ts +91 -0
  41. package/dist/types/api.js +17 -15
  42. package/dist/types/api.js.map +1 -1
  43. package/dist/utils/shake.d.ts +1 -0
  44. package/dist/utils/shake.js +30 -0
  45. package/dist/utils/shake.js.map +1 -0
  46. package/dist/utils/tree.d.ts +15 -0
  47. package/dist/utils/tree.js +43 -29
  48. package/dist/utils/tree.js.map +1 -1
  49. package/dist/utils/treeCard.d.ts +6 -0
  50. package/dist/utils/treeCard.js +9 -0
  51. package/dist/utils/treeCard.js.map +1 -0
  52. package/dist/views/error/404.vue.js +2 -2
  53. package/dist/views/error/4042.vue.js +81 -24
  54. package/dist/views/error/4042.vue.js.map +1 -1
  55. package/dist/views/login/LoginForm.vue.js +2 -2
  56. package/dist/views/login/LoginForm2.vue.js +135 -135
  57. package/dist/views/login/LoginForm2.vue.js.map +1 -1
  58. package/dist/views/login/components/LoginDesk.vue.js +2 -2
  59. package/dist/views/login/components/LoginDesk2.vue.js +59 -47
  60. package/dist/views/login/components/LoginDesk2.vue.js.map +1 -1
  61. package/dist/views/login/components/LoginHeroPanel.vue.js +2 -2
  62. package/dist/views/login/components/LoginHeroPanel2.vue.js.map +1 -1
  63. package/dist/views/personal/notice2.vue.js +1 -1
  64. package/dist/views/personal/notice2.vue.js.map +1 -1
  65. package/dist/views/personal/sessions.vue.js +2 -2
  66. package/dist/views/personal/sessions2.vue.js +41 -40
  67. package/dist/views/personal/sessions2.vue.js.map +1 -1
  68. package/dist/views/system/ai-usage/index.vue.js +3 -3
  69. package/dist/views/system/ai-usage/index2.vue.js +13 -12
  70. package/dist/views/system/ai-usage/index2.vue.js.map +1 -1
  71. package/dist/views/system/config/components/AdvancedSection.vue.js +3 -3
  72. package/dist/views/system/config/components/AdvancedSection2.vue.js +2 -1
  73. package/dist/views/system/config/components/AdvancedSection2.vue.js.map +1 -1
  74. package/dist/views/system/dict/index.vue.js +2 -2
  75. package/dist/views/system/dict/index2.vue.js +12 -1
  76. package/dist/views/system/dict/index2.vue.js.map +1 -1
  77. package/dist/views/system/file/index2.vue.js +1 -0
  78. package/dist/views/system/file/index2.vue.js.map +1 -1
  79. package/dist/views/system/job/index.vue.js +3 -3
  80. package/dist/views/system/job/index2.vue.js +182 -181
  81. package/dist/views/system/job/index2.vue.js.map +1 -1
  82. package/dist/views/system/job-log/components/AttemptTable2.vue.js +8 -3
  83. package/dist/views/system/job-log/components/AttemptTable2.vue.js.map +1 -1
  84. package/dist/views/system/job-log/index.vue.js +1 -1
  85. package/dist/views/system/job-log/index2.vue.js +5 -5
  86. package/dist/views/system/job-log/index2.vue.js.map +1 -1
  87. package/dist/views/system/job-monitor/index.vue.js +2 -2
  88. package/dist/views/system/job-monitor/index2.vue.js +46 -46
  89. package/dist/views/system/job-monitor/index2.vue.js.map +1 -1
  90. package/dist/views/system/log/exception/index.vue.js +2 -2
  91. package/dist/views/system/log/exception/index2.vue.js +8 -5
  92. package/dist/views/system/log/exception/index2.vue.js.map +1 -1
  93. package/dist/views/system/log/login/index2.vue.js +21 -12
  94. package/dist/views/system/log/login/index2.vue.js.map +1 -1
  95. package/dist/views/system/log/op/index.vue.js +2 -2
  96. package/dist/views/system/log/op/index2.vue.js +50 -40
  97. package/dist/views/system/log/op/index2.vue.js.map +1 -1
  98. package/dist/views/system/menu/components/ButtonManager.vue.js +2 -2
  99. package/dist/views/system/menu/components/ButtonManager2.vue.js +45 -36
  100. package/dist/views/system/menu/components/ButtonManager2.vue.js.map +1 -1
  101. package/dist/views/system/menu/index.vue.js +1 -1
  102. package/dist/views/system/menu/index2.vue.js +162 -151
  103. package/dist/views/system/menu/index2.vue.js.map +1 -1
  104. package/dist/views/system/module/index.vue.js +1 -1
  105. package/dist/views/system/module/index2.vue.js +171 -117
  106. package/dist/views/system/module/index2.vue.js.map +1 -1
  107. package/dist/views/system/notice/index.vue.js +3 -3
  108. package/dist/views/system/notice/index2.vue.js +1 -0
  109. package/dist/views/system/notice/index2.vue.js.map +1 -1
  110. package/dist/views/system/org/index.vue.js +2 -2
  111. package/dist/views/system/org/index2.vue.js +204 -195
  112. package/dist/views/system/org/index2.vue.js.map +1 -1
  113. package/dist/views/system/position/index.vue.js +2 -2
  114. package/dist/views/system/position/index2.vue.js +57 -48
  115. package/dist/views/system/position/index2.vue.js.map +1 -1
  116. package/dist/views/system/recycle/index.vue.js +1 -1
  117. package/dist/views/system/recycle/index2.vue.js +3 -1
  118. package/dist/views/system/recycle/index2.vue.js.map +1 -1
  119. package/dist/views/system/role/components/GrantMenuSheet.vue.d.ts +2 -0
  120. package/dist/views/system/role/components/GrantMenuSheet.vue.js +2 -2
  121. package/dist/views/system/role/components/GrantMenuSheet2.vue.js +54 -49
  122. package/dist/views/system/role/components/GrantMenuSheet2.vue.js.map +1 -1
  123. package/dist/views/system/role/components/GrantMenuTable.vue.js +1 -1
  124. package/dist/views/system/role/components/GrantMenuTable2.vue.js +159 -195
  125. package/dist/views/system/role/components/GrantMenuTable2.vue.js.map +1 -1
  126. package/dist/views/system/role/components/grantMenuGroups.d.ts +54 -0
  127. package/dist/views/system/role/components/grantMenuGroups.js +116 -76
  128. package/dist/views/system/role/components/grantMenuGroups.js.map +1 -1
  129. package/dist/views/system/role/index.vue.js +2 -2
  130. package/dist/views/system/role/index2.vue.js +152 -145
  131. package/dist/views/system/role/index2.vue.js.map +1 -1
  132. package/dist/views/system/session/index2.vue.js +39 -35
  133. package/dist/views/system/session/index2.vue.js.map +1 -1
  134. package/dist/views/system/user/components/UserGrantEffectiveTable.vue.d.ts +10 -0
  135. package/dist/views/system/user/components/UserGrantEffectiveTable.vue.js +5 -0
  136. package/dist/views/system/user/components/UserGrantEffectiveTable.vue.js.map +1 -0
  137. package/dist/views/system/user/components/UserGrantEffectiveTable2.vue.js +96 -0
  138. package/dist/views/system/user/components/UserGrantEffectiveTable2.vue.js.map +1 -0
  139. package/dist/views/system/user/components/UserGrantMenuSheet.vue.d.ts +13 -0
  140. package/dist/views/system/user/components/UserGrantMenuSheet.vue.js +8 -0
  141. package/dist/views/system/user/components/UserGrantMenuSheet.vue.js.map +1 -0
  142. package/dist/views/system/user/components/UserGrantMenuSheet2.vue.js +216 -0
  143. package/dist/views/system/user/components/UserGrantMenuSheet2.vue.js.map +1 -0
  144. package/dist/views/system/user/components/UserGrantMenuTable.vue.d.ts +13 -0
  145. package/dist/views/system/user/components/UserGrantMenuTable.vue.js +8 -0
  146. package/dist/views/system/user/components/UserGrantMenuTable.vue.js.map +1 -0
  147. package/dist/views/system/user/components/UserGrantMenuTable2.vue.js +186 -0
  148. package/dist/views/system/user/components/UserGrantMenuTable2.vue.js.map +1 -0
  149. package/dist/views/system/user/components/UserGrantNodeRow.vue.d.ts +31 -0
  150. package/dist/views/system/user/components/UserGrantNodeRow.vue.js +8 -0
  151. package/dist/views/system/user/components/UserGrantNodeRow.vue.js.map +1 -0
  152. package/dist/views/system/user/components/UserGrantNodeRow2.vue.js +171 -0
  153. package/dist/views/system/user/components/UserGrantNodeRow2.vue.js.map +1 -0
  154. package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.d.ts +21 -0
  155. package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.js +5 -0
  156. package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.js.map +1 -0
  157. package/dist/views/system/user/components/UserGrantOverviewDrawer2.vue.js +178 -0
  158. package/dist/views/system/user/components/UserGrantOverviewDrawer2.vue.js.map +1 -0
  159. package/dist/views/system/user/components/userGrantState.d.ts +64 -0
  160. package/dist/views/system/user/components/userGrantState.js +117 -0
  161. package/dist/views/system/user/components/userGrantState.js.map +1 -0
  162. package/dist/views/system/user/index.vue.js +2 -2
  163. package/dist/views/system/user/index2.vue.js +369 -295
  164. package/dist/views/system/user/index2.vue.js.map +1 -1
  165. 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":";;;;;;;;;;;;;;;;;;;;;;;AAeA,UAAMA,IAAQC,GACRC,IAAOC,GAEP,EAAE,GAAAC,EAAA,IAAMC,GAAA,GACRC,IAAUC,GAAA,GAEVC,IAAOC,EAAI,EAAK,GAChBC,IAAWD,EAAA,GAOX,EAAE,IAAAE,EAAA,IAAOC,GAAA,GACT,EAAE,OAAOC,EAAA,IAASC,GAAA,GAClBC,IAAUC,EAAS,MAAML,EAAG,UAAU,YAAYE,EAAK,QAAQ,GAAG,GAClEI,IAAUD,EAAS,MAAOL,EAAG,UAAU,WAAW,WAAW,OAAQ,GACrEO,IAAMT,EAA0B,OAAO,GACvCU,IAAUH,EAAS,MAAOD,EAAQ,QAAQ,UAAU,OAAQ,GAG5DK,IAAUX,EAAc,EAAE,GAC1BY,IAAUL,EAAS,MAEhB,CADK,EAAE,IAAI,GAAG,UAAU,IAAI,MAAMZ,EAAE,YAAY,GAAG,UAAUkB,GAAUF,EAAQ,KAAK,EAAA,CAChF,CACZ,GACKG,IAAiBd,EAAmB,IAAI,GACxCe,IAAQR;AAAA,MAAS,MACrBO,EAAe,SAASA,EAAe,UAAU,IAAIA,EAAe,QAAQ;AAAA,IAAA;AAG9E,IAAAE,GAAU,YAAY;AACpB,UAAI;AACF,QAAAL,EAAQ,QAAQ,MAAMM,GAAO,KAAA;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAED,aAASC,EAAYC,GAA8B;;AACjD,MAAAL,EAAe,QAASK,EAAK,CAAC,KAAgB,OAC9CC,IAAAnB,EAAS,UAAT,QAAAmB,EAAgB;AAAA,IAClB;AAEA,aAASC,EAAUC,GAAoB;;AACrC,MAAAR,EAAe,QAAQQ,KAAO,OAC9BF,IAAAnB,EAAS,UAAT,QAAAmB,EAAgB;AAAA,IAClB;AAGA,UAAMG,IAAcvB,EAAyB,EAAE,GACzCwB,IAAajB,EAAS,MAAM,IAAI,IAAIhB,EAAM,cAAc,CAAA,CAAE,CAAC,GAE3DkC,IAAU,CAACC,MACfC,EAAQ,KAAK;AAAA,MACX,MAAMD,EAAO;AAAA,MACb,UAAUA,EAAO;AAAA,MACjB,SAASA,EAAO;AAAA,MAChB,OAAOX,EAAM;AAAA,IAAA,CACd,GAEGa,IAAwC;AAAA,MAC5C,EAAE,MAAM,YAAA;AAAA,MACR,EAAE,KAAK,QAAQ,OAAO,MAAMjC,EAAE,WAAW,GAAG,OAAO,IAAA;AAAA,MACnD;AAAA,QACE,KAAK;AAAA,QACL,OAAO,MAAMA,EAAE,cAAc;AAAA,QAC7B,OAAO;AAAA,QACP,QAAQ,EAAE,SAASkC,GAAe,MAAA;AAAA,MAAM;AAAA,MAE1C;AAAA,QACE,KAAK;AAAA,QACL,OAAO,MAAMlC,EAAE,kBAAkB;AAAA,QACjC,OAAO;AAAA,QACP,QAAQ,CAAAmC,MAAK;AACX,gBAAMC,IAAUC,EAAY,MAAM,IAAIF,EAAE,EAAE,KAAKN,EAAW,MAAM,IAAIM,EAAE,EAAE;AACxE,iBAAOG;AAAA,YACLC;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,MAAM;AAAA,cACN,YAAY;AAAA,cACZ,UAAUH;AAAA,cACV,SAAS,MAAMI,EAAS,CAACL,CAAC,CAAC;AAAA,YAAA;AAAA,YAE7B,MAAiBnC,EAAVoC,IAAY,qBAAwB,gBAAN;AAAA,UAAsB;AAAA,QAE/D;AAAA,MAAA;AAAA,IACF,GAIIK,IAAWC,GAAqB,EAAE,GAClCL,IAAczB,EAAS,MAAM,IAAI,IAAI6B,EAAS,IAAI,CAAAE,MAAKA,EAAE,EAAE,CAAC,CAAC,GAC7DC,IAAiBvC,EAAI,EAAE,GACvBwC,IAAmBjC,EAAS,MAAM;AACtC,YAAMkC,IAAIF,EAAe,MAAM,KAAA,EAAO,YAAA;AACtC,aAAKE,IACEL,EAAS;AAAA,QACd,CAAAE,MAAKA,EAAE,KAAK,YAAA,EAAc,SAASG,CAAC,KAAKH,EAAE,QAAQ,YAAA,EAAc,SAASG,CAAC;AAAA,MAAA,IAF9DL;AAAA,IAIjB,CAAC;AAED,aAASD,EAASO,GAAmB;AACnC,iBAAWJ,KAAKI;AACd,QAAI,CAACV,EAAY,MAAM,IAAIM,EAAE,EAAE,KAAK,CAACd,EAAW,MAAM,IAAIc,EAAE,EAAE,KAC5DF,EAAS,KAAKE,CAAC;AAAA,IAGrB;AAEA,aAASK,IAAa;;AAGpB,YAAMC,KAAQC,GAAMzB,IAAAnB,EAAS,UAAT,gBAAAmB,EAAgB,IAAI,KAAK,CAAA,GAAI,OAAO,OAAKG,EAAY,MAAM,SAASO,EAAE,EAAE,CAAC;AAC7F,MAAAK,EAASS,CAAI,GACbrB,EAAY,QAAQ,CAAA;AAAA,IACtB;AAEA,aAASuB,EAAWC,GAAY;AAC9B,YAAMC,IAAMZ,EAAS,UAAU,CAAAE,MAAKA,EAAE,OAAOS,CAAE;AAC/C,MAAIC,KAAO,KAAGZ,EAAS,OAAOY,GAAK,CAAC;AAAA,IACtC;AAEA,aAASC,KAAgB;AACvB,MAAAb,EAAS,OAAO,GAAGA,EAAS,MAAM;AAAA,IACpC;AAGA,mBAAec,GAAKC,GAAgB;AAQlC,UAPAf,EAAS,OAAO,GAAGA,EAAS,MAAM,GAClCb,EAAY,QAAQ,CAAA,GACpBgB,EAAe,QAAQ,IACvBzB,EAAe,QAAQ,MACvBL,EAAI,QAAQ,SACZV,EAAK,QAAQ,IAEToD,KAAA,QAAAA,EAAK;AACP,YAAI;AAIF,gBAAM,EAAE,OAAAC,MAAU,MAAMzB,EAAQ,KAAK,EAAE,MAAM,GAAG,UAAU,KAAK,GACzD0B,IAAMD,EAAM,OAAO,CAAAd,MAAKa,EAAI,SAASb,EAAE,EAAE,CAAC,GAE1CgB,IAAQ,IAAI,IAAID,EAAI,IAAI,CAAAf,MAAKA,EAAE,EAAE,CAAC;AACxC,qBAAWA,KAAKe,EAAK,CAAAjB,EAAS,KAAKE,CAAC;AACpC,qBAAWS,KAAMI;AACf,YAAKG,EAAM,IAAIP,CAAE,OAAY,KAAK,EAAE,IAAAA,GAAI,SAAS,OAAOA,CAAE,GAAG,MAAM,OAAOA,CAAE,GAAe;AAAA,QAE/F,QAAQ;AACN,qBAAWA,KAAMI,EAAK,CAAAf,EAAS,KAAK,EAAE,IAAAW,GAAI,SAAS,OAAOA,CAAE,GAAG,MAAM,OAAOA,CAAE,GAAe;AAAA,QAC/F;AAAA,IAEJ;AAEA,mBAAeQ,KAAgB;AAC7B,YAAMJ,IAAMf,EAAS,IAAI,CAAAE,MAAKA,EAAE,EAAE;AAClC,MAAA7C,EAAK,WAAW0D,CAAG;AAAA,IACrB;AAEA,WAAAK,EAAa,EAAE,MAAAN,IAAM,mBAInBO,EA6GgBC,IAAA;AAAA,MA5GN,MAAM3D,EAAA;AAAA,8CAAAA,EAAI,QAAA4D;AAAA,MACjB,SAASnD,EAAA;AAAA,MACT,OAAOoD,EAAAjE,CAAA,EAAC,kBAAA;AAAA,MACR,OAAO;AAAA,MACP,cAAY4D;AAAA,MACZ,gBAAcK,EAAAjE,CAAA,EAAC,gBAAA;AAAA,IAAA;iBAEhB,MAGS;AAAA,QAHKW,EAAA,cAAdmD,EAGSG,EAAAC,EAAA,GAAA;AAAA;UAHsB,OAAOpD,EAAA;AAAA,mDAAAA,EAAG,QAAAkD;AAAA,UAAE,MAAK;AAAA,UAAU,UAAA;AAAA,UAAS,OAAM;AAAA,QAAA;qBACvE,MAA2D;AAAA,YAA3DG,EAA2DF,EAAAG,CAAA,GAAA,EAApD,MAAK,WAAO;AAAA,yBAAC,MAA+B;AAAA,oBAA5BH,EAAAjE,CAAA,EAAC,sBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACxBmE,EAAoFF,EAAAG,CAAA,GAAA,EAA7E,MAAK,cAAU;AAAA,yBAAC,MAA8B;AAAA,gBAA3BC,EAAAC,EAAAL,EAAAjE,CAAA,4BAA2B,MAACsE,EAAG7B,EAAS,MAAM,IAAG,KAAC,CAAA;AAAA,cAAA;;;;;;QAE9E8B,EAgGM,OAAA;AAAA,UAhGD,OAAKC,GAAA,CAAC,eAAa,EAAA,cAAyB7D,EAAA,OAAO,CAAA;AAAA,QAAA;UAE1CA,EAAA,qBAAZ8D,KAAAC,EAaM,OAbNC,IAaM;AAAA,YAZJJ,EAAoD,OAApDK,IAAoDN,EAAvBL,EAAAjE,CAAA,EAAC,WAAA,CAAA,GAAA,CAAA;AAAA,YAC9BmE,EAUEF,EAAAY,EAAA,GAAA;AAAA,cATC,MAAM5D,EAAA;AAAA,cACP,aAAU;AAAA,cACV,eAAY;AAAA,cACZ,kBAAe;AAAA,cACf,YAAA;AAAA,cACA,cAAA;AAAA,cACA,sBAAA;AAAA,cACC,iBAAeE,EAAA,SAAc,OAAA,CAAYA,EAAA,KAAc,IAAA,CAAA;AAAA,cACvD,yBAAsBI;AAAA,YAAA;;UAK3BuD,EAAAP,EAsCM,OAtCNQ,IAsCM;AAAA,YApCIpE,EAAA,cADRmD,EAWEG,EAAAe,EAAA,GAAA;AAAA;cATC,OAAO7D,EAAA,SAAc;AAAA,cACrB,SAASF,EAAA;AAAA,cACV,aAAU;AAAA,cACV,eAAY;AAAA,cACZ,kBAAe;AAAA,cACf,sBAAA;AAAA,cACA,MAAK;AAAA,cACL,OAAM;AAAA,cACL,kBAAcS;AAAA,YAAA;YAEjB6C,EAUM,OAVNU,IAUM;AAAA,cATJV,EAA4C,gBAAnCN,EAAAjE,CAAA,EAAC,sBAAA,CAAA,GAAA,CAAA;AAAA,cACVmE,EAOWF,EAAA1B,CAAA,GAAA;AAAA,gBANR,MAAMxB,EAAA;AAAA,gBACP,MAAK;AAAA,gBACJ,UAAUa,EAAA,MAAY,WAAM;AAAA,gBAC5B,SAAOoB;AAAA,cAAA;2BAER,MAAgC;AAAA,kBAA7BqB,EAAAC,EAAAL,EAAAjE,CAAA,8BAA6B,MAACsE,EAAG1C,QAAY,MAAM,IAAG,MAC3D,CAAA;AAAA,gBAAA;;;;YAEFuC,EAaEF,EAAAiB,EAAA,GAAA;AAAA,cAZC,qBAAmB;AAAA,uBAChB;AAAA,cAAJ,KAAI5E;AAAA,cACH,SAAA2B;AAAA,cACA,SAAAH;AAAA,cACA,QAAQ,EAAA,WAAA,QAAA;AAAA,cACR,YAAY,EAAA,UAAA,GAAA;AAAA,cACb,MAAK;AAAA,cACL,eAAY;AAAA,cACX,eAAa;AAAA,cACb,oBAAkBF,EAAA;AAAA,cAClB,2BAAuBuD,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAG3D,MAA+BI,EAAA,QAAcJ;AAAA,cACvE,SAAK2D,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAE,CAAAC,MAAKnB,EAAA/D,CAAA,EAAQ,MAAM+D,EAAAoB,EAAA,EAAeD,CAAC,CAAA;AAAA,YAAA;;YApCjC,CAAAE,GAAA,CAAA3E,EAAA,SAAWG,EAAA,UAAG,OAAA;AAAA,UAAA;UAyC5BgE,EAAAP,EAoCM,OApCNgB,IAoCM;AAAA,YAnCJhB,EAWM,OAXNiB,IAWM;AAAA,cAVJjB,EAAkE,QAAA,MAAAD,EAAzDL,KAAC,qBAAA,CAAA,IAA0B,QAAIxB,EAAS,MAAM,IAAG,KAAC,CAAA;AAAA,cAC3D0B,EAQWF,EAAA1B,CAAA,GAAA;AAAA,gBAPR,MAAMxB,EAAA;AAAA,gBACP,YAAA;AAAA,gBACA,MAAK;AAAA,gBACJ,UAAU0B,EAAS,WAAM;AAAA,gBACzB,SAAOa;AAAA,cAAA;2BAER,MAA2B;AAAA,sBAAxBW,EAAAjE,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;YAGRmE,EAMEF,EAAAwB,EAAA,GAAA;AAAA,cALQ,OAAO7C,EAAA;AAAA,uDAAAA,EAAc,QAAAoB;AAAA,cAC5B,MAAMrD,EAAA,QAAO,UAAA;AAAA,cACd,WAAA;AAAA,cACC,aAAasD,EAAAjE,CAAA,EAAC,eAAA;AAAA,cACf,OAAA,EAAA,iBAAA,MAAA;AAAA,YAAA;YAEFuE,EAeM,OAfNmB,IAeM;AAAA,sBAdJhB,EAQMiB,IAAA,MAAAC,GARW/C,EAAA,OAAgB,CAArBF,YAAZ+B,EAQM,OAAA;AAAA,gBAR8B,KAAK/B,EAAE;AAAA,gBAAI,OAAM;AAAA,cAAA;gBACnD4B,EAGO,QAHPsB,IAGO;AAAA,sBAFFlD,EAAE,IAAI,IAAG,KACZ,CAAA;AAAA,kBAAA4B,EAAuD,QAAvDuB,IAA+B,QAAInD,EAAE,OAAO,IAAG,KAAC,CAAA;AAAA,gBAAA;gBAElDwB,EAEWF,EAAA1B,CAAA,GAAA;AAAA,kBAFA,MAAMxB,EAAA;AAAA,kBAAS,YAAA;AAAA,kBAAW,MAAK;AAAA,kBAAS,SAAK,CAAAiD,MAAEb,EAAWR,EAAE,EAAE;AAAA,gBAAA;kBAC5D,QAAK,MAAkC;AAAA,oBAAlCwB,EAAkC4B,IAAA;AAAA,sBAAzB,MAAK;AAAA,sBAAQ,MAAM;AAAA,oBAAA;;;;;cAIxClD,EAAA,MAAiB,WAAM,UAD/BiB,EAIEG,EAAA+B,EAAA,GAAA;AAAA;gBAFC,aAAa/B,EAAAjE,CAAA,EAAC,eAAA;AAAA,gBACf,OAAA,EAAA,SAAA,SAAA;AAAA,cAAA;;;YAjCQ,CAAAsF,GAAA,CAAA3E,EAAA,SAAWG,EAAA,UAAG,UAAA;AAAA,UAAA;;;;;;;"}
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 n } from "../utils/url.js";
2
- import { menuTitleKey as p } from "../locales/menuTitle.js";
3
- import { MenuType as u } from "../types/menu.js";
4
- function s(t, a) {
5
- if (t.type !== u.Menu || !t.path || n(t.path)) return null;
6
- const r = t.path.startsWith("/") ? t.path : `/${t.path}`, e = `menu-${t.id}`, i = t.component;
7
- if (i && n(i))
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: r,
11
- name: e,
12
- title: p(t.title, t.path),
11
+ path: i,
12
+ name: n,
13
+ title: c(t.title, t.path),
13
14
  icon: t.icon,
14
- iframeSrc: i
15
+ iframeSrc: e
15
16
  };
16
- if (!i) return null;
17
- const c = i.replace(/^\/+/, "").replace(/\.vue$/, "");
18
- return a.has(c) ? {
17
+ if (!e) return null;
18
+ const a = e.replace(/^\/+/, "").replace(/\.vue$/, "");
19
+ return r.has(a) ? {
19
20
  kind: "view",
20
- path: r,
21
- name: e,
22
- title: p(t.title, t.path),
21
+ path: i,
22
+ name: n,
23
+ title: c(t.title, t.path),
23
24
  icon: t.icon,
24
- viewKey: c
25
+ viewKey: a
25
26
  } : {
26
27
  kind: "missing",
27
- path: r,
28
- name: e,
29
- title: p(t.title, t.path),
28
+ path: i,
29
+ name: n,
30
+ title: c(t.title, t.path),
30
31
  icon: t.icon,
31
- component: i
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
- s as describeMenuRoute
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":";;;AAiBO,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;"}
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 I } from "../stores/auth.js";
2
- import { useTabsStore as g } from "../stores/tabs.js";
3
- import { useUserStore as f } from "../stores/user.js";
4
- import { personalApi as s } from "../api/index.js";
5
- import { buildRoutesForModule as M } from "./useAuthMenu.js";
6
- import { router as S } from "../router/index.js";
7
- let i = null;
8
- async function n(r) {
9
- return await M(r), { chooser: !1, moduleId: r };
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 b() {
12
- const r = I();
13
- function d() {
14
- return i ?? (i = c().finally(() => {
15
- i = null;
16
- })), i;
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 c() {
19
- const [{ modules: o, defaultModuleId: t }, m, a] = await Promise.all([
20
- s.modules(),
21
- s.permissions().then((e) => ({ ok: !0, codes: e })).catch(() => ({ ok: !1, codes: [] })),
22
- s.profile().then((e) => ({
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 = f().userInfo) == null ? void 0 : e.isSuperAdmin) ?? !1,
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
- r.modules = o, r.defaultModuleId = t ?? null, r.permissionCodes = m.codes, r.permissionsLoaded = m.ok, r.isSuperAdmin = a.sadm;
38
- const u = f();
39
- if (u.userInfo && (u.userInfo.avatar = a.avatar, u.userInfo.orgName = a.orgName, u.userInfo.phoneTail = a.phoneTail), o.length === 0) return { chooser: !0 };
40
- const l = r.currentModuleId;
41
- return l && o.some((e) => e.id === l) ? n(l) : o.length === 1 ? n(o[0].id) : t && o.some((e) => e.id === t) ? n(t) : { chooser: !0 };
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 p(o) {
44
- await n(o), g().clearTabs(), S.replace(r.homePath);
45
+ async function I(t, r) {
46
+ await l(t), y().clearTabs(), T.replace(r ?? o.homePath);
45
47
  }
46
- async function h(o) {
47
- await s.setDefaultModule(o), r.defaultModuleId = o;
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
- return { enter: n, enterInitial: d, switchModule: p, setDefault: h };
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
- b as useModule
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) // 落到新应用自己的首页\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, 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","useTabsStore","router","setDefault"],"mappings":";;;;;;AAcA,IAAIA,IAAwC;AAG5C,eAAeC,EAAMC,GAAwC;AAC3D,eAAMC,EAAqBD,CAAQ,GAC5B,EAAE,SAAS,IAAO,UAAAA,EAAA;AAC3B;AAGO,SAASE,IAAY;AAC1B,QAAMC,IAAOC,EAAA;AAEb,WAASC,IAAqC;AAC5C,WAAAP,UAAaQ,IAAiB,QAAQ,MAAM;AAC1C,MAAAR,IAAW;AAAA,IACb,CAAC,IACMA;AAAA,EACT;AAEA,iBAAeQ,IAAuC;AAIpD,UAAM,CAAC,EAAE,SAAAC,GAAS,iBAAAC,EAAA,GAAmBC,GAAMC,CAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtEC,EAAY,QAAA;AAAA,MACZA,EACG,YAAA,EACA,KAAK,CAAAC,OAAU,EAAE,IAAI,IAAM,OAAAA,EAAA,EAAQ,EACnC,MAAM,OAAO,EAAE,IAAI,IAAO,OAAO,CAAA,IAAiB;AAAA,MACrDD,EACG,QAAA,EACA,KAAK,CAAAE,OAAM;AAAA,QACV,MAAMA,EAAE;AAAA,QACR,QAAQA,EAAE,UAAU;AAAA,QACpB,SAASA,EAAE,WAAW;AAAA,QACtB,WAAWA,EAAE,QAAQA,EAAE,MAAM,MAAM,EAAE,IAAI;AAAA,MAAA,EACzC,EACD,MAAM,MAAA;;AAAO;AAAA,UACZ,QAAMC,IAAAC,EAAA,EAAe,aAAf,gBAAAD,EAAyB,iBAAgB;AAAA,UAC/C,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,WAAW;AAAA,QAAA;AAAA,OACX;AAAA,IAAA,CACL;AACD,IAAAX,EAAK,UAAUI,GACfJ,EAAK,kBAAkBK,KAAmB,MAC1CL,EAAK,kBAAkBM,EAAK,OAC5BN,EAAK,oBAAoBM,EAAK,IAC9BN,EAAK,eAAeO,EAAQ;AAE5B,UAAMM,IAAOD,EAAA;AAMb,QALIC,EAAK,aACPA,EAAK,SAAS,SAASN,EAAQ,QAC/BM,EAAK,SAAS,UAAUN,EAAQ,SAChCM,EAAK,SAAS,YAAYN,EAAQ,YAEhCH,EAAQ,WAAW,EAAG,QAAO,EAAE,SAAS,GAAA;AAE5C,UAAMU,IAAad,EAAK;AACxB,WAAIc,KAAcV,EAAQ,KAAK,CAAAW,MAAKA,EAAE,OAAOD,CAAU,IAAUlB,EAAMkB,CAAU,IAC7EV,EAAQ,WAAW,IAAUR,EAAMQ,EAAQ,CAAC,EAAG,EAAE,IACjDC,KAAmBD,EAAQ,KAAK,CAAAW,MAAKA,EAAE,OAAOV,CAAe,IACxDT,EAAMS,CAAe,IACvB,EAAE,SAAS,GAAA;AAAA,EACpB;AAEA,iBAAeW,EAAanB,GAAiC;AAC3D,UAAMD,EAAMC,CAAQ,GACpBoB,EAAA,EAAe,UAAA,GACfC,EAAO,QAAQlB,EAAK,QAAQ;AAAA,EAC9B;AAEA,iBAAemB,EAAWtB,GAAiC;AACzD,UAAMW,EAAY,iBAAiBX,CAAQ,GAC3CG,EAAK,kBAAkBH;AAAA,EACzB;AAEA,SAAO,EAAE,OAAAD,GAAO,cAAAM,GAAc,cAAAc,GAAc,YAAAG,EAAA;AAC9C;"}
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":";;;;;;;;AAiBA,IAAIA,IAAwC;AAG5C,eAAeC,EAAMC,GAAwC;AAC3D,eAAMC,EAAqBD,CAAQ,GAC5B,EAAE,SAAS,IAAO,UAAAA,EAAA;AAC3B;AAGO,SAASE,IAAY;AAC1B,QAAMC,IAAOC,EAAA;AAEb,WAASC,IAAqC;AAC5C,WAAAP,UAAaQ,IAAiB,QAAQ,MAAM;AAC1C,MAAAR,IAAW;AAAA,IACb,CAAC,IACMA;AAAA,EACT;AAEA,iBAAeQ,IAAuC;AAIpD,UAAM,CAAC,EAAE,SAAAC,GAAS,iBAAAC,EAAA,GAAmBC,GAAMC,CAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtEC,EAAY,QAAA;AAAA,MACZA,EACG,YAAA,EACA,KAAK,CAAAC,OAAU,EAAE,IAAI,IAAM,OAAAA,EAAA,EAAQ,EACnC,MAAM,OAAO,EAAE,IAAI,IAAO,OAAO,CAAA,IAAiB;AAAA,MACrDD,EACG,QAAA,EACA,KAAK,CAAAE,OAAM;AAAA,QACV,MAAMA,EAAE;AAAA,QACR,QAAQA,EAAE,UAAU;AAAA,QACpB,SAASA,EAAE,WAAW;AAAA,QACtB,WAAWA,EAAE,QAAQA,EAAE,MAAM,MAAM,EAAE,IAAI;AAAA,MAAA,EACzC,EACD,MAAM,MAAA;;AAAO;AAAA,UACZ,QAAMC,IAAAC,EAAA,EAAe,aAAf,gBAAAD,EAAyB,iBAAgB;AAAA,UAC/C,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,WAAW;AAAA,QAAA;AAAA,OACX;AAAA,IAAA,CACL;AACD,IAAAX,EAAK,UAAUI,GACfJ,EAAK,kBAAkBK,KAAmB,MAC1CL,EAAK,kBAAkBM,EAAK,OAC5BN,EAAK,oBAAoBM,EAAK,IAC9BN,EAAK,eAAeO,EAAQ;AAE5B,UAAMM,IAAOD,EAAA;AAMb,QALIC,EAAK,aACPA,EAAK,SAAS,SAASN,EAAQ,QAC/BM,EAAK,SAAS,UAAUN,EAAQ,SAChCM,EAAK,SAAS,YAAYN,EAAQ,YAEhCH,EAAQ,WAAW,EAAG,QAAO,EAAE,SAAS,GAAA;AAE5C,UAAMU,IAAad,EAAK;AACxB,WAAIc,KAAcV,EAAQ,KAAK,CAAAW,MAAKA,EAAE,OAAOD,CAAU,IAAUlB,EAAMkB,CAAU,IAC7EV,EAAQ,WAAW,IAAUR,EAAMQ,EAAQ,CAAC,EAAG,EAAE,IACjDC,KAAmBD,EAAQ,KAAK,CAAAW,MAAKA,EAAE,OAAOV,CAAe,IACxDT,EAAMS,CAAe,IACvB,EAAE,SAAS,GAAA;AAAA,EACpB;AAGA,iBAAeW,EAAanB,GAAkBoB,GAAgC;AAC5E,UAAMrB,EAAMC,CAAQ,GACpBqB,EAAA,EAAe,UAAA,GACfC,EAAO,QAAQF,KAAUjB,EAAK,QAAQ;AAAA,EACxC;AAQA,iBAAeoB,EAAgBC,GAAyC;AACtE,UAAMJ,IAASK,EAAmBD,CAAI,GAChCE,IAASvB,EAAK,QAAQ,OAAO,OAAKe,EAAE,OAAOf,EAAK,eAAe,GAE/DwB,IAAYD,EAAO;AAAA,MACvB,OAAKR,EAAE,gBAAgBO,EAAmBP,EAAE,YAAY,MAAME;AAAA,IAAA;AAEhE,QAAIO,EAAW,QAAOA;AAEtB,UAAMC,IAAW,IAAI,IAAIC,GAAoB,GAIvCC,KAHQ,MAAM,QAAQ;AAAA,MAC1BJ,EAAO,IAAI,CAAAR,MAAKP,EAAY,KAAKO,EAAE,EAAE,EAAE,MAAM,MAAM,EAAgB,CAAC;AAAA,IAAA,GAEpD,UAAU,CAAAa,MAAQC,EAAYD,GAAMX,GAAQQ,CAAQ,CAAC;AACvE,WAAOE,IAAM,IAAI,OAAOJ,EAAOI,CAAG;AAAA,EACpC;AAEA,iBAAeG,EAAWjC,GAAiC;AACzD,UAAMW,EAAY,iBAAiBX,CAAQ,GAC3CG,EAAK,kBAAkBH;AAAA,EACzB;AAEA,SAAO,EAAE,OAAAD,GAAO,cAAAM,GAAc,cAAAc,GAAc,iBAAAI,GAAiB,YAAAU,EAAA;AAC/D;"}