@gx-design-vue/pro-layout 0.1.0-alpha.11 → 0.1.0-alpha.13

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 (35) hide show
  1. package/dist/ProLayout.js +6 -1
  2. package/dist/components/AppPage/index.d.ts +1 -1
  3. package/dist/components/Breadcrumb/index.js +23 -2
  4. package/dist/components/Breadcrumb/interface.d.ts +5 -0
  5. package/dist/components/PageTransition/index.d.ts +1 -1
  6. package/dist/components/Tabs/InnerTabs.d.ts +8 -0
  7. package/dist/components/Tabs/InnerTabs.js +117 -0
  8. package/dist/components/Tabs/hooks/flipAnimate.d.ts +24 -0
  9. package/dist/components/Tabs/hooks/flipAnimate.js +40 -0
  10. package/dist/components/Tabs/hooks/useAutoScroll.d.ts +27 -0
  11. package/dist/components/Tabs/hooks/useAutoScroll.js +122 -0
  12. package/dist/components/Tabs/hooks/useTabDrag.d.ts +31 -0
  13. package/dist/components/Tabs/hooks/useTabDrag.js +252 -0
  14. package/dist/components/Tabs/index.d.ts +7 -1
  15. package/dist/components/Tabs/index.js +44 -29
  16. package/dist/components/Tabs/interface.d.ts +47 -6
  17. package/dist/components/Tabs/style/index.d.ts +34 -0
  18. package/dist/components/Tabs/style/index.js +140 -0
  19. package/dist/context/index.d.ts +2 -0
  20. package/dist/defaultConfig.js +2 -1
  21. package/dist/hooks/useLayoutBase.d.ts +8 -6
  22. package/dist/hooks/useLayoutBase.js +4 -0
  23. package/dist/hooks/useTabs.d.ts +3 -0
  24. package/dist/hooks/useTabs.js +12 -0
  25. package/dist/index.d.ts +3 -2
  26. package/dist/index.js +2 -1
  27. package/dist/interface.d.ts +6 -2
  28. package/dist/pro-layout.esm.js +1353 -893
  29. package/dist/pro-layout.js +2 -2
  30. package/dist/style/tabs.d.ts +6 -0
  31. package/dist/style/tabs.js +9 -53
  32. package/dist/theme/augment.d.ts +4 -2
  33. package/dist/theme/interface/components.d.ts +4 -2
  34. package/dist/utils/menu.js +2 -1
  35. package/package.json +3 -3
