xto-fronted 0.1.1 → 0.1.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 (67) hide show
  1. package/.env.development +3 -4
  2. package/.env.production +3 -4
  3. package/bin/cli.js +104 -0
  4. package/dist/{403-MQkNUulz.js → 403-DM5wfQkM.js} +6 -6
  5. package/dist/{404-BOFYLq4X.js → 404-BurAu5LC.js} +7 -7
  6. package/dist/api/auth.d.ts +9 -8
  7. package/dist/api/menu.d.ts +3 -0
  8. package/dist/api/user.d.ts +2 -12
  9. package/dist/composables/index.d.ts +8 -0
  10. package/dist/composables/useApp.d.ts +64 -0
  11. package/dist/composables/useAuth.d.ts +19 -4
  12. package/dist/composables/useMenu.d.ts +34 -0
  13. package/dist/config/index.d.ts +11 -0
  14. package/dist/index-BNiEld34.js +15 -0
  15. package/dist/index-Be9RiEfo.js +98 -0
  16. package/dist/index-BqRv1bdN.js +1185 -0
  17. package/dist/index-CQLVXvNJ.js +15 -0
  18. package/dist/index-CyiE8n2V.js +15 -0
  19. package/dist/index-xauR1bOL.js +15 -0
  20. package/dist/index.d.ts +7 -4
  21. package/dist/index.es.js +50 -66
  22. package/dist/index.umd.js +1 -1
  23. package/dist/stores/auth.d.ts +60 -23
  24. package/dist/stores/menu.d.ts +40 -29
  25. package/dist/stores/user.d.ts +63 -84
  26. package/dist/style.css +1 -1
  27. package/dist/utils/auth.d.ts +15 -7
  28. package/dist/utils/permission.d.ts +1 -6
  29. package/dist/views/system/menu/index.vue.d.ts +1 -3
  30. package/dist/views/system/role/index.vue.d.ts +1 -3
  31. package/dist/views/system/user/index.vue.d.ts +1 -3
  32. package/package.json +27 -19
  33. package/src/api/auth.ts +34 -25
  34. package/src/api/menu.ts +13 -0
  35. package/src/api/user.ts +11 -45
  36. package/src/components/Layout/Header.vue +334 -389
  37. package/src/components/Layout/Sidebar.vue +212 -296
  38. package/src/components/Layout/Tabs.vue +19 -133
  39. package/src/composables/index.ts +9 -0
  40. package/src/composables/useApp.ts +170 -0
  41. package/src/composables/useAuth.ts +69 -44
  42. package/src/composables/useMenu.ts +141 -0
  43. package/src/config/index.ts +19 -0
  44. package/src/directives/permission.ts +40 -37
  45. package/src/index.ts +9 -4
  46. package/src/router/index.ts +70 -80
  47. package/src/stores/auth.ts +44 -31
  48. package/src/stores/menu.ts +157 -79
  49. package/src/stores/user.ts +40 -72
  50. package/src/types/api.d.ts +102 -83
  51. package/src/types/xto.d.ts +148 -148
  52. package/src/utils/auth.ts +85 -61
  53. package/src/utils/permission.ts +29 -41
  54. package/src/utils/request.ts +125 -125
  55. package/src/utils/storage.ts +10 -1
  56. package/src/views/dashboard/index.vue +31 -283
  57. package/src/views/login/index.vue +140 -247
  58. package/src/views/system/menu/index.vue +31 -380
  59. package/src/views/system/role/index.vue +31 -303
  60. package/src/views/system/user/index.vue +31 -326
  61. package/vite.config.ts +3 -3
  62. package/dist/index-BJxYdNPy.js +0 -475
  63. package/dist/index-BvnIIBR1.js +0 -142
  64. package/dist/index-CEvAq6KE.js +0 -372
  65. package/dist/index-DPkqej__.js +0 -345
  66. package/dist/index-pq9Z5K62.js +0 -184
  67. package/dist/index-vVfjShJR.js +0 -1183
