smart-admin-web 10.23.0 → 10.24.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index2.vue.js","sources":["../../../../src/views/system/menu/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, h, onMounted, ref, shallowRef, toRaw, watch } from 'vue'\nimport {\n NButton,\n NSpace,\n NTag,\n NSelect,\n NDropdown,\n useMessage,\n type DropdownOption,\n} from 'naive-ui'\nimport {\n SmartTable,\n type SmartTableColumn,\n type SmartTableFetcher,\n type SmartTableInst,\n} from 'smart-naive-table'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\nimport StatusSwitch from '#/components/StatusSwitch/index.vue'\nimport ButtonManager from './components/ButtonManager.vue'\nimport MenuFormModal from './components/MenuFormModal.vue'\nimport { useConfirm } from '#/composables/useConfirm'\nimport { useShellBreakpoint } from '#/composables/useShellBreakpoint'\nimport { buildRoutesForModule } from '#/composables/useAuthMenu'\nimport { useAuthStore } from '#/stores/auth'\nimport { menuApi, moduleApi } from '#/api'\nimport { translateError } from '#/utils/error'\nimport { SEARCH_ACTIONS, deriveHeaderFilters } from '#/utils/tableFilter'\nimport { TABLE_MIN_ROW_HEIGHT, TABLE_TOOLBAR } from '#/utils/tableToolbar'\nimport { expandableIds, flattenTree } from '#/utils/tree'\nimport { indentByDepth } from '#/utils/treeCard'\nimport { filterTreeByParams, hasTreeCondition, type TreeSearchFields } from '#/utils/treeFilter'\nimport { MenuType, type MenuTreeNode, splitPermission, toMenuInput } from '#/types/menu'\nimport type { ModuleRow, PermissionRouteItem } from '#/types/api'\n\nimport { lineIcon } from '#/utils/menuIcon'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nconst { t } = useI18n()\nconst message = useMessage()\nconst { confirm } = useConfirm()\nconst auth = useAuthStore()\n\nconst loading = ref(false)\n// 整份替换 + 只读(行内改动一律重拉,不往节点上写值),不需要深响应\nconst tree = shallowRef<MenuTreeNode[]>([])\nconst modules = shallowRef<ModuleRow[]>([])\n/** 「未分配」哨兵:雪花 id 无 0,用它表示 moduleId==null 的顶级目录分组。 */\nconst UNASSIGNED = 0\n/** 「全部」哨兵:雪花 id 无负数,用它表示不按应用过滤。默认筛选停在当前应用时,\n * 别的应用底下新建的菜单会被筛掉、看着像\"存了但不见了\"——需要一个能看全量的退路。 */\nconst ALL_MODULES = -1\n/** 所属应用筛选:默认跟随当前进入的应用,modules 加载后再校正。 */\nconst moduleFilter = ref<number>(auth.currentModuleId ?? UNASSIGNED)\n/** 后端实时路由表(含消费方自建控制器)——权限码下拉的数据源,免手敲 `GET:/api/v1/...`。 */\nconst routes = ref<PermissionRouteItem[]>([])\n\n/** 从服务端拉整棵菜单树。只由 treeFetcher 调用:增删改后一律走 `reload()`(让表格重新取数),不要直接调它。 */\nasync function load() {\n loading.value = true\n try {\n tree.value = await menuApi.tree()\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * 菜单改完顺手重建当前应用的壳层(侧边栏 + 动态路由)——否则新建的菜单要 F5 才出现,\n * 而这与「组件路径写错→进入 MissingRoute」都指向配置错误,这里仍需把失败显式反馈给管理员。\n * 失败静默:菜单本身已存成功,壳层没刷新不该把成功报成失败,下次 F5 自愈。\n */\nasync function syncShell() {\n if (!auth.currentModuleId) return\n try {\n await buildRoutesForModule(auth.currentModuleId)\n } catch {\n /* 见上 */\n }\n}\n// 菜单树本身由表格的 fetcher 首次取数时拉(immediate),这里只拉两份下拉辅助数据。\nonMounted(async () => {\n try {\n modules.value = await moduleApi.list()\n // 若默认筛选值不在选项内(如当前未进入任何应用),回退到第一个模块。\n if (!filterOptions.value.some(o => o.value === moduleFilter.value)) {\n moduleFilter.value = modules.value[0]?.id ?? UNASSIGNED\n }\n } catch {\n // 模块列表仅供「所属应用」下拉;拉取失败不阻塞菜单管理主流程。\n }\n try {\n routes.value = await menuApi.routes()\n } catch {\n // 路由清单仅供「权限码」下拉;拉取失败(如未授该码)退化为手输,不阻塞菜单管理主流程。\n }\n})\n\n// 类型文案。树里只有目录/页面(按钮不进树),Button 分支只为类型穷举。\nconst typeText = (ty: MenuType) =>\n ty === MenuType.Catalog\n ? t('menu.typeCatalog')\n : ty === MenuType.Menu\n ? t('menu.typeMenu')\n : t('menu.typeButton')\n\n// 目录/页面用不同色系(蓝/绿)区分 —— 树里只有这两类,按钮不进树。\nconst typeTagType = (ty: MenuType): 'info' | 'success' =>\n ty === MenuType.Catalog ? 'info' : 'success'\n\nconst moduleOptions = computed(() =>\n modules.value.map(m => ({ label: translateMenuTitle(m.title), value: m.id })),\n)\n\n/** 顶部筛选下拉:「全部」+ 各应用 +「未分配」。 */\nconst filterOptions = computed(() => [\n { label: t('menu.moduleAll'), value: ALL_MODULES },\n ...moduleOptions.value,\n { label: t('menu.moduleUnassigned'), value: UNASSIGNED },\n])\n\n/** 递归剔除按钮节点 —— 按钮统一在「权限按钮」弹窗里单独管理,不进主树,树里只留目录/菜单。 */\nfunction stripButtons(nodes: MenuTreeNode[]): MenuTreeNode[] {\n return nodes\n .filter(n => n.type !== MenuType.Button)\n .map(n => ({ ...n, children: stripButtons(n.children) }))\n}\n\n/**\n * 按所属应用过滤:moduleId 只存在顶级目录(parentId==0)上,子节点为 null 靠继承,\n * 所以只过滤顶级数组、子树自动跟随;UNASSIGNED 归拢 moduleId==null 的顶级目录。\n * 再剥掉按钮节点后交给表格渲染。\n */\nconst filteredTree = computed(() =>\n stripButtons(\n tree.value.filter(r =>\n moduleFilter.value === ALL_MODULES\n ? true\n : moduleFilter.value === UNASSIGNED\n ? r.moduleId == null\n : r.moduleId === moduleFilter.value,\n ),\n ),\n)\n\n/**\n * 各节点直属按钮的数量 + 权限码拼串(取自未剥离的原树,无需额外请求)。\n * count 给「权限按钮」列的徽标;perms 给关键字搜索 —— 按钮不在树里,\n * 「这个权限码归哪个页面」只能靠父页面命中来回答,否则藏起按钮就等于把它搜没了。\n */\nconst buttonInfoById = computed(() => {\n const m = new Map<number, { count: number; perms: string }>()\n const walk = (nodes: MenuTreeNode[]) => {\n for (const n of nodes) {\n const btns = n.children.filter(c => c.type === MenuType.Button)\n m.set(n.id, {\n count: btns.length,\n perms: btns\n .flatMap(b => splitPermission(b.permission))\n .join(' ')\n .toLowerCase(),\n })\n walk(n.children)\n }\n }\n walk(tree.value)\n return m\n})\n\n// ── 取数 + 条件搜索 + 展开控制 ────────────────────────────────────\n// 内核开箱就播了整棵系统菜单树,全展开、没搜索、没折叠 = 第一天打开就糊脸。\n// 树表用远程模式:3.0 静态模式的条件过滤是平铺的 rows.filter,不递归 children,在树上只会命中顶层行。\n// 所以 fetcher 自己递归过滤,条件构造器只负责收集条件(全局序列化器摊平成 { title, path, permission })。\n// 取数器每次都重新拉一遍服务端:工具栏内置的刷新图标只会重新调 fetcher,\n// 只过滤内存里的旧树的话,点刷新就是假刷新(别处新建的菜单永远出不来)。\n// 过滤结果只喂给表格,不喂给 parentOptions —— 父级下拉必须始终是完整的当前应用子树,不能随搜索缩水。\nconst tableRef = ref<SmartTableInst<MenuTreeNode>>()\nconst reload = () => tableRef.value?.refresh()\n\n// 搜索项 → 取值。权限码是特例:过滤跑在已剥掉按钮的 filteredTree 上,而目录/页面的 permission 恒为空,\n// 写成 n.permission 等于永不命中。要按权限码搜,只能查该节点「按钮子节点」的码(见 buttonInfoById)。\nconst SEARCH_FIELDS: TreeSearchFields<MenuTreeNode> = {\n // 英文界面下表里显示的是译名,搜索要两边都认(库里存的中文原文 + 当前语言的显示名)。\n title: n => `${n.title} ${translateMenuTitle(n.title, n.path || undefined)}`,\n path: n => n.path,\n permission: n => buttonInfoById.value.get(n.id)?.perms,\n}\n\n// 每次取数结果是否带搜索条件:挂在结果数组上,@loaded 时按数组引用取回。\n// 不用「最近一次 fetcher 调用的参数」是因为库会丢弃过期响应,@loaded 对应的未必是最后一次调用。\nconst searchedResults = new WeakMap<object, boolean>()\n// 窄档(整页自然滚动)表格是卡片列表,库的卡片模式不渲染 children:取数器把整棵树摊成一列带 depth 的行,\n// 标题按 depth 缩进。此时没有折叠态可言(每个节点一张卡),工具栏的「展开 / 折叠全部」也就不显示。\nconst { bp } = useShellBreakpoint()\nconst narrow = computed(() => bp.value === 'narrow')\nconst treeFetcher: SmartTableFetcher<MenuTreeNode> = async params => {\n await load()\n const matched = filterTreeByParams(filteredTree.value, params, SEARCH_FIELDS)\n const items = narrow.value ? flattenTree(matched) : matched\n searchedResults.set(items, hasTreeCondition(params, SEARCH_FIELDS))\n return { items, total: items.length }\n}\n// 宽窄档切换(旋转屏幕、拖动窗口)时行的形状变了,重取一遍\nwatch(narrow, () => reload())\n\n// 展开受控:一旦传了 expanded-row-keys,naive 就以它为准(default-expand-all 会被初始值直接覆盖)。\n// 初值留空 = 进来全折叠:先给一屏顶级目录的骨架,要看哪一支自己点开;\n// 整棵树一进门就全摊开,反而得先滚半天才找得到自己要改的那条。\nconst expandedKeys = ref<number[]>([])\n// 取数器返回的(已过滤的)树:「展开全部」与层级序号都按它算。\nconst visibleTree = shallowRef<MenuTreeNode[]>([])\n// 当前展示的结果是不是搜索结果。\nlet searching = false\n\nfunction onLoaded(rows: MenuTreeNode[]) {\n visibleTree.value = rows\n const now = searchedResults.get(toRaw(rows)) ?? false\n if (now) {\n // 搜索是唯一必须自动展开的场景:filterTree 命中的节点可能埋在任意深度,祖先还折着就等于搜了个寂寞。\n // 结果每变一次重播一次(收窄条件也算)。\n expandedKeys.value = expandableIds(rows)\n } else if (searching) {\n // 条件清空 → 回到默认的全折叠。\n expandedKeys.value = []\n }\n // 无条件且上一次也没搜索:一律不碰展开态 ——\n // 保存菜单后会重拉树,无条件重置的话用户手点开的行会被一起按回去,改完一条就迷路一次。\n searching = now\n}\n\n// 切应用 = 换了一棵树,展开态归位(搜索中则交给 onLoaded 按新结果重新展开);同时让表格按新应用重新取数。\nwatch(moduleFilter, () => {\n if (!searching) expandedKeys.value = []\n void reload()\n})\n\nconst allExpanded = computed(() => expandedKeys.value.length > 0)\nconst toggleExpandAll = () =>\n (expandedKeys.value = allExpanded.value ? [] : expandableIds(visibleTree.value))\n\n// 当前所属应用的路由匹配前缀(sys/biz…):传给 ButtonManager,权限路由下拉据此按应用软过滤。\n// 「未分配」筛选或该应用未填 apiPrefix → undefined,ButtonManager 退化为不过滤。\nconst currentAppPrefix = computed(() =>\n moduleFilter.value === UNASSIGNED\n ? undefined\n : (modules.value.find(m => m.id === moduleFilter.value)?.apiPrefix ?? undefined),\n)\n\n// ── 新增/编辑弹窗(MenuFormModal:表单态/父级下拉/保存均在其内,父页只传数据 + 收 saved)──\nconst menuFormRef = ref<InstanceType<typeof MenuFormModal> | null>(null)\n\n// ── 权限按钮弹窗 ────────────────────────────────────────────────\nconst buttonMgrRef = ref<InstanceType<typeof ButtonManager> | null>(null)\nconst openButtons = (r: MenuTreeNode) => buttonMgrRef.value?.open({ id: r.id, title: r.title })\n/** 菜单/按钮增删改后:重拉树(刷新徽标)+ 重建壳层(即时反映授权/菜单变更)。表单弹窗与按钮弹窗共用。 */\nconst reloadTree = () => Promise.all([reload(), syncShell()])\n\n/** 删除:下拉菜单项不适合内联 popconfirm,走 useConfirm().confirm 的 dialog(它的设计用途)。 */\nconst onDelete = (r: MenuTreeNode) =>\n confirm({\n content: t('menu.deleteConfirm', { title: r.title }),\n type: 'error',\n action: () => menuApi.remove(r.id),\n successMsg: t('menu.deleted'),\n }).then(ok => {\n if (ok) reloadTree()\n })\n\n// 展开图标:naive 默认三角太淡、展开态朝下,辨识度低。统一成固定尺寸的「>」caret,展开时旋转 90°\n// —— 观感清晰,尺寸恒定,行高不会因默认图标忽大忽小而参差。\nconst renderExpandIcon = ({ expanded }: { expanded: boolean }) =>\n h(\n 'span',\n {\n style: `display:inline-flex;transition:transform .2s var(--ease);transform:rotate(${expanded ? 90 : 0}deg)`,\n },\n h(AppIcon, { icon: 'ph:caret-right', size: 14 }),\n )\n\n/**\n * 层级序号:`5` / `5.2` / `5.2.3`,按当前可见树逐层递推。\n * 不能用表格自带的 index 列 —— naive 树表 flatten 时让每个后代继承「根行下标」\n * (Body.tsx: traverse(r.children, rootIndex)),一整棵子树全叫「5」,序号等于没有。\n * 自己数则行行唯一,点号路径本身就是一条「到根」的定位。\n */\nconst rowNoById = computed(() => {\n const m = new Map<number, string>()\n const walk = (nodes: MenuTreeNode[], prefix: string) => {\n nodes.forEach((n, i) => {\n const no = prefix ? `${prefix}.${i + 1}` : String(i + 1)\n m.set(n.id, no)\n walk(n.children, no)\n })\n }\n walk(visibleTree.value, '')\n return m\n})\n\n/** 路由 / 组件路径单元格:有值用等宽次级色,空值画灰色的「—」。 */\nconst cellPath = (v?: string | null) =>\n v ? h('span', { class: 'mono muted' }, v) : h('span', { class: 'faint' }, '—')\n\nconst columns: SmartTableColumn<MenuTreeNode>[] = [\n // 序号列是渲染 rowNoById 层级号的普通数据列,fixed:'left' 与标题列同锚 ——\n // 不锚定的话,横向滚动时会滑到 fixed 的标题列底下,「这是哪一行」照样丢。\n {\n title: () => t('common.rowNo'),\n key: '__no',\n width: 72,\n align: 'center',\n fixed: 'left',\n hideInSetting: true, // 结构性列,不进列设置\n card: false, // 窄档卡片以标题为首,层级号不进卡片(默认会顶替标题成为卡片首行)\n render: r => {\n const no = rowNoById.value.get(r.id)\n if (!no) return null\n const cut = no.lastIndexOf('.') + 1\n // 祖先前缀压暗、末段留亮:一眼读出「哪一层的第几个」,不用逐字符数点号。\n return h('span', { class: 'row-no' }, [\n cut ? h('span', { class: 'row-no__path' }, no.slice(0, cut)) : null,\n h('span', { class: 'row-no__self' }, no.slice(cut)),\n ])\n },\n },\n {\n title: () => t('menu.title'),\n key: 'title',\n search: { actions: SEARCH_ACTIONS.fuzzy },\n align: 'left', // 树列左对齐,展开缩进才好读\n // 展开箭头与每层 26px 缩进钉死在这一列。默认会落到「首个无 type 的列」= 序号列,\n // 于是缩进把序号列里的数字一层层挤出可视区(展开后序号就看不见了);缩进也只有紧贴标题才读得通。\n tree: true,\n width: 240, // 让出让给缩进的宽度\n fixed: 'left', // 横向滚动时不能丢失「这是哪一行」\n render: r =>\n // inline:必须行内 —— naive 的展开箭头是 inline-flex,默认块级 NSpace 会被挤到第二行。\n indentByDepth(\n r,\n h(NSpace, { inline: true, align: 'center', size: 6, wrapItem: false }, () => [\n translateMenuTitle(r.title, r.path || undefined),\n // 「隐藏」是罕见状态(种子里的目录/页面全是显示),只在为真时才占视觉 —— 省掉一整列同一个词。\n r.visible\n ? null\n : h(NTag, { size: 'tiny', bordered: false, type: 'warning' }, () => t('common.hidden')),\n ]),\n ),\n },\n // 图标独占一列:留在标题列会和树形展开箭头 + 缩进挤在一起撑破首列换行。\n {\n title: () => t('menu.icon'),\n key: 'icon',\n width: 64,\n align: 'center',\n render: r => (r.icon ? h(AppIcon, { icon: lineIcon(r.icon), size: 18 }) : null),\n },\n // 类型独占一列,不塞在标题里。按类型着色,目录/页面一眼可辨。\n {\n title: () => t('menu.type'),\n key: 'type',\n width: 90,\n render: r =>\n h(NTag, { size: 'small', bordered: false, type: typeTagType(r.type) }, () =>\n typeText(r.type),\n ),\n },\n // 路由/组件路径:管理员核对菜单时真正要看的东西,直接列出,不必点开编辑弹窗。\n // 不设权限码列 —— 树里只有目录/页面,而它俩结构上永远没有权限码(种子里全为空),\n // 那一列必然是「—」。权限码的正主在「权限按钮」弹窗里。\n {\n title: () => t('menu.path'),\n key: 'path',\n align: 'left',\n width: 160,\n ellipsis: { tooltip: true },\n render: r => cellPath(r.path),\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n // 权限码只作搜索项:表格里不设这一列(见上),但「这个权限码归哪个页面」得能搜到 ——\n // 取值与匹配见 SEARCH_FIELDS.permission(查该节点按钮子节点的码)。\n {\n key: 'permission',\n title: () => t('menu.permission'),\n hideInTable: true,\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n {\n title: () => t('menu.component'),\n key: 'component',\n align: 'left',\n width: 180,\n ellipsis: { tooltip: true },\n render: r => cellPath(r.component),\n },\n {\n title: () => t('menu.buttons'),\n key: 'buttons',\n width: 140,\n render: r => {\n const n = buttonInfoById.value.get(r.id)?.count ?? 0\n // 页面和目录都能「配置权限」:按钮正常挂页面下;直接挂目录下的是无页面权限项\n //(只给移动端 / 第三方调的接口),目录就是它们的权限组,角色授权界面会把它们渲染成一行。\n // 无菜单新增权限则不显示「配置权限」入口(只读用户不该看到写入口;弹窗内写按钮亦各自设门,服务端兜底)\n if (!auth.hasPerm('POST:/api/v1/sys/menu/add')) return null\n return h(\n NButton,\n {\n size: 'small',\n quaternary: true,\n type: n > 0 ? 'primary' : undefined,\n onClick: () => openButtons(r),\n },\n {\n icon: () => h(AppIcon, { icon: 'ph:shield-check', size: 15 }),\n default: () => t('menu.configPerms', { n }),\n },\n )\n },\n },\n {\n title: () => t('menu.sort'),\n key: 'sort',\n width: 70,\n render: r => h('span', { class: 'num muted' }, r.sort),\n },\n {\n title: () => t('common.status'),\n key: 'enabled',\n width: 84,\n render: r =>\n h(StatusSwitch, {\n value: r.enabled,\n disabled: !auth.hasPerm('PUT:/api/v1/sys/menu/{id}'), // 启停走全量 update,无更新权限则置灰\n // 停用是一键隐藏整棵子树的重操作,先确认;启用无副作用,跳过确认(返回 null)。\n confirm: (next: boolean) => (next ? null : t('menu.disableConfirm', { title: r.title })),\n request: (next: boolean) => menuApi.update(r.id, { ...toMenuInput(r), enabled: next }),\n // 重拉而非往行对象上写:搜索态下的祖先行是 filterTree 的浅拷贝,写它不会回到源树(开关会弹回去)。\n // StatusSwitch 是悲观更新(请求成功才 emit),重拉一次就是最终态。\n 'onUpdate:value': () => {\n reload()\n syncShell() // 停用/启用即刻反映到侧边栏\n },\n }),\n },\n // 操作收敛成「编辑 + 更多▾」且 fixed:'right':4 个操作平铺会把「删除」挤到第二行、行高参差,\n // 不 fixed 的话横向滚动时整列会被裁掉够不着。\n {\n title: () => t('common.operation'),\n key: 'op',\n width: 150,\n fixed: 'right',\n render: r =>\n h(NSpace, { size: 2, wrapItem: false, justify: 'center' }, () => {\n // 「更多」里两项各自按权限码保留;都无权时整个下拉不渲染,避免空触发器。\n const canAddChild = auth.hasPerm('POST:/api/v1/sys/menu/add')\n const canDelete = auth.hasPerm('DELETE:/api/v1/sys/menu/{id}')\n const moreOptions: DropdownOption[] = [\n canAddChild && {\n key: 'addChild',\n label: t('menu.addChild'),\n icon: () => h(AppIcon, { icon: 'ph:plus', size: 16 }),\n },\n // 新增与删除之间一条分隔线;只剩一项时不画\n canAddChild && canDelete && { type: 'divider', key: 'divider' },\n canDelete && {\n key: 'delete',\n label: t('common.delete'),\n icon: () => h(AppIcon, { icon: 'ph:trash', size: 16 }),\n props: { style: 'color: var(--err)' },\n },\n ].filter(Boolean) as DropdownOption[]\n return [\n auth.hasPerm('PUT:/api/v1/sys/menu/{id}')\n ? h(\n NButton,\n {\n size: 'small',\n quaternary: true,\n type: 'primary',\n onClick: () => menuFormRef.value?.openEdit(r),\n },\n () => t('common.edit'),\n )\n : null,\n moreOptions.length\n ? h(\n NDropdown,\n {\n trigger: 'click',\n options: moreOptions,\n onSelect: (key: string) =>\n key === 'addChild' ? menuFormRef.value?.openAdd(r.id) : onDelete(r),\n },\n () =>\n h(\n NButton,\n { size: 'small', quaternary: true, type: 'primary', iconPlacement: 'right' },\n {\n default: () => t('common.more'),\n icon: () => h(AppIcon, { icon: 'ph:caret-down', size: 12 }),\n },\n ),\n )\n : null,\n ]\n }),\n },\n]\nderiveHeaderFilters(columns)\n</script>\n\n<template>\n <!-- .fill-page 是站内约定(styles/layout.css):它的 :has() 规则把路由外层 .page-view 撑到 100%,\n 页面自身 flex 列 + overflow:hidden —— 卡片吃满内容区,滚动只发生在表体里。 -->\n <div class=\"view fill-page\">\n <!-- expanded-row-keys / update:expanded-row-keys 不是 SmartTable 的 prop —— 它 inheritAttrs:false + v-bind=\"attrs\",\n 未声明的 attr 原样透传给内层 n-data-table(:loading 走的也是这条路)。 -->\n <SmartTable\n ref=\"tableRef\"\n :columns=\"columns\"\n :fetcher=\"treeFetcher\"\n :loading=\"loading\"\n row-key=\"id\"\n :pagination=\"false\"\n :search=\"{ container: 'table' }\"\n :toolbar=\"TABLE_TOOLBAR\"\n storage-key=\"sys-menu\"\n :indent=\"26\"\n fill-height\n :min-row-height=\"TABLE_MIN_ROW_HEIGHT\"\n card-on-narrow\n :render-expand-icon=\"renderExpandIcon\"\n :expanded-row-keys=\"expandedKeys\"\n @update:expanded-row-keys=\"(keys: number[]) => (expandedKeys = keys)\"\n @loaded=\"onLoaded\"\n >\n <!-- 应用选择器决定「看哪个应用的菜单树」,是限定数据范围的控件而不是业务按钮,所以留在工具栏左半。 -->\n <template #toolbar>\n <n-select\n v-model:value=\"moduleFilter\"\n :options=\"filterOptions\"\n style=\"width: 200px\"\n :placeholder=\"t('menu.module')\"\n />\n </template>\n <template #toolbar-right>\n <n-button v-if=\"!narrow\" secondary @click=\"toggleExpandAll\">\n <template #icon>\n <AppIcon\n :icon=\"allExpanded ? 'ph:arrows-in-line-vertical' : 'ph:arrows-out-line-vertical'\"\n :size=\"16\"\n />\n </template>\n {{ allExpanded ? t('common.collapseAll') : t('common.expandAll') }}\n </n-button>\n <n-button\n v-auth=\"'POST:/api/v1/sys/menu/add'\"\n type=\"primary\"\n @click=\"menuFormRef?.openAdd(0)\"\n >\n <template #icon><AppIcon icon=\"ph:plus\" :size=\"16\" /></template>\n {{ t('common.add') }}\n </n-button>\n </template>\n </SmartTable>\n\n <MenuFormModal\n ref=\"menuFormRef\"\n :modules=\"modules\"\n :tree=\"tree\"\n :filtered-tree=\"filteredTree\"\n :module-filter=\"moduleFilter\"\n @saved=\"reloadTree\"\n />\n\n <ButtonManager\n ref=\"buttonMgrRef\"\n :tree=\"tree\"\n :routes=\"routes\"\n :app-prefix=\"currentAppPrefix\"\n @changed=\"reloadTree\"\n />\n </div>\n</template>\n\n<style scoped>\n.view {\n display: flex;\n flex-direction: column;\n gap: var(--gap-card);\n}\n\n/* 层级序号:字号/字族都跟标题列走(不用 12px 等宽),只借 tabular-nums 让各行点号竖着对齐;\n 前缀是祖先路径(压暗),末段才是这一行自己的号。 */\n:deep(.row-no) {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n:deep(.row-no__path) {\n color: var(--text-3);\n}\n\n:deep(.row-no__self) {\n color: var(--text-2);\n font-weight: var(--font-weight-medium);\n}\n\n/* 单元格里的辅助字样:路径 / 组件用等宽,排序用等宽数字,空值灰色「—」(表格单元格由库渲染,要 :deep) */\n:deep(.mono) {\n font-family: var(--font-mono);\n font-size: 13.5px;\n letter-spacing: 0;\n}\n:deep(.muted) {\n color: var(--text-2);\n}\n:deep(.faint) {\n color: var(--text-3);\n}\n:deep(.num) {\n font-variant-numeric: tabular-nums;\n}\n</style>\n"],"names":["UNASSIGNED","ALL_MODULES","t","useI18n","message","useMessage","confirm","useConfirm","auth","useAuthStore","loading","ref","tree","shallowRef","modules","moduleFilter","routes","load","menuApi","translateError","syncShell","buildRoutesForModule","onMounted","moduleApi","filterOptions","o","_a","typeText","ty","MenuType","typeTagType","moduleOptions","computed","translateMenuTitle","m","stripButtons","nodes","n","filteredTree","r","buttonInfoById","walk","btns","c","b","splitPermission","tableRef","reload","SEARCH_FIELDS","searchedResults","bp","useShellBreakpoint","narrow","treeFetcher","params","matched","filterTreeByParams","items","flattenTree","hasTreeCondition","watch","expandedKeys","visibleTree","searching","onLoaded","rows","now","toRaw","expandableIds","allExpanded","toggleExpandAll","currentAppPrefix","menuFormRef","buttonMgrRef","openButtons","reloadTree","onDelete","ok","renderExpandIcon","expanded","h","AppIcon","rowNoById","prefix","i","no","cellPath","v","columns","cut","SEARCH_ACTIONS","indentByDepth","NSpace","NTag","lineIcon","NButton","StatusSwitch","next","toMenuInput","canAddChild","canDelete","moreOptions","NDropdown","key","deriveHeaderFilters","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","SmartTable","TABLE_TOOLBAR","TABLE_MIN_ROW_HEIGHT","_cache","keys","NSelect","$event","_createBlock","_createTextVNode","_toDisplayString","MenuFormModal","ButtonManager"],"mappings":"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"}
1
+ {"version":3,"file":"index2.vue.js","sources":["../../../../src/views/system/menu/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, h, onMounted, ref, shallowRef, toRaw, watch } from 'vue'\nimport {\n NButton,\n NSpace,\n NTag,\n NSelect,\n NDropdown,\n useMessage,\n type DropdownOption,\n} from 'naive-ui'\nimport {\n SmartTable,\n type SmartTableColumn,\n type SmartTableFetcher,\n type SmartTableInst,\n} from 'smart-naive-table'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\nimport StatusSwitch from '#/components/StatusSwitch/index.vue'\nimport ButtonManager from './components/ButtonManager.vue'\nimport MenuFormModal from './components/MenuFormModal.vue'\nimport { useConfirm } from '#/composables/useConfirm'\nimport { buildRoutesForModule } from '#/composables/useAuthMenu'\nimport { useAuthStore } from '#/stores/auth'\nimport { menuApi, moduleApi } from '#/api'\nimport { translateError } from '#/utils/error'\nimport { SEARCH_ACTIONS, deriveHeaderFilters } from '#/utils/tableFilter'\nimport { TABLE_MIN_ROW_HEIGHT, TABLE_TOOLBAR } from '#/utils/tableToolbar'\nimport { expandableIds } from '#/utils/tree'\nimport { filterTreeByParams, hasTreeCondition, type TreeSearchFields } from '#/utils/treeFilter'\nimport { MenuType, type MenuTreeNode, splitPermission, toMenuInput } from '#/types/menu'\nimport type { ModuleRow, PermissionRouteItem } from '#/types/api'\n\nimport { lineIcon } from '#/utils/menuIcon'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nconst { t } = useI18n()\nconst message = useMessage()\nconst { confirm } = useConfirm()\nconst auth = useAuthStore()\n\nconst loading = ref(false)\n// 整份替换 + 只读(行内改动一律重拉,不往节点上写值),不需要深响应\nconst tree = shallowRef<MenuTreeNode[]>([])\nconst modules = shallowRef<ModuleRow[]>([])\n/** 「未分配」哨兵:雪花 id 无 0,用它表示 moduleId==null 的顶级目录分组。 */\nconst UNASSIGNED = 0\n/** 「全部」哨兵:雪花 id 无负数,用它表示不按应用过滤。默认筛选停在当前应用时,\n * 别的应用底下新建的菜单会被筛掉、看着像\"存了但不见了\"——需要一个能看全量的退路。 */\nconst ALL_MODULES = -1\n/** 所属应用筛选:默认跟随当前进入的应用,modules 加载后再校正。 */\nconst moduleFilter = ref<number>(auth.currentModuleId ?? UNASSIGNED)\n/** 后端实时路由表(含消费方自建控制器)——权限码下拉的数据源,免手敲 `GET:/api/v1/...`。 */\nconst routes = ref<PermissionRouteItem[]>([])\n\n/** 从服务端拉整棵菜单树。只由 treeFetcher 调用:增删改后一律走 `reload()`(让表格重新取数),不要直接调它。 */\nasync function load() {\n loading.value = true\n try {\n tree.value = await menuApi.tree()\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\n/**\n * 菜单改完顺手重建当前应用的壳层(侧边栏 + 动态路由)——否则新建的菜单要 F5 才出现,\n * 而这与「组件路径写错→进入 MissingRoute」都指向配置错误,这里仍需把失败显式反馈给管理员。\n * 失败静默:菜单本身已存成功,壳层没刷新不该把成功报成失败,下次 F5 自愈。\n */\nasync function syncShell() {\n if (!auth.currentModuleId) return\n try {\n await buildRoutesForModule(auth.currentModuleId)\n } catch {\n /* 见上 */\n }\n}\n// 菜单树本身由表格的 fetcher 首次取数时拉(immediate),这里只拉两份下拉辅助数据。\nonMounted(async () => {\n try {\n modules.value = await moduleApi.list()\n // 若默认筛选值不在选项内(如当前未进入任何应用),回退到第一个模块。\n if (!filterOptions.value.some(o => o.value === moduleFilter.value)) {\n moduleFilter.value = modules.value[0]?.id ?? UNASSIGNED\n }\n } catch {\n // 模块列表仅供「所属应用」下拉;拉取失败不阻塞菜单管理主流程。\n }\n try {\n routes.value = await menuApi.routes()\n } catch {\n // 路由清单仅供「权限码」下拉;拉取失败(如未授该码)退化为手输,不阻塞菜单管理主流程。\n }\n})\n\n// 类型文案。树里只有目录/页面(按钮不进树),Button 分支只为类型穷举。\nconst typeText = (ty: MenuType) =>\n ty === MenuType.Catalog\n ? t('menu.typeCatalog')\n : ty === MenuType.Menu\n ? t('menu.typeMenu')\n : t('menu.typeButton')\n\n// 目录/页面用不同色系(蓝/绿)区分 —— 树里只有这两类,按钮不进树。\nconst typeTagType = (ty: MenuType): 'info' | 'success' =>\n ty === MenuType.Catalog ? 'info' : 'success'\n\nconst moduleOptions = computed(() =>\n modules.value.map(m => ({ label: translateMenuTitle(m.title), value: m.id })),\n)\n\n/** 顶部筛选下拉:「全部」+ 各应用 +「未分配」。 */\nconst filterOptions = computed(() => [\n { label: t('menu.moduleAll'), value: ALL_MODULES },\n ...moduleOptions.value,\n { label: t('menu.moduleUnassigned'), value: UNASSIGNED },\n])\n\n/** 递归剔除按钮节点 —— 按钮统一在「权限按钮」弹窗里单独管理,不进主树,树里只留目录/菜单。 */\nfunction stripButtons(nodes: MenuTreeNode[]): MenuTreeNode[] {\n return nodes\n .filter(n => n.type !== MenuType.Button)\n .map(n => ({ ...n, children: stripButtons(n.children) }))\n}\n\n/**\n * 按所属应用过滤:moduleId 只存在顶级目录(parentId==0)上,子节点为 null 靠继承,\n * 所以只过滤顶级数组、子树自动跟随;UNASSIGNED 归拢 moduleId==null 的顶级目录。\n * 再剥掉按钮节点后交给表格渲染。\n */\nconst filteredTree = computed(() =>\n stripButtons(\n tree.value.filter(r =>\n moduleFilter.value === ALL_MODULES\n ? true\n : moduleFilter.value === UNASSIGNED\n ? r.moduleId == null\n : r.moduleId === moduleFilter.value,\n ),\n ),\n)\n\n/**\n * 各节点直属按钮的数量 + 权限码拼串(取自未剥离的原树,无需额外请求)。\n * count 给「权限按钮」列的徽标;perms 给关键字搜索 —— 按钮不在树里,\n * 「这个权限码归哪个页面」只能靠父页面命中来回答,否则藏起按钮就等于把它搜没了。\n */\nconst buttonInfoById = computed(() => {\n const m = new Map<number, { count: number; perms: string }>()\n const walk = (nodes: MenuTreeNode[]) => {\n for (const n of nodes) {\n const btns = n.children.filter(c => c.type === MenuType.Button)\n m.set(n.id, {\n count: btns.length,\n perms: btns\n .flatMap(b => splitPermission(b.permission))\n .join(' ')\n .toLowerCase(),\n })\n walk(n.children)\n }\n }\n walk(tree.value)\n return m\n})\n\n// ── 取数 + 条件搜索 + 展开控制 ────────────────────────────────────\n// 内核开箱就播了整棵系统菜单树,全展开、没搜索、没折叠 = 第一天打开就糊脸。\n// 树表用远程模式:3.0 静态模式的条件过滤是平铺的 rows.filter,不递归 children,在树上只会命中顶层行。\n// 所以 fetcher 自己递归过滤,条件构造器只负责收集条件(全局序列化器摊平成 { title, path, permission })。\n// 取数器每次都重新拉一遍服务端:工具栏内置的刷新图标只会重新调 fetcher,\n// 只过滤内存里的旧树的话,点刷新就是假刷新(别处新建的菜单永远出不来)。\n// 过滤结果只喂给表格,不喂给 parentOptions —— 父级下拉必须始终是完整的当前应用子树,不能随搜索缩水。\nconst tableRef = ref<SmartTableInst<MenuTreeNode>>()\nconst reload = () => tableRef.value?.refresh()\n\n// 搜索项 → 取值。权限码是特例:过滤跑在已剥掉按钮的 filteredTree 上,而目录/页面的 permission 恒为空,\n// 写成 n.permission 等于永不命中。要按权限码搜,只能查该节点「按钮子节点」的码(见 buttonInfoById)。\nconst SEARCH_FIELDS: TreeSearchFields<MenuTreeNode> = {\n // 英文界面下表里显示的是译名,搜索要两边都认(库里存的中文原文 + 当前语言的显示名)。\n title: n => `${n.title} ${translateMenuTitle(n.title, n.path || undefined)}`,\n path: n => n.path,\n permission: n => buttonInfoById.value.get(n.id)?.perms,\n}\n\n// 每次取数结果是否带搜索条件:挂在结果数组上,@loaded 时按数组引用取回。\n// 不用「最近一次 fetcher 调用的参数」是因为库会丢弃过期响应,@loaded 对应的未必是最后一次调用。\nconst searchedResults = new WeakMap<object, boolean>()\nconst treeFetcher: SmartTableFetcher<MenuTreeNode> = async params => {\n await load()\n const items = filterTreeByParams(filteredTree.value, params, SEARCH_FIELDS)\n searchedResults.set(items, hasTreeCondition(params, SEARCH_FIELDS))\n return { items, total: items.length }\n}\n\n// 展开受控:一旦传了 expanded-row-keys,naive 就以它为准(default-expand-all 会被初始值直接覆盖)。\n// 初值留空 = 进来全折叠:先给一屏顶级目录的骨架,要看哪一支自己点开;\n// 整棵树一进门就全摊开,反而得先滚半天才找得到自己要改的那条。\nconst expandedKeys = ref<number[]>([])\n// 取数器返回的(已过滤的)树:「展开全部」与层级序号都按它算。\nconst visibleTree = shallowRef<MenuTreeNode[]>([])\n// 当前展示的结果是不是搜索结果。\nlet searching = false\n\nfunction onLoaded(rows: MenuTreeNode[]) {\n visibleTree.value = rows\n const now = searchedResults.get(toRaw(rows)) ?? false\n if (now) {\n // 搜索是唯一必须自动展开的场景:filterTree 命中的节点可能埋在任意深度,祖先还折着就等于搜了个寂寞。\n // 结果每变一次重播一次(收窄条件也算)。\n expandedKeys.value = expandableIds(rows)\n } else if (searching) {\n // 条件清空 → 回到默认的全折叠。\n expandedKeys.value = []\n }\n // 无条件且上一次也没搜索:一律不碰展开态 ——\n // 保存菜单后会重拉树,无条件重置的话用户手点开的行会被一起按回去,改完一条就迷路一次。\n searching = now\n}\n\n// 切应用 = 换了一棵树,展开态归位(搜索中则交给 onLoaded 按新结果重新展开);同时让表格按新应用重新取数。\nwatch(moduleFilter, () => {\n if (!searching) expandedKeys.value = []\n void reload()\n})\n\nconst allExpanded = computed(() => expandedKeys.value.length > 0)\nconst toggleExpandAll = () =>\n (expandedKeys.value = allExpanded.value ? [] : expandableIds(visibleTree.value))\n\n// 当前所属应用的路由匹配前缀(sys/biz…):传给 ButtonManager,权限路由下拉据此按应用软过滤。\n// 「未分配」筛选或该应用未填 apiPrefix → undefined,ButtonManager 退化为不过滤。\nconst currentAppPrefix = computed(() =>\n moduleFilter.value === UNASSIGNED\n ? undefined\n : (modules.value.find(m => m.id === moduleFilter.value)?.apiPrefix ?? undefined),\n)\n\n// ── 新增/编辑弹窗(MenuFormModal:表单态/父级下拉/保存均在其内,父页只传数据 + 收 saved)──\nconst menuFormRef = ref<InstanceType<typeof MenuFormModal> | null>(null)\n\n// ── 权限按钮弹窗 ────────────────────────────────────────────────\nconst buttonMgrRef = ref<InstanceType<typeof ButtonManager> | null>(null)\nconst openButtons = (r: MenuTreeNode) => buttonMgrRef.value?.open({ id: r.id, title: r.title })\n/** 菜单/按钮增删改后:重拉树(刷新徽标)+ 重建壳层(即时反映授权/菜单变更)。表单弹窗与按钮弹窗共用。 */\nconst reloadTree = () => Promise.all([reload(), syncShell()])\n\n/** 删除:下拉菜单项不适合内联 popconfirm,走 useConfirm().confirm 的 dialog(它的设计用途)。 */\nconst onDelete = (r: MenuTreeNode) =>\n confirm({\n content: t('menu.deleteConfirm', { title: r.title }),\n type: 'error',\n action: () => menuApi.remove(r.id),\n successMsg: t('menu.deleted'),\n }).then(ok => {\n if (ok) reloadTree()\n })\n\n// 展开图标:naive 默认三角太淡、展开态朝下,辨识度低。统一成固定尺寸的「>」caret,展开时旋转 90°\n// —— 观感清晰,尺寸恒定,行高不会因默认图标忽大忽小而参差。\nconst renderExpandIcon = ({ expanded }: { expanded: boolean }) =>\n h(\n 'span',\n {\n style: `display:inline-flex;transition:transform .2s var(--ease);transform:rotate(${expanded ? 90 : 0}deg)`,\n },\n h(AppIcon, { icon: 'ph:caret-right', size: 14 }),\n )\n\n/**\n * 层级序号:`5` / `5.2` / `5.2.3`,按当前可见树逐层递推。\n * 不能用表格自带的 index 列 —— naive 树表 flatten 时让每个后代继承「根行下标」\n * (Body.tsx: traverse(r.children, rootIndex)),一整棵子树全叫「5」,序号等于没有。\n * 自己数则行行唯一,点号路径本身就是一条「到根」的定位。\n */\nconst rowNoById = computed(() => {\n const m = new Map<number, string>()\n const walk = (nodes: MenuTreeNode[], prefix: string) => {\n nodes.forEach((n, i) => {\n const no = prefix ? `${prefix}.${i + 1}` : String(i + 1)\n m.set(n.id, no)\n walk(n.children, no)\n })\n }\n walk(visibleTree.value, '')\n return m\n})\n\n/** 路由 / 组件路径单元格:有值用等宽次级色,空值画灰色的「—」。 */\nconst cellPath = (v?: string | null) =>\n v ? h('span', { class: 'mono muted' }, v) : h('span', { class: 'faint' }, '—')\n\nconst columns: SmartTableColumn<MenuTreeNode>[] = [\n // 序号列是渲染 rowNoById 层级号的普通数据列,fixed:'left' 与标题列同锚 ——\n // 不锚定的话,横向滚动时会滑到 fixed 的标题列底下,「这是哪一行」照样丢。\n {\n title: () => t('common.rowNo'),\n key: '__no',\n width: 72,\n align: 'center',\n fixed: 'left',\n hideInSetting: true, // 结构性列,不进列设置\n card: false, // 窄档卡片以标题为首,层级号不进卡片(默认会顶替标题成为卡片首行)\n render: r => {\n const no = rowNoById.value.get(r.id)\n if (!no) return null\n const cut = no.lastIndexOf('.') + 1\n // 祖先前缀压暗、末段留亮:一眼读出「哪一层的第几个」,不用逐字符数点号。\n return h('span', { class: 'row-no' }, [\n cut ? h('span', { class: 'row-no__path' }, no.slice(0, cut)) : null,\n h('span', { class: 'row-no__self' }, no.slice(cut)),\n ])\n },\n },\n {\n title: () => t('menu.title'),\n key: 'title',\n search: { actions: SEARCH_ACTIONS.fuzzy },\n align: 'left', // 树列左对齐,展开缩进才好读\n // 展开箭头与每层 26px 缩进钉死在这一列。默认会落到「首个无 type 的列」= 序号列,\n // 于是缩进把序号列里的数字一层层挤出可视区(展开后序号就看不见了);缩进也只有紧贴标题才读得通。\n tree: true,\n width: 240, // 让出让给缩进的宽度\n fixed: 'left', // 横向滚动时不能丢失「这是哪一行」\n // inline:必须行内 —— naive 的展开箭头是 inline-flex,默认块级 NSpace 会被挤到第二行。\n render: r =>\n h(NSpace, { inline: true, align: 'center', size: 6, wrapItem: false }, () => [\n translateMenuTitle(r.title, r.path || undefined),\n // 「隐藏」是罕见状态(种子里的目录/页面全是显示),只在为真时才占视觉 —— 省掉一整列同一个词。\n r.visible\n ? null\n : h(NTag, { size: 'tiny', bordered: false, type: 'warning' }, () => t('common.hidden')),\n ]),\n },\n // 图标独占一列:留在标题列会和树形展开箭头 + 缩进挤在一起撑破首列换行。\n {\n title: () => t('menu.icon'),\n key: 'icon',\n width: 64,\n align: 'center',\n render: r => (r.icon ? h(AppIcon, { icon: lineIcon(r.icon), size: 18 }) : null),\n },\n // 类型独占一列,不塞在标题里。按类型着色,目录/页面一眼可辨。\n {\n title: () => t('menu.type'),\n key: 'type',\n width: 90,\n render: r =>\n h(NTag, { size: 'small', bordered: false, type: typeTagType(r.type) }, () =>\n typeText(r.type),\n ),\n },\n // 路由/组件路径:管理员核对菜单时真正要看的东西,直接列出,不必点开编辑弹窗。\n // 不设权限码列 —— 树里只有目录/页面,而它俩结构上永远没有权限码(种子里全为空),\n // 那一列必然是「—」。权限码的正主在「权限按钮」弹窗里。\n {\n title: () => t('menu.path'),\n key: 'path',\n align: 'left',\n width: 160,\n ellipsis: { tooltip: true },\n render: r => cellPath(r.path),\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n // 权限码只作搜索项:表格里不设这一列(见上),但「这个权限码归哪个页面」得能搜到 ——\n // 取值与匹配见 SEARCH_FIELDS.permission(查该节点按钮子节点的码)。\n {\n key: 'permission',\n title: () => t('menu.permission'),\n hideInTable: true,\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n {\n title: () => t('menu.component'),\n key: 'component',\n align: 'left',\n width: 180,\n ellipsis: { tooltip: true },\n render: r => cellPath(r.component),\n },\n {\n title: () => t('menu.buttons'),\n key: 'buttons',\n width: 140,\n render: r => {\n const n = buttonInfoById.value.get(r.id)?.count ?? 0\n // 页面和目录都能「配置权限」:按钮正常挂页面下;直接挂目录下的是无页面权限项\n //(只给移动端 / 第三方调的接口),目录就是它们的权限组,角色授权界面会把它们渲染成一行。\n // 无菜单新增权限则不显示「配置权限」入口(只读用户不该看到写入口;弹窗内写按钮亦各自设门,服务端兜底)\n if (!auth.hasPerm('POST:/api/v1/sys/menu/add')) return null\n return h(\n NButton,\n {\n size: 'small',\n quaternary: true,\n type: n > 0 ? 'primary' : undefined,\n onClick: () => openButtons(r),\n },\n {\n icon: () => h(AppIcon, { icon: 'ph:shield-check', size: 15 }),\n default: () => t('menu.configPerms', { n }),\n },\n )\n },\n },\n {\n title: () => t('menu.sort'),\n key: 'sort',\n width: 70,\n render: r => h('span', { class: 'num muted' }, r.sort),\n },\n {\n title: () => t('common.status'),\n key: 'enabled',\n width: 84,\n render: r =>\n h(StatusSwitch, {\n value: r.enabled,\n disabled: !auth.hasPerm('PUT:/api/v1/sys/menu/{id}'), // 启停走全量 update,无更新权限则置灰\n // 停用是一键隐藏整棵子树的重操作,先确认;启用无副作用,跳过确认(返回 null)。\n confirm: (next: boolean) => (next ? null : t('menu.disableConfirm', { title: r.title })),\n request: (next: boolean) => menuApi.update(r.id, { ...toMenuInput(r), enabled: next }),\n // 重拉而非往行对象上写:搜索态下的祖先行是 filterTree 的浅拷贝,写它不会回到源树(开关会弹回去)。\n // StatusSwitch 是悲观更新(请求成功才 emit),重拉一次就是最终态。\n 'onUpdate:value': () => {\n reload()\n syncShell() // 停用/启用即刻反映到侧边栏\n },\n }),\n },\n // 操作收敛成「编辑 + 更多▾」且 fixed:'right':4 个操作平铺会把「删除」挤到第二行、行高参差,\n // 不 fixed 的话横向滚动时整列会被裁掉够不着。\n {\n title: () => t('common.operation'),\n key: 'op',\n width: 150,\n fixed: 'right',\n render: r =>\n h(NSpace, { size: 2, wrapItem: false, justify: 'center' }, () => {\n // 「更多」里两项各自按权限码保留;都无权时整个下拉不渲染,避免空触发器。\n const canAddChild = auth.hasPerm('POST:/api/v1/sys/menu/add')\n const canDelete = auth.hasPerm('DELETE:/api/v1/sys/menu/{id}')\n const moreOptions: DropdownOption[] = [\n canAddChild && {\n key: 'addChild',\n label: t('menu.addChild'),\n icon: () => h(AppIcon, { icon: 'ph:plus', size: 16 }),\n },\n // 新增与删除之间一条分隔线;只剩一项时不画\n canAddChild && canDelete && { type: 'divider', key: 'divider' },\n canDelete && {\n key: 'delete',\n label: t('common.delete'),\n icon: () => h(AppIcon, { icon: 'ph:trash', size: 16 }),\n props: { style: 'color: var(--err)' },\n },\n ].filter(Boolean) as DropdownOption[]\n return [\n auth.hasPerm('PUT:/api/v1/sys/menu/{id}')\n ? h(\n NButton,\n {\n size: 'small',\n quaternary: true,\n type: 'primary',\n onClick: () => menuFormRef.value?.openEdit(r),\n },\n () => t('common.edit'),\n )\n : null,\n moreOptions.length\n ? h(\n NDropdown,\n {\n trigger: 'click',\n options: moreOptions,\n onSelect: (key: string) =>\n key === 'addChild' ? menuFormRef.value?.openAdd(r.id) : onDelete(r),\n },\n () =>\n h(\n NButton,\n { size: 'small', quaternary: true, type: 'primary', iconPlacement: 'right' },\n {\n default: () => t('common.more'),\n icon: () => h(AppIcon, { icon: 'ph:caret-down', size: 12 }),\n },\n ),\n )\n : null,\n ]\n }),\n },\n]\nderiveHeaderFilters(columns)\n</script>\n\n<template>\n <!-- .fill-page 是站内约定(styles/layout.css):它的 :has() 规则把路由外层 .page-view 撑到 100%,\n 页面自身 flex 列 + overflow:hidden —— 卡片吃满内容区,滚动只发生在表体里。 -->\n <div class=\"view fill-page\">\n <!-- expanded-row-keys / update:expanded-row-keys 不是 SmartTable 的 prop —— 它 inheritAttrs:false + v-bind=\"attrs\",\n 未声明的 attr 原样透传给内层 n-data-table(:loading 走的也是这条路)。 -->\n <SmartTable\n ref=\"tableRef\"\n :columns=\"columns\"\n :fetcher=\"treeFetcher\"\n :loading=\"loading\"\n row-key=\"id\"\n :pagination=\"false\"\n :search=\"{ container: 'table' }\"\n :toolbar=\"TABLE_TOOLBAR\"\n storage-key=\"sys-menu\"\n :indent=\"26\"\n fill-height\n :min-row-height=\"TABLE_MIN_ROW_HEIGHT\"\n card-on-narrow\n :render-expand-icon=\"renderExpandIcon\"\n :expanded-row-keys=\"expandedKeys\"\n @update:expanded-row-keys=\"(keys: number[]) => (expandedKeys = keys)\"\n @loaded=\"onLoaded\"\n >\n <!-- 应用选择器决定「看哪个应用的菜单树」,是限定数据范围的控件而不是业务按钮,所以留在工具栏左半。 -->\n <template #toolbar>\n <n-select\n v-model:value=\"moduleFilter\"\n :options=\"filterOptions\"\n style=\"width: 200px\"\n :placeholder=\"t('menu.module')\"\n />\n </template>\n <template #toolbar-right>\n <n-button secondary @click=\"toggleExpandAll\">\n <template #icon>\n <AppIcon\n :icon=\"allExpanded ? 'ph:arrows-in-line-vertical' : 'ph:arrows-out-line-vertical'\"\n :size=\"16\"\n />\n </template>\n {{ allExpanded ? t('common.collapseAll') : t('common.expandAll') }}\n </n-button>\n <n-button\n v-auth=\"'POST:/api/v1/sys/menu/add'\"\n type=\"primary\"\n @click=\"menuFormRef?.openAdd(0)\"\n >\n <template #icon><AppIcon icon=\"ph:plus\" :size=\"16\" /></template>\n {{ t('common.add') }}\n </n-button>\n </template>\n </SmartTable>\n\n <MenuFormModal\n ref=\"menuFormRef\"\n :modules=\"modules\"\n :tree=\"tree\"\n :filtered-tree=\"filteredTree\"\n :module-filter=\"moduleFilter\"\n @saved=\"reloadTree\"\n />\n\n <ButtonManager\n ref=\"buttonMgrRef\"\n :tree=\"tree\"\n :routes=\"routes\"\n :app-prefix=\"currentAppPrefix\"\n @changed=\"reloadTree\"\n />\n </div>\n</template>\n\n<style scoped>\n.view {\n display: flex;\n flex-direction: column;\n gap: var(--gap-card);\n}\n\n/* 层级序号:字号/字族都跟标题列走(不用 12px 等宽),只借 tabular-nums 让各行点号竖着对齐;\n 前缀是祖先路径(压暗),末段才是这一行自己的号。 */\n:deep(.row-no) {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n:deep(.row-no__path) {\n color: var(--text-3);\n}\n\n:deep(.row-no__self) {\n color: var(--text-2);\n font-weight: var(--font-weight-medium);\n}\n\n/* 单元格里的辅助字样:路径 / 组件用等宽,排序用等宽数字,空值灰色「—」(表格单元格由库渲染,要 :deep) */\n:deep(.mono) {\n font-family: var(--font-mono);\n font-size: 13.5px;\n letter-spacing: 0;\n}\n:deep(.muted) {\n color: var(--text-2);\n}\n:deep(.faint) {\n color: var(--text-3);\n}\n:deep(.num) {\n font-variant-numeric: tabular-nums;\n}\n</style>\n"],"names":["UNASSIGNED","ALL_MODULES","t","useI18n","message","useMessage","confirm","useConfirm","auth","useAuthStore","loading","ref","tree","shallowRef","modules","moduleFilter","routes","load","menuApi","translateError","syncShell","buildRoutesForModule","onMounted","moduleApi","filterOptions","o","_a","typeText","ty","MenuType","typeTagType","moduleOptions","computed","translateMenuTitle","m","stripButtons","nodes","n","filteredTree","r","buttonInfoById","walk","btns","c","b","splitPermission","tableRef","reload","SEARCH_FIELDS","searchedResults","treeFetcher","params","items","filterTreeByParams","hasTreeCondition","expandedKeys","visibleTree","searching","onLoaded","rows","now","toRaw","expandableIds","watch","allExpanded","toggleExpandAll","currentAppPrefix","menuFormRef","buttonMgrRef","openButtons","reloadTree","onDelete","ok","renderExpandIcon","expanded","h","AppIcon","rowNoById","prefix","i","no","cellPath","v","columns","cut","SEARCH_ACTIONS","NSpace","NTag","lineIcon","NButton","StatusSwitch","next","toMenuInput","canAddChild","canDelete","moreOptions","NDropdown","key","deriveHeaderFilters","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","SmartTable","TABLE_TOOLBAR","TABLE_MIN_ROW_HEIGHT","_cache","keys","NSelect","$event","_createTextVNode","_toDisplayString","_createBlock","MenuFormModal","ButtonManager"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import o from "./index2.vue.js";
2
2
  /* empty css */
3
3
  import t from "../../../_virtual/_plugin-vue_export-helper.js";
4
- const _ = /* @__PURE__ */ t(o, [["__scopeId", "data-v-23065335"]]);
4
+ const m = /* @__PURE__ */ t(o, [["__scopeId", "data-v-e8a5166d"]]);
5
5
  export {
6
- _ as default
6
+ m as default
7
7
  };
8
8
  //# sourceMappingURL=index.vue.js.map