xto-fronted 0.4.7 → 0.4.8

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 (75) hide show
  1. package/.env.development +7 -7
  2. package/.env.production +7 -7
  3. package/dist/assets/403-AFBQifUI.js +1 -0
  4. package/dist/assets/403-BHEXXbt2.css +1 -0
  5. package/dist/assets/404-Ct_A1n7S.css +1 -0
  6. package/dist/assets/404-WFvpcD2_.js +1 -0
  7. package/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
  8. package/dist/assets/index-1juADvYN.js +2 -0
  9. package/dist/assets/index-B-sX4Ru0.js +1 -0
  10. package/dist/assets/index-BMcziU5a.css +1 -0
  11. package/dist/assets/index-BRR97dc6.js +1 -0
  12. package/dist/assets/index-BZA0ksjx.css +1 -0
  13. package/dist/assets/index-BpV_8nl0.js +1 -0
  14. package/dist/assets/index-BvzhR4zp.js +1 -0
  15. package/dist/assets/index-CUh_s55Z.css +1 -0
  16. package/dist/assets/index-CVjdnIgR.css +1 -0
  17. package/dist/assets/index-CYq57-zj.js +1 -0
  18. package/dist/assets/index-CkL3sVAQ.js +2 -0
  19. package/dist/assets/index-CtrKVYJb.css +1 -0
  20. package/dist/assets/index-Cz2P_bsS.js +1 -0
  21. package/dist/assets/index-D9wlAuR_.js +1 -0
  22. package/dist/assets/index-DawJb02s.css +1 -0
  23. package/dist/assets/index-DfFR6NLf.js +1 -0
  24. package/dist/assets/index-DwVgMO8e.js +1 -0
  25. package/dist/assets/index-GDP-IkXE.css +1 -0
  26. package/dist/assets/index-Iaz1ZzPC.js +2 -0
  27. package/dist/assets/index-PfV8pzQz.css +1 -0
  28. package/dist/assets/index-Swfu6yvD.css +1 -0
  29. package/dist/assets/index-Te8_PRgJ.js +1 -0
  30. package/dist/assets/index-WyZ91RLx.css +1 -0
  31. package/dist/assets/index-tFYRoFdE.js +1 -0
  32. package/dist/assets/vendor-42ANG6Sg.js +6 -0
  33. package/dist/assets/vite-Dw-pgLOX.js +1 -0
  34. package/dist/assets/vue-vendor-Br-l7wbK.js +29 -0
  35. package/dist/assets/xto-base-C-IBqjVs.js +1 -0
  36. package/dist/assets/xto-base-C6eqMPdO.css +1 -0
  37. package/dist/assets/xto-business--V1F5Gwb.css +1 -0
  38. package/dist/assets/xto-core-DZK7Cyg0.js +1 -0
  39. package/dist/assets/xto-data-BFpiDgJi.js +1 -0
  40. package/dist/assets/xto-data-CnAQAQH2.css +1 -0
  41. package/dist/assets/xto-feedback-B7ipsTfz.js +1 -0
  42. package/dist/assets/xto-feedback-DBwJzoTj.css +1 -0
  43. package/dist/assets/xto-form-CrsyAjyr.css +1 -0
  44. package/dist/assets/xto-form-NRjKKNcY.js +1 -0
  45. package/dist/assets/xto-layout-BqU8RuWL.css +1 -0
  46. package/dist/assets/xto-navigation-BiSaXPfr.js +1 -0
  47. package/dist/assets/xto-navigation-C1cnSL2E.css +1 -0
  48. package/dist/assets/xto-navigation-CBPg4dCc.css +1 -0
  49. package/dist/assets/xto-navigation-CKabFu9d.js +1 -0
  50. package/dist/index.html +28 -0
  51. package/package.json +85 -85
  52. package/src/api/auth.ts +25 -25
  53. package/src/api/system.ts +66 -66
  54. package/src/assets/styles/_dark.scss +406 -406
  55. package/src/components/Layout/Header.vue +973 -973
  56. package/src/components/Layout/Sidebar.vue +273 -273
  57. package/src/components/Layout/index.vue +443 -63
  58. package/src/composables/useApp.ts +61 -61
  59. package/src/composables/useAuth.ts +16 -16
  60. package/src/directives/permission.ts +27 -27
  61. package/src/env.d.ts +18 -18
  62. package/src/index.ts +47 -47
  63. package/src/router/dynamicRoutes.ts +162 -162
  64. package/src/router/guards.ts +128 -128
  65. package/src/router/index.ts +79 -79
  66. package/src/stores/auth.ts +65 -65
  67. package/src/stores/menu.ts +48 -48
  68. package/src/stores/user.ts +50 -50
  69. package/src/types/api.d.ts +80 -80
  70. package/src/utils/auth.ts +99 -99
  71. package/src/utils/config.ts +80 -80
  72. package/src/utils/permission.ts +32 -32
  73. package/src/utils/request.ts +124 -124
  74. package/src/views/login/index.vue +194 -194
  75. package/vite.config.ts +135 -135
