@veltra/desktop 1.2.5 → 1.2.7

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 (38) hide show
  1. package/dist/components/action/action-group.vue.d.ts +1 -1
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/dual-nav/dual-nav-app.js +29 -8
  4. package/dist/components/dual-nav/dual-nav-app.js.map +1 -1
  5. package/dist/components/dual-nav/dual-nav.js +10 -7
  6. package/dist/components/dual-nav/dual-nav.js.map +1 -1
  7. package/dist/components/dual-nav/helper.js +6 -1
  8. package/dist/components/dual-nav/helper.js.map +1 -1
  9. package/dist/components/dual-nav/style.js +1 -0
  10. package/dist/components/dual-nav/style2.css +29 -2
  11. package/dist/components/empty/empty.vue.d.ts +1 -1
  12. package/dist/components/input/input.vue.d.ts +4 -4
  13. package/dist/components/loading/loading.js +1 -1
  14. package/dist/components/loading/loading.js.map +1 -1
  15. package/dist/components/loading/style2.css +61 -81
  16. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
  17. package/dist/components/number/number.vue.d.ts +1 -1
  18. package/dist/components/progress-nodes/progress-nodes.vue.d.ts +1 -1
  19. package/dist/components/scroll/scroll.vue.d.ts +2 -2
  20. package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
  21. package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
  22. package/dist/components/tabs/tabs.vue.d.ts +2 -2
  23. package/dist/components/tree/tree.vue.d.ts +1 -1
  24. package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
  25. package/dist/types/dual-nav.d.ts +1 -1
  26. package/dist/types/loading.d.ts +1 -1
  27. package/dist/types/nav.d.ts +1 -1
  28. package/package.json +6 -6
  29. package/src/components/dual-nav/dual-nav-app.vue +34 -6
  30. package/src/components/dual-nav/dual-nav.vue +10 -6
  31. package/src/components/dual-nav/helper.ts +7 -1
  32. package/src/components/dual-nav/style.scss +35 -2
  33. package/src/components/dual-nav/style.ts +1 -0
  34. package/src/components/loading/loading.vue +1 -1
  35. package/src/components/loading/style.scss +68 -104
  36. package/src/types/dual-nav.ts +1 -1
  37. package/src/types/loading.ts +1 -1
  38. package/src/types/nav.ts +1 -1
@@ -7,9 +7,9 @@ type __VLS_Slots = {
7
7
  };
8
8
  declare const __VLS_base: import("vue").DefineComponent<ActionGroupProps, _ActionGroupExposed, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ActionGroupProps> & Readonly<{}>, {
9
9
  type: import("@veltra/utils").ColorType;
10
+ text: boolean;
10
11
  size: "small" | "default" | "large";
11
12
  circle: boolean;
12
- text: boolean;
13
13
  max: number;
14
14
  loading: boolean;
15
15
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -13,9 +13,9 @@ declare const __VLS_base: import("vue").DefineComponent<ActionProps, {}, {}, {},
13
13
  onRun?: (() => any) | undefined;
14
14
  }>, {
15
15
  type: ButtonType;
16
+ text: boolean;
16
17
  size: import("@veltra/utils").ComponentSize;
17
18
  circle: boolean;
18
- text: boolean;
19
19
  loading: boolean;
20
20
  inDropdown: boolean;
21
21
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,7 +1,8 @@
1
1
  import _sfc_main$1 from "../tip/tip.js";
2
2
  import _sfc_main$2 from "../nav/nav-icon.js";
3
- import { createBlock, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, openBlock, toDisplayString, unref, withCtx } from "vue";
3
+ import { createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, shallowRef, toDisplayString, unref, withCtx, withDirectives } from "vue";
4
4
  import { bem } from "@veltra/utils";
5
+ import { vRipple } from "@veltra/directives";
5
6
  //#region src/components/dual-nav/dual-nav-app.vue
6
7
  const _hoisted_1 = ["disabled"];
7
8
  const _sfc_main = /* @__PURE__ */ defineComponent({
@@ -9,27 +10,47 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
9
10
  __name: "dual-nav-app",
10
11
  props: {
11
12
  app: {},
12
- active: { type: Boolean }
13
+ active: { type: Boolean },
14
+ selected: { type: Boolean }
13
15
  },
14
16
  emits: ["click"],
15
17
  setup(__props, { emit: __emit }) {
16
18
  const cls = bem("dual-nav");
17
19
  const emit = __emit;
20
+ const tipVisible = shallowRef(false);
21
+ /** 点击后暂时禁用 hover 弹出,移出触发区后恢复 */
22
+ const tipSuppressed = shallowRef(false);
23
+ function handleClick() {
24
+ tipSuppressed.value = true;
25
+ tipVisible.value = false;
26
+ emit("click");
27
+ }
28
+ function handleMouseleave() {
29
+ tipSuppressed.value = false;
30
+ }
18
31
  return (_ctx, _cache) => {
19
32
  return openBlock(), createBlock(unref(_sfc_main$1), {
33
+ visible: tipVisible.value,
34
+ "onUpdate:visible": _cache[0] || (_cache[0] = ($event) => tipVisible.value = $event),
20
35
  direction: "right",
21
- "hide-arrow": ""
36
+ "hide-arrow": "",
37
+ disabled: tipSuppressed.value
22
38
  }, {
23
- content: withCtx(() => [createTextVNode(toDisplayString(__props.app.title), 1)]),
24
- default: withCtx(() => [createElementVNode("button", {
39
+ content: withCtx(() => [createElementVNode("div", { class: normalizeClass(unref(cls).e("app-tip")) }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("app-tip-title")) }, toDisplayString(__props.app.title), 3), __props.app.description ? (openBlock(), createElementBlock("p", {
40
+ key: 0,
41
+ class: normalizeClass(unref(cls).e("app-tip-description"))
42
+ }, toDisplayString(__props.app.description), 3)) : createCommentVNode("v-if", true)], 2)]),
43
+ default: withCtx(() => [withDirectives((openBlock(), createElementBlock("button", {
25
44
  type: "button",
26
45
  class: normalizeClass([
27
46
  unref(cls).e("app"),
28
47
  unref(bem).is("active", __props.active),
48
+ unref(bem).is("selected", __props.selected),
29
49
  unref(bem).is("disabled", __props.app.disabled ?? false)
30
50
  ]),
31
51
  disabled: __props.app.disabled,
32
- onClick: _cache[0] || (_cache[0] = ($event) => emit("click"))
52
+ onClick: handleClick,
53
+ onMouseleave: handleMouseleave
33
54
  }, [__props.app.icon ? (openBlock(), createBlock(_sfc_main$2, {
34
55
  key: 0,
35
56
  icon: __props.app.icon,
@@ -37,9 +58,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
37
58
  }, null, 8, ["icon", "class"])) : (openBlock(), createElementBlock("span", {
38
59
  key: 1,
39
60
  class: normalizeClass(unref(cls).e("app-fallback"))
40
- }, toDisplayString(__props.app.title[0]), 3))], 10, _hoisted_1)]),
61
+ }, toDisplayString(__props.app.title[0]), 3))], 42, _hoisted_1)), [[unref(vRipple), !__props.app.disabled]])]),
41
62
  _: 1
42
- });
63
+ }, 8, ["visible", "disabled"]);
43
64
  };
44
65
  }
45
66
  });
