smart-admin-web 10.20.0 → 10.21.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 (146) 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/index.js +248 -246
  17. package/dist/layouts/NoticeBell.vue.js +2 -2
  18. package/dist/layouts/NoticeBell2.vue.js +18 -17
  19. package/dist/layouts/NoticeBell2.vue.js.map +1 -1
  20. package/dist/lib/runtime.js +1 -1
  21. package/dist/lib/watermark.d.ts +1 -1
  22. package/dist/locales/en-US.d.ts +62 -3
  23. package/dist/locales/en-US.js +66 -7
  24. package/dist/locales/en-US.js.map +1 -1
  25. package/dist/locales/kernelMenuTitles.js +2 -1
  26. package/dist/locales/kernelMenuTitles.js.map +1 -1
  27. package/dist/locales/zh-CN.d.ts +62 -3
  28. package/dist/locales/zh-CN.js +66 -7
  29. package/dist/locales/zh-CN.js.map +1 -1
  30. package/dist/router/viewRegistry.js +6 -6
  31. package/dist/router/viewRegistry.js.map +1 -1
  32. package/dist/style.css +1 -1
  33. package/dist/theme/naive-theme.js +72 -67
  34. package/dist/theme/naive-theme.js.map +1 -1
  35. package/dist/types/api.d.ts +91 -0
  36. package/dist/types/api.js +17 -15
  37. package/dist/types/api.js.map +1 -1
  38. package/dist/utils/shake.d.ts +1 -0
  39. package/dist/utils/shake.js +30 -0
  40. package/dist/utils/shake.js.map +1 -0
  41. package/dist/views/login/LoginForm.vue.js +2 -2
  42. package/dist/views/login/LoginForm2.vue.js +135 -135
  43. package/dist/views/login/LoginForm2.vue.js.map +1 -1
  44. package/dist/views/login/components/LoginDesk.vue.js +1 -1
  45. package/dist/views/login/components/LoginDesk2.vue.js +59 -47
  46. package/dist/views/login/components/LoginDesk2.vue.js.map +1 -1
  47. package/dist/views/personal/notice2.vue.js +1 -1
  48. package/dist/views/personal/notice2.vue.js.map +1 -1
  49. package/dist/views/personal/sessions.vue.js +2 -2
  50. package/dist/views/personal/sessions2.vue.js +41 -40
  51. package/dist/views/personal/sessions2.vue.js.map +1 -1
  52. package/dist/views/system/ai-usage/index.vue.js +3 -3
  53. package/dist/views/system/ai-usage/index2.vue.js +13 -12
  54. package/dist/views/system/ai-usage/index2.vue.js.map +1 -1
  55. package/dist/views/system/config/components/AdvancedSection.vue.js +3 -3
  56. package/dist/views/system/config/components/AdvancedSection2.vue.js +2 -1
  57. package/dist/views/system/config/components/AdvancedSection2.vue.js.map +1 -1
  58. package/dist/views/system/dict/index.vue.js +2 -2
  59. package/dist/views/system/dict/index2.vue.js +12 -1
  60. package/dist/views/system/dict/index2.vue.js.map +1 -1
  61. package/dist/views/system/file/index2.vue.js +1 -0
  62. package/dist/views/system/file/index2.vue.js.map +1 -1
  63. package/dist/views/system/job/index.vue.js +3 -3
  64. package/dist/views/system/job/index2.vue.js +182 -181
  65. package/dist/views/system/job/index2.vue.js.map +1 -1
  66. package/dist/views/system/job-log/components/AttemptTable2.vue.js +8 -3
  67. package/dist/views/system/job-log/components/AttemptTable2.vue.js.map +1 -1
  68. package/dist/views/system/job-log/index.vue.js +1 -1
  69. package/dist/views/system/job-log/index2.vue.js +5 -5
  70. package/dist/views/system/job-log/index2.vue.js.map +1 -1
  71. package/dist/views/system/log/exception/index.vue.js +2 -2
  72. package/dist/views/system/log/exception/index2.vue.js +8 -5
  73. package/dist/views/system/log/exception/index2.vue.js.map +1 -1
  74. package/dist/views/system/log/login/index2.vue.js +21 -12
  75. package/dist/views/system/log/login/index2.vue.js.map +1 -1
  76. package/dist/views/system/log/op/index.vue.js +2 -2
  77. package/dist/views/system/log/op/index2.vue.js +50 -40
  78. package/dist/views/system/log/op/index2.vue.js.map +1 -1
  79. package/dist/views/system/menu/components/ButtonManager.vue.js +2 -2
  80. package/dist/views/system/menu/components/ButtonManager2.vue.js +45 -36
  81. package/dist/views/system/menu/components/ButtonManager2.vue.js.map +1 -1
  82. package/dist/views/system/menu/index.vue.js +2 -2
  83. package/dist/views/system/menu/index2.vue.js +11 -11
  84. package/dist/views/system/menu/index2.vue.js.map +1 -1
  85. package/dist/views/system/module/index.vue.js +1 -1
  86. package/dist/views/system/module/index2.vue.js +171 -117
  87. package/dist/views/system/module/index2.vue.js.map +1 -1
  88. package/dist/views/system/notice/index.vue.js +3 -3
  89. package/dist/views/system/notice/index2.vue.js +1 -0
  90. package/dist/views/system/notice/index2.vue.js.map +1 -1
  91. package/dist/views/system/org/index.vue.js +2 -2
  92. package/dist/views/system/org/index2.vue.js +70 -67
  93. package/dist/views/system/org/index2.vue.js.map +1 -1
  94. package/dist/views/system/position/index.vue.js +2 -2
  95. package/dist/views/system/position/index2.vue.js +57 -48
  96. package/dist/views/system/position/index2.vue.js.map +1 -1
  97. package/dist/views/system/recycle/index.vue.js +1 -1
  98. package/dist/views/system/recycle/index2.vue.js +3 -1
  99. package/dist/views/system/recycle/index2.vue.js.map +1 -1
  100. package/dist/views/system/role/components/GrantMenuSheet.vue.d.ts +2 -0
  101. package/dist/views/system/role/components/GrantMenuSheet.vue.js +2 -2
  102. package/dist/views/system/role/components/GrantMenuSheet2.vue.js +54 -49
  103. package/dist/views/system/role/components/GrantMenuSheet2.vue.js.map +1 -1
  104. package/dist/views/system/role/components/GrantMenuTable.vue.js +1 -1
  105. package/dist/views/system/role/components/GrantMenuTable2.vue.js +159 -195
  106. package/dist/views/system/role/components/GrantMenuTable2.vue.js.map +1 -1
  107. package/dist/views/system/role/components/grantMenuGroups.d.ts +54 -0
  108. package/dist/views/system/role/components/grantMenuGroups.js +116 -76
  109. package/dist/views/system/role/components/grantMenuGroups.js.map +1 -1
  110. package/dist/views/system/role/index.vue.js +2 -2
  111. package/dist/views/system/role/index2.vue.js +152 -145
  112. package/dist/views/system/role/index2.vue.js.map +1 -1
  113. package/dist/views/system/session/index2.vue.js +39 -35
  114. package/dist/views/system/session/index2.vue.js.map +1 -1
  115. package/dist/views/system/user/components/UserGrantEffectiveTable.vue.d.ts +10 -0
  116. package/dist/views/system/user/components/UserGrantEffectiveTable.vue.js +5 -0
  117. package/dist/views/system/user/components/UserGrantEffectiveTable.vue.js.map +1 -0
  118. package/dist/views/system/user/components/UserGrantEffectiveTable2.vue.js +96 -0
  119. package/dist/views/system/user/components/UserGrantEffectiveTable2.vue.js.map +1 -0
  120. package/dist/views/system/user/components/UserGrantMenuSheet.vue.d.ts +13 -0
  121. package/dist/views/system/user/components/UserGrantMenuSheet.vue.js +8 -0
  122. package/dist/views/system/user/components/UserGrantMenuSheet.vue.js.map +1 -0
  123. package/dist/views/system/user/components/UserGrantMenuSheet2.vue.js +216 -0
  124. package/dist/views/system/user/components/UserGrantMenuSheet2.vue.js.map +1 -0
  125. package/dist/views/system/user/components/UserGrantMenuTable.vue.d.ts +13 -0
  126. package/dist/views/system/user/components/UserGrantMenuTable.vue.js +8 -0
  127. package/dist/views/system/user/components/UserGrantMenuTable.vue.js.map +1 -0
  128. package/dist/views/system/user/components/UserGrantMenuTable2.vue.js +186 -0
  129. package/dist/views/system/user/components/UserGrantMenuTable2.vue.js.map +1 -0
  130. package/dist/views/system/user/components/UserGrantNodeRow.vue.d.ts +31 -0
  131. package/dist/views/system/user/components/UserGrantNodeRow.vue.js +8 -0
  132. package/dist/views/system/user/components/UserGrantNodeRow.vue.js.map +1 -0
  133. package/dist/views/system/user/components/UserGrantNodeRow2.vue.js +171 -0
  134. package/dist/views/system/user/components/UserGrantNodeRow2.vue.js.map +1 -0
  135. package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.d.ts +21 -0
  136. package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.js +5 -0
  137. package/dist/views/system/user/components/UserGrantOverviewDrawer.vue.js.map +1 -0
  138. package/dist/views/system/user/components/UserGrantOverviewDrawer2.vue.js +178 -0
  139. package/dist/views/system/user/components/UserGrantOverviewDrawer2.vue.js.map +1 -0
  140. package/dist/views/system/user/components/userGrantState.d.ts +64 -0
  141. package/dist/views/system/user/components/userGrantState.js +117 -0
  142. package/dist/views/system/user/components/userGrantState.js.map +1 -0
  143. package/dist/views/system/user/index.vue.js +2 -2
  144. package/dist/views/system/user/index2.vue.js +250 -207
  145. package/dist/views/system/user/index2.vue.js.map +1 -1
  146. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { defineComponent as W, ref as u, openBlock as l, createElementBlock as d, Fragment as R, createVNode as n, unref as e, withCtx as o, createElementVNode as m, toDisplayString as c, createTextVNode as p, renderList as I, normalizeClass as L, createCommentVNode as T, createBlock as S, createSlots as X } from "vue";
