smart-admin-web 10.12.0 → 10.12.2

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 (37) hide show
  1. package/dist/api/index.d.ts +13 -1
  2. package/dist/api/index.js +25 -13
  3. package/dist/api/index.js.map +1 -1
  4. package/dist/api/schema.d.ts +360 -3
  5. package/dist/assets/icons/ph-subset.json.js +1 -1
  6. package/dist/lib/runtime.js +1 -1
  7. package/dist/locales/en-US.d.ts +13 -1
  8. package/dist/locales/en-US.js +15 -3
  9. package/dist/locales/en-US.js.map +1 -1
  10. package/dist/locales/zh-CN.d.ts +13 -1
  11. package/dist/locales/zh-CN.js +15 -3
  12. package/dist/locales/zh-CN.js.map +1 -1
  13. package/dist/router/index.js +18 -13
  14. package/dist/router/index.js.map +1 -1
  15. package/dist/router/viewRegistry.js +1 -1
  16. package/dist/router/viewRegistry.js.map +1 -1
  17. package/dist/style.css +1 -1
  18. package/dist/types/api.d.ts +26 -0
  19. package/dist/types/api.js.map +1 -1
  20. package/dist/views/dashboard/ShortcutPicker.vue.d.ts +16 -0
  21. package/dist/views/dashboard/ShortcutPicker.vue.js +8 -0
  22. package/dist/views/dashboard/ShortcutPicker.vue.js.map +1 -0
  23. package/dist/views/dashboard/ShortcutPicker2.vue.js +86 -0
  24. package/dist/views/dashboard/ShortcutPicker2.vue.js.map +1 -0
  25. package/dist/views/dashboard/biz.vue.js +2 -2
  26. package/dist/views/dashboard/biz2.vue.js +265 -93
  27. package/dist/views/dashboard/biz2.vue.js.map +1 -1
  28. package/dist/views/dashboard/onlineDuration.d.ts +5 -0
  29. package/dist/views/dashboard/onlineDuration.js +8 -0
  30. package/dist/views/dashboard/onlineDuration.js.map +1 -0
  31. package/dist/views/dashboard/shortcutGroups.d.ts +18 -0
  32. package/dist/views/dashboard/shortcutGroups.js +19 -0
  33. package/dist/views/dashboard/shortcutGroups.js.map +1 -0
  34. package/dist/views/system/user/index.vue.js +2 -2
  35. package/dist/views/system/user/index2.vue.js +281 -211
  36. package/dist/views/system/user/index2.vue.js.map +1 -1
  37. package/package.json +1 -1
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ excludePaths: string[];
3
+ };
4
+ type __VLS_ModelProps = {
5
+ 'show'?: boolean;
6
+ };
7
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
8
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
9
+ pick: (path: string) => any;
10
+ "update:show": (value: boolean) => any;
11
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
12
+ onPick?: ((path: string) => any) | undefined;
13
+ "onUpdate:show"?: ((value: boolean) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import o from "./ShortcutPicker2.vue.js";
2
+ /* empty css */
3
+ import t from "../../_virtual/_plugin-vue_export-helper.js";
4
+ const m = /* @__PURE__ */ t(o, [["__scopeId", "data-v-1ba501cb"]]);
5
+ export {
6
+ m as default
7
+ };
8
+ //# sourceMappingURL=ShortcutPicker.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShortcutPicker.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -0,0 +1,86 @@
1
+ import { defineComponent as _, useModel as g, ref as m, watch as M, nextTick as b, computed as B, openBlock as n, createBlock as f, unref as l, withCtx as i, createVNode as s, createCommentVNode as S, createElementBlock as h, Fragment as z, renderList as E, createElementVNode as L, toDisplayString as P, mergeModels as v } from "vue";
2
+ import { NModal as V, NCard as q, NInput as F, NScrollbar as I, NEmpty as R } from "naive-ui";
3
+ import { useI18n as T } from "vue-i18n";
4
+ import { useMenuFlat as U } from "../../composables/useMenuFlat.js";
5
+ import { translateMenuTitle as $ } from "../../locales/menuTitle.js";
6
+ import A from "../../components/AppIcon.vue.js";
7
+ const D = ["onClick"], W = /* @__PURE__ */ _({
8
+ __name: "ShortcutPicker",
9
+ props: /* @__PURE__ */ v({
10
+ excludePaths: {}
11
+ }, {
12
+ show: { type: Boolean, default: !1 },
13
+ showModifiers: {}
14
+ }),
15
+ emits: /* @__PURE__ */ v(["pick"], ["update:show"]),
16
+ setup(u, { emit: w }) {
17
+ const k = u, y = w, a = g(u, "show"), { t: c } = T(), x = U(), r = m(""), p = m();
18
+ M(a, (o) => {
19
+ o && (r.value = "", b(() => {
20
+ var t;
21
+ return (t = p.value) == null ? void 0 : t.focus();
22
+ }));
23
+ });
24
+ function C(o) {
25
+ return $(o.title);
26
+ }
27
+ const d = B(() => {
28
+ const o = new Set(k.excludePaths), t = r.value.trim().toLowerCase();
29
+ return x.value.filter((e) => !o.has(e.path)).map((e) => ({ ...e, display: C(e) })).filter((e) => !t || e.display.toLowerCase().includes(t));
30
+ });
31
+ function N(o) {
32
+ y("pick", o), a.value = !1;
33
+ }
34
+ return (o, t) => (n(), f(l(V), {
35
+ show: a.value,
36
+ "onUpdate:show": t[1] || (t[1] = (e) => a.value = e)
37
+ }, {
38
+ default: i(() => [
39
+ s(l(q), {
40
+ style: { width: "420px" },
41
+ title: l(c)("biz.quickAdd"),
42
+ bordered: !1,
43
+ size: "small",
44
+ role: "dialog"
45
+ }, {
46
+ default: i(() => [
47
+ s(l(F), {
48
+ ref_key: "inputRef",
49
+ ref: p,
50
+ value: r.value,
51
+ "onUpdate:value": t[0] || (t[0] = (e) => r.value = e),
52
+ placeholder: l(c)("common.search"),
53
+ clearable: ""
54
+ }, null, 8, ["value", "placeholder"]),
55
+ s(l(I), { style: { "max-height": "320px", "margin-top": "12px" } }, {
56
+ default: i(() => [
57
+ d.value.length ? S("", !0) : (n(), f(l(R), {
58
+ key: 0,
59
+ class: "empty"
60
+ })),
61
+ (n(!0), h(z, null, E(d.value, (e) => (n(), h("div", {
62
+ key: e.path,
63
+ class: "row",
64
+ onClick: (j) => N(e.path)
65
+ }, [
66
+ s(A, {
67
+ icon: e.icon || "ph:squares-four",
68
+ size: 16
69
+ }, null, 8, ["icon"]),
70
+ L("span", null, P(e.display), 1)
71
+ ], 8, D))), 128))
72
+ ]),
73
+ _: 1
74
+ })
75
+ ]),
76
+ _: 1
77
+ }, 8, ["title"])
78
+ ]),
79
+ _: 1
80
+ }, 8, ["show"]));
81
+ }
82
+ });
83
+ export {
84
+ W as default
85
+ };
86
+ //# sourceMappingURL=ShortcutPicker2.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShortcutPicker2.vue.js","sources":["../../../src/views/dashboard/ShortcutPicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 复用 MenuSearch.vue 的\"搜索+标题翻译+高亮候选\"思路,但不是同一个组件——MenuSearch 是全局命令面板,\n// 点了就跳转;这里点了是回调 pick(仅置顶,不导航),排除列表也不一样(排除已置顶,不是排除当前页)。\nimport { computed, nextTick, ref, watch } from 'vue'\nimport { NModal, NCard, NInput, NEmpty, NScrollbar } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { useMenuFlat, type MenuLeaf } from '#/composables/useMenuFlat'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nimport AppIcon from '#/components/AppIcon.vue'\n\nconst props = defineProps<{ excludePaths: string[] }>()\nconst emit = defineEmits<{ pick: [path: string] }>()\nconst show = defineModel<boolean>('show', { default: false })\n\nconst { t } = useI18n()\nconst leaves = useMenuFlat()\nconst keyword = ref('')\nconst inputRef = ref<InstanceType<typeof NInput>>()\n\nwatch(show, v => {\n if (v) {\n keyword.value = ''\n nextTick(() => inputRef.value?.focus())\n }\n})\n\nfunction label(item: MenuLeaf) {\n return translateMenuTitle(item.title)\n}\n\nconst candidates = computed(() => {\n const excluded = new Set(props.excludePaths)\n const kw = keyword.value.trim().toLowerCase()\n return leaves.value\n .filter(l => !excluded.has(l.path))\n .map(l => ({ ...l, display: label(l) }))\n .filter(l => !kw || l.display.toLowerCase().includes(kw))\n})\n\nfunction pick(path: string) {\n emit('pick', path)\n show.value = false\n}\n</script>\n\n<template>\n <n-modal v-model:show=\"show\">\n <n-card\n style=\"width: 420px\"\n :title=\"t('biz.quickAdd')\"\n :bordered=\"false\"\n size=\"small\"\n role=\"dialog\"\n >\n <n-input ref=\"inputRef\" v-model:value=\"keyword\" :placeholder=\"t('common.search')\" clearable />\n <n-scrollbar style=\"max-height: 320px; margin-top: 12px\">\n <n-empty v-if=\"!candidates.length\" class=\"empty\" />\n <div v-for=\"c in candidates\" :key=\"c.path\" class=\"row\" @click=\"pick(c.path)\">\n <AppIcon :icon=\"c.icon || 'ph:squares-four'\" :size=\"16\" />\n <span>{{ c.display }}</span>\n </div>\n </n-scrollbar>\n </n-card>\n </n-modal>\n</template>\n\n<style scoped>\n.row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-radius: var(--radius-md);\n cursor: pointer;\n}\n.row:hover {\n background: var(--color-fill-hover);\n}\n.empty {\n padding: 24px 0;\n}\n</style>\n"],"names":["props","__props","emit","__emit","show","_useModel","t","useI18n","leaves","useMenuFlat","keyword","ref","inputRef","watch","v","nextTick","_a","label","item","translateMenuTitle","candidates","computed","excluded","kw","l","pick","path","_createBlock","_unref","NModal","$event","_createVNode","NCard","NInput","NScrollbar","NEmpty","_createElementBlock","_Fragment","_renderList","c","AppIcon","_createElementVNode","_toDisplayString"],"mappings":";;;;;;;;;;;;;;;;AAUA,UAAMA,IAAQC,GACRC,IAAOC,GACPC,IAAOC,EAAoBJ,GAAC,MAA0B,GAEtD,EAAE,GAAAK,EAAA,IAAMC,EAAA,GACRC,IAASC,EAAA,GACTC,IAAUC,EAAI,EAAE,GAChBC,IAAWD,EAAA;AAEjB,IAAAE,EAAMT,GAAM,CAAAU,MAAK;AACf,MAAIA,MACFJ,EAAQ,QAAQ,IAChBK,EAAS,MAAA;;AAAM,gBAAAC,IAAAJ,EAAS,UAAT,gBAAAI,EAAgB;AAAA,OAAO;AAAA,IAE1C,CAAC;AAED,aAASC,EAAMC,GAAgB;AAC7B,aAAOC,EAAmBD,EAAK,KAAK;AAAA,IACtC;AAEA,UAAME,IAAaC,EAAS,MAAM;AAChC,YAAMC,IAAW,IAAI,IAAItB,EAAM,YAAY,GACrCuB,IAAKb,EAAQ,MAAM,KAAA,EAAO,YAAA;AAChC,aAAOF,EAAO,MACX,OAAO,CAAAgB,MAAK,CAACF,EAAS,IAAIE,EAAE,IAAI,CAAC,EACjC,IAAI,CAAAA,OAAM,EAAE,GAAGA,GAAG,SAASP,EAAMO,CAAC,EAAA,EAAI,EACtC,OAAO,CAAAA,MAAK,CAACD,KAAMC,EAAE,QAAQ,YAAA,EAAc,SAASD,CAAE,CAAC;AAAA,IAC5D,CAAC;AAED,aAASE,EAAKC,GAAc;AAC1B,MAAAxB,EAAK,QAAQwB,CAAI,GACjBtB,EAAK,QAAQ;AAAA,IACf;2BAIEuB,EAiBUC,EAAAC,CAAA,GAAA;AAAA,MAjBO,MAAMzB,EAAA;AAAA,8CAAAA,EAAI,QAAA0B;AAAA,IAAA;iBACzB,MAeS;AAAA,QAfTC,EAeSH,EAAAI,CAAA,GAAA;AAAA,UAdP,OAAA,EAAA,OAAA,QAAA;AAAA,UACC,OAAOJ,EAAAtB,CAAA,EAAC,cAAA;AAAA,UACR,UAAU;AAAA,UACX,MAAK;AAAA,UACL,MAAK;AAAA,QAAA;qBAEL,MAA8F;AAAA,YAA9FyB,EAA8FH,EAAAK,CAAA,GAAA;AAAA,uBAAjF;AAAA,cAAJ,KAAIrB;AAAA,cAAmB,OAAOF,EAAA;AAAA,uDAAAA,EAAO,QAAAoB;AAAA,cAAG,aAAaF,EAAAtB,CAAA,EAAC,eAAA;AAAA,cAAmB,WAAA;AAAA,YAAA;YAClFyB,EAMcH,EAAAM,CAAA,GAAA,EAND,OAAA,EAAA,cAAA,SAAA,cAAA,OAAA,KAA2C;AAAA,yBACtD,MAAmD;AAAA,gBAAnCd,EAAA,MAAW,2BAA3BO,EAAmDC,EAAAO,CAAA,GAAA;AAAA;kBAAhB,OAAM;AAAA,gBAAA;wBACzCC,EAGMC,GAAA,MAAAC,EAHWlB,EAAA,OAAU,CAAfmB,YAAZH,EAGM,OAAA;AAAA,kBAHwB,KAAKG,EAAE;AAAA,kBAAM,OAAM;AAAA,kBAAO,SAAK,CAAAT,MAAEL,EAAKc,EAAE,IAAI;AAAA,gBAAA;kBACxER,EAA0DS,GAAA;AAAA,oBAAhD,MAAMD,EAAE,QAAI;AAAA,oBAAwB,MAAM;AAAA,kBAAA;kBACpDE,EAA4B,QAAA,MAAAC,EAAnBH,EAAE,OAAO,GAAA,CAAA;AAAA,gBAAA;;;;;;;;;;;;"}
@@ -1,8 +1,8 @@
1
1
  import o from "./biz2.vue.js";
2
2
  /* empty css */
3
3
  import t from "../../_virtual/_plugin-vue_export-helper.js";
4
- const m = /* @__PURE__ */ t(o, [["__scopeId", "data-v-e7ff753e"]]);
4
+ const p = /* @__PURE__ */ t(o, [["__scopeId", "data-v-4a6a23e0"]]);
5
5
  export {
6
- m as default
6
+ p as default
7
7
  };
8
8
  //# sourceMappingURL=biz.vue.js.map
@@ -1,136 +1,308 @@
1
- import { defineComponent as B, computed as b, ref as T, onMounted as V, openBlock as o, createElementBlock as u, createElementVNode as s, normalizeStyle as $, createVNode as d, unref as e, toDisplayString as a, withCtx as r, Fragment as z, renderList as N, createBlock as p, createTextVNode as _, createCommentVNode as E } from "vue";
2
- import { NCard as g, NButton as w, NEmpty as C, NList as L, NListItem as R } from "naive-ui";
3
- import { Icon as D } from "@iconify/vue";
4
- import { useI18n as M } from "vue-i18n";
5
- import { useRoute as F, useRouter as G } from "vue-router";
6
- import { useUserStore as U } from "../../stores/user.js";
7
- import { useAuthStore as j } from "../../stores/auth.js";
8
- import { useAppStore as H } from "../../stores/app.js";
9
- import { btnGrad as J } from "../../theme/mix.js";
10
- import { noticeApi as K } from "../../api/index.js";
11
- import O from "../../components/AppIcon.vue.js";
12
- import { fmtDateTime as P } from "../../utils/format.js";
13
- const Q = { class: "view" }, W = { class: "banner" }, X = { class: "hi" }, Y = { class: "tip" }, Z = {
1
+ import { defineComponent as W, computed as b, ref as h, onMounted as X, onUnmounted as Y, openBlock as o, createElementBlock as s, createElementVNode as n, normalizeStyle as Z, createVNode as c, unref as t, toDisplayString as l, createCommentVNode as r, createSlots as tt, withCtx as d, Fragment as C, renderList as q, createBlock as y, createTextVNode as R, withModifiers as F } from "vue";
2
+ import { NCard as D, NEmpty as I, NTag as et, NButton as ot, NList as it, NListItem as st } from "naive-ui";
3
+ import { Icon as S } from "@iconify/vue";
4
+ import { useI18n as nt } from "vue-i18n";
5
+ import { useRoute as at, useRouter as lt } from "vue-router";
6
+ import { useUserStore as ut } from "../../stores/user.js";
7
+ import { useAppStore as ct } from "../../stores/app.js";
8
+ import { btnGrad as rt } from "../../theme/mix.js";
9
+ import { personalApi as _, noticeApi as dt } from "../../api/index.js";
10
+ import { useMenuFlat as pt } from "../../composables/useMenuFlat.js";
11
+ import { translateMenuTitle as G } from "../../locales/menuTitle.js";
12
+ import { onlineDurationParts as vt } from "./onlineDuration.js";
13
+ import { buildShortcutGroups as mt } from "./shortcutGroups.js";
14
+ import ht from "./ShortcutPicker.vue.js";
15
+ import j from "../../components/AppIcon.vue.js";
16
+ import { fmtDateTime as E } from "../../utils/format.js";
17
+ import { uaSummary as _t } from "../../utils/ua.js";
18
+ const kt = { class: "view" }, ft = { class: "banner" }, bt = { class: "banner-body" }, yt = { class: "hi" }, gt = { class: "tip" }, wt = {
14
19
  key: 0,
15
- class: "quick"
16
- }, ee = { class: "notice-title" }, te = {
20
+ class: "meta-row"
21
+ }, zt = { key: 0 }, Ct = {
22
+ key: 1,
23
+ class: "meta-dot"
24
+ }, qt = { class: "row" }, St = {
25
+ key: 0,
26
+ class: "todo-list"
27
+ }, $t = ["onClick"], Tt = { class: "todo-main" }, xt = { class: "todo-title" }, Nt = {
28
+ key: 0,
29
+ class: "todo-sub"
30
+ }, Lt = { class: "todo-time" }, Pt = { key: 0 }, Dt = {
31
+ key: 0,
32
+ class: "quick-group-label"
33
+ }, It = {
34
+ key: 1,
35
+ class: "quick-grid"
36
+ }, Et = ["aria-label", "onClick"], Mt = ["onClick"], At = { class: "quick-name" }, Bt = {
37
+ key: 2,
38
+ class: "quick-group-label"
39
+ }, Ut = {
40
+ key: 3,
41
+ class: "quick-grid"
42
+ }, Vt = ["aria-label", "onClick"], Rt = ["onClick"], Ft = { class: "quick-name" }, Gt = { class: "notice-title" }, jt = {
17
43
  key: 0,
18
44
  class: "dot"
19
- }, oe = { class: "notice-time" }, _e = /* @__PURE__ */ B({
45
+ }, Ht = { class: "notice-time" }, re = /* @__PURE__ */ W({
20
46
  __name: "biz",
21
- setup(ie) {
22
- const { t: c } = M(), v = U(), S = j(), x = H(), I = F(), f = G(), q = b(() => ({ background: J(x.accent) }));
23
- function k(m) {
24
- var l;
25
- const i = [];
26
- for (const n of m)
27
- n.visible && ((l = n.children) != null && l.length ? i.push(...k(n.children)) : n.path && i.push(n));
28
- return i;
47
+ setup(Jt) {
48
+ const { t: a } = nt(), M = ut(), H = ct(), A = at(), g = lt(), J = pt(), K = b(() => ({ background: rt(H.accent) })), $ = h(null), m = h(null), B = h(/* @__PURE__ */ new Date());
49
+ let T;
50
+ const w = b(() => {
51
+ if (!$.value) return "";
52
+ const { hours: i, minutes: u } = vt($.value, B.value);
53
+ return i > 0 ? a("biz.onlineDuration", { hours: i, minutes: u }) : a("biz.onlineDurationShort", { minutes: u });
54
+ }), x = b(() => {
55
+ if (!m.value) return a("biz.firstLogin");
56
+ const i = _t(m.value.userAgent), u = m.value.ip ? ` · ${m.value.ip}` : "";
57
+ return `${a("biz.lastLogin", { time: E(m.value.time, { seconds: !1 }) })}${u}${i ? ` · ${i}` : ""}`;
58
+ }), p = h(null), z = h([]), N = h(!1), v = b(
59
+ () => mt(
60
+ z.value.filter((i) => i.menuPath !== A.path),
61
+ J.value
62
+ )
63
+ ), O = b(() => z.value.filter((i) => i.pinned).map((i) => i.menuPath));
64
+ async function L() {
65
+ try {
66
+ z.value = await _.shortcuts();
67
+ } catch {
68
+ z.value = [];
69
+ }
29
70
  }
30
- const y = b(() => k(S.menuTree).filter((m) => m.path !== I.path)), h = T([]);
31
- return V(async () => {
71
+ async function U(i) {
72
+ await _.pinShortcut(i), await L();
73
+ }
74
+ async function Q(i) {
75
+ await _.unpinShortcut(i), await L();
76
+ }
77
+ const P = h([]);
78
+ return X(async () => {
79
+ var i;
80
+ T = setInterval(() => {
81
+ B.value = /* @__PURE__ */ new Date();
82
+ }, 6e4);
83
+ try {
84
+ const u = await _.sessions();
85
+ $.value = ((i = u.find((k) => k.isCurrent)) == null ? void 0 : i.loginTime) ?? null;
86
+ } catch {
87
+ }
32
88
  try {
33
- h.value = (await K.mine({ page: 1, pageSize: 5 })).items;
89
+ m.value = await _.lastLogin();
34
90
  } catch {
91
+ m.value = null;
35
92
  }
36
- }), (m, i) => {
37
- var l, n;
38
- return o(), u("div", Q, [
39
- s("div", W, [
40
- s("div", {
93
+ try {
94
+ p.value = await _.workbenchTodo();
95
+ } catch {
96
+ p.value = null;
97
+ }
98
+ await L();
99
+ try {
100
+ P.value = (await dt.mine({ page: 1, pageSize: 5 })).items;
101
+ } catch {
102
+ }
103
+ }), Y(() => {
104
+ T && clearInterval(T);
105
+ }), (i, u) => {
106
+ var k, V;
107
+ return o(), s("div", kt, [
108
+ n("div", ft, [
109
+ n("div", {
41
110
  class: "avatar",
42
- style: $(q.value)
111
+ style: Z(K.value)
43
112
  }, [
44
- d(e(D), {
113
+ c(t(S), {
45
114
  icon: "ph:user",
46
115
  width: 26,
47
116
  color: "#fff"
48
117
  })
49
118
  ], 4),
50
- s("div", null, [
51
- s("div", X, a(e(c)("biz.welcome", { name: ((l = e(v).userInfo) == null ? void 0 : l.name) ?? ((n = e(v).userInfo) == null ? void 0 : n.account) ?? "" })), 1),
52
- s("div", Y, a(e(c)("biz.subtitle")), 1)
119
+ n("div", bt, [
120
+ n("div", yt, l(t(a)("biz.welcome", { name: ((k = t(M).userInfo) == null ? void 0 : k.name) ?? ((V = t(M).userInfo) == null ? void 0 : V.account) ?? "" })), 1),
121
+ n("div", gt, l(t(a)("biz.subtitle")), 1),
122
+ w.value || x.value ? (o(), s("div", wt, [
123
+ w.value ? (o(), s("span", zt, l(w.value), 1)) : r("", !0),
124
+ w.value && x.value ? (o(), s("span", Ct)) : r("", !0),
125
+ n("span", null, l(x.value), 1)
126
+ ])) : r("", !0)
53
127
  ])
54
128
  ]),
55
- d(e(g), {
56
- title: e(c)("biz.quick"),
57
- bordered: !0
58
- }, {
59
- default: r(() => [
60
- y.value.length ? (o(), u("div", Z, [
61
- (o(!0), u(z, null, N(y.value, (t) => (o(), p(e(w), {
62
- key: t.id,
63
- secondary: "",
64
- onClick: (A) => e(f).push(t.path)
129
+ n("div", qt, [
130
+ c(t(D), {
131
+ title: t(a)("biz.todo"),
132
+ bordered: !0
133
+ }, tt({
134
+ default: d(() => [
135
+ p.value && p.value.items.length ? (o(), s("div", St, [
136
+ (o(!0), s(C, null, q(p.value.items, (e) => (o(), s("div", {
137
+ key: e.id,
138
+ class: "todo-row",
139
+ onClick: (f) => e.url && t(g).push(e.url)
140
+ }, [
141
+ n("div", Tt, [
142
+ n("div", xt, l(e.title), 1),
143
+ e.description ? (o(), s("div", Nt, l(e.description), 1)) : r("", !0)
144
+ ]),
145
+ n("span", Lt, l(t(E)(e.createTime, { seconds: !1 })), 1)
146
+ ], 8, $t))), 128))
147
+ ])) : (o(), y(t(I), {
148
+ key: 1,
149
+ description: t(a)("biz.todoEmpty")
150
+ }, null, 8, ["description"]))
151
+ ]),
152
+ _: 2
153
+ }, [
154
+ p.value && p.value.totalCount > 0 ? {
155
+ name: "header-extra",
156
+ fn: d(() => [
157
+ c(t(et), {
158
+ type: "error",
159
+ round: "",
160
+ size: "small"
161
+ }, {
162
+ default: d(() => [
163
+ R(l(p.value.totalCount), 1)
164
+ ]),
165
+ _: 1
166
+ })
167
+ ]),
168
+ key: "0"
169
+ } : void 0
170
+ ]), 1032, ["title"]),
171
+ c(t(D), {
172
+ title: t(a)("biz.quick"),
173
+ bordered: !0
174
+ }, {
175
+ "header-extra": d(() => [
176
+ c(t(ot), {
177
+ size: "small",
178
+ quaternary: "",
179
+ circle: "",
180
+ onClick: u[0] || (u[0] = (e) => N.value = !0)
65
181
  }, {
66
- icon: r(() => [
67
- d(O, {
68
- icon: t.icon || "ph:squares-four",
69
- size: 16
70
- }, null, 8, ["icon"])
71
- ]),
72
- default: r(() => [
73
- _(" " + a(t.title), 1)
182
+ icon: d(() => [
183
+ c(t(S), {
184
+ icon: "ph:plus",
185
+ width: 16
186
+ })
74
187
  ]),
75
- _: 2
76
- }, 1032, ["onClick"]))), 128))
77
- ])) : (o(), p(e(C), {
78
- key: 1,
79
- description: e(c)("biz.noMenus")
80
- }, null, 8, ["description"]))
81
- ]),
82
- _: 1
83
- }, 8, ["title"]),
84
- d(e(g), {
85
- title: e(c)("biz.notices"),
188
+ _: 1
189
+ })
190
+ ]),
191
+ default: d(() => [
192
+ v.value.pinned.length || v.value.suggested.length ? (o(), s("div", Pt, [
193
+ v.value.pinned.length ? (o(), s("div", Dt, l(t(a)("biz.quickPinned")), 1)) : r("", !0),
194
+ v.value.pinned.length ? (o(), s("div", It, [
195
+ (o(!0), s(C, null, q(v.value.pinned, (e) => (o(), s("div", {
196
+ key: e.path,
197
+ class: "quick-item"
198
+ }, [
199
+ n("button", {
200
+ class: "quick-star active",
201
+ type: "button",
202
+ "aria-label": t(a)("biz.quickUnpin"),
203
+ onClick: F((f) => Q(e.path), ["stop"])
204
+ }, [
205
+ c(t(S), {
206
+ icon: "ph:star-fill",
207
+ width: 12
208
+ })
209
+ ], 8, Et),
210
+ n("div", {
211
+ class: "quick-body",
212
+ onClick: (f) => t(g).push(e.path)
213
+ }, [
214
+ c(j, {
215
+ icon: e.icon,
216
+ size: 17
217
+ }, null, 8, ["icon"]),
218
+ n("span", At, l(t(G)(e.title)), 1)
219
+ ], 8, Mt)
220
+ ]))), 128))
221
+ ])) : r("", !0),
222
+ v.value.suggested.length ? (o(), s("div", Bt, l(t(a)("biz.quickSuggested")), 1)) : r("", !0),
223
+ v.value.suggested.length ? (o(), s("div", Ut, [
224
+ (o(!0), s(C, null, q(v.value.suggested, (e) => (o(), s("div", {
225
+ key: e.path,
226
+ class: "quick-item suggested"
227
+ }, [
228
+ n("button", {
229
+ class: "quick-star",
230
+ type: "button",
231
+ "aria-label": t(a)("biz.quickPin"),
232
+ onClick: F((f) => U(e.path), ["stop"])
233
+ }, [
234
+ c(t(S), {
235
+ icon: "ph:star",
236
+ width: 12
237
+ })
238
+ ], 8, Vt),
239
+ n("div", {
240
+ class: "quick-body",
241
+ onClick: (f) => t(g).push(e.path)
242
+ }, [
243
+ c(j, {
244
+ icon: e.icon,
245
+ size: 17
246
+ }, null, 8, ["icon"]),
247
+ n("span", Ft, l(t(G)(e.title)), 1)
248
+ ], 8, Rt)
249
+ ]))), 128))
250
+ ])) : r("", !0)
251
+ ])) : (o(), y(t(I), {
252
+ key: 1,
253
+ description: t(a)("biz.noMenus")
254
+ }, null, 8, ["description"]))
255
+ ]),
256
+ _: 1
257
+ }, 8, ["title"])
258
+ ]),
259
+ c(t(D), {
260
+ title: t(a)("biz.notices"),
86
261
  bordered: !0
87
262
  }, {
88
- "header-extra": r(() => [
89
- d(e(w), {
90
- text: "",
91
- type: "primary",
92
- onClick: i[0] || (i[0] = (t) => e(f).push("/personal/notice"))
93
- }, {
94
- default: r(() => [
95
- _(a(e(c)("biz.viewAll")), 1)
96
- ]),
97
- _: 1
98
- })
99
- ]),
100
- default: r(() => [
101
- h.value.length ? (o(), p(e(L), {
263
+ default: d(() => [
264
+ P.value.length ? (o(), y(t(it), {
102
265
  key: 0,
103
266
  "show-divider": !1
104
267
  }, {
105
- default: r(() => [
106
- (o(!0), u(z, null, N(h.value, (t) => (o(), p(e(R), {
107
- key: t.id
268
+ default: d(() => [
269
+ (o(!0), s(C, null, q(P.value, (e) => (o(), y(t(st), {
270
+ key: e.id
108
271
  }, {
109
- default: r(() => [
110
- s("div", {
272
+ default: d(() => [
273
+ n("div", {
111
274
  class: "notice-row",
112
- onClick: i[1] || (i[1] = (A) => e(f).push("/personal/notice"))
275
+ onClick: u[1] || (u[1] = (f) => t(g).push("/personal/notice"))
113
276
  }, [
114
- s("span", ee, [
115
- t.isRead ? E("", !0) : (o(), u("span", te)),
116
- _(" " + a(t.title), 1)
277
+ n("span", Gt, [
278
+ e.isRead ? r("", !0) : (o(), s("span", jt)),
279
+ R(" " + l(e.title), 1)
117
280
  ]),
118
- s("span", oe, a(e(P)(t.publishTime, { seconds: !1 })), 1)
281
+ n("span", Ht, l(t(E)(e.publishTime, { seconds: !1 })), 1)
119
282
  ])
120
283
  ]),
121
284
  _: 2
122
285
  }, 1024))), 128))
123
286
  ]),
124
287
  _: 1
125
- })) : (o(), p(e(C), { key: 1 }))
288
+ })) : (o(), y(t(I), {
289
+ key: 1,
290
+ description: t(a)("biz.noticesEmpty")
291
+ }, null, 8, ["description"]))
126
292
  ]),
127
293
  _: 1
128
- }, 8, ["title"])
294
+ }, 8, ["title"]),
295
+ c(ht, {
296
+ show: N.value,
297
+ "onUpdate:show": u[2] || (u[2] = (e) => N.value = e),
298
+ "exclude-paths": [...O.value, t(A).path],
299
+ onPick: U
300
+ }, null, 8, ["show", "exclude-paths"])
129
301
  ]);
130
302
  };
131
303
  }
132
304
  });
133
305
  export {
134
- _e as default
306
+ re as default
135
307
  };
136
308
  //# sourceMappingURL=biz2.vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"biz2.vue.js","sources":["../../../src/views/dashboard/biz.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 业务系统的工作台。每个应用一个首页组件——这里和 dashboard/workbench.vue 是两个独立页面,\n// 由各自应用的「工作台」菜单(component 字段)指向,切应用即换首页。\n// 全部真数据拼装:欢迎卡(user store)+ 快捷入口(当前应用菜单树的可见叶子)+ 我的通知(notice/mine)。\n// 应用要换成自己的业务统计,在自己的 views/ 下放同 key(dashboard/biz)的页面覆盖本页;别造写死的假数字。\nimport { computed, onMounted, ref } from 'vue'\nimport { NCard, NButton, NList, NListItem, NEmpty } from 'naive-ui'\nimport { Icon } from '@iconify/vue'\nimport { useI18n } from 'vue-i18n'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useUserStore } from '#/stores/user'\nimport { useAuthStore } from '#/stores/auth'\nimport { useAppStore } from '#/stores/app'\nimport { btnGrad } from '#/theme/mix'\nimport { noticeApi } from '#/api'\nimport type { NoticeMineItem } from '#/types/api'\nimport type { MenuNode } from '#/types/menu'\nimport AppIcon from '#/components/AppIcon.vue'\nimport { fmtDateTime } from '#/utils/format'\n\nconst { t } = useI18n()\nconst user = useUserStore()\nconst auth = useAuthStore()\nconst app = useAppStore()\nconst route = useRoute()\nconst router = useRouter()\n\nconst avatarStyle = computed(() => ({ background: btnGrad(app.accent) }))\n\n/** 当前应用菜单树的可见叶子(带 path)= 快捷入口;排除本页自身。 */\nfunction leaves(tree: MenuNode[]): MenuNode[] {\n const out: MenuNode[] = []\n for (const n of tree) {\n if (!n.visible) continue\n if (n.children?.length) out.push(...leaves(n.children))\n else if (n.path) out.push(n)\n }\n return out\n}\nconst quick = computed(() => leaves(auth.menuTree).filter(n => n.path !== route.path))\n\nconst notices = ref<NoticeMineItem[]>([])\nonMounted(async () => {\n try {\n notices.value = (await noticeApi.mine({ page: 1, pageSize: 5 })).items\n } catch {\n // 首页不因一张卡挂了糊用户一脸红(同 workbench):通知取不到就空态,其余照常可用。\n }\n})\n</script>\n\n<template>\n <div class=\"view\">\n <!-- 欢迎横幅(与 workbench 同语法:btnGrad 头像 + 主副标题) -->\n <div class=\"banner\">\n <div class=\"avatar\" :style=\"avatarStyle\">\n <Icon icon=\"ph:user\" :width=\"26\" color=\"#fff\" />\n </div>\n <div>\n <div class=\"hi\">\n {{ t('biz.welcome', { name: user.userInfo?.name ?? user.userInfo?.account ?? '' }) }}\n </div>\n <div class=\"tip\">{{ t('biz.subtitle') }}</div>\n </div>\n </div>\n\n <n-card :title=\"t('biz.quick')\" :bordered=\"true\">\n <div v-if=\"quick.length\" class=\"quick\">\n <n-button v-for=\"m in quick\" :key=\"m.id\" secondary @click=\"router.push(m.path!)\">\n <template #icon><AppIcon :icon=\"m.icon || 'ph:squares-four'\" :size=\"16\" /></template>\n {{ m.title }}\n </n-button>\n </div>\n <n-empty v-else :description=\"t('biz.noMenus')\" />\n </n-card>\n\n <n-card :title=\"t('biz.notices')\" :bordered=\"true\">\n <template #header-extra>\n <n-button text type=\"primary\" @click=\"router.push('/personal/notice')\">\n {{ t('biz.viewAll') }}\n </n-button>\n </template>\n <n-list v-if=\"notices.length\" :show-divider=\"false\">\n <n-list-item v-for=\"n in notices\" :key=\"n.id\">\n <div class=\"notice-row\" @click=\"router.push('/personal/notice')\">\n <span class=\"notice-title\">\n <span v-if=\"!n.isRead\" class=\"dot\" />\n {{ n.title }}\n </span>\n <span class=\"notice-time\">{{ fmtDateTime(n.publishTime, { seconds: false }) }}</span>\n </div>\n </n-list-item>\n </n-list>\n <n-empty v-else />\n </n-card>\n </div>\n</template>\n\n<style scoped>\n.view {\n display: flex;\n flex-direction: column;\n gap: var(--gap-card);\n}\n.banner {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 24px;\n border-radius: var(--radius-lg);\n background: var(--color-primary-light);\n}\n.avatar {\n width: 52px;\n height: 52px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.hi {\n font-size: var(--font-size-lg);\n font-weight: 600;\n color: var(--color-text-primary);\n}\n.tip {\n color: var(--color-text-secondary);\n margin-top: 4px;\n}\n.quick {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n}\n.notice-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n cursor: pointer;\n}\n.notice-title {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-text-primary);\n}\n.dot {\n flex-shrink: 0;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--color-primary);\n}\n.notice-time {\n flex-shrink: 0;\n color: var(--color-text-secondary);\n font-size: 12px;\n}\n</style>\n"],"names":["t","useI18n","user","useUserStore","auth","useAuthStore","app","useAppStore","route","useRoute","router","useRouter","avatarStyle","computed","btnGrad","leaves","tree","out","_a","quick","n","notices","ref","onMounted","noticeApi","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","_unref","Icon","_hoisted_3","_toDisplayString","_hoisted_4","NCard","_hoisted_5","_Fragment","_renderList","m","_createBlock","NButton","AppIcon","NEmpty","_cache","$event","NList","NListItem","_hoisted_6","_hoisted_7","_hoisted_8"],"mappings":"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"}
1
+ {"version":3,"file":"biz2.vue.js","sources":["../../../src/views/dashboard/biz.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 业务系统的工作台。每个应用一个首页组件——这里和 dashboard/workbench.vue 是两个独立页面,\n// 由各自应用的「工作台」菜单(component 字段)指向,切应用即换首页。\n// 全部真数据拼装:欢迎横幅(user store + sessions + last-login)+ 待办事项(工作台待办扩展点,\n// 内核默认空)+ 快捷方式(手动置顶 + 高频自动补位)+ 我的通知(notice/mine)。\n// 应用要换成自己的业务统计,在自己的 views/ 下放同 key(dashboard/biz)的页面覆盖本页;别造写死的假数字。\nimport { computed, onMounted, onUnmounted, ref } from 'vue'\nimport { NCard, NButton, NList, NListItem, NEmpty, NTag } from 'naive-ui'\nimport { Icon } from '@iconify/vue'\nimport { useI18n } from 'vue-i18n'\nimport { useRoute, useRouter } from 'vue-router'\nimport { useUserStore } from '#/stores/user'\nimport { useAppStore } from '#/stores/app'\nimport { btnGrad } from '#/theme/mix'\nimport { noticeApi, personalApi } from '#/api'\nimport type {\n NoticeMineItem,\n LastLoginInfo,\n WorkbenchTodoSummary,\n UserShortcutItem,\n} from '#/types/api'\nimport { useMenuFlat } from '#/composables/useMenuFlat'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nimport { onlineDurationParts } from './onlineDuration'\nimport { buildShortcutGroups } from './shortcutGroups'\nimport ShortcutPicker from './ShortcutPicker.vue'\nimport AppIcon from '#/components/AppIcon.vue'\nimport { fmtDateTime } from '#/utils/format'\nimport { uaSummary } from '#/utils/ua'\n\nconst { t } = useI18n()\nconst user = useUserStore()\nconst app = useAppStore()\nconst route = useRoute()\nconst router = useRouter()\nconst leaves = useMenuFlat()\n\nconst avatarStyle = computed(() => ({ background: btnGrad(app.accent) }))\n\n// ── 在线时长 + 上次登录 ─────────────────────────────────────\nconst currentLoginTime = ref<string | null>(null)\nconst lastLogin = ref<LastLoginInfo | null>(null)\nconst now = ref(new Date())\nlet tickTimer: ReturnType<typeof setInterval> | undefined\n\nconst onlineText = computed(() => {\n if (!currentLoginTime.value) return ''\n const { hours, minutes } = onlineDurationParts(currentLoginTime.value, now.value)\n return hours > 0\n ? t('biz.onlineDuration', { hours, minutes })\n : t('biz.onlineDurationShort', { minutes })\n})\nconst lastLoginText = computed(() => {\n if (!lastLogin.value) return t('biz.firstLogin')\n const device = uaSummary(lastLogin.value.userAgent)\n const ip = lastLogin.value.ip ? ` · ${lastLogin.value.ip}` : ''\n const base = t('biz.lastLogin', { time: fmtDateTime(lastLogin.value.time, { seconds: false }) })\n return `${base}${ip}${device ? ` · ${device}` : ''}`\n})\n\n// ── 待办事项:内核默认空,消费方接入真实审批/工单系统后有内容 ──────────\nconst todo = ref<WorkbenchTodoSummary | null>(null)\n\n// ── 快捷方式 ─────────────────────────────────────────────\nconst shortcuts = ref<UserShortcutItem[]>([])\nconst pickerShow = ref(false)\n// 排除本页自身:工作台自己也是菜单树里的一条,不该出现在\"去哪儿\"的快捷方式里\nconst groups = computed(() =>\n buildShortcutGroups(\n shortcuts.value.filter(s => s.menuPath !== route.path),\n leaves.value,\n ),\n)\nconst pinnedPaths = computed(() => shortcuts.value.filter(s => s.pinned).map(s => s.menuPath))\n\nasync function loadShortcuts() {\n try {\n shortcuts.value = await personalApi.shortcuts()\n } catch {\n shortcuts.value = []\n }\n}\nasync function pin(path: string) {\n await personalApi.pinShortcut(path)\n await loadShortcuts()\n}\nasync function unpin(path: string) {\n await personalApi.unpinShortcut(path)\n await loadShortcuts()\n}\n\n// ── 通知 ────────────────────────────────────────────────\nconst notices = ref<NoticeMineItem[]>([])\n\nonMounted(async () => {\n tickTimer = setInterval(() => {\n now.value = new Date()\n }, 60_000)\n\n try {\n const sessions = await personalApi.sessions()\n currentLoginTime.value = sessions.find(s => s.isCurrent)?.loginTime ?? null\n } catch {\n // 在线时长拿不到就不显示这一段,不影响页面其余部分\n }\n try {\n lastLogin.value = await personalApi.lastLogin()\n } catch {\n lastLogin.value = null\n }\n try {\n todo.value = await personalApi.workbenchTodo()\n } catch {\n todo.value = null\n }\n await loadShortcuts()\n try {\n notices.value = (await noticeApi.mine({ page: 1, pageSize: 5 })).items\n } catch {\n // 首页不因一张卡挂了糊用户一脸红:通知取不到就空态,其余照常可用。\n }\n})\nonUnmounted(() => {\n if (tickTimer) clearInterval(tickTimer)\n})\n</script>\n\n<template>\n <div class=\"view\">\n <!-- 欢迎横幅:身份 + 在线状态,一行说完 -->\n <div class=\"banner\">\n <div class=\"avatar\" :style=\"avatarStyle\">\n <Icon icon=\"ph:user\" :width=\"26\" color=\"#fff\" />\n </div>\n <div class=\"banner-body\">\n <div class=\"hi\">\n {{ t('biz.welcome', { name: user.userInfo?.name ?? user.userInfo?.account ?? '' }) }}\n </div>\n <div class=\"tip\">{{ t('biz.subtitle') }}</div>\n <div v-if=\"onlineText || lastLoginText\" class=\"meta-row\">\n <span v-if=\"onlineText\">{{ onlineText }}</span>\n <span v-if=\"onlineText && lastLoginText\" class=\"meta-dot\" />\n <span>{{ lastLoginText }}</span>\n </div>\n </div>\n </div>\n\n <div class=\"row\">\n <!-- 待办事项:内核默认空,消费方接入真实审批/工单系统后有内容 -->\n <n-card :title=\"t('biz.todo')\" :bordered=\"true\">\n <template v-if=\"todo && todo.totalCount > 0\" #header-extra>\n <n-tag type=\"error\" round size=\"small\">{{ todo.totalCount }}</n-tag>\n </template>\n <div v-if=\"todo && todo.items.length\" class=\"todo-list\">\n <div\n v-for=\"i in todo.items\"\n :key=\"i.id\"\n class=\"todo-row\"\n @click=\"i.url && router.push(i.url)\"\n >\n <div class=\"todo-main\">\n <div class=\"todo-title\">{{ i.title }}</div>\n <div v-if=\"i.description\" class=\"todo-sub\">{{ i.description }}</div>\n </div>\n <span class=\"todo-time\">{{ fmtDateTime(i.createTime, { seconds: false }) }}</span>\n </div>\n </div>\n <n-empty v-else :description=\"t('biz.todoEmpty')\" />\n </n-card>\n\n <!-- 快捷方式:实心星=手动置顶(可移除),虚线框+空心星=高频访问自动推荐(点固定即置顶) -->\n <n-card :title=\"t('biz.quick')\" :bordered=\"true\">\n <template #header-extra>\n <n-button size=\"small\" quaternary circle @click=\"pickerShow = true\">\n <template #icon><Icon icon=\"ph:plus\" :width=\"16\" /></template>\n </n-button>\n </template>\n <div v-if=\"groups.pinned.length || groups.suggested.length\">\n <div v-if=\"groups.pinned.length\" class=\"quick-group-label\">\n {{ t('biz.quickPinned') }}\n </div>\n <div v-if=\"groups.pinned.length\" class=\"quick-grid\">\n <div v-for=\"s in groups.pinned\" :key=\"s.path\" class=\"quick-item\">\n <button\n class=\"quick-star active\"\n type=\"button\"\n :aria-label=\"t('biz.quickUnpin')\"\n @click.stop=\"unpin(s.path)\"\n >\n <Icon icon=\"ph:star-fill\" :width=\"12\" />\n </button>\n <div class=\"quick-body\" @click=\"router.push(s.path)\">\n <AppIcon :icon=\"s.icon\" :size=\"17\" />\n <span class=\"quick-name\">{{ translateMenuTitle(s.title) }}</span>\n </div>\n </div>\n </div>\n <div v-if=\"groups.suggested.length\" class=\"quick-group-label\">\n {{ t('biz.quickSuggested') }}\n </div>\n <div v-if=\"groups.suggested.length\" class=\"quick-grid\">\n <div v-for=\"s in groups.suggested\" :key=\"s.path\" class=\"quick-item suggested\">\n <button\n class=\"quick-star\"\n type=\"button\"\n :aria-label=\"t('biz.quickPin')\"\n @click.stop=\"pin(s.path)\"\n >\n <Icon icon=\"ph:star\" :width=\"12\" />\n </button>\n <div class=\"quick-body\" @click=\"router.push(s.path)\">\n <AppIcon :icon=\"s.icon\" :size=\"17\" />\n <span class=\"quick-name\">{{ translateMenuTitle(s.title) }}</span>\n </div>\n </div>\n </div>\n </div>\n <n-empty v-else :description=\"t('biz.noMenus')\" />\n </n-card>\n </div>\n\n <!-- 通知:没有\"查看全部\"——卡片本身就是完整内容 -->\n <n-card :title=\"t('biz.notices')\" :bordered=\"true\">\n <n-list v-if=\"notices.length\" :show-divider=\"false\">\n <n-list-item v-for=\"n in notices\" :key=\"n.id\">\n <div class=\"notice-row\" @click=\"router.push('/personal/notice')\">\n <span class=\"notice-title\">\n <span v-if=\"!n.isRead\" class=\"dot\" />\n {{ n.title }}\n </span>\n <span class=\"notice-time\">{{ fmtDateTime(n.publishTime, { seconds: false }) }}</span>\n </div>\n </n-list-item>\n </n-list>\n <n-empty v-else :description=\"t('biz.noticesEmpty')\" />\n </n-card>\n\n <ShortcutPicker\n v-model:show=\"pickerShow\"\n :exclude-paths=\"[...pinnedPaths, route.path]\"\n @pick=\"pin\"\n />\n </div>\n</template>\n\n<style scoped>\n.view {\n display: flex;\n flex-direction: column;\n gap: var(--gap-card);\n}\n.banner {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 24px;\n border-radius: var(--radius-lg);\n background: var(--color-primary-light);\n}\n.avatar {\n flex-shrink: 0;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.banner-body {\n min-width: 0;\n}\n.hi {\n font-size: var(--font-size-lg);\n font-weight: 600;\n color: var(--color-text-primary);\n}\n.tip {\n color: var(--color-text-secondary);\n margin-top: 4px;\n}\n.meta-row {\n margin-top: 8px;\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 13px;\n color: var(--color-text-tertiary);\n font-variant-numeric: tabular-nums;\n}\n.meta-dot {\n width: 3px;\n height: 3px;\n border-radius: 50%;\n background: var(--color-text-disabled);\n flex-shrink: 0;\n}\n.row {\n display: grid;\n grid-template-columns: 1.35fr 1fr;\n gap: var(--gap-card);\n align-items: start;\n}\n@media (max-width: 760px) {\n .row {\n grid-template-columns: 1fr;\n }\n}\n.todo-list {\n display: flex;\n flex-direction: column;\n}\n.todo-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n padding: 10px 0;\n border-bottom: 1px solid var(--color-border);\n cursor: pointer;\n}\n.todo-row:last-child {\n border-bottom: none;\n}\n.todo-title {\n color: var(--color-text-primary);\n font-weight: 500;\n}\n.todo-sub {\n margin-top: 2px;\n font-size: 12px;\n color: var(--color-text-tertiary);\n}\n.todo-time {\n flex-shrink: 0;\n font-size: 12px;\n color: var(--color-text-tertiary);\n}\n.quick-group-label {\n font-size: 12px;\n color: var(--color-text-tertiary);\n margin: 4px 0 8px;\n}\n.quick-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-bottom: 4px;\n}\n.quick-item {\n position: relative;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n.quick-item.suggested {\n border-style: dashed;\n}\n.quick-body {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px 8px 10px;\n cursor: pointer;\n}\n.quick-name {\n font-size: 13px;\n color: var(--color-text-secondary);\n}\n.quick-star {\n position: absolute;\n top: -6px;\n right: -6px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: 1px solid var(--color-border);\n background: var(--color-bg-elevated);\n color: var(--color-text-disabled);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n}\n.quick-star.active {\n color: var(--color-warning);\n}\n.notice-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n cursor: pointer;\n}\n.notice-title {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--color-text-primary);\n}\n.dot {\n flex-shrink: 0;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--color-primary);\n}\n.notice-time {\n flex-shrink: 0;\n color: var(--color-text-secondary);\n font-size: 12px;\n}\n</style>\n"],"names":["t","useI18n","user","useUserStore","app","useAppStore","route","useRoute","router","useRouter","leaves","useMenuFlat","avatarStyle","computed","btnGrad","currentLoginTime","ref","lastLogin","now","tickTimer","onlineText","hours","minutes","onlineDurationParts","lastLoginText","device","uaSummary","ip","fmtDateTime","todo","shortcuts","pickerShow","groups","buildShortcutGroups","s","pinnedPaths","loadShortcuts","personalApi","pin","path","unpin","notices","onMounted","sessions","_a","noticeApi","onUnmounted","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","_unref","Icon","_hoisted_3","_hoisted_4","_toDisplayString","_hoisted_5","_hoisted_6","_hoisted_8","_hoisted_9","NCard","_hoisted_10","_Fragment","_renderList","i","$event","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_createBlock","NEmpty","NTag","_createTextVNode","NButton","_hoisted_16","_hoisted_17","_hoisted_18","_withModifiers","AppIcon","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_26","NList","n","NListItem","_cache","_hoisted_27","_hoisted_28","_hoisted_29","ShortcutPicker"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** 在线时长拆成小时/分钟,供 i18n 模板插值;不做负数(时钟回拨/服务器时间误差时钳到 0)。 */
2
+ export declare function onlineDurationParts(loginTime: string, now?: Date): {
3
+ hours: number;
4
+ minutes: number;
5
+ };