@@ -1 +1 @@
1
- {"version":3,"file":"dual-nav-app.js","names":[],"sources":["../../../src/components/dual-nav/dual-nav-app.vue"],"sourcesContent":["<template>\n <u-tip direction=\"right\" hide-arrow>\n <button\n type=\"button\"\n :class=\"[cls.e('app'), bem.is('active', active), bem.is('disabled', app.disabled ?? false)]\"\n :disabled=\"app.disabled\"\n @click=\"emit('click')\"\n >\n <UNavIcon v-if=\"app.icon\" :icon=\"app.icon\" :class=\"cls.e('app-icon')\" />\n <span v-else :class=\"cls.e('app-fallback')\">{{ app.title[0] }}</span>\n </button>\n\n <template #content>\n {{ app.title }}\n </template>\n </u-tip>\n</template>\n\n<script setup lang=\"ts\">\nimport { bem } from '@veltra/utils'\n\nimport type { NavItem } from '../../types'\nimport UNavIcon from '../nav/nav-icon.vue'\nimport { UTip } from '../tip'\n\ndefineOptions({ name: 'DualNavApp' })\n\nconst cls = bem('dual-nav')\n\ndefineProps<{ app: NavItem; active: boolean }>()\n\nconst emit = defineEmits<{ click: [] }>()\n</script>\n"],"mappings":";;;;;;;;;;;;;;;EA2BA,MAAM,MAAM,IAAI,UAAU;EAI1B,MAAM,OAAO;;uBA9BX,YAcQ,MAAA,WAAA,GAAA;IAdD,WAAU;IAAQ,cAAA;;IAWZ,SAAO,cACD,CAAA,gBAAA,gBAAZ,QAAA,IAAI,KAAK,GAAA,CAAA,CAAA,CAAA;2BAHL,CART,mBAQS,UAAA;KAPP,MAAK;KACJ,OAAK,eAAA;MAAG,MAAA,GAAA,EAAI,EAAC,KAAA;MAAS,MAAA,GAAA,EAAI,GAAE,UAAW,QAAA,MAAM;MAAG,MAAA,GAAA,EAAI,GAAE,YAAa,QAAA,IAAI,YAAQ,KAAA;KAAA,CAAA;KAC/E,UAAU,QAAA,IAAI;KACd,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;QAEI,QAAA,IAAI,QAAA,UAAA,GAApB,YAAwE,aAAA;;KAA7C,MAAM,QAAA,IAAI;KAAO,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,UAAA,CAAA;oDACxD,mBAAqE,QAAA;;KAAvD,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,cAAA,CAAA;uBAAqB,QAAA,IAAI,MAAK,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,UAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"dual-nav-app.js","names":[],"sources":["../../../src/components/dual-nav/dual-nav-app.vue"],"sourcesContent":["<template>\n <u-tip v-model:visible=\"tipVisible\" direction=\"right\" hide-arrow :disabled=\"tipSuppressed\">\n <button\n type=\"button\"\n v-ripple=\"!app.disabled\"\n :class=\"[\n cls.e('app'),\n bem.is('active', active),\n bem.is('selected', selected),\n bem.is('disabled', app.disabled ?? false)\n ]\"\n :disabled=\"app.disabled\"\n @click=\"handleClick\"\n @mouseleave=\"handleMouseleave\"\n >\n <UNavIcon v-if=\"app.icon\" :icon=\"app.icon\" :class=\"cls.e('app-icon')\" />\n <span v-else :class=\"cls.e('app-fallback')\">{{ app.title[0] }}</span>\n </button>\n\n <template #content>\n <div :class=\"cls.e('app-tip')\">\n <div :class=\"cls.e('app-tip-title')\">{{ app.title }}</div>\n <p v-if=\"app.description\" :class=\"cls.e('app-tip-description')\">\n {{ app.description }}\n </p>\n </div>\n </template>\n </u-tip>\n</template>\n\n<script setup lang=\"ts\">\nimport { vRipple } from '@veltra/directives'\nimport { bem } from '@veltra/utils'\nimport { shallowRef } from 'vue'\n\nimport type { DualNavRootItem } from '../../types'\nimport UNavIcon from '../nav/nav-icon.vue'\nimport { UTip } from '../tip'\n\ndefineOptions({ name: 'DualNavApp' })\n\nconst cls = bem('dual-nav')\n\ndefineProps<{ app: DualNavRootItem; active: boolean; selected: boolean }>()\n\nconst emit = defineEmits<{ click: [] }>()\n\nconst tipVisible = shallowRef(false)\n/** 点击后暂时禁用 hover 弹出,移出触发区后恢复 */\nconst tipSuppressed = shallowRef(false)\n\nfunction handleClick() {\n tipSuppressed.value = true\n tipVisible.value = false\n emit('click')\n}\n\nfunction handleMouseleave() {\n tipSuppressed.value = false\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;EAyCA,MAAM,MAAM,IAAI,UAAU;EAI1B,MAAM,OAAO;EAEb,MAAM,aAAa,WAAW,KAAK;;EAEnC,MAAM,gBAAgB,WAAW,KAAK;EAEtC,SAAS,cAAc;GACrB,cAAc,QAAQ;GACtB,WAAW,QAAQ;GACnB,KAAK,OAAO;EACd;EAEA,SAAS,mBAAmB;GAC1B,cAAc,QAAQ;EACxB;;uBA1DE,YA0BQ,MAAA,WAAA,GAAA;IA1BO,SAAS,WAAA;8DAAA,WAAU,QAAA;IAAE,WAAU;IAAQ,cAAA;IAAY,UAAU,cAAA;;IAkB/D,SAAO,cAMV,CALN,mBAKM,OAAA,EALA,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,SAAA,CAAA,EAAA,GAAA,CAChB,mBAA0D,OAAA,EAApD,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,eAAA,CAAA,EAAA,GAAA,gBAAsB,QAAA,IAAI,KAAK,GAAA,CAAA,GACxC,QAAA,IAAI,eAAA,UAAA,GAAb,mBAEI,KAAA;;KAFuB,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,qBAAA,CAAA;uBAClC,QAAA,IAAI,WAAW,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,CAAA,CAAA,CAAA;2BANf,CAAA,gBAAA,UAAA,GAfT,mBAeS,UAAA;KAdP,MAAK;KAEJ,OAAK,eAAA;MAAY,MAAA,GAAA,EAAI,EAAC,KAAA;MAAiB,MAAA,GAAA,EAAI,GAAE,UAAW,QAAA,MAAM;MAAW,MAAA,GAAA,EAAI,GAAE,YAAa,QAAA,QAAQ;MAAW,MAAA,GAAA,EAAI,GAAE,YAAa,QAAA,IAAI,YAAQ,KAAA;;KAM9I,UAAU,QAAA,IAAI;KACd,SAAO;KACP,cAAY;QAEG,QAAA,IAAI,QAAA,UAAA,GAApB,YAAwE,aAAA;;KAA7C,MAAM,QAAA,IAAI;KAAO,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,UAAA,CAAA;oDACxD,mBAAqE,QAAA;;KAAvD,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,cAAA,CAAA;uBAAqB,QAAA,IAAI,MAAK,EAAA,GAAA,CAAA,EAAA,GAAA,IAAA,UAAA,IAAA,CAAA,CAAA,MAAA,OAAA,GAAA,CAZ7C,QAAA,IAAI,QAAQ,CAAA,CAAA,CAAA,CAAA"}
@@ -3,7 +3,7 @@ import _sfc_main$2 from "../tip/tip.js";
3
3
  import { collectNavBranchPaths } from "../nav/helper.js";
4
4
  import _sfc_main$3 from "../nav/nav.js";
5
5
  import _sfc_main$4 from "./dual-nav-app.js";
6
- import { findRootApp } from "./helper.js";
6
+ import { findFirstLeaf, findRootApp } from "./helper.js";
7
7
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
8
8
  import { bem } from "@veltra/utils";
9
9
  import { Folder, FolderOpened } from "@veltra/icons/normal";
@@ -53,14 +53,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
53
53
  watch(panelAppPath, () => {
54
54
  allExpanded.value = false;
55
55
  });
56
- /** 左轨高亮:仅当 currentPath 落在该应用下时激活 */
56
+ /** 左轨高亮:currentPath 落在该应用下时为激活态 */
57
57
  function isAppActive(app) {
58
58
  return findRootApp(props.menus, props.currentPath)?.path === app.path;
59
59
  }
60
+ /** 左轨选中:已切换右栏但尚未导航到该应用下任一菜单时,使用 hover 风格 */
61
+ function isAppSelected(app) {
62
+ return panelAppPath.value === app.path && !isAppActive(app);
63
+ }
60
64
  function handleAppClick(app) {
61
65
  if (app.disabled) return;
62
66
  panelAppPath.value = app.path;
63
- if (!app.children?.length) emit("item-click", app);
67
+ emit("item-click", findFirstLeaf(app));
64
68
  }
65
69
  function handleToggleExpandAll() {
66
70
  if (allExpanded.value) {
@@ -78,17 +82,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
78
82
  key: app.path,
79
83
  app,
80
84
  active: isAppActive(app),
85
+ selected: isAppSelected(app),
81
86
  onClick: ($event) => handleAppClick(app)
82
87
  }, null, 8, [
83
88
  "app",
84
89
  "active",
90
+ "selected",
85
91
  "onClick"
86
92
  ]);
87
93
  }), 128))], 2),
