hrp-ui-base 1.0.2 → 1.0.3

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 (85) hide show
  1. package/dist/components.cjs +1 -1
  2. package/dist/components.es.js +3595 -1224
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.es.js +86 -69
  5. package/dist/style.css +1 -1
  6. package/package.json +2 -1
  7. package/src/api/notice/NoticeController.ts +73 -0
  8. package/src/api/notice/bo/NoticePageBO.ts +24 -0
  9. package/src/api/notice/bo/NoticeVO.ts +31 -0
  10. package/src/assets/layout/avatar-default.svg +6 -0
  11. package/src/assets/layout/collect-active.svg +7 -0
  12. package/src/assets/layout/collect-default.svg +7 -0
  13. package/src/assets/layout/download-active.svg +8 -0
  14. package/src/assets/layout/download-default.svg +8 -0
  15. package/src/assets/layout/export-dark.svg +7 -0
  16. package/src/assets/layout/export-default.svg +7 -0
  17. package/src/assets/layout/font-active.svg +6 -0
  18. package/src/assets/layout/font-default.svg +6 -0
  19. package/src/assets/layout/help-active.svg +10 -0
  20. package/src/assets/layout/help-default.svg +10 -0
  21. package/src/assets/layout/home-active.svg +6 -0
  22. package/src/assets/layout/home.svg +6 -0
  23. package/src/assets/layout/menu-expand.svg +6 -0
  24. package/src/assets/layout/menu-icon.png +0 -0
  25. package/src/assets/layout/menu-position-active.svg +6 -0
  26. package/src/assets/layout/menu-position-default.svg +6 -0
  27. package/src/assets/layout/message-active.svg +9 -0
  28. package/src/assets/layout/message-default.svg +9 -0
  29. package/src/assets/layout/moon.svg +8 -0
  30. package/src/assets/layout/router-all-dark.svg +11 -0
  31. package/src/assets/layout/router-all-default.svg +17 -0
  32. package/src/assets/layout/search-active.svg +20 -0
  33. package/src/assets/layout/search-default.svg +20 -0
  34. package/src/assets/layout/star-active.svg +1 -0
  35. package/src/assets/layout/star-default.svg +3 -0
  36. package/src/assets/layout/sun-active.svg +6 -0
  37. package/src/assets/layout/sun-default.svg +6 -0
  38. package/src/assets/layout/switch-moon.svg +3 -0
  39. package/src/assets/layout/switch-sun.svg +41 -0
  40. package/src/assets/layout/todo-active.svg +12 -0
  41. package/src/assets/layout/todo-default.svg +12 -0
  42. package/src/assets/layout/user-dark.svg +13 -0
  43. package/src/assets/layout/user-default.svg +13 -0
  44. package/src/components/base-layout/index.vue +198 -0
  45. package/src/components/layout/SysHeader.vue +90 -0
  46. package/src/components/layout/SysHeaderLeft.vue +116 -0
  47. package/src/components/layout/SysHeaderRight.vue +112 -0
  48. package/src/components/layout/SysHeaderTabs.vue +289 -0
  49. package/src/components/layout/components/avatar-component.vue +77 -0
  50. package/src/components/layout/components/dark-component.vue +82 -0
  51. package/src/components/layout/components/download-component.vue +49 -0
  52. package/src/components/layout/components/font-size-component.vue +66 -0
  53. package/src/components/layout/components/menu-position-component.vue +97 -0
  54. package/src/components/layout/components/message-component.vue +64 -0
  55. package/src/components/layout/components/todo-component.vue +55 -0
  56. package/src/components/layout/index.ts +24 -0
  57. package/src/components/layout/message/message-dictionary.ts +27 -0
  58. package/src/components/layout/message/message-icon//345/205/250/351/203/250/351/200/232/347/237/245-/350/223/235.svg +8 -0
  59. package/src/components/layout/message/message-icon//345/205/250/351/203/250/351/200/232/347/237/245-/351/273/221.svg +8 -0
  60. package/src/components/layout/message/message-icon//345/205/250/351/203/250/351/200/232/347/237/245.svg +8 -0
  61. package/src/components/layout/message/message-icon//345/205/254/345/221/212/351/200/232/347/237/245-/350/223/235.svg +12 -0
  62. package/src/components/layout/message/message-icon//345/205/254/345/221/212/351/200/232/347/237/245-/351/273/221.svg +12 -0
  63. package/src/components/layout/message/message-icon//345/205/254/345/221/212/351/200/232/347/237/245.svg +12 -0
  64. package/src/components/layout/message/message-icon//345/256/241/346/211/271/351/200/232/347/237/245-/350/223/235.svg +8 -0
  65. package/src/components/layout/message/message-icon//345/256/241/346/211/271/351/200/232/347/237/245-/351/273/221.svg +8 -0
  66. package/src/components/layout/message/message-icon//345/256/241/346/211/271/351/200/232/347/237/245.svg +8 -0
  67. package/src/components/layout/message/message-icon//345/257/274/345/207/272/351/200/232/347/237/245-/350/223/235.svg +13 -0
  68. package/src/components/layout/message/message-icon//345/257/274/345/207/272/351/200/232/347/237/245-/351/273/221.svg +13 -0
  69. package/src/components/layout/message/message-icon//345/257/274/345/207/272/351/200/232/347/237/245.svg +13 -0
  70. package/src/components/layout/message/message-icon//346/234/252/350/257/273/351/200/232/347/237/245-/350/223/235.svg +11 -0
  71. package/src/components/layout/message/message-icon//346/234/252/350/257/273/351/200/232/347/237/245-/351/273/221.svg +11 -0
  72. package/src/components/layout/message/message-icon//346/234/252/350/257/273/351/200/232/347/237/245.svg +11 -0
  73. package/src/components/layout/message/message-icon//347/251/272/347/212/266/346/200/201.svg +17 -0
  74. package/src/components/layout/message/message-icon//347/263/273/347/273/237/351/200/232/347/237/245-/350/223/235.svg +13 -0
  75. package/src/components/layout/message/message-icon//347/263/273/347/273/237/351/200/232/347/237/245-/351/273/221.svg +13 -0
  76. package/src/components/layout/message/message-icon//347/263/273/347/273/237/351/200/232/347/237/245.svg +13 -0
  77. package/src/components/layout/message/message-notification-drawer.vue +529 -0
  78. package/src/components/layout/personalization-guide-dialog.vue +255 -0
  79. package/src/components/layout/sideMenu-global.scss +115 -0
  80. package/src/components/layout/sideMenu.scss +312 -0
  81. package/src/components/layout/sideMenu.vue +542 -0
  82. package/src/components/layout/sideMenuSonList.vue +185 -0
  83. package/src/components/layout/styles/icon.scss +72 -0
  84. package/src/components/layout/types.ts +98 -0
  85. package/src/components.ts +19 -4