@@ -0,0 +1,252 @@
1
+ import { flipAnimate } from "./flipAnimate.js";
2
+ import { useAutoScroll } from "./useAutoScroll.js";
3
+ import { nextTick, ref } from "vue";
4
+ import { useEventListener } from "@vueuse/core";
5
+ //#region src/components/Tabs/hooks/useTabDrag.ts
6
+ const IGNORE_SELECTOR = "button, a, input, [data-drag-ignore=\"true\"]";
7
+ /** 移动阈值(px),超过才视为拖拽,避免误触影响点击切换 */
8
+ const DRAG_THRESHOLD = 3;
9
+ const SCROLL_RESTORE_FRAMES = 2;
10
+ const DISPLACEMENT_DURATION = 160;
11
+ const DISPLACEMENT_EASING = "cubic-bezier(0.2, 0, 0, 1)";
12
+ function isIgnoredTarget(target) {
13
+ return target instanceof HTMLElement && Boolean(target.closest(IGNORE_SELECTOR));
14
+ }
15
+ /**
16
+ * 页签拖拽排序 —— 精简复刻 dnd-kit 的 useDraggable + sortable 实时让位。
17
+ *
18
+ * - 移动阈值(3px)区分点击与拖拽:未超阈值视为点击,让原生 click 走切换
19
+ * - 拖拽项 `transform: translateX(delta)` 跟随指针
20
+ * - 同组其他 tab 按虚拟插入位置实时 `translate` 让位(CSS translate 属性)
21
+ * - drop 锚点只在 `item.group === dragGroup` 内查找 → 天然不跨级
22
+ * - 松手 → onReorder 触发数据重排 → nextTick 用 flipAnimate(WAAPI FLIP)平滑归位
23
+ * - 拖拽结束后捕获抑制下一次 click,避免误触发 tab 切换
24
+ * - 边缘自动滚动串联 useAutoScroll
25
+ */
26
+ function useTabDrag(options) {
27
+ const { items, scrollEl, onReorder, enabled } = options;
28
+ const isDragging = ref(false);
29
+ const draggingKey = ref("");
30
+ let dragGroup;
31
+ let dragStartClientX = 0;
32
+ let dragStartScrollLeft = 0;
33
+ let dragPointerOffsetX = 0;
34
+ let pointerClientX = 0;
35
+ let dragEl = null;
36
+ let groupRects = /* @__PURE__ */ new Map();
37
+ let pendingKey = "";
38
+ let pendingStartX = 0;
39
+ const { scroll: scrollOnDragMove, start: startAutoScroll, stop: stopAutoScroll } = useAutoScroll(scrollEl, () => pointerClientX, { onScroll: updateDragPosition });
40
+ function getGroupItems() {
41
+ return items.value.filter((item) => item.group === dragGroup);
42
+ }
43
+ function queryTabEl(key) {
44
+ return scrollEl.value?.querySelector(`[data-key="${key}"]`) ?? null;
45
+ }
46
+ function recordGroupRects() {
47
+ groupRects = /* @__PURE__ */ new Map();
48
+ for (const item of getGroupItems()) {
49
+ const el = queryTabEl(item.key);
50
+ if (!el) continue;
51
+ const rect = el.getBoundingClientRect();
52
+ groupRects.set(item.key, {
53
+ el,
54
+ left: rect.left,
55
+ width: rect.width,
56
+ translate: 0
57
+ });
58
+ }
59
+ }
60
+ function getCurrentScrollOffset() {
61
+ return (scrollEl.value?.scrollLeft ?? 0) - dragStartScrollLeft;
62
+ }
63
+ function getVisualPointerX() {
64
+ return pointerClientX + getCurrentScrollOffset();
65
+ }
66
+ function getDragCenterX() {
67
+ const dragRect = groupRects.get(draggingKey.value);
68
+ if (!dragRect) return getVisualPointerX();
69
+ return getVisualPointerX() - dragPointerOffsetX + dragRect.width / 2;
70
+ }
71
+ function getItemDisplacement(fromIdx, toIdx, dragWidth) {
72
+ const fromRect = groupRects.get(draggingKey.value);
73
+ const nextKey = fromIdx < toIdx ? getGroupItems()[fromIdx + 1]?.key : getGroupItems()[fromIdx - 1]?.key;
74
+ const nextRect = nextKey ? groupRects.get(nextKey) : void 0;
75
+ return dragWidth + (fromRect && nextRect ? Math.max(0, Math.abs(nextRect.left - fromRect.left) - (fromIdx < toIdx ? fromRect.width : nextRect.width)) : 0);
76
+ }
77
+ /** 计算拖拽项应去的目标项索引(匹配 reorderWithinGroup 的 toKey 语义) */
78
+ function computeTargetIdx(dragCenterX) {
79
+ const groupList = getGroupItems();
80
+ if (groupList.findIndex((item) => item.key === draggingKey.value) < 0) return -1;
81
+ const others = groupList.filter((item) => item.key !== draggingKey.value);
82
+ let targetIdx = others.length;
83
+ for (let i = 0; i < others.length; i++) {
84
+ const rect = groupRects.get(others[i].key);
85
+ if (!rect) continue;
86
+ if (dragCenterX < rect.left + rect.width / 2) {
87
+ targetIdx = i;
88
+ break;
89
+ }
90
+ }
91
+ return targetIdx;
92
+ }
93
+ /** 同组其他 tab 实时让位(区间内项 translate ±dragWidth) */
94
+ function applyDisplacement(targetIdx) {
95
+ const groupList = getGroupItems();
96
+ const fromIdx = groupList.findIndex((item) => item.key === draggingKey.value);
97
+ if (fromIdx < 0) return;
98
+ const dragWidth = groupRects.get(draggingKey.value)?.width ?? 0;
99
+ for (let i = 0; i < groupList.length; i++) {
100
+ const key = groupList[i].key;
101
+ if (key === draggingKey.value) continue;
102
+ const rect = groupRects.get(key);
103
+ if (!rect) continue;
104
+ let translate = 0;
105
+ if (fromIdx < targetIdx && i > fromIdx && i <= targetIdx) translate = -getItemDisplacement(fromIdx, targetIdx, dragWidth);
106
+ else if (fromIdx > targetIdx && i >= targetIdx && i < fromIdx) translate = getItemDisplacement(fromIdx, targetIdx, dragWidth);
107
+ applyTabTranslate(rect, translate);
108
+ }
109
+ }
110
+ function applyTabTranslate(rect, nextTranslate) {
111
+ const prevTranslate = rect.translate;
112
+ if (prevTranslate === nextTranslate) return;
113
+ rect.animation?.cancel();
114
+ rect.translate = nextTranslate;
115
+ rect.el.style.translate = nextTranslate ? `${nextTranslate}px` : "";
116
+ if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches || typeof rect.el.animate !== "function") return;
117
+ rect.animation = rect.el.animate({ translate: [`${prevTranslate}px`, `${nextTranslate}px`] }, {
118
+ duration: DISPLACEMENT_DURATION,
119
+ easing: DISPLACEMENT_EASING
120
+ });
121
+ const clearAnimation = () => {
122
+ rect.animation = void 0;
123
+ };
124
+ rect.animation.addEventListener("finish", clearAnimation, { once: true });
125
+ rect.animation.addEventListener("cancel", clearAnimation, { once: true });
126
+ }
127
+ function updateDragPosition() {
128
+ if (!isDragging.value) return;
129
+ if (dragEl) dragEl.style.transform = `translateX(${getVisualPointerX() - dragStartClientX}px)`;
130
+ applyDisplacement(computeTargetIdx(getDragCenterX()));
131
+ }
132
+ function clearInlineStyles(rects = groupRects, activeDragEl = dragEl) {
133
+ for (const rect of rects.values()) {
134
+ rect.animation?.cancel();
135
+ rect.animation = void 0;
136
+ rect.translate = 0;
137
+ const { el } = rect;
138
+ el.style.translate = "";
139
+ }
140
+ if (activeDragEl) activeDragEl.style.transform = "";
141
+ }
142
+ function restoreScrollLeft(scrollLeft, frames = SCROLL_RESTORE_FRAMES) {
143
+ const el = scrollEl.value;
144
+ if (!el) return;
145
+ el.scrollLeft = scrollLeft;
146
+ if (frames <= 0 || typeof requestAnimationFrame === "undefined") return;
147
+ requestAnimationFrame(() => restoreScrollLeft(scrollLeft, frames - 1));
148
+ }
149
+ function beginDrag(clientX) {
150
+ isDragging.value = true;
151
+ dragStartClientX = clientX;
152
+ dragStartScrollLeft = scrollEl.value?.scrollLeft ?? 0;
153
+ pointerClientX = clientX;
154
+ dragEl = queryTabEl(draggingKey.value);
155
+ recordGroupRects();
156
+ const dragRect = groupRects.get(draggingKey.value);
157
+ dragPointerOffsetX = dragRect ? clientX - dragRect.left : 0;
158
+ startAutoScroll();
159
+ }
160
+ /** 捕获阶段抑制拖拽结束后的下一次 click,避免误触发切换 */
161
+ function suppressNextClick() {
162
+ const target = dragEl;
163
+ if (!target) return;
164
+ const suppress = (event) => {
165
+ event.stopPropagation();
166
+ event.preventDefault();
167
+ target.removeEventListener("click", suppress, true);
168
+ };
169
+ target.addEventListener("click", suppress, {
170
+ capture: true,
171
+ once: true
172
+ });
173
+ }
174
+ function handleMousedown(event, item) {
175
+ if (event.button !== 0 || item.disabled) return;
176
+ if (isIgnoredTarget(event.target)) return;
177
+ if (enabled && !enabled.value) return;
178
+ event.preventDefault();
179
+ pendingKey = item.key;
180
+ pendingStartX = event.clientX;
181
+ draggingKey.value = item.key;
182
+ dragGroup = item.group;
183
+ }
184
+ function handleMousemove(event) {
185
+ if (!isDragging.value) {
186
+ if (!pendingKey) return;
187
+ if (Math.abs(event.clientX - pendingStartX) < DRAG_THRESHOLD) return;
188
+ beginDrag(pendingStartX);
189
+ }
190
+ pointerClientX = event.clientX;
191
+ updateDragPosition();
192
+ scrollOnDragMove();
193
+ }
194
+ function endDrag() {
195
+ const targetIdx = computeTargetIdx(getDragCenterX());
196
+ const groupList = getGroupItems();
197
+ const fromIdx = groupList.findIndex((item) => item.key === draggingKey.value);
198
+ const prevRects = /* @__PURE__ */ new Map();
199
+ for (const [key, { el }] of groupRects) prevRects.set(key, el.getBoundingClientRect());
200
+ stopAutoScroll();
201
+ const dragKey = draggingKey.value;
202
+ const targetItem = groupList[targetIdx];
203
+ const hasReorder = targetIdx >= 0 && targetIdx !== fromIdx && targetItem && targetItem.key !== dragKey;
204
+ const currentGroupRects = groupRects;
205
+ const currentDragEl = dragEl;
206
+ const currentScrollLeft = scrollEl.value?.scrollLeft ?? 0;
207
+ if (hasReorder) {
208
+ onReorder(dragKey, targetItem.key);
209
+ nextTick(() => {
210
+ restoreScrollLeft(currentScrollLeft);
211
+ clearInlineStyles(currentGroupRects, currentDragEl);
212
+ flipAnimate(groupList.map((item) => ({
213
+ key: item.key,
214
+ el: queryTabEl(item.key)
215
+ })).filter((entry) => Boolean(entry.el)), prevRects);
216
+ restoreScrollLeft(currentScrollLeft);
217
+ });
218
+ } else clearInlineStyles();
219
+ suppressNextClick();
220
+ isDragging.value = false;
221
+ draggingKey.value = "";
222
+ dragEl = null;
223
+ groupRects = /* @__PURE__ */ new Map();
224
+ dragPointerOffsetX = 0;
225
+ pendingKey = "";
226
+ dragGroup = void 0;
227
+ }
228
+ function handleMouseup() {
229
+ if (isDragging.value) endDrag();
230
+ else if (pendingKey) {
231
+ pendingKey = "";
232
+ draggingKey.value = "";
233
+ dragGroup = void 0;
234
+ }
235
+ }
236
+ const documentTarget = typeof document === "undefined" ? void 0 : document;
237
+ useEventListener(documentTarget, "mousemove", handleMousemove);
238
+ useEventListener(documentTarget, "mouseup", handleMouseup);
239
+ function getItemProps(key) {
240
+ const item = items.value.find((entry) => entry.key === key);
241
+ return { onMousedown: (event) => {
242
+ if (item) handleMousedown(event, item);
243
+ } };
244
+ }
245
+ return {
246
+ isDragging,
247
+ draggingKey,
248
+ getItemProps
249
+ };
250
+ }
251
+ //#endregion
252
+ export { useTabDrag };
@@ -3,6 +3,12 @@ import * as _$vue from "vue";
3
3
  import { SlotsType } from "vue";