@@ -1,125 +1,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
-
9
- // 响应数据接口(Euler 框架 Result 格式)
10
- export interface ApiResponse<T = unknown> {
11
- code: number
12
- data: T
13
- message: string
14
- }
15
-
16
- // 分页参数(Euler 框架格式)
17
- export interface PageParams {
18
- pageNo: number
19
- pageSize: number
20
- }
21
-
22
- // 分页响应(Euler 框架格式)
23
- export interface PageResponse<T> {
24
- records: T[]
25
- total: number
26
- }
27
-
28
- // 创建 axios 实例
29
- const createRequest = (): AxiosInstance => {
30
- const instance = axios.create({
31
- baseURL: import.meta.env.VITE_API_BASE_URL,
32
- timeout: 30000,
33
- headers: {
34
- 'Content-Type': 'application/json'
35
- }
36
- })
37
-
38
- // 请求拦截器
39
- instance.interceptors.request.use(
40
- (config: InternalAxiosRequestConfig) => {
41
- const token = getToken()
42
- const tokenType = getTokenType() || 'Bearer'
43
- if (token) {
44
- config.headers.Authorization = `${tokenType} ${token}`
45
- }
46
- return config
47
- },
48
- (error) => {
49
- return Promise.reject(error)
50
- }
51
- )
52
-
53
- // 响应拦截器
54
- instance.interceptors.response.use(
55
- (response: AxiosResponse<ApiResponse>) => {
56
- const { data } = response
57
-
58
- // Euler 框架返回 Result 包装格式,成功时直接返回 data.data 中的实际数据
59
- if (data.code === 200 || data.code === 0) {
60
- return data.data as any
61
- }
62
-
63
- // 业务错误
64
- Message.error(data.message || '请求失败')
65
- return Promise.reject(new Error(data.message || '请求失败'))
66
- },
67
- (error) => {
68
- const { response } = error
69
-
70
- if (response) {
71
- switch (response.status) {
72
- case 401:
73
- Message.error('登录已过期,请重新登录')
74
- clearToken()
75
- window.location.href = '/login'
76
- break
77
- case 403:
78
- Message.error('没有权限访问')
79
- break
80
- case 404:
81
- Message.error('请求资源不存在')
82
- break
83
- case 500:
84
- Message.error('服务器错误')
85
- break
86
- default:
87
- Message.error(response.data?.message || '请求失败')
88
- }
89
- } else {
90
- Message.error('网络连接失败')
91
- }
92
-
93
- return Promise.reject(error)
94
- }
95
- )
96
-
97
- return instance
98
- }
99
-
100
- const request = createRequest()
101
-
102
- // 请求方法 - 直接返回实际数据,无需手动解包
103
- export const http = {
104
- get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
105
- return request.get(url, config)
106
- },
107
-
108
- post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
109
- return request.post(url, data, config)
110
- },
111
-
112
- put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
113
- return request.put(url, data, config)
114
- },
115
-
116
- patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
117
- return request.patch(url, data, config)
118
- },
119
-
120
- delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
121
- return request.delete(url, config)
122
- }
123
- }
124
-
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
+
9
+ // 响应数据接口(Euler 框架 Result 格式)
10
+ export interface ApiResponse<T = unknown> {
11
+ code: number
12
+ data: T
13
+ message: string
14
+ }
15
+
16
+ // 分页参数(Euler 框架格式)
17
+ export interface PageParams {
18
+ pageNo: number
19
+ pageSize: number
20
+ }
21
+
22
+ // 分页响应(Euler 框架格式)
23
+ export interface PageResponse<T> {
24
+ records: T[]
25
+ total: number
26
+ }
27
+
28
+ // 创建 axios 实例
29
+ const createRequest = (): AxiosInstance => {
30
+ const instance = axios.create({
31
+ baseURL: import.meta.env.VITE_API_BASE_URL,
32
+ timeout: 30000,
33
+ headers: {
34
+ 'Content-Type': 'application/json'
35
+ }
36
+ })
37
+
38
+ // 请求拦截器
39
+ instance.interceptors.request.use(
40
+ (config: InternalAxiosRequestConfig) => {
41
+ const token = getToken()
42
+ const tokenType = getTokenType() || 'Bearer'
43
+ if (token) {
44
+ config.headers.Authorization = `${tokenType} ${token}`
45
+ }
46
+ return config
47
+ },
48
+ (error) => {
49
+ return Promise.reject(error)
50
+ }
51
+ )
52
+
53
+ // 响应拦截器
54
+ instance.interceptors.response.use(
55
+ (response: AxiosResponse<ApiResponse>) => {
56
+ const { data } = response
57
+
58
+ // Euler 框架返回 Result 包装格式,成功时直接返回 data.data 中的实际数据
59
+ if (data.code === 200 || data.code === 0) {
60
+ return data.data as any
61
+ }
62
+
63
+ // 业务错误
64
+ Message.error(data.message || '请求失败')
65
+ return Promise.reject(new Error(data.message || '请求失败'))
66
+ },
67
+ (error) => {
68
+ const { response } = error
69
+
70
+ if (response) {
71
+ switch (response.status) {
72
+ case 401:
73
+ Message.error('登录已过期,请重新登录')
74
+ clearToken()
75
+ window.location.href = '/login'
76
+ break
77
+ case 403:
78
+ Message.error('没有权限访问')
79
+ break
80
+ case 404:
81
+ Message.error('请求资源不存在')
82
+ break
83
+ case 500:
84
+ Message.error('服务器错误')
85
+ break
86
+ default:
87
+ Message.error(response.data?.message || '请求失败')
88
+ }
89
+ } else {
90
+ Message.error('网络连接失败')
91
+ }
92
+
93
+ return Promise.reject(error)
94
+ }
95
+ )
96
+
97
+ return instance
98
+ }
99
+
100
+ const request = createRequest()
101
+
102
+ // 请求方法 - 直接返回实际数据,无需手动解包
103
+ export const http = {
104
+ get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
105
+ return request.get(url, config)
106
+ },
107
+
108
+ post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
109
+ return request.post(url, data, config)
110
+ },
111
+
112
+ put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
113
+ return request.put(url, data, config)
114
+ },
115
+
116
+ patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
117
+ return request.patch(url, data, config)
118
+ },
119
+
120
+ delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
121
+ return request.delete(url, config)
122
+ }
123
+ }
124
+
125
125
  export default request