@@ -0,0 +1,255 @@
1
+ <template>
2
+ <teleport to="body">
3
+ <div v-if="tourVisible" class="personalization-guide-layer">
4
+ <div class="guide-highlight" :style="highlightStyle"></div>
5
+ <div ref="bubbleRef" class="guide-bubble" :style="bubbleStyle">
6
+ <span class="guide-arrow" :style="arrowStyle"></span>
7
+ <div class="guide-step">
8
+ <div class="guide-head">
9
+ <div>
10
+ <div class="guide-step-count">步骤 {{ currentStepIndex + 1 }} / {{ guideSteps.length }}</div>
11
+ <div class="guide-step-title">{{ currentStep.title }}</div>
12
+ </div>
13
+ <div class="guide-dots">
14
+ <span
15
+ v-for="(_, index) in guideSteps"
16
+ :key="index"
17
+ :class="['guide-dot', { 'is-active': index === currentStepIndex, 'is-done': index < currentStepIndex }]"
18
+ ></span>
19
+ </div>
20
+ </div>
21
+
22
+ <el-radio-group
23
+ v-if="currentStep.key === 'menuPosition'"
24
+ v-model="guideForm.menuPosition"
25
+ class="guide-control"
26
+ @change="applyPreview"
27
+ >
28
+ <el-radio-button label="top">顶部</el-radio-button>
29
+ <el-radio-button label="left">左侧</el-radio-button>
30
+ </el-radio-group>
31
+
32
+ <el-radio-group
33
+ v-if="currentStep.key === 'fontSize'"
34
+ v-model="guideForm.fontSize"
35
+ class="guide-control"
36
+ @change="applyPreview"
37
+ >
38
+ <el-radio-button label="small">小</el-radio-button>
39
+ <el-radio-button label="medium">中</el-radio-button>
40
+ <el-radio-button label="large">大</el-radio-button>
41
+ </el-radio-group>
42
+
43
+ <el-radio-group
44
+ v-if="currentStep.key === 'colorMode'"
45
+ v-model="guideForm.colorMode"
46
+ class="guide-control"
47
+ @change="applyPreview"
48
+ >
49
+ <el-radio-button label="day">日间</el-radio-button>
50
+ <el-radio-button label="night">黑夜</el-radio-button>
51
+ </el-radio-group>
52
+
53
+ <div class="guide-step-desc">{{ currentStep.desc }}</div>
54
+ </div>
55
+
56
+ <div class="guide-footer">
57
+ <el-button class="guide-skip-button" link :disabled="saving" @click="skipGuide">跳过</el-button>
58
+ <el-button class="guide-next-button" type="primary" :loading="saving" @click="goNextStep">
59
+ {{ isLastStep ? "完成" : "下一步" }}
60
+ </el-button>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </teleport>
65
+ </template>
66
+
67
+ <script setup lang="ts">
68
+ import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
69
+
70
+ type MenuPosition = "top" | "left";
71
+ type FontSize = "small" | "medium" | "large";
72
+ type ColorMode = "day" | "night";
73
+ type GuideStepKey = "menuPosition" | "fontSize" | "colorMode";
74
+
75
+ const props = defineProps<{
76
+ visible: boolean
77
+ menuPosition?: string
78
+ fontSize?: string
79
+ dayOrNight?: string
80
+ followSystem?: boolean
81
+ }>()
82
+
83
+ const emit = defineEmits<{
84
+ (e: 'update:visible', value: boolean): void
85
+ (e: 'preview', config: { menuPosition: string; fontSize: string; colorMode: string; followSystem: boolean }): void
86
+ (e: 'finish', config: { menuPosition: string; fontSize: string; colorMode: string }): void
87
+ (e: 'skip'): void
88
+ (e: 'restore', config: { menuPosition: string; fontSize: string; followSystem: boolean; dayOrNight: string }): void
89
+ }>()
90
+
91
+ const tourVisible = ref(false);
92
+ const saving = ref(false);
93
+ const currentStepIndex = ref(0);
94
+ const bubbleRef = ref<HTMLElement>();
95
+ const highlightStyle = ref<Record<string, string>>({});
96
+ const bubbleStyle = ref<Record<string, string>>({});
97
+ const arrowStyle = ref<Record<string, string>>({});
98
+
99
+ const guideSteps: { key: GuideStepKey; title: string; desc: string; targetId: string; }[] = [
100
+ { key: "menuPosition", title: "菜单栏位置", desc: "选择左侧后,菜单会移动到页面左侧。", targetId: "tourMenuPositionId" },
101
+ { key: "fontSize", title: "字体大小", desc: "可以先预览显示效果,完成后会保存到个性化配置。", targetId: "tourFontSizeId" },
102
+ { key: "colorMode", title: "黑夜模式", desc: "完成后仍然可以在右上角图标中继续调整。", targetId: "tourDarkId" },
103
+ ];
104
+
105
+ const currentStep = computed(() => guideSteps[currentStepIndex.value]);
106
+ const isLastStep = computed(() => currentStepIndex.value === guideSteps.length - 1);
107
+
108
+ const guideForm = reactive<{ menuPosition: MenuPosition; fontSize: FontSize; colorMode: ColorMode }>({
109
+ menuPosition: "top",
110
+ fontSize: "medium",
111
+ colorMode: "day",
112
+ });
113
+
114
+ const originalConfig = ref({
115
+ menuPosition: "top" as MenuPosition,
116
+ fontSize: "medium" as FontSize,
117
+ followSystem: false,
118
+ dayOrNight: "day" as ColorMode,
119
+ });
120
+
121
+ const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
122
+ const waitFrame = () => new Promise<void>((resolve) => { window.requestAnimationFrame(() => resolve()); });
123
+ const getStepTarget = () => document.getElementById(currentStep.value.targetId);
124
+
125
+ const syncGuideForm = () => {
126
+ guideForm.menuPosition = (props.menuPosition || "top") as MenuPosition;
127
+ guideForm.fontSize = (props.fontSize || "medium") as FontSize;
128
+ guideForm.colorMode = (props.dayOrNight || "day") as ColorMode;
129
+ originalConfig.value = {
130
+ menuPosition: guideForm.menuPosition,
131
+ fontSize: guideForm.fontSize,
132
+ followSystem: !!props.followSystem,
133
+ dayOrNight: guideForm.colorMode,
134
+ };
135
+ };
136
+
137
+ const updateGuidePosition = () => {
138
+ if (!tourVisible.value) return;
139
+ const target = getStepTarget();
140
+ const bubbleWidth = 344;
141
+ const padding = 16;
142
+ const targetGap = 10;
143
+ if (!target) {
144
+ highlightStyle.value = { display: "none" };
145
+ bubbleStyle.value = { top: "88px", right: "24px", width: `${bubbleWidth}px` };
146
+ arrowStyle.value = { display: "none" };
147
+ return;
148
+ }
149
+ const rect = target.getBoundingClientRect();
150
+ const highlightOffset = 6;
151
+ const bubbleHeight = bubbleRef.value?.offsetHeight || 220;
152
+ const targetCenterX = rect.left + rect.width / 2;
153
+ const left = clamp(targetCenterX - bubbleWidth / 2, padding, window.innerWidth - bubbleWidth - padding);
154
+ const belowTop = rect.bottom + targetGap + highlightOffset;
155
+ const aboveTop = rect.top - bubbleHeight - targetGap - highlightOffset;
156
+ const top = belowTop + bubbleHeight + padding > window.innerHeight ? Math.max(padding, aboveTop) : belowTop;
157
+ const arrowLeft = clamp(targetCenterX - left - 7, 18, bubbleWidth - 28);
158
+
159
+ highlightStyle.value = { top: `${rect.top - highlightOffset}px`, left: `${rect.left - highlightOffset}px`, width: `${rect.width + highlightOffset * 2}px`, height: `${rect.height + highlightOffset * 2}px` };
160
+ bubbleStyle.value = { top: `${top}px`, left: `${left}px`, width: `${bubbleWidth}px` };
161
+ arrowStyle.value = { left: `${arrowLeft}px`, display: "block" };
162
+ };
163
+
164
+ const waitCurrentTarget = async () => {
165
+ for (let i = 0; i < 20; i += 1) { await nextTick(); await waitFrame(); if (getStepTarget()) return; }
166
+ };
167
+
168
+ const openGuide = async () => {
169
+ syncGuideForm();
170
+ currentStepIndex.value = 0;
171
+ await waitCurrentTarget();
172
+ tourVisible.value = true;
173
+ await nextTick();
174
+ updateGuidePosition();
175
+ };
176
+
177
+ const applyPreview = async () => {
178
+ emit('preview', {
179
+ menuPosition: guideForm.menuPosition,
180
+ fontSize: guideForm.fontSize,
181
+ colorMode: guideForm.colorMode,
182
+ followSystem: false,
183
+ });
184
+ await nextTick();
185
+ await waitFrame();
186
+ updateGuidePosition();
187
+ };
188
+
189
+ const finishGuide = async () => {
190
+ if (saving.value) return;
191
+ saving.value = true;
192
+ try {
193
+ await applyPreview();
194
+ emit('finish', { menuPosition: guideForm.menuPosition, fontSize: guideForm.fontSize, colorMode: guideForm.colorMode });
195
+ tourVisible.value = false;
196
+ emit('update:visible', false);
197
+ } finally { saving.value = false; }
198
+ };
199
+
200
+ const skipGuide = async () => {
201
+ if (saving.value) return;
202
+ saving.value = true;
203
+ try {
204
+ emit('restore', originalConfig.value);
205
+ emit('skip');
206
+ tourVisible.value = false;
207
+ emit('update:visible', false);
208
+ } finally { saving.value = false; }
209
+ };
210
+
211
+ const goNextStep = async () => {
212
+ if (isLastStep.value) { await finishGuide(); return; }
213
+ currentStepIndex.value += 1;
214
+ await waitCurrentTarget();
215
+ await nextTick();
216
+ updateGuidePosition();
217
+ };
218
+
219
+ watch(() => props.visible, (val) => {
220
+ if (val) { openGuide(); } else { tourVisible.value = false; }
221
+ }, { immediate: true });
222
+
223
+ onMounted(() => {
224
+ window.addEventListener("resize", updateGuidePosition);
225
+ window.addEventListener("scroll", updateGuidePosition, true);
226
+ });
227
+ onUnmounted(() => {
228
+ window.removeEventListener("resize", updateGuidePosition);
229
+ window.removeEventListener("scroll", updateGuidePosition, true);
230
+ });
231
+ </script>
232
+
233
+ <style scoped lang="scss">
234
+ .personalization-guide-layer { position: fixed; inset: 0; z-index: 3000; pointer-events: none; }
235
+ .guide-highlight { position: fixed; z-index: 3001; border: 1px solid var(--el-color-primary); border-radius: 8px; background: rgba(64, 158, 255, 0.08); box-shadow: 0 0 0 5px rgba(64, 158, 255, 0.14), 0 0 0 9999px rgba(20, 24, 31, 0.18); pointer-events: none; }
236
+ .guide-bubble { position: fixed; z-index: 3002; overflow: hidden; padding: 18px 18px 14px; background: var(--el-bg-color); border: 1px solid var(--el-border-color-lighter); border-radius: 8px; box-shadow: 0 16px 42px rgba(20, 24, 31, 0.16), 0 2px 8px rgba(20, 24, 31, 0.06); pointer-events: auto; }
237
+ .guide-bubble::before { position: absolute; top: 0; left: 0; width: 100%; height: 3px; background: var(--el-color-primary); content: ""; }
238
+ .guide-arrow { position: absolute; top: -6px; width: 12px; height: 12px; background: var(--el-bg-color); border-top: 1px solid var(--el-border-color-lighter); border-left: 1px solid var(--el-border-color-lighter); border-radius: 2px; transform: rotate(45deg); }
239
+ .guide-step { min-width: 260px; }
240
+ .guide-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
241
+ .guide-step-count { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; color: var(--el-color-primary); font-size: 12px; font-weight: 600; line-height: 18px; background: var(--el-color-primary-light-9); border-radius: 999px; }
242
+ .guide-step-title { margin-top: 10px; color: var(--el-text-color-primary); font-size: 17px; font-weight: 600; line-height: 24px; }
243
+ .guide-dots { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding-top: 7px; }
244
+ .guide-dot { width: 6px; height: 6px; background: var(--el-border-color); border-radius: 999px; transition: all 0.2s ease; }
245
+ .guide-dot.is-active { width: 18px; background: var(--el-color-primary); }
246
+ .guide-dot.is-done { background: var(--el-color-primary-light-5); }
247
+ .guide-control { margin-top: 18px; }
248
+ .guide-control :deep(.el-radio-button__inner) { min-width: 48px; height: 30px; padding: 7px 14px; font-size: 13px; line-height: 14px; }
249
+ .guide-control :deep(.el-radio-button:first-child .el-radio-button__inner) { border-radius: 6px 0 0 6px; }
250
+ .guide-control :deep(.el-radio-button:last-child .el-radio-button__inner) { border-radius: 0 6px 6px 0; }
251
+ .guide-step-desc { margin-top: 14px; color: var(--el-text-color-regular); font-size: 14px; line-height: 22px; }
252
+ .guide-footer { display: flex; justify-content: space-between; align-items: center; margin: 22px -18px -14px; padding: 12px 18px 14px; background: var(--el-fill-color-extra-light); border-top: 1px solid var(--el-border-color-lighter); }
253
+ .guide-skip-button { color: var(--el-text-color-secondary); }
254
+ .guide-next-button { min-width: 76px; border-radius: 6px; box-shadow: 0 6px 14px rgba(64, 158, 255, 0.22); }
255
+ </style>
@@ -0,0 +1,115 @@
1
+ /* 抽屉全局样式 */
2
+ .side-menu-drawer {
3
+ position: fixed;
4
+ top: 50px;
5
+ left: 0;
6
+ right: 0;
7
+ z-index: 2001;
8
+ background: var(--el-bg-color-overlay);
9
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
10
+ max-height: calc(80vh - 50px);
11
+ overflow-y: auto;
12
+ padding: 12px;
13
+ }
14
+
15
+ /* 抽屉滑动过渡 */
16
+ .side-drawer-slide-enter-active,
17
+ .side-drawer-slide-leave-active {
18
+ transition: transform 0.3s ease, opacity 0.3s ease;
19
+ }
20
+ .side-drawer-slide-enter-from,
21
+ .side-drawer-slide-leave-to {
22
+ transform: translateY(-20px);
23
+ opacity: 0;
24
+ }
25
+
26
+ .left-collapsed-flyout {
27
+ position: fixed;
28
+ left: 58px;
29
+ width: 240px;
30
+ max-height: min(460px, calc(100vh - 72px));
31
+ overflow-y: auto;
32
+ padding: 10px;
33
+ z-index: 2100;
34
+ background: var(--el-bg-color-overlay);
35
+ border: 1px solid var(--el-border-color-lighter);
36
+ border-radius: 8px;
37
+ box-shadow: var(--el-box-shadow-light);
38
+ box-sizing: border-box;
39
+ }
40
+
41
+ .left-collapsed-collapse {
42
+ margin-bottom: 6px;
43
+ }
44
+
45
+ .left-submenu-item {
46
+ min-height: 38px;
47
+ line-height: 38px;
48
+ padding: 0 12px;
49
+ margin: 4px 0;
50
+ color: var(--el-text-color-regular);
51
+ font-size: 15px;
52
+ border-radius: 5px;
53
+ cursor: pointer;
54
+ transition: color 0.2s, background 0.2s;
55
+ box-sizing: border-box;
56
+
57
+ &.with-icon {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 8px;
61
+ line-height: normal;
62
+ }
63
+
64
+ span:not(.left-page-icon):not(.left-page-icon-fallback) {
65
+ min-width: 0;
66
+ display: block;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ &:hover {
73
+ color: var(--el-color-primary);
74
+ background: var(--el-fill-color-light);
75
+ }
76
+
77
+ &.is-active {
78
+ color: var(--el-color-primary);
79
+ background: var(--el-color-primary-light-9);
80
+ font-weight: 500;
81
+ }
82
+ }
83
+
84
+ .left-page-icon {
85
+ width: 22px;
86
+ height: 22px;
87
+ flex-shrink: 0;
88
+ display: inline-flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ color: currentColor;
92
+ font-size: 13px;
93
+ font-weight: 600;
94
+ background: var(--el-fill-color-light);
95
+ border-radius: 5px;
96
+ overflow: hidden;
97
+ }
98
+
99
+ .left-page-icon-img {
100
+ width: 100%;
101
+ height: 100%;
102
+ object-fit: cover;
103
+ display: block;
104
+ }
105
+
106
+ .left-collapsed-flyout-enter-active,
107
+ .left-collapsed-flyout-leave-active {
108
+ transition: transform 0.18s ease, opacity 0.18s ease;
109
+ }
110
+
111
+ .left-collapsed-flyout-enter-from,
112
+ .left-collapsed-flyout-leave-to {
113
+ transform: translateX(-8px);
114
+ opacity: 0;
115
+ }
@@ -0,0 +1,312 @@
1
+ #header-sidebar {
2
+ height: 50px;
3
+ width: 100%;
4
+ display: flex;
5
+ align-items: center;
6
+ position: relative;
7
+ }
8
+
9
+ .scroll-arrow {
10
+ width: 28px;
11
+ height: 50px;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ cursor: pointer;
16
+ flex-shrink: 0;
17
+ color: var(--el-text-color-secondary);
18
+ transition: color 0.2s;
19
+
20
+ &:hover {
21
+ color: var(--el-color-primary);
22
+ }
23
+ }
24
+
25
+ .menu-scroll-wrapper {
26
+ flex: 1;
27
+ overflow-x: auto;
28
+ overflow-y: hidden;
29
+ min-width: 0;
30
+
31
+ /* 隐藏滚动条 */
32
+ scrollbar-width: none;
33
+ -ms-overflow-style: none;
34
+ &::-webkit-scrollbar {
35
+ display: none;
36
+ }
37
+ }
38
+
39
+ .menu-scroll-content {
40
+ display: flex;
41
+ align-items: center;
42
+ height: 50px;
43
+ white-space: nowrap;
44
+ }
45
+
46
+ .menu-tab-item {
47
+ display: flex;
48
+ align-items: center;
49
+ height: 50px;
50
+ padding: 0 16px;
51
+ cursor: pointer;
52
+ flex-shrink: 0;
53
+ position: relative;
54
+ transition: color 0.2s, background 0.2s;
55
+ color: var(--el-text-color-primary);
56
+
57
+ .menu-tab-name {
58
+ font-size: 14px;
59
+ font-weight: 400;
60
+ white-space: nowrap;
61
+ }
62
+
63
+ .menu-tab-arrow {
64
+ margin-left: 4px;
65
+ font-size: 12px;
66
+ transition: transform 0.3s;
67
+
68
+ &.is-open {
69
+ transform: rotate(180deg);
70
+ }
71
+ }
72
+
73
+ &:hover {
74
+ color: var(--el-color-primary);
75
+ background: var(--el-fill-color-light);
76
+ }
77
+
78
+ &.is-active {
79
+ color: var(--el-color-primary);
80
+ //background: var(--el-color-primary-light-9);
81
+
82
+ .menu-tab-name {
83
+ font-weight: 500;
84
+ }
85
+
86
+ &::after {
87
+ content: '';
88
+ position: absolute;
89
+ bottom: 6px;
90
+ left: 50%;
91
+ transform: translateX(-50%);
92
+ width: 90%;
93
+ height: 3px;
94
+ background: var(--el-color-primary);
95
+ border-radius: 2px;
96
+ }
97
+ }
98
+ }
99
+
100
+ #header-sidebar.menu-position-left {
101
+ width: 200px;
102
+ height: 100%;
103
+ align-items: stretch;
104
+ flex-direction: column;
105
+ transition: width 0.2s ease;
106
+
107
+ .menu-scroll-wrapper {
108
+ width: 100%;
109
+ overflow-x: hidden;
110
+ overflow-y: auto;
111
+ }
112
+
113
+ .menu-scroll-content {
114
+ height: auto;
115
+ min-height: 100%;
116
+ flex-direction: column;
117
+ align-items: stretch;
118
+ padding: 8px;
119
+ white-space: normal;
120
+ box-sizing: border-box;
121
+ }
122
+
123
+ .menu-tab-item {
124
+ width: 100%;
125
+ height: 40px;
126
+ padding: 0 12px;
127
+ border-radius: 6px;
128
+ justify-content: space-between;
129
+ box-sizing: border-box;
130
+
131
+ .menu-tab-icon {
132
+ width: 22px;
133
+ height: 22px;
134
+ margin-right: 8px;
135
+ flex-shrink: 0;
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ color: currentColor;
140
+ font-size: 13px;
141
+ font-weight: 600;
142
+ background: var(--el-fill-color-light);
143
+ border-radius: 5px;
144
+ overflow: hidden;
145
+
146
+ .menu-tab-icon-img {
147
+ width: 100%;
148
+ height: 100%;
149
+ object-fit: cover;
150
+ display: block;
151
+ }
152
+
153
+ .menu-tab-icon-fallback {
154
+ line-height: 22px;
155
+ }
156
+ }
157
+
158
+ .menu-tab-name {
159
+ flex: 1;
160
+ overflow: hidden;
161
+ text-overflow: ellipsis;
162
+ }
163
+
164
+ &.is-active {
165
+ &::after {
166
+ display: none;
167
+ }
168
+ }
169
+ }
170
+
171
+ .left-collapse-control {
172
+ height: 38px;
173
+ margin: 6px 8px 8px;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ gap: 6px;
178
+ color: var(--el-text-color-regular);
179
+ font-size: 13px;
180
+ border-radius: 6px;
181
+ cursor: pointer;
182
+ flex-shrink: 0;
183
+ transition: color 0.2s, background 0.2s;
184
+
185
+ &:hover {
186
+ color: var(--el-color-primary);
187
+ background: var(--el-fill-color-light);
188
+ }
189
+ }
190
+
191
+ .left-inline-submenu {
192
+ margin: 2px 0 8px;
193
+ padding: 4px 0 4px 12px;
194
+ border-left: 1px solid var(--el-border-color-light);
195
+ }
196
+
197
+ .left-submenu-group {
198
+ margin-bottom: 4px;
199
+ }
200
+
201
+ .left-submenu-group-title {
202
+ min-height: 30px;
203
+ line-height: 30px;
204
+ padding: 0 10px 0 12px;
205
+ margin: 6px 0 4px;
206
+ color: var(--el-color-primary);
207
+ font-size: 13px;
208
+ font-weight: 600;
209
+ background: var(--el-color-primary-light-9);
210
+ border-left: 3px solid var(--el-color-primary);
211
+ border-radius: 5px;
212
+ cursor: default;
213
+ overflow: hidden;
214
+ text-overflow: ellipsis;
215
+ white-space: nowrap;
216
+ box-sizing: border-box;
217
+
218
+ &.is-active {
219
+ background: var(--el-color-primary-light-8);
220
+ cursor: pointer;
221
+ }
222
+ }
223
+
224
+ .left-submenu-item {
225
+ min-height: 32px;
226
+ line-height: 32px;
227
+ padding: 0 10px;
228
+ margin: 2px 0;
229
+ color: var(--el-text-color-regular);
230
+ font-size: 13px;
231
+ border-radius: 5px;
232
+ cursor: pointer;
233
+ transition: color 0.2s, background 0.2s;
234
+
235
+ &.with-icon {
236
+ display: flex;
237
+ align-items: center;
238
+ gap: 8px;
239
+ line-height: normal;
240
+ }
241
+
242
+ .left-page-icon {
243
+ width: 20px;
244
+ height: 20px;
245
+ flex-shrink: 0;
246
+ display: inline-flex;
247
+ align-items: center;
248
+ justify-content: center;
249
+ color: currentColor;
250
+ font-size: 12px;
251
+ font-weight: 600;
252
+ background: var(--el-fill-color-light);
253
+ border-radius: 5px;
254
+ overflow: hidden;
255
+ }
256
+
257
+ .left-page-icon-img {
258
+ width: 100%;
259
+ height: 100%;
260
+ object-fit: cover;
261
+ display: block;
262
+ }
263
+
264
+ span {
265
+ display: block;
266
+ overflow: hidden;
267
+ text-overflow: ellipsis;
268
+ white-space: nowrap;
269
+ }
270
+
271
+ &:hover {
272
+ color: var(--el-color-primary);
273
+ background: var(--el-fill-color-light);
274
+ }
275
+
276
+ &.is-active {
277
+ color: var(--el-color-primary);
278
+ background: var(--el-color-primary-light-9);
279
+ font-weight: 500;
280
+ }
281
+ }
282
+ }
283
+
284
+ #header-sidebar.menu-position-left.is-left-collapsed {
285
+ width: 58px;
286
+
287
+ .menu-scroll-content {
288
+ padding: 8px 6px;
289
+ align-items: center;
290
+ }
291
+
292
+ .menu-tab-item {
293
+ width: 40px;
294
+ padding: 0;
295
+ justify-content: center;
296
+
297
+ .menu-tab-icon {
298
+ margin-right: 0;
299
+ }
300
+
301
+ &.is-active {
302
+ &::after {
303
+ display: none;
304
+ }
305
+ }
306
+ }
307
+
308
+ .left-collapse-control {
309
+ width: 40px;
310
+ margin: 6px auto 8px;
311
+ }
312
+ }