xto-fronted 0.4.64 → 0.4.66

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.
@@ -5,8 +5,9 @@ import { useMenuStore } from '@/stores/menu'
5
5
  import { useUserStore } from '@/stores/user'
6
6
  import { useAuthStore } from '@/stores/auth'
7
7
  import { useAppStore } from '@/stores/app'
8
- import { Menu, MenuItem, SubMenu } from '@xto/navigation'
9
- import { Button, Icon } from '@xto/base'
8
+ import { Menu } from '@xto/navigation'
9
+ import { Button } from '@xto/base'
10
+ import SidebarMenuItem from './SidebarMenuItem.vue'
10
11
 
11
12
  // Props
12
13
  const props = withDefaults(defineProps<{
@@ -51,87 +52,6 @@ const handleLogout = () => {
51
52
  menuStore.clearMenu()
52
53
  router.push('/login')
53
54
  }
54
-
55
- // 已知的图标名称列表(来自 @xto/base/icons.ts)
56
- const knownIcons = new Set([
57
- 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right',
58
- 'caret-down', 'caret-right', 'plus', 'minus', 'close', 'check',
59
- 'edit', 'delete', 'copy', 'download', 'upload', 'refresh', 'search',
60
- 'filter', 'more', 'setting', 'share', 'loading', 'info', 'success',
61
- 'warning', 'error', 'question', 'user', 'user-add', 'user-group',
62
- 'logout', 'login', 'file', 'folder', 'folder-open', 'document',
63
- 'image', 'video', 'music', 'camera', 'mail', 'phone', 'chat',
64
- 'bell', 'message', 'eye', 'eye-off', 'calendar', 'clock', 'history',
65
- 'timer', 'location', 'map', 'globe', 'star', 'heart', 'thumb-up',
66
- 'link', 'external-link', 'lock', 'unlock', 'key', 'home', 'menu',
67
- 'menu-fold', 'menu-unfold', 'sidebar-fold', 'sidebar-expand',
68
- 'sidebar-left', 'dashboard', 'chart', 'chart-pie', 'chart-line',
69
- 'report', 'analytics', 'system', 'permission', 'role', 'user-manage',
70
- 'log', 'notification', 'app', 'list', 'grid', 'fullscreen',
71
- 'fullscreen-exit', 'zoom-in', 'zoom-out', 'print', 'bookmark',
72
- 'tag', 'code', 'terminal', 'database', 'server', 'cloud', 'gift',
73
- 'moon', 'sun', 'theme', 'skin'
74
- ])
75
-
76
- // 获取菜单图标名称
77
- const getMenuIcon = (icon?: string): string => {
78
- // 无图标时返回空
79
- if (!icon || icon === '') return ''
80
-
81
- // 处理 tineco-ui 的图标类名(如 tineco-icon-home)
82
- if (icon.startsWith('tineco-icon-')) {
83
- const iconName = icon.replace('tineco-icon-', '')
84
- // 常见的 tineco icon 映射
85
- const tinecoIconMap: Record<string, string> = {
86
- home: 'home',
87
- dashboard: 'dashboard',
88
- system: 'system',
89
- user: 'user',
90
- role: 'role',
91
- menu: 'list',
92
- setting: 'setting',
93
- file: 'file',
94
- folder: 'folder',
95
- chart: 'chart',
96
- report: 'report',
97
- analytics: 'analytics'
98
- }
99
- return tinecoIconMap[iconName] || iconName
100
- }
101
-
102
- // 常见业务图标映射
103
- const iconMap: Record<string, string> = {
104
- dashboard: 'dashboard',
105
- system: 'system',
106
- user: 'user',
107
- role: 'role',
108
- menu: 'list',
109
- setting: 'setting',
110
- home: 'home',
111
- chart: 'chart',
112
- report: 'report',
113
- analytics: 'analytics',
114
- permission: 'permission',
115
- log: 'log',
116
- notification: 'notification',
117
- app: 'app',
118
- list: 'list',
119
- grid: 'grid'
120
- }
121
-
122
- return iconMap[icon] || icon
123
- }
124
-
125
- // 获取菜单名称第一个字
126
- const getFirstChar = (name?: string): string => {
127
- if (!name) return ''
128
- return name.charAt(0)
129
- }
130
-
131
- // 判断图标是否存在
132
- const iconExists = (iconName: string): boolean => {
133
- return knownIcons.has(iconName)
134
- }
135
55
  </script>
136
56
 
137
57
  <template>
@@ -155,41 +75,7 @@ const iconExists = (iconName: string): boolean => {
155
75
  @select="handleMenuSelect"
156
76
  >
157
77
  <template v-for="menu in displayMenuList" :key="menu.menuUrl">
158
- <!-- 有子菜单 -->
159
- <SubMenu v-if="menu.children && menu.children.length > 0" :index="menu.menuUrl">
160
- <template #title>
161
- <span class="sidebar__menu-content">
162
- <span v-if="menu.menuName !== '首页'" class="sidebar__menu-icon">
163
- <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
164
- <span v-else class="sidebar__menu-char">{{ getFirstChar(menu.menuName) }}</span>
165
- </span>
166
- <span class="sidebar__menu-text">{{ menu.menuName }}</span>
167
- </span>
168
- </template>
169
- <MenuItem
170
- v-for="child in menu.children"
171
- :key="child.menuUrl"
172
- :index="child.menuUrl"
173
- >
174
- <span class="sidebar__menu-content">
175
- <span class="sidebar__menu-icon">
176
- <Icon v-if="iconExists(getMenuIcon(child.icon))" :name="getMenuIcon(child.icon)" :size="16" />
177
- <span v-else class="sidebar__menu-char">{{ getFirstChar(child.menuName) }}</span>
178
- </span>
179
- <span class="sidebar__menu-text">{{ child.menuName }}</span>
180
- </span>
181
- </MenuItem>
182
- </SubMenu>
183
- <!-- 无子菜单 -->
184
- <MenuItem v-else :index="menu.menuUrl">
185
- <span class="sidebar__menu-content">
186
- <span v-if="menu.menuName !== '首页'" class="sidebar__menu-icon">
187
- <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
188
- <span v-else class="sidebar__menu-char">{{ getFirstChar(menu.menuName) }}</span>
189
- </span>
190
- <span class="sidebar__menu-text">{{ menu.menuName }}</span>
191
- </span>
192
- </MenuItem>
78
+ <SidebarMenuItem :menu="menu" />
193
79
  </template>
194
80
  </Menu>
195
81
 
@@ -245,40 +131,6 @@ const iconExists = (iconName: string): boolean => {
245
131
  overflow-y: auto;
246
132
  }
247
133
 
248
- &__menu-content {
249
- display: inline-flex;
250
- align-items: center;
251
- line-height: 1;
252
- }
253
-
254
- &__menu-text {
255
- flex: 1;
256
- line-height: 1;
257
- }
258
-
259
- &__menu-icon {
260
- display: inline-flex;
261
- align-items: center;
262
- justify-content: center;
263
- width: 16px;
264
- height: 16px;
265
- margin-right: 8px;
266
- flex-shrink: 0;
267
- }
268
-
269
- &__menu-char {
270
- display: inline-flex;
271
- align-items: center;
272
- justify-content: center;
273
- width: 16px;
274
- height: 16px;
275
- font-size: 12px;
276
- font-weight: 600;
277
- color: var(--color-primary);
278
- background-color: var(--color-primary-light-8);
279
- border-radius: 4px;
280
- }
281
-
282
134
  &__user {
283
135
  padding: 10px;
284
136
  border-top: 1px solid var(--color-border-lighter);
@@ -0,0 +1,158 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { MenuItem, SubMenu } from '@xto/navigation'
4
+ import { Icon } from '@xto/base'
5
+
6
+ // Props
7
+ const props = defineProps<{
8
+ menu: any
9
+ }>()
10
+
11
+ // 已知的图标名称列表
12
+ const knownIcons = new Set([
13
+ 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right',
14
+ 'caret-down', 'caret-right', 'plus', 'minus', 'close', 'check',
15
+ 'edit', 'delete', 'copy', 'download', 'upload', 'refresh', 'search',
16
+ 'filter', 'more', 'setting', 'share', 'loading', 'info', 'success',
17
+ 'warning', 'error', 'question', 'user', 'user-add', 'user-group',
18
+ 'logout', 'login', 'file', 'folder', 'folder-open', 'document',
19
+ 'image', 'video', 'music', 'camera', 'mail', 'phone', 'chat',
20
+ 'bell', 'message', 'eye', 'eye-off', 'calendar', 'clock', 'history',
21
+ 'timer', 'location', 'map', 'globe', 'star', 'heart', 'thumb-up',
22
+ 'link', 'external-link', 'lock', 'unlock', 'key', 'home', 'menu',
23
+ 'menu-fold', 'menu-unfold', 'sidebar-fold', 'sidebar-expand',
24
+ 'sidebar-left', 'dashboard', 'chart', 'chart-pie', 'chart-line',
25
+ 'report', 'analytics', 'system', 'permission', 'role', 'user-manage',
26
+ 'log', 'notification', 'app', 'list', 'grid', 'fullscreen',
27
+ 'fullscreen-exit', 'zoom-in', 'zoom-out', 'print', 'bookmark',
28
+ 'tag', 'code', 'terminal', 'database', 'server', 'cloud', 'gift',
29
+ 'moon', 'sun', 'theme', 'skin'
30
+ ])
31
+
32
+ // 获取菜单图标名称
33
+ const getMenuIcon = (icon?: string): string => {
34
+ if (!icon || icon === '') return ''
35
+
36
+ if (icon.startsWith('tineco-icon-')) {
37
+ const iconName = icon.replace('tineco-icon-', '')
38
+ const tinecoIconMap: Record<string, string> = {
39
+ home: 'home',
40
+ dashboard: 'dashboard',
41
+ system: 'system',
42
+ user: 'user',
43
+ role: 'role',
44
+ menu: 'list',
45
+ setting: 'setting',
46
+ file: 'file',
47
+ folder: 'folder',
48
+ chart: 'chart',
49
+ report: 'report',
50
+ analytics: 'analytics'
51
+ }
52
+ return tinecoIconMap[iconName] || iconName
53
+ }
54
+
55
+ const iconMap: Record<string, string> = {
56
+ dashboard: 'dashboard',
57
+ system: 'system',
58
+ user: 'user',
59
+ role: 'role',
60
+ menu: 'list',
61
+ setting: 'setting',
62
+ home: 'home',
63
+ chart: 'chart',
64
+ report: 'report',
65
+ analytics: 'analytics',
66
+ permission: 'permission',
67
+ log: 'log',
68
+ notification: 'notification',
69
+ app: 'app',
70
+ list: 'list',
71
+ grid: 'grid'
72
+ }
73
+
74
+ return iconMap[icon] || icon
75
+ }
76
+
77
+ // 获取菜单名称第一个字
78
+ const getFirstChar = (name?: string): string => {
79
+ if (!name) return ''
80
+ return name.charAt(0)
81
+ }
82
+
83
+ // 判断图标是否存在
84
+ const iconExists = (iconName: string): boolean => {
85
+ return knownIcons.has(iconName)
86
+ }
87
+
88
+ // 是否有子菜单
89
+ const hasChildren = computed(() => {
90
+ return props.menu.children && props.menu.children.length > 0
91
+ })
92
+ </script>
93
+
94
+ <template>
95
+ <!-- 有子菜单 -->
96
+ <SubMenu v-if="hasChildren" :index="menu.menuUrl">
97
+ <template #title>
98
+ <span class="menu-item__content">
99
+ <span class="menu-item__icon">
100
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
101
+ <span v-else class="menu-item__char">{{ getFirstChar(menu.menuName) }}</span>
102
+ </span>
103
+ <span class="menu-item__text">{{ menu.menuName }}</span>
104
+ </span>
105
+ </template>
106
+ <!-- 递归渲染子菜单 -->
107
+ <template v-for="child in menu.children" :key="child.menuUrl">
108
+ <SidebarMenuItem :menu="child" />
109
+ </template>
110
+ </SubMenu>
111
+
112
+ <!-- 无子菜单 -->
113
+ <MenuItem v-else :index="menu.menuUrl">
114
+ <span class="menu-item__content">
115
+ <span v-if="menu.menuName !== '首页'" class="menu-item__icon">
116
+ <Icon v-if="iconExists(getMenuIcon(menu.icon))" :name="getMenuIcon(menu.icon)" :size="16" />
117
+ <span v-else class="menu-item__char">{{ getFirstChar(menu.menuName) }}</span>
118
+ </span>
119
+ <span class="menu-item__text">{{ menu.menuName }}</span>
120
+ </span>
121
+ </MenuItem>
122
+ </template>
123
+
124
+ <style lang="scss" scoped>
125
+ .menu-item__content {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ line-height: 1;
129
+ }
130
+
131
+ .menu-item__text {
132
+ flex: 1;
133
+ line-height: 1;
134
+ }
135
+
136
+ .menu-item__icon {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ width: 16px;
141
+ height: 16px;
142
+ margin-right: 8px;
143
+ flex-shrink: 0;
144
+ }
145
+
146
+ .menu-item__char {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ width: 16px;
151
+ height: 16px;
152
+ font-size: 12px;
153
+ font-weight: 600;
154
+ color: var(--color-primary);
155
+ background-color: var(--color-primary-light-8);
156
+ border-radius: 4px;
157
+ }
158
+ </style>
package/src/style.scss CHANGED
@@ -1,14 +1,14 @@
1
- /**
2
- * xto-fronted 样式入口
3
- * 组件样式已包含在各自的 Vue 组件中(scoped)
4
- */
5
-
6
- /* 导入全局样式(包含灰色模式、暗黑模式等) */
7
- @import './assets/styles/index.scss';
8
-
9
- /* 全局样式变量(可选) */
10
- :root {
11
- --bg-color: #ffffff;
12
- --bg-color-page: #f5f7fa;
13
- --color-border-lighter: #e4e7ed;
1
+ /**
2
+ * xto-fronted 样式入口
3
+ * 组件样式已包含在各自的 Vue 组件中(scoped)
4
+ */
5
+
6
+ /* 导入全局样式(包含灰色模式、暗黑模式等) */
7
+ @import './assets/styles/index.scss';
8
+
9
+ /* 全局样式变量(可选) */
10
+ :root {
11
+ --bg-color: #ffffff;
12
+ --bg-color-page: #f5f7fa;
13
+ --color-border-lighter: #e4e7ed;
14
14
  }
@@ -1,57 +1,57 @@
1
- <script setup lang="ts">
2
- import { useRouter } from 'vue-router'
3
- import { Button } from '@xto/base'
4
-
5
- const router = useRouter()
6
-
7
- const goBack = () => {
8
- router.push('/')
9
- }
10
- </script>
11
-
12
- <template>
13
- <div class="error-page">
14
- <div class="error-page__content">
15
- <div class="error-page__code">403</div>
16
- <div class="error-page__title">无访问权限</div>
17
- <div class="error-page__desc">抱歉,您没有权限访问此页面</div>
18
- <Button type="primary" @click="goBack">返回首页</Button>
19
- </div>
20
- </div>
21
- </template>
22
-
23
- <style lang="scss" scoped>
24
- .error-page {
25
- width: 100%;
26
- height: 100%;
27
- display: flex;
28
- align-items: center;
29
- justify-content: center;
30
- background-color: var(--bg-color-page);
31
-
32
- &__content {
33
- text-align: center;
34
- }
35
-
36
- &__code {
37
- font-size: 120px;
38
- font-weight: 600;
39
- color: var(--color-warning);
40
- line-height: 1;
41
- margin-bottom: 20px;
42
- }
43
-
44
- &__title {
45
- font-size: 24px;
46
- font-weight: 500;
47
- color: var(--color-text-primary);
48
- margin-bottom: 10px;
49
- }
50
-
51
- &__desc {
52
- font-size: 14px;
53
- color: var(--color-text-secondary);
54
- margin-bottom: 30px;
55
- }
56
- }
1
+ <script setup lang="ts">
2
+ import { useRouter } from 'vue-router'
3
+ import { Button } from '@xto/base'
4
+
5
+ const router = useRouter()
6
+
7
+ const goBack = () => {
8
+ router.push('/')
9
+ }
10
+ </script>
11
+
12
+ <template>
13
+ <div class="error-page">
14
+ <div class="error-page__content">
15
+ <div class="error-page__code">403</div>
16
+ <div class="error-page__title">无访问权限</div>
17
+ <div class="error-page__desc">抱歉,您没有权限访问此页面</div>
18
+ <Button type="primary" @click="goBack">返回首页</Button>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <style lang="scss" scoped>
24
+ .error-page {
25
+ width: 100%;
26
+ height: 100%;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ background-color: var(--bg-color-page);
31
+
32
+ &__content {
33
+ text-align: center;
34
+ }
35
+
36
+ &__code {
37
+ font-size: 120px;
38
+ font-weight: 600;
39
+ color: var(--color-warning);
40
+ line-height: 1;
41
+ margin-bottom: 20px;
42
+ }
43
+
44
+ &__title {
45
+ font-size: 24px;
46
+ font-weight: 500;
47
+ color: var(--color-text-primary);
48
+ margin-bottom: 10px;
49
+ }
50
+
51
+ &__desc {
52
+ font-size: 14px;
53
+ color: var(--color-text-secondary);
54
+ margin-bottom: 30px;
55
+ }
56
+ }
57
57
  </style>
@@ -1,57 +1,57 @@
1
- <script setup lang="ts">
2
- import { useRouter } from 'vue-router'
3
- import { Button } from '@xto/base'
4
-
5
- const router = useRouter()
6
-
7
- const goBack = () => {
8
- router.push('/')
9
- }
10
- </script>
11
-
12
- <template>
13
- <div class="error-page">
14
- <div class="error-page__content">
15
- <div class="error-page__code">404</div>
16
- <div class="error-page__title">页面不存在</div>
17
- <div class="error-page__desc">抱歉,您访问的页面不存在或已被删除</div>
18
- <Button type="primary" @click="goBack">返回首页</Button>
19
- </div>
20
- </div>
21
- </template>
22
-
23
- <style lang="scss" scoped>
24
- .error-page {
25
- width: 100%;
26
- height: 100%;
27
- display: flex;
28
- align-items: center;
29
- justify-content: center;
30
- background-color: var(--bg-color-page);
31
-
32
- &__content {
33
- text-align: center;
34
- }
35
-
36
- &__code {
37
- font-size: 120px;
38
- font-weight: 600;
39
- color: var(--color-primary);
40
- line-height: 1;
41
- margin-bottom: 20px;
42
- }
43
-
44
- &__title {
45
- font-size: 24px;
46
- font-weight: 500;
47
- color: var(--color-text-primary);
48
- margin-bottom: 10px;
49
- }
50
-
51
- &__desc {
52
- font-size: 14px;
53
- color: var(--color-text-secondary);
54
- margin-bottom: 30px;
55
- }
56
- }
1
+ <script setup lang="ts">
2
+ import { useRouter } from 'vue-router'
3
+ import { Button } from '@xto/base'
4
+
5
+ const router = useRouter()
6
+
7
+ const goBack = () => {
8
+ router.push('/')
9
+ }
10
+ </script>
11
+
12
+ <template>
13
+ <div class="error-page">
14
+ <div class="error-page__content">
15
+ <div class="error-page__code">404</div>
16
+ <div class="error-page__title">页面不存在</div>
17
+ <div class="error-page__desc">抱歉,您访问的页面不存在或已被删除</div>
18
+ <Button type="primary" @click="goBack">返回首页</Button>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <style lang="scss" scoped>
24
+ .error-page {
25
+ width: 100%;
26
+ height: 100%;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ background-color: var(--bg-color-page);
31
+
32
+ &__content {
33
+ text-align: center;
34
+ }
35
+
36
+ &__code {
37
+ font-size: 120px;
38
+ font-weight: 600;
39
+ color: var(--color-primary);
40
+ line-height: 1;
41
+ margin-bottom: 20px;
42
+ }
43
+
44
+ &__title {
45
+ font-size: 24px;
46
+ font-weight: 500;
47
+ color: var(--color-text-primary);
48
+ margin-bottom: 10px;
49
+ }
50
+
51
+ &__desc {
52
+ font-size: 14px;
53
+ color: var(--color-text-secondary);
54
+ margin-bottom: 30px;
55
+ }
56
+ }
57
57
  </style>