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
@@ -0,0 +1,171 @@
1
+ import { defineComponent as A, computed as k, openBlock as n, createElementBlock as o, normalizeClass as B, createElementVNode as u, toDisplayString as a, createCommentVNode as f, Fragment as U, renderList as V, createBlock as h, unref as e, withCtx as d, createTextVNode as c, createVNode as i } from "vue";
2
+ import { NTag as G, NTooltip as F, NRadioGroup as I, NRadioButton as D, NDatePicker as L, NInput as $ } from "naive-ui";
3
+ import { useI18n as P } from "vue-i18n";
4
+ import S from "../../../../components/AppIcon.vue.js";
5
+ import { UserMenuEffect as j } from "../../../../types/api.js";
6
+ import { formatDate as C, isExpiryDateDisabled as q } from "./userGrantState.js";
7
+ const H = { class: "ugr-main" }, J = { class: "ugr-name" }, K = { class: "ugr-title" }, O = {
8
+ key: 0,
9
+ class: "ugr-path"
10
+ }, Q = { class: "ugr-src" }, W = {
11
+ key: 0,
12
+ class: "faint"
13
+ }, X = { class: "ugr-tri" }, Y = {
14
+ key: 0,
15
+ class: "ugr-edit"
16
+ }, Z = {
17
+ key: 1,
18
+ class: "ugr-leak"
19
+ }, ne = /* @__PURE__ */ A({
20
+ __name: "UserGrantNodeRow",
21
+ props: {
22
+ title: {},
23
+ path: {},
24
+ level: {},
25
+ node: {},
26
+ state: {},
27
+ entry: {},
28
+ editable: { type: Boolean },
29
+ notGrantable: { type: Boolean },
30
+ maxDate: {},
31
+ leaked: {}
32
+ },
33
+ emits: ["update:state", "update:expireDate", "update:remark"],
34
+ setup(t, { emit: E }) {
35
+ const v = t, y = E, { t: l } = P(), b = k(() => {
36
+ var s;
37
+ return ((s = v.node) == null ? void 0 : s.roles) ?? [];
38
+ }), g = k(() => b.value.slice(0, 2)), R = C(/* @__PURE__ */ new Date()), T = (s) => q(C(new Date(s)), R, v.maxDate), M = k(
39
+ () => {
40
+ var s;
41
+ return v.maxDate == null || ((s = v.entry) == null ? void 0 : s.effect) === j.Deny;
42
+ }
43
+ );
44
+ return (s, m) => {
45
+ var p, x, w, N, z;
46
+ return n(), o("div", {
47
+ class: B(["ugr", [`is-${t.level}`, { "is-off": !((p = t.node) != null && p.effective) }]])
48
+ }, [
49
+ u("div", H, [
50
+ u("div", J, [
51
+ u("span", K, a(t.title), 1),
52
+ t.path ? (n(), o("span", O, a(t.path), 1)) : f("", !0)
53
+ ]),
54
+ u("div", Q, [
55
+ (n(!0), o(U, null, V(g.value, (r) => (n(), h(e(G), {
56
+ key: r,
57
+ size: "small",
58
+ bordered: !1
59
+ }, {
60
+ default: d(() => [
61
+ c(a(r), 1)
62
+ ]),
63
+ _: 2
64
+ }, 1024))), 128)),
65
+ b.value.length > g.value.length ? (n(), o("span", W, " +" + a(b.value.length - g.value.length), 1)) : f("", !0),
66
+ (x = t.node) != null && x.expired ? (n(), h(e(G), {
67
+ key: 1,
68
+ size: "small",
69
+ type: "warning",
70
+ bordered: !1
71
+ }, {
72
+ default: d(() => [
73
+ c(a(e(l)("userGrant.expired")), 1)
74
+ ]),
75
+ _: 1
76
+ })) : f("", !0),
77
+ (w = t.node) != null && w.deniedByAncestor ? (n(), h(e(G), {
78
+ key: 2,
79
+ size: "small",
80
+ type: "error",
81
+ bordered: !1
82
+ }, {
83
+ default: d(() => [
84
+ c(a(e(l)("userGrant.deniedByAncestor")), 1)
85
+ ]),
86
+ _: 1
87
+ })) : f("", !0),
88
+ u("span", {
89
+ class: B(["ugr-dot", { on: (N = t.node) == null ? void 0 : N.effective }])
90
+ }, a((z = t.node) != null && z.effective ? e(l)("userGrant.effective") : e(l)("userGrant.ineffective")), 3)
91
+ ]),
92
+ i(e(F), {
93
+ disabled: !t.notGrantable
94
+ }, {
95
+ trigger: d(() => [
96
+ u("span", X, [
97
+ i(e(I), {
98
+ value: t.state,
99
+ size: "small",
100
+ disabled: !t.editable,
101
+ "onUpdate:value": m[0] || (m[0] = (r) => y("update:state", r))
102
+ }, {
103
+ default: d(() => [
104
+ i(e(D), { value: "follow" }, {
105
+ default: d(() => [
106
+ c(a(e(l)("userGrant.follow")), 1)
107
+ ]),
108
+ _: 1
109
+ }),
110
+ i(e(D), { value: "allow" }, {
111
+ default: d(() => [
112
+ c(a(e(l)("userGrant.allow")), 1)
113
+ ]),
114
+ _: 1
115
+ }),
116
+ i(e(D), { value: "deny" }, {
117
+ default: d(() => [
118
+ c(a(e(l)("userGrant.deny")), 1)
119
+ ]),
120
+ _: 1
121
+ })
122
+ ]),
123
+ _: 1
124
+ }, 8, ["value", "disabled"])
125
+ ])
126
+ ]),
127
+ default: d(() => [
128
+ c(" " + a(e(l)("userGrant.notGrantable")), 1)
129
+ ]),
130
+ _: 1
131
+ }, 8, ["disabled"])
132
+ ]),
133
+ t.entry ? (n(), o("div", Y, [
134
+ i(e(L), {
135
+ type: "date",
136
+ size: "small",
137
+ "value-format": "yyyy-MM-dd",
138
+ "formatted-value": t.entry.expireDate,
139
+ clearable: M.value,
140
+ "is-date-disabled": T,
141
+ disabled: !t.editable,
142
+ placeholder: e(l)("userGrant.expireLongTerm"),
143
+ "onUpdate:formattedValue": m[1] || (m[1] = (r) => y("update:expireDate", r))
144
+ }, null, 8, ["formatted-value", "clearable", "disabled", "placeholder"]),
145
+ i(e($), {
146
+ size: "small",
147
+ value: t.entry.remark ?? "",
148
+ maxlength: 200,
149
+ disabled: !t.editable,
150
+ placeholder: e(l)("userGrant.remark"),
151
+ "onUpdate:value": m[2] || (m[2] = (r) => y("update:remark", r))
152
+ }, null, 8, ["value", "disabled", "placeholder"])
153
+ ])) : f("", !0),
154
+ t.state === "deny" && t.leaked.length ? (n(), o("div", Z, [
155
+ i(S, {
156
+ icon: "ph:warning",
157
+ size: 14
158
+ }),
159
+ u("span", null, a(e(l)("userGrant.leaked")), 1),
160
+ (n(!0), o(U, null, V(t.leaked, (r) => (n(), o("code", {
161
+ key: r.code
162
+ }, a(e(l)("userGrant.leakedFrom", { code: r.code, from: r.from })), 1))), 128))
163
+ ])) : f("", !0)
164
+ ], 2);
165
+ };
166
+ }
167
+ });
168
+ export {
169
+ ne as default
170
+ };
171
+ //# sourceMappingURL=UserGrantNodeRow2.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserGrantNodeRow2.vue.js","sources":["../../../../../src/views/system/user/components/UserGrantNodeRow.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 用户授权弹窗里一个节点一行:名称 / 来源与是否有效 / 三态;选了允许或拒绝再展开到期日与理由。\n// 选「拒绝」时,这个节点的接口若还由别的有效节点携带,就地列出来:按节点拒绝不收回共用的接口。\nimport { computed } from 'vue'\nimport { NDatePicker, NInput, NRadioButton, NRadioGroup, NTag, NTooltip } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\nimport { UserMenuEffect, type UserMenuEffectiveNode } from '#/types/api'\nimport { formatDate, isExpiryDateDisabled, type DraftEntry, type TriState } from './userGrantState'\n\nconst props = defineProps<{\n title: string\n path?: string\n level: 'catalog' | 'page' | 'button'\n node?: UserMenuEffectiveNode\n state: TriState\n entry?: DraftEntry\n /** 目标用户可编辑且这个节点所属模块可授 */\n editable: boolean\n /** 只因模块不可转授而不能改(悬浮说明) */\n notGrantable: boolean\n /** 到期日上限 yyyy-MM-dd;null = 不限 */\n maxDate: string | null\n leaked: { code: string; from: string }[]\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:state', v: TriState): void\n (e: 'update:expireDate', v: string | null): void\n (e: 'update:remark', v: string): void\n}>()\n\nconst { t } = useI18n()\n/** 角色标签最多露两个,其余折成 +N。 */\nconst roles = computed(() => props.node?.roles ?? [])\nconst shownRoles = computed(() => roles.value.slice(0, 2))\nconst today = formatDate(new Date())\n/** 今天以前、上限以后的日期不可选。 */\nconst isDateDisabled = (ts: number) =>\n isExpiryDateDisabled(formatDate(new Date(ts)), today, props.maxDate)\n/** 受限时「允许」的到期日必填(不给清空);拒绝与不受限时可留空 = 长期。 */\nconst clearable = computed(\n () => props.maxDate == null || props.entry?.effect === UserMenuEffect.Deny,\n)\n</script>\n\n<template>\n <div class=\"ugr\" :class=\"[`is-${level}`, { 'is-off': !node?.effective }]\">\n <div class=\"ugr-main\">\n <div class=\"ugr-name\">\n <span class=\"ugr-title\">{{ title }}</span>\n <span v-if=\"path\" class=\"ugr-path\">{{ path }}</span>\n </div>\n <div class=\"ugr-src\">\n <n-tag v-for=\"r in shownRoles\" :key=\"r\" size=\"small\" :bordered=\"false\">{{ r }}</n-tag>\n <span v-if=\"roles.length > shownRoles.length\" class=\"faint\">\n +{{ roles.length - shownRoles.length }}\n </span>\n <n-tag v-if=\"node?.expired\" size=\"small\" type=\"warning\" :bordered=\"false\">\n {{ t('userGrant.expired') }}\n </n-tag>\n <n-tag v-if=\"node?.deniedByAncestor\" size=\"small\" type=\"error\" :bordered=\"false\">\n {{ t('userGrant.deniedByAncestor') }}\n </n-tag>\n <span class=\"ugr-dot\" :class=\"{ on: node?.effective }\">\n {{ node?.effective ? t('userGrant.effective') : t('userGrant.ineffective') }}\n </span>\n </div>\n <n-tooltip :disabled=\"!notGrantable\">\n <template #trigger>\n <!-- 禁用的单选组收不到鼠标事件,包一层让悬浮说明能出来 -->\n <span class=\"ugr-tri\">\n <n-radio-group\n :value=\"state\"\n size=\"small\"\n :disabled=\"!editable\"\n @update:value=\"(v: TriState) => emit('update:state', v)\"\n >\n <n-radio-button value=\"follow\">{{ t('userGrant.follow') }}</n-radio-button>\n <n-radio-button value=\"allow\">{{ t('userGrant.allow') }}</n-radio-button>\n <n-radio-button value=\"deny\">{{ t('userGrant.deny') }}</n-radio-button>\n </n-radio-group>\n </span>\n </template>\n {{ t('userGrant.notGrantable') }}\n </n-tooltip>\n </div>\n\n <div v-if=\"entry\" class=\"ugr-edit\">\n <n-date-picker\n type=\"date\"\n size=\"small\"\n value-format=\"yyyy-MM-dd\"\n :formatted-value=\"entry.expireDate\"\n :clearable=\"clearable\"\n :is-date-disabled=\"isDateDisabled\"\n :disabled=\"!editable\"\n :placeholder=\"t('userGrant.expireLongTerm')\"\n @update:formatted-value=\"(v: string | null) => emit('update:expireDate', v)\"\n />\n <n-input\n size=\"small\"\n :value=\"entry.remark ?? ''\"\n :maxlength=\"200\"\n :disabled=\"!editable\"\n :placeholder=\"t('userGrant.remark')\"\n @update:value=\"(v: string) => emit('update:remark', v)\"\n />\n </div>\n\n <div v-if=\"state === 'deny' && leaked.length\" class=\"ugr-leak\">\n <AppIcon icon=\"ph:warning\" :size=\"14\" />\n <span>{{ t('userGrant.leaked') }}</span>\n <code v-for=\"l in leaked\" :key=\"l.code\">\n {{ t('userGrant.leakedFrom', { code: l.code, from: l.from }) }}\n </code>\n </div>\n </div>\n</template>\n\n<style scoped>\n.ugr {\n padding: 8px 16px;\n border-top: 1px solid var(--hairline);\n}\n.ugr.is-button {\n padding-left: 40px;\n}\n.ugr.is-catalog {\n flex: 1;\n min-width: 0;\n padding: 0;\n border-top: 0;\n}\n.ugr-main {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;\n gap: 6px 16px;\n align-items: center;\n}\n.ugr-name {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.ugr-title {\n overflow-wrap: anywhere;\n}\n.ugr.is-catalog .ugr-title {\n font-size: 15px;\n font-weight: 600;\n}\n.ugr.is-off .ugr-title {\n color: var(--text-2);\n}\n.ugr-path {\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text-3);\n}\n.ugr-src {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n font-size: 13px;\n}\n.ugr-dot {\n color: var(--text-3);\n}\n.ugr-dot.on {\n color: var(--sel-fg);\n}\n.ugr-tri {\n display: inline-flex;\n}\n.ugr-edit {\n display: grid;\n grid-template-columns: 170px minmax(0, 1fr);\n gap: 8px;\n margin-top: 8px;\n}\n.ugr-leak {\n display: flex;\n flex-wrap: wrap;\n gap: 4px 8px;\n align-items: center;\n margin-top: 6px;\n font-size: 12px;\n color: var(--warn);\n}\n.ugr-leak code {\n font-family: var(--font-mono);\n}\n@media (max-width: 640px) {\n .ugr-main,\n .ugr-edit {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n</style>\n"],"names":["props","__props","emit","__emit","t","useI18n","roles","computed","_a","shownRoles","today","formatDate","isDateDisabled","ts","isExpiryDateDisabled","clearable","UserMenuEffect","_createElementBlock","_createElementVNode","_hoisted_1","_hoisted_2","_hoisted_3","_toDisplayString","_hoisted_4","_hoisted_5","_Fragment","_renderList","_createBlock","_unref","NTag","_hoisted_6","_b","_c","_normalizeClass","_d","_e","_createVNode","NTooltip","_hoisted_7","NRadioGroup","_cache","v","NRadioButton","_createTextVNode","_openBlock","_hoisted_8","NDatePicker","NInput","_hoisted_9","AppIcon","l"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ type __VLS_ModelProps = {
2
+ 'show'?: boolean;
3
+ };
4
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_ModelProps, {
5
+ refresh: () => Promise<void> | undefined;
6
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
7
+ "update:show": (value: boolean) => any;
8
+ } & {
9
+ adjust: (user: {
10
+ id: number;
11
+ name: string;
12
+ }) => any;
13
+ }, string, import('vue').PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
14
+ onAdjust?: ((user: {
15
+ id: number;
16
+ name: string;
17
+ }) => any) | undefined;
18
+ "onUpdate:show"?: ((value: boolean) => any) | undefined;
19
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
20
+ declare const _default: typeof __VLS_export;
21
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import f from "./UserGrantOverviewDrawer2.vue.js";
2
+ export {
3
+ f as default
4
+ };
5
+ //# sourceMappingURL=UserGrantOverviewDrawer.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserGrantOverviewDrawer.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,178 @@
1
+ import { defineComponent as N, useModel as _, ref as M, computed as w, h as l, openBlock as B, createBlock as I, unref as r, withCtx as u, createVNode as c, mergeModels as S } from "vue";
2
+ import { useMessage as z, NTag as y, NButton as O, NDrawer as U, NDrawerContent as C } from "naive-ui";
3
+ import { useI18n as R } from "vue-i18n";
4
+ import { SmartTable as D } from "smart-naive-table";
5
+ import L from "../../../../components/TableTotal/index.vue.js";
6
+ import { userApi as j } from "../../../../api/index.js";
7
+ import { translateMenuTitle as G } from "../../../../locales/menuTitle.js";
8
+ import { translateError as H } from "../../../../utils/error.js";
9
+ import { SEARCH_ACTIONS as i, deriveHeaderFilters as $ } from "../../../../utils/tableFilter.js";
10
+ import { TABLE_MIN_ROW_HEIGHT as q, TABLE_TOOLBAR as F } from "../../../../utils/tableToolbar.js";
11
+ import { UserMenuEffect as n, UserMenuGrantStatus as s } from "../../../../types/api.js";
12
+ const re = /* @__PURE__ */ N({
13
+ __name: "UserGrantOverviewDrawer",
14
+ props: {
15
+ show: { type: Boolean, default: !1 },
16
+ showModifiers: {}
17
+ },
18
+ emits: /* @__PURE__ */ S(["adjust"], ["update:show"]),
19
+ setup(T, { expose: g, emit: x }) {
20
+ const m = _(T, "show"), b = x, { t } = R(), k = z(), p = M();
21
+ g({ refresh: () => {
22
+ var e;
23
+ return (e = p.value) == null ? void 0 : e.refresh();
24
+ } });
25
+ const v = w(() => [
26
+ { label: t("userGrant.allow"), value: n.Allow },
27
+ { label: t("userGrant.deny"), value: n.Deny }
28
+ ]), d = w(() => [
29
+ { label: t("userGrant.statusActive"), value: s.Active },
30
+ { label: t("userGrant.statusExpiring"), value: s.Expiring },
31
+ { label: t("userGrant.statusExpired"), value: s.Expired }
32
+ ]), A = {
33
+ [s.Active]: "success",
34
+ [s.Expiring]: "warning",
35
+ [s.Expired]: "default"
36
+ }, E = (e) => {
37
+ var a;
38
+ return ((a = d.value.find((o) => o.value === e)) == null ? void 0 : a.label) ?? "";
39
+ }, f = () => l("span", { class: "faint" }, "—"), h = [
40
+ { type: "index", title: () => t("common.rowNo"), width: 64, align: "center" },
41
+ // 只作搜索项:目标用户账号或姓名
42
+ {
43
+ key: "user",
44
+ title: () => t("userGrant.colUser"),
45
+ hideInTable: !0,
46
+ search: { actions: i.fuzzy }
47
+ },
48
+ {
49
+ key: "userName",
50
+ title: () => t("userGrant.colUser"),
51
+ width: 140,
52
+ ellipsis: { tooltip: !0 },
53
+ card: "title",
54
+ render: (e) => `${e.userName}(${e.userAccount})`
55
+ },
56
+ {
57
+ key: "menuTitle",
58
+ title: () => t("userGrant.colMenu"),
59
+ width: 140,
60
+ ellipsis: { tooltip: !0 },
61
+ render: (e) => [e.moduleTitle ? G(e.moduleTitle) : null, G(e.menuTitle)].filter(Boolean).join(" · ")
62
+ },
63
+ {
64
+ key: "effect",
65
+ title: () => t("userGrant.colEffect"),
66
+ width: 102,
67
+ options: v,
68
+ search: { actions: i.exact, props: { clearable: !0 } },
69
+ render: (e) => l(
70
+ y,
71
+ {
72
+ size: "small",
73
+ bordered: !1,
74
+ type: e.effect === n.Allow ? "success" : "error"
75
+ },
76
+ () => t(e.effect === n.Allow ? "userGrant.allow" : "userGrant.deny")
77
+ )
78
+ },
79
+ {
80
+ key: "status",
81
+ title: () => t("userGrant.colStatus"),
82
+ width: 108,
83
+ options: d,
84
+ search: { actions: i.exact, props: { clearable: !0 } },
85
+ render: (e) => l(
86
+ y,
87
+ { size: "small", bordered: !1, type: A[e.status] },
88
+ () => E(e.status)
89
+ )
90
+ },
91
+ {
92
+ key: "expireTime",
93
+ title: () => t("userGrant.colExpire"),
94
+ width: 160,
95
+ render: (e) => e.expireTime ? e.expireTime.replace("T", " ") : t("userGrant.expireLongTerm")
96
+ },
97
+ // 只作搜索项:授权人账号或姓名
98
+ {
99
+ key: "grantor",
100
+ title: () => t("userGrant.colGrantor"),
101
+ hideInTable: !0,
102
+ search: { actions: i.fuzzy }
103
+ },
104
+ {
105
+ key: "grantorName",
106
+ title: () => t("userGrant.colGrantor"),
107
+ width: 100,
108
+ ellipsis: { tooltip: !0 },
109
+ render: (e) => e.grantorName || f()
110
+ },
111
+ { key: "grantTime", title: () => t("userGrant.colGrantTime"), width: 160, format: "datetime" },
112
+ {
113
+ key: "remark",
114
+ title: () => t("userGrant.colRemark"),
115
+ ellipsis: { tooltip: !0 },
116
+ render: (e) => e.remark || f()
117
+ },
118
+ {
119
+ key: "op",
120
+ title: () => t("common.operation"),
121
+ width: 84,
122
+ fixed: "right",
123
+ hideInSetting: !0,
124
+ render: (e) => l(
125
+ O,
126
+ {
127
+ size: "small",
128
+ quaternary: !0,
129
+ type: "primary",
130
+ onClick: () => b("adjust", { id: e.userId, name: e.userName })
131
+ },
132
+ () => t("userGrant.adjust")
133
+ )
134
+ }
135
+ ];
136
+ return $(h), (e, a) => (B(), I(r(U), {
137
+ show: m.value,
138
+ "onUpdate:show": a[1] || (a[1] = (o) => m.value = o),
139
+ placement: "right",
140
+ width: "min(1440px, 100vw)"
141
+ }, {
142
+ default: u(() => [
143
+ c(r(C), {
144
+ title: r(t)("userGrant.overview"),
145
+ closable: "",
146
+ "body-content-style": { height: "100%", display: "flex", flexDirection: "column" }
147
+ }, {
148
+ default: u(() => [
149
+ c(r(D), {
150
+ ref_key: "tableRef",
151
+ ref: p,
152
+ columns: h,
153
+ fetcher: r(j).menuGrantPage,
154
+ search: { container: "table" },
155
+ toolbar: r(F),
156
+ "fill-height": "",
157
+ "min-row-height": r(q),
158
+ "card-on-narrow": "",
159
+ "storage-key": "sys-user-menu-grants",
160
+ onError: a[0] || (a[0] = (o) => r(k).error(r(H)(o)))
161
+ }, {
162
+ "pagination-prefix": u(({ itemCount: o }) => [
163
+ c(L, { count: o }, null, 8, ["count"])
164
+ ]),
165
+ _: 1
166
+ }, 8, ["fetcher", "toolbar", "min-row-height"])
167
+ ]),
168
+ _: 1
169
+ }, 8, ["title"])
170
+ ]),
171
+ _: 1
172
+ }, 8, ["show"]));
173
+ }
174
+ });
175
+ export {
176
+ re as default
177
+ };
178
+ //# sourceMappingURL=UserGrantOverviewDrawer2.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserGrantOverviewDrawer2.vue.js","sources":["../../../../../src/views/system/user/components/UserGrantOverviewDrawer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 单独授权一览:全系统的例外放在一张表里给超管复核;普通管理员只看得到自己数据范围内用户的记录(后端收口)。\n// 行操作「去调整」把用户交给父页,打开授权菜单弹窗。\n// 取数全交给 SmartTable:翻页、改筛选时它自己丢弃慢的旧响应;抽屉关闭即卸载,下次打开重新取最新数据。\nimport { computed, h, ref } from 'vue'\nimport { NButton, NDrawer, NDrawerContent, NTag, useMessage } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { SmartTable, type SmartTableColumn, type SmartTableInst } from 'smart-naive-table'\nimport TableTotal from '#/components/TableTotal/index.vue'\nimport { userApi } from '#/api'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nimport { translateError } from '#/utils/error'\nimport { SEARCH_ACTIONS, deriveHeaderFilters } from '#/utils/tableFilter'\nimport { TABLE_MIN_ROW_HEIGHT, TABLE_TOOLBAR } from '#/utils/tableToolbar'\nimport { UserMenuEffect, UserMenuGrantStatus, type UserMenuGrantPageItem } from '#/types/api'\n\nconst show = defineModel<boolean>('show', { default: false })\nconst emit = defineEmits<{ (e: 'adjust', user: { id: number; name: string }): void }>()\nconst { t } = useI18n()\nconst message = useMessage()\n\n// 父页在授权弹窗保存后调 refresh,让「去调整」改过的记录马上反映在列表里。\n// 抽屉没打开时表格未挂载,tableRef 为空,refresh 什么都不做。\nconst tableRef = ref<SmartTableInst<UserMenuGrantPageItem>>()\ndefineExpose({ refresh: () => tableRef.value?.refresh() })\n\nconst effectOptions = computed(() => [\n { label: t('userGrant.allow'), value: UserMenuEffect.Allow },\n { label: t('userGrant.deny'), value: UserMenuEffect.Deny },\n])\nconst statusOptions = computed(() => [\n { label: t('userGrant.statusActive'), value: UserMenuGrantStatus.Active },\n { label: t('userGrant.statusExpiring'), value: UserMenuGrantStatus.Expiring },\n { label: t('userGrant.statusExpired'), value: UserMenuGrantStatus.Expired },\n])\nconst STATUS_TAG: Record<UserMenuGrantStatus, 'success' | 'warning' | 'default'> = {\n [UserMenuGrantStatus.Active]: 'success',\n [UserMenuGrantStatus.Expiring]: 'warning',\n [UserMenuGrantStatus.Expired]: 'default',\n}\nconst statusLabel = (s: UserMenuGrantStatus) =>\n statusOptions.value.find(o => o.value === s)?.label ?? ''\nconst dash = () => h('span', { class: 'faint' }, '—')\n\n// 这张表有 10 列,比普通列表宽得多。列宽:「授权理由」以外都写死,合计约 1060,理由列拿剩下的(最窄 120)。\n// 抽屉跟着视口铺满、最宽 1440(抽屉内边距与卡片占约 84),所以视口 ≥ 1270 左右表格不出横向滚动条;\n// 更窄时由表格自己横向滚动。「效果」列带筛选漏斗,窄于 102 会被表头撑开,宽度照实写。\nconst columns: SmartTableColumn<UserMenuGrantPageItem>[] = [\n { type: 'index', title: () => t('common.rowNo'), width: 64, align: 'center' },\n // 只作搜索项:目标用户账号或姓名\n {\n key: 'user',\n title: () => t('userGrant.colUser'),\n hideInTable: true,\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n {\n key: 'userName',\n title: () => t('userGrant.colUser'),\n width: 140,\n ellipsis: { tooltip: true },\n card: 'title',\n render: r => `${r.userName}(${r.userAccount})`,\n },\n {\n key: 'menuTitle',\n title: () => t('userGrant.colMenu'),\n width: 140,\n ellipsis: { tooltip: true },\n render: r =>\n [r.moduleTitle ? translateMenuTitle(r.moduleTitle) : null, translateMenuTitle(r.menuTitle)]\n .filter(Boolean)\n .join(' · '),\n },\n {\n key: 'effect',\n title: () => t('userGrant.colEffect'),\n width: 102,\n options: effectOptions,\n search: { actions: SEARCH_ACTIONS.exact, props: { clearable: true } },\n render: r =>\n h(\n NTag,\n {\n size: 'small',\n bordered: false,\n type: r.effect === UserMenuEffect.Allow ? 'success' : 'error',\n },\n () => t(r.effect === UserMenuEffect.Allow ? 'userGrant.allow' : 'userGrant.deny'),\n ),\n },\n {\n key: 'status',\n title: () => t('userGrant.colStatus'),\n width: 108,\n options: statusOptions,\n search: { actions: SEARCH_ACTIONS.exact, props: { clearable: true } },\n render: r =>\n h(NTag, { size: 'small', bordered: false, type: STATUS_TAG[r.status] }, () =>\n statusLabel(r.status),\n ),\n },\n {\n key: 'expireTime',\n title: () => t('userGrant.colExpire'),\n width: 160,\n render: r => (r.expireTime ? r.expireTime.replace('T', ' ') : t('userGrant.expireLongTerm')),\n },\n // 只作搜索项:授权人账号或姓名\n {\n key: 'grantor',\n title: () => t('userGrant.colGrantor'),\n hideInTable: true,\n search: { actions: SEARCH_ACTIONS.fuzzy },\n },\n {\n key: 'grantorName',\n title: () => t('userGrant.colGrantor'),\n width: 100,\n ellipsis: { tooltip: true },\n render: r => r.grantorName || dash(),\n },\n { key: 'grantTime', title: () => t('userGrant.colGrantTime'), width: 160, format: 'datetime' },\n {\n key: 'remark',\n title: () => t('userGrant.colRemark'),\n ellipsis: { tooltip: true },\n render: r => r.remark || dash(),\n },\n {\n key: 'op',\n title: () => t('common.operation'),\n width: 84,\n fixed: 'right',\n hideInSetting: true,\n render: r =>\n h(\n NButton,\n {\n size: 'small',\n quaternary: true,\n type: 'primary',\n onClick: () => emit('adjust', { id: r.userId, name: r.userName }),\n },\n () => t('userGrant.adjust'),\n ),\n },\n]\nderiveHeaderFilters(columns)\n</script>\n\n<template>\n <n-drawer v-model:show=\"show\" placement=\"right\" width=\"min(1440px, 100vw)\">\n <n-drawer-content\n :title=\"t('userGrant.overview')\"\n closable\n :body-content-style=\"{ height: '100%', display: 'flex', flexDirection: 'column' }\"\n >\n <SmartTable\n ref=\"tableRef\"\n :columns=\"columns\"\n :fetcher=\"userApi.menuGrantPage\"\n :search=\"{ container: 'table' }\"\n :toolbar=\"TABLE_TOOLBAR\"\n fill-height\n :min-row-height=\"TABLE_MIN_ROW_HEIGHT\"\n card-on-narrow\n storage-key=\"sys-user-menu-grants\"\n @error=\"e => message.error(translateError(e))\"\n >\n <template #pagination-prefix=\"{ itemCount }\">\n <TableTotal :count=\"itemCount\" />\n </template>\n </SmartTable>\n </n-drawer-content>\n </n-drawer>\n</template>\n"],"names":["show","_useModel","__props","emit","__emit","useI18n","message","useMessage","tableRef","ref","__expose","_a","effectOptions","computed","UserMenuEffect","statusOptions","UserMenuGrantStatus","STATUS_TAG","statusLabel","s","dash","h","columns","SEARCH_ACTIONS","r","translateMenuTitle","NTag","NButton","deriveHeaderFilters","_createBlock","_unref","NDrawer","$event","_createVNode","NDrawerContent","SmartTable","userApi","TABLE_TOOLBAR","TABLE_MIN_ROW_HEIGHT","_cache","e","translateError","_withCtx","itemCount","TableTotal"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { MenuTreeNode } from '../../../../types/menu';
2
+ import { UserMenuEffect, UserMenuGrantItem, UserMenuGrantUpsert } from '../../../../types/api';
3
+ export type TriState = 'follow' | 'allow' | 'deny';
4
+ export interface DraftEntry {
5
+ effect: UserMenuEffect;
6
+ /** 到期日 yyyy-MM-dd;null = 长期 */
7
+ expireDate: string | null;
8
+ /** 基线记录的原始到期时间串,只在日期没被改动时原样带回;新建的记录没有 */
9
+ expireTime?: string | null;
10
+ remark: string | null;
11
+ }
12
+ export type Draft = Map<number, DraftEntry>;
13
+ /** 弹窗只读的原因码 → 文案 key(直接复用后端错误码的 msgKey 文案)。 */
14
+ export declare const READONLY_REASON_KEYS: Record<number, string>;
15
+ /** 本地日期 yyyy-MM-dd。 */
16
+ export declare const formatDate: (d: Date) => string;
17
+ /** 后端到期时间(本地时间串)→ 日期部分。 */
18
+ export declare const toExpireDate: (expireTime?: string | null) => string | null;
19
+ /** 日期 → 后端到期时间:当天最后一秒失效。 */
20
+ export declare const toExpireTime: (date: string | null) => string | null;
21
+ export declare function draftFromGrants(grants: UserMenuGrantItem[]): Draft;
22
+ export declare function triStateOf(draft: Draft, menuId: number): TriState;
23
+ /**
24
+ * 切换三态。从「跟随」进入允许时带上默认到期日(普通管理员授允许必须限时,默认给到上限);
25
+ * 进入拒绝不带(拒绝只会收紧,不要求限时);已有记录切换效果时保留已填的到期日与备注。
26
+ */
27
+ export declare function setTriState(draft: Draft, menuId: number, next: TriState, defaultExpireDate: string | null): void;
28
+ /** 与打开时的快照比出变更集。按菜单 Id 升序,提交内容稳定。 */
29
+ export declare function diffDraft(baseline: Draft, draft: Draft): {
30
+ upserts: UserMenuGrantUpsert[];
31
+ removes: number[];
32
+ };
33
+ export declare function countDraft(draft: Draft): {
34
+ allow: number;
35
+ deny: number;
36
+ };
37
+ /** 浏览器的今天 + 最长天数;不限时为 null。只在服务端没给出最晚到期日时作后备。 */
38
+ export declare function maxExpireDate(maxDays: number | null | undefined, today: Date): string | null;
39
+ /**
40
+ * 普通管理员授允许的到期日上限(也是默认值);不限时为 null。
41
+ * 服务端给了 delegatedMaxDate 就直接用它:那是服务器本地日期 + 最长天数,与后端校验是同一份计算。
42
+ * 浏览器与服务器不在同一时区时(服务器 UTC、用户东八区),浏览器自己加天数每天凌晨会领先一天,选出被后端拒收的上限。
43
+ * 日期串只做字符串比较,不转成 Date(new Date('yyyy-MM-dd') 按 UTC 解析,在 UTC 以西的时区会差一天)。
44
+ * 没给(自定义的策略实现没提供最晚到期日)才退回浏览器的今天 + delegatedMaxDays。
45
+ */
46
+ export declare function expiryLimit(effective: {
47
+ delegatedMaxDate?: string | null;
48
+ delegatedMaxDays?: number | null;
49
+ } | null | undefined, today: Date): string | null;
50
+ /** 日期选择器里不可选的日子:今天以前,以及(受限时)最晚到期日以后。三个参数都是 yyyy-MM-dd,按字符串比较。 */
51
+ export declare const isExpiryDateDisabled: (date: string, today: string, max: string | null) => boolean;
52
+ /**
53
+ * 要提交的记录里到期日不合规的菜单 Id:谁都不能选今天以前的日期;受限(max 非空,即 expiryLimit 的结果)时,
54
+ * 「允许」还必须有到期日且不晚于上限。只看变更集,没动的老记录不校验(后端同样只校验变更集)。
55
+ */
56
+ export declare function invalidExpiry(baseline: Draft, draft: Draft, max: string | null, today: Date): number[];
57
+ /**
58
+ * 拒掉 menuId(连同子孙)后,仍由子树外的有效节点携带的权限码 → 携带它们的节点 Id。
59
+ * 规则同后端 UserMenuGrantRules.LeakedCodes;这里给还没保存的「拒绝」就地提示用,effective 取打开时的有效节点。
60
+ */
61
+ export declare function leakedCodes(tree: MenuTreeNode[], effective: Set<number>, menuId: number): {
62
+ code: string;
63
+ carriers: number[];
64
+ }[];
@@ -0,0 +1,117 @@
1
+ import { splitPermission as D } from "../../../../types/menu.js";
2
+ import { UserMenuEffect as u } from "../../../../types/api.js";
3
+ const y = {
4
+ 42029: "error.user.cannotOperateSelf",
5
+ 42007: "error.user.superAdminProtected",
6
+ 41005: "error.user.outOfDataScope",
7
+ 41007: "error.perm.targetIsDelegatedAdmin"
8
+ }, g = (e) => String(e).padStart(2, "0"), w = (e) => `${e.getFullYear()}-${g(e.getMonth() + 1)}-${g(e.getDate())}`, m = (e) => e ? e.slice(0, 10) : null, S = (e) => e ? `${e}T23:59:59` : null;
9
+ function O(e) {
10
+ return new Map(
11
+ e.map((t) => [
12
+ t.menuId,
13
+ {
14
+ effect: t.effect,
15
+ expireDate: m(t.expireTime),
16
+ expireTime: t.expireTime ?? null,
17
+ remark: t.remark ?? null
18
+ }
19
+ ])
20
+ );
21
+ }
22
+ function I(e, t) {
23
+ const n = e.get(t);
24
+ return n ? n.effect === u.Allow ? "allow" : "deny" : "follow";
25
+ }
26
+ function Y(e, t, n, l) {
27
+ if (n === "follow") {
28
+ e.delete(t);
29
+ return;
30
+ }
31
+ const o = n === "allow" ? u.Allow : u.Deny, r = e.get(t), i = (r == null ? void 0 : r.expireDate) ?? (o === u.Allow ? l : null);
32
+ e.set(t, {
33
+ effect: o,
34
+ expireDate: i,
35
+ expireTime: r == null ? void 0 : r.expireTime,
36
+ remark: (r == null ? void 0 : r.remark) ?? null
37
+ });
38
+ }
39
+ const p = (e) => (e == null ? void 0 : e.trim()) || null, T = (e, t) => e.effect === t.effect && e.expireDate === t.expireDate && p(e.remark) === p(t.remark), E = (e) => e.expireTime && m(e.expireTime) === e.expireDate ? e.expireTime : S(e.expireDate);
40
+ function M(e, t) {
41
+ const n = [];
42
+ for (const [o, r] of t) {
43
+ const i = e.get(o);
44
+ i && T(i, r) || n.push({
45
+ menuId: o,
46
+ effect: r.effect,
47
+ expireTime: E(r),
48
+ remark: p(r.remark)
49
+ });
50
+ }
51
+ const l = [...e.keys()].filter((o) => !t.has(o));
52
+ return {
53
+ upserts: n.toSorted((o, r) => o.menuId - r.menuId),
54
+ removes: l.toSorted((o, r) => o - r)
55
+ };
56
+ }
57
+ function $(e) {
58
+ let t = 0, n = 0;
59
+ for (const l of e.values())
60
+ l.effect === u.Allow ? t++ : n++;
61
+ return { allow: t, deny: n };
62
+ }
63
+ function h(e, t) {
64
+ return e == null ? null : w(new Date(t.getFullYear(), t.getMonth(), t.getDate() + e));
65
+ }
66
+ function F(e, t) {
67
+ return e != null && e.delegatedMaxDate ? e.delegatedMaxDate : h(e == null ? void 0 : e.delegatedMaxDays, t);
68
+ }
69
+ const R = (e, t, n) => e < t || n != null && e > n;
70
+ function L(e, t, n, l) {
71
+ const o = w(l);
72
+ return M(e, t).upserts.filter((r) => {
73
+ const i = m(r.expireTime);
74
+ return i && i < o ? !0 : r.effect !== u.Allow || n == null ? !1 : !i || i > n;
75
+ }).map((r) => r.menuId);
76
+ }
77
+ function N(e, t, n) {
78
+ const l = /* @__PURE__ */ new Map(), o = (s) => {
79
+ for (const a of s)
80
+ l.set(a.id, a), o(a.children);
81
+ };
82
+ o(e);
83
+ const r = l.get(n);
84
+ if (!r) return [];
85
+ const i = /* @__PURE__ */ new Set(), d = (s) => {
86
+ i.add(s.id), s.children.forEach(d);
87
+ };
88
+ d(r);
89
+ const x = new Set([...i].flatMap((s) => D(l.get(s).permission)));
90
+ if (!x.size) return [];
91
+ const f = /* @__PURE__ */ new Map();
92
+ for (const s of t) {
93
+ if (i.has(s)) continue;
94
+ const a = l.get(s);
95
+ if (a)
96
+ for (const c of D(a.permission))
97
+ x.has(c) && f.set(c, [...f.get(c) ?? [], s]);
98
+ }
99
+ return [...f.keys()].toSorted().map((s) => ({ code: s, carriers: f.get(s).toSorted((a, c) => a - c) }));
100
+ }
101
+ export {
102
+ y as READONLY_REASON_KEYS,
103
+ $ as countDraft,
104
+ M as diffDraft,
105
+ O as draftFromGrants,
106
+ F as expiryLimit,
107
+ w as formatDate,
108
+ L as invalidExpiry,
109
+ R as isExpiryDateDisabled,
110
+ N as leakedCodes,
111
+ h as maxExpireDate,
112
+ Y as setTriState,
113
+ m as toExpireDate,
114
+ S as toExpireTime,
115
+ I as triStateOf
116
+ };
117
+ //# sourceMappingURL=userGrantState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"userGrantState.js","sources":["../../../../../src/views/system/user/components/userGrantState.ts"],"sourcesContent":["// 用户授权弹窗的草稿状态(纯函数,组件只管渲染)。草稿 = 菜单 Id → 单独授权记录;没有记录就是「跟随角色」。\n// 保存只提交变更集:与打开时的快照比,算出新增 / 修改(upserts)与移除(removes),没动的记录不提交。\n// 到期按日期:前端只选日期,新选的日期提交当天 23:59:59;日期没动的老记录原样带回它的到期时间串\n//(后端存的不一定是 23:59:59,只改备注不能把它悄悄改写,更不能让已过期的记录重新生效)。\n// 普通管理员授允许的上限是服务端给出的最晚到期日(服务器本地日期 + 最长天数),校验与展示共用这一天。\nimport { splitPermission, type MenuTreeNode } from '#/types/menu'\nimport { UserMenuEffect, type UserMenuGrantItem, type UserMenuGrantUpsert } from '#/types/api'\n\nexport type TriState = 'follow' | 'allow' | 'deny'\n\nexport interface DraftEntry {\n effect: UserMenuEffect\n /** 到期日 yyyy-MM-dd;null = 长期 */\n expireDate: string | null\n /** 基线记录的原始到期时间串,只在日期没被改动时原样带回;新建的记录没有 */\n expireTime?: string | null\n remark: string | null\n}\nexport type Draft = Map<number, DraftEntry>\n\n/** 弹窗只读的原因码 → 文案 key(直接复用后端错误码的 msgKey 文案)。 */\nexport const READONLY_REASON_KEYS: Record<number, string> = {\n 42029: 'error.user.cannotOperateSelf',\n 42007: 'error.user.superAdminProtected',\n 41005: 'error.user.outOfDataScope',\n 41007: 'error.perm.targetIsDelegatedAdmin',\n}\n\nconst pad = (n: number) => String(n).padStart(2, '0')\n\n/** 本地日期 yyyy-MM-dd。 */\nexport const formatDate = (d: Date) =>\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`\n\n/** 后端到期时间(本地时间串)→ 日期部分。 */\nexport const toExpireDate = (expireTime?: string | null) =>\n expireTime ? expireTime.slice(0, 10) : null\n\n/** 日期 → 后端到期时间:当天最后一秒失效。 */\nexport const toExpireTime = (date: string | null) => (date ? `${date}T23:59:59` : null)\n\nexport function draftFromGrants(grants: UserMenuGrantItem[]): Draft {\n return new Map(\n grants.map(g => [\n g.menuId,\n {\n effect: g.effect,\n expireDate: toExpireDate(g.expireTime),\n expireTime: g.expireTime ?? null,\n remark: g.remark ?? null,\n },\n ]),\n )\n}\n\nexport function triStateOf(draft: Draft, menuId: number): TriState {\n const entry = draft.get(menuId)\n if (!entry) return 'follow'\n return entry.effect === UserMenuEffect.Allow ? 'allow' : 'deny'\n}\n\n/**\n * 切换三态。从「跟随」进入允许时带上默认到期日(普通管理员授允许必须限时,默认给到上限);\n * 进入拒绝不带(拒绝只会收紧,不要求限时);已有记录切换效果时保留已填的到期日与备注。\n */\nexport function setTriState(\n draft: Draft,\n menuId: number,\n next: TriState,\n defaultExpireDate: string | null,\n): void {\n if (next === 'follow') {\n draft.delete(menuId)\n return\n }\n const effect = next === 'allow' ? UserMenuEffect.Allow : UserMenuEffect.Deny\n const cur = draft.get(menuId)\n const expireDate = cur?.expireDate ?? (effect === UserMenuEffect.Allow ? defaultExpireDate : null)\n draft.set(menuId, {\n effect,\n expireDate,\n expireTime: cur?.expireTime,\n remark: cur?.remark ?? null,\n })\n}\n\nconst normRemark = (remark: string | null) => remark?.trim() || null\n\nconst sameEntry = (a: DraftEntry, b: DraftEntry) =>\n a.effect === b.effect &&\n a.expireDate === b.expireDate &&\n normRemark(a.remark) === normRemark(b.remark)\n\n/** 提交用的到期时间:日期没动就沿用原始时间串,日期被改动(或新选)才取当天最后一秒。 */\nconst expireTimeOf = (entry: DraftEntry) =>\n entry.expireTime && toExpireDate(entry.expireTime) === entry.expireDate\n ? entry.expireTime\n : toExpireTime(entry.expireDate)\n\n/** 与打开时的快照比出变更集。按菜单 Id 升序,提交内容稳定。 */\nexport function diffDraft(\n baseline: Draft,\n draft: Draft,\n): { upserts: UserMenuGrantUpsert[]; removes: number[] } {\n const upserts: UserMenuGrantUpsert[] = []\n for (const [menuId, entry] of draft) {\n const before = baseline.get(menuId)\n if (before && sameEntry(before, entry)) continue\n upserts.push({\n menuId,\n effect: entry.effect,\n expireTime: expireTimeOf(entry),\n remark: normRemark(entry.remark),\n })\n }\n const removes = [...baseline.keys()].filter(id => !draft.has(id))\n return {\n upserts: upserts.toSorted((a, b) => a.menuId - b.menuId),\n removes: removes.toSorted((a, b) => a - b),\n }\n}\n\nexport function countDraft(draft: Draft): { allow: number; deny: number } {\n let allow = 0\n let deny = 0\n for (const entry of draft.values()) {\n if (entry.effect === UserMenuEffect.Allow) allow++\n else deny++\n }\n return { allow, deny }\n}\n\n/** 浏览器的今天 + 最长天数;不限时为 null。只在服务端没给出最晚到期日时作后备。 */\nexport function maxExpireDate(maxDays: number | null | undefined, today: Date): string | null {\n if (maxDays == null) return null\n return formatDate(new Date(today.getFullYear(), today.getMonth(), today.getDate() + maxDays))\n}\n\n/**\n * 普通管理员授允许的到期日上限(也是默认值);不限时为 null。\n * 服务端给了 delegatedMaxDate 就直接用它:那是服务器本地日期 + 最长天数,与后端校验是同一份计算。\n * 浏览器与服务器不在同一时区时(服务器 UTC、用户东八区),浏览器自己加天数每天凌晨会领先一天,选出被后端拒收的上限。\n * 日期串只做字符串比较,不转成 Date(new Date('yyyy-MM-dd') 按 UTC 解析,在 UTC 以西的时区会差一天)。\n * 没给(自定义的策略实现没提供最晚到期日)才退回浏览器的今天 + delegatedMaxDays。\n */\nexport function expiryLimit(\n effective:\n { delegatedMaxDate?: string | null; delegatedMaxDays?: number | null } | null | undefined,\n today: Date,\n): string | null {\n if (effective?.delegatedMaxDate) return effective.delegatedMaxDate\n return maxExpireDate(effective?.delegatedMaxDays, today)\n}\n\n/** 日期选择器里不可选的日子:今天以前,以及(受限时)最晚到期日以后。三个参数都是 yyyy-MM-dd,按字符串比较。 */\nexport const isExpiryDateDisabled = (date: string, today: string, max: string | null): boolean =>\n date < today || (max != null && date > max)\n\n/**\n * 要提交的记录里到期日不合规的菜单 Id:谁都不能选今天以前的日期;受限(max 非空,即 expiryLimit 的结果)时,\n * 「允许」还必须有到期日且不晚于上限。只看变更集,没动的老记录不校验(后端同样只校验变更集)。\n */\nexport function invalidExpiry(\n baseline: Draft,\n draft: Draft,\n max: string | null,\n today: Date,\n): number[] {\n const todayStr = formatDate(today)\n return diffDraft(baseline, draft)\n .upserts.filter(u => {\n const date = toExpireDate(u.expireTime)\n if (date && date < todayStr) return true\n if (u.effect !== UserMenuEffect.Allow || max == null) return false\n return !date || date > max\n })\n .map(u => u.menuId)\n}\n\n/**\n * 拒掉 menuId(连同子孙)后,仍由子树外的有效节点携带的权限码 → 携带它们的节点 Id。\n * 规则同后端 UserMenuGrantRules.LeakedCodes;这里给还没保存的「拒绝」就地提示用,effective 取打开时的有效节点。\n */\nexport function leakedCodes(\n tree: MenuTreeNode[],\n effective: Set<number>,\n menuId: number,\n): { code: string; carriers: number[] }[] {\n const byId = new Map<number, MenuTreeNode>()\n const index = (nodes: MenuTreeNode[]) => {\n for (const n of nodes) {\n byId.set(n.id, n)\n index(n.children)\n }\n }\n index(tree)\n const root = byId.get(menuId)\n if (!root) return []\n\n const subtree = new Set<number>()\n const collect = (n: MenuTreeNode) => {\n subtree.add(n.id)\n n.children.forEach(collect)\n }\n collect(root)\n const denied = new Set([...subtree].flatMap(id => splitPermission(byId.get(id)!.permission)))\n if (!denied.size) return []\n\n const carriers = new Map<string, number[]>()\n for (const id of effective) {\n if (subtree.has(id)) continue\n const n = byId.get(id)\n if (!n) continue\n for (const code of splitPermission(n.permission))\n if (denied.has(code)) carriers.set(code, [...(carriers.get(code) ?? []), id])\n }\n return [...carriers.keys()]\n .toSorted()\n .map(code => ({ code, carriers: carriers.get(code)!.toSorted((a, b) => a - b) }))\n}\n"],"names":["READONLY_REASON_KEYS","pad","n","formatDate","d","toExpireDate","expireTime","toExpireTime","date","draftFromGrants","grants","g","triStateOf","draft","menuId","entry","UserMenuEffect","setTriState","next","defaultExpireDate","effect","cur","expireDate","normRemark","remark","sameEntry","a","b","expireTimeOf","diffDraft","baseline","upserts","before","removes","id","countDraft","allow","deny","maxExpireDate","maxDays","today","expiryLimit","effective","isExpiryDateDisabled","max","invalidExpiry","todayStr","u","leakedCodes","tree","byId","index","nodes","root","subtree","collect","denied","splitPermission","carriers","code"],"mappings":"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"}