Login.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. <template>
  2. <div class="login-page">
  3. <!-- 左侧品牌展示区 — 五维能量轮盘 -->
  4. <div class="brand-panel">
  5. <div class="brand-content">
  6. <!-- ===== Logo + 品牌标识 ===== -->
  7. <div class="brand-header">
  8. <img src="logo.png" alt="浠艾福" class="logo-img" />
  9. <div class="brand-titles">
  10. <span class="brand-cn">亿图腾<span class="brand-dot">·</span>浠艾福</span>
  11. <span class="brand-en">Care Family</span>
  12. </div>
  13. </div>
  14. <!-- ===== 五维能量轮盘 ===== -->
  15. <div class="orbit-section">
  16. <div class="orbit-container">
  17. <!-- 中心"家"徽章 -->
  18. <div class="orbit-center-node">
  19. <span class="center-text">家</span>
  20. </div>
  21. <!-- 轨道装饰 SVG -->
  22. <svg class="orbit-ring-svg" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
  23. <!-- 外层装饰轨道 -->
  24. <circle cx="150" cy="150" r="138" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
  25. <circle cx="150" cy="150" r="134" fill="none" stroke="rgba(255,215,0,0.08)" stroke-width="0.5" stroke-dasharray="2,4" class="orbital-dash" />
  26. <!-- 相生弧线 -->
  27. <defs>
  28. <marker id="orbitArrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
  29. <polygon points="0,0 10,3.5 0,7" fill="#FFD700" opacity="0.6"/>
  30. </marker>
  31. </defs>
  32. <path d="M 150,22 A 128,128 0 0,1 271,83" fill="none" stroke="#FFD700" stroke-width="1.5" stroke-opacity="0.5" class="arc-gen" marker-end="url(#orbitArrow)" />
  33. <path d="M 271,83 A 128,128 0 0,1 226,260" fill="none" stroke="#FFD700" stroke-width="1.5" stroke-opacity="0.5" class="arc-gen" marker-end="url(#orbitArrow)" />
  34. <path d="M 226,260 A 128,128 0 0,1 74,260" fill="none" stroke="#FFD700" stroke-width="1.5" stroke-opacity="0.5" class="arc-gen" marker-end="url(#orbitArrow)" />
  35. <path d="M 74,260 A 128,128 0 0,1 29,83" fill="none" stroke="#FFD700" stroke-width="1.5" stroke-opacity="0.5" class="arc-gen" marker-end="url(#orbitArrow)" />
  36. <path d="M 29,83 A 128,128 0 0,1 150,22" fill="none" stroke="#FFD700" stroke-width="1.5" stroke-opacity="0.5" class="arc-gen" marker-end="url(#orbitArrow)" />
  37. <!-- 轨道粒子 -->
  38. <circle cx="150" cy="12" r="2" fill="#FFE066" opacity="0.5" class="orbit-particle" />
  39. <circle cx="278" cy="80" r="2" fill="#FFE066" opacity="0.5" class="orbit-particle" />
  40. <circle cx="232" cy="265" r="2" fill="#FFE066" opacity="0.5" class="orbit-particle" />
  41. <circle cx="68" cy="265" r="2" fill="#FFE066" opacity="0.5" class="orbit-particle" />
  42. <circle cx="22" cy="80" r="2" fill="#FFE066" opacity="0.5" class="orbit-particle" />
  43. </svg>
  44. <!-- 五维节点 (CSS 公转,72°等分) -->
  45. <!-- 身(橙) 0° -->
  46. <div class="orbit-node" style="--node-color:#FF8C42; --orbit-angle:0deg; --orbit-delay:0s;">
  47. <span class="node-icon">🌏</span>
  48. <span class="node-label">身</span>
  49. </div>
  50. <!-- 智(紫) 72° -->
  51. <div class="orbit-node" style="--node-color:#6366F1; --orbit-angle:72deg; --orbit-delay:-3s;">
  52. <span class="node-icon">⚔️</span>
  53. <span class="node-label">智</span>
  54. </div>
  55. <!-- 富(金) 144° -->
  56. <div class="orbit-node" style="--node-color:#F59E0B; --orbit-angle:144deg; --orbit-delay:-6s;">
  57. <span class="node-icon">💧</span>
  58. <span class="node-label">富</span>
  59. </div>
  60. <!-- 行(绿) 216° -->
  61. <div class="orbit-node" style="--node-color:#10B981; --orbit-angle:216deg; --orbit-delay:-9s;">
  62. <span class="node-icon">🌿</span>
  63. <span class="node-label">行</span>
  64. </div>
  65. <!-- 心(粉) 288° -->
  66. <div class="orbit-node" style="--node-color:#FF6B9D; --orbit-angle:288deg; --orbit-delay:-12s;">
  67. <span class="node-icon">🔥</span>
  68. <span class="node-label">心</span>
  69. </div>
  70. <!-- 图例 -->
  71. <div class="orbit-legend">
  72. <span class="legend-item">
  73. <svg width="20" height="6" viewBox="0 0 20 6"><line x1="0" y1="3" x2="14" y2="3" stroke="#FFD700" stroke-width="1.5"/><polygon points="14,3 18,1 18,5" fill="#FFD700"/></svg>
  74. <span>相生</span>
  75. </span>
  76. </div>
  77. </div>
  78. </div>
  79. <!-- ===== 核心主张 ===== -->
  80. <div class="brand-mission">
  81. <h1 class="mission-title">给全家的<span class="highlight">一站式幸福提案</span></h1>
  82. <p class="mission-desc">
  83. AI 智慧家庭成长平台 · 五维能量驱动全家持续成长
  84. </p>
  85. </div>
  86. <!-- ===== 数据 + 角色标签 ===== -->
  87. <div class="brand-footer">
  88. <div class="stats-row">
  89. <div class="stat-item">
  90. <span class="stat-num">120,000+</span>
  91. <span class="stat-label">家庭信赖</span>
  92. </div>
  93. <div class="stat-divider"></div>
  94. <div class="stat-item">
  95. <span class="stat-num">1,000+</span>
  96. <span class="stat-label">专业人员</span>
  97. </div>
  98. <div class="stat-divider"></div>
  99. <div class="stat-item">
  100. <span class="stat-num">5</span>
  101. <span class="stat-label">幸福维度</span>
  102. </div>
  103. </div>
  104. <div class="role-tags">
  105. <span class="role-tag" style="border-color:#6366F1;color:#818CF8">家庭客户</span>
  106. <span class="role-tag" style="border-color:#FF6B9D;color:#FF8CB3">服务商</span>
  107. <span class="role-tag" style="border-color:#10B981;color:#34D399">平台运营</span>
  108. </div>
  109. </div>
  110. </div>
  111. </div>
  112. <!-- 右侧登录表单区 -->
  113. <div class="form-panel">
  114. <div class="form-card">
  115. <div class="form-header">
  116. <div class="header-icon-wrap">
  117. <svg viewBox="0 0 32 32" fill="none" width="28" height="28" class="header-svg">
  118. <circle cx="16" cy="16" r="14" stroke="currentColor" stroke-width="1.8" fill="none" opacity="0.3"/>
  119. <circle cx="16" cy="12" r="4" stroke="currentColor" stroke-width="1.5" fill="none"/>
  120. <path d="M8 26c0-5 3-8 8-8s8 3 8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/>
  121. </svg>
  122. </div>
  123. <h2 class="form-title">管理后台</h2>
  124. <p class="form-desc">管理员 / 规划师登录</p>
  125. </div>
  126. <el-form
  127. :model="loginForm"
  128. :rules="rules"
  129. ref="loginForm"
  130. class="login-form"
  131. @submit.native.prevent="handleLogin"
  132. >
  133. <el-form-item prop="username">
  134. <el-input
  135. v-model="loginForm.username"
  136. placeholder="用户名 / 手机号"
  137. size="large"
  138. :disabled="loading"
  139. >
  140. <i slot="prefix" class="el-icon-user input-icon"></i>
  141. </el-input>
  142. </el-form-item>
  143. <el-form-item prop="password">
  144. <el-input
  145. v-model="loginForm.password"
  146. type="password"
  147. placeholder="密码"
  148. size="large"
  149. :disabled="loading"
  150. show-password
  151. >
  152. <i slot="prefix" class="el-icon-lock input-icon"></i>
  153. </el-input>
  154. </el-form-item>
  155. <el-form-item prop="captcha" class="captcha-item">
  156. <SliderCaptcha
  157. ref="sliderCaptcha"
  158. @success="onCaptchaSuccess"
  159. @fail="onCaptchaFail"
  160. />
  161. </el-form-item>
  162. <el-form-item>
  163. <el-button
  164. type="primary"
  165. class="login-btn"
  166. @click="handleLogin"
  167. :loading="loading"
  168. :disabled="!captchaPassed"
  169. >
  170. {{ loading ? '登录中...' : '登 录' }}
  171. </el-button>
  172. </el-form-item>
  173. </el-form>
  174. <div class="form-footer">
  175. <span class="copyright">&copy; 2026 亿图腾(北京)科技有限公司</span>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. </template>
  181. <script>
  182. import { adminLogin } from '@/api/auth'
  183. import SliderCaptcha from '@/components/SliderCaptcha'
  184. export default {
  185. name: 'Login',
  186. components: { SliderCaptcha },
  187. data() {
  188. return {
  189. loginForm: {
  190. username: '',
  191. password: ''
  192. },
  193. rules: {
  194. username: [
  195. { required: true, message: '请输入用户名', trigger: 'blur' },
  196. { min: 2, message: '用户名至少2个字符', trigger: 'blur' }
  197. ],
  198. password: [
  199. { required: true, message: '请输入密码', trigger: 'blur' },
  200. { min: 4, message: '密码至少4个字符', trigger: 'blur' }
  201. ]
  202. },
  203. loading: false,
  204. captchaPassed: false
  205. }
  206. },
  207. mounted() {
  208. // 测试环境自动跳过滑块验证码
  209. if (process.env.VUE_APP_SKIP_CAPTCHA === 'true') {
  210. this.$nextTick(() => {
  211. this.captchaPassed = true
  212. // 将 SliderCaptcha 设为已通过状态
  213. if (this.$refs.sliderCaptcha) {
  214. this.$refs.sliderCaptcha.status = 'success'
  215. this.$refs.sliderCaptcha.currentOffset = this.$refs.sliderCaptcha.trackWidth
  216. }
  217. })
  218. }
  219. },
  220. methods: {
  221. onCaptchaSuccess() {
  222. this.captchaPassed = true
  223. },
  224. onCaptchaFail() {
  225. this.captchaPassed = false
  226. },
  227. resetCaptcha() {
  228. if (this.$refs.sliderCaptcha) this.$refs.sliderCaptcha.reset()
  229. this.captchaPassed = false
  230. },
  231. handleLogin() {
  232. if (!this.captchaPassed) {
  233. this.$message.warning('请先完成滑动验证')
  234. return
  235. }
  236. if (this.loading) {
  237. return
  238. }
  239. this.$refs.loginForm.validate(async (valid) => {
  240. if (!valid) return
  241. this.loading = true
  242. try {
  243. const res = await adminLogin({
  244. phone: this.loginForm.username,
  245. password: this.loginForm.password
  246. })
  247. localStorage.setItem('token', res.data.token)
  248. localStorage.setItem('adminId', res.data.adminId)
  249. localStorage.setItem('adminName', res.data.realName)
  250. const role = res.data.role || 'admin'
  251. localStorage.setItem('role', role)
  252. const roles = res.data.roles || [role]
  253. localStorage.setItem('roles', JSON.stringify(roles))
  254. if (this.$message) {
  255. this.$message.success('登录成功')
  256. } else {
  257. console.log('登录成功')
  258. }
  259. await this.$nextTick()
  260. const redirectRole = role === 'teacher' ? '/teacher-dashboard' : '/'
  261. this.$router.replace({ path: redirectRole })
  262. } catch (e) {
  263. const msg = e && e.message ? e.message : '登录失败,请检查账号密码'
  264. if (this.$message) {
  265. this.$message.error(msg)
  266. } else {
  267. console.error(msg)
  268. }
  269. this.resetCaptcha()
  270. } finally {
  271. this.loading = false
  272. }
  273. })
  274. }
  275. }
  276. }
  277. </script>
  278. <style scoped>
  279. /* ============================================================
  280. 登录页面 — 亿图腾 · 浠艾福 (Care Family)
  281. 五维能量轮盘设计 · 2026-07-02 重新设计
  282. 品牌色: 身#FF8C42 / 智#6366F1 / 富#F59E0B / 行#10B981 / 心#FF6B9D
  283. ============================================================ */
  284. .login-page {
  285. display: flex;
  286. height: 100vh;
  287. width: 100vw;
  288. overflow: hidden;
  289. font-family: system-ui, -apple-system, BlinkMacSystemFont,
  290. 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
  291. }
  292. /* ==================== 左侧品牌区 — 暖棕渐变 ==================== */
  293. .brand-panel {
  294. flex: 0 0 55%;
  295. position: relative;
  296. display: flex;
  297. align-items: center;
  298. justify-content: center;
  299. background: linear-gradient(160deg,
  300. #1A0F00 0%,
  301. #2D1810 30%,
  302. #3D2B1F 60%,
  303. #4A3020 100%);
  304. overflow: hidden;
  305. }
  306. /* 背景纹理 — 暖金粒子光晕 */
  307. .brand-panel::before {
  308. content: '';
  309. position: absolute;
  310. top: 0; left: 0; right: 0; bottom: 0;
  311. background:
  312. radial-gradient(ellipse 100% 40% at 50% 30%, rgba(255, 215, 0, 0.04) 0%, transparent 70%),
  313. radial-gradient(ellipse 80% 60% at 80% 80%, rgba(255, 140, 66, 0.05) 0%, transparent 70%),
  314. radial-gradient(circle 50% 50% at 20% 80%, rgba(99, 102, 241, 0.03) 0%, transparent 60%);
  315. pointer-events: none;
  316. z-index: 0;
  317. }
  318. .brand-content {
  319. position: relative;
  320. z-index: 1;
  321. display: flex;
  322. flex-direction: column;
  323. align-items: center;
  324. justify-content: center;
  325. max-width: 480px;
  326. width: 100%;
  327. padding: 32px 36px;
  328. color: #fff;
  329. min-height: 100vh;
  330. box-sizing: border-box;
  331. }
  332. /* -------- Logo + 品牌 -------- */
  333. .brand-header {
  334. flex-shrink: 0;
  335. display: flex;
  336. align-items: center;
  337. gap: 14px;
  338. margin-bottom: 6px;
  339. }
  340. .logo-img {
  341. width: 40px;
  342. height: 40px;
  343. display: block;
  344. filter: brightness(1.1);
  345. }
  346. .brand-titles {
  347. display: flex;
  348. flex-direction: column;
  349. gap: 1px;
  350. }
  351. .brand-cn {
  352. font-size: 24px;
  353. font-weight: 700;
  354. letter-spacing: 4px;
  355. line-height: 1.3;
  356. background: linear-gradient(135deg, #FFE066 0%, #F59E0B 50%, #FFE066 100%);
  357. -webkit-background-clip: text;
  358. -webkit-text-fill-color: transparent;
  359. background-clip: text;
  360. }
  361. .brand-dot {
  362. margin: 0 2px;
  363. opacity: 0.6;
  364. }
  365. .brand-en {
  366. font-size: 11px;
  367. opacity: 0.5;
  368. letter-spacing: 3px;
  369. font-weight: 300;
  370. text-transform: uppercase;
  371. }
  372. /* -------- 五维能量轮盘 ==================== */
  373. .orbit-section {
  374. flex: 1;
  375. display: flex;
  376. align-items: center;
  377. justify-content: center;
  378. width: 100%;
  379. min-height: 0;
  380. margin: -6px 0;
  381. }
  382. .orbit-container {
  383. position: relative;
  384. width: 300px;
  385. height: 300px;
  386. display: flex;
  387. align-items: center;
  388. justify-content: center;
  389. }
  390. /* 轨道 SVG */
  391. .orbit-ring-svg {
  392. position: absolute;
  393. top: 0;
  394. left: 0;
  395. width: 100%;
  396. height: 100%;
  397. z-index: 1;
  398. overflow: visible;
  399. }
  400. /* 虚线轨道旋转 */
  401. .orbital-dash {
  402. animation: rotateDash 20s linear infinite;
  403. transform-origin: 150px 150px;
  404. }
  405. @keyframes rotateDash {
  406. 0% { transform: rotate(0deg); }
  407. 100% { transform: rotate(360deg); }
  408. }
  409. /* 相生弧线流光 */
  410. .arc-gen {
  411. stroke-dasharray: 8 6;
  412. animation: flowArc 1.2s linear infinite;
  413. }
  414. @keyframes flowArc {
  415. 0% { stroke-dashoffset: 0; }
  416. 100% { stroke-dashoffset: -14; }
  417. }
  418. /* 轨道粒子公转 */
  419. .orbit-particle {
  420. animation: orbitParticle 8s linear infinite;
  421. transform-origin: 150px 150px;
  422. }
  423. @keyframes orbitParticle {
  424. 0% { opacity: 0; transform: rotate(0deg) translateX(138px) rotate(0deg); }
  425. 12% { opacity: 0.6; }
  426. 88% { opacity: 0.6; }
  427. 100% { opacity: 0; transform: rotate(360deg) translateX(138px) rotate(-360deg); }
  428. }
  429. /* 中心节点 — 金色"家"徽章 */
  430. .orbit-center-node {
  431. position: relative;
  432. z-index: 3;
  433. width: 72px;
  434. height: 72px;
  435. border-radius: 50%;
  436. background: linear-gradient(135deg, #FFD700, #F59E0B);
  437. display: flex;
  438. align-items: center;
  439. justify-content: center;
  440. box-shadow:
  441. 0 0 20px rgba(245, 158, 11, 0.3),
  442. 0 0 60px rgba(245, 158, 11, 0.1);
  443. animation: pulseCenter 3s ease-in-out infinite;
  444. }
  445. .center-text {
  446. font-size: 24px;
  447. font-weight: 700;
  448. color: #fff;
  449. letter-spacing: 2px;
  450. text-shadow: 0 1px 4px rgba(0,0,0,0.15);
  451. }
  452. @keyframes pulseCenter {
  453. 0%, 100% { box-shadow: 0 0 20px rgba(245, 158, 11, 0.3), 0 0 60px rgba(245, 158, 11, 0.1); transform: scale(1); }
  454. 50% { box-shadow: 0 0 30px rgba(245, 158, 11, 0.5), 0 0 80px rgba(245, 158, 11, 0.2); transform: scale(1.05); }
  455. }
  456. /* 五维节点公转 */
  457. .orbit-node {
  458. position: absolute;
  459. z-index: 2;
  460. width: 52px;
  461. height: 52px;
  462. border-radius: 50%;
  463. background: var(--node-color);
  464. border: 2px solid rgba(255, 255, 255, 0.2);
  465. display: flex;
  466. flex-direction: column;
  467. align-items: center;
  468. justify-content: center;
  469. box-shadow: 0 0 12px rgba(0,0,0,0.15), inset 0 1px 2px rgba(255,255,255,0.15);
  470. animation: orbitRotate 15s linear infinite;
  471. animation-delay: var(--orbit-delay);
  472. left: calc(50% - 26px);
  473. top: calc(50% - 26px);
  474. transform-origin: center;
  475. }
  476. /* 轨道公转: 绕中心公转 125px 半径 */
  477. @keyframes orbitRotate {
  478. 0% { transform: rotate(var(--orbit-angle)) translateX(125px) rotate(calc(-1 * var(--orbit-angle))); }
  479. 100% { transform: rotate(calc(var(--orbit-angle) + 360deg)) translateX(125px) rotate(calc(-1 * var(--orbit-angle) - 360deg)); }
  480. }
  481. .node-icon {
  482. font-size: 14px;
  483. line-height: 1;
  484. margin-bottom: 1px;
  485. }
  486. .node-label {
  487. font-size: 11px;
  488. font-weight: 700;
  489. color: #fff;
  490. line-height: 1;
  491. text-shadow: 0 1px 2px rgba(0,0,0,0.1);
  492. }
  493. /* 图例 */
  494. .orbit-legend {
  495. position: absolute;
  496. bottom: -10px;
  497. left: 50%;
  498. transform: translateX(-50%);
  499. z-index: 2;
  500. display: flex;
  501. align-items: center;
  502. gap: 4px;
  503. font-size: 9px;
  504. opacity: 0.4;
  505. color: #fff;
  506. letter-spacing: 1px;
  507. }
  508. .legend-item {
  509. display: flex;
  510. align-items: center;
  511. gap: 4px;
  512. }
  513. .legend-item svg {
  514. display: block;
  515. }
  516. /* -------- 核心主张 -------- */
  517. .brand-mission {
  518. flex-shrink: 0;
  519. text-align: center;
  520. margin-bottom: 8px;
  521. }
  522. .mission-title {
  523. font-size: 20px;
  524. font-weight: 700;
  525. letter-spacing: 2px;
  526. margin: 0 0 4px;
  527. line-height: 1.4;
  528. }
  529. .mission-title .highlight {
  530. background: linear-gradient(135deg, #FF8C42, #F59E0B);
  531. -webkit-background-clip: text;
  532. -webkit-text-fill-color: transparent;
  533. background-clip: text;
  534. }
  535. .mission-desc {
  536. font-size: 13px;
  537. opacity: 0.55;
  538. letter-spacing: 1px;
  539. margin: 0;
  540. font-weight: 300;
  541. }
  542. /* -------- 底部数据 + 角色 -------- */
  543. .brand-footer {
  544. flex-shrink: 0;
  545. width: 100%;
  546. margin-top: 4px;
  547. }
  548. .stats-row {
  549. display: flex;
  550. align-items: center;
  551. justify-content: center;
  552. gap: 0;
  553. margin-bottom: 10px;
  554. }
  555. .stat-item {
  556. display: flex;
  557. flex-direction: column;
  558. align-items: center;
  559. padding: 0 16px;
  560. }
  561. .stat-num {
  562. font-size: 22px;
  563. font-weight: 700;
  564. background: linear-gradient(135deg, #FFE066, #F59E0B);
  565. -webkit-background-clip: text;
  566. -webkit-text-fill-color: transparent;
  567. background-clip: text;
  568. line-height: 1.2;
  569. }
  570. .stat-label {
  571. font-size: 10px;
  572. opacity: 0.5;
  573. letter-spacing: 1px;
  574. margin-top: 1px;
  575. }
  576. .stat-divider {
  577. width: 1px;
  578. height: 28px;
  579. background: rgba(255, 255, 255, 0.12);
  580. }
  581. .role-tags {
  582. display: flex;
  583. gap: 10px;
  584. justify-content: center;
  585. }
  586. .role-tag {
  587. display: inline-block;
  588. padding: 3px 14px;
  589. font-size: 11px;
  590. border: 1px solid;
  591. border-radius: 20px;
  592. opacity: 0.5;
  593. transition: opacity 0.3s;
  594. letter-spacing: 1px;
  595. }
  596. .role-tag:hover {
  597. opacity: 0.9;
  598. }
  599. /* ==================== 右侧表单区 ==================== */
  600. .form-panel {
  601. flex: 1;
  602. display: flex;
  603. align-items: center;
  604. justify-content: center;
  605. background: #F5FAFE;
  606. padding: 40px;
  607. }
  608. .form-card {
  609. width: 100%;
  610. max-width: 400px;
  611. background: #fff;
  612. border-radius: 20px;
  613. padding: 44px 40px 36px;
  614. box-shadow:
  615. 0 4px 24px rgba(74, 155, 215, 0.06),
  616. 0 1px 4px rgba(0, 0, 0, 0.04);
  617. transition: box-shadow 0.3s;
  618. }
  619. .form-card:hover {
  620. box-shadow:
  621. 0 8px 40px rgba(74, 155, 215, 0.10),
  622. 0 1px 4px rgba(0, 0, 0, 0.04);
  623. }
  624. .form-header {
  625. text-align: center;
  626. margin-bottom: 36px;
  627. }
  628. .header-icon-wrap {
  629. width: 52px;
  630. height: 52px;
  631. border-radius: 50%;
  632. background: linear-gradient(135deg, #E8F4FA 0%, #D6EAF8 100%);
  633. display: inline-flex;
  634. align-items: center;
  635. justify-content: center;
  636. margin-bottom: 16px;
  637. color: #3A7CC4;
  638. }
  639. .header-icon-wrap .header-svg {
  640. display: block;
  641. }
  642. .form-title {
  643. font-size: 22px;
  644. font-weight: 600;
  645. color: #1E293B;
  646. margin: 0 0 6px;
  647. letter-spacing: 2px;
  648. }
  649. .form-desc {
  650. font-size: 13px;
  651. color: #94A3B8;
  652. margin: 0;
  653. letter-spacing: 1px;
  654. }
  655. .login-form {
  656. margin-bottom: 4px;
  657. }
  658. .login-form >>> .el-input__inner {
  659. height: 44px;
  660. line-height: 44px;
  661. border-radius: 10px;
  662. border-color: #E2E8F0;
  663. background: #F8FAFC;
  664. transition: all 0.25s;
  665. font-size: 14px;
  666. padding-left: 42px;
  667. font-family: inherit;
  668. }
  669. .login-form >>> .el-input__inner:focus {
  670. border-color: #4A9BD7;
  671. background: #fff;
  672. box-shadow: 0 0 0 3px rgba(74, 155, 215, 0.10);
  673. }
  674. .login-form >>> .el-input__inner:hover {
  675. border-color: #8FC5E8;
  676. }
  677. .login-form >>> .el-input__prefix {
  678. left: 0;
  679. width: 42px;
  680. display: flex;
  681. align-items: center;
  682. justify-content: center;
  683. }
  684. .input-icon {
  685. font-size: 18px;
  686. color: #94A3B8;
  687. transition: color 0.25s;
  688. }
  689. .login-form >>> .el-input.is-focused .input-icon {
  690. color: #4A9BD7;
  691. }
  692. .login-form >>> .el-form-item {
  693. margin-bottom: 22px;
  694. }
  695. .captcha-item {
  696. margin-bottom: 18px !important;
  697. }
  698. .login-btn {
  699. width: 100%;
  700. height: 44px;
  701. font-size: 16px;
  702. font-weight: 500;
  703. letter-spacing: 6px;
  704. border-radius: 10px;
  705. background: linear-gradient(135deg, #4A9BD7, #3A7CC4);
  706. border: none;
  707. transition: all 0.3s;
  708. font-family: inherit;
  709. }
  710. .login-btn:hover {
  711. background: linear-gradient(135deg, #5BABE8, #4A86CE);
  712. box-shadow: 0 4px 16px rgba(74, 155, 215, 0.35);
  713. transform: translateY(-1px);
  714. }
  715. .login-btn:active {
  716. transform: translateY(0);
  717. }
  718. .login-btn.is-disabled {
  719. background: #C0C4CC !important;
  720. box-shadow: none !important;
  721. }
  722. .form-footer {
  723. text-align: center;
  724. margin-top: 8px;
  725. }
  726. .copyright {
  727. font-size: 12px;
  728. color: #B2BEC3;
  729. letter-spacing: 1px;
  730. }
  731. /* ==================== 响应式 ==================== */
  732. @media screen and (max-width: 900px) {
  733. .brand-panel { display: none; }
  734. .form-panel {
  735. flex: 1;
  736. padding: 24px;
  737. background: linear-gradient(160deg, #1A0F00, #3D2B1F);
  738. }
  739. .form-card {
  740. box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
  741. }
  742. .form-card:hover {
  743. box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
  744. }
  745. }
  746. @media screen and (max-width: 480px) {
  747. .form-panel {
  748. padding: 0;
  749. align-items: stretch;
  750. }
  751. .form-card {
  752. max-width: none;
  753. border-radius: 0;
  754. padding: 36px 24px 24px;
  755. box-shadow: none;
  756. min-height: 100vh;
  757. display: flex;
  758. flex-direction: column;
  759. justify-content: center;
  760. }
  761. .form-card:hover {
  762. box-shadow: none;
  763. }
  764. .form-header {
  765. margin-bottom: 28px;
  766. }
  767. }
  768. </style>