xto-fronted 0.4.102 → 0.4.104

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 (145) hide show
  1. package/dist/{index-CJSTBnGF.js → index-C2zTmROz.js} +1072 -1062
  2. package/dist/{index-BKj-34y6.js → index-Ci9SM-gg.js} +2 -2
  3. package/dist/{index-BK4Mut6H.js → index-HtulbTHk.js} +2 -2
  4. package/dist/{index-3ekBp4iW.js → index-j1GPEQjY.js} +2 -2
  5. package/dist/{index-B5DLfOYb.js → index-x7bKZmey.js} +23 -23
  6. package/dist/index.js +1 -1
  7. package/dist/stores/app.d.ts +8 -2
  8. package/dist/stores/auth.d.ts +2 -2
  9. package/dist/style.css +1 -1
  10. package/package.json +94 -94
  11. package/src/App.vue +48 -48
  12. package/src/api/index.ts +7 -7
  13. package/src/assets/styles/_dark.scss +639 -639
  14. package/src/assets/styles/_root.scss +183 -183
  15. package/src/assets/styles/_variables.scss +69 -69
  16. package/src/assets/styles/index.scss +460 -460
  17. package/src/components/Layout/Header.vue +2 -2
  18. package/src/components/Layout/MixTopMenu.vue +1185 -1185
  19. package/src/components/Layout/Sidebar.vue +229 -229
  20. package/src/components/Layout/SidebarMenuItem.vue +163 -163
  21. package/src/components/Layout/TopMenu.vue +1177 -1177
  22. package/src/components/Layout/index.vue +199 -199
  23. package/src/composables/useI18n.ts +43 -43
  24. package/src/index.ts +114 -100
  25. package/src/router/guards.ts +129 -127
  26. package/src/router/layoutRoute.ts +70 -70
  27. package/src/stores/app.ts +9 -0
  28. package/src/stores/index.ts +15 -15
  29. package/src/stores/locale.ts +66 -66
  30. package/src/stores/user.ts +0 -2
  31. package/src/types/api.d.ts +0 -1
  32. package/src/types/json-bigint.d.ts +18 -18
  33. package/src/types/xto.d.ts +172 -172
  34. package/src/utils/request.ts +184 -184
  35. package/src/views/dashboard/index.vue +545 -545
  36. package/src/views/error/403.vue +251 -251
  37. package/src/views/error/404.vue +253 -253
  38. package/src/views/login/index.vue +586 -586
  39. package/src/views/system/menu/index.vue +690 -690
  40. package/src/views/system/role/index.vue +583 -583
  41. package/src/views/system/user/index.vue +655 -655
  42. package/vite.config.ts +139 -139
  43. package/dist/assets/404-C9Uh6Uu-.css +0 -1
  44. package/dist/assets/404-fVB40gfP.js +0 -1
  45. package/dist/assets/404-zjGLLssH.js +0 -1
  46. package/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +0 -1
  47. package/dist/assets/index-B2Y_ySNp.js +0 -2
  48. package/dist/assets/index-B5xc4gQB.css +0 -1
  49. package/dist/assets/index-B75sburk.js +0 -1
  50. package/dist/assets/index-BBdRdMfs.js +0 -1
  51. package/dist/assets/index-BDgOY6Rp.js +0 -1
  52. package/dist/assets/index-BIoRANs0.js +0 -1
  53. package/dist/assets/index-BRR97dc6.js +0 -1
  54. package/dist/assets/index-Bz0BgZQ1.js +0 -1
  55. package/dist/assets/index-CAdztNsv.css +0 -1
  56. package/dist/assets/index-CCXrcISf.css +0 -1
  57. package/dist/assets/index-CDPHn9Pd.js +0 -1
  58. package/dist/assets/index-CfpZmcpk.css +0 -1
  59. package/dist/assets/index-Cpew6d-v.css +0 -1
  60. package/dist/assets/index-CwJSA85U.js +0 -1
  61. package/dist/assets/index-CwRA10ac.js +0 -1
  62. package/dist/assets/index-D8NDxq9d.js +0 -1
  63. package/dist/assets/index-DEB6-Iv_.js +0 -2
  64. package/dist/assets/index-DM4Ezclc.css +0 -1
  65. package/dist/assets/index-DYv7nImj.css +0 -1
  66. package/dist/assets/index-Dm3Gq6SY.js +0 -1
  67. package/dist/assets/index-DxbgF-OR.js +0 -1
  68. package/dist/assets/index-RUdXk1fA.css +0 -1
  69. package/dist/assets/index-_xB0udHf.js +0 -1
  70. package/dist/assets/index-t-2Y0KhA.css +0 -1
  71. package/dist/assets/vendor-CUVPinTg.js +0 -13
  72. package/dist/assets/vue-vendor-Bpie-0gH.js +0 -29
  73. package/dist/assets/vue-vendor-DeJXJVbN.js +0 -29
  74. package/dist/assets/xto-base-C3XNcx7i.js +0 -1
  75. package/dist/assets/xto-base-CL2NKZJJ.css +0 -1
  76. package/dist/assets/xto-base-PwLGsxxb.js +0 -1
  77. package/dist/assets/xto-business--V1F5Gwb.css +0 -1
  78. package/dist/assets/xto-core-B1Ho_Ytu.js +0 -1
  79. package/dist/assets/xto-core-CtL4zKiV.js +0 -1
  80. package/dist/assets/xto-data-Coeo_ZYH.js +0 -1
  81. package/dist/assets/xto-data-MxZsiJgi.css +0 -1
  82. package/dist/assets/xto-data-bCXQa7fT.js +0 -1
  83. package/dist/assets/xto-feedback-Bxx38c3P.css +0 -1
  84. package/dist/assets/xto-feedback-CFysasJi.js +0 -1
  85. package/dist/assets/xto-feedback-CPydp0kn.js +0 -1
  86. package/dist/assets/xto-form-Cu6q3VLG.css +0 -1
  87. package/dist/assets/xto-form-DBlhgyXp.js +0 -1
  88. package/dist/assets/xto-form-bywohdAf.js +0 -1
  89. package/dist/assets/xto-layout-BDD6sSlM.css +0 -1
  90. package/dist/assets/xto-navigation-Bbdpine9.js +0 -1
  91. package/dist/assets/xto-navigation-I2o1CycT.js +0 -1
  92. package/dist/assets/xto-navigation-XfpyMpEo.css +0 -1
  93. package/dist/index-58aI1w0v.js +0 -515
  94. package/dist/index-A_B_Ap_A.js +0 -4240
  95. package/dist/index-B-lMqzxZ.js +0 -479
  96. package/dist/index-B6s_uLJE.js +0 -189
  97. package/dist/index-BAmYUT0G.js +0 -189
  98. package/dist/index-BJlOXgu5.js +0 -515
  99. package/dist/index-BMQao91y.js +0 -189
  100. package/dist/index-BRvi9qW-.js +0 -515
  101. package/dist/index-BVGW4DDQ.js +0 -189
  102. package/dist/index-BXg94yA2.js +0 -515
  103. package/dist/index-BYAkZ2gD.js +0 -641
  104. package/dist/index-BfXnrw05.js +0 -515
  105. package/dist/index-Bmb0rt9C.js +0 -641
  106. package/dist/index-Bmf0YbVq.js +0 -189
  107. package/dist/index-C1BnOFy7.js +0 -3145
  108. package/dist/index-C1j4f3mM.js +0 -479
  109. package/dist/index-C2-a5KSQ.js +0 -4233
  110. package/dist/index-C3K89jzC.js +0 -515
  111. package/dist/index-C92NkXAn.js +0 -479
  112. package/dist/index-CAHSv7LK.js +0 -4285
  113. package/dist/index-CVH7bDsl.js +0 -4285
  114. package/dist/index-Ccp6zfq-.js +0 -4290
  115. package/dist/index-CeZ0CSSs.js +0 -641
  116. package/dist/index-Cf8E7FM1.js +0 -4270
  117. package/dist/index-CgyQqbdx.js +0 -189
  118. package/dist/index-ChowNrlU.js +0 -641
  119. package/dist/index-CvQgEgUM.js +0 -641
  120. package/dist/index-D25KzR0I.js +0 -479
  121. package/dist/index-D4LWXVnG.js +0 -515
  122. package/dist/index-DCApv1oX.js +0 -641
  123. package/dist/index-DCBIjLHy.js +0 -515
  124. package/dist/index-DEYOivza.js +0 -641
  125. package/dist/index-DHH8Os_2.js +0 -189
  126. package/dist/index-DReodgBw.js +0 -4233
  127. package/dist/index-DTRJONCd.js +0 -515
  128. package/dist/index-DgffG7KK.js +0 -641
  129. package/dist/index-DjERNRXX.js +0 -515
  130. package/dist/index-DjXyzwL0.js +0 -479
  131. package/dist/index-DkOqM4e2.js +0 -3147
  132. package/dist/index-Ds8IV04t.js +0 -189
  133. package/dist/index-LSdsO2Ox.js +0 -479
  134. package/dist/index-UJixTdep.js +0 -479
  135. package/dist/index-WPRGF_GX.js +0 -189
  136. package/dist/index-WPWzllES.js +0 -641
  137. package/dist/index-Wl2Qg26t.js +0 -3147
  138. package/dist/index-dk0diNwi.js +0 -479
  139. package/dist/index-gBlRG4kk.js +0 -479
  140. package/dist/index-mVol7F2K.js +0 -479
  141. package/dist/index-xWU3J3OH.js +0 -641
  142. package/dist/index-zKJLxthI.js +0 -189
  143. package/dist/index.es.js +0 -95
  144. package/dist/index.html +0 -28
  145. package/dist/index.umd.js +0 -8