@@ -1,42 +1,30 @@
1
- /**
2
- * 权限工具函数
3
- */
4
-
5
- import { useUserStore } from '@/stores/user'
6
-
7
- /**
8
- * 检查是否有权限
9
- * @param permission 权限标识
10
- */
11
- export function hasPermission(permission: string | string[]): boolean {
12
- const userStore = useUserStore()
13
- const permissions = userStore.permissions
14
-
15
- if (Array.isArray(permission)) {
16
- return permission.some(p => permissions.includes(p))
17
- }
18
-
19
- return permissions.includes(permission)
20
- }
21
-
22
- /**
23
- * 检查是否有角色
24
- * @param role 角色标识
25
- */
26
- export function hasRole(role: string | string[]): boolean {
27
- const userStore = useUserStore()
28
- const roles = userStore.roles
29
-
30
- if (Array.isArray(role)) {
31
- return role.some(r => roles.includes(r))
32
- }
33
-
34
- return roles.includes(role)
35
- }
36
-
37
- /**
38
- * 检查是否是管理员
39
- */
40
- export function isAdmin(): boolean {
41
- return hasRole('admin')
1
+ /**
2
+ * 权限工具函数
3
+ */
4
+
5
+ import { useMenuStore } from '@/stores/menu'
6
+
7
+ /**
8
+ * 检查是否有权限(基于菜单按钮权限)
9
+ * @param permission 权限标识
10
+ */
11
+ export function hasPermission(permission: string | string[]): boolean {
12
+ const menuStore = useMenuStore()
13
+ const currentPath = window.location.pathname
14
+ const btnList = menuStore.menuBtnListMap[currentPath] || []
15
+ const btnCodes = btnList.map(item => item.code)
16
+
17
+ if (Array.isArray(permission)) {
18
+ return permission.some(p => btnCodes.includes(p))
19
+ }
20
+
21
+ return btnCodes.includes(permission)
22
+ }
23
+
24
+ /**
25
+ * 检查是否是管理员
26
+ */
27
+ export function isAdmin(): boolean {
28
+ // 暂时返回 true,后续可根据实际需求实现
29
+ return true
42
30
  }
@@ -1,126 +1,126 @@
1
- /**
2
- * Axios 请求封装
3
- */
4
-
5
- import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
6
- import { getToken, clearToken } from './auth'
7
- import { Message } from '@xto/feedback'
8
-
9
- // 响应数据接口
10
- export interface ApiResponse<T = unknown> {
11
- code: number
12
- data: T
13
- message: string
14
- }
15
-
16
- // 分页参数
17
- export interface PageParams {
18
- page: number
19
- pageSize: number
20
- }
21
-
22
- // 分页响应
23
- export interface PageResponse<T> {
24
- list: T[]
25
- total: number
26
- page: number
27
- pageSize: number
28
- }
29
-
30
- // 创建 axios 实例
31
- const createRequest = (): AxiosInstance => {
32
- const instance = axios.create({
33
- baseURL: import.meta.env.VITE_API_BASE_URL,
34
- timeout: 15000,
35
- headers: {
36
- 'Content-Type': 'application/json'
37
- }
38
- })
39
-
40
- // 请求拦截器
41
- instance.interceptors.request.use(
42
- (config: InternalAxiosRequestConfig) => {
43
- const token = getToken()
44
- if (token) {
45
- config.headers.Authorization = `Bearer ${token}`
46
- }
47
- return config
48
- },
49
- (error) => {
50
- return Promise.reject(error)
51
- }
52
- )
53
-
54
- // 响应拦截器
55
- instance.interceptors.response.use(
56
- (response: AxiosResponse<ApiResponse>) => {
57
- const { data } = response
58
-
59
- // 成功
60
- if (data.code === 200 || data.code === 0) {
61
- return response
62
- }
63
-
64
- // 业务错误
65
- Message.error(data.message || '请求失败')
66
- return Promise.reject(new Error(data.message || '请求失败'))
67
- },
68
- (error) => {
69
- const { response } = error
70
-
71
- if (response) {
72
- switch (response.status) {
73
- case 401:
74
- Message.error('登录已过期,请重新登录')
75
- clearToken()
76
- window.location.href = '/login'
77
- break
78
- case 403:
79
- Message.error('没有权限访问')
80
- break
81
- case 404:
82
- Message.error('请求资源不存在')
83
- break
84
- case 500:
85
- Message.error('服务器错误')
86
- break
87
- default:
88
- Message.error(response.data?.message || '请求失败')
89
- }
90
- } else {
91
- Message.error('网络连接失败')
92
- }
93
-
94
- return Promise.reject(error)
95
- }
96
- )
97
-
98
- return instance
99
- }
100
-
101
- const request = createRequest()
102
-
103
- // 请求方法
104
- export const http = {
105
- get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
106
- return request.get(url, config).then((res) => res.data)
107
- },
108
-
109
- post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
110
- return request.post(url, data, config).then((res) => res.data)
111
- },
112
-
113
- put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
114
- return request.put(url, data, config).then((res) => res.data)
115
- },
116
-
117
- patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
118
- return request.patch(url, data, config).then((res) => res.data)
119
- },
120
-
121
- delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
122
- return request.delete(url, config).then((res) => res.data)
123
- }
124
- }
125
-
1
+ /**
2
+ * Axios 请求封装
3
+ */
4
+
5
+ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
6
+ import { getToken, getTokenType, clearToken } from './auth'
7
+ import { Message } from '@xto/feedback'
8
+ import config from '@/config'
9
+
10
+ // 响应数据接口
11
+ export interface ApiResponse<T = unknown> {
12
+ code: number
13
+ data: T
14
+ message: string
15
+ }
16
+
17
+ // 分页参数
18
+ export interface PageParams {
19
+ page: number
20
+ pageSize: number
21
+ }
22
+
23
+ // 分页响应
24
+ export interface PageResponse<T> {
25
+ list: T[]
26
+ total: number
27
+ page: number
28
+ pageSize: number
29
+ }
30
+
31
+ // 创建 axios 实例
32
+ const createRequest = (): AxiosInstance => {
33
+ const instance = axios.create({
34
+ baseURL: config.baseUrl,
35
+ timeout: 15000,
36
+ headers: {
37
+ 'Content-Type': 'application/json'
38
+ }
39
+ })
40
+
41
+ // 请求拦截器
42
+ instance.interceptors.request.use(
43
+ (config: InternalAxiosRequestConfig) => {
44
+ const token = getToken()
45
+ if (token) {
46
+ const tokenType = getTokenType()
47
+ config.headers.Authorization = `${tokenType} ${token}`
48
+ }
49
+ return config
50
+ },
51
+ (error) => {
52
+ return Promise.reject(error)
53
+ }
54
+ )
55
+
56
+ // 响应拦截器
57
+ instance.interceptors.response.use(
58
+ (response: AxiosResponse<ApiResponse>) => {
59
+ const { data } = response
60
+
61
+ // 成功
62
+ if (data.code === 200 || data.code === 0) {
63
+ return response
64
+ }
65
+
66
+ // 业务错误
67
+ Message.error(data.message || '请求失败')
68
+ return Promise.reject(new Error(data.message || '请求失败'))
69
+ },
70
+ (error) => {
71
+ const { response } = error
72
+
73
+ if (response) {
74
+ switch (response.status) {
75
+ case 401:
76
+ case 403:
77
+ Message.error('登录已过期,请重新登录')
78
+ clearToken()
79
+ window.location.href = '/login'
80
+ break
81
+ case 404:
82
+ Message.error('请求资源不存在')
83
+ break
84
+ case 500:
85
+ Message.error('服务器错误')
86
+ break
87
+ default:
88
+ Message.error(response.data?.message || '请求失败')
89
+ }
90
+ } else {
91
+ Message.error('网络连接失败')
92
+ }
93
+
94
+ return Promise.reject(error)
95
+ }
96
+ )
97
+
98
+ return instance
99
+ }
100
+
101
+ const request = createRequest()
102
+
103
+ // 请求方法
104
+ export const http = {
105
+ get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
106
+ return request.get(url, config).then((res) => res.data)
107
+ },
108
+
109
+ post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
110
+ return request.post(url, data, config).then((res) => res.data)
111
+ },
112
+
113
+ put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
114
+ return request.put(url, data, config).then((res) => res.data)
115
+ },
116
+
117
+ patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
118
+ return request.patch(url, data, config).then((res) => res.data)
119
+ },
120
+
121
+ delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<ApiResponse<T>> {
122
+ return request.delete(url, config).then((res) => res.data)
123
+ }
124
+ }
125
+
126
126
  export default request