4
4
 
5
5
  //#region src/components/Tabs/index.d.ts
6
+ /**
7
+ * 标签栏默认高度(px,首帧兜底值,基于默认主题实测取整)。
8
+ * 真实高度由 LayoutTabs 挂载后用 useElementSize 测量并写回 context,
9
+ * 此常量仅用于测量回填前的占位,避免 fixed 占位首帧塌陷。
10
+ */
11
+ declare const DEFAULT_TABS_HEIGHT = 41;
6
12
  declare const LayoutTabs: _$vue.DefineSetupFnComponent<LayoutTabsProps, LayoutTabsEmits, SlotsType<LayoutTabsSlots>, LayoutTabsProps, _$vue.PublicProps>;
7
13
  //#endregion
8
- export { LayoutTabs as default };
14
+ export { DEFAULT_TABS_HEIGHT, LayoutTabs as default };
@@ -1,21 +1,30 @@
1
1
  import { useLayoutBase } from "../../hooks/useLayoutBase.js";
2
2
  import { CONTEXT_MENU_ITEMS, getDisabledState, getMenuItemLabel } from "./contextMenu.js";
3
- import { Fragment, computed, createVNode, defineComponent, ref } from "vue";
3
+ import InnerTabs from "./InnerTabs.js";
4
+ import { Fragment, computed, createVNode, defineComponent, ref, watch } from "vue";
4
5
  import { unit } from "@gx-design-vue/pro-provider";