1
+ import { defineComponent as W, ref as u, openBlock as l, createElementBlock as d, Fragment as R, createVNode as n, unref as e, withCtx as o, createElementVNode as f, toDisplayString as c, createTextVNode as p, renderList as I, normalizeClass as L, createCommentVNode as T, createBlock as S, createSlots as X } from "vue";
2
2
  import { useRouter as Y } from "vue-router";
3
3
  import { useMessage as Z, NPopover as ee, NButton as k, NSpin as te, NScrollbar as ae, NEmpty as oe, NTag as ne, NEllipsis as ie, NText as U, NBadge as le, NModal as se, NSpace as ce } from "naive-ui";
4
4
  import { Icon as N } from "@iconify/vue";
@@ -8,9 +8,9 @@ import { noticeApi as b } from "../api/index.js";
8
8
  import { noticeBus as de } from "../composables/useRealtime.js";
9
9
  import { useNoticeAction as pe } from "../composables/useNoticeAction.js";
10
10
  import { NoticeType as x } from "../types/api.js";
11
- import me from "../components/MarkdownEditor/MarkdownView.vue.js";
11
+ import fe from "../components/MarkdownEditor/MarkdownView.vue.js";
12
12
  import { translateError as D } from "../utils/error.js";
13
- import { fmtDateTime as fe } from "../utils/format.js";
13
+ import { fmtDateTime as me } from "../utils/format.js";
14
14
  const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { class: "notice-title" }, _e = {
15
15
  key: 0,
16
16
  class: "notice-empty"
@@ -24,7 +24,7 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
24
24
  __name: "NoticeBell",
25
25
  setup(Ae) {
26
26
  const { t: s } = ue(), F = Y(), z = Z(), A = pe(), y = u(0), _ = u([]), C = u(!1), w = u(!1);
27
- async function f() {
27
+ async function m() {
28
28
  try {
29
29
  y.value = await b.unreadCount();
30
30
  } catch {
@@ -48,7 +48,7 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
48
48
  async function j(t) {
49
49
  if (!t.isRead)
50
50
  try {
51
- await b.markRead(t.id), t.isRead = !0, await f();
51
+ await b.markRead(t.id), t.isRead = !0, await m();
52
52
  } catch (i) {
53
53
  z.error(D(i));
54
54
  }
@@ -67,7 +67,7 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
67
67
  }
68
68
  async function H() {
69
69
  try {
70
- await b.markAllRead(), await Promise.all([f(), B()]);
70
+ await b.markAllRead(), await Promise.all([m(), B()]);
71
71
  } catch (t) {
72
72
  z.error(D(t));
73
73
  }
@@ -82,8 +82,8 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
82
82
  return i.length > 60 ? i.slice(0, 60) + "…" : i;
83
83
  }
84
84
  return de.on(() => {
85
- f();
86
- }), re(f, 3e4), f(), (t, i) => {
85
+ m();
86
+ }), re(m, 3e4), m(), (t, i) => {
87
87
  var M, V, $;
88
88
  return l(), d(R, null, [
89
89
  n(e(ee), {
@@ -101,7 +101,8 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
101
101
  n(e(le), {
102
102
  value: y.value,
103
103
  max: 99,
104
- show: y.value > 0
104
+ show: y.value > 0,
105
+ offset: [0, 5]
105
106
  }, {
106
107
  default: o(() => [
107
108
  n(e(k), {
@@ -122,9 +123,9 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
122
123
  }, 8, ["value", "show"])
123
124
  ]),
124
125
  default: o(() => [
125
- m("div", he, [
126
- m("div", ve, [
127
- m("span", ye, c(e(s)("app.notice.title")), 1),
126
+ f("div", he, [
127
+ f("div", ve, [
128
+ f("span", ye, c(e(s)("app.notice.title")), 1),
128
129
  n(e(k), {
129
130
  text: "",
130
131
  size: "tiny",
@@ -161,8 +162,8 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
161
162
  onClick: (Q) => q(a)
162
163
  }, [
163
164
  a.isRead ? T("", !0) : (l(), d("span", ke)),
164
- m("div", Ne, [
165
- m("div", be, [
165
+ f("div", Ne, [
166
+ f("div", be, [
166
167
  n(e(ne), {
167
168
  type: K(a.type),
168
169
  size: "tiny",
@@ -204,7 +205,7 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
204
205
  class: "item-time"
205
206
  }, {
206
207
  default: o(() => [
207
- p(c(e(fe)(a.publishTime, { seconds: !1 })), 1)
208
+ p(c(e(me)(a.publishTime, { seconds: !1 })), 1)
208
209
  ]),
209
210
  _: 2
210
211
  }, 1024)
@@ -218,7 +219,7 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
218
219
  ]),
219
220
  _: 1
220
221
  }, 8, ["show"]),
221
- m("div", xe, [
222
+ f("div", xe, [
222
223
  n(e(k), {
223
224
  text: "",
224
225
  size: "small",
@@ -250,7 +251,7 @@ const he = { class: "notice-panel" }, ve = { class: "notice-head" }, ye = { clas
250
251
  default: o(() => {
251
252
  var a;
252
253
  return [
253
- n(me, {
254
+ n(fe, {
254
255
  value: (a = h.value) == null ? void 0 : a.content
255
256
  }, null, 8, ["value"])
256
257
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"NoticeBell2.vue.js","sources":["../../src/layouts/NoticeBell.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 顶栏消息通知铃铛。富面板(n-popover):无页签单列表 + 正文弹层。\n// 未读=圆点+加粗+淡主色底,已读=整行变淡;\"读没读\"是过滤器不是分类,不做成页签(筛选在个人通知页)。\n// 未读数每 30s 轮询;面板展开时拉取列表。\n// 通知带动作列表时,正文弹层底部渲染成按钮(useNoticeAction):铃铛里直接\"办\",不必先跳到目标页面找按钮。\nimport { ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport {\n NPopover,\n NBadge,\n NButton,\n NTag,\n NEllipsis,\n NScrollbar,\n NSpace,\n NText,\n NEmpty,\n NModal,\n NSpin,\n useMessage,\n} from 'naive-ui'\nimport { Icon } from '@iconify/vue'\nimport { useIntervalFn } from '@vueuse/core'\nimport { useI18n } from 'vue-i18n'\nimport { noticeApi } from '#/api'\nimport { noticeBus } from '#/composables/useRealtime'\nimport { useNoticeAction } from '#/composables/useNoticeAction'\nimport { NoticeType, type NoticeAction, type NoticeMineItem } from '#/types/api'\nimport MarkdownView from '#/components/MarkdownEditor/MarkdownView.vue'\nimport { translateError } from '#/utils/error'\nimport { fmtDateTime } from '#/utils/format'\n\nconst { t } = useI18n()\nconst router = useRouter()\nconst message = useMessage()\nconst noticeAction = useNoticeAction()\n\nconst unread = ref(0)\nconst items = ref<NoticeMineItem[]>([])\nconst loading = ref(false)\nconst panelShow = ref(false)\n\nasync function fetchUnread() {\n try {\n unread.value = await noticeApi.unreadCount()\n } catch {\n /* 轮询失败静默,下次自愈 */\n }\n}\n// 无页签单列表:恒拉最近一页混排,未读靠条目样式强调;筛选能力在\"查看全部\"的个人通知页。\nasync function fetchList() {\n loading.value = true\n try {\n const { items: list } = await noticeApi.mine({ page: 1, pageSize: 10 })\n items.value = list\n } catch {\n items.value = []\n } finally {\n loading.value = false\n }\n}\nfunction onShow(show: boolean) {\n if (show) fetchList()\n}\n\n// 正文弹层:内容随列表取回,直接渲染(Markdown,不能纯文本直出);点条目即标记已读。\nconst showNotice = ref(false)\nconst viewNotice = ref<NoticeMineItem | null>(null)\nasync function markRead(item: NoticeMineItem) {\n if (item.isRead) return\n try {\n await noticeApi.markRead(item.id)\n item.isRead = true\n await fetchUnread()\n } catch (e) {\n message.error(translateError(e))\n }\n}\nasync function openNotice(item: NoticeMineItem) {\n viewNotice.value = item\n showNotice.value = true\n await markRead(item)\n}\n// 动作:成功后关掉弹层与面板(页面跳转时尤其要关,否则面板盖在新页面上)。\nconst running = ref<number | null>(null)\nasync function onAction(a: NoticeAction, index: number) {\n running.value = index\n try {\n if (await noticeAction.run(a)) {\n showNotice.value = false\n panelShow.value = false\n }\n } finally {\n running.value = null\n }\n}\nasync function markAllRead() {\n try {\n await noticeApi.markAllRead()\n await Promise.all([fetchUnread(), fetchList()])\n } catch (e) {\n message.error(translateError(e))\n }\n}\nfunction viewAll() {\n panelShow.value = false\n router.push('/personal/notice')\n}\n\n// 类型 → 标签色/文案。新增类型在此补一行即可。\nconst tagType = (ty: NoticeType) =>\n ty === NoticeType.Announcement ? 'warning' : ty === NoticeType.Message ? 'success' : 'info'\nconst typeLabel = (ty: NoticeType) =>\n ty === NoticeType.Announcement\n ? t('notice.typeAnnouncement')\n : ty === NoticeType.Message\n ? t('notice.typeMessage')\n : t('notice.typeNotice')\n\n// 正文摘要:去掉常见 Markdown 记号,压空白,截断。ponytail: 正则够用,要精准渲染再引 md 解析。\nfunction snippet(md?: string | null) {\n if (!md) return ''\n const s = md\n .replace(/!\\[[^\\]]*\\]\\([^)]*\\)/g, '') // 图片\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1') // 链接留文字\n .replace(/[#>*_`~-]/g, '') // 记号\n .replace(/\\s+/g, ' ')\n .trim()\n return s.length > 60 ? s.slice(0, 60) + '…' : s\n}\n\n// 双腿:实时推送到达即刷新未读(SignalR 开启时,即时);30s 轮询兜底(实时关闭/连接掉线时)。\n// VueUse 的 useEventBus.on 在组件卸载时自动退订。\nnoticeBus.on(() => {\n fetchUnread()\n})\nuseIntervalFn(fetchUnread, 30000)\nfetchUnread()\n</script>\n\n<template>\n <n-popover\n v-model:show=\"panelShow\"\n trigger=\"click\"\n placement=\"bottom-end\"\n :show-arrow=\"false\"\n style=\"padding: 0\"\n @update:show=\"onShow\"\n >\n <template #trigger>\n <n-badge :value=\"unread\" :max=\"99\" :show=\"unread > 0\">\n <n-button quaternary circle :aria-label=\"t('app.notice.title')\">\n <Icon icon=\"ph:bell\" :width=\"19\" />\n </n-button>\n </n-badge>\n </template>\n\n <div class=\"notice-panel\">\n <div class=\"notice-head\">\n <span class=\"notice-title\">{{ t('app.notice.title') }}</span>\n <n-button text size=\"tiny\" :disabled=\"unread === 0\" @click=\"markAllRead\">\n <template #icon><Icon icon=\"ph:checks\" :width=\"15\" /></template>\n {{ t('app.notice.markAllRead') }}\n </n-button>\n </div>\n\n <n-spin :show=\"loading\">\n <n-scrollbar style=\"max-height: 360px\">\n <div v-if=\"items.length === 0\" class=\"notice-empty\">\n <n-empty :description=\"t('app.notice.empty')\" size=\"small\" />\n </div>\n <ul v-else class=\"notice-list\">\n <li\n v-for=\"n in items\"\n :key=\"n.id\"\n :class=\"['notice-item', n.isRead ? 'read' : 'unread']\"\n @click=\"openNotice(n)\"\n >\n <span v-if=\"!n.isRead\" class=\"dot\" />\n <div class=\"notice-body\">\n <div class=\"line1\">\n <n-tag :type=\"tagType(n.type)\" size=\"tiny\" :bordered=\"false\" round>\n {{ typeLabel(n.type) }}\n </n-tag>\n <n-ellipsis class=\"item-title\" :class=\"{ unread: !n.isRead }\">\n {{ n.title }}\n </n-ellipsis>\n <Icon\n v-if=\"n.actions?.length\"\n icon=\"ph:lightning\"\n :width=\"13\"\n class=\"item-actions-mark\"\n />\n </div>\n <n-text v-if=\"snippet(n.content)\" depth=\"3\" class=\"item-snippet\">\n {{ snippet(n.content) }}\n </n-text>\n <n-text depth=\"3\" class=\"item-time\">\n {{ fmtDateTime(n.publishTime, { seconds: false }) }}\n </n-text>\n </div>\n </li>\n </ul>\n </n-scrollbar>\n </n-spin>\n\n <div class=\"notice-foot\">\n <n-button text size=\"small\" @click=\"viewAll\">\n {{ t('app.notice.viewAll') }}\n <template #icon><Icon icon=\"ph:arrow-right\" :width=\"14\" /></template>\n </n-button>\n </div>\n </div>\n </n-popover>\n\n <n-modal\n v-model:show=\"showNotice\"\n preset=\"card\"\n :title=\"viewNotice?.title || t('notice.detailTitle')\"\n style=\"width: 640px; max-width: 92vw\"\n >\n <MarkdownView :value=\"viewNotice?.content\" />\n <template v-if=\"viewNotice?.actions?.length\" #footer>\n <n-space justify=\"end\">\n <n-button\n v-for=\"(a, i) in viewNotice.actions\"\n :key=\"i\"\n size=\"small\"\n :type=\"noticeAction.buttonType(a)\"\n :loading=\"running === i\"\n :disabled=\"running !== null && running !== i\"\n @click=\"onAction(a, i)\"\n >\n {{ noticeAction.label(a) }}\n </n-button>\n </n-space>\n </template>\n </n-modal>\n</template>\n\n<style scoped>\n.notice-panel {\n width: 360px;\n max-width: 92vw;\n}\n.notice-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px 4px;\n}\n.notice-title {\n font-weight: 600;\n font-size: 15px;\n}\n.notice-empty {\n padding: 32px 0;\n}\n.notice-list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.notice-item {\n position: relative;\n display: flex;\n gap: 8px;\n padding: 12px 16px;\n cursor: pointer;\n border-top: 1px solid var(--separator);\n transition: background-color var(--transition-fast);\n}\n.notice-item:hover {\n background-color: var(--hover);\n}\n/* 未读:整行淡主色底(明暗主题都成立);已读:整行变淡 */\n.notice-item.unread {\n background-color: color-mix(in srgb, var(--signal) 5%, transparent);\n}\n.notice-item.unread:hover {\n background-color: color-mix(in srgb, var(--signal) 10%, transparent);\n}\n.notice-item.read {\n opacity: 0.55;\n}\n.dot {\n position: absolute;\n left: 6px;\n top: 18px;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--signal);\n}\n.notice-body {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.line1 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.item-title {\n flex: 1;\n min-width: 0;\n font-size: 14px;\n}\n.item-title.unread {\n font-weight: 600;\n}\n.item-actions-mark {\n flex-shrink: 0;\n color: var(--signal);\n}\n.item-snippet {\n font-size: 13px;\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.item-time {\n font-size: 12.5px;\n}\n.notice-foot {\n display: flex;\n justify-content: center;\n padding: 8px;\n border-top: 1px solid var(--separator);\n}\n</style>\n"],"names":["t","useI18n","router","useRouter","message","useMessage","noticeAction","useNoticeAction","unread","ref","items","loading","panelShow","fetchUnread","noticeApi","fetchList","list","onShow","show","showNotice","viewNotice","markRead","item","e","translateError","openNotice","running","onAction","a","index","markAllRead","viewAll","tagType","ty","NoticeType","typeLabel","snippet","md","s","noticeBus","useIntervalFn","_createVNode","_unref","NPopover","$event","NBadge","NButton","Icon","_createElementVNode","_hoisted_1","_hoisted_2","_hoisted_3","_toDisplayString","_createTextVNode","NSpin","NScrollbar","_openBlock","_createElementBlock","_hoisted_4","NEmpty","_hoisted_5","_Fragment","_renderList","n","_normalizeClass","_hoisted_7","_hoisted_8","_hoisted_9","NTag","NEllipsis","_a","_createBlock","NText","fmtDateTime","_hoisted_10","NModal","MarkdownView","_c","_b","NSpace","i"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,UAAM,EAAE,GAAAA,EAAA,IAAMC,GAAA,GACRC,IAASC,EAAA,GACTC,IAAUC,EAAA,GACVC,IAAeC,GAAA,GAEfC,IAASC,EAAI,CAAC,GACdC,IAAQD,EAAsB,EAAE,GAChCE,IAAUF,EAAI,EAAK,GACnBG,IAAYH,EAAI,EAAK;AAE3B,mBAAeI,IAAc;AAC3B,UAAI;AACF,QAAAL,EAAO,QAAQ,MAAMM,EAAU,YAAA;AAAA,MACjC,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,mBAAeC,IAAY;AACzB,MAAAJ,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAM,EAAE,OAAOK,EAAA,IAAS,MAAMF,EAAU,KAAK,EAAE,MAAM,GAAG,UAAU,GAAA,CAAI;AACtE,QAAAJ,EAAM,QAAQM;AAAA,MAChB,QAAQ;AACN,QAAAN,EAAM,QAAQ,CAAA;AAAA,MAChB,UAAA;AACE,QAAAC,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AACA,aAASM,EAAOC,GAAe;AAC7B,MAAIA,KAAMH,EAAA;AAAA,IACZ;AAGA,UAAMI,IAAaV,EAAI,EAAK,GACtBW,IAAaX,EAA2B,IAAI;AAClD,mBAAeY,EAASC,GAAsB;AAC5C,UAAI,CAAAA,EAAK;AACT,YAAI;AACF,gBAAMR,EAAU,SAASQ,EAAK,EAAE,GAChCA,EAAK,SAAS,IACd,MAAMT,EAAA;AAAA,QACR,SAASU,GAAG;AACV,UAAAnB,EAAQ,MAAMoB,EAAeD,CAAC,CAAC;AAAA,QACjC;AAAA,IACF;AACA,mBAAeE,EAAWH,GAAsB;AAC9C,MAAAF,EAAW,QAAQE,GACnBH,EAAW,QAAQ,IACnB,MAAME,EAASC,CAAI;AAAA,IACrB;AAEA,UAAMI,IAAUjB,EAAmB,IAAI;AACvC,mBAAekB,EAASC,GAAiBC,GAAe;AACtD,MAAAH,EAAQ,QAAQG;AAChB,UAAI;AACF,QAAI,MAAMvB,EAAa,IAAIsB,CAAC,MAC1BT,EAAW,QAAQ,IACnBP,EAAU,QAAQ;AAAA,MAEtB,UAAA;AACE,QAAAc,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AACA,mBAAeI,IAAc;AAC3B,UAAI;AACF,cAAMhB,EAAU,YAAA,GAChB,MAAM,QAAQ,IAAI,CAACD,KAAeE,EAAA,CAAW,CAAC;AAAA,MAChD,SAASQ,GAAG;AACV,QAAAnB,EAAQ,MAAMoB,EAAeD,CAAC,CAAC;AAAA,MACjC;AAAA,IACF;AACA,aAASQ,IAAU;AACjB,MAAAnB,EAAU,QAAQ,IAClBV,EAAO,KAAK,kBAAkB;AAAA,IAChC;AAGA,UAAM8B,IAAU,CAACC,MACfA,MAAOC,EAAW,eAAe,YAAYD,MAAOC,EAAW,UAAU,YAAY,QACjFC,IAAY,CAACF,MACjBA,MAAOC,EAAW,eACdlC,EAAE,yBAAyB,IAC3BiC,MAAOC,EAAW,UAChBlC,EAAE,oBAAoB,IACtBA,EAAE,mBAAmB;AAG7B,aAASoC,EAAQC,GAAoB;AACnC,UAAI,CAACA,EAAI,QAAO;AAChB,YAAMC,IAAID,EACP,QAAQ,yBAAyB,EAAE,EACnC,QAAQ,0BAA0B,IAAI,EACtC,QAAQ,cAAc,EAAE,EACxB,QAAQ,QAAQ,GAAG,EACnB,KAAA;AACH,aAAOC,EAAE,SAAS,KAAKA,EAAE,MAAM,GAAG,EAAE,IAAI,MAAMA;AAAA,IAChD;AAIA,WAAAC,GAAU,GAAG,MAAM;AACjB,MAAA1B,EAAA;AAAA,IACF,CAAC,GACD2B,GAAc3B,GAAa,GAAK,GAChCA,EAAA;;;QAIE4B,EAwEYC,EAAAC,EAAA,GAAA;AAAA,UAvEF,MAAM/B,EAAA;AAAA;mCAAAA,EAAS,QAAAgC;AAAA,YAKT3B;AAAA,UAAA;AAAA,UAJd,SAAQ;AAAA,UACR,WAAU;AAAA,UACT,cAAY;AAAA,UACb,OAAA,EAAA,SAAA,IAAA;AAAA,QAAA;UAGW,WACT,MAIU;AAAA,YAJVwB,EAIUC,EAAAG,EAAA,GAAA;AAAA,cAJA,OAAOrC,EAAA;AAAA,cAAS,KAAK;AAAA,cAAK,MAAMA,EAAA,QAAM;AAAA,YAAA;yBAC9C,MAEW;AAAA,gBAFXiC,EAEWC,EAAAI,CAAA,GAAA;AAAA,kBAFD,YAAA;AAAA,kBAAW,QAAA;AAAA,kBAAQ,cAAYJ,EAAA1C,CAAA,EAAC,kBAAA;AAAA,gBAAA;6BACxC,MAAmC;AAAA,oBAAnCyC,EAAmCC,EAAAK,CAAA,GAAA;AAAA,sBAA7B,MAAK;AAAA,sBAAW,OAAO;AAAA,oBAAA;;;;;;;;qBAKnC,MAuDM;AAAA,YAvDNC,EAuDM,OAvDNC,IAuDM;AAAA,cAtDJD,EAMM,OANNE,IAMM;AAAA,gBALJF,EAA6D,QAA7DG,IAA6DC,EAA/BV,EAAA1C,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,gBAC/ByC,EAGWC,EAAAI,CAAA,GAAA;AAAA,kBAHD,MAAA;AAAA,kBAAK,MAAK;AAAA,kBAAQ,UAAUtC,EAAA,UAAM;AAAA,kBAAS,SAAOsB;AAAA,gBAAA;kBAC/C,QAAK,MAAqC;AAAA,oBAArCW,EAAqCC,EAAAK,CAAA,GAAA;AAAA,sBAA/B,MAAK;AAAA,sBAAa,OAAO;AAAA,oBAAA;;6BAAiB,MAChE;AAAA,oBADgEM,EAAA,QAC7DX,EAAA1C,CAAA,EAAC,wBAAA,CAAA,GAAA,CAAA;AAAA,kBAAA;;;;cAIRyC,EAsCSC,EAAAY,EAAA,GAAA,EAtCA,MAAM3C,EAAA,SAAO;AAAA,2BACpB,MAoCc;AAAA,kBApCd8B,EAoCcC,EAAAa,EAAA,GAAA,EApCD,OAAA,EAAA,cAAA,QAAA,KAAyB;AAAA,+BACpC,MAEM;AAAA,sBAFK7C,EAAA,MAAM,WAAM,KAAvB8C,KAAAC,EAEM,OAFNC,IAEM;AAAA,wBADJjB,EAA6DC,EAAAiB,EAAA,GAAA;AAAA,0BAAnD,aAAajB,EAAA1C,CAAA,EAAC,kBAAA;AAAA,0BAAsB,MAAK;AAAA,wBAAA;6BAErDwD,EAAA,GAAAC,EA+BK,MA/BLG,IA+BK;AAAA,gCA9BHH,EA6BKI,GAAA,MAAAC,EA5BSpD,EAAA,OAAK,CAAVqD,MAAC;;sCADVN,EA6BK,MAAA;AAAA,4BA3BF,KAAKM,EAAE;AAAA,4BACP,OAAKC,EAAA,CAAA,eAAkBD,EAAE,SAAM,SAAA,QAAA,CAAA;AAAA,4BAC/B,SAAK,CAAAnB,MAAEnB,EAAWsC,CAAC;AAAA,0BAAA;4BAEPA,EAAE,sBAAfP,EAAA,GAAAC,EAAqC,QAArCQ,EAAqC;AAAA,4BACrCjB,EAqBM,OArBNkB,IAqBM;AAAA,8BApBJlB,EAaM,OAbNmB,IAaM;AAAA,gCAZJ1B,EAEQC,EAAA0B,EAAA,GAAA;AAAA,kCAFA,MAAMpC,EAAQ+B,EAAE,IAAI;AAAA,kCAAG,MAAK;AAAA,kCAAQ,UAAU;AAAA,kCAAO,OAAA;AAAA,gCAAA;6CAC3D,MAAuB;AAAA,wCAApB5B,EAAU4B,EAAE,IAAI,CAAA,GAAA,CAAA;AAAA,kCAAA;;;gCAErBtB,EAEaC,EAAA2B,EAAA,GAAA;AAAA,kCAFD,OAAKL,EAAA,CAAC,cAAY,EAAA,QAAA,CAAoBD,EAAE,QAAM,CAAA;AAAA,gCAAA;6CACxD,MAAa;AAAA,oCAAVV,EAAAD,EAAAW,EAAE,KAAK,GAAA,CAAA;AAAA,kCAAA;;;iCAGJO,IAAAP,EAAE,YAAF,QAAAO,EAAW,eADnBC,EAKE7B,EAAAK,CAAA,GAAA;AAAA;kCAHA,MAAK;AAAA,kCACJ,OAAO;AAAA,kCACR,OAAM;AAAA,gCAAA;;8BAGIX,EAAQ2B,EAAE,OAAO,UAA/BQ,EAES7B,EAAA8B,CAAA,GAAA;AAAA;gCAFyB,OAAM;AAAA,gCAAI,OAAM;AAAA,8BAAA;2CAChD,MAAwB;AAAA,sCAArBpC,EAAQ2B,EAAE,OAAO,CAAA,GAAA,CAAA;AAAA,gCAAA;;;8BAEtBtB,EAESC,EAAA8B,CAAA,GAAA;AAAA,gCAFD,OAAM;AAAA,gCAAI,OAAM;AAAA,8BAAA;2CACtB,MAAoD;AAAA,sCAAjD9B,EAAA+B,EAAA,EAAYV,EAAE,aAAW,EAAA,SAAA,GAAA,CAAA,CAAA,GAAA,CAAA;AAAA,gCAAA;;;;;;;;;;;;;cAQxCf,EAKM,OALN0B,IAKM;AAAA,gBAJJjC,EAGWC,EAAAI,CAAA,GAAA;AAAA,kBAHD,MAAA;AAAA,kBAAK,MAAK;AAAA,kBAAS,SAAOf;AAAA,gBAAA;kBAEvB,QAAK,MAA0C;AAAA,oBAA1CU,EAA0CC,EAAAK,CAAA,GAAA;AAAA,sBAApC,MAAK;AAAA,sBAAkB,OAAO;AAAA,oBAAA;;6BADpD,MAA6B;AAAA,oBAA1BM,EAAAD,EAAAV,EAAA1C,CAAA,2BAA0B,KAC7B,CAAA;AAAA,kBAAA;;;;;;;;QAMRyC,EAsBUC,EAAAiC,EAAA,GAAA;AAAA,UArBA,MAAMxD,EAAA;AAAA,kDAAAA,EAAU,QAAAyB;AAAA,UACxB,QAAO;AAAA,UACN,SAAO0B,IAAAlD,EAAA,UAAA,gBAAAkD,EAAY,UAAS5B,EAAA1C,CAAA,EAAC,oBAAA;AAAA,UAC9B,OAAA,EAAA,OAAA,SAAA,aAAA,OAAA;AAAA,QAAA;qBAEA,MAAA;;AAA6C;AAAA,cAA7CyC,EAA6CmC,IAAA;AAAA,gBAA9B,QAAON,IAAAlD,EAAA,UAAA,gBAAAkD,EAAY;AAAA,cAAA;;;;;WAClBO,KAAAC,IAAA1D,EAAA,UAAA,gBAAA0D,EAAY,YAAZ,QAAAD,EAAqB;kBAAS;AAAA,kBAC5C,MAYU;AAAA,cAZVpC,EAYUC,EAAAqC,EAAA,GAAA,EAZD,SAAQ,SAAK;AAAA,2BAElB,MAAoC;AAAA,mBADtCvB,EAAA,EAAA,GAAAC,EAUWI,WATQzC,EAAA,MAAW,SAAO,CAA3B,GAAG4D,YADbT,EAUW7B,EAAAI,CAAA,GAAA;AAAA,oBARR,KAAKkC;AAAA,oBACN,MAAK;AAAA,oBACJ,MAAMtC,EAAApC,CAAA,EAAa,WAAW,CAAC;AAAA,oBAC/B,SAASoB,EAAA,UAAYsD;AAAA,oBACrB,UAAUtD,EAAA,UAAO,QAAaA,EAAA,UAAYsD;AAAA,oBAC1C,SAAK,CAAApC,MAAEjB,EAAS,GAAGqD,CAAC;AAAA,kBAAA;+BAErB,MAA2B;AAAA,0BAAxBtC,EAAApC,CAAA,EAAa,MAAM,CAAC,CAAA,GAAA,CAAA;AAAA,oBAAA;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"NoticeBell2.vue.js","sources":["../../src/layouts/NoticeBell.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 顶栏消息通知铃铛。富面板(n-popover):无页签单列表 + 正文弹层。\n// 未读=圆点+加粗+淡主色底,已读=整行变淡;\"读没读\"是过滤器不是分类,不做成页签(筛选在个人通知页)。\n// 未读数每 30s 轮询;面板展开时拉取列表。\n// 通知带动作列表时,正文弹层底部渲染成按钮(useNoticeAction):铃铛里直接\"办\",不必先跳到目标页面找按钮。\nimport { ref } from 'vue'\nimport { useRouter } from 'vue-router'\nimport {\n NPopover,\n NBadge,\n NButton,\n NTag,\n NEllipsis,\n NScrollbar,\n NSpace,\n NText,\n NEmpty,\n NModal,\n NSpin,\n useMessage,\n} from 'naive-ui'\nimport { Icon } from '@iconify/vue'\nimport { useIntervalFn } from '@vueuse/core'\nimport { useI18n } from 'vue-i18n'\nimport { noticeApi } from '#/api'\nimport { noticeBus } from '#/composables/useRealtime'\nimport { useNoticeAction } from '#/composables/useNoticeAction'\nimport { NoticeType, type NoticeAction, type NoticeMineItem } from '#/types/api'\nimport MarkdownView from '#/components/MarkdownEditor/MarkdownView.vue'\nimport { translateError } from '#/utils/error'\nimport { fmtDateTime } from '#/utils/format'\n\nconst { t } = useI18n()\nconst router = useRouter()\nconst message = useMessage()\nconst noticeAction = useNoticeAction()\n\nconst unread = ref(0)\nconst items = ref<NoticeMineItem[]>([])\nconst loading = ref(false)\nconst panelShow = ref(false)\n\nasync function fetchUnread() {\n try {\n unread.value = await noticeApi.unreadCount()\n } catch {\n /* 轮询失败静默,下次自愈 */\n }\n}\n// 无页签单列表:恒拉最近一页混排,未读靠条目样式强调;筛选能力在\"查看全部\"的个人通知页。\nasync function fetchList() {\n loading.value = true\n try {\n const { items: list } = await noticeApi.mine({ page: 1, pageSize: 10 })\n items.value = list\n } catch {\n items.value = []\n } finally {\n loading.value = false\n }\n}\nfunction onShow(show: boolean) {\n if (show) fetchList()\n}\n\n// 正文弹层:内容随列表取回,直接渲染(Markdown,不能纯文本直出);点条目即标记已读。\nconst showNotice = ref(false)\nconst viewNotice = ref<NoticeMineItem | null>(null)\nasync function markRead(item: NoticeMineItem) {\n if (item.isRead) return\n try {\n await noticeApi.markRead(item.id)\n item.isRead = true\n await fetchUnread()\n } catch (e) {\n message.error(translateError(e))\n }\n}\nasync function openNotice(item: NoticeMineItem) {\n viewNotice.value = item\n showNotice.value = true\n await markRead(item)\n}\n// 动作:成功后关掉弹层与面板(页面跳转时尤其要关,否则面板盖在新页面上)。\nconst running = ref<number | null>(null)\nasync function onAction(a: NoticeAction, index: number) {\n running.value = index\n try {\n if (await noticeAction.run(a)) {\n showNotice.value = false\n panelShow.value = false\n }\n } finally {\n running.value = null\n }\n}\nasync function markAllRead() {\n try {\n await noticeApi.markAllRead()\n await Promise.all([fetchUnread(), fetchList()])\n } catch (e) {\n message.error(translateError(e))\n }\n}\nfunction viewAll() {\n panelShow.value = false\n router.push('/personal/notice')\n}\n\n// 类型 → 标签色/文案。新增类型在此补一行即可。\nconst tagType = (ty: NoticeType) =>\n ty === NoticeType.Announcement ? 'warning' : ty === NoticeType.Message ? 'success' : 'info'\nconst typeLabel = (ty: NoticeType) =>\n ty === NoticeType.Announcement\n ? t('notice.typeAnnouncement')\n : ty === NoticeType.Message\n ? t('notice.typeMessage')\n : t('notice.typeNotice')\n\n// 正文摘要:去掉常见 Markdown 记号,压空白,截断。ponytail: 正则够用,要精准渲染再引 md 解析。\nfunction snippet(md?: string | null) {\n if (!md) return ''\n const s = md\n .replace(/!\\[[^\\]]*\\]\\([^)]*\\)/g, '') // 图片\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1') // 链接留文字\n .replace(/[#>*_`~-]/g, '') // 记号\n .replace(/\\s+/g, ' ')\n .trim()\n return s.length > 60 ? s.slice(0, 60) + '…' : s\n}\n\n// 双腿:实时推送到达即刷新未读(SignalR 开启时,即时);30s 轮询兜底(实时关闭/连接掉线时)。\n// VueUse 的 useEventBus.on 在组件卸载时自动退订。\nnoticeBus.on(() => {\n fetchUnread()\n})\nuseIntervalFn(fetchUnread, 30000)\nfetchUnread()\n</script>\n\n<template>\n <n-popover\n v-model:show=\"panelShow\"\n trigger=\"click\"\n placement=\"bottom-end\"\n :show-arrow=\"false\"\n style=\"padding: 0\"\n @update:show=\"onShow\"\n >\n <template #trigger>\n <!-- offset 的 y 把徽标整体下移:默认它以按钮右上角为中心、一半在按钮顶边之上,\n 顶栏面板 overflow:hidden,两三位数(99+)的胶囊顶边会被切平 -->\n <n-badge :value=\"unread\" :max=\"99\" :show=\"unread > 0\" :offset=\"[0, 5]\">\n <n-button quaternary circle :aria-label=\"t('app.notice.title')\">\n <Icon icon=\"ph:bell\" :width=\"19\" />\n </n-button>\n </n-badge>\n </template>\n\n <div class=\"notice-panel\">\n <div class=\"notice-head\">\n <span class=\"notice-title\">{{ t('app.notice.title') }}</span>\n <n-button text size=\"tiny\" :disabled=\"unread === 0\" @click=\"markAllRead\">\n <template #icon><Icon icon=\"ph:checks\" :width=\"15\" /></template>\n {{ t('app.notice.markAllRead') }}\n </n-button>\n </div>\n\n <n-spin :show=\"loading\">\n <n-scrollbar style=\"max-height: 360px\">\n <div v-if=\"items.length === 0\" class=\"notice-empty\">\n <n-empty :description=\"t('app.notice.empty')\" size=\"small\" />\n </div>\n <ul v-else class=\"notice-list\">\n <li\n v-for=\"n in items\"\n :key=\"n.id\"\n :class=\"['notice-item', n.isRead ? 'read' : 'unread']\"\n @click=\"openNotice(n)\"\n >\n <span v-if=\"!n.isRead\" class=\"dot\" />\n <div class=\"notice-body\">\n <div class=\"line1\">\n <n-tag :type=\"tagType(n.type)\" size=\"tiny\" :bordered=\"false\" round>\n {{ typeLabel(n.type) }}\n </n-tag>\n <n-ellipsis class=\"item-title\" :class=\"{ unread: !n.isRead }\">\n {{ n.title }}\n </n-ellipsis>\n <Icon\n v-if=\"n.actions?.length\"\n icon=\"ph:lightning\"\n :width=\"13\"\n class=\"item-actions-mark\"\n />\n </div>\n <n-text v-if=\"snippet(n.content)\" depth=\"3\" class=\"item-snippet\">\n {{ snippet(n.content) }}\n </n-text>\n <n-text depth=\"3\" class=\"item-time\">\n {{ fmtDateTime(n.publishTime, { seconds: false }) }}\n </n-text>\n </div>\n </li>\n </ul>\n </n-scrollbar>\n </n-spin>\n\n <div class=\"notice-foot\">\n <n-button text size=\"small\" @click=\"viewAll\">\n {{ t('app.notice.viewAll') }}\n <template #icon><Icon icon=\"ph:arrow-right\" :width=\"14\" /></template>\n </n-button>\n </div>\n </div>\n </n-popover>\n\n <n-modal\n v-model:show=\"showNotice\"\n preset=\"card\"\n :title=\"viewNotice?.title || t('notice.detailTitle')\"\n style=\"width: 640px; max-width: 92vw\"\n >\n <MarkdownView :value=\"viewNotice?.content\" />\n <template v-if=\"viewNotice?.actions?.length\" #footer>\n <n-space justify=\"end\">\n <n-button\n v-for=\"(a, i) in viewNotice.actions\"\n :key=\"i\"\n size=\"small\"\n :type=\"noticeAction.buttonType(a)\"\n :loading=\"running === i\"\n :disabled=\"running !== null && running !== i\"\n @click=\"onAction(a, i)\"\n >\n {{ noticeAction.label(a) }}\n </n-button>\n </n-space>\n </template>\n </n-modal>\n</template>\n\n<style scoped>\n.notice-panel {\n width: 360px;\n max-width: 92vw;\n}\n.notice-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px 4px;\n}\n.notice-title {\n font-weight: 600;\n font-size: 15px;\n}\n.notice-empty {\n padding: 32px 0;\n}\n.notice-list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.notice-item {\n position: relative;\n display: flex;\n gap: 8px;\n padding: 12px 16px;\n cursor: pointer;\n border-top: 1px solid var(--separator);\n transition: background-color var(--transition-fast);\n}\n.notice-item:hover {\n background-color: var(--hover);\n}\n/* 未读:整行淡主色底(明暗主题都成立);已读:整行变淡 */\n.notice-item.unread {\n background-color: color-mix(in srgb, var(--signal) 5%, transparent);\n}\n.notice-item.unread:hover {\n background-color: color-mix(in srgb, var(--signal) 10%, transparent);\n}\n.notice-item.read {\n opacity: 0.55;\n}\n.dot {\n position: absolute;\n left: 6px;\n top: 18px;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--signal);\n}\n.notice-body {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.line1 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.item-title {\n flex: 1;\n min-width: 0;\n font-size: 14px;\n}\n.item-title.unread {\n font-weight: 600;\n}\n.item-actions-mark {\n flex-shrink: 0;\n color: var(--signal);\n}\n.item-snippet {\n font-size: 13px;\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.item-time {\n font-size: 12.5px;\n}\n.notice-foot {\n display: flex;\n justify-content: center;\n padding: 8px;\n border-top: 1px solid var(--separator);\n}\n</style>\n"],"names":["t","useI18n","router","useRouter","message","useMessage","noticeAction","useNoticeAction","unread","ref","items","loading","panelShow","fetchUnread","noticeApi","fetchList","list","onShow","show","showNotice","viewNotice","markRead","item","e","translateError","openNotice","running","onAction","a","index","markAllRead","viewAll","tagType","ty","NoticeType","typeLabel","snippet","md","s","noticeBus","useIntervalFn","_createVNode","_unref","NPopover","$event","NBadge","NButton","Icon","_createElementVNode","_hoisted_1","_hoisted_2","_hoisted_3","_toDisplayString","_createTextVNode","NSpin","NScrollbar","_openBlock","_createElementBlock","_hoisted_4","NEmpty","_hoisted_5","_Fragment","_renderList","n","_normalizeClass","_hoisted_7","_hoisted_8","_hoisted_9","NTag","NEllipsis","_a","_createBlock","NText","fmtDateTime","_hoisted_10","NModal","MarkdownView","_c","_b","NSpace","i"],"mappings":"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"}
@@ -2,7 +2,7 @@ const e = {
2
2
  /** API 根地址;空串 = 同源(dev 由 Vite 代理,生产由反代或后端托管 dist) */
3
3
  apiBase: "",
4
4
  /** 登录页页脚展示的版本号;默认内核版本,消费方可覆盖为应用版本 */
5
- version: "10.20.0",
5
+ version: "10.21.0",
6
6
  /** 开发态:登录页预填超管账号、设置抽屉显示「复制配置」 */
7
7
  dev: !1,
8
8
  brand: {}
@@ -57,7 +57,7 @@ export declare function normalizeWatermarkFields(raw: unknown): WatermarkField[]
57
57
  /** 站点信息里的 watermark 节点(或任何半成品)→ 合法设置。 */
58
58
  export declare function normalizeWatermark(raw?: Partial<Record<keyof WatermarkSettings, unknown>> | null): {
59
59
  enabled: boolean;
60
- fields: ("name" | "account" | "phone" | "org" | "time" | "text")[];
60
+ fields: ("name" | "org" | "account" | "phone" | "time" | "text")[];
61
61
  text: string;
62
62
  timeFormat: "YYYY-MM-DD" | "YYYY-MM-DD HH:mm" | "YYYY-MM-DD HH:mm:ss" | "MM-DD HH:mm";
63
63
  layout: WatermarkLayout;
@@ -61,11 +61,19 @@ declare const _default: {
61
61
  };
62
62
  userPicker: {
63
63
  title: string;
64
+ org: string;
64
65
  available: string;
65
66
  selected: string;
66
- add: string;
67
- added: string;
68
- addChecked: string;
67
+ searchName: string;
68
+ searchAccount: string;
69
+ selectPage: string;
70
+ unselectPage: string;
71
+ total: string;
72
+ existing: string;
73
+ remove: string;
74
+ emptySelected: string;
75
+ prevPage: string;
76
+ nextPage: string;
69
77
  clear: string;
70
78
  };
71
79
  app: {
@@ -241,6 +249,9 @@ declare const _default: {
241
249
  deleteConfirm: string;
242
250
  disableConfirm: string;
243
251
  builtinNoDisable: string;
252
+ delegatable: string;
253
+ delegatableHint: string;
254
+ builtinNotDelegatable: string;
244
255
  saved: string;
245
256
  deleted: string;
246
257
  };
@@ -355,6 +366,7 @@ declare const _default: {
355
366
  userToggle: string;
356
367
  userImport: string;
357
368
  userExport: string;
369
+ userGrantMenus: string;
358
370
  roleQuery: string;
359
371
  roleCreate: string;
360
372
  roleUpdate: string;
@@ -551,6 +563,47 @@ declare const _default: {
551
563
  totpInviteToken: string;
552
564
  totpInviteExpires: string;
553
565
  };
566
+ userGrant: {
567
+ action: string;
568
+ title: string;
569
+ tabGrant: string;
570
+ tabEffective: string;
571
+ hint: string;
572
+ noRoles: string;
573
+ readonly: string;
574
+ readonlyNoReason: string;
575
+ follow: string;
576
+ allow: string;
577
+ deny: string;
578
+ effective: string;
579
+ ineffective: string;
580
+ expired: string;
581
+ deniedByAncestor: string;
582
+ notGrantable: string;
583
+ expireLongTerm: string;
584
+ remark: string;
585
+ leaked: string;
586
+ leakedFrom: string;
587
+ summary: string;
588
+ unsaved: string;
589
+ invalidExpiry: string;
590
+ saved: string;
591
+ overview: string;
592
+ colUser: string;
593
+ colMenu: string;
594
+ colState: string;
595
+ colSource: string;
596
+ colEffect: string;
597
+ colStatus: string;
598
+ colExpire: string;
599
+ colGrantor: string;
600
+ colGrantTime: string;
601
+ colRemark: string;
602
+ adjust: string;
603
+ statusActive: string;
604
+ statusExpiring: string;
605
+ statusExpired: string;
606
+ };
554
607
  mfa: {
555
608
  bindTitle: string;
556
609
  recoveryTitle: string;
@@ -1247,6 +1300,7 @@ declare const _default: {
1247
1300
  grantSummary: string;
1248
1301
  grantSummarySplit: string;
1249
1302
  grantUnsaved: string;
1303
+ systemMenusBuiltinOnly: string;
1250
1304
  dataScope: string;
1251
1305
  scopeType: string;
1252
1306
  customOrgs: string;
@@ -1747,6 +1801,9 @@ declare const _default: {
1747
1801
  denied: string;
1748
1802
  demoReadOnly: string;
1749
1803
  superAdminRequired: string;
1804
+ menuNotGrantable: string;
1805
+ targetIsDelegatedAdmin: string;
1806
+ delegatedGrantExpiryInvalid: string;
1750
1807
  };
1751
1808
  user: {
1752
1809
  notFound: string;
@@ -1757,11 +1814,13 @@ declare const _default: {
1757
1814
  outOfDataScope: string;
1758
1815
  avatarUrlInvalid: string;
1759
1816
  cannotOperateSelf: string;
1817
+ menuGrantInvalid: string;
1760
1818
  };
1761
1819
  role: {
1762
1820
  notFound: string;
1763
1821
  codeExists: string;
1764
1822
  notDelegatable: string;
1823
+ systemMenuNotAssignable: string;
1765
1824
  };
1766
1825
  org: {
1767
1826
  notFound: string;
@@ -63,11 +63,19 @@ const e = {
63
63
  },
64
64
  userPicker: {
65
65
  title: "Select users",
66
+ org: "Organizations",
66
67
  available: "Available",
67
68
  selected: "Selected",
68
- add: "Add",
69
- added: "Added",
70
- addChecked: "Add checked",
69
+ searchName: "Search by name",
70
+ searchAccount: "Search by account",
71
+ selectPage: "Select page",
72
+ unselectPage: "Deselect page",
73
+ total: "{n} users",
74
+ existing: "Existing",
75
+ remove: "Remove",
76
+ emptySelected: "No users selected",
77
+ prevPage: "Previous page",
78
+ nextPage: "Next page",
71
79
  clear: "Clear"
72
80
  },
73
81
  app: {
@@ -243,6 +251,9 @@ const e = {
243
251
  deleteConfirm: 'Delete app "{title}"?',
244
252
  disableConfirm: 'Disable app "{title}"? It will disappear from the portal',
245
253
  builtinNoDisable: "The built-in app cannot be disabled",
254
+ delegatable: "Delegatable",
255
+ delegatableHint: "When on, administrators can grant or deny this app's menus to individual users (time-limited)",
256
+ builtinNotDelegatable: "The built-in app is never delegatable",
246
257
  saved: "Saved",
247
258
  deleted: "Deleted"
248
259
  },
@@ -312,7 +323,7 @@ const e = {
312
323
  ops: "System Operations",
313
324
  schedule: "Job Scheduling",
314
325
  audit: "Audit Logs",
315
- ai: "AI Management"
326
+ ai: "AI Gateway"
316
327
  },
317
328
  page: {
318
329
  workbench: "Workbench",
@@ -358,6 +369,7 @@ const e = {
358
369
  userToggle: "User - Enable / Disable",
359
370
  userImport: "User - Import",
360
371
  userExport: "User - Export",
372
+ userGrantMenus: "User - Grant Menus",
361
373
  roleQuery: "Role - Query",
362
374
  roleCreate: "Role - Create",
363
375
  roleUpdate: "Role - Update",
@@ -560,6 +572,47 @@ const e = {
560
572
  totpInviteToken: "Invite token",
561
573
  totpInviteExpires: "Expires at"
562
574
  },
575
+ userGrant: {
576
+ action: "Grant menus",
577
+ title: "Grant menus · {name}",
578
+ tabGrant: "Individual grants",
579
+ tabEffective: "Effective access",
580
+ hint: "Grant through roles as usual and keep only exceptions here. Allow covers that one node; Deny also revokes everything below it and beats any allow.",
581
+ noRoles: 'This user has no role, so the data scope is "self only": granted pages open but show only data the user created.',
582
+ readonly: "View only: {reason}",
583
+ readonlyNoReason: "View only",
584
+ follow: "Follow roles",
585
+ allow: "Allow",
586
+ deny: "Deny",
587
+ effective: "Effective",
588
+ ineffective: "Not effective",
589
+ expired: "Expired",
590
+ deniedByAncestor: "Denied by parent",
591
+ notGrantable: "Only a super admin can grant menus in this module",
592
+ expireLongTerm: "No expiry",
593
+ remark: "Reason (optional)",
594
+ leaked: "These APIs stay callable:",
595
+ leakedFrom: "{code} (from {from})",
596
+ summary: "Allow {allow} · Deny {deny}",
597
+ unsaved: "Unsaved changes",
598
+ invalidExpiry: "{count} record(s) have a missing or out-of-range expiry date",
599
+ saved: "Grants saved",
600
+ overview: "Individual grants",
601
+ colUser: "User",
602
+ colMenu: "Menu",
603
+ colState: "State",
604
+ colSource: "Source",
605
+ colEffect: "Effect",
606
+ colStatus: "Validity",
607
+ colExpire: "Expires",
608
+ colGrantor: "Granted by",
609
+ colGrantTime: "Granted at",
610
+ colRemark: "Reason",
611
+ adjust: "Adjust",
612
+ statusActive: "Active",
613
+ statusExpiring: "Expiring in 7 days",
614
+ statusExpired: "Expired"
615
+ },
563
616
  mfa: {
564
617
  bindTitle: "Set up authenticator",
565
618
  recoveryTitle: "Use recovery code",
@@ -1250,6 +1303,7 @@ const e = {
1250
1303
  grantSummary: "Granted {on} / {total}",
1251
1304
  grantSummarySplit: "{pages} pages · {buttons} buttons",
1252
1305
  grantUnsaved: "Unsaved changes",
1306
+ systemMenusBuiltinOnly: "Apart from User Management and Role Management, menus that ship with the system can only be granted to built-in roles and are hidden here",
1253
1307
  dataScope: "Data scope",
1254
1308
  scopeType: "Scope type",
1255
1309
  customOrgs: "Custom orgs",
@@ -1747,7 +1801,10 @@ const e = {
1747
1801
  perm: {
1748
1802
  denied: "Access denied",
1749
1803
  demoReadOnly: "Demo environment is read-only",
1750
- superAdminRequired: "This action requires super admin privileges"
1804
+ superAdminRequired: "This action requires super admin privileges",
1805
+ menuNotGrantable: "This menu's module does not allow delegated grants",
1806
+ targetIsDelegatedAdmin: "The user is an administrator; only a super admin can grant them menus individually",
1807
+ delegatedGrantExpiryInvalid: "An allow grant needs an expiry date at most {maxDays} days away"
1751
1808
  },
1752
1809
  user: {
1753
1810
  notFound: "User not found",
@@ -1757,12 +1814,14 @@ const e = {
1757
1814
  passwordReused: "New password must differ from your current and recently used passwords",
1758
1815
  outOfDataScope: "Target user is outside your data scope",
1759
1816
  avatarUrlInvalid: "Avatar URL is not allowed",
1760
- cannotOperateSelf: "You cannot perform this action on yourself"
1817
+ cannotOperateSelf: "You cannot perform this action on yourself",
1818
+ menuGrantInvalid: "Invalid grant changes: each menu at most once, and the expiry must be in the future"
1761
1819
  },
1762
1820
  role: {
1763
1821
  notFound: "Role not found",
1764
1822
  codeExists: "Role code already exists",
1765
- notDelegatable: "Role is not delegatable"
1823
+ notDelegatable: "Role is not delegatable",
1824
+ systemMenuNotAssignable: "Apart from User Management and Role Management, menus that ship with the system can only be granted to built-in roles"
1766
1825
  },
1767
1826
  org: {
1768
1827
  notFound: "Org not found",