88
- createElementVNode("div", {
89
- class: normalizeClass(unref(cls).e("divider")),
90
- "aria-hidden": "true"
91
- }, null, 2),
94
+ createCommentVNode(" <div :class=\"cls.e('divider')\" aria-hidden=\"true\" /> "),
92
95
  createElementVNode("div", { class: normalizeClass(unref(cls).e("panel")) }, [panelApp.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("panel-header")) }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("panel-header-main")) }, [createElementVNode("h3", { class: normalizeClass(unref(cls).e("panel-title")) }, toDisplayString(panelApp.value.title), 3), panelApp.value.description ? (openBlock(), createElementBlock("p", {
93
96
  key: 0,
94
97
  class: normalizeClass(unref(cls).e("description"))
@@ -1 +1 @@
1
- {"version":3,"file":"dual-nav.js","names":[],"sources":["../../../src/components/dual-nav/dual-nav.vue"],"sourcesContent":["<template>\n <div :class=\"cls.b\">\n <div :class=\"cls.e('rail')\">\n <DualNavApp\n v-for=\"app in menus\"\n :key=\"app.path\"\n :app=\"app\"\n :active=\"isAppActive(app)\"\n @click=\"handleAppClick(app)\"\n />\n </div>\n\n <div :class=\"cls.e('divider')\" aria-hidden=\"true\" />\n\n <div :class=\"cls.e('panel')\">\n <template v-if=\"panelApp\">\n <div :class=\"cls.e('panel-header')\">\n <div :class=\"cls.e('panel-header-main')\">\n <h3 :class=\"cls.e('panel-title')\">{{ panelApp.title }}</h3>\n <p v-if=\"panelApp.description\" :class=\"cls.e('description')\">\n {{ panelApp.description }}\n </p>\n </div>\n\n <u-tip\n v-if=\"hasExpandableMenus\"\n :content=\"allExpanded ? '折叠全部' : '展开全部'\"\n hide-arrow\n >\n <button\n type=\"button\"\n :class=\"cls.e('toggle-expand')\"\n :aria-label=\"allExpanded ? '折叠全部' : '展开全部'\"\n @click=\"handleToggleExpandAll\"\n >\n <u-icon>\n <FolderOpened v-if=\"!allExpanded\" />\n <Folder v-else />\n </u-icon>\n </button>\n </u-tip>\n </div>\n\n <div :class=\"cls.e('panel-body')\">\n <UNav\n ref=\"navRef\"\n :menus=\"panelMenus\"\n :current-path=\"currentPath\"\n :collapsed=\"false\"\n @item-click=\"emit('item-click', $event)\"\n />\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { Folder, FolderOpened } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, ref, useTemplateRef, watch } from 'vue'\n\nimport type { DualNavEmits, DualNavProps, DualNavRootItem } from '../../types/dual-nav'\nimport type { NavExposed, NavItem } from '../../types/nav'\nimport { UIcon } from '../icon'\nimport { UNav } from '../nav'\nimport { collectNavBranchPaths } from '../nav/helper'\nimport { UTip } from '../tip'\nimport DualNavApp from './dual-nav-app.vue'\nimport { findRootApp } from './helper'\n\ndefineOptions({ name: 'DualNav' })\n\nconst props = defineProps<DualNavProps>()\nconst emit = defineEmits<DualNavEmits>()\n\nconst cls = bem('dual-nav')\nconst navRef = useTemplateRef<NavExposed>('navRef')\n\n/** 右栏当前展示的应用(点击左轨或 currentPath 同步) */\nconst panelAppPath = ref<string>()\nconst allExpanded = ref(false)\n\nfunction syncPanelAppFromCurrentPath(currentPath?: string) {\n const rootApp = findRootApp(props.menus, currentPath)\n if (rootApp) {\n panelAppPath.value = rootApp.path\n return\n }\n\n if (!panelAppPath.value && props.menus?.length) {\n panelAppPath.value = props.menus[0]!.path\n }\n}\n\nwatch(\n () => [props.currentPath, props.menus] as const,\n ([currentPath]) => {\n syncPanelAppFromCurrentPath(currentPath)\n },\n { immediate: true }\n)\n\nconst panelApp = computed<DualNavRootItem | undefined>(() => {\n if (!panelAppPath.value || !props.menus?.length) return undefined\n return props.menus.find((app) => app.path === panelAppPath.value)\n})\n\n/** 右栏菜单:有子级时展示子级,单层时展示根项自身 */\nconst panelMenus = computed<NavItem[] | undefined>(() => {\n const app = panelApp.value\n if (!app) return undefined\n if (app.children?.length) return app.children\n return [app]\n})\n\nconst hasExpandableMenus = computed(() => {\n return collectNavBranchPaths(panelMenus.value).length > 0\n})\n\nwatch(panelAppPath, () => {\n allExpanded.value = false\n})\n\n/** 左轨高亮:仅当 currentPath 落在该应用下时激活 */\nfunction isAppActive(app: NavItem): boolean {\n return findRootApp(props.menus, props.currentPath)?.path === app.path\n}\n\nfunction handleAppClick(app: NavItem) {\n if (app.disabled) return\n\n panelAppPath.value = app.path\n\n if (!app.children?.length) {\n emit('item-click', app)\n }\n}\n\nfunction handleToggleExpandAll() {\n if (allExpanded.value) {\n navRef.value?.collapseAll()\n allExpanded.value = false\n return\n }\n\n navRef.value?.expandAll()\n allExpanded.value = true\n}\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"dual-nav.js","names":[],"sources":["../../../src/components/dual-nav/dual-nav.vue"],"sourcesContent":["<template>\n <div :class=\"cls.b\">\n <div :class=\"cls.e('rail')\">\n <DualNavApp\n v-for=\"app in menus\"\n :key=\"app.path\"\n :app=\"app\"\n :active=\"isAppActive(app)\"\n :selected=\"isAppSelected(app)\"\n @click=\"handleAppClick(app)\"\n />\n </div>\n\n <!-- <div :class=\"cls.e('divider')\" aria-hidden=\"true\" /> -->\n\n <div :class=\"cls.e('panel')\">\n <template v-if=\"panelApp\">\n <div :class=\"cls.e('panel-header')\">\n <div :class=\"cls.e('panel-header-main')\">\n <h3 :class=\"cls.e('panel-title')\">{{ panelApp.title }}</h3>\n <p v-if=\"panelApp.description\" :class=\"cls.e('description')\">\n {{ panelApp.description }}\n </p>\n </div>\n\n <u-tip\n v-if=\"hasExpandableMenus\"\n :content=\"allExpanded ? '折叠全部' : '展开全部'\"\n hide-arrow\n >\n <button\n type=\"button\"\n :class=\"cls.e('toggle-expand')\"\n :aria-label=\"allExpanded ? '折叠全部' : '展开全部'\"\n @click=\"handleToggleExpandAll\"\n >\n <u-icon>\n <FolderOpened v-if=\"!allExpanded\" />\n <Folder v-else />\n </u-icon>\n </button>\n </u-tip>\n </div>\n\n <div :class=\"cls.e('panel-body')\">\n <UNav\n ref=\"navRef\"\n :menus=\"panelMenus\"\n :current-path=\"currentPath\"\n :collapsed=\"false\"\n @item-click=\"emit('item-click', $event)\"\n />\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { Folder, FolderOpened } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, ref, useTemplateRef, watch } from 'vue'\n\nimport type { DualNavEmits, DualNavProps, DualNavRootItem } from '../../types/dual-nav'\nimport type { NavExposed, NavItem } from '../../types/nav'\nimport { UIcon } from '../icon'\nimport { UNav } from '../nav'\nimport { collectNavBranchPaths } from '../nav/helper'\nimport { UTip } from '../tip'\nimport DualNavApp from './dual-nav-app.vue'\nimport { findFirstLeaf, findRootApp } from './helper'\n\ndefineOptions({ name: 'DualNav' })\n\nconst props = defineProps<DualNavProps>()\nconst emit = defineEmits<DualNavEmits>()\n\nconst cls = bem('dual-nav')\nconst navRef = useTemplateRef<NavExposed>('navRef')\n\n/** 右栏当前展示的应用(点击左轨或 currentPath 同步) */\nconst panelAppPath = ref<string>()\nconst allExpanded = ref(false)\n\nfunction syncPanelAppFromCurrentPath(currentPath?: string) {\n const rootApp = findRootApp(props.menus, currentPath)\n if (rootApp) {\n panelAppPath.value = rootApp.path\n return\n }\n\n if (!panelAppPath.value && props.menus?.length) {\n panelAppPath.value = props.menus[0]!.path\n }\n}\n\nwatch(\n () => [props.currentPath, props.menus] as const,\n ([currentPath]) => {\n syncPanelAppFromCurrentPath(currentPath)\n },\n { immediate: true }\n)\n\nconst panelApp = computed<DualNavRootItem | undefined>(() => {\n if (!panelAppPath.value || !props.menus?.length) return undefined\n return props.menus.find((app) => app.path === panelAppPath.value)\n})\n\n/** 右栏菜单:有子级时展示子级,单层时展示根项自身 */\nconst panelMenus = computed<NavItem[] | undefined>(() => {\n const app = panelApp.value\n if (!app) return undefined\n if (app.children?.length) return app.children\n return [app]\n})\n\nconst hasExpandableMenus = computed(() => {\n return collectNavBranchPaths(panelMenus.value).length > 0\n})\n\nwatch(panelAppPath, () => {\n allExpanded.value = false\n})\n\n/** 左轨高亮:currentPath 落在该应用下时为激活态 */\nfunction isAppActive(app: NavItem): boolean {\n return findRootApp(props.menus, props.currentPath)?.path === app.path\n}\n\n/** 左轨选中:已切换右栏但尚未导航到该应用下任一菜单时,使用 hover 风格 */\nfunction isAppSelected(app: NavItem): boolean {\n return panelAppPath.value === app.path && !isAppActive(app)\n}\n\nfunction handleAppClick(app: NavItem) {\n if (app.disabled) return\n\n panelAppPath.value = app.path\n\n emit('item-click', findFirstLeaf(app))\n}\n\nfunction handleToggleExpandAll() {\n if (allExpanded.value) {\n navRef.value?.collapseAll()\n allExpanded.value = false\n return\n }\n\n navRef.value?.expandAll()\n allExpanded.value = true\n}\n</script>\n"],"mappings":"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"}
@@ -15,7 +15,12 @@ function findRootApp(menus, currentPath) {
15
15
  if (matched) return app;
16
16
  }
17
17
  }
18
+ /** 深度优先查找首个叶子节点(无子级或子级为空) */
19
+ function findFirstLeaf(node) {
20
+ if (!node.children?.length) return node;
21
+ return findFirstLeaf(node.children[0]);
22
+ }
18
23
  //#endregion
19
- export { findRootApp };
24
+ export { findFirstLeaf, findRootApp };
20
25
 
21
26
  //# sourceMappingURL=helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","names":[],"sources":["../../../src/components/dual-nav/helper.ts"],"sourcesContent":["import type { DualNavRootItem } from '../../types'\nimport { walkNavWithPath } from '../nav/walk-nav-path'\n\n/** 根据当前路径反查所属顶层应用 */\nexport function findRootApp(\n menus: DualNavRootItem[] | undefined,\n currentPath?: string\n): DualNavRootItem | undefined {\n if (!currentPath || !menus?.length) return undefined\n\n for (const app of menus) {\n if (app.path === currentPath) return app\n\n let matched = false\n walkNavWithPath(app, (node) => {\n if (node.path === currentPath) {\n matched = true\n return false\n }\n })\n if (matched) return app\n }\n\n return undefined\n}\n"],"mappings":";;;AAIA,SAAgB,YACd,OACA,aAC6B;CAC7B,IAAI,CAAC,eAAe,CAAC,OAAO,QAAQ,OAAO,KAAA;CAE3C,KAAK,MAAM,OAAO,OAAO;EACvB,IAAI,IAAI,SAAS,aAAa,OAAO;EAErC,IAAI,UAAU;EACd,gBAAgB,MAAM,SAAS;GAC7B,IAAI,KAAK,SAAS,aAAa;IAC7B,UAAU;IACV,OAAO;GACT;EACF,CAAC;EACD,IAAI,SAAS,OAAO;CACtB;AAGF"}
1
+ {"version":3,"file":"helper.js","names":[],"sources":["../../../src/components/dual-nav/helper.ts"],"sourcesContent":["import type { DualNavRootItem, NavItem } from '../../types'\nimport { walkNavWithPath } from '../nav/walk-nav-path'\n\n/** 根据当前路径反查所属顶层应用 */\nexport function findRootApp(\n menus: DualNavRootItem[] | undefined,\n currentPath?: string\n): DualNavRootItem | undefined {\n if (!currentPath || !menus?.length) return undefined\n\n for (const app of menus) {\n if (app.path === currentPath) return app\n\n let matched = false\n walkNavWithPath(app, (node) => {\n if (node.path === currentPath) {\n matched = true\n return false\n }\n })\n if (matched) return app\n }\n\n return undefined\n}\n\n/** 深度优先查找首个叶子节点(无子级或子级为空) */\nexport function findFirstLeaf(node: NavItem): NavItem {\n if (!node.children?.length) return node\n return findFirstLeaf(node.children[0]!)\n}\n"],"mappings":";;;AAIA,SAAgB,YACd,OACA,aAC6B;CAC7B,IAAI,CAAC,eAAe,CAAC,OAAO,QAAQ,OAAO,KAAA;CAE3C,KAAK,MAAM,OAAO,OAAO;EACvB,IAAI,IAAI,SAAS,aAAa,OAAO;EAErC,IAAI,UAAU;EACd,gBAAgB,MAAM,SAAS;GAC7B,IAAI,KAAK,SAAS,aAAa;IAC7B,UAAU;IACV,OAAO;GACT;EACF,CAAC;EACD,IAAI,SAAS,OAAO;CACtB;AAGF;;AAGA,SAAgB,cAAc,MAAwB;CACpD,IAAI,CAAC,KAAK,UAAU,QAAQ,OAAO;CACnC,OAAO,cAAc,KAAK,SAAS,EAAG;AACxC"}
@@ -2,3 +2,4 @@ import "../icon/style.js";
2
2
  import "../tip/style.js";
3
3
  import "../nav/style.js";
4
4
  import "./style2.css";
5
+ import "@veltra/directives/ripple/style.js";
@@ -20,7 +20,6 @@
20
20
  height: 100%;
21
21
  min-height: 0;
22
22
  background-color: var(--u-nav-bg-color);
23
- border: 1px solid var(--u-border-color);
24
23
  border-radius: calc(var(--u-radius-large) + 6px);
25
24
  overflow: hidden;
26
25
  }
@@ -53,7 +52,6 @@
53
52
  justify-content: space-between;
54
53
  gap: 8px;
55
54
  padding: 12px 12px 10px;
56
- border-bottom: 1px solid var(--u-border-color);
57
55
  }
58
56
  .u-dual-nav__panel-header-main {
59
57
  flex: 1;
@@ -124,10 +122,22 @@
124
122
  background-color: var(--u-nav-hover-bg);
125
123
  color: var(--u-nav-hover-color);
126
124
  }
125
+ .u-dual-nav__app.is-selected {
126
+ background-color: var(--u-nav-hover-bg);
127
+ color: var(--u-nav-hover-color);
128
+ }
129
+ .u-dual-nav__app.is-selected:hover:not(:disabled) {
130
+ background-color: var(--u-nav-hover-bg);
131
+ color: var(--u-nav-hover-color);
132
+ }
127
133
  .u-dual-nav__app.is-active {
128
134
  background-color: var(--u-nav-active-bg);
129
135
  color: var(--u-nav-active-color);
130
136
  }
137
+ .u-dual-nav__app.is-active:hover:not(:disabled) {
138
+ background-color: var(--u-nav-active-bg);
139
+ color: var(--u-nav-active-color);
140
+ }
131
141
  .u-dual-nav__app.is-disabled {
132
142
  opacity: 0.55;
133
143
  cursor: not-allowed;
@@ -143,3 +153,20 @@
143
153
  font-weight: 600;
144
154
  line-height: 1;
145
155
  }
156
+ .u-dual-nav__app-tip {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: 4px;
160
+ max-width: 200px;
161
+ }
162
+ .u-dual-nav__app-tip-title {
163
+ font-weight: 600;
164
+ line-height: 1.4;
165
+ color: var(--u-text-color-title);
166
+ }
167
+ .u-dual-nav__app-tip-description {
168
+ margin: 0;
169
+ font-size: var(--u-font-size-assist-default);
170
+ line-height: 1.5;
171
+ color: var(--u-text-color-second);
172
+ }
@@ -1,8 +1,8 @@
1
1
  import { EmptyProps } from "../../types/empty.js";
2
2
  //#region src/components/empty/empty.vue.d.ts
3
3
  declare const __VLS_export: import("vue").DefineComponent<EmptyProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<EmptyProps> & Readonly<{}>, {
4
- size: number;
5
4
  text: string;
5
+ size: number;
6
6
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
7
  declare const _default: typeof __VLS_export;
8
8
  //#endregion
@@ -17,19 +17,19 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, _Inpu
17
17
  "update:modelValue": (value: string) => any;
18
18
  clear: () => any;
19
19
  change: (value: string) => any;
20
- "suffix:click": (value?: string | undefined) => any;
21
- "prefix:click": (value?: string | undefined) => any;
22
20
  focus: (value?: string | undefined) => any;
23
21
  blur: (value?: string | undefined) => any;
22
+ "suffix:click": (value?: string | undefined) => any;
23
+ "prefix:click": (value?: string | undefined) => any;
24
24
  "native:input": (ev: Event) => any;
25
25
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
26
26
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
27
27
  onClear?: (() => any) | undefined;
28
28
  onChange?: ((value: string) => any) | undefined;
29
- "onSuffix:click"?: ((value?: string | undefined) => any) | undefined;
30
- "onPrefix:click"?: ((value?: string | undefined) => any) | undefined;
31
29
  onFocus?: ((value?: string | undefined) => any) | undefined;
32
30
  onBlur?: ((value?: string | undefined) => any) | undefined;
31
+ "onSuffix:click"?: ((value?: string | undefined) => any) | undefined;
32
+ "onPrefix:click"?: ((value?: string | undefined) => any) | undefined;
33
33
  "onNative:input"?: ((ev: Event) => any) | undefined;
34
34
  }>, {
35
35
  placeholder: string;
@@ -5,7 +5,7 @@ import { bem, zIndex } from "@veltra/utils";
5
5
  const _sfc_main = /* @__PURE__ */ defineComponent({
6
6
  name: "Loading",
7
7
  __name: "loading",
8
- props: { type: { default: "spinner" } },
8
+ props: { type: { default: "dual-ring" } },
9
9
  setup(__props) {
10
10
  const props = __props;
11
11
  const { size } = useFallbackProps([props], { size: "default" });
@@ -1 +1 @@
1
- {"version":3,"file":"loading.js","names":[],"sources":["../../../src/components/loading/loading.vue"],"sourcesContent":["<template>\n <transition name=\"fade\" appear>\n <div :class=\"className\" :style=\"{ zIndex: zIndex() }\">\n <div :class=\"cls.e('loader')\"></div>\n </div>\n </transition>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFallbackProps } from '@veltra/compositions'\nimport { bem, zIndex } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { LoadingProps, ComponentSize } from '../../types'\n\ndefineOptions({ name: 'Loading' })\n\nconst props = withDefaults(defineProps<LoadingProps>(), { type: 'spinner' })\n\nconst { size } = useFallbackProps([props], { size: 'default' as ComponentSize })\n\nconst cls = bem('loading')\n\nconst className = computed(() => {\n return [cls.b, cls.m(size.value), cls.e(props.type)]\n})\n</script>\n"],"mappings":";;;;;;;;;EAiBA,MAAM,QAAQ;EAEd,MAAM,EAAE,SAAS,iBAAiB,CAAC,KAAK,GAAG,EAAE,MAAM,UAA2B,CAAC;EAE/E,MAAM,MAAM,IAAI,SAAS;EAEzB,MAAM,YAAY,eAAe;GAC/B,OAAO;IAAC,IAAI;IAAG,IAAI,EAAE,KAAK,KAAK;IAAG,IAAI,EAAE,MAAM,IAAI;GAAC;EACrD,CAAC;;uBAxBC,YAIa,YAAA;IAJD,MAAK;IAAO,QAAA;;2BAGhB,CAFN,mBAEM,OAAA;KAFA,OAAK,eAAE,UAAA,KAAS;KAAG,OAAK,eAAA,EAAA,QAAY,MAAA,MAAA,EAAM,EAAA,CAAA;QAC9C,mBAAoC,OAAA,EAA9B,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,QAAA,CAAA,EAAA,GAAA,MAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"loading.js","names":[],"sources":["../../../src/components/loading/loading.vue"],"sourcesContent":["<template>\n <transition name=\"fade\" appear>\n <div :class=\"className\" :style=\"{ zIndex: zIndex() }\">\n <div :class=\"cls.e('loader')\"></div>\n </div>\n </transition>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFallbackProps } from '@veltra/compositions'\nimport { bem, zIndex } from '@veltra/utils'\nimport { computed } from 'vue'\n\nimport type { LoadingProps, ComponentSize } from '../../types'\n\ndefineOptions({ name: 'Loading' })\n\nconst props = withDefaults(defineProps<LoadingProps>(), { type: 'dual-ring' })\n\nconst { size } = useFallbackProps([props], { size: 'default' as ComponentSize })\n\nconst cls = bem('loading')\n\nconst className = computed(() => {\n return [cls.b, cls.m(size.value), cls.e(props.type)]\n})\n</script>\n"],"mappings":";;;;;;;;;EAiBA,MAAM,QAAQ;EAEd,MAAM,EAAE,SAAS,iBAAiB,CAAC,KAAK,GAAG,EAAE,MAAM,UAA2B,CAAC;EAE/E,MAAM,MAAM,IAAI,SAAS;EAEzB,MAAM,YAAY,eAAe;GAC/B,OAAO;IAAC,IAAI;IAAG,IAAI,EAAE,KAAK,KAAK;IAAG,IAAI,EAAE,MAAM,IAAI;GAAC;EACrD,CAAC;;uBAxBC,YAIa,YAAA;IAJD,MAAK;IAAO,QAAA;;2BAGhB,CAFN,mBAEM,OAAA;KAFA,OAAK,eAAE,UAAA,KAAS;KAAG,OAAK,eAAA,EAAA,QAAY,MAAA,MAAA,EAAM,EAAA,CAAA;QAC9C,mBAAoC,OAAA,EAA9B,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,QAAA,CAAA,EAAA,GAAA,MAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA"}
@@ -20,7 +20,7 @@
20
20
  width: 100%;
21
21
  height: 100%;
22
22
  background-color: var(--u-bg-color-top-alpha);
23
- color: var(--u-text-color-main);
23
+ color: var(--u-color-primary);
24
24
  display: flex;
25
25
  justify-content: center;
26
26
  align-items: center;
@@ -29,77 +29,85 @@
29
29
  .u-loading__container {
30
30
  position: relative;
31
31
  }
32
- .u-loading__spinner .u-loading__loader {
33
- width: 42px;
32
+ .u-loading__dual-ring .u-loading__loader {
33
+ width: 36px;
34
34
  aspect-ratio: 1;
35
35
  border-radius: 50%;
36
- background: conic-gradient(from 0deg, transparent 25%, currentColor 70%, transparent 100%);
37
- mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 0);
38
- animation: loading-spin 0.85s linear infinite;
36
+ border: 3px solid color-mix(in srgb, currentColor 22%, transparent);
37
+ border-top-color: currentColor;
38
+ position: relative;
39
+ animation: loading-spin 0.8s linear infinite;
40
+ }
41
+ .u-loading__dual-ring .u-loading__loader::after {
42
+ content: "";
43
+ position: absolute;
44
+ inset: 5px;
45
+ border-radius: 50%;
46
+ border: 2px solid color-mix(in srgb, currentColor 22%, transparent);
47
+ border-bottom-color: currentColor;
48
+ animation: loading-spin 1.2s linear infinite reverse;
39
49
  }
40
50
  .u-loading__dot .u-loading__loader {
41
- width: 10px;
42
- height: 10px;
51
+ width: 9px;
52
+ height: 9px;
43
53
  border-radius: 50%;
44
54
  background: currentColor;
45
55
  position: relative;
46
- animation: loading-dot 1.4s ease-in-out infinite;
56
+ animation: loading-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
47
57
  }
48
58
  .u-loading__dot .u-loading__loader::before, .u-loading__dot .u-loading__loader::after {
49
59
  content: "";
50
60
  position: absolute;
51
61
  top: 0;
52
- width: 10px;
53
- height: 10px;
62
+ width: 9px;
63
+ height: 9px;
54
64
  border-radius: 50%;
55
65
  background: currentColor;
56
66
  }
57
67
  .u-loading__dot .u-loading__loader::before {
58
- left: -20px;
59
- animation: loading-dot 1.4s ease-in-out infinite;
60
- animation-delay: 0.2s;
68
+ left: -16px;
69
+ animation: loading-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
70
+ animation-delay: 0.18s;
61
71
  }
62
72
  .u-loading__dot .u-loading__loader::after {
63
- right: -20px;
64
- animation: loading-dot 1.4s ease-in-out infinite;
65
- animation-delay: 0.4s;
66
- }
67
- .u-loading__line .u-loading__loader {
68
- width: 160px;
69
- height: 3px;
70
- border-radius: 1.5px;
71
- background: var(--u-bg-color-hover);
72
- overflow: hidden;
73
- position: relative;
74
- }
75
- .u-loading__line .u-loading__loader::after {
76
- content: "";
77
- position: absolute;
78
- inset: 0;
79
- background: linear-gradient(90deg, transparent 0%, currentColor 40%, currentColor 60%, transparent 100%);
80
- animation: loading-shimmer 1.4s ease-in-out infinite;
73
+ right: -16px;
74
+ animation: loading-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
75
+ animation-delay: 0.36s;
81
76
  }
82
- .u-loading__classic .u-loading__loader {
83
- width: 44px;
77
+ .u-loading__ring .u-loading__loader {
78
+ width: 36px;
84
79
  aspect-ratio: 1;
80
+ border-radius: 50%;
81
+ border: 3px solid color-mix(in srgb, currentColor 22%, transparent);
82
+ border-top-color: currentColor;
83
+ animation: loading-spin 0.9s linear infinite;
84
+ }
85
+ .u-loading__bars .u-loading__loader {
86
+ width: 6px;
87
+ height: 26px;
88
+ background: currentColor;
89
+ border-radius: 3px;
85
90
  position: relative;
91
+ animation: loading-bars 1s ease-in-out infinite;
92
+ animation-delay: 0.15s;
86
93
  }
87
- .u-loading__classic .u-loading__loader::before, .u-loading__classic .u-loading__loader::after {
94
+ .u-loading__bars .u-loading__loader::before, .u-loading__bars .u-loading__loader::after {
88
95
  content: "";
89
96
  position: absolute;
90
- inset: 0;
91
- border-radius: 50%;
92
- border: 3px solid currentColor;
93
- animation: loading-pulse 1.6s ease-out infinite;
97
+ top: 50%;
98
+ transform: translateY(-50%);
99
+ width: 6px;
100
+ height: 26px;
101
+ background: currentColor;
102
+ border-radius: 3px;
103
+ animation: loading-bars 1s ease-in-out infinite;
94
104
  }
95
- .u-loading__classic .u-loading__loader::after {
96
- animation-delay: 0.8s;
105
+ .u-loading__bars .u-loading__loader::before {
106
+ left: -12px;
97
107
  }
98
- .u-loading__morph .u-loading__loader {
99
- width: 34px;
100
- aspect-ratio: 1;
101
- background: currentColor;
102
- animation: loading-morph 2.4s ease-in-out infinite;
108
+ .u-loading__bars .u-loading__loader::after {
109
+ right: -12px;
110
+ animation-delay: 0.3s;
103
111
  }
104
112
 
105
113
  @keyframes loading-spin {
@@ -108,48 +116,20 @@
108
116
  }
109
117
  }
110
118
  @keyframes loading-dot {
111
- 0%, 80%, 100% {
112
- transform: scale(0.4);
113
- opacity: 0.25;
119
+ 0%, 100% {
120
+ transform: scale(0.5);
121
+ opacity: 0.3;
114
122
  }
115
- 40% {
123
+ 50% {
116
124
  transform: scale(1);
117
125
  opacity: 1;
118
126
  }
119
127
  }
120
- @keyframes loading-shimmer {
121
- 0% {
122
- transform: translateX(-100%);
123
- }
124
- 100% {
125
- transform: translateX(100%);
126
- }
127
- }
128
- @keyframes loading-pulse {
129
- 0% {
130
- transform: scale(0.2);
131
- opacity: 1;
132
- }
133
- 100% {
134
- transform: scale(1.35);
135
- opacity: 0;
136
- }
137
- }
138
- @keyframes loading-morph {
128
+ @keyframes loading-bars {
139
129
  0%, 100% {
140
- border-radius: 50%;
141
- transform: rotate(0deg) scale(1);
142
- }
143
- 25% {
144
- border-radius: 8px;
145
- transform: rotate(45deg) scale(0.7);
130
+ height: 10px;
146
131
  }
147
132
  50% {
148
- border-radius: 2px;
149
- transform: rotate(90deg) scale(1);
150
- }
151
- 75% {
152
- border-radius: 8px;
153
- transform: rotate(135deg) scale(0.7);
133
+ height: 26px;
154
134
  }
155
135
  }
@@ -21,9 +21,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
21
21
  onClear?: (() => any) | undefined;
22
22
  onChange?: ((checked: Record<string, any>[]) => any) | undefined;
23
23
  }>, {
24
- expandAll: boolean;
25
24
  labelKey: string;
26
25
  valueKey: string;
26
+ expandAll: boolean;
27
27
  placeholder: string;
28
28
  clearable: boolean;
29
29
  filterable: boolean;
@@ -1,8 +1,8 @@
1
1
  import { NumberProps } from "../../types/number.js";
2
2
  //#region src/components/number/number.vue.d.ts
3
3
  declare const __VLS_export: import("vue").DefineComponent<NumberProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NumberProps> & Readonly<{}>, {
4
- duration: number;
5
4
  format: "currency" | "percent" | "decimal";
5
+ duration: number;
6
6
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
7
  declare const _default: typeof __VLS_export;
8
8
  //#endregion
@@ -19,9 +19,9 @@ declare const __VLS_base: import("vue").DefineComponent<ProgressNodesProps, {},
19
19
  "onUpdate:modelValue"?: ((value: string | number) => any) | undefined;
20
20
  onClick?: ((node: Record<string, any>, index: number) => any) | undefined;
21
21
  }>, {
22
+ colorType: import("@veltra/utils").ColorType;
22
23
  labelKey: string;
23
24
  valueKey: string;
24
- colorType: import("@veltra/utils").ColorType;
25
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
26
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
27
27
  declare const _default: typeof __VLS_export;
@@ -5,11 +5,11 @@ type __VLS_Slots = {
5
5
  content(): any;
6
6
  };
7
7
  declare const __VLS_base: import("vue").DefineComponent<ScrollProps, _ScrollExposed, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
8
- scroll: (position: Required<ScrollPosition>) => any;
9
8
  resize: (targets: HTMLElement[]) => any;
9
+ scroll: (position: Required<ScrollPosition>) => any;
10
10
  }, string, import("vue").PublicProps, Readonly<ScrollProps> & Readonly<{
11
- onScroll?: ((position: Required<ScrollPosition>) => any) | undefined;
12
11
  onResize?: ((targets: HTMLElement[]) => any) | undefined;
12
+ onScroll?: ((position: Required<ScrollPosition>) => any) | undefined;
13
13
  }>, {
14
14
  tag: string;
15
15
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -15,12 +15,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
15
15
  "update:modelValue": (value: string | undefined) => any;
16
16
  } & {
17
17
  "update:modelValue": (value: string) => any;
18
- close: (item: TabItem, index: number) => any;
19
18
  click: (item: TabItem, index: number) => any;
19
+ close: (item: TabItem, index: number) => any;
20
20
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
21
21
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
22
- onClose?: ((item: TabItem, index: number) => any) | undefined;
23
22
  onClick?: ((item: TabItem, index: number) => any) | undefined;
23
+ onClose?: ((item: TabItem, index: number) => any) | undefined;
24
24
  }>, {
25
25
  closable: boolean;
26
26
  position: "top" | "bottom";
@@ -15,12 +15,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
15
15
  "update:modelValue": (value: string | undefined) => any;
16
16
  } & {
17
17
  "update:modelValue": (value: string) => any;
18
- close: (item: TabItem, index: number) => any;
19
18
  click: (item: TabItem, index: number) => any;
19
+ close: (item: TabItem, index: number) => any;
20
20
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
21
21
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
22
- onClose?: ((item: TabItem, index: number) => any) | undefined;
23
22
  onClick?: ((item: TabItem, index: number) => any) | undefined;
23
+ onClose?: ((item: TabItem, index: number) => any) | undefined;
24
24
  }>, {
25
25
  closable: boolean;
26
26
  position: "left" | "right";
@@ -10,12 +10,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
10
10
  "update:modelValue": (value: string | undefined) => any;
11
11
  } & {
12
12
  "update:modelValue": (value: string) => any;
13
- close: (item: TabItem, index: number) => any;
14
13
  click: (item: TabItem, index: number) => any;
14
+ close: (item: TabItem, index: number) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
16
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
17
- onClose?: ((item: TabItem, index: number) => any) | undefined;
18
17
  onClick?: ((item: TabItem, index: number) => any) | undefined;
18
+ onClose?: ((item: TabItem, index: number) => any) | undefined;
19
19
  }>, {
20
20
  closable: boolean;
21
21
  position: "left" | "right" | "top" | "bottom";
@@ -19,9 +19,9 @@ declare const __VLS_base: import("vue").DefineComponent<TreeProps, _TreeExposed,
19
19
  "onNode-contextmenu"?: ((event: MouseEvent, node: TreeNode<Record<string, any>>) => any) | undefined;
20
20
  "onSelected-synced"?: ((selected?: Record<string, any> | undefined) => any) | undefined;
21
21
  }>, {
22
- expandOnClickNode: boolean;
23
22
  labelKey: string;
24
23
  valueKey: string;
24
+ expandOnClickNode: boolean;
25
25
  childrenKey: string;
26
26
  data: Record<string, any>[];
27
27
  checkStrictly: boolean;
@@ -23,9 +23,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
23
23
  onChange?: ((value?: string | number | undefined, selectedData?: Record<string, any> | undefined) => any) | undefined;
24
24
  "onUpdate:text"?: ((text?: string | undefined) => any) | undefined;
25
25
  }>, {
26
- expandAll: boolean;
27
26
  labelKey: string;
28
27
  valueKey: string;
28
+ expandAll: boolean;
29
29
  placeholder: string;
30
30
  clearable: boolean;
31
31
  filterable: boolean;
@@ -2,7 +2,7 @@ import { NavItem } from "./nav.js";
2
2
  import { DeconstructValue } from "@veltra/utils";
3
3
 
4
4
  //#region src/types/dual-nav.d.ts
5
- /** 双栏导航根级应用项;`description` 仅在此层级生效,且仅在双栏导航右栏展示 */
5
+ /** 双栏导航根级应用项;`description` 在左轨 tooltip 与右栏顶部展示 */
6
6
  interface DualNavRootItem extends NavItem {
7
7
  /** 子导航 */
8
8
  children?: NavItem[];
@@ -1,7 +1,7 @@
1
1
  import { DeconstructValue } from "@veltra/utils";
2
2
 
3
3
  //#region src/types/loading.d.ts
4
- type LoadingType = 'classic' | 'line' | 'dot' | 'spinner' | 'morph';
4
+ type LoadingType = 'dual-ring' | 'dot' | 'ring' | 'bars';
5
5
  /** loading组件属性 */
6
6
  interface LoadingProps {
7
7
  /** 加载类型 */
@@ -11,7 +11,7 @@ interface NavItem {
11
11
  /** 导航路径 */
12
12
  path: string;
13
13
  /**
14
- * 应用描述;仅根级导航项有效,且仅 UDualNav 右栏顶部展示
14
+ * 应用描述;仅根级导航项有效,在 UDualNav 左轨 tooltip 与右栏顶部展示
15
15
  * @see DualNavRootItem
16
16
  */
17
17
  description?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/desktop",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -68,11 +68,11 @@
68
68
  "@cat-kit/core": ">=1.1.6",
69
69
  "@cat-kit/excel": ">=1.0.0",
70
70
  "@cat-kit/fe": "^1.1.6",
71
- "@veltra/compositions": "1.2.5",
72
- "@veltra/directives": "1.2.5",
73
- "@veltra/icons": "1.2.5",
74
- "@veltra/styles": "1.2.5",
75
- "@veltra/utils": "1.2.5",
71
+ "@veltra/compositions": "1.2.7",
72
+ "@veltra/directives": "1.2.7",
73
+ "@veltra/icons": "1.2.7",
74
+ "@veltra/styles": "1.2.7",
75
+ "@veltra/utils": "1.2.7",
76
76
  "vue": ">=3.5.0"
77
77
  }
78
78
  }
@@ -1,25 +1,39 @@
1
1
  <template>
2
- <u-tip direction="right" hide-arrow>
2
+ <u-tip v-model:visible="tipVisible" direction="right" hide-arrow :disabled="tipSuppressed">
3
3
  <button
4
4
  type="button"
5
- :class="[cls.e('app'), bem.is('active', active), bem.is('disabled', app.disabled ?? false)]"
5
+ v-ripple="!app.disabled"
6
+ :class="[
7
+ cls.e('app'),
8
+ bem.is('active', active),
9
+ bem.is('selected', selected),
10
+ bem.is('disabled', app.disabled ?? false)
11
+ ]"
6
12
  :disabled="app.disabled"
7
- @click="emit('click')"
13
+ @click="handleClick"
14
+ @mouseleave="handleMouseleave"
8
15
  >
9
16
  <UNavIcon v-if="app.icon" :icon="app.icon" :class="cls.e('app-icon')" />
10
17
  <span v-else :class="cls.e('app-fallback')">{{ app.title[0] }}</span>
11
18
  </button>
12
19
 
13
20
  <template #content>
14
- {{ app.title }}
21
+ <div :class="cls.e('app-tip')">
22
+ <div :class="cls.e('app-tip-title')">{{ app.title }}</div>
23
+ <p v-if="app.description" :class="cls.e('app-tip-description')">
24
+ {{ app.description }}
25
+ </p>
26
+ </div>
15
27
  </template>
16
28
  </u-tip>
17
29
  </template>
18
30
 
19
31
  <script setup lang="ts">
32
+ import { vRipple } from '@veltra/directives'
20
33
  import { bem } from '@veltra/utils'
34
+ import { shallowRef } from 'vue'
21
35
 
22
- import type { NavItem } from '../../types'
36
+ import type { DualNavRootItem } from '../../types'
23
37
  import UNavIcon from '../nav/nav-icon.vue'
24
38
  import { UTip } from '../tip'
25
39
 
@@ -27,7 +41,21 @@ defineOptions({ name: 'DualNavApp' })
27
41
 
28
42
  const cls = bem('dual-nav')
29
43
 
30
- defineProps<{ app: NavItem; active: boolean }>()
44
+ defineProps<{ app: DualNavRootItem; active: boolean; selected: boolean }>()
31
45
 
32
46
  const emit = defineEmits<{ click: [] }>()
47
+
48
+ const tipVisible = shallowRef(false)
49
+ /** 点击后暂时禁用 hover 弹出,移出触发区后恢复 */
50
+ const tipSuppressed = shallowRef(false)
51
+
52
+ function handleClick() {
53
+ tipSuppressed.value = true
54
+ tipVisible.value = false
55
+ emit('click')
56
+ }
57
+
58
+ function handleMouseleave() {
59
+ tipSuppressed.value = false
60
+ }
33
61
  </script>
@@ -6,11 +6,12 @@
6
6
  :key="app.path"
7
7
  :app="app"
8
8
  :active="isAppActive(app)"
9
+ :selected="isAppSelected(app)"
9
10
  @click="handleAppClick(app)"
10
11
  />
11
12
  </div>
12
13
 
13
- <div :class="cls.e('divider')" aria-hidden="true" />
14
+ <!-- <div :class="cls.e('divider')" aria-hidden="true" /> -->
14
15
 
15
16
  <div :class="cls.e('panel')">
16
17
  <template v-if="panelApp">
@@ -67,7 +68,7 @@ import { UNav } from '../nav'
67
68
  import { collectNavBranchPaths } from '../nav/helper'
68
69
  import { UTip } from '../tip'
69
70
  import DualNavApp from './dual-nav-app.vue'
70
- import { findRootApp } from './helper'
71
+ import { findFirstLeaf, findRootApp } from './helper'
71
72
 
72
73
  defineOptions({ name: 'DualNav' })
73
74
 
@@ -122,19 +123,22 @@ watch(panelAppPath, () => {
122
123
  allExpanded.value = false
123
124
  })
124
125
 
125
- /** 左轨高亮:仅当 currentPath 落在该应用下时激活 */
126
+ /** 左轨高亮:currentPath 落在该应用下时为激活态 */
126
127
  function isAppActive(app: NavItem): boolean {
127
128
  return findRootApp(props.menus, props.currentPath)?.path === app.path
128
129
  }
129
130
 
131
+ /** 左轨选中:已切换右栏但尚未导航到该应用下任一菜单时,使用 hover 风格 */
132
+ function isAppSelected(app: NavItem): boolean {
133
+ return panelAppPath.value === app.path && !isAppActive(app)
134
+ }
135
+
130
136
  function handleAppClick(app: NavItem) {
131
137
  if (app.disabled) return
132
138
 
133
139
  panelAppPath.value = app.path
134
140
 
135
- if (!app.children?.length) {
136
- emit('item-click', app)
137
- }
141
+ emit('item-click', findFirstLeaf(app))
138
142
  }
139
143
 
140
144
  function handleToggleExpandAll() {
@@ -1,4 +1,4 @@
1
- import type { DualNavRootItem } from '../../types'
1
+ import type { DualNavRootItem, NavItem } from '../../types'
2
2
  import { walkNavWithPath } from '../nav/walk-nav-path'
3
3
 
4
4
  /** 根据当前路径反查所属顶层应用 */
@@ -23,3 +23,9 @@ export function findRootApp(
23
23
 
24
24
  return undefined
25
25
  }
26
+
27
+ /** 深度优先查找首个叶子节点(无子级或子级为空) */
28
+ export function findFirstLeaf(node: NavItem): NavItem {
29
+ if (!node.children?.length) return node
30
+ return findFirstLeaf(node.children[0]!)
31
+ }
@@ -7,7 +7,6 @@
7
7
  height: 100%;
8
8
  min-height: 0;
9
9
  background-color: var(--u-nav-bg-color);
10
- border: 1px solid fn.use-var(border, color);
11
10
  border-radius: calc(fn.use-var(radius, large) + 6px);
12
11
  overflow: hidden;
13
12
 
@@ -43,7 +42,6 @@
43
42
  justify-content: space-between;
44
43
  gap: 8px;
45
44
  padding: 12px 12px 10px;
46
- border-bottom: 1px solid fn.use-var(border, color);
47
45
  }
48
46
 
49
47
  @include m.e(panel-header-main) {
@@ -127,9 +125,24 @@
127
125
  color: var(--u-nav-hover-color);
128
126
  }
129
127
 
128
+ @include m.is(selected) {
129
+ background-color: var(--u-nav-hover-bg);
130
+ color: var(--u-nav-hover-color);
131
+
132
+ &:hover:not(:disabled) {
133
+ background-color: var(--u-nav-hover-bg);
134
+ color: var(--u-nav-hover-color);
135
+ }
136
+ }
137
+
130
138
  @include m.is(active) {
131
139
  background-color: var(--u-nav-active-bg);
132
140
  color: var(--u-nav-active-color);
141
+
142
+ &:hover:not(:disabled) {
143
+ background-color: var(--u-nav-active-bg);
144
+ color: var(--u-nav-active-color);
145
+ }
133
146
  }
134
147
 
135
148
  @include m.is(disabled) {
@@ -150,4 +163,24 @@
150
163
  font-weight: 600;
151
164
  line-height: 1;
152
165
  }
166
+
167
+ @include m.e(app-tip) {
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: 4px;
171
+ max-width: 200px;
172
+ }
173
+
174
+ @include m.e(app-tip-title) {
175
+ font-weight: 600;
176
+ line-height: 1.4;
177
+ color: fn.use-var(text-color, title);
178
+ }
179
+
180
+ @include m.e(app-tip-description) {
181
+ margin: 0;
182
+ font-size: fn.use-var(font-size-assist, default);
183
+ line-height: 1.5;
184
+ color: fn.use-var(text-color, second);
185
+ }
153
186
  }
@@ -1,3 +1,4 @@
1
+ import '@veltra/directives/ripple/style.js'
1
2
  import '../icon/style'
2
3
  import '../nav/style'
3
4
  import '../tip/style'
@@ -15,7 +15,7 @@ import type { LoadingProps, ComponentSize } from '../../types'
15
15
 
16
16
  defineOptions({ name: 'Loading' })
17
17
 
18
- const props = withDefaults(defineProps<LoadingProps>(), { type: 'spinner' })
18
+ const props = withDefaults(defineProps<LoadingProps>(), { type: 'dual-ring' })
19
19
 
20
20
  const { size } = useFallbackProps([props], { size: 'default' as ComponentSize })
21
21
 
@@ -9,7 +9,7 @@ $root-name: loading;
9
9
  width: 100%;
10
10
  height: 100%;
11
11
  background-color: fn.use-var(bg-color, top, alpha);
12
- color: fn.use-var(text-color, main);
12
+ color: fn.use-var(color, primary);
13
13
  @include m.flex($justify: center, $align: center);
14
14
 
15
15
  @include m.e(container) {
@@ -17,123 +17,118 @@ $root-name: loading;
17
17
  }
18
18
 
19
19
  // ============================================================
20
- // spinner — 渐变圆弧旋转
21
- // 使用 conic-gradient + mask 实现缺口圆环旋转
20
+ // dual-ring — 双环反向旋转(经典 loader)
21
+ // 外环顺时针,内嵌小环逆时针,border + linear 旋转无缝循环
22
22
  // ============================================================
23
- @include m.e(spinner) {
23
+ @include m.e(dual-ring) {
24
24
  @include m.bem($root-name, loader) {
25
- width: 42px;
25
+ width: 36px;
26
26
  aspect-ratio: 1;
27
27
  border-radius: 50%;
28
- background: conic-gradient(from 0deg, transparent 25%, currentColor 70%, transparent 100%);
29
- mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 0);
30
- animation: loading-spin 0.85s linear infinite;
28
+ border: 3px solid color-mix(in srgb, currentColor 22%, transparent);
29
+ border-top-color: currentColor;
30
+ position: relative;
31
+ animation: loading-spin 0.8s linear infinite;
32
+
33
+ &::after {
34
+ content: '';
35
+ position: absolute;
36
+ inset: 5px;
37
+ border-radius: 50%;
38
+ border: 2px solid color-mix(in srgb, currentColor 22%, transparent);
39
+ border-bottom-color: currentColor;
40
+ animation: loading-spin 1.2s linear infinite reverse;
41
+ }
31
42
  }
32
43
  }
33
44
 
34
45
  // ============================================================
35
- // dot — 三点波浪弹跳
36
- // 元素自身 + 两个伪元素,交错弹跳
46
+ // dot — 三点呼吸弹跳
47
+ // 元素自身 + 两个伪元素,交错起伏
37
48
  // ============================================================
38
49
  @include m.e(dot) {
39
50
  @include m.bem($root-name, loader) {
40
- width: 10px;
41
- height: 10px;
51
+ width: 9px;
52
+ height: 9px;
42
53
  border-radius: 50%;
43
54
  background: currentColor;
44
55
  position: relative;
45
- animation: loading-dot 1.4s ease-in-out infinite;
56
+ animation: loading-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
46
57
 
47
58
  &::before,
48
59
  &::after {
49
60
  content: '';
50
61
  position: absolute;
51
62
  top: 0;
52
- width: 10px;
53
- height: 10px;
63
+ width: 9px;
64
+ height: 9px;
54
65
  border-radius: 50%;
55
66
  background: currentColor;
56
67
  }
57
68
 
58
69
  &::before {
59
- left: -20px;
60
- animation: loading-dot 1.4s ease-in-out infinite;
61
- animation-delay: 0.2s;
70
+ left: -16px;
71
+ animation: loading-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
72
+ animation-delay: 0.18s;
62
73
  }
63
74
 
64
75
  &::after {
65
- right: -20px;
66
- animation: loading-dot 1.4s ease-in-out infinite;
67
- animation-delay: 0.4s;
76
+ right: -16px;
77
+ animation: loading-dot 1.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
78
+ animation-delay: 0.36s;
68
79
  }
69
80
  }
70
81
  }
71
82
 
72
83
  // ============================================================
73
- // line — 流光扫过
74
- // 半透明底轨 + 渐变光条水平扫过
84
+ // ring — 单环旋转
75
85
  // ============================================================
76
- @include m.e(line) {
86
+ @include m.e(ring) {
77
87
  @include m.bem($root-name, loader) {
78
- width: 160px;
79
- height: 3px;
80
- border-radius: 1.5px;
81
- background: fn.use-var(bg-color, hover);
82
- overflow: hidden;
83
- position: relative;
84
-
85
- &::after {
86
- content: '';
87
- position: absolute;
88
- inset: 0;
89
- background: linear-gradient(
90
- 90deg,
91
- transparent 0%,
92
- currentColor 40%,
93
- currentColor 60%,
94
- transparent 100%
95
- );
96
- animation: loading-shimmer 1.4s ease-in-out infinite;
97
- }
88
+ width: 36px;
89
+ aspect-ratio: 1;
90
+ border-radius: 50%;
91
+ border: 3px solid color-mix(in srgb, currentColor 22%, transparent);
92
+ border-top-color: currentColor;
93
+ animation: loading-spin 0.9s linear infinite;
98
94
  }
99
95
  }
100
96
 
101
97
  // ============================================================
102
- // classic — 雷达波纹扩散
103
- // 两个同心圆环交替向外扩散并淡出
98
+ // bars — 三条音频跳动
99
+ // 三根竖条以容器中心为基准上下对称伸缩
104
100
  // ============================================================
105
- @include m.e(classic) {
101
+ @include m.e(bars) {
106
102
  @include m.bem($root-name, loader) {
107
- width: 44px;
108
- aspect-ratio: 1;
103
+ width: 6px;
104
+ height: 26px;
105
+ background: currentColor;
106
+ border-radius: 3px;
109
107
  position: relative;
108
+ animation: loading-bars 1s ease-in-out infinite;
109
+ animation-delay: 0.15s;
110
110
 
111
111
  &::before,
112
112
  &::after {
113
113
  content: '';
114
114
  position: absolute;
115
- inset: 0;
116
- border-radius: 50%;
117
- border: 3px solid currentColor;
118
- animation: loading-pulse 1.6s ease-out infinite;
115
+ top: 50%;
116
+ transform: translateY(-50%);
117
+ width: 6px;
118
+ height: 26px;
119
+ background: currentColor;
120
+ border-radius: 3px;
121
+ animation: loading-bars 1s ease-in-out infinite;
119
122
  }
120
123
 
121
- &::after {
122
- animation-delay: 0.8s;
124
+ &::before {
125
+ left: -12px;
123
126
  }
124
- }
125
- }
126
127
 
127
- // ============================================================
128
- // morph — 形态变换
129
- // 方块 ⟷ 圆形 来回变换,配合旋转
130
- // ============================================================
131
- @include m.e(morph) {
132
- @include m.bem($root-name, loader) {
133
- width: 34px;
134
- aspect-ratio: 1;
135
- background: currentColor;
136
- animation: loading-morph 2.4s ease-in-out infinite;
128
+ &::after {
129
+ right: -12px;
130
+ animation-delay: 0.3s;
131
+ }
137
132
  }
138
133
  }
139
134
  }
@@ -150,53 +145,22 @@ $root-name: loading;
150
145
 
151
146
  @keyframes loading-dot {
152
147
  0%,
153
- 80%,
154
148
  100% {
155
- transform: scale(0.4);
156
- opacity: 0.25;
149
+ transform: scale(0.5);
150
+ opacity: 0.3;
157
151
  }
158
- 40% {
152
+ 50% {
159
153
  transform: scale(1);
160
154
  opacity: 1;
161
155
  }
162
156
  }
163
157
 
164
- @keyframes loading-shimmer {
165
- 0% {
166
- transform: translateX(-100%);
167
- }
168
- 100% {
169
- transform: translateX(100%);
170
- }
171
- }
172
-
173
- @keyframes loading-pulse {
174
- 0% {
175
- transform: scale(0.2);
176
- opacity: 1;
177
- }
178
- 100% {
179
- transform: scale(1.35);
180
- opacity: 0;
181
- }
182
- }
183
-
184
- @keyframes loading-morph {
158
+ @keyframes loading-bars {
185
159
  0%,
186
160
  100% {
187
- border-radius: 50%;
188
- transform: rotate(0deg) scale(1);
189
- }
190
- 25% {
191
- border-radius: 8px;
192
- transform: rotate(45deg) scale(0.7);
161
+ height: 10px;
193
162
  }
194
163
  50% {
195
- border-radius: 2px;
196
- transform: rotate(90deg) scale(1);
197
- }
198
- 75% {
199
- border-radius: 8px;
200
- transform: rotate(135deg) scale(0.7);
164
+ height: 26px;
201
165
  }
202
166
  }
@@ -2,7 +2,7 @@ import type { DeconstructValue } from '@veltra/utils'
2
2
 
3
3
  import type { NavItem } from './nav'
4
4
 
5
- /** 双栏导航根级应用项;`description` 仅在此层级生效,且仅在双栏导航右栏展示 */
5
+ /** 双栏导航根级应用项;`description` 在左轨 tooltip 与右栏顶部展示 */
6
6
  export interface DualNavRootItem extends NavItem {
7
7
  /** 子导航 */
8
8
  children?: NavItem[]
@@ -1,6 +1,6 @@
1
1
  import type { DeconstructValue } from '@veltra/utils'
2
2
 
3
- export type LoadingType = 'classic' | 'line' | 'dot' | 'spinner' | 'morph'
3
+ export type LoadingType = 'dual-ring' | 'dot' | 'ring' | 'bars'
4
4
 
5
5
  /** loading组件属性 */
6
6
  export interface LoadingProps {
package/src/types/nav.ts CHANGED
@@ -10,7 +10,7 @@ export interface NavItem {
10
10
  /** 导航路径 */
11
11
  path: string
12
12
  /**
13
- * 应用描述;仅根级导航项有效,且仅 UDualNav 右栏顶部展示
13
+ * 应用描述;仅根级导航项有效,在 UDualNav 左轨 tooltip 与右栏顶部展示
14
14
  * @see DualNavRootItem
15
15
  */
16
16
  description?: string