@@ -2,7 +2,12 @@
2
2
  * 本地存储封装
3
3
  */
4
4
 
5
- const prefix = 'xto_'
5
+ import config from '@/config'
6
+
7
+ // toboyu-cloud 格式的 key 前缀
8
+ const getPrefix = (): string => {
9
+ return `tooyu-cloud:${config.appId}:`
10
+ }
6
11
 
7
12
  interface StorageWrapper {
8
13
  get<T>(key: string): T | null
@@ -14,6 +19,7 @@ interface StorageWrapper {
14
19
  const createStorage = (storage: globalThis.Storage): StorageWrapper => {
15
20
  return {
16
21
  get<T>(key: string): T | null {
22
+ const prefix = getPrefix()
17
23
  const value = storage.getItem(prefix + key)
18
24
  if (!value) return null
19
25
  try {
@@ -23,6 +29,7 @@ const createStorage = (storage: globalThis.Storage): StorageWrapper => {
23
29
  }
24
30
  },
25
31
  set(key: string, value: unknown): void {
32
+ const prefix = getPrefix()
26
33
  if (value === null || value === undefined) {
27
34
  storage.removeItem(prefix + key)
28
35
  return
@@ -31,9 +38,11 @@ const createStorage = (storage: globalThis.Storage): StorageWrapper => {
31
38
  storage.setItem(prefix + key, stringValue)
32
39
  },
33
40
  remove(key: string): void {
41
+ const prefix = getPrefix()
34
42
  storage.removeItem(prefix + key)
35
43
  },
36
44
  clear(): void {
45
+ const prefix = getPrefix()
37
46
  const keys = Object.keys(storage)
38
47
  keys.forEach(key => {
39
48
  if (key.startsWith(prefix)) {
@@ -1,284 +1,32 @@
1
- <script setup lang="ts">
2
- import { ref } from 'vue'
3
- import { Card, Tag, Progress } from '@xto/data'
4
-
5
- const stats = ref([
6
- { title: '用户总数', value: 1234, icon: '👥', color: '#409eff' },
7
- { title: '今日访问', value: 567, icon: '👀', color: '#67c23a' },
8
- { title: '订单数量', value: 890, icon: '📦', color: '#e6a23c' },
9
- { title: '销售金额', value: 123456, icon: '💰', color: '#f56c6c' }
10
- ])
11
-
12
- const activities = ref([
13
- { user: '张三', action: '登录系统', time: '2分钟前', type: 'success' },
14
- { user: '李四', action: '修改了用户信息', time: '5分钟前', type: 'warning' },
15
- { user: '王五', action: '创建了新订单', time: '10分钟前', type: 'info' },
16
- { user: '赵六', action: '删除了测试数据', time: '30分钟前', type: 'danger' },
17
- { user: '钱七', action: '更新了系统配置', time: '1小时前', type: 'primary' }
18
- ])
19
-
20
- const quickLinks = ref([
21
- { title: '用户管理', path: '/system/user', icon: '👤' },
22
- { title: '角色管理', path: '/system/role', icon: '👥' },
23
- { title: '菜单管理', path: '/system/menu', icon: '📋' },
24
- { title: '系统设置', path: '/system', icon: '⚙️' }
25
- ])
26
- </script>
27
-
28
- <template>
29
- <div class="dashboard">
30
- <!-- 统计卡片 -->
31
- <div class="dashboard__stats">
32
- <Card v-for="stat in stats" :key="stat.title" class="stat-card">
33
- <div class="stat-card__content">
34
- <div class="stat-card__icon" :style="{ backgroundColor: stat.color + '20' }">
35
- {{ stat.icon }}
36
- </div>
37
- <div class="stat-card__info">
38
- <div class="stat-card__title">{{ stat.title }}</div>
39
- <div class="stat-card__value" :style="{ color: stat.color }">
40
- {{ stat.value.toLocaleString() }}
41
- </div>
42
- </div>
43
- </div>
44
- </Card>
45
- </div>
46
-
47
- <!-- 主体内容 -->
48
- <div class="dashboard__main">
49
- <!-- 快捷入口 -->
50
- <Card class="dashboard__quick">
51
- <template #header>
52
- <span class="card-title">快捷入口</span>
53
- </template>
54
- <div class="quick-links">
55
- <router-link
56
- v-for="link in quickLinks"
57
- :key="link.path"
58
- :to="link.path"
59
- class="quick-link"
60
- >
61
- <span class="quick-link__icon">{{ link.icon }}</span>
62
- <span class="quick-link__title">{{ link.title }}</span>
63
- </router-link>
64
- </div>
65
- </Card>
66
-
67
- <!-- 最近活动 -->
68
- <Card class="dashboard__activity">
69
- <template #header>
70
- <span class="card-title">最近活动</span>
71
- </template>
72
- <div class="activity-list">
73
- <div v-for="(activity, index) in activities" :key="index" class="activity-item">
74
- <Tag :type="activity.type as any" size="small">{{ activity.user }}</Tag>
75
- <span class="activity-item__action">{{ activity.action }}</span>
76
- <span class="activity-item__time">{{ activity.time }}</span>
77
- </div>
78
- </div>
79
- </Card>
80
- </div>
81
-
82
- <!-- 系统信息 -->
83
- <Card class="dashboard__system">
84
- <template #header>
85
- <span class="card-title">系统信息</span>
86
- </template>
87
- <div class="system-info">
88
- <div class="system-info__item">
89
- <span class="system-info__label">系统版本</span>
90
- <span class="system-info__value">v1.0.0</span>
91
- </div>
92
- <div class="system-info__item">
93
- <span class="system-info__label">Vue 版本</span>
94
- <span class="system-info__value">3.4.21</span>
95
- </div>
96
- <div class="system-info__item">
97
- <span class="system-info__label">构建工具</span>
98
- <span class="system-info__value">Vite 5</span>
99
- </div>
100
- <div class="system-info__item">
101
- <span class="system-info__label">UI 组件库</span>
102
- <span class="system-info__value">xto</span>
103
- </div>
104
- <div class="system-info__item">
105
- <span class="system-info__label">服务器状态</span>
106
- <Progress :percentage="75" status="success" />
107
- </div>
108
- <div class="system-info__item">
109
- <span class="system-info__label">内存使用</span>
110
- <Progress :percentage="45" />
111
- </div>
112
- </div>
113
- </Card>
114
- </div>
115
- </template>
116
-
117
- <style lang="scss" scoped>
118
- .dashboard {
119
- padding: 20px;
120
-
121
- &__stats {
122
- display: grid;
123
- grid-template-columns: repeat(4, 1fr);
124
- gap: 20px;
125
- margin-bottom: 20px;
126
-
127
- @media (max-width: 1200px) {
128
- grid-template-columns: repeat(2, 1fr);
129
- }
130
-
131
- @media (max-width: 768px) {
132
- grid-template-columns: 1fr;
133
- }
134
- }
135
-
136
- &__main {
137
- display: grid;
138
- grid-template-columns: repeat(2, 1fr);
139
- gap: 20px;
140
- margin-bottom: 20px;
141
-
142
- @media (max-width: 992px) {
143
- grid-template-columns: 1fr;
144
- }
145
- }
146
-
147
- &__quick,
148
- &__activity {
149
- min-height: 300px;
150
- }
151
- }
152
-
153
- .card-title {
154
- font-size: 16px;
155
- font-weight: 500;
156
- }
157
-
158
- .stat-card {
159
- &__content {
160
- display: flex;
161
- align-items: center;
162
- gap: 15px;
163
- }
164
-
165
- &__icon {
166
- width: 50px;
167
- height: 50px;
168
- display: flex;
169
- align-items: center;
170
- justify-content: center;
171
- font-size: 24px;
172
- border-radius: var(--border-radius-base);
173
- }
174
-
175
- &__info {
176
- flex: 1;
177
- }
178
-
179
- &__title {
180
- font-size: 14px;
181
- color: var(--color-text-secondary);
182
- margin-bottom: 5px;
183
- }
184
-
185
- &__value {
186
- font-size: 24px;
187
- font-weight: 600;
188
- }
189
- }
190
-
191
- .quick-links {
192
- display: grid;
193
- grid-template-columns: repeat(4, 1fr);
194
- gap: 15px;
195
-
196
- @media (max-width: 768px) {
197
- grid-template-columns: repeat(2, 1fr);
198
- }
199
- }
200
-
201
- .quick-link {
202
- display: flex;
203
- flex-direction: column;
204
- align-items: center;
205
- justify-content: center;
206
- padding: 20px;
207
- border-radius: var(--border-radius-base);
208
- background-color: var(--color-fill);
209
- text-decoration: none;
210
- transition: all var(--transition-duration-fast);
211
-
212
- &:hover {
213
- background-color: var(--color-primary-light-9);
214
- transform: translateY(-2px);
215
- }
216
-
217
- &__icon {
218
- font-size: 28px;
219
- margin-bottom: 10px;
220
- }
221
-
222
- &__title {
223
- font-size: 14px;
224
- color: var(--color-text-primary);
225
- }
226
- }
227
-
228
- .activity-list {
229
- display: flex;
230
- flex-direction: column;
231
- gap: 15px;
232
- }
233
-
234
- .activity-item {
235
- display: flex;
236
- align-items: center;
237
- gap: 10px;
238
- padding-bottom: 15px;
239
- border-bottom: 1px solid var(--color-border-lighter);
240
-
241
- &:last-child {
242
- border-bottom: none;
243
- padding-bottom: 0;
244
- }
245
-
246
- &__action {
247
- flex: 1;
248
- font-size: 14px;
249
- color: var(--color-text-regular);
250
- }
251
-
252
- &__time {
253
- font-size: 12px;
254
- color: var(--color-text-placeholder);
255
- }
256
- }
257
-
258
- .system-info {
259
- display: grid;
260
- grid-template-columns: repeat(2, 1fr);
261
- gap: 20px;
262
-
263
- @media (max-width: 768px) {
264
- grid-template-columns: 1fr;
265
- }
266
-
267
- &__item {
268
- display: flex;
269
- align-items: center;
270
- gap: 10px;
271
- }
272
-
273
- &__label {
274
- font-size: 14px;
275
- color: var(--color-text-secondary);
276
- min-width: 80px;
277
- }
278
-
279
- &__value {
280
- font-size: 14px;
281
- color: var(--color-text-primary);
282
- }
283
- }
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <div class="dashboard">
6
+ <div class="dashboard__placeholder">
7
+ <h2>首页</h2>
8
+ <p>功能开发中...</p>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <style lang="scss" scoped>
14
+ .dashboard {
15
+ padding: 20px;
16
+ height: 100%;
17
+
18
+ &__placeholder {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ height: 100%;
24
+ color: var(--color-text-secondary);
25
+
26
+ h2 {
27
+ margin-bottom: 10px;
28
+ color: var(--color-text-primary);
29
+ }
30
+ }
31
+ }
284
32
  </style>