@@ -1,587 +1,587 @@
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-page">
69
- <!-- 左侧品牌区域 -->
70
- <div class="login-brand">
71
- <div class="brand-content">
72
- <!-- Logo -->
73
- <div class="brand-logo">
74
- <div class="logo-icon">
75
- <svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
76
- <rect width="48" height="48" rx="12" fill="currentColor"/>
77
- <path d="M14 24L20 30L34 16" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
78
- </svg>
79
- </div>
80
- <span class="logo-text">XTO</span>
81
- </div>
82
-
83
- <!-- 标语 -->
84
- <div class="brand-slogan">
85
- <h1>企业级后台管理解决方案</h1>
86
- <p>开箱即用的中后台前端/设计解决方案</p>
87
- </div>
88
-
89
- <!-- 特性列表 -->
90
- <div class="brand-features">
91
- <div class="feature-item">
92
- <div class="feature-icon">
93
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
94
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
95
- </svg>
96
- </div>
97
- <div class="feature-text">
98
- <h4>高性能</h4>
99
- <p>极致的渲染性能与响应速度</p>
100
- </div>
101
- </div>
102
- <div class="feature-item">
103
- <div class="feature-icon">
104
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
105
- <rect x="3" y="3" width="18" height="18" rx="2"/>
106
- <path d="M9 9h6M9 12h6M9 15h4"/>
107
- </svg>
108
- </div>
109
- <div class="feature-text">
110
- <h4>丰富组件</h4>
111
- <p>60+ 高质量业务组件</p>
112
- </div>
113
- </div>
114
- <div class="feature-item">
115
- <div class="feature-icon">
116
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
117
- <circle cx="12" cy="12" r="10"/>
118
- <path d="M12 6v6l4 2"/>
119
- </svg>
120
- </div>
121
- <div class="feature-text">
122
- <h4>持续更新</h4>
123
- <p>活跃的社区与快速迭代</p>
124
- </div>
125
- </div>
126
- </div>
127
-
128
- <!-- 底部装饰 -->
129
- <div class="brand-decoration">
130
- <div class="decoration-line"></div>
131
- <div class="decoration-dots">
132
- <span></span><span></span><span></span>
133
- </div>
134
- </div>
135
- </div>
136
-
137
- <!-- 背景装饰 -->
138
- <div class="brand-bg">
139
- <div class="bg-grid"></div>
140
- <div class="bg-glow"></div>
141
- <div class="bg-pattern"></div>
142
- </div>
143
- </div>
144
-
145
- <!-- 右侧登录区域 -->
146
- <div class="login-form-section">
147
- <div class="form-container">
148
- <!-- 头部 -->
149
- <div class="form-header">
150
- <h2>欢迎登录</h2>
151
- <p>请输入您的账户信息</p>
152
- </div>
153
-
154
- <!-- 表单 -->
155
- <Form
156
- ref="formRef"
157
- :model="formData"
158
- :rules="rules"
159
- class="login-form"
160
- label-width="0"
161
- >
162
- <FormItem prop="uid">
163
- <div class="input-wrapper">
164
- <label class="input-label">用户名</label>
165
- <Input
166
- v-model="formData.uid"
167
- placeholder="请输入用户名"
168
- size="large"
169
- >
170
- <template #prefix>
171
- <Icon name="user" :size="18" />
172
- </template>
173
- </Input>
174
- </div>
175
- </FormItem>
176
-
177
- <FormItem prop="password">
178
- <div class="input-wrapper">
179
- <label class="input-label">密码</label>
180
- <Input
181
- v-model="formData.password"
182
- type="password"
183
- placeholder="请输入密码"
184
- size="large"
185
- show-password
186
- @keyup.enter="handleLogin"
187
- >
188
- <template #prefix>
189
- <Icon name="lock" :size="18" />
190
- </template>
191
- </Input>
192
- </div>
193
- </FormItem>
194
-
195
- <div class="form-options">
196
- <Checkbox v-model="rememberMe">记住我</Checkbox>
197
- <a href="#" class="forgot-link">忘记密码?</a>
198
- </div>
199
-
200
- <FormItem>
201
- <Button
202
- type="primary"
203
- size="large"
204
- :loading="loading"
205
- class="login-btn"
206
- @click="handleLogin"
207
- >
208
- <span v-if="!loading">登 录</span>
209
- <span v-else>登录中...</span>
210
- </Button>
211
- </FormItem>
212
- </Form>
213
-
214
- <!-- 底部 -->
215
- <div class="form-footer">
216
- <p>其他登录方式</p>
217
- <div class="social-login">
218
- <button class="social-btn" title="企业微信">
219
- <svg viewBox="0 0 24 24" fill="currentColor">
220
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
221
- </svg>
222
- </button>
223
- <button class="social-btn" title="钉钉">
224
- <svg viewBox="0 0 24 24" fill="currentColor">
225
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/>
226
- </svg>
227
- </button>
228
- <button class="social-btn" title="飞书">
229
- <svg viewBox="0 0 24 24" fill="currentColor">
230
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
231
- </svg>
232
- </button>
233
- </div>
234
- </div>
235
- </div>
236
-
237
- <!-- 版权信息 -->
238
- <div class="copyright">
239
- <p>© 2024 XTO Team. All rights reserved.</p>
240
- </div>
241
- </div>
242
- </div>
243
- </template>
244
-
245
- <style lang="scss" scoped>
246
- .login-page {
247
- width: 100%;
248
- min-height: 100vh;
249
- display: flex;
250
- background: var(--bg-color-page);
251
- }
252
-
253
- // 左侧品牌区域
254
- .login-brand {
255
- flex: 1;
256
- position: relative;
257
- display: flex;
258
- align-items: center;
259
- justify-content: center;
260
- background: linear-gradient(135deg, #002c8c 0%, #0958d9 50%, #1677ff 100%);
261
- overflow: hidden;
262
-
263
- @media (max-width: 992px) {
264
- display: none;
265
- }
266
-
267
- .brand-content {
268
- position: relative;
269
- z-index: 2;
270
- padding: 60px;
271
- max-width: 520px;
272
- color: #fff;
273
- }
274
-
275
- .brand-logo {
276
- display: flex;
277
- align-items: center;
278
- gap: 12px;
279
- margin-bottom: 48px;
280
-
281
- .logo-icon {
282
- width: 48px;
283
- height: 48px;
284
- color: #fff;
285
- }
286
-
287
- .logo-text {
288
- font-size: 28px;
289
- font-weight: 700;
290
- letter-spacing: 2px;
291
- }
292
- }
293
-
294
- .brand-slogan {
295
- margin-bottom: 48px;
296
-
297
- h1 {
298
- font-size: 32px;
299
- font-weight: 600;
300
- line-height: 1.3;
301
- margin-bottom: 12px;
302
- }
303
-
304
- p {
305
- font-size: 16px;
306
- opacity: 0.8;
307
- line-height: 1.6;
308
- }
309
- }
310
-
311
- .brand-features {
312
- display: flex;
313
- flex-direction: column;
314
- gap: 24px;
315
-
316
- .feature-item {
317
- display: flex;
318
- align-items: flex-start;
319
- gap: 16px;
320
- padding: 16px;
321
- background: rgba(255, 255, 255, 0.1);
322
- border-radius: var(--border-radius-large);
323
- backdrop-filter: blur(10px);
324
- transition: all 0.3s ease;
325
-
326
- &:hover {
327
- background: rgba(255, 255, 255, 0.15);
328
- transform: translateX(8px);
329
- }
330
- }
331
-
332
- .feature-icon {
333
- width: 40px;
334
- height: 40px;
335
- display: flex;
336
- align-items: center;
337
- justify-content: center;
338
- background: rgba(255, 255, 255, 0.2);
339
- border-radius: var(--border-radius-base);
340
- flex-shrink: 0;
341
-
342
- svg {
343
- width: 20px;
344
- height: 20px;
345
- }
346
- }
347
-
348
- .feature-text {
349
- h4 {
350
- font-size: 15px;
351
- font-weight: 600;
352
- margin-bottom: 4px;
353
- }
354
-
355
- p {
356
- font-size: 13px;
357
- opacity: 0.8;
358
- }
359
- }
360
- }
361
-
362
- .brand-decoration {
363
- position: absolute;
364
- bottom: 60px;
365
- left: 60px;
366
- right: 60px;
367
- display: flex;
368
- align-items: center;
369
- gap: 16px;
370
-
371
- .decoration-line {
372
- flex: 1;
373
- height: 1px;
374
- background: linear-gradient(90deg, rgba(255, 255, 255, 0.3), transparent);
375
- }
376
-
377
- .decoration-dots {
378
- display: flex;
379
- gap: 8px;
380
-
381
- span {
382
- width: 6px;
383
- height: 6px;
384
- background: rgba(255, 255, 255, 0.5);
385
- border-radius: 50%;
386
- }
387
- }
388
- }
389
-
390
- // 背景装饰
391
- .brand-bg {
392
- position: absolute;
393
- inset: 0;
394
- z-index: 1;
395
- overflow: hidden;
396
-
397
- .bg-grid {
398
- position: absolute;
399
- inset: 0;
400
- background-image:
401
- linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
402
- linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
403
- background-size: 60px 60px;
404
- }
405
-
406
- .bg-glow {
407
- position: absolute;
408
- top: -20%;
409
- right: -10%;
410
- width: 60%;
411
- height: 60%;
412
- background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
413
- animation: glow-pulse 8s ease-in-out infinite;
414
- }
415
-
416
- .bg-pattern {
417
- position: absolute;
418
- bottom: -10%;
419
- left: -10%;
420
- width: 40%;
421
- height: 40%;
422
- background: radial-gradient(circle, rgba(255, 255, 255, 0.08) 0%, transparent 60%);
423
- }
424
- }
425
- }
426
-
427
- @keyframes glow-pulse {
428
- 0%, 100% { opacity: 0.6; transform: scale(1); }
429
- 50% { opacity: 1; transform: scale(1.1); }
430
- }
431
-
432
- // 右侧登录区域
433
- .login-form-section {
434
- width: 500px;
435
- min-height: 100vh;
436
- display: flex;
437
- flex-direction: column;
438
- justify-content: center;
439
- padding: 60px 80px;
440
- background: var(--bg-color);
441
-
442
- @media (max-width: 768px) {
443
- width: 100%;
444
- padding: 40px 24px;
445
- }
446
-
447
- .form-container {
448
- width: 100%;
449
- max-width: 360px;
450
- margin: 0 auto;
451
- }
452
-
453
- .form-header {
454
- margin-bottom: 40px;
455
-
456
- h2 {
457
- font-size: 28px;
458
- font-weight: 600;
459
- color: var(--color-text-primary);
460
- margin-bottom: 8px;
461
- }
462
-
463
- p {
464
- font-size: 14px;
465
- color: var(--color-text-secondary);
466
- }
467
- }
468
-
469
- .login-form {
470
- .input-wrapper {
471
- margin-bottom: 4px;
472
- }
473
-
474
- .input-label {
475
- display: block;
476
- font-size: 14px;
477
- font-weight: 500;
478
- color: var(--color-text-regular);
479
- margin-bottom: 8px;
480
- }
481
-
482
- :deep(.x-form-item) {
483
- margin-bottom: 24px;
484
- }
485
-
486
- :deep(.x-input) {
487
- --x-input-border-radius: 8px;
488
- }
489
-
490
- :deep(.x-input__prefix) {
491
- margin-right: 12px;
492
- color: var(--color-text-placeholder);
493
- }
494
- }
495
-
496
- .form-options {
497
- display: flex;
498
- justify-content: space-between;
499
- align-items: center;
500
- margin-bottom: 24px;
501
-
502
- .forgot-link {
503
- font-size: 14px;
504
- color: var(--color-primary);
505
- text-decoration: none;
506
- transition: color 0.2s;
507
-
508
- &:hover {
509
- color: var(--color-primary-dark-1);
510
- }
511
- }
512
- }
513
-
514
- .login-btn {
515
- width: 100%;
516
- height: 44px;
517
- font-size: 16px;
518
- font-weight: 500;
519
- border-radius: 8px;
520
- letter-spacing: 4px;
521
- transition: all 0.3s ease;
522
-
523
- &:hover:not(:disabled) {
524
- transform: translateY(-2px);
525
- box-shadow: 0 4px 12px rgba(22, 119, 255, 0.4);
526
- }
527
-
528
- &:active:not(:disabled) {
529
- transform: translateY(0);
530
- }
531
- }
532
-
533
- .form-footer {
534
- margin-top: 32px;
535
- padding-top: 24px;
536
- border-top: 1px solid var(--color-border-lighter);
537
- text-align: center;
538
-
539
- p {
540
- font-size: 13px;
541
- color: var(--color-text-placeholder);
542
- margin-bottom: 16px;
543
- }
544
-
545
- .social-login {
546
- display: flex;
547
- justify-content: center;
548
- gap: 16px;
549
- }
550
-
551
- .social-btn {
552
- width: 44px;
553
- height: 44px;
554
- display: flex;
555
- align-items: center;
556
- justify-content: center;
557
- background: var(--color-fill);
558
- border: none;
559
- border-radius: 50%;
560
- cursor: pointer;
561
- transition: all 0.2s ease;
562
- color: var(--color-text-secondary);
563
-
564
- svg {
565
- width: 20px;
566
- height: 20px;
567
- }
568
-
569
- &:hover {
570
- background: var(--color-primary-light-6);
571
- color: var(--color-primary);
572
- transform: translateY(-2px);
573
- }
574
- }
575
- }
576
-
577
- .copyright {
578
- margin-top: 48px;
579
- text-align: center;
580
-
581
- p {
582
- font-size: 12px;
583
- color: var(--color-text-placeholder);
584
- }
585
- }
586
- }
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-page">
69
+ <!-- 左侧品牌区域 -->
70
+ <div class="login-brand">
71
+ <div class="brand-content">
72
+ <!-- Logo -->
73
+ <div class="brand-logo">
74
+ <div class="logo-icon">
75
+ <svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
76
+ <rect width="48" height="48" rx="12" fill="currentColor"/>
77
+ <path d="M14 24L20 30L34 16" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
78
+ </svg>
79
+ </div>
80
+ <span class="logo-text">XTO</span>
81
+ </div>
82
+
83
+ <!-- 标语 -->
84
+ <div class="brand-slogan">
85
+ <h1>企业级后台管理解决方案</h1>
86
+ <p>开箱即用的中后台前端/设计解决方案</p>
87
+ </div>
88
+
89
+ <!-- 特性列表 -->
90
+ <div class="brand-features">
91
+ <div class="feature-item">
92
+ <div class="feature-icon">
93
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
94
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
95
+ </svg>
96
+ </div>
97
+ <div class="feature-text">
98
+ <h4>高性能</h4>
99
+ <p>极致的渲染性能与响应速度</p>
100
+ </div>
101
+ </div>
102
+ <div class="feature-item">
103
+ <div class="feature-icon">
104
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
105
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
106
+ <path d="M9 9h6M9 12h6M9 15h4"/>
107
+ </svg>
108
+ </div>
109
+ <div class="feature-text">
110
+ <h4>丰富组件</h4>
111
+ <p>60+ 高质量业务组件</p>
112
+ </div>
113
+ </div>
114
+ <div class="feature-item">
115
+ <div class="feature-icon">
116
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
117
+ <circle cx="12" cy="12" r="10"/>
118
+ <path d="M12 6v6l4 2"/>
119
+ </svg>
120
+ </div>
121
+ <div class="feature-text">
122
+ <h4>持续更新</h4>
123
+ <p>活跃的社区与快速迭代</p>
124
+ </div>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- 底部装饰 -->
129
+ <div class="brand-decoration">
130
+ <div class="decoration-line"></div>
131
+ <div class="decoration-dots">
132
+ <span></span><span></span><span></span>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- 背景装饰 -->
138
+ <div class="brand-bg">
139
+ <div class="bg-grid"></div>
140
+ <div class="bg-glow"></div>
141
+ <div class="bg-pattern"></div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- 右侧登录区域 -->
146
+ <div class="login-form-section">
147
+ <div class="form-container">
148
+ <!-- 头部 -->
149
+ <div class="form-header">
150
+ <h2>欢迎登录</h2>
151
+ <p>请输入您的账户信息</p>
152
+ </div>
153
+
154
+ <!-- 表单 -->
155
+ <Form
156
+ ref="formRef"
157
+ :model="formData"
158
+ :rules="rules"
159
+ class="login-form"
160
+ label-width="0"
161
+ >
162
+ <FormItem prop="uid">
163
+ <div class="input-wrapper">
164
+ <label class="input-label">用户名</label>
165
+ <Input
166
+ v-model="formData.uid"
167
+ placeholder="请输入用户名"
168
+ size="large"
169
+ >
170
+ <template #prefix>
171
+ <Icon name="user" :size="18" />
172
+ </template>
173
+ </Input>
174
+ </div>
175
+ </FormItem>
176
+
177
+ <FormItem prop="password">
178
+ <div class="input-wrapper">
179
+ <label class="input-label">密码</label>
180
+ <Input
181
+ v-model="formData.password"
182
+ type="password"
183
+ placeholder="请输入密码"
184
+ size="large"
185
+ show-password
186
+ @keyup.enter="handleLogin"
187
+ >
188
+ <template #prefix>
189
+ <Icon name="lock" :size="18" />
190
+ </template>
191
+ </Input>
192
+ </div>
193
+ </FormItem>
194
+
195
+ <div class="form-options">
196
+ <Checkbox v-model="rememberMe">记住我</Checkbox>
197
+ <a href="#" class="forgot-link">忘记密码?</a>
198
+ </div>
199
+
200
+ <FormItem>
201
+ <Button
202
+ type="primary"
203
+ size="large"
204
+ :loading="loading"
205
+ class="login-btn"
206
+ @click="handleLogin"
207
+ >
208
+ <span v-if="!loading">登 录</span>
209
+ <span v-else>登录中...</span>
210
+ </Button>
211
+ </FormItem>
212
+ </Form>
213
+
214
+ <!-- 底部 -->
215
+ <div class="form-footer">
216
+ <p>其他登录方式</p>
217
+ <div class="social-login">
218
+ <button class="social-btn" title="企业微信">
219
+ <svg viewBox="0 0 24 24" fill="currentColor">
220
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
221
+ </svg>
222
+ </button>
223
+ <button class="social-btn" title="钉钉">
224
+ <svg viewBox="0 0 24 24" fill="currentColor">
225
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/>
226
+ </svg>
227
+ </button>
228
+ <button class="social-btn" title="飞书">
229
+ <svg viewBox="0 0 24 24" fill="currentColor">
230
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
231
+ </svg>
232
+ </button>
233
+ </div>
234
+ </div>
235
+ </div>
236
+
237
+ <!-- 版权信息 -->
238
+ <div class="copyright">
239
+ <p>© 2024 XTO Team. All rights reserved.</p>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ </template>
244
+
245
+ <style lang="scss" scoped>
246
+ .login-page {
247
+ width: 100%;
248
+ min-height: 100vh;
249
+ display: flex;
250
+ background: var(--bg-color-page);
251
+ }
252
+
253
+ // 左侧品牌区域
254
+ .login-brand {
255
+ flex: 1;
256
+ position: relative;
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ background: linear-gradient(135deg, #002c8c 0%, #0958d9 50%, #1677ff 100%);
261
+ overflow: hidden;
262
+
263
+ @media (max-width: 992px) {
264
+ display: none;
265
+ }
266
+
267
+ .brand-content {
268
+ position: relative;
269
+ z-index: 2;
270
+ padding: 60px;
271
+ max-width: 520px;
272
+ color: #fff;
273
+ }
274
+
275
+ .brand-logo {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 12px;
279
+ margin-bottom: 48px;
280
+
281
+ .logo-icon {
282
+ width: 48px;
283
+ height: 48px;
284
+ color: #fff;
285
+ }
286
+
287
+ .logo-text {
288
+ font-size: 28px;
289
+ font-weight: 700;
290
+ letter-spacing: 2px;
291
+ }
292
+ }
293
+
294
+ .brand-slogan {
295
+ margin-bottom: 48px;
296
+
297
+ h1 {
298
+ font-size: 32px;
299
+ font-weight: 600;
300
+ line-height: 1.3;
301
+ margin-bottom: 12px;
302
+ }
303
+
304
+ p {
305
+ font-size: 16px;
306
+ opacity: 0.8;
307
+ line-height: 1.6;
308
+ }
309
+ }
310
+
311
+ .brand-features {
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: 24px;
315
+
316
+ .feature-item {
317
+ display: flex;
318
+ align-items: flex-start;
319
+ gap: 16px;
320
+ padding: 16px;
321
+ background: rgba(255, 255, 255, 0.1);
322
+ border-radius: var(--border-radius-large);
323
+ backdrop-filter: blur(10px);
324
+ transition: all 0.3s ease;
325
+
326
+ &:hover {
327
+ background: rgba(255, 255, 255, 0.15);
328
+ transform: translateX(8px);
329
+ }
330
+ }
331
+
332
+ .feature-icon {
333
+ width: 40px;
334
+ height: 40px;
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ background: rgba(255, 255, 255, 0.2);
339
+ border-radius: var(--border-radius-base);
340
+ flex-shrink: 0;
341
+
342
+ svg {
343
+ width: 20px;
344
+ height: 20px;
345
+ }
346
+ }
347
+
348
+ .feature-text {
349
+ h4 {
350
+ font-size: 15px;
351
+ font-weight: 600;
352
+ margin-bottom: 4px;
353
+ }
354
+
355
+ p {
356
+ font-size: 13px;
357
+ opacity: 0.8;
358
+ }
359
+ }
360
+ }
361
+
362
+ .brand-decoration {
363
+ position: absolute;
364
+ bottom: 60px;
365
+ left: 60px;
366
+ right: 60px;
367
+ display: flex;
368
+ align-items: center;
369
+ gap: 16px;
370
+
371
+ .decoration-line {
372
+ flex: 1;
373
+ height: 1px;
374
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0.3), transparent);
375
+ }
376
+
377
+ .decoration-dots {
378
+ display: flex;
379
+ gap: 8px;
380
+
381
+ span {
382
+ width: 6px;
383
+ height: 6px;
384
+ background: rgba(255, 255, 255, 0.5);
385
+ border-radius: 50%;
386
+ }
387
+ }
388
+ }
389
+
390
+ // 背景装饰
391
+ .brand-bg {
392
+ position: absolute;
393
+ inset: 0;
394
+ z-index: 1;
395
+ overflow: hidden;
396
+
397
+ .bg-grid {
398
+ position: absolute;
399
+ inset: 0;
400
+ background-image:
401
+ linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
402
+ linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
403
+ background-size: 60px 60px;
404
+ }
405
+
406
+ .bg-glow {
407
+ position: absolute;
408
+ top: -20%;
409
+ right: -10%;
410
+ width: 60%;
411
+ height: 60%;
412
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
413
+ animation: glow-pulse 8s ease-in-out infinite;
414
+ }
415
+
416
+ .bg-pattern {
417
+ position: absolute;
418
+ bottom: -10%;
419
+ left: -10%;
420
+ width: 40%;
421
+ height: 40%;
422
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.08) 0%, transparent 60%);
423
+ }
424
+ }
425
+ }
426
+
427
+ @keyframes glow-pulse {
428
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
429
+ 50% { opacity: 1; transform: scale(1.1); }
430
+ }
431
+
432
+ // 右侧登录区域
433
+ .login-form-section {
434
+ width: 500px;
435
+ min-height: 100vh;
436
+ display: flex;
437
+ flex-direction: column;
438
+ justify-content: center;
439
+ padding: 60px 80px;
440
+ background: var(--bg-color);
441
+
442
+ @media (max-width: 768px) {
443
+ width: 100%;
444
+ padding: 40px 24px;
445
+ }
446
+
447
+ .form-container {
448
+ width: 100%;
449
+ max-width: 360px;
450
+ margin: 0 auto;
451
+ }
452
+
453
+ .form-header {
454
+ margin-bottom: 40px;
455
+
456
+ h2 {
457
+ font-size: 28px;
458
+ font-weight: 600;
459
+ color: var(--color-text-primary);
460
+ margin-bottom: 8px;
461
+ }
462
+
463
+ p {
464
+ font-size: 14px;
465
+ color: var(--color-text-secondary);
466
+ }
467
+ }
468
+
469
+ .login-form {
470
+ .input-wrapper {
471
+ margin-bottom: 4px;
472
+ }
473
+
474
+ .input-label {
475
+ display: block;
476
+ font-size: 14px;
477
+ font-weight: 500;
478
+ color: var(--color-text-regular);
479
+ margin-bottom: 8px;
480
+ }
481
+
482
+ :deep(.x-form-item) {
483
+ margin-bottom: 24px;
484
+ }
485
+
486
+ :deep(.x-input) {
487
+ --x-input-border-radius: 8px;
488
+ }
489
+
490
+ :deep(.x-input__prefix) {
491
+ margin-right: 12px;
492
+ color: var(--color-text-placeholder);
493
+ }
494
+ }
495
+
496
+ .form-options {
497
+ display: flex;
498
+ justify-content: space-between;
499
+ align-items: center;
500
+ margin-bottom: 24px;
501
+
502
+ .forgot-link {
503
+ font-size: 14px;
504
+ color: var(--color-primary);
505
+ text-decoration: none;
506
+ transition: color 0.2s;
507
+
508
+ &:hover {
509
+ color: var(--color-primary-dark-1);
510
+ }
511
+ }
512
+ }
513
+
514
+ .login-btn {
515
+ width: 100%;
516
+ height: 44px;
517
+ font-size: 16px;
518
+ font-weight: 500;
519
+ border-radius: 8px;
520
+ letter-spacing: 4px;
521
+ transition: all 0.3s ease;
522
+
523
+ &:hover:not(:disabled) {
524
+ transform: translateY(-2px);
525
+ box-shadow: 0 4px 12px rgba(22, 119, 255, 0.4);
526
+ }
527
+
528
+ &:active:not(:disabled) {
529
+ transform: translateY(0);
530
+ }
531
+ }
532
+
533
+ .form-footer {
534
+ margin-top: 32px;
535
+ padding-top: 24px;
536
+ border-top: 1px solid var(--color-border-lighter);
537
+ text-align: center;
538
+
539
+ p {
540
+ font-size: 13px;
541
+ color: var(--color-text-placeholder);
542
+ margin-bottom: 16px;
543
+ }
544
+
545
+ .social-login {
546
+ display: flex;
547
+ justify-content: center;
548
+ gap: 16px;
549
+ }
550
+
551
+ .social-btn {
552
+ width: 44px;
553
+ height: 44px;
554
+ display: flex;
555
+ align-items: center;
556
+ justify-content: center;
557
+ background: var(--color-fill);
558
+ border: none;
559
+ border-radius: 50%;
560
+ cursor: pointer;
561
+ transition: all 0.2s ease;
562
+ color: var(--color-text-secondary);
563
+
564
+ svg {
565
+ width: 20px;
566
+ height: 20px;
567
+ }
568
+
569
+ &:hover {
570
+ background: var(--color-primary-light-6);
571
+ color: var(--color-primary);
572
+ transform: translateY(-2px);
573
+ }
574
+ }
575
+ }
576
+
577
+ .copyright {
578
+ margin-top: 48px;
579
+ text-align: center;
580
+
581
+ p {
582
+ font-size: 12px;
583
+ color: var(--color-text-placeholder);
584
+ }
585
+ }
586
+ }
587
587
  </style>