xto-fronted 0.9.13 → 0.9.14

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.
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { ref, reactive } from 'vue'
2
+ import { ref, reactive, computed } from 'vue'
3
3
  import { useRouter, useRoute } from 'vue-router'
4
4
  import { Button, Icon } from '@xto/base'
5
5
  import { Form, FormItem, Input, Checkbox } from '@xto/form'
@@ -7,9 +7,14 @@ import { Message } from '@xto/feedback'
7
7
  import { login } from '@/api/auth'
8
8
  import { setTokenInfo } from '@/utils/auth'
9
9
  import { getAppId, getClientId } from '@/utils/config'
10
+ import { useAppStore } from '@/stores/app'
10
11
 
11
12
  const router = useRouter()
12
13
  const route = useRoute()
14
+ const appStore = useAppStore()
15
+
16
+ const appName = computed(() => appStore.appName || 'XTO App')
17
+ const year = new Date().getFullYear()
13
18
 
14
19
  const loading = ref(false)
15
20
  const rememberMe = ref(false)
@@ -21,7 +26,7 @@ const formData = reactive({
21
26
 
22
27
  const rules: Record<string, any[]> = {
23
28
  uid: [
24
- { required: true, message: '请输入用户名', trigger: 'blur' }
29
+ { required: true, message: '请输入账号', trigger: 'blur' }
25
30
  ],
26
31
  password: [
27
32
  { required: true, message: '请输入密码', trigger: 'blur' },
@@ -34,7 +39,10 @@ const formRef = ref()
34
39
  // 登录
35
40
  const handleLogin = async () => {
36
41
  try {
37
- await formRef.value?.validate()
42
+ // Form.validate 返回布尔值(失败不抛异常),校验不通过则终止登录
43
+ const valid = await formRef.value?.validate()
44
+ if (valid === false) return
45
+
38
46
  loading.value = true
39
47
 
40
48
  // 调用登录 API
@@ -71,490 +79,570 @@ const handleLogin = async () => {
71
79
 
72
80
  <template>
73
81
  <div class="login-page">
74
- <!-- 左侧品牌区域 -->
75
- <div class="login-brand">
76
- <div class="brand-content">
77
- <!-- Logo -->
78
- <div class="brand-logo">
79
- <div class="logo-icon">
80
- <svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
81
- <rect width="48" height="48" rx="12" fill="currentColor"/>
82
- <path d="M14 24L20 30L34 16" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
83
- </svg>
84
- </div>
85
- <span class="logo-text">XTO</span>
86
- </div>
87
-
88
- <!-- 标语 -->
89
- <div class="brand-slogan">
90
- <h1>企业级后台管理解决方案</h1>
91
- <p>开箱即用的中后台前端/设计解决方案</p>
92
- </div>
93
-
94
- <!-- 特性列表 -->
95
- <div class="brand-features">
96
- <div class="feature-item">
97
- <div class="feature-icon">
98
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
99
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
100
- </svg>
101
- </div>
102
- <div class="feature-text">
103
- <h4>高性能</h4>
104
- <p>极致的渲染性能与响应速度</p>
82
+ <a class="skip-link" href="#login-card">跳转到登录表单</a>
83
+
84
+ <div class="page">
85
+ <!-- 左侧品牌区 -->
86
+ <aside class="brand-side" aria-hidden="true">
87
+ <div class="deco-grid"></div>
88
+ <div class="deco-glow"></div>
89
+ <div class="deco-line l1"></div>
90
+ <div class="deco-line l2"></div>
91
+
92
+ <div class="brand-inner">
93
+ <div class="brand-logo">
94
+ <div class="logo-mark">X</div>
95
+ <div class="logo-name">
96
+ {{ appName }}
97
+ <small>XTO PLATFORM</small>
105
98
  </div>
106
99
  </div>
107
- <div class="feature-item">
108
- <div class="feature-icon">
109
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
110
- <rect x="3" y="3" width="18" height="18" rx="2"/>
111
- <path d="M9 9h6M9 12h6M9 15h4"/>
112
- </svg>
113
- </div>
114
- <div class="feature-text">
115
- <h4>丰富组件</h4>
116
- <p>60+ 高质量业务组件</p>
117
- </div>
118
- </div>
119
- <div class="feature-item">
120
- <div class="feature-icon">
121
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
122
- <circle cx="12" cy="12" r="10"/>
123
- <path d="M12 6v6l4 2"/>
124
- </svg>
125
- </div>
126
- <div class="feature-text">
127
- <h4>持续更新</h4>
128
- <p>活跃的社区与快速迭代</p>
129
- </div>
130
- </div>
131
- </div>
132
100
 
133
- <!-- 底部装饰 -->
134
- <div class="brand-decoration">
135
- <div class="decoration-line"></div>
136
- <div class="decoration-dots">
137
- <span></span><span></span><span></span>
138
- </div>
101
+ <h1 class="brand-title">
102
+ 统一身份认证<br>数字化赋能<em>高效协同</em>
103
+ </h1>
104
+ <p class="brand-desc">
105
+ 一站式访问所有业务系统<br>
106
+ 安全、高效、可信的企业级数字化工作入口
107
+ </p>
108
+
109
+ <!-- 漂浮插画:认证盾牌 + 业务卡片 -->
110
+ <svg class="brand-visual" viewBox="0 0 460 290" fill="none" xmlns="http://www.w3.org/2000/svg">
111
+ <defs>
112
+ <linearGradient id="gShield" x1="160" y1="60" x2="300" y2="250" gradientUnits="userSpaceOnUse">
113
+ <stop offset="0" stop-color="#4fa3ff"/>
114
+ <stop offset="1" stop-color="#1e5fd6"/>
115
+ </linearGradient>
116
+ <linearGradient id="gOrbit" x1="0" y1="0" x2="460" y2="290" gradientUnits="userSpaceOnUse">
117
+ <stop offset="0" stop-color="#67e8f9" stop-opacity=".5"/>
118
+ <stop offset=".5" stop-color="#818cf8" stop-opacity=".35"/>
119
+ <stop offset="1" stop-color="#34d399" stop-opacity=".4"/>
120
+ </linearGradient>
121
+ <radialGradient id="gHalo" cx=".5" cy=".5" r=".5">
122
+ <stop offset="0" stop-color="#38bdf8" stop-opacity=".3"/>
123
+ <stop offset="1" stop-color="#38bdf8" stop-opacity="0"/>
124
+ </radialGradient>
125
+ <linearGradient id="gViolet" x1="0" y1="0" x2="0" y2="1">
126
+ <stop offset="0" stop-color="#a78bfa"/>
127
+ <stop offset="1" stop-color="#7c5cf0"/>
128
+ </linearGradient>
129
+ </defs>
130
+
131
+ <!-- 光晕与轨道 -->
132
+ <circle cx="230" cy="160" r="130" fill="url(#gHalo)"/>
133
+ <ellipse cx="230" cy="165" rx="190" ry="105" stroke="url(#gOrbit)" stroke-width="1.5" stroke-dasharray="3 8"/>
134
+ <ellipse cx="230" cy="165" rx="145" ry="78" stroke="rgba(255,255,255,.14)" stroke-width="1"/>
135
+
136
+ <!-- 连接点 -->
137
+ <circle cx="52" cy="120" r="4" fill="#67e8f9"/>
138
+ <circle cx="410" cy="98" r="4" fill="#fbbf24"/>
139
+ <circle cx="392" cy="228" r="3.5" fill="#818cf8"/>
140
+ <path d="M52 120 C 110 90, 140 78, 178 92" stroke="#67e8f9" stroke-opacity=".45" stroke-width="1.2" stroke-dasharray="2 6"/>
141
+ <path d="M410 98 C 360 80, 320 76, 286 92" stroke="#fbbf24" stroke-opacity=".45" stroke-width="1.2" stroke-dasharray="2 6"/>
142
+
143
+ <!-- 中央盾牌 -->
144
+ <g class="float-a">
145
+ <path d="M230 58 L296 84 V150 C296 200 268 232 230 246 C192 232 164 200 164 150 V84 Z" fill="url(#gShield)"/>
146
+ <path d="M230 58 L296 84 V150 C296 200 268 232 230 246 Z" fill="#0b3f9e" fill-opacity=".25"/>
147
+ <rect x="206" y="130" width="48" height="40" rx="8" fill="#fff"/>
148
+ <path d="M214 130 v-8 a16 16 0 0 1 32 0 v8" stroke="#fff" stroke-width="7" stroke-linecap="round" fill="none"/>
149
+ <circle cx="230" cy="148" r="5.5" fill="#1e5fd6"/>
150
+ <rect x="227.5" y="150" width="5" height="11" rx="2.5" fill="#1e5fd6"/>
151
+ </g>
152
+
153
+ <!-- 左侧:数据卡片 -->
154
+ <g class="float-b">
155
+ <rect x="66" y="120" width="96" height="74" rx="12" fill="#ffffff"/>
156
+ <rect x="66" y="120" width="96" height="74" rx="12" stroke="#e2e8f0" stroke-width="1"/>
157
+ <rect x="80" y="134" width="42" height="7" rx="3.5" fill="#cbd5e1"/>
158
+ <rect x="80" y="168" width="10" height="16" rx="3" fill="#f59e0b"/>
159
+ <rect x="96" y="158" width="10" height="26" rx="3" fill="#fbbf24"/>
160
+ <rect x="112" y="150" width="10" height="34" rx="3" fill="#10b981"/>
161
+ <path d="M124 146 L138 134" stroke="#10b981" stroke-width="2.5" stroke-linecap="round"/>
162
+ <path d="M133 133 h6 v6" stroke="#10b981" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
163
+ </g>
164
+
165
+ <!-- 右上:齿轮 -->
166
+ <g class="float-c">
167
+ <circle cx="330" cy="86" r="30" fill="url(#gViolet)"/>
168
+ <g fill="#fff">
169
+ <rect x="326.5" y="60" width="7" height="10" rx="2"/>
170
+ <rect x="326.5" y="102" width="7" height="10" rx="2"/>
171
+ <rect x="304" y="82.5" width="10" height="7" rx="2"/>
172
+ <rect x="346" y="82.5" width="10" height="7" rx="2"/>
173
+ <rect x="310.5" y="63" width="7" height="10" rx="2" transform="rotate(-45 314 68)"/>
174
+ <rect x="342.5" y="99" width="7" height="10" rx="2" transform="rotate(-45 346 104)"/>
175
+ <rect x="310.5" y="99" width="7" height="10" rx="2" transform="rotate(45 314 104)"/>
176
+ <rect x="342.5" y="63" width="7" height="10" rx="2" transform="rotate(45 346 68)"/>
177
+ </g>
178
+ <circle cx="330" cy="86" r="12" fill="#fff"/>
179
+ <circle cx="330" cy="86" r="6" fill="#7c5cf0"/>
180
+ </g>
181
+
182
+ <!-- 右下:系统列表卡片 -->
183
+ <g class="float-b">
184
+ <rect x="312" y="150" width="104" height="66" rx="12" fill="#ffffff"/>
185
+ <rect x="312" y="150" width="104" height="66" rx="12" stroke="#e2e8f0" stroke-width="1"/>
186
+ <circle cx="330" cy="168" r="6" fill="#22d3ee"/>
187
+ <rect x="342" y="164" width="52" height="7" rx="3.5" fill="#cbd5e1"/>
188
+ <circle cx="330" cy="186" r="6" fill="#fbbf24"/>
189
+ <rect x="342" y="182" width="40" height="7" rx="3.5" fill="#cbd5e1"/>
190
+ <circle cx="330" cy="204" r="6" fill="#a78bfa"/>
191
+ <rect x="342" y="200" width="46" height="7" rx="3.5" fill="#cbd5e1"/>
192
+ </g>
193
+
194
+ <!-- 左下:认证徽章 -->
195
+ <g class="float-c">
196
+ <circle cx="128" cy="232" r="24" fill="#10b981"/>
197
+ <circle cx="128" cy="232" r="24" stroke="#34d399" stroke-width="4" stroke-opacity=".5"/>
198
+ <path d="M118 232 l7 7 14 -15" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
199
+ </g>
200
+
201
+ <!-- 散落装饰 -->
202
+ <path d="M84 62 l0 12 M78 68 l12 0" stroke="#818cf8" stroke-width="2" stroke-linecap="round"/>
203
+ <path d="M372 262 l0 10 M367 267 l10 0" stroke="#67e8f9" stroke-width="2" stroke-linecap="round"/>
204
+ <circle cx="416" cy="180" r="2.5" fill="#f59e0b"/>
205
+ <circle cx="98" cy="180" r="2.5" fill="#a78bfa"/>
206
+ </svg>
139
207
  </div>
140
- </div>
141
-
142
- <!-- 背景装饰 -->
143
- <div class="brand-bg">
144
- <div class="bg-grid"></div>
145
- </div>
146
- </div>
147
-
148
- <!-- 右侧登录区域 -->
149
- <div class="login-form-section">
150
- <div class="form-container">
151
- <!-- 头部 -->
152
- <div class="form-header">
153
- <h2>欢迎登录</h2>
154
- <p>请输入您的账户信息</p>
155
- </div>
156
-
157
- <!-- 表单 -->
158
- <Form
159
- ref="formRef"
160
- :model="formData"
161
- :rules="rules"
162
- class="login-form"
163
- label-width="0"
164
- >
165
- <FormItem prop="uid">
166
- <div class="input-wrapper">
167
- <label class="input-label">用户名</label>
168
- <Input
169
- v-model="formData.uid"
170
- placeholder="请输入用户名"
171
- size="large"
172
- >
173
- <template #prefix>
174
- <Icon name="user" :size="18" />
175
- </template>
176
- </Input>
208
+ </aside>
209
+
210
+ <!-- 右侧登录区 -->
211
+ <main class="login-side">
212
+ <section class="login-card" id="login-card">
213
+ <header class="card-header">
214
+ <div class="logo-mark" aria-hidden="true">X</div>
215
+ <h2 class="welcome">账号登录</h2>
216
+ <p class="welcome-sub">欢迎登录{{ appName }}</p>
217
+ </header>
218
+
219
+ <Form
220
+ ref="formRef"
221
+ :model="formData"
222
+ :rules="rules"
223
+ class="login-form"
224
+ label-width="0"
225
+ >
226
+ <FormItem prop="uid">
227
+ <div class="input-wrapper">
228
+ <label class="input-label">账号</label>
229
+ <Input
230
+ v-model="formData.uid"
231
+ placeholder="请输入账号"
232
+ size="large"
233
+ >
234
+ <template #prefix>
235
+ <Icon name="user" :size="18" />
236
+ </template>
237
+ </Input>
238
+ </div>
239
+ </FormItem>
240
+
241
+ <FormItem prop="password">
242
+ <div class="input-wrapper">
243
+ <label class="input-label">密码</label>
244
+ <Input
245
+ v-model="formData.password"
246
+ type="password"
247
+ placeholder="请输入登录密码"
248
+ size="large"
249
+ show-password
250
+ @keyup.enter="handleLogin"
251
+ >
252
+ <template #prefix>
253
+ <Icon name="lock" :size="18" />
254
+ </template>
255
+ </Input>
256
+ </div>
257
+ </FormItem>
258
+
259
+ <div class="form-options">
260
+ <Checkbox v-model="rememberMe">记住账号</Checkbox>
261
+ <a href="#" class="link">忘记密码?</a>
177
262
  </div>
178
- </FormItem>
179
-
180
- <FormItem prop="password">
181
- <div class="input-wrapper">
182
- <label class="input-label">密码</label>
183
- <Input
184
- v-model="formData.password"
185
- type="password"
186
- placeholder="请输入密码"
263
+
264
+ <FormItem>
265
+ <Button
266
+ type="primary"
187
267
  size="large"
188
- show-password
189
- @keyup.enter="handleLogin"
268
+ :loading="loading"
269
+ class="btn-login"
270
+ @click="handleLogin"
190
271
  >
191
- <template #prefix>
192
- <Icon name="lock" :size="18" />
193
- </template>
194
- </Input>
195
- </div>
196
- </FormItem>
197
-
198
- <div class="form-options">
199
- <Checkbox v-model="rememberMe">记住我</Checkbox>
200
- <a href="#" class="forgot-link">忘记密码?</a>
201
- </div>
202
-
203
- <FormItem>
204
- <Button
205
- type="primary"
206
- size="large"
207
- :loading="loading"
208
- class="login-btn"
209
- @click="handleLogin"
210
- >
211
- <span v-if="!loading">登 录</span>
212
- <span v-else>登录中...</span>
213
- </Button>
214
- </FormItem>
215
- </Form>
216
-
217
- <!-- 底部 -->
218
- <div class="form-footer">
219
- <p>其他登录方式</p>
220
- <div class="social-login">
221
- <button class="social-btn" title="企业微信">
222
- <svg viewBox="0 0 24 24" fill="currentColor">
223
- <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"/>
224
- </svg>
225
- </button>
226
- <button class="social-btn" title="钉钉">
227
- <svg viewBox="0 0 24 24" fill="currentColor">
228
- <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"/>
229
- </svg>
230
- </button>
231
- <button class="social-btn" title="飞书">
232
- <svg viewBox="0 0 24 24" fill="currentColor">
233
- <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"/>
234
- </svg>
235
- </button>
236
- </div>
237
- </div>
238
- </div>
239
-
240
- <!-- 版权信息 -->
241
- <div class="copyright">
242
- <p>© 2024 XTO Team. All rights reserved.</p>
243
- </div>
272
+ {{ loading ? '正在登录…' : '登 录' }}
273
+ </Button>
274
+ </FormItem>
275
+ </Form>
276
+ </section>
277
+
278
+ <!-- 版权信息 -->
279
+ <footer class="footer">
280
+ Copyright © {{ year }} {{ appName }} All Rights Reserved<br>
281
+ 建议使用 Chrome / Edge 浏览器访问
282
+ </footer>
283
+ </main>
244
284
  </div>
245
285
  </div>
246
286
  </template>
247
287
 
248
288
  <style lang="scss" scoped>
249
289
  .login-page {
290
+ // 设计令牌(来源:桌面 login.html 设计稿)
291
+ --login-accent: #0A6CD1;
292
+ --login-accent-hover: #0857A8;
293
+ --login-accent-ring: rgba(10, 108, 209, 0.18);
294
+ --login-border: #E2E8F0;
295
+ --login-muted: #475569;
296
+ --login-foreground: #020617;
297
+
298
+ // 登录页内整体切换为设计稿蓝(按钮 / 链接 / 聚焦环)
299
+ --xto-color-primary: #0A6CD1;
300
+ --xto-color-primary-light-1: #0857A8;
301
+ --xto-color-primary-light-3: #4A94DC;
302
+ --xto-color-primary-light-9: #E8F1FB;
303
+ --xto-focus-ring: 0 0 0 3px var(--login-accent-ring);
304
+
250
305
  width: 100%;
251
306
  min-height: 100vh;
307
+ font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', Arial, sans-serif;
308
+ color: var(--login-foreground);
309
+ background: #F8FAFC;
310
+ }
311
+
312
+ .skip-link {
313
+ position: absolute;
314
+ top: -48px;
315
+ left: 16px;
316
+ z-index: 100;
317
+ padding: 10px 18px;
318
+ background: #0F172A;
319
+ color: #fff;
320
+ border-radius: 0 0 8px 8px;
321
+ text-decoration: none;
322
+ transition: top 180ms ease;
323
+
324
+ &:focus {
325
+ top: 0;
326
+ }
327
+ }
328
+
329
+ .page {
252
330
  display: flex;
253
- background: var(--xto-bg-color-page, #F7F8FA);
331
+ min-height: 100vh;
332
+ min-width: 320px;
254
333
  }
255
334
 
256
- // 左侧品牌区域(企业级纯色品牌底 + 细网格纹理)
257
- .login-brand {
258
- flex: 1;
259
- position: relative;
335
+ // 共用 Logo 方块
336
+ .logo-mark {
260
337
  display: flex;
261
338
  align-items: center;
262
339
  justify-content: center;
263
- background: var(--xto-color-primary-dark-1, #4338CA);
340
+ width: 48px;
341
+ height: 48px;
342
+ border-radius: 12px;
343
+ background: var(--login-accent);
344
+ color: #fff;
345
+ font-size: 22px;
346
+ font-weight: 700;
347
+ }
348
+
349
+ /* ===== 左侧品牌区 ===== */
350
+ .brand-side {
351
+ position: relative;
352
+ flex: 1.15;
353
+ display: flex;
354
+ flex-direction: column;
355
+ justify-content: center;
356
+ padding: 0 8%;
357
+ background: linear-gradient(150deg, #0e2f5c 0%, #164a80 55%, #2268ab 100%);
358
+ color: #fff;
264
359
  overflow: hidden;
265
360
 
266
- @media (max-width: 992px) {
361
+ @media (max-width: 900px) {
267
362
  display: none;
268
363
  }
364
+ }
269
365
 
270
- .brand-content {
271
- position: relative;
272
- z-index: 2;
273
- padding: 60px;
274
- max-width: 520px;
275
- color: var(--xto-bg-color, #FFFFFF);
276
- }
277
-
278
- .brand-logo {
279
- display: flex;
280
- align-items: center;
281
- gap: 12px;
282
- margin-bottom: 48px;
366
+ .deco-grid {
367
+ position: absolute;
368
+ inset: 0;
369
+ background-image:
370
+ linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
371
+ linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px);
372
+ background-size: 72px 72px;
373
+ pointer-events: none;
374
+ }
283
375
 
284
- .logo-icon {
285
- width: 48px;
286
- height: 48px;
287
- color: var(--xto-bg-color, #FFFFFF);
288
- }
376
+ .deco-glow {
377
+ position: absolute;
378
+ width: 560px;
379
+ height: 560px;
380
+ border-radius: 50%;
381
+ background: radial-gradient(circle, rgba(30, 120, 210, 0.5) 0%, transparent 65%);
382
+ bottom: -200px;
383
+ right: -140px;
384
+ pointer-events: none;
385
+ }
289
386
 
290
- .logo-text {
291
- font-size: 28px;
292
- font-weight: 700;
293
- letter-spacing: 2px;
294
- }
387
+ .deco-line {
388
+ position: absolute;
389
+ width: 1px;
390
+ height: 42%;
391
+ left: 0;
392
+ background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.18), transparent);
393
+ pointer-events: none;
394
+
395
+ &.l1 { top: 8%; }
396
+ &.l2 {
397
+ bottom: 4%;
398
+ background: linear-gradient(180deg, transparent, rgba(70, 150, 230, 0.55), transparent);
295
399
  }
400
+ }
296
401
 
297
- .brand-slogan {
298
- margin-bottom: 48px;
402
+ .brand-inner {
403
+ position: relative;
404
+ z-index: 1;
405
+ }
299
406
 
300
- h1 {
301
- font-size: 32px;
302
- font-weight: 600;
303
- line-height: 1.3;
304
- margin-bottom: 12px;
305
- }
407
+ .brand-logo {
408
+ display: flex;
409
+ align-items: center;
410
+ gap: 14px;
411
+ margin-bottom: 80px;
306
412
 
307
- p {
308
- font-size: 16px;
309
- opacity: 0.8;
310
- line-height: 1.6;
311
- }
312
- }
413
+ .logo-name {
414
+ font-size: 20px;
415
+ font-weight: 600;
416
+ letter-spacing: 2px;
313
417
 
314
- .brand-features {
315
- display: flex;
316
- flex-direction: column;
317
- gap: 24px;
318
-
319
- .feature-item {
320
- display: flex;
321
- align-items: flex-start;
322
- gap: 16px;
323
- padding: 16px;
324
- background: rgba(255, 255, 255, 0.08);
325
- border-radius: var(--xto-border-radius-large, 12px);
326
- transition: background-color 0.2s ease;
327
-
328
- &:hover {
329
- background: rgba(255, 255, 255, 0.14);
330
- }
418
+ small {
419
+ display: block;
420
+ font-size: 11px;
421
+ font-weight: 400;
422
+ letter-spacing: 3.5px;
423
+ opacity: 0.55;
424
+ margin-top: 3px;
331
425
  }
426
+ }
427
+ }
332
428
 
333
- .feature-icon {
334
- width: 40px;
335
- height: 40px;
336
- display: flex;
337
- align-items: center;
338
- justify-content: center;
339
- background: rgba(255, 255, 255, 0.2);
340
- border-radius: var(--xto-border-radius-base, 6px);
341
- flex-shrink: 0;
342
-
343
- svg {
344
- width: 20px;
345
- height: 20px;
346
- }
347
- }
429
+ .brand-title {
430
+ font-size: 40px;
431
+ font-weight: 700;
432
+ line-height: 1.45;
433
+ letter-spacing: 2px;
434
+ color: #fff;
348
435
 
349
- .feature-text {
350
- h4 {
351
- font-size: 15px;
352
- font-weight: 600;
353
- margin-bottom: 4px;
354
- }
355
-
356
- p {
357
- font-size: 13px;
358
- opacity: 0.8;
359
- }
360
- }
436
+ em {
437
+ font-style: normal;
438
+ color: #8ec3f0;
361
439
  }
440
+ }
362
441
 
363
- .brand-decoration {
364
- position: absolute;
365
- bottom: 60px;
366
- left: 60px;
367
- right: 60px;
368
- display: flex;
369
- align-items: center;
370
- gap: 16px;
371
-
372
- .decoration-line {
373
- flex: 1;
374
- height: 1px;
375
- background: linear-gradient(90deg, rgba(255, 255, 255, 0.3), transparent);
376
- }
442
+ .brand-desc {
443
+ margin-top: 24px;
444
+ max-width: 440px;
445
+ font-size: 15px;
446
+ line-height: 2;
447
+ letter-spacing: 0.5px;
448
+ color: rgba(255, 255, 255, 0.62);
449
+ }
377
450
 
378
- .decoration-dots {
379
- display: flex;
380
- gap: 8px;
451
+ .brand-visual {
452
+ margin-top: 52px;
453
+ margin-left: 64px;
454
+ max-width: 460px;
455
+ filter: drop-shadow(0 18px 32px rgba(2, 6, 23, 0.35));
381
456
 
382
- span {
383
- width: 6px;
384
- height: 6px;
385
- background: rgba(255, 255, 255, 0.5);
386
- border-radius: 50%;
387
- }
388
- }
457
+ // 矮屏先缩小插画,过矮再隐藏,保证品牌区不溢出
458
+ @media (max-height: 840px) {
459
+ max-width: 360px;
460
+ margin-top: 36px;
389
461
  }
390
462
 
391
- // 背景装饰(淡紫蓝网点 + 光斑)
392
- .brand-bg {
393
- position: absolute;
394
- inset: 0;
395
- z-index: 1;
396
- overflow: hidden;
397
-
398
- .bg-grid {
399
- position: absolute;
400
- inset: 0;
401
- background-image:
402
- radial-gradient(rgba(255, 255, 255, 0.14) 1.2px, transparent 1.2px),
403
- linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
404
- linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
405
- background-size: 28px 28px, 56px 56px, 56px 56px;
406
- }
463
+ @media (max-height: 700px) {
464
+ display: none;
407
465
  }
408
466
  }
409
467
 
410
- // 右侧登录区域
411
- .login-form-section {
412
- width: 500px;
413
- min-height: 100vh;
468
+ .float-a { animation: login-float-y 5.5s ease-in-out infinite; }
469
+ .float-b { animation: login-float-y 7s ease-in-out 0.8s infinite; }
470
+ .float-c { animation: login-float-y 6s ease-in-out 1.6s infinite; }
471
+
472
+ @keyframes login-float-y {
473
+ 0%, 100% { transform: translateY(0); }
474
+ 50% { transform: translateY(-9px); }
475
+ }
476
+
477
+ /* ===== 右侧登录区 ===== */
478
+ .login-side {
479
+ position: relative;
480
+ flex: 1;
414
481
  display: flex;
415
- flex-direction: column;
482
+ align-items: center;
416
483
  justify-content: center;
417
- padding: 60px 80px;
418
- background: var(--xto-bg-color, #FFFFFF);
484
+ padding: 40px;
485
+ }
486
+
487
+ .login-card {
488
+ width: 100%;
489
+ max-width: 424px;
490
+ padding: 52px 48px 44px;
491
+ background: #fff;
492
+ border: 1px solid var(--login-border);
493
+ border-radius: 16px;
494
+ box-shadow: 0 16px 48px rgba(2, 6, 23, 0.06), 0 2px 6px rgba(2, 6, 23, 0.04);
495
+ }
419
496
 
420
- @media (max-width: 768px) {
421
- width: 100%;
422
- padding: 40px 24px;
497
+ .card-header {
498
+ text-align: center;
499
+
500
+ .logo-mark {
501
+ margin: 0 auto 22px;
502
+ width: 52px;
503
+ height: 52px;
504
+ font-size: 24px;
423
505
  }
424
506
 
425
- .form-container {
426
- width: 100%;
427
- max-width: 360px;
428
- margin: 0 auto;
507
+ .welcome {
508
+ font-size: 24px;
509
+ font-weight: 700;
510
+ letter-spacing: 1px;
511
+ color: var(--login-foreground);
429
512
  }
430
513
 
431
- .form-header {
432
- margin-bottom: 40px;
514
+ .welcome-sub {
515
+ margin-top: 8px;
516
+ font-size: 14px;
517
+ color: var(--login-muted);
518
+ }
519
+ }
433
520
 
434
- h2 {
435
- font-size: 28px;
436
- font-weight: 600;
437
- color: var(--xto-color-text-primary, #1F2329);
438
- margin-bottom: 8px;
439
- }
521
+ .login-form {
522
+ margin-top: 38px;
440
523
 
441
- p {
442
- font-size: 14px;
443
- color: var(--xto-color-text-secondary, #86909C);
444
- }
524
+ .input-wrapper {
525
+ margin-bottom: 2px;
445
526
  }
446
527
 
447
- .login-form {
448
- .input-wrapper {
449
- margin-bottom: 4px;
450
- }
528
+ .input-label {
529
+ display: block;
530
+ margin-bottom: 8px;
531
+ font-size: 14px;
532
+ font-weight: 500;
533
+ color: var(--login-foreground);
534
+ }
451
535
 
452
- .input-label {
453
- display: block;
454
- font-size: 14px;
455
- font-weight: 500;
456
- color: var(--xto-color-text-regular, #4E5566);
457
- margin-bottom: 8px;
458
- }
536
+ :deep(.x-form-item) {
537
+ // 校验错误信息以绝对定位挂在表单项底部,预留空间避免与下一项重叠
538
+ margin-bottom: 28px;
539
+ }
459
540
 
460
- :deep(.x-form-item) {
461
- margin-bottom: 24px;
462
- }
541
+ :deep(.x-form-item__error) {
542
+ line-height: 1.5;
543
+ }
463
544
 
464
- :deep(.x-input) {
465
- --x-input-border-radius: 8px;
466
- }
545
+ :deep(.x-input.x-input--large) {
546
+ height: 48px;
547
+ border: 1.5px solid var(--login-border);
548
+ border-radius: 10px;
549
+ transition: border-color 180ms ease, box-shadow 180ms ease;
467
550
 
468
- :deep(.x-input__prefix) {
469
- margin-right: 12px;
470
- color: var(--xto-color-text-placeholder, #A9AEB8);
551
+ &:hover {
552
+ border-color: #cbd5e1;
471
553
  }
472
554
  }
473
555
 
474
- .form-options {
475
- display: flex;
476
- justify-content: space-between;
477
- align-items: center;
478
- margin-bottom: 24px;
479
-
480
- .forgot-link {
481
- font-size: 14px;
482
- color: var(--xto-color-primary, #4F46E5);
483
- text-decoration: none;
484
- transition: color 0.2s;
485
-
486
- &:hover {
487
- color: var(--xto-color-primary-dark-1, #4338CA);
488
- }
489
- }
556
+ :deep(.x-input__prefix) {
557
+ margin-right: 8px;
558
+ color: #94a3b8;
559
+ transition: color 180ms ease;
490
560
  }
491
561
 
492
- .login-btn {
493
- width: 100%;
494
- height: 44px;
495
- font-size: 16px;
496
- font-weight: 500;
497
- border-radius: var(--xto-border-radius-large, 10px);
498
- letter-spacing: 4px;
499
- transition: all 0.3s ease;
562
+ :deep(.x-input:focus-within .x-input__prefix) {
563
+ color: var(--login-accent);
564
+ }
565
+ }
500
566
 
501
- &:hover:not(:disabled) {
502
- background-color: var(--xto-color-primary-light-1, #6158E7);
567
+ .form-options {
568
+ display: flex;
569
+ align-items: center;
570
+ justify-content: space-between;
571
+ margin: 2px 0 26px;
572
+ font-size: 14px;
573
+ color: var(--login-muted);
574
+
575
+ .link {
576
+ color: var(--login-accent);
577
+ text-decoration: none;
578
+ transition: color 180ms ease;
579
+
580
+ &:hover {
581
+ color: var(--login-accent-hover);
582
+ text-decoration: underline;
503
583
  }
504
584
  }
585
+ }
586
+
587
+ .btn-login {
588
+ width: 100%;
589
+ height: 48px;
590
+ font-size: 16px;
591
+ font-weight: 600;
592
+ letter-spacing: 4px;
593
+ text-indent: 4px;
594
+ border-radius: 10px;
595
+ transition: background-color 180ms ease, box-shadow 180ms ease, transform 100ms ease;
596
+
597
+ &:hover:not(:disabled) {
598
+ box-shadow: 0 6px 18px rgba(10, 108, 209, 0.32);
599
+ }
505
600
 
506
- .form-footer {
507
- margin-top: 32px;
508
- padding-top: 24px;
509
- border-top: 1px solid var(--xto-border-color-lighter, #EDEEF1);
510
- text-align: center;
601
+ &:active:not(:disabled) {
602
+ transform: scale(0.99);
603
+ }
604
+ }
511
605
 
512
- p {
513
- font-size: 13px;
514
- color: var(--xto-color-text-placeholder, #A9AEB8);
515
- margin-bottom: 16px;
516
- }
606
+ .footer {
607
+ position: absolute;
608
+ bottom: 20px;
609
+ left: 0;
610
+ width: 100%;
611
+ text-align: center;
612
+ font-size: 12px;
613
+ letter-spacing: 0.5px;
614
+ line-height: 1.9;
615
+ color: var(--login-muted);
616
+ }
517
617
 
518
- .social-login {
519
- display: flex;
520
- justify-content: center;
521
- gap: 16px;
522
- }
618
+ /* ===== 响应式 ===== */
619
+ @media (max-width: 900px) {
620
+ .login-side {
621
+ flex-direction: column;
622
+ justify-content: center;
623
+ padding: 48px 20px 40px;
624
+ }
523
625
 
524
- .social-btn {
525
- width: 44px;
526
- height: 44px;
527
- display: flex;
528
- align-items: center;
529
- justify-content: center;
530
- background: var(--xto-fill-color, #F2F3F5);
531
- border: none;
532
- border-radius: 50%;
533
- cursor: pointer;
534
- transition: all 0.2s ease;
535
- color: var(--xto-color-text-secondary, #86909C);
536
-
537
- svg {
538
- width: 20px;
539
- height: 20px;
540
- }
541
-
542
- &:hover {
543
- background: var(--xto-color-primary-light-8, #DCDAFC);
544
- color: var(--xto-color-primary, #4F46E5);
545
- transform: translateY(-2px);
546
- }
547
- }
626
+ .login-card {
627
+ padding: 40px 26px 34px;
628
+ border-radius: 14px;
548
629
  }
549
630
 
550
- .copyright {
551
- margin-top: 48px;
552
- text-align: center;
631
+ .footer {
632
+ position: static;
633
+ margin-top: 28px;
634
+ }
635
+ }
553
636
 
554
- p {
555
- font-size: 12px;
556
- color: var(--xto-color-text-placeholder, #A9AEB8);
557
- }
637
+ /* ===== 无障碍:减少动效 ===== */
638
+ @media (prefers-reduced-motion: reduce) {
639
+ .float-a,
640
+ .float-b,
641
+ .float-c,
642
+ .skip-link,
643
+ .btn-login {
644
+ animation: none;
645
+ transition: none;
558
646
  }
559
647
  }
560
- </style>
648
+ </style>