xto-fronted 0.9.2 → 0.9.4
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/dist/{index-DWJW3Fh8.js → index-C0VbPQUq.js} +1 -1
- package/dist/{index-D5ZqtqTW.js → index-DG5BwN0D.js} +1 -1
- package/dist/{index-C3AlXquT.js → index-i4mPNvy-.js} +1 -1
- package/dist/{index-CW3IiVm8.js → index-kLXXr6Og.js} +920 -872
- package/dist/{index-TQslB33m.js → index-xhdpvVQC.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Layout/Sidebar.vue +5 -0
- package/src/components/Layout/SidebarMenuItem.vue +70 -37
- package/src/components/Layout/index.vue +16 -2
- package/src/stores/app.ts +9 -8
|
@@ -29,10 +29,70 @@ const knownIcons = new Set([
|
|
|
29
29
|
'moon', 'sun', 'theme', 'skin'
|
|
30
30
|
])
|
|
31
31
|
|
|
32
|
+
// Element UI(el-icon-*)图标名到组件库图标名的映射:
|
|
33
|
+
// sys_menu.icon 大量使用 el-icon-* 命名,不映射时只能退化为首字徽标
|
|
34
|
+
const elIconMap: Record<string, string> = {
|
|
35
|
+
'el-icon-s-platform': 'system',
|
|
36
|
+
'el-icon-platform': 'system',
|
|
37
|
+
'el-icon-s-data': 'database',
|
|
38
|
+
'el-icon-data-analysis': 'chart',
|
|
39
|
+
'el-icon-files': 'folder',
|
|
40
|
+
'el-icon-folder': 'folder',
|
|
41
|
+
'el-icon-folder-opened': 'folder-open',
|
|
42
|
+
'el-icon-s-custom': 'user',
|
|
43
|
+
'el-icon-user': 'user',
|
|
44
|
+
'el-icon-s-grid': 'grid',
|
|
45
|
+
'el-icon-menu': 'list',
|
|
46
|
+
'el-icon-setting': 'setting',
|
|
47
|
+
'el-icon-s-tools': 'setting',
|
|
48
|
+
'el-icon-s-operation': 'setting',
|
|
49
|
+
'el-icon-s-home': 'home',
|
|
50
|
+
'el-icon-house': 'home',
|
|
51
|
+
'el-icon-document': 'document',
|
|
52
|
+
'el-icon-document-copy': 'copy',
|
|
53
|
+
'el-icon-monitor': 'dashboard',
|
|
54
|
+
'el-icon-odometer': 'dashboard',
|
|
55
|
+
'el-icon-s-order': 'list',
|
|
56
|
+
'el-icon-s-check': 'check',
|
|
57
|
+
'el-icon-s-claim': 'file',
|
|
58
|
+
'el-icon-s-comment': 'message',
|
|
59
|
+
'el-icon-s-promotion': 'share',
|
|
60
|
+
'el-icon-s-marketing': 'chart',
|
|
61
|
+
'el-icon-s-finance': 'chart',
|
|
62
|
+
'el-icon-s-management': 'list',
|
|
63
|
+
'el-icon-s-cooperation': 'user-group',
|
|
64
|
+
'el-icon-s-shop': 'gift',
|
|
65
|
+
'el-icon-picture': 'image',
|
|
66
|
+
'el-icon-location': 'location',
|
|
67
|
+
'el-icon-map-location': 'map',
|
|
68
|
+
'el-icon-lock': 'lock',
|
|
69
|
+
'el-icon-unlock': 'unlock',
|
|
70
|
+
'el-icon-key': 'key',
|
|
71
|
+
'el-icon-bell': 'bell',
|
|
72
|
+
'el-icon-edit': 'edit',
|
|
73
|
+
'el-icon-delete': 'delete',
|
|
74
|
+
'el-icon-link': 'link',
|
|
75
|
+
'el-icon-connection': 'link',
|
|
76
|
+
'el-icon-upload': 'upload',
|
|
77
|
+
'el-icon-upload2': 'upload',
|
|
78
|
+
'el-icon-download': 'download',
|
|
79
|
+
'el-icon-search': 'search',
|
|
80
|
+
'el-icon-timer': 'clock',
|
|
81
|
+
'el-icon-time': 'clock',
|
|
82
|
+
'el-icon-calendar': 'calendar',
|
|
83
|
+
'el-icon-view': 'eye',
|
|
84
|
+
'el-icon-message': 'mail',
|
|
85
|
+
'el-icon-chat-dot-round': 'chat',
|
|
86
|
+
'el-icon-cpu': 'server',
|
|
87
|
+
'el-icon-cloudy': 'cloud'
|
|
88
|
+
}
|
|
89
|
+
|
|
32
90
|
// 获取菜单图标名称
|
|
33
91
|
const getMenuIcon = (icon?: string): string => {
|
|
34
92
|
if (!icon || icon === '') return ''
|
|
35
93
|
|
|
94
|
+
if (elIconMap[icon]) return elIconMap[icon]
|
|
95
|
+
|
|
36
96
|
if (icon.startsWith('tineco-icon-')) {
|
|
37
97
|
const iconName = icon.replace('tineco-icon-', '')
|
|
38
98
|
const tinecoIconMap: Record<string, string> = {
|
|
@@ -74,7 +134,7 @@ const getMenuIcon = (icon?: string): string => {
|
|
|
74
134
|
return iconMap[icon] || icon
|
|
75
135
|
}
|
|
76
136
|
|
|
77
|
-
//
|
|
137
|
+
// 获取菜单名称第一个字(无匹配图标时的兜底)
|
|
78
138
|
const getFirstChar = (name?: string): string => {
|
|
79
139
|
if (!name) return ''
|
|
80
140
|
return name.charAt(0)
|
|
@@ -99,15 +159,11 @@ const hasChildren = computed(() => {
|
|
|
99
159
|
<template>
|
|
100
160
|
<!-- 有子菜单 -->
|
|
101
161
|
<SubMenu v-if="hasChildren" :index="menuIndex">
|
|
102
|
-
<template #
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
<Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
|
|
106
|
-
<span v-else class="menu-item__char">{{ getFirstChar(menu.menuName) }}</span>
|
|
107
|
-
</span>
|
|
108
|
-
<span class="menu-item__text">{{ menu.menuName }}</span>
|
|
109
|
-
</span>
|
|
162
|
+
<template #icon>
|
|
163
|
+
<Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
|
|
164
|
+
<span v-else class="menu-item__char">{{ getFirstChar(menu.menuName) }}</span>
|
|
110
165
|
</template>
|
|
166
|
+
<template #title>{{ menu.menuName }}</template>
|
|
111
167
|
<!-- 递归渲染子菜单 -->
|
|
112
168
|
<template v-for="child in menu.children" :key="child.menuId || child.menuCode || child.menuUrl">
|
|
113
169
|
<SidebarMenuItem :menu="child" />
|
|
@@ -116,38 +172,15 @@ const hasChildren = computed(() => {
|
|
|
116
172
|
|
|
117
173
|
<!-- 无子菜单 -->
|
|
118
174
|
<MenuItem v-else :index="menu.menuUrl || menuIndex">
|
|
119
|
-
<
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<span class="menu-item__text">{{ menu.menuName }}</span>
|
|
125
|
-
</span>
|
|
175
|
+
<template #icon>
|
|
176
|
+
<Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
|
|
177
|
+
<span v-else class="menu-item__char">{{ getFirstChar(menu.menuName) }}</span>
|
|
178
|
+
</template>
|
|
179
|
+
{{ menu.menuName }}
|
|
126
180
|
</MenuItem>
|
|
127
181
|
</template>
|
|
128
182
|
|
|
129
183
|
<style lang="scss" scoped>
|
|
130
|
-
.menu-item__content {
|
|
131
|
-
display: inline-flex;
|
|
132
|
-
align-items: center;
|
|
133
|
-
line-height: 1;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.menu-item__text {
|
|
137
|
-
flex: 1;
|
|
138
|
-
line-height: 1;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.menu-item__icon {
|
|
142
|
-
display: inline-flex;
|
|
143
|
-
align-items: center;
|
|
144
|
-
justify-content: center;
|
|
145
|
-
width: 16px;
|
|
146
|
-
height: 16px;
|
|
147
|
-
margin-right: 8px;
|
|
148
|
-
flex-shrink: 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
184
|
.menu-item__char {
|
|
152
185
|
display: inline-flex;
|
|
153
186
|
align-items: center;
|
|
@@ -53,7 +53,11 @@ const showMixSidebar = computed(() => {
|
|
|
53
53
|
<template>
|
|
54
54
|
<!-- sidebar模式:左侧菜单 + 顶部Header + 主内容 -->
|
|
55
55
|
<div v-if="layoutMode === 'sidebar'" class="layout layout--sidebar">
|
|
56
|
-
<aside
|
|
56
|
+
<aside
|
|
57
|
+
class="layout__aside"
|
|
58
|
+
:class="{ 'layout__aside--collapsed': appStore.isCollapsed }"
|
|
59
|
+
:style="{ width: sidebarWidth }"
|
|
60
|
+
>
|
|
57
61
|
<Sidebar :menu-list="sidebarMenuList" :show-logo="true" :show-user="true" :logo-src="resolvedLogoSrc" />
|
|
58
62
|
</aside>
|
|
59
63
|
<div class="layout__main">
|
|
@@ -86,7 +90,12 @@ const showMixSidebar = computed(() => {
|
|
|
86
90
|
</div>
|
|
87
91
|
<!-- 下方:左侧子菜单 + 主内容 -->
|
|
88
92
|
<div class="layout__mix-body">
|
|
89
|
-
<aside
|
|
93
|
+
<aside
|
|
94
|
+
v-if="showMixSidebar"
|
|
95
|
+
class="layout__aside"
|
|
96
|
+
:class="{ 'layout__aside--collapsed': appStore.isCollapsed }"
|
|
97
|
+
:style="{ width: sidebarWidth }"
|
|
98
|
+
>
|
|
90
99
|
<Sidebar :menu-list="sidebarMenuList" :show-logo="false" :show-user="false" />
|
|
91
100
|
</aside>
|
|
92
101
|
<main class="layout__content">
|
|
@@ -159,6 +168,11 @@ const showMixSidebar = computed(() => {
|
|
|
159
168
|
overflow: hidden;
|
|
160
169
|
flex-shrink: 0;
|
|
161
170
|
height: 100%;
|
|
171
|
+
|
|
172
|
+
// 折叠态:弹出子菜单浮层需要溢出侧栏显示
|
|
173
|
+
&--collapsed {
|
|
174
|
+
overflow: visible;
|
|
175
|
+
}
|
|
162
176
|
}
|
|
163
177
|
|
|
164
178
|
&__top-menu {
|
package/src/stores/app.ts
CHANGED
|
@@ -31,22 +31,23 @@ const PREFERENCE_KEYS = ['layout', 'primaryColor', 'theme', 'isDark', 'showTabs'
|
|
|
31
31
|
// 本模块经 index.ts 静态导出,若在模块顶层取值会在 createXtoApp 拉取配置前固化成空对象。
|
|
32
32
|
|
|
33
33
|
export const useAppStore = defineStore('app', () => {
|
|
34
|
-
//
|
|
34
|
+
// 服务端按应用下发的界面配置(xto-admin「界面设置」维护)
|
|
35
35
|
const serverUi = getServerUiConfig()
|
|
36
36
|
const serverFeatures = serverUi.features || {}
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// 状态(优先级:服务端已配置的项 > 用户本地偏好 > 内置默认。
|
|
39
|
+
// 应用配置了的项始终以配置为准;未配置的项允许用户自行设置并本地记忆)
|
|
39
40
|
const appName = ref<string>(local.get<string>('appName') || 'XTO App')
|
|
40
41
|
const indexPath = ref<string>(local.get<string>('indexPath') || '/dashboard')
|
|
41
42
|
const loginPath = ref<string>(local.get<string>('loginPath') || '/login')
|
|
42
|
-
const theme = ref<ThemeMode>(readUserPreference<ThemeMode>('theme') ??
|
|
43
|
+
const theme = ref<ThemeMode>(serverUi.theme ?? readUserPreference<ThemeMode>('theme') ?? 'light')
|
|
43
44
|
const isDark = ref<boolean>(theme.value === 'dark')
|
|
44
|
-
const layout = ref<LayoutMode>(readUserPreference<LayoutMode>('layout') ??
|
|
45
|
+
const layout = ref<LayoutMode>(serverUi.layout ?? readUserPreference<LayoutMode>('layout') ?? 'sidebar')
|
|
45
46
|
const isCollapsed = ref<boolean>(local.get<boolean>('isCollapsed') || false)
|
|
46
|
-
const showTabs = ref<boolean>(readUserPreference<boolean>('showTabs') ??
|
|
47
|
-
const showFooter = ref<boolean>(readUserPreference<boolean>('showFooter') ??
|
|
48
|
-
const showBreadcrumb = ref<boolean>(readUserPreference<boolean>('showBreadcrumb') ??
|
|
49
|
-
const primaryColor = ref<string>(readUserPreference<string>('primaryColor') ??
|
|
47
|
+
const showTabs = ref<boolean>(serverFeatures.showTabs ?? readUserPreference<boolean>('showTabs') ?? true)
|
|
48
|
+
const showFooter = ref<boolean>(serverFeatures.showFooter ?? readUserPreference<boolean>('showFooter') ?? true)
|
|
49
|
+
const showBreadcrumb = ref<boolean>(serverFeatures.showBreadcrumb ?? readUserPreference<boolean>('showBreadcrumb') ?? true)
|
|
50
|
+
const primaryColor = ref<string>(serverUi.primaryColor ?? readUserPreference<string>('primaryColor') ?? '#4F46E5')
|
|
50
51
|
const allowUserOverride = ref<boolean>(serverUi.allowUserOverride !== false)
|
|
51
52
|
const cachedViews = ref<string[]>([])
|
|
52
53
|
const mixSubMenus = ref<any[]>([])
|