5
6
  import { classNames } from "@gx-design-vue/pro-utils";
6
- import { Dropdown, Tabs } from "antdv-next";
7
+ import { useElementSize } from "@vueuse/core";
8
+ import { Dropdown } from "antdv-next";
7
9
  import { GIcon } from "@gx-design-vue/icon";
8
10
  //#region src/components/Tabs/index.tsx
11
+ /**
12
+ * 标签栏默认高度(px,首帧兜底值,基于默认主题实测取整)。
13
+ * 真实高度由 LayoutTabs 挂载后用 useElementSize 测量并写回 context,
14
+ * 此常量仅用于测量回填前的占位,避免 fixed 占位首帧塌陷。
15
+ */
16
+ const DEFAULT_TABS_HEIGHT = 41;
9
17
  const LayoutTabs = /* @__PURE__ */ defineComponent((props, { slots }) => {
10
- const { prefixCls, proClasses, tabsState, contentFullscreen, header, isMobile } = useLayoutBase();
18
+ const { prefixCls, proClasses, tabsState, tabsHeight, setTabsHeight, contentFullscreen, header, isMobile } = useLayoutBase();
11
19
  const contextMenuActiveKey = ref("");
20
+ const tabsContentRef = ref();
21
+ const { height: measuredTabsHeight } = useElementSize(tabsContentRef, void 0, { box: "border-box" });
22
+ watch(measuredTabsHeight, (height) => {
23
+ if (height > 0) setTabsHeight?.(height);
24
+ }, { immediate: true });
12
25
  const dataSource = computed(() => tabsState?.dataSource?.value ?? []);
13
26
  const activeKey = computed(() => tabsState?.activeKey?.value ?? "");
14
27
  const checkIsFixed = computed(() => tabsState?.checkIsFixed ?? (() => false));
15
- const tabsType = computed(() => {
16
- if (props.config?.type === "card") return "card";
17
- return "line";
18
- });
19
28
  const isFixed = computed(() => !!props.config?.fixed && !isMobile.value);
20
29
  const tabsClassNames = computed(() => classNames(`${prefixCls.value}-tabs`, proClasses.value.tabs, {
21
30
  [`${prefixCls.value}-tabs-${props.config?.type}`]: props.config?.type,
@@ -55,7 +64,14 @@ const LayoutTabs = /* @__PURE__ */ defineComponent((props, { slots }) => {
55
64
  meta: {}
56
65
  };
57
66
  }
58
- function renderTabTitle(route) {
67
+ const innerItems = computed(() => dataSource.value.map((route) => ({
68
+ key: route.name,
69
+ label: route.meta?.title,
70
+ closable: !checkIsFixed.value(route) && dataSource.value.length > 1,
71
+ group: checkIsFixed.value(route) ? "fixed" : "normal"
72
+ })));
73
+ function renderTabSlot({ item }) {
74
+ const route = getRouteByName(item.key);
59
75
  const routeName = route.name;
60
76
  const isFixed = checkIsFixed.value(route);
61
77
  const customRender = slots.tabBarItem?.({ route }) ?? props.tabBarItem?.({ route });
@@ -74,11 +90,13 @@ const LayoutTabs = /* @__PURE__ */ defineComponent((props, { slots }) => {
74
90
  createVNode("span", { "class": `${prefixCls.value}-tabs-title-text` }, [route.meta?.title]),
75
91
  isFixed && createVNode(GIcon, {
76
92
  "type": "Pin",
77
- "class": `${prefixCls.value}-tabs-title-pin`
93
+ "class": `${prefixCls.value}-tabs-title-pin`,
94
+ "data-drag-ignore": "true"
78
95
  }, null),
79
96
  dataSource.value.length > 1 && !isFixed && createVNode(GIcon, {
80
97
  "type": "CloseOutlined",
81
98
  "class": `${prefixCls.value}-tabs-title-close`,
99
+ "data-drag-ignore": "true",
82
100
  "onClick": (event) => {
83
101
  event.stopPropagation();
84
102
  event.preventDefault();
@@ -87,11 +105,6 @@ const LayoutTabs = /* @__PURE__ */ defineComponent((props, { slots }) => {
87
105
  }, null)
88
106
  ])] });
89
107
  }
90
- const tabItems = computed(() => dataSource.value.map((route) => ({
91
- key: route.name,
92
- label: renderTabTitle(route),
93
- closable: !checkIsFixed.value(route)
94
- })));
95
108
  function renderRightExtra() {
96
109
  if (dataSource.value.length === 0) return null;
97
110
  return createVNode("div", { "class": classNames(`${prefixCls.value}-tabs-extra`) }, [
@@ -122,29 +135,31 @@ const LayoutTabs = /* @__PURE__ */ defineComponent((props, { slots }) => {
122
135
  }
123
136
  return () => {
124
137
  if (!tabsState || dataSource.value.length === 0) return null;
125
- return createVNode(Fragment, null, [createVNode("div", { "class": tabsClassNames.value }, [isFixed.value && createVNode("div", { "class": `${prefixCls.value}-tabs-placeholder` }, null), createVNode("div", {
138
+ return createVNode(Fragment, null, [createVNode("div", { "class": tabsClassNames.value }, [isFixed.value && createVNode("div", {
139
+ "class": `${prefixCls.value}-tabs-placeholder`,
140
+ "style": { height: unit(tabsHeight?.value ?? 41) }
141
+ }, null), createVNode("div", {
142
+ "ref": tabsContentRef,
126
143
  "class": classNames(`${prefixCls.value}-tabs-content`),
127
144
  "style": tabsContentStyle.value
128
- }, [createVNode(Tabs, {
129
- "type": tabsType.value,
130
- "hideAdd": true,
145
+ }, [createVNode(InnerTabs, {
146
+ "items": innerItems.value,
131
147
  "activeKey": activeKey.value,
132
- "items": tabItems.value,
148
+ "draggable": props.config?.draggable !== false,
133
149
  "onChange": (key) => {
134
150
  tabsState?.handleTabClick?.(key);
151
+ },
152
+ "onReorder": (fromKey, toKey) => {
153
+ const fromItem = innerItems.value.find((item) => item.key === fromKey);
154
+ if (fromItem?.group) tabsState?.reorderWithinGroup?.(fromKey, toKey, fromItem.group);
135
155
  }
136
- }, { rightExtra: renderRightExtra })])])]);
156
+ }, {
157
+ tab: renderTabSlot,
158
+ extra: renderRightExtra
159
+ })])])]);
137
160
  };
138
161
  }, {
139
162
  props: {
140
- items: {
141
- type: Array,
142
- required: false
143
- },
144
- tabs: {
145
- type: Array,
146
- required: false
147
- },
148
163
  config: {
149
164
  type: Object,
150
165
  required: false
@@ -163,4 +178,4 @@ const LayoutTabs = /* @__PURE__ */ defineComponent((props, { slots }) => {
163
178
  inheritAttrs: false
164
179
  });
165
180
  //#endregion
166
- export { LayoutTabs as default };
181
+ export { DEFAULT_TABS_HEIGHT, LayoutTabs as default };
@@ -1,8 +1,53 @@
1
1
  import { LayoutMenuRoute, LayoutTabsConfig } from "../../interface.js";
2
2
  import { CustomRender } from "@gx-design-vue/pro-utils";
3
- import { Tab } from "antdv-next/dist/tabs";
4
3
 
5
4
  //#region src/components/Tabs/interface.d.ts
5
+ /** 页签分组(拖拽排序约束:组内可拖,不跨级) */
6
+ type TabGroup = 'fixed' | 'normal';
7
+ /** 单个页签数据 */
8
+ interface InnerTabItem {
9
+ /** 唯一标识 */
10
+ key: string;
11
+ /** 标题(string / VNode),未提供 #tab slot 时默认渲染 */
12
+ label?: any;
13
+ /** 是否显示关闭按钮,默认 true */
14
+ closable?: boolean;
15
+ /** 是否禁用 */
16
+ disabled?: boolean;
17
+ /** 所属分组(拖拽排序时约束组内,不跨级) */
18
+ group?: TabGroup;
19
+ }
20
+ interface InnerTabsEmits {
21
+ 'update:activeKey': (key: string) => any;
22
+ 'change': (key: string) => any;
23
+ 'close': (key: string) => any;
24
+ /** 拖拽排序:从 fromKey 移到 toKey 位置(同组内) */
25
+ 'reorder': (fromKey: string, toKey: string) => any;
26
+ }
27
+ interface InnerTabsEmitsProps {
28
+ 'onUpdate:activeKey'?: InnerTabsEmits['update:activeKey'];
29
+ onChange?: InnerTabsEmits['change'];
30
+ onClose?: InnerTabsEmits['close'];
31
+ onReorder?: InnerTabsEmits['reorder'];
32
+ }
33
+ interface InnerTabsProps extends InnerTabsEmitsProps {
34
+ prefixCls?: string;
35
+ /** 页签列表 */
36
+ items?: InnerTabItem[];
37
+ /** 当前激活 key(支持 v-model:activeKey) */
38
+ activeKey?: string;
39
+ /** 是否开启拖拽排序,默认 true */
40
+ draggable?: boolean;
41
+ }
42
+ interface InnerTabsSlots {
43
+ /** 自定义单个页签内容(如上层挂右键菜单 / 图标) */
44
+ tab?: (props: {
45
+ item: InnerTabItem;
46
+ active: boolean;
47
+ }) => any;
48
+ /** 导航栏右侧额外区域 */
49
+ extra?: () => any;
50
+ }
6
51
  interface LayoutTabsEmits {
7
52
  'tabsChange': (tabs: LayoutMenuRoute[]) => void;
8
53
  'reloadPage': () => void;
@@ -14,10 +59,6 @@ interface LayoutTabsEmitsProps {
14
59
  onContentFullscreenChange?: LayoutTabsEmits['contentFullscreenChange'];
15
60
  }
16
61
  interface LayoutTabsProps extends LayoutTabsEmitsProps {
17
- /** 已转换好的 antdv-next 标签数据 */
18
- items?: Tab[];
19
- /** 原始标签路由 */
20
- tabs?: LayoutMenuRoute[];
21
62
  config?: LayoutTabsConfig;
22
63
  /** 标签项自定义(等价 #tabBarItem slot) */
23
64
  tabBarItem?: (slotProps: {
@@ -30,4 +71,4 @@ interface LayoutTabsSlots {
30
71
  }) => any;
31
72
  }
32
73
  //#endregion
33
- export { LayoutTabsEmits, LayoutTabsEmitsProps, LayoutTabsProps, LayoutTabsSlots };
74
+ export { InnerTabItem, InnerTabsEmits, InnerTabsEmitsProps, InnerTabsProps, InnerTabsSlots, LayoutTabsEmits, LayoutTabsEmitsProps, LayoutTabsProps, LayoutTabsSlots, TabGroup };
@@ -0,0 +1,34 @@
1
+ import * as _$vue from "vue";
2
+ import { GetDefaultToken } from "antdv-next/dist/theme/internal";
3
+
4
+ //#region src/components/Tabs/style/index.d.ts
5
+ /**
6
+ * InnerTabs 组件级 Token —— 仅承载尺寸相关字段;
7
+ * 卡片配色复用全局扁平 token `tabsColor*`(见 pro-provider/theme),保持与 layout 主题一致。
8
+ */
9
+ interface ComponentToken {
10
+ /** 单个页签高度 */
11
+ itemHeight: number | string;
12
+ /** 页签横向内边距 */
13
+ itemPaddingInline: number | string;
14
+ /** 页签纵向内边距 */
15
+ itemPaddingBlock: number | string;
16
+ /** 页签间距 */
17
+ itemGap: number | string;
18
+ /** 页签圆角 */
19
+ itemBorderRadius: number | string;
20
+ /** 标题字号 */
21
+ titleFontSize: number;
22
+ /** 标题最大宽度(超出省略) */
23
+ titleMaxWidth: number | string;
24
+ /** 关闭按钮尺寸 */
25
+ closeIconSize: number | string;
26
+ /** 导航栏纵向内边距 */
27
+ navPaddingBlock: number | string;
28
+ /** 导航栏横向内边距 */
29
+ navPaddingInline: number | string;
30
+ }
31
+ declare const prepareComponentToken: GetDefaultToken<'InnerTabs'>;
32
+ declare const _default: (prefixCls: _$vue.Ref<string>, rootCls?: _$vue.Ref<string | undefined>) => readonly [_$vue.Ref<string, string>, _$vue.ComputedRef<string | undefined>];
33
+ //#endregion
34
+ export { ComponentToken, _default as default, prepareComponentToken };
@@ -0,0 +1,140 @@
1
+ import { proGenStyleHooks } from "@gx-design-vue/pro-provider";
2
+ import { unit as unit$1 } from "@antdv-next/cssinjs";
3
+ //#region src/components/Tabs/style/index.ts
4
+ const prepareComponentToken = (token) => ({
5
+ itemHeight: token.controlHeightSM,
6
+ itemPaddingInline: token.paddingXS,
7
+ itemPaddingBlock: 2,
8
+ itemGap: token.marginXS,
9
+ itemBorderRadius: token.borderRadiusSM,
10
+ titleFontSize: token.fontSizeSM,
11
+ titleMaxWidth: 120,
12
+ closeIconSize: token.fontSizeSM,
13
+ navPaddingBlock: token.paddingXXS,
14
+ navPaddingInline: token.paddingSM
15
+ });
16
+ /**
17
+ * InnerTabs 样式:横向卡片式页签条(default / active 双态),无底部 ink-bar。
18
+ * 颜色取自扁平 token `tabsColor*`;mask-image 溢出淡出由组件内联 style 动态控制。
19
+ */
20
+ const genInnerTabsStyle = (token) => {
21
+ const { componentCls } = token;
22
+ return { [componentCls]: {
23
+ display: "flex",
24
+ minWidth: 0,
25
+ [`${componentCls}-nav`]: {
26
+ position: "relative",
27
+ display: "flex",
28
+ flex: "1 1 auto",
29
+ minWidth: 0,
30
+ alignItems: "center",
31
+ paddingBlock: token.navPaddingBlock
32
+ },
33
+ [`${componentCls}-nav-wrap`]: {
34
+ position: "relative",
35
+ flex: "1 1 auto",
36
+ minWidth: 0,
37
+ marginBlock: token.calc(token.marginXXS).mul(-1).equal(),
38
+ paddingBlock: token.marginXXS,
39
+ overflowX: "auto",
40
+ overflowY: "hidden",
41
+ overflowAnchor: "none",
42
+ whiteSpace: "nowrap",
43
+ scrollbarWidth: "none",
44
+ msOverflowStyle: "none",
45
+ "&::-webkit-scrollbar": { display: "none" }
46
+ },
47
+ [`${componentCls}-nav-list`]: {
48
+ display: "inline-flex",
49
+ alignItems: "center",
50
+ gap: token.itemGap,
51
+ overflowAnchor: "none"
52
+ },
53
+ [`${componentCls}-tab`]: {
54
+ display: "inline-flex",
55
+ alignItems: "center",
56
+ flex: "none",
57
+ boxSizing: "border-box",
58
+ height: token.itemHeight,
59
+ paddingInline: token.itemPaddingInline,
60
+ paddingBlock: token.itemPaddingBlock,
61
+ fontSize: token.titleFontSize,
62
+ lineHeight: 1,
63
+ color: token.tabsColorText,
64
+ backgroundColor: token.tabsColorBgContainer,
65
+ border: `${unit$1(token.lineWidth)} ${token.lineType} ${token.tabsColorBorder}`,
66
+ borderRadius: token.itemBorderRadius,
67
+ cursor: "pointer",
68
+ userSelect: "none",
69
+ transition: [
70
+ `color ${token.motionDurationMid} ${token.motionEaseInOut}`,
71
+ `background-color ${token.motionDurationMid} ${token.motionEaseInOut}`,
72
+ `border-color ${token.motionDurationMid} ${token.motionEaseInOut}`,
73
+ `box-shadow ${token.motionDurationMid} ${token.motionEaseInOut}`
74
+ ].join(", "),
75
+ "&:hover": {
76
+ color: token.tabsColorTextHover,
77
+ borderColor: token.tabsColorBorderHover,
78
+ backgroundColor: token.tabsColorBgContainerHover
79
+ },
80
+ [`&${componentCls}-tab-active`]: {
81
+ color: token.tabsColorTextActive,
82
+ borderColor: token.tabsColorBorderActive,
83
+ backgroundColor: token.tabsColorBgContainerActive,
84
+ "&:hover": {
85
+ color: token.tabsColorTextActive,
86
+ borderColor: token.tabsColorBorderActive,
87
+ backgroundColor: token.tabsColorBgContainerActive
88
+ }
89
+ },
90
+ [`&${componentCls}-tab-disabled`]: {
91
+ color: token.colorTextDisabled,
92
+ cursor: "not-allowed"
93
+ },
94
+ [`&${componentCls}-tab-dragging`]: {
95
+ zIndex: 1,
96
+ cursor: "grabbing",
97
+ color: token.tabsColorText,
98
+ backgroundColor: token.colorBgElevated,
99
+ borderColor: token.tabsColorBorderHover,
100
+ boxShadow: token.boxShadowSecondary,
101
+ opacity: 1,
102
+ transition: "none"
103
+ }
104
+ },
105
+ [`${componentCls}-tab-btn`]: {
106
+ display: "inline-block",
107
+ maxWidth: token.titleMaxWidth,
108
+ overflow: "hidden",
109
+ whiteSpace: "nowrap",
110
+ textOverflow: "ellipsis",
111
+ verticalAlign: "middle",
112
+ transition: `color ${token.motionDurationMid}`
113
+ },
114
+ [`${componentCls}-tab-remove`]: {
115
+ display: "inline-flex",
116
+ alignItems: "center",
117
+ justifyContent: "center",
118
+ marginInlineStart: token.marginXXS,
119
+ fontSize: token.closeIconSize,
120
+ lineHeight: 1,
121
+ color: token.tabsColorIcon,
122
+ cursor: "pointer",
123
+ transition: `all ${token.motionDurationMid}`,
124
+ "&:hover": { color: token.tabsColorIconHover }
125
+ },
126
+ [`${componentCls}-tab-active ${componentCls}-tab-remove`]: {
127
+ color: token.tabsColorIconActive,
128
+ "&:hover": { color: token.tabsColorIconActive }
129
+ },
130
+ [`${componentCls}-extra`]: {
131
+ display: "inline-flex",
132
+ flex: "none",
133
+ alignItems: "center",
134
+ marginInlineStart: token.marginSM
135
+ }
136
+ } };
137
+ };
138
+ var style_default = proGenStyleHooks("InnerTabs", genInnerTabsStyle, prepareComponentToken);
139
+ //#endregion
140
+ export { style_default as default, prepareComponentToken };
@@ -34,6 +34,8 @@ interface LayoutContextProps {
34
34
  cssVarCls: ComputedRef<string>;
35
35
  rootCls: ComputedRef<string>;
36
36
  hasFooterToolbar: Ref<boolean>;
37
+ tabsHeight: Ref<number>;
38
+ setTabsHeight: (value: number) => void;
37
39
  /**
38
40
  * PageContainer 级别的面包屑 slot 覆盖。
39
41
  * 当 position='header' 时,PageContainer 的 breadcrumb slot 通过此 ref 传到 Header。
@@ -19,7 +19,8 @@ const DEFAULT_LAYOUT_CONFIG = {
19
19
  },
20
20
  breadcrumb: {
21
21
  placement: "header",
22
- showHome: true
22
+ showHome: true,
23
+ homePath: "/"
23
24
  },
24
25
  tabsConfig: {
25
26
  type: "button",