vue-stack-tabs 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +14 -0
- package/README.md +14 -0
- package/dist/hooks/tabPageContext.d.ts +18 -0
- package/dist/hooks/useTabLoading.d.ts +2 -1
- package/dist/hooks/useTabRouter.d.ts +5 -1
- package/dist/style.css +1 -1
- package/dist/vue-stack-tabs.es.js +1 -1
- package/package.json +2 -2
- package/src/lib/assets/image/open.svg +1 -1
- package/src/lib/assets/style/common/mixins.scss +6 -6
- package/src/lib/assets/style/common/theme.scss +148 -0
- package/src/lib/assets/style/common/var.scss +104 -1
- package/src/lib/assets/style/index.scss +1 -0
- package/src/lib/assets/style/loading.scss +3 -3
- package/src/lib/assets/style/stack-tab.scss +50 -50
- package/src/lib/components/ContextMenu/index.vue +3 -1
- package/src/lib/hooks/tabPageContext.ts +50 -0
- package/src/lib/hooks/tabPanel/pageComponentFactory.tsx +13 -5
- package/src/lib/hooks/useTabLoading.ts +6 -4
- package/src/lib/hooks/useTabRouter.ts +11 -14
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
$stack-tab-color-white: #fff !default;
|
|
9
9
|
$stack-tab-color-black: #000 !default;
|
|
10
10
|
|
|
11
|
+
// 主色上的文字/图标(深色主题下会反转为深色)
|
|
12
|
+
$stack-tab-color-accent-contrast: $stack-tab-color-white !default;
|
|
13
|
+
|
|
11
14
|
// 主色
|
|
12
15
|
$stack-tab-color-primary: #409eff !default;
|
|
13
16
|
$stack-tab-color-primary-invert: #ffac30 !default;
|
|
@@ -42,6 +45,7 @@ $stack-tab-iframe-error-bg: rgb(255 255 255 / 92%) !default;
|
|
|
42
45
|
|
|
43
46
|
$stack-tab-border-color: #f0f0f0 !default;
|
|
44
47
|
$stack-tab-border: 1px solid $stack-tab-border-color !default;
|
|
48
|
+
$stack-tab-color-divider: #a9a9a9 !default;
|
|
45
49
|
$stack-tab-tab-radius: 8px 8px 0 0 !default;
|
|
46
50
|
$stack-tab-contextmenu-radius: 10px 0 !default;
|
|
47
51
|
|
|
@@ -88,13 +92,14 @@ $stack-tab-transition-slide:
|
|
|
88
92
|
// ============================================================
|
|
89
93
|
|
|
90
94
|
$stack-tab-shadow-color: rgb(0 0 0 / 60%) !default;
|
|
95
|
+
$stack-tab-shadow-soft-color: rgb(0 0 0 / 10%) !default;
|
|
91
96
|
$stack-tab-shadow-left: -5px 0 5px -5px $stack-tab-shadow-color !default;
|
|
92
97
|
$stack-tab-shadow-right: 5px 0 5px -5px $stack-tab-shadow-color !default;
|
|
93
98
|
$stack-tab-shadow-right-inset: inset -5px 0 5px -5px $stack-tab-shadow-color !default;
|
|
94
99
|
$stack-tab-shadow-left-inset: inset 5px 0 5px -5px $stack-tab-shadow-color !default;
|
|
95
100
|
$stack-tab-shadow-top-inset: inset 0 5px 5px -5px $stack-tab-shadow-color !default;
|
|
96
101
|
$stack-tab-shadow-bottom-inset: inset 0 -5px 5px -5px $stack-tab-shadow-color !default;
|
|
97
|
-
$stack-tab-shadow-context-menu: 5px 5px 5px
|
|
102
|
+
$stack-tab-shadow-context-menu: 5px 5px 5px $stack-tab-shadow-soft-color !default;
|
|
98
103
|
|
|
99
104
|
// ============================================================
|
|
100
105
|
// 加载态
|
|
@@ -106,3 +111,101 @@ $stack-tab-loading-size: 50px !default;
|
|
|
106
111
|
$stack-tab-loading-border-width: 8px !default;
|
|
107
112
|
$stack-tab-loading-color: #007bff97 !default;
|
|
108
113
|
$stack-tab-loading-duration: 1s !default;
|
|
114
|
+
|
|
115
|
+
// ============================================================
|
|
116
|
+
// 主题令牌(运行时 CSS 变量)
|
|
117
|
+
//
|
|
118
|
+
// 规则统一消费这一组变量;默认值定义在 theme.scss 的 :root 中。
|
|
119
|
+
// var() 的 fallback 使用上面的编译期变量,保证单独编译的 scoped
|
|
120
|
+
// 样式或未加载主题入口时仍有正确颜色,同时保留 SCSS 变量定制能力。
|
|
121
|
+
// ============================================================
|
|
122
|
+
|
|
123
|
+
// accent
|
|
124
|
+
$stack-tab-accent: var(--stack-tab-accent, #{$stack-tab-color-primary}) !default;
|
|
125
|
+
$stack-tab-accent-active: var(
|
|
126
|
+
--stack-tab-accent-active,
|
|
127
|
+
#{$stack-tab-color-primary-activate}
|
|
128
|
+
) !default;
|
|
129
|
+
$stack-tab-accent-contrast: var(
|
|
130
|
+
--stack-tab-accent-contrast,
|
|
131
|
+
#{$stack-tab-color-accent-contrast}
|
|
132
|
+
) !default;
|
|
133
|
+
$stack-tab-accent-shadow: var(
|
|
134
|
+
--stack-tab-accent-shadow,
|
|
135
|
+
#{rgba($stack-tab-color-primary, 0.8)}
|
|
136
|
+
) !default;
|
|
137
|
+
|
|
138
|
+
// surface
|
|
139
|
+
$stack-tab-surface: var(--stack-tab-surface, #{$stack-tab-background-color-base}) !default;
|
|
140
|
+
$stack-tab-surface-muted: var(
|
|
141
|
+
--stack-tab-surface-muted,
|
|
142
|
+
#{$stack-tab-background-color-inactive}
|
|
143
|
+
) !default;
|
|
144
|
+
$stack-tab-overlay: var(--stack-tab-overlay, #{$stack-tab-loading-mask-color}) !default;
|
|
145
|
+
|
|
146
|
+
// text / icon
|
|
147
|
+
$stack-tab-text: var(--stack-tab-text, #{$stack-tab-text-color}) !default;
|
|
148
|
+
$stack-tab-text-strong: var(--stack-tab-text-strong, #{$stack-tab-color-black}) !default;
|
|
149
|
+
$stack-tab-text-disabled: var(--stack-tab-text-disabled, #{$stack-tab-color-disabled}) !default;
|
|
150
|
+
$stack-tab-icon-mask: var(--stack-tab-mask, #{$stack-tab-text-color}) !default;
|
|
151
|
+
$stack-tab-font-size-token: var(--stack-tab-font-size, #{$stack-tab-font-size}) !default;
|
|
152
|
+
|
|
153
|
+
// feedback
|
|
154
|
+
$stack-tab-scrollbar: var(--stack-tab-scrollbar, #{$stack-tab-color-scrollbar}) !default;
|
|
155
|
+
|
|
156
|
+
// border
|
|
157
|
+
$stack-tab-border-color-token: var(--stack-tab-border, #{$stack-tab-border-color}) !default;
|
|
158
|
+
$stack-tab-border-token: 1px solid $stack-tab-border-color-token !default;
|
|
159
|
+
$stack-tab-divider: var(--stack-tab-divider, #{$stack-tab-color-divider}) !default;
|
|
160
|
+
|
|
161
|
+
// shadow:颜色层
|
|
162
|
+
$stack-tab-shadow-color-token: var(--stack-tab-shadow-color, #{$stack-tab-shadow-color}) !default;
|
|
163
|
+
|
|
164
|
+
// shadow:整体层
|
|
165
|
+
$stack-tab-shadow-tab: var(
|
|
166
|
+
--stack-tab-shadow-tab,
|
|
167
|
+
#{$stack-tab-shadow-left},
|
|
168
|
+
#{$stack-tab-shadow-right}
|
|
169
|
+
) !default;
|
|
170
|
+
$stack-tab-shadow-tab-left: var(--stack-tab-shadow-tab-left, #{$stack-tab-shadow-left}) !default;
|
|
171
|
+
$stack-tab-shadow-tab-right: var(--stack-tab-shadow-tab-right, #{$stack-tab-shadow-right}) !default;
|
|
172
|
+
$stack-tab-shadow-menu: var(
|
|
173
|
+
--stack-tab-shadow-menu,
|
|
174
|
+
5px 5px 5px #{$stack-tab-shadow-soft-color}
|
|
175
|
+
) !default;
|
|
176
|
+
$stack-tab-shadow-drop: var(--stack-tab-shadow-drop, 0 0 6px #{$stack-tab-accent-shadow}) !default;
|
|
177
|
+
|
|
178
|
+
// shadow:inset 工具类
|
|
179
|
+
$stack-tab-shadow-inset-left: var(
|
|
180
|
+
--stack-tab-shadow-inset-left,
|
|
181
|
+
#{$stack-tab-shadow-left-inset}
|
|
182
|
+
) !default;
|
|
183
|
+
$stack-tab-shadow-inset-right: var(
|
|
184
|
+
--stack-tab-shadow-inset-right,
|
|
185
|
+
#{$stack-tab-shadow-right-inset}
|
|
186
|
+
) !default;
|
|
187
|
+
$stack-tab-shadow-inset-top: var(
|
|
188
|
+
--stack-tab-shadow-inset-top,
|
|
189
|
+
#{$stack-tab-shadow-top-inset}
|
|
190
|
+
) !default;
|
|
191
|
+
$stack-tab-shadow-inset-bottom: var(
|
|
192
|
+
--stack-tab-shadow-inset-bottom,
|
|
193
|
+
#{$stack-tab-shadow-bottom-inset}
|
|
194
|
+
) !default;
|
|
195
|
+
$stack-tab-shadow-inset-all: var(
|
|
196
|
+
--stack-tab-shadow-inset-all,
|
|
197
|
+
#{$stack-tab-shadow-left-inset},
|
|
198
|
+
#{$stack-tab-shadow-right-inset}
|
|
199
|
+
) !default;
|
|
200
|
+
$stack-tab-shadow-inset-vertical: var(
|
|
201
|
+
--stack-tab-shadow-inset-vertical,
|
|
202
|
+
#{$stack-tab-shadow-top-inset},
|
|
203
|
+
#{$stack-tab-shadow-bottom-inset}
|
|
204
|
+
) !default;
|
|
205
|
+
|
|
206
|
+
// badge
|
|
207
|
+
$stack-tab-badge-default: var(--stack-tab-badge-default, #{$stack-tab-color-danger}) !default;
|
|
208
|
+
$stack-tab-badge-primary: var(--stack-tab-badge-primary, #{$stack-tab-color-primary}) !default;
|
|
209
|
+
|
|
210
|
+
// loading
|
|
211
|
+
$stack-tab-loading-accent: var(--stack-tab-loading-accent, #{$stack-tab-loading-color}) !default;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
&__mask {
|
|
7
7
|
@include absolute-fill;
|
|
8
8
|
@include flex-center;
|
|
9
|
-
background-color: $stack-tab-
|
|
9
|
+
background-color: $stack-tab-overlay;
|
|
10
10
|
opacity: $stack-tab-loading-mask-opacity;
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
width: $stack-tab-loading-size;
|
|
17
17
|
aspect-ratio: 1;
|
|
18
18
|
border: $stack-tab-loading-border-width solid transparent;
|
|
19
|
-
border-right-color: $stack-tab-loading-
|
|
19
|
+
border-right-color: $stack-tab-loading-accent;
|
|
20
20
|
border-radius: 50%;
|
|
21
21
|
animation: stack-tab-loading-rotate $stack-tab-loading-duration infinite linear;
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
position: absolute;
|
|
27
27
|
inset: -$stack-tab-loading-border-width;
|
|
28
28
|
border: $stack-tab-loading-border-width solid transparent;
|
|
29
|
-
border-right-color: $stack-tab-loading-
|
|
29
|
+
border-right-color: $stack-tab-loading-accent;
|
|
30
30
|
border-radius: 50%;
|
|
31
31
|
animation: stack-tab-loading-rotate $stack-tab-loading-duration * 2 infinite linear;
|
|
32
32
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
.stack-tab {
|
|
6
6
|
display: flex;
|
|
7
7
|
min-height: 300px;
|
|
8
|
-
background-color: $stack-tab-
|
|
8
|
+
background-color: $stack-tab-surface;
|
|
9
9
|
flex-direction: column;
|
|
10
10
|
gap: 5px;
|
|
11
11
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
.stack-tab__header-button[disabled] & {
|
|
43
43
|
cursor: not-allowed;
|
|
44
|
-
background-color: $stack-tab-
|
|
44
|
+
background-color: $stack-tab-text-disabled;
|
|
45
45
|
transition: none;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
display: flex;
|
|
58
58
|
width: 100%;
|
|
59
59
|
height: $stack-tab-head-height;
|
|
60
|
-
border-bottom: 1px solid $stack-tab-border-color;
|
|
60
|
+
border-bottom: 1px solid $stack-tab-border-color-token;
|
|
61
61
|
box-sizing: border-box;
|
|
62
62
|
|
|
63
63
|
// tab header 上面的按钮
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
height: $stack-tab-head-height;
|
|
67
67
|
width: fit-content;
|
|
68
68
|
overflow: hidden;
|
|
69
|
-
background-color: $stack-tab-
|
|
69
|
+
background-color: $stack-tab-surface;
|
|
70
70
|
cursor: pointer;
|
|
71
71
|
display: flex;
|
|
72
72
|
align-items: center;
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
|
|
77
77
|
// 按钮禁用样式
|
|
78
78
|
&[disabled] {
|
|
79
|
-
background-color: $stack-tab-
|
|
79
|
+
background-color: $stack-tab-surface-muted;
|
|
80
80
|
box-shadow: none;
|
|
81
81
|
cursor: not-allowed;
|
|
82
82
|
}
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
bottom: 0;
|
|
114
114
|
left: 0;
|
|
115
115
|
height: $stack-tab-scroll-height;
|
|
116
|
-
background-color: $stack-tab-
|
|
116
|
+
background-color: $stack-tab-scrollbar;
|
|
117
117
|
border-radius: $stack-tab-scroll-border-radius;
|
|
118
118
|
opacity: 0;
|
|
119
119
|
transition: $stack-tab-transition-fast;
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
top: 0;
|
|
130
130
|
left: 0;
|
|
131
131
|
height: 100%;
|
|
132
|
-
background-color: $stack-tab-
|
|
132
|
+
background-color: $stack-tab-scrollbar;
|
|
133
133
|
border-radius: $stack-tab-scroll-border-radius;
|
|
134
134
|
transition: background-color $stack-tab-transition-duration ease-in-out;
|
|
135
135
|
// 拖拽由 Pointer Events 接管,避免触摸时被浏览器手势打断
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
|
|
146
146
|
.stack-tab__scrollbar--dragging &,
|
|
147
147
|
&:hover {
|
|
148
|
-
background-color: $stack-tab-
|
|
148
|
+
background-color: $stack-tab-accent !important;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -166,11 +166,11 @@
|
|
|
166
166
|
position: relative;
|
|
167
167
|
display: flex;
|
|
168
168
|
padding: 0 $stack-tab-padding;
|
|
169
|
-
font-size: $stack-tab-font-size;
|
|
170
|
-
color: $stack-tab-text
|
|
169
|
+
font-size: $stack-tab-font-size-token;
|
|
170
|
+
color: $stack-tab-text;
|
|
171
171
|
cursor: pointer;
|
|
172
|
-
background-color: $stack-tab-
|
|
173
|
-
border: $stack-tab-border;
|
|
172
|
+
background-color: $stack-tab-surface-muted;
|
|
173
|
+
border: $stack-tab-border-token;
|
|
174
174
|
border-left: none;
|
|
175
175
|
border-radius: $stack-tab-tab-radius;
|
|
176
176
|
transition: $stack-tab-transition-fast;
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
margin-top: 5px;
|
|
183
183
|
|
|
184
184
|
&:first-child {
|
|
185
|
-
border-left: $stack-tab-border;
|
|
185
|
+
border-left: $stack-tab-border-token;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
&--dragging {
|
|
@@ -197,9 +197,9 @@
|
|
|
197
197
|
bottom: 2px;
|
|
198
198
|
width: 5px;
|
|
199
199
|
content: '';
|
|
200
|
-
background: $stack-tab-
|
|
200
|
+
background: $stack-tab-accent;
|
|
201
201
|
border-radius: 2px;
|
|
202
|
-
box-shadow:
|
|
202
|
+
box-shadow: $stack-tab-shadow-drop;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
&--drop-before::before {
|
|
@@ -212,21 +212,21 @@
|
|
|
212
212
|
|
|
213
213
|
// 激活右键菜单的页签
|
|
214
214
|
&--context-menu {
|
|
215
|
-
color: $stack-tab-
|
|
215
|
+
color: $stack-tab-text-strong;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// 激活的页签
|
|
219
219
|
&--active {
|
|
220
220
|
z-index: 1;
|
|
221
|
-
background-color: $stack-tab-
|
|
222
|
-
border-bottom-color: $stack-tab-
|
|
223
|
-
box-shadow: $stack-tab-shadow-
|
|
221
|
+
background-color: $stack-tab-surface;
|
|
222
|
+
border-bottom-color: $stack-tab-surface;
|
|
223
|
+
box-shadow: $stack-tab-shadow-tab;
|
|
224
224
|
margin-top: unset;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
&:hover,
|
|
228
228
|
&--active {
|
|
229
|
-
color: $stack-tab-
|
|
229
|
+
color: $stack-tab-accent;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// 可关闭标签在悬停/激活时给关闭按钮腾出空间
|
|
@@ -300,19 +300,19 @@
|
|
|
300
300
|
padding: 0.3rem;
|
|
301
301
|
font-size: 0.5rem;
|
|
302
302
|
line-height: 0.4rem;
|
|
303
|
-
color: $stack-tab-
|
|
303
|
+
color: $stack-tab-accent-contrast;
|
|
304
304
|
text-align: center;
|
|
305
|
-
border: 1px solid $stack-tab-
|
|
305
|
+
border: 1px solid $stack-tab-surface;
|
|
306
306
|
border-radius: 0.5rem;
|
|
307
307
|
transform: translateX(100%) translateY(-50%);
|
|
308
308
|
box-sizing: border-box;
|
|
309
309
|
|
|
310
310
|
&--default {
|
|
311
|
-
background: $stack-tab-
|
|
311
|
+
background: $stack-tab-badge-default;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
&--primary {
|
|
315
|
-
background: $stack-tab-
|
|
315
|
+
background: $stack-tab-badge-primary;
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
}
|
|
@@ -361,11 +361,11 @@
|
|
|
361
361
|
|
|
362
362
|
&-retry,
|
|
363
363
|
&-open {
|
|
364
|
-
border: $stack-tab-border;
|
|
364
|
+
border: $stack-tab-border-token;
|
|
365
365
|
padding: 4px 10px;
|
|
366
|
-
color: $stack-tab-text
|
|
366
|
+
color: $stack-tab-text;
|
|
367
367
|
cursor: pointer;
|
|
368
|
-
background: $stack-tab-
|
|
368
|
+
background: $stack-tab-surface;
|
|
369
369
|
border-radius: 6px;
|
|
370
370
|
font: inherit;
|
|
371
371
|
}
|
|
@@ -377,11 +377,11 @@
|
|
|
377
377
|
z-index: 999;
|
|
378
378
|
min-width: 120px;
|
|
379
379
|
padding: 8px;
|
|
380
|
-
font-size: $stack-tab-font-size;
|
|
381
|
-
background: $stack-tab-
|
|
382
|
-
border: $stack-tab-border;
|
|
380
|
+
font-size: $stack-tab-font-size-token;
|
|
381
|
+
background: $stack-tab-surface;
|
|
382
|
+
border: $stack-tab-border-token;
|
|
383
383
|
border-radius: $stack-tab-contextmenu-radius;
|
|
384
|
-
box-shadow: $stack-tab-shadow-
|
|
384
|
+
box-shadow: $stack-tab-shadow-menu;
|
|
385
385
|
transform-origin: left top;
|
|
386
386
|
|
|
387
387
|
&-item {
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
padding: 0 10px;
|
|
397
397
|
line-height: 30px;
|
|
398
398
|
box-sizing: border-box;
|
|
399
|
-
color: $stack-tab-text
|
|
399
|
+
color: $stack-tab-text;
|
|
400
400
|
cursor: pointer;
|
|
401
401
|
background: transparent;
|
|
402
402
|
border-radius: $stack-tab-contextmenu-radius;
|
|
@@ -409,11 +409,11 @@
|
|
|
409
409
|
&:hover,
|
|
410
410
|
&:active,
|
|
411
411
|
&:focus-visible {
|
|
412
|
-
color: $stack-tab-
|
|
413
|
-
background-color: $stack-tab-
|
|
412
|
+
color: $stack-tab-accent-contrast;
|
|
413
|
+
background-color: $stack-tab-accent;
|
|
414
414
|
|
|
415
415
|
.stack-tab__icon--mask {
|
|
416
|
-
background-color: $stack-tab-
|
|
416
|
+
background-color: $stack-tab-accent-contrast;
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
|
|
@@ -422,62 +422,62 @@
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
&[disabled] {
|
|
425
|
-
color: $stack-tab-
|
|
425
|
+
color: $stack-tab-text-disabled;
|
|
426
426
|
pointer-events: none;
|
|
427
427
|
cursor: default;
|
|
428
428
|
background: none;
|
|
429
429
|
|
|
430
430
|
.stack-tab__icon--mask {
|
|
431
|
-
background-color: $stack-tab-
|
|
431
|
+
background-color: $stack-tab-text-disabled;
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
&-icon {
|
|
437
|
-
width: $stack-tab-font-size;
|
|
438
|
-
height: $stack-tab-font-size;
|
|
437
|
+
width: $stack-tab-font-size-token;
|
|
438
|
+
height: $stack-tab-font-size-token;
|
|
439
439
|
transition: background-color 0.2s ease-in-out;
|
|
440
440
|
|
|
441
441
|
&.stack-tab__icon--mask {
|
|
442
|
-
width: $stack-tab-font-size;
|
|
443
|
-
height: $stack-tab-font-size;
|
|
444
|
-
background-color: $stack-tab-
|
|
442
|
+
width: $stack-tab-font-size-token;
|
|
443
|
+
height: $stack-tab-font-size-token;
|
|
444
|
+
background-color: $stack-tab-icon-mask;
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
.stack-tab__shadow-left {
|
|
451
|
-
box-shadow: $stack-tab-shadow-left;
|
|
451
|
+
box-shadow: $stack-tab-shadow-tab-left;
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
.stack-tab__shadow-right {
|
|
455
|
-
box-shadow: $stack-tab-shadow-right;
|
|
455
|
+
box-shadow: $stack-tab-shadow-tab-right;
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
// 内阴影
|
|
459
459
|
.stack-tab__shadow-inset {
|
|
460
460
|
&-left {
|
|
461
|
-
@include inset-shadow($stack-tab-shadow-left
|
|
461
|
+
@include inset-shadow($stack-tab-shadow-inset-left);
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
&-right {
|
|
465
|
-
@include inset-shadow($stack-tab-shadow-right
|
|
465
|
+
@include inset-shadow($stack-tab-shadow-inset-right);
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
&-all {
|
|
469
|
-
@include inset-shadow($stack-tab-shadow-
|
|
469
|
+
@include inset-shadow($stack-tab-shadow-inset-all);
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
&-top {
|
|
473
|
-
@include inset-shadow($stack-tab-shadow-top
|
|
473
|
+
@include inset-shadow($stack-tab-shadow-inset-top);
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
&-bottom {
|
|
477
|
-
@include inset-shadow($stack-tab-shadow-bottom
|
|
477
|
+
@include inset-shadow($stack-tab-shadow-inset-bottom);
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
&-vertical {
|
|
481
|
-
@include inset-shadow($stack-tab-shadow-
|
|
481
|
+
@include inset-shadow($stack-tab-shadow-inset-vertical);
|
|
482
482
|
}
|
|
483
483
|
}
|
|
@@ -218,6 +218,8 @@ onMounted(() => {
|
|
|
218
218
|
</script>
|
|
219
219
|
|
|
220
220
|
<style lang="scss" scoped>
|
|
221
|
+
@use '../../assets/style/common/var' as *;
|
|
222
|
+
|
|
221
223
|
.divider {
|
|
222
224
|
position: relative;
|
|
223
225
|
height: 1px;
|
|
@@ -231,7 +233,7 @@ onMounted(() => {
|
|
|
231
233
|
left: 0;
|
|
232
234
|
width: 100%;
|
|
233
235
|
height: 1px;
|
|
234
|
-
background-image: linear-gradient(to right, transparent,
|
|
236
|
+
background-image: linear-gradient(to right, transparent, $stack-tab-divider, transparent);
|
|
235
237
|
content: '';
|
|
236
238
|
}
|
|
237
239
|
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 页面级运行时上下文。
|
|
3
|
+
*
|
|
4
|
+
* 每个缓存页面(cacheName / pId)都归属于一个标签(tabId / tId)。
|
|
5
|
+
* 这组信息由页面缓存包装组件通过 provide 提供,页面内的 useTabRouter、
|
|
6
|
+
* useTabLoading 通过 inject 读取,不再作为 attrs 注入到用户页面组件。
|
|
7
|
+
*
|
|
8
|
+
* 这样做的原因:
|
|
9
|
+
* - 多根节点(Fragment)页面无法继承 attrs,Vue 会在开发环境提示
|
|
10
|
+
* “Extraneous non-props attributes”;
|
|
11
|
+
* - 单根节点页面会把 tId / pId 透传到根 DOM 上,造成内部 ID 泄漏;
|
|
12
|
+
* - provide/inject 的边界与缓存包装组件一致,也允许页面内的嵌套组件复用。
|
|
13
|
+
*/
|
|
14
|
+
import type { InjectionKey } from 'vue'
|
|
15
|
+
import { getCurrentInstance, hasInjectionContext, inject } from 'vue'
|
|
16
|
+
|
|
17
|
+
export interface TabPageContext {
|
|
18
|
+
/** 当前页面所属的标签 id(旧实现中的 attrs.tId) */
|
|
19
|
+
tabId: string
|
|
20
|
+
/** 当前页面的缓存 id,即 cacheName(旧实现中的 attrs.pId) */
|
|
21
|
+
pageId: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const tabPageContextKey: InjectionKey<TabPageContext> = Symbol('tabPageContext')
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 解析当前页面上下文。
|
|
28
|
+
*
|
|
29
|
+
* 优先读取缓存包装组件 provide 的上下文;attrs / props 仅作为旧版
|
|
30
|
+
* cloneVNode 注入方式以及手工挂载场景的兼容回退。
|
|
31
|
+
*
|
|
32
|
+
* 返回值允许只包含其中一个字段:useTabLoading 只依赖 tabId,
|
|
33
|
+
* 旧代码或手工挂载可能只传了其中一个 id。
|
|
34
|
+
*/
|
|
35
|
+
export const resolveTabPageContext = (): Partial<TabPageContext> | null => {
|
|
36
|
+
if (hasInjectionContext()) {
|
|
37
|
+
const injected = inject(tabPageContextKey, null)
|
|
38
|
+
if (injected) return injected
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const instance = getCurrentInstance()
|
|
42
|
+
if (!instance) return null
|
|
43
|
+
|
|
44
|
+
const tabId = instance.attrs.tId ?? instance.props.tId
|
|
45
|
+
const pageId = instance.attrs.pId ?? instance.props.pId
|
|
46
|
+
const context: Partial<TabPageContext> = {}
|
|
47
|
+
if (typeof tabId === 'string') context.tabId = tabId
|
|
48
|
+
if (typeof pageId === 'string') context.pageId = pageId
|
|
49
|
+
return context.tabId !== undefined || context.pageId !== undefined ? context : null
|
|
50
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
onDeactivated,
|
|
14
14
|
onMounted,
|
|
15
15
|
onUnmounted,
|
|
16
|
+
provide,
|
|
16
17
|
ref,
|
|
17
18
|
shallowRef,
|
|
18
19
|
watch
|
|
@@ -21,6 +22,7 @@ import type { DefineComponent, PropType, VNode } from 'vue'
|
|
|
21
22
|
import type { ITabBase } from '../../model/TabModel'
|
|
22
23
|
import PageLoading from '../../components/PageLoading.vue'
|
|
23
24
|
import type { StackTabsRuntimeContext } from '../stackTabsContext'
|
|
25
|
+
import { tabPageContextKey } from '../tabPageContext'
|
|
24
26
|
import { useScrollShadow } from '../useScrollShadow'
|
|
25
27
|
|
|
26
28
|
interface PageComponentFactoryOptions {
|
|
@@ -139,6 +141,14 @@ export const createPageComponentFactory = (options: PageComponentFactoryOptions)
|
|
|
139
141
|
},
|
|
140
142
|
emits: ['onLoaded'],
|
|
141
143
|
setup(props, context) {
|
|
144
|
+
// 页面上下文只在缓存包装组件这一层 provide,页面组件及其嵌套组件都可 inject。
|
|
145
|
+
// 不再把 tId / pId 作为 attrs 注入页面:多根节点页面会触发 Vue 的
|
|
146
|
+
// “Extraneous non-props attributes”警告,单根节点页面则会把内部 ID 透传到 DOM。
|
|
147
|
+
provide(tabPageContextKey, {
|
|
148
|
+
tabId: tabInfo.id!,
|
|
149
|
+
pageId: cacheName
|
|
150
|
+
})
|
|
151
|
+
|
|
142
152
|
const localBackParams = ref<Record<string, unknown> | null>(null)
|
|
143
153
|
const lastVnode = shallowRef<VNode | null | undefined>(null)
|
|
144
154
|
const lastCloned = shallowRef<VNode | null>(null)
|
|
@@ -337,12 +347,10 @@ export const createPageComponentFactory = (options: PageComponentFactoryOptions)
|
|
|
337
347
|
const vnode = props.vnode
|
|
338
348
|
const backParams = localBackParams.value
|
|
339
349
|
if (vnode !== lastVnode.value || backParams !== lastBackParams.value) {
|
|
350
|
+
// _back 是页面需要读取的公开 prop,仍通过 cloneVNode 注入;
|
|
351
|
+
// tId / pId 已改为 provide/inject,不再污染页面 attrs。
|
|
340
352
|
lastCloned.value = vnode
|
|
341
|
-
? cloneVNode(vnode, {
|
|
342
|
-
tId: tabInfo.id,
|
|
343
|
-
pId: cacheName,
|
|
344
|
-
...(backParams ? { _back: backParams } : {})
|
|
345
|
-
})
|
|
353
|
+
? cloneVNode(vnode, backParams ? { _back: backParams } : null)
|
|
346
354
|
: null
|
|
347
355
|
lastVnode.value = vnode
|
|
348
356
|
lastBackParams.value = backParams
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { onUnmounted } from 'vue'
|
|
2
2
|
import { TabEventType, useTabEmitter } from '../hooks/useTabEventBus'
|
|
3
|
+
import { resolveTabPageContext } from './tabPageContext'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* useTabLoading - 标签内页面加载状态 Hook
|
|
6
7
|
*
|
|
7
8
|
* 职责:通过 PAGE_LOADING 事件控制 PageLoading 组件的显示/隐藏
|
|
8
|
-
*
|
|
9
|
+
* 使用范围:标签内的页面组件;tabId 由缓存包装组件 provide,
|
|
10
|
+
* 页面通过 tabPageContext 解析(兼容旧版 attrs / props 注入)。
|
|
9
11
|
*/
|
|
10
12
|
export default () => {
|
|
11
13
|
const emitter = useTabEmitter()
|
|
12
|
-
const
|
|
13
|
-
const getTabId = () =>
|
|
14
|
+
const pageContext = resolveTabPageContext()
|
|
15
|
+
const getTabId = () => pageContext?.tabId ?? ''
|
|
14
16
|
onUnmounted(() => {
|
|
15
17
|
closeTabLoading()
|
|
16
18
|
})
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { getCurrentInstance } from 'vue'
|
|
2
|
-
import type { ComponentInternalInstance } from 'vue'
|
|
3
1
|
import type { LocationQueryRaw, RouteLocationPathRaw } from 'vue-router'
|
|
4
2
|
import { isNavigationFailure, useRoute, useRouter } from 'vue-router'
|
|
5
3
|
|
|
6
4
|
import useTabPanel from './useTabPanel'
|
|
5
|
+
import { resolveTabPageContext } from './tabPageContext'
|
|
7
6
|
import {
|
|
8
7
|
parseUrl,
|
|
9
8
|
cloneLocationQuery,
|
|
@@ -22,30 +21,26 @@ import { createPageId } from '../utils/tabInfoEncoder'
|
|
|
22
21
|
* useTabRouter - 标签内路由 Hook
|
|
23
22
|
*
|
|
24
23
|
* 职责:在单个标签内实现「前进/后退」栈式导航,以及滚动位置的记录与恢复
|
|
25
|
-
*
|
|
24
|
+
* 使用范围:仅在标签内的页面组件中使用;页面上下文由缓存包装组件 provide,
|
|
25
|
+
* 通过 tabPageContext 解析(兼容旧版 attrs / props 注入)。
|
|
26
26
|
*
|
|
27
27
|
* 主要 API:
|
|
28
28
|
* - forward(to) 前进:push 新页面到该标签的 pages 栈
|
|
29
29
|
* - backward(to, backQuery) 后退:pop 栈顶、驱逐缓存,跳转到新栈顶
|
|
30
30
|
* - addScrollTarget(...) 注册需要记录滚动位置的选择器
|
|
31
|
+
* - tabId / pageId 当前页面所属标签与缓存页 id(只读)
|
|
31
32
|
*/
|
|
32
33
|
export default function useTabRouter() {
|
|
33
|
-
const
|
|
34
|
-
const { attrs, props } = instance
|
|
34
|
+
const pageContext = resolveTabPageContext()
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
* StackTabs 在 StackTabs.vue 的 tabWrapper 里通过 cloneVNode(Node, { tId, pId }) 注入,
|
|
39
|
-
* 未在子组件 setup 中声明为 prop 的属性会落入 attrs,
|
|
40
|
-
* 因此优先读 attrs.tId,再 fallback 到 props.tId。
|
|
41
|
-
*/
|
|
42
|
-
const tabId = (attrs.tId ?? props.tId) as string
|
|
36
|
+
/** 当前标签 ID,用于定位页面栈与标签激活状态。 */
|
|
37
|
+
const tabId = pageContext?.tabId ?? ''
|
|
43
38
|
|
|
44
39
|
/**
|
|
45
40
|
* 当前页面的缓存 ID(即 cacheName / keep-alive include 中的名称)。
|
|
46
41
|
* 用于 addScrollTarget 向 useTabPanel 绑定该页面的可滚动容器选择器。
|
|
47
42
|
*/
|
|
48
|
-
const pageId =
|
|
43
|
+
const pageId = pageContext?.pageId ?? ''
|
|
49
44
|
|
|
50
45
|
const route = useRoute()
|
|
51
46
|
const router = useRouter()
|
|
@@ -249,6 +244,8 @@ export default function useTabRouter() {
|
|
|
249
244
|
return {
|
|
250
245
|
forward,
|
|
251
246
|
backward,
|
|
252
|
-
addScrollTarget
|
|
247
|
+
addScrollTarget,
|
|
248
|
+
tabId,
|
|
249
|
+
pageId
|
|
253
250
|
}
|
|
254
251
|
}
|