xto-fronted 0.4.89 → 0.4.91

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 (116) hide show
  1. package/dist/assets/404-Bq0LY5Cd.js +1 -0
  2. package/dist/assets/404-Cw_4ZCL6.css +1 -0
  3. package/dist/assets/{index-BDgOY6Rp.js → index-7ZZxpSfk.js} +1 -1
  4. package/dist/assets/index-BJUe8VUp.js +1 -0
  5. package/dist/assets/{index-Bz0BgZQ1.js → index-BlOR_ICg.js} +1 -1
  6. package/dist/assets/index-BlRslYYI.css +1 -0
  7. package/dist/assets/index-BudArKxR.css +1 -0
  8. package/dist/assets/{index-CwRA10ac.js → index-BzbOWBCV.js} +1 -1
  9. package/dist/assets/index-CFhWBbxk.css +1 -0
  10. package/dist/assets/{index-CfpZmcpk.css → index-CH6aTfYg.css} +1 -1
  11. package/dist/assets/{index-BIoRANs0.js → index-CT5f37nN.js} +1 -1
  12. package/dist/assets/index-Ce-kjtEM.js +2 -0
  13. package/dist/assets/{index-t-2Y0KhA.css → index-Cpew6d-v.css} +1 -1
  14. package/dist/assets/index-DkkuYBgT.css +1 -0
  15. package/dist/assets/index-vfvEFrCH.css +1 -0
  16. package/dist/assets/{index-CwJSA85U.js → index-wVLLAoVp.js} +1 -1
  17. package/dist/assets/vendor-DZmPBJ9d.js +16 -0
  18. package/dist/assets/vue-vendor-DjmFuEnG.js +29 -0
  19. package/dist/assets/{xto-base-PwLGsxxb.js → xto-base-B5HYOo6i.js} +1 -1
  20. package/dist/assets/{xto-core-CtL4zKiV.js → xto-core-DZYp_YAR.js} +1 -1
  21. package/dist/assets/{xto-data-bCXQa7fT.js → xto-data-ogck6x_i.js} +1 -1
  22. package/dist/assets/{xto-feedback-CPydp0kn.js → xto-feedback-C0-6cAL6.js} +1 -1
  23. package/dist/assets/{xto-form-bywohdAf.js → xto-form-IDg_78Vf.js} +1 -1
  24. package/dist/assets/{xto-navigation-Bbdpine9.js → xto-navigation-CPYLzfu7.js} +1 -1
  25. package/dist/index.html +9 -9
  26. package/package.json +91 -91
  27. package/src/App.vue +48 -48
  28. package/src/assets/styles/_dark.scss +639 -572
  29. package/src/assets/styles/_root.scss +183 -183
  30. package/src/assets/styles/_variables.scss +69 -69
  31. package/src/assets/styles/index.scss +460 -460
  32. package/src/components/Layout/Sidebar.vue +198 -198
  33. package/src/components/Layout/TopMenu.vue +1170 -1170
  34. package/src/components/Layout/index.vue +192 -192
  35. package/src/directives/permission.ts +12 -3
  36. package/src/index.ts +100 -100
  37. package/src/router/layoutRoute.ts +59 -59
  38. package/src/stores/menu.ts +64 -3
  39. package/src/types/json-bigint.d.ts +18 -18
  40. package/src/utils/permission.ts +12 -5
  41. package/src/utils/request.ts +184 -164
  42. package/src/views/dashboard/index.vue +545 -545
  43. package/src/views/error/403.vue +251 -251
  44. package/src/views/error/404.vue +253 -253
  45. package/src/views/login/index.vue +586 -586
  46. package/src/views/system/menu/index.vue +690 -690
  47. package/src/views/system/role/index.vue +583 -583
  48. package/src/views/system/user/index.vue +655 -655
  49. package/dist/App.vue.d.ts +0 -2
  50. package/dist/api/auth.d.ts +0 -8
  51. package/dist/api/system.d.ts +0 -16
  52. package/dist/api/user.d.ts +0 -13
  53. package/dist/assets/404-C9Uh6Uu-.css +0 -1
  54. package/dist/assets/404-zjGLLssH.js +0 -1
  55. package/dist/assets/index-B5xc4gQB.css +0 -1
  56. package/dist/assets/index-CAdztNsv.css +0 -1
  57. package/dist/assets/index-CCXrcISf.css +0 -1
  58. package/dist/assets/index-D8NDxq9d.js +0 -1
  59. package/dist/assets/index-DEB6-Iv_.js +0 -2
  60. package/dist/assets/index-DM4Ezclc.css +0 -1
  61. package/dist/assets/index-DYv7nImj.css +0 -1
  62. package/dist/assets/vendor-CUVPinTg.js +0 -13
  63. package/dist/assets/vue-vendor-DeJXJVbN.js +0 -29
  64. package/dist/components/Layout/Footer.vue.d.ts +0 -2
  65. package/dist/components/Layout/Header.vue.d.ts +0 -5
  66. package/dist/components/Layout/MixTopMenu.vue.d.ts +0 -5
  67. package/dist/components/Layout/Sidebar.vue.d.ts +0 -11
  68. package/dist/components/Layout/SidebarMenuItem.vue.d.ts +0 -5
  69. package/dist/components/Layout/Tabs.vue.d.ts +0 -2
  70. package/dist/components/Layout/TopMenu.vue.d.ts +0 -5
  71. package/dist/components/Layout/index.vue.d.ts +0 -2
  72. package/dist/composables/useApp.d.ts +0 -29
  73. package/dist/composables/useAuth.d.ts +0 -6
  74. package/dist/composables/useForm.d.ts +0 -20
  75. package/dist/composables/useI18n.d.ts +0 -30
  76. package/dist/composables/useTable.d.ts +0 -29
  77. package/dist/directives/permission.d.ts +0 -4
  78. package/dist/enums/index.d.ts +0 -32
  79. package/dist/index-BRvi9qW-.js +0 -515
  80. package/dist/index-BVGW4DDQ.js +0 -189
  81. package/dist/index-Bmf0YbVq.js +0 -189
  82. package/dist/index-C2-a5KSQ.js +0 -4233
  83. package/dist/index-CeZ0CSSs.js +0 -641
  84. package/dist/index-D25KzR0I.js +0 -479
  85. package/dist/index-DEYOivza.js +0 -641
  86. package/dist/index-DReodgBw.js +0 -4233
  87. package/dist/index-DjERNRXX.js +0 -515
  88. package/dist/index-gBlRG4kk.js +0 -479
  89. package/dist/index.d.ts +0 -59
  90. package/dist/index.es.js +0 -94
  91. package/dist/index.umd.js +0 -8
  92. package/dist/main.d.ts +0 -0
  93. package/dist/router/dynamicRoutes.d.ts +0 -30
  94. package/dist/router/guards.d.ts +0 -17
  95. package/dist/router/index.d.ts +0 -6
  96. package/dist/router/layoutRoute.d.ts +0 -22
  97. package/dist/router/staticRoutes.d.ts +0 -2
  98. package/dist/stores/app.d.ts +0 -93
  99. package/dist/stores/auth.d.ts +0 -41
  100. package/dist/stores/index.d.ts +0 -10
  101. package/dist/stores/locale.d.ts +0 -42
  102. package/dist/stores/menu.d.ts +0 -77
  103. package/dist/stores/user.d.ts +0 -92
  104. package/dist/style.css +0 -1
  105. package/dist/utils/auth.d.ts +0 -27
  106. package/dist/utils/config.d.ts +0 -30
  107. package/dist/utils/permission.d.ts +0 -18
  108. package/dist/utils/request.d.ts +0 -24
  109. package/dist/utils/storage.d.ts +0 -24
  110. package/dist/views/dashboard/index.vue.d.ts +0 -2
  111. package/dist/views/error/403.vue.d.ts +0 -2
  112. package/dist/views/error/404.vue.d.ts +0 -2
  113. package/dist/views/login/index.vue.d.ts +0 -4
  114. package/dist/views/system/menu/index.vue.d.ts +0 -4
  115. package/dist/views/system/role/index.vue.d.ts +0 -4
  116. package/dist/views/system/user/index.vue.d.ts +0 -4
@@ -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>