@@ -1,195 +1,195 @@
1
- <script setup lang="ts">
2
- import { ref, reactive } from 'vue'
3
- import { useRouter, useRoute } from 'vue-router'
4
- import { Button, Icon } from '@xto/base'
5
- import { Form, FormItem, Input, Checkbox } from '@xto/form'
6
- import { Message } from '@xto/feedback'
7
- import { login } from '@/api/auth'
8
- import { setTokenInfo } from '@/utils/auth'
9
- import { getAppId, getClientId } from '@/utils/config'
10
-
11
- const router = useRouter()
12
- const route = useRoute()
13
-
14
- const loading = ref(false)
15
- const rememberMe = ref(false)
16
-
17
- const formData = reactive({
18
- uid: '',
19
- password: ''
20
- })
21
-
22
- const rules: Record<string, any[]> = {
23
- uid: [
24
- { required: true, message: '请输入用户名', trigger: 'blur' }
25
- ],
26
- password: [
27
- { required: true, message: '请输入密码', trigger: 'blur' },
28
- { min: 6, message: '密码长度至少6位', trigger: 'blur' }
29
- ]
30
- }
31
-
32
- const formRef = ref()
33
-
34
- // 登录
35
- const handleLogin = async () => {
36
- try {
37
- await formRef.value?.validate()
38
- loading.value = true
39
-
40
- // 调用登录 API
41
- const result = await login({
42
- appId: getAppId(),
43
- clientId: getClientId(),
44
- uid: formData.uid,
45
- password: formData.password,
46
- code: true
47
- })
48
-
49
- // 保存 token
50
- setTokenInfo(result)
51
-
52
- Message.success('登录成功')
53
-
54
- // 获取重定向地址
55
- const redirect = route.query.redirect as string || '/'
56
-
57
- // 跳转到目标页面(路由守卫会自动获取用户信息和菜单)
58
- router.push(redirect)
59
- } catch (error) {
60
- console.error('登录失败:', error)
61
- } finally {
62
- loading.value = false
63
- }
64
- }
65
- </script>
66
-
67
- <template>
68
- <div class="login">
69
- <div class="login__container">
70
- <div class="login__header">
71
- <img src="/vite.svg" alt="Logo" class="login__logo" />
72
- <h1 class="login__title">Xto Demo</h1>
73
- <p class="login__subtitle">后台管理系统</p>
74
- </div>
75
-
76
- <Form
77
- ref="formRef"
78
- :model="formData"
79
- :rules="rules"
80
- class="login__form"
81
- label-width="0"
82
- >
83
- <FormItem prop="uid">
84
- <Input
85
- v-model="formData.uid"
86
- placeholder="用户名"
87
- size="large"
88
- >
89
- <template #prefix>
90
- <Icon name="user" :size="18" />
91
- </template>
92
- </Input>
93
- </FormItem>
94
-
95
- <FormItem prop="password">
96
- <Input
97
- v-model="formData.password"
98
- type="password"
99
- placeholder="密码"
100
- size="large"
101
- show-password
102
- @keyup.enter="handleLogin"
103
- >
104
- <template #prefix>
105
- <Icon name="lock" :size="18" />
106
- </template>
107
- </Input>
108
- </FormItem>
109
-
110
- <FormItem>
111
- <Checkbox v-model="rememberMe">记住我</Checkbox>
112
- </FormItem>
113
-
114
- <FormItem>
115
- <Button
116
- type="primary"
117
- size="large"
118
- :loading="loading"
119
- class="login__submit"
120
- @click="handleLogin"
121
- >
122
- 登录
123
- </Button>
124
- </FormItem>
125
- </Form>
126
-
127
- <div class="login__footer">
128
- <p>请输入您的用户名和密码</p>
129
- </div>
130
- </div>
131
- </div>
132
- </template>
133
-
134
- <style lang="scss" scoped>
135
- .login {
136
- width: 100%;
137
- min-height: 100vh;
138
- display: flex;
139
- align-items: center;
140
- justify-content: flex-end;
141
- padding-right: 15%;
142
- background: linear-gradient(135deg, var(--color-primary-light-9) 0%, var(--color-primary-light-7) 100%);
143
-
144
- &__container {
145
- width: 400px;
146
- padding: 40px;
147
- background-color: var(--bg-color);
148
- border-radius: var(--border-radius-large);
149
- box-shadow: var(--box-shadow-dark);
150
- }
151
-
152
- &__header {
153
- text-align: center;
154
- margin-bottom: 30px;
155
- }
156
-
157
- &__logo {
158
- width: 60px;
159
- height: 60px;
160
- }
161
-
162
- &__title {
163
- font-size: 28px;
164
- font-weight: 600;
165
- color: var(--color-primary);
166
- margin: 15px 0 5px;
167
- }
168
-
169
- &__subtitle {
170
- font-size: 14px;
171
- color: var(--color-text-secondary);
172
- }
173
-
174
- &__form {
175
- :deep(.x-form-item) {
176
- margin-bottom: 20px;
177
- }
178
-
179
- :deep(.x-input__prefix) {
180
- margin-right: 8px;
181
- }
182
- }
183
-
184
- &__submit {
185
- width: 100%;
186
- }
187
-
188
- &__footer {
189
- text-align: center;
190
- margin-top: 20px;
191
- font-size: 12px;
192
- color: var(--color-text-placeholder);
193
- }
194
- }
1
+ <script setup lang="ts">
2
+ import { ref, reactive } from 'vue'
3
+ import { useRouter, useRoute } from 'vue-router'
4
+ import { Button, Icon } from '@xto/base'
5
+ import { Form, FormItem, Input, Checkbox } from '@xto/form'
6
+ import { Message } from '@xto/feedback'
7
+ import { login } from '@/api/auth'
8
+ import { setTokenInfo } from '@/utils/auth'
9
+ import { getAppId, getClientId } from '@/utils/config'
10
+
11
+ const router = useRouter()
12
+ const route = useRoute()
13
+
14
+ const loading = ref(false)
15
+ const rememberMe = ref(false)
16
+
17
+ const formData = reactive({
18
+ uid: '',
19
+ password: ''
20
+ })
21
+
22
+ const rules: Record<string, any[]> = {
23
+ uid: [
24
+ { required: true, message: '请输入用户名', trigger: 'blur' }
25
+ ],
26
+ password: [
27
+ { required: true, message: '请输入密码', trigger: 'blur' },
28
+ { min: 6, message: '密码长度至少6位', trigger: 'blur' }
29
+ ]
30
+ }
31
+
32
+ const formRef = ref()
33
+
34
+ // 登录
35
+ const handleLogin = async () => {
36
+ try {
37
+ await formRef.value?.validate()
38
+ loading.value = true
39
+
40
+ // 调用登录 API
41
+ const result = await login({
42
+ appId: getAppId(),
43
+ clientId: getClientId(),
44
+ uid: formData.uid,
45
+ password: formData.password,
46
+ code: true
47
+ })
48
+
49
+ // 保存 token
50
+ setTokenInfo(result)
51
+
52
+ Message.success('登录成功')
53
+
54
+ // 获取重定向地址
55
+ const redirect = route.query.redirect as string || '/'
56
+
57
+ // 跳转到目标页面(路由守卫会自动获取用户信息和菜单)
58
+ router.push(redirect)
59
+ } catch (error) {
60
+ console.error('登录失败:', error)
61
+ } finally {
62
+ loading.value = false
63
+ }
64
+ }
65
+ </script>
66
+
67
+ <template>
68
+ <div class="login">
69
+ <div class="login__container">
70
+ <div class="login__header">
71
+ <img src="/vite.svg" alt="Logo" class="login__logo" />
72
+ <h1 class="login__title">Xto Demo</h1>
73
+ <p class="login__subtitle">后台管理系统</p>
74
+ </div>
75
+
76
+ <Form
77
+ ref="formRef"
78
+ :model="formData"
79
+ :rules="rules"
80
+ class="login__form"
81
+ label-width="0"
82
+ >
83
+ <FormItem prop="uid">
84
+ <Input
85
+ v-model="formData.uid"
86
+ placeholder="用户名"
87
+ size="large"
88
+ >
89
+ <template #prefix>
90
+ <Icon name="user" :size="18" />
91
+ </template>
92
+ </Input>
93
+ </FormItem>
94
+
95
+ <FormItem prop="password">
96
+ <Input
97
+ v-model="formData.password"
98
+ type="password"
99
+ placeholder="密码"
100
+ size="large"
101
+ show-password
102
+ @keyup.enter="handleLogin"
103
+ >
104
+ <template #prefix>
105
+ <Icon name="lock" :size="18" />
106
+ </template>
107
+ </Input>
108
+ </FormItem>
109
+
110
+ <FormItem>
111
+ <Checkbox v-model="rememberMe">记住我</Checkbox>
112
+ </FormItem>
113
+
114
+ <FormItem>
115
+ <Button
116
+ type="primary"
117
+ size="large"
118
+ :loading="loading"
119
+ class="login__submit"
120
+ @click="handleLogin"
121
+ >
122
+ 登录
123
+ </Button>
124
+ </FormItem>
125
+ </Form>
126
+
127
+ <div class="login__footer">
128
+ <p>请输入您的用户名和密码</p>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </template>
133
+
134
+ <style lang="scss" scoped>
135
+ .login {
136
+ width: 100%;
137
+ min-height: 100vh;
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: flex-end;
141
+ padding-right: 15%;
142
+ background: linear-gradient(135deg, var(--color-primary-light-9) 0%, var(--color-primary-light-7) 100%);
143
+
144
+ &__container {
145
+ width: 400px;
146
+ padding: 40px;
147
+ background-color: var(--bg-color);
148
+ border-radius: var(--border-radius-large);
149
+ box-shadow: var(--box-shadow-dark);
150
+ }
151
+
152
+ &__header {
153
+ text-align: center;
154
+ margin-bottom: 30px;
155
+ }
156
+
157
+ &__logo {
158
+ width: 60px;
159
+ height: 60px;
160
+ }
161
+
162
+ &__title {
163
+ font-size: 28px;
164
+ font-weight: 600;
165
+ color: var(--color-primary);
166
+ margin: 15px 0 5px;
167
+ }
168
+
169
+ &__subtitle {
170
+ font-size: 14px;
171
+ color: var(--color-text-secondary);
172
+ }
173
+
174
+ &__form {
175
+ :deep(.x-form-item) {
176
+ margin-bottom: 20px;
177
+ }
178
+
179
+ :deep(.x-input__prefix) {
180
+ margin-right: 8px;
181
+ }
182
+ }
183
+
184
+ &__submit {
185
+ width: 100%;
186
+ }
187
+
188
+ &__footer {
189
+ text-align: center;
190
+ margin-top: 20px;
191
+ font-size: 12px;
192
+ color: var(--color-text-placeholder);
193
+ }
194
+ }
195
195
  </style>