Login.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  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. @input="onPhoneInput"
  140. >
  141. <i slot="prefix" class="el-icon-user input-icon"></i>
  142. </el-input>
  143. </el-form-item>
  144. <el-form-item prop="password">
  145. <el-input
  146. v-model="loginForm.password"
  147. type="password"
  148. placeholder="密码"
  149. size="large"
  150. :disabled="loading"
  151. show-password
  152. >
  153. <i slot="prefix" class="el-icon-lock input-icon"></i>
  154. </el-input>
  155. </el-form-item>
  156. <el-form-item prop="loginRole">
  157. <el-select
  158. v-model="loginForm.loginRole"
  159. placeholder="登录身份(输入手机号后自动带出)"
  160. size="large"
  161. class="identity-select"
  162. :disabled="loading || identityOptions.length === 0"
  163. :loading="identityLoading"
  164. >
  165. <el-option
  166. v-for="item in identityOptions"
  167. :key="item.role"
  168. :label="item.label"
  169. :value="item.role"
  170. ></el-option>
  171. </el-select>
  172. </el-form-item>
  173. <el-form-item prop="captcha" class="captcha-item">
  174. <SliderCaptcha
  175. ref="sliderCaptcha"
  176. @success="onCaptchaSuccess"
  177. @fail="onCaptchaFail"
  178. />
  179. </el-form-item>
  180. <el-form-item>
  181. <el-button
  182. type="primary"
  183. class="login-btn"
  184. @click="handleLogin"
  185. :loading="loading"
  186. :disabled="!captchaPassed"
  187. >
  188. {{ loading ? '登录中...' : '登 录' }}
  189. </el-button>
  190. </el-form-item>
  191. </el-form>
  192. <div class="form-footer">
  193. <span class="copyright">&copy; 2026 亿图腾(北京)科技有限公司</span>
  194. </div>
  195. </div>
  196. </div>
  197. </div>
  198. </template>
  199. <script>
  200. import { adminLogin, getAccountRoles } from '@/api/auth'
  201. import { getRoleHomePage } from '@/utils/permissions'
  202. import SliderCaptcha from '@/components/SliderCaptcha'
  203. export default {
  204. name: 'Login',
  205. components: { SliderCaptcha },
  206. data() {
  207. return {
  208. loginForm: {
  209. username: '',
  210. password: '',
  211. loginRole: ''
  212. },
  213. rules: {
  214. username: [
  215. { required: true, message: '请输入用户名', trigger: 'blur' },
  216. { min: 2, message: '用户名至少2个字符', trigger: 'blur' }
  217. ],
  218. password: [
  219. { required: true, message: '请输入密码', trigger: 'blur' },
  220. { min: 4, message: '密码至少4个字符', trigger: 'blur' }
  221. ]
  222. },
  223. loading: false,
  224. captchaPassed: false,
  225. identityOptions: [],
  226. identityLoading: false,
  227. lastFetchedPhone: '',
  228. phoneInputTimer: null
  229. }
  230. },
  231. mounted() {
  232. // 测试环境自动跳过滑块验证码
  233. if (process.env.VUE_APP_SKIP_CAPTCHA === 'true') {
  234. this.$nextTick(() => {
  235. this.captchaPassed = true
  236. // 将 SliderCaptcha 设为已通过状态
  237. if (this.$refs.sliderCaptcha) {
  238. this.$refs.sliderCaptcha.status = 'success'
  239. this.$refs.sliderCaptcha.currentOffset = this.$refs.sliderCaptcha.trackWidth
  240. }
  241. })
  242. }
  243. },
  244. methods: {
  245. onCaptchaSuccess() {
  246. this.captchaPassed = true
  247. },
  248. onPhoneInput() {
  249. // 防抖:输入停止 500ms 后自动查询身份
  250. if (this.phoneInputTimer) {
  251. clearTimeout(this.phoneInputTimer)
  252. }
  253. var self = this
  254. this.phoneInputTimer = setTimeout(function() {
  255. self.fetchIdentities()
  256. }, 500)
  257. },
  258. async fetchIdentities() {
  259. const phone = (this.loginForm.username || '').trim()
  260. if (!phone || phone === this.lastFetchedPhone) {
  261. return
  262. }
  263. this.lastFetchedPhone = phone
  264. this.identityLoading = true
  265. try {
  266. const res = await getAccountRoles(phone)
  267. const list = (res && res.data) ? res.data : []
  268. this.identityOptions = list
  269. if (list.length > 0) {
  270. // 默认选中第一个身份;当前选中值不在列表中时重置
  271. const exists = list.some(function(it) { return it.role === this.loginForm.loginRole }.bind(this))
  272. if (!exists) {
  273. this.loginForm.loginRole = list[0].role
  274. }
  275. } else {
  276. this.loginForm.loginRole = ''
  277. }
  278. } catch (e) {
  279. this.identityOptions = []
  280. this.loginForm.loginRole = ''
  281. } finally {
  282. this.identityLoading = false
  283. }
  284. },
  285. onCaptchaFail() {
  286. this.captchaPassed = false
  287. },
  288. resetCaptcha() {
  289. if (this.$refs.sliderCaptcha) this.$refs.sliderCaptcha.reset()
  290. this.captchaPassed = false
  291. },
  292. handleLogin() {
  293. if (!this.captchaPassed) {
  294. this.$message.warning('请先完成滑动验证')
  295. return
  296. }
  297. if (this.loading) {
  298. return
  299. }
  300. this.$refs.loginForm.validate(async (valid) => {
  301. if (!valid) return
  302. this.loading = true
  303. try {
  304. if (this.identityOptions.length === 0) {
  305. await this.fetchIdentities()
  306. }
  307. const res = await adminLogin({
  308. phone: this.loginForm.username,
  309. password: this.loginForm.password,
  310. loginRole: this.loginForm.loginRole || undefined
  311. })
  312. localStorage.setItem('token', res.data.token)
  313. localStorage.setItem('adminId', res.data.adminId)
  314. localStorage.setItem('adminName', res.data.realName)
  315. const role = res.data.role || 'admin'
  316. localStorage.setItem('role', role)
  317. const roles = res.data.roles || [role]
  318. localStorage.setItem('roles', JSON.stringify(roles))
  319. if (this.$message) {
  320. this.$message.success('登录成功')
  321. } else {
  322. console.log('登录成功')
  323. }
  324. await this.$nextTick()
  325. // B-6 按所选登录身份跳转:入驻角色进个人工作台,平台角色进首页
  326. const redirectRole = getRoleHomePage([role])
  327. this.$router.replace({ path: redirectRole })
  328. } catch (e) {
  329. const msg = e && e.message ? e.message : '登录失败,请检查账号密码'
  330. if (this.$message) {
  331. this.$message.error(msg)
  332. } else {
  333. console.error(msg)
  334. }
  335. this.resetCaptcha()
  336. } finally {
  337. this.loading = false
  338. }
  339. })
  340. }
  341. }
  342. }
  343. </script>
  344. <style scoped>
  345. /* ============================================================
  346. 登录页面 — 亿图腾 · 浠艾福 (Care Family)
  347. 五维能量轮盘设计 · 2026-07-02 重新设计
  348. 品牌色: 身#FF8C42 / 智#6366F1 / 富#F59E0B / 行#10B981 / 心#FF6B9D
  349. ============================================================ */
  350. .login-page {
  351. display: flex;
  352. height: 100vh;
  353. width: 100vw;
  354. overflow: hidden;
  355. font-family: system-ui, -apple-system, BlinkMacSystemFont,
  356. 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
  357. }
  358. /* 身份选择下拉框擑满表单宽度 */
  359. .identity-select {
  360. width: 100%;
  361. }
  362. /* ==================== 左侧品牌区 — 暖棕渐变 ==================== */
  363. .brand-panel {
  364. flex: 0 0 55%;
  365. position: relative;
  366. display: flex;
  367. align-items: center;
  368. justify-content: center;
  369. background: linear-gradient(160deg,
  370. #1A0F00 0%,
  371. #2D1810 30%,
  372. #3D2B1F 60%,
  373. #4A3020 100%);
  374. overflow: hidden;
  375. }
  376. /* 背景纹理 — 暖金粒子光晕 */
  377. .brand-panel::before {
  378. content: '';
  379. position: absolute;
  380. top: 0; left: 0; right: 0; bottom: 0;
  381. background:
  382. radial-gradient(ellipse 100% 40% at 50% 30%, rgba(255, 215, 0, 0.04) 0%, transparent 70%),
  383. radial-gradient(ellipse 80% 60% at 80% 80%, rgba(255, 140, 66, 0.05) 0%, transparent 70%),
  384. radial-gradient(circle 50% 50% at 20% 80%, rgba(99, 102, 241, 0.03) 0%, transparent 60%);
  385. pointer-events: none;
  386. z-index: 0;
  387. }
  388. .brand-content {
  389. position: relative;
  390. z-index: 1;
  391. display: flex;
  392. flex-direction: column;
  393. align-items: center;
  394. justify-content: center;
  395. max-width: 480px;
  396. width: 100%;
  397. padding: 32px 36px;
  398. color: #fff;
  399. min-height: 100vh;
  400. box-sizing: border-box;
  401. }
  402. /* -------- Logo + 品牌 -------- */
  403. .brand-header {
  404. flex-shrink: 0;
  405. display: flex;
  406. align-items: center;
  407. gap: 14px;
  408. margin-bottom: 6px;
  409. }
  410. .logo-img {
  411. width: 40px;
  412. height: 40px;
  413. display: block;
  414. filter: brightness(1.1);
  415. }
  416. .brand-titles {
  417. display: flex;
  418. flex-direction: column;
  419. gap: 1px;
  420. }
  421. .brand-cn {
  422. font-size: 24px;
  423. font-weight: 700;
  424. letter-spacing: 4px;
  425. line-height: 1.3;
  426. background: linear-gradient(135deg, #FFE066 0%, #F59E0B 50%, #FFE066 100%);
  427. -webkit-background-clip: text;
  428. -webkit-text-fill-color: transparent;
  429. background-clip: text;
  430. }
  431. .brand-dot {
  432. margin: 0 2px;
  433. opacity: 0.6;
  434. }
  435. .brand-en {
  436. font-size: 11px;
  437. opacity: 0.5;
  438. letter-spacing: 3px;
  439. font-weight: 300;
  440. text-transform: uppercase;
  441. }
  442. /* -------- 五维能量轮盘 ==================== */
  443. .orbit-section {
  444. flex: 1;
  445. display: flex;
  446. align-items: center;
  447. justify-content: center;
  448. width: 100%;
  449. min-height: 0;
  450. margin: -6px 0;
  451. }
  452. .orbit-container {
  453. position: relative;
  454. width: 300px;
  455. height: 300px;
  456. display: flex;
  457. align-items: center;
  458. justify-content: center;
  459. }
  460. /* 轨道 SVG */
  461. .orbit-ring-svg {
  462. position: absolute;
  463. top: 0;
  464. left: 0;
  465. width: 100%;
  466. height: 100%;
  467. z-index: 1;
  468. overflow: visible;
  469. }
  470. /* 虚线轨道旋转 */
  471. .orbital-dash {
  472. animation: rotateDash 20s linear infinite;
  473. transform-origin: 150px 150px;
  474. }
  475. @keyframes rotateDash {
  476. 0% { transform: rotate(0deg); }
  477. 100% { transform: rotate(360deg); }
  478. }
  479. /* 相生弧线流光 */
  480. .arc-gen {
  481. stroke-dasharray: 8 6;
  482. animation: flowArc 1.2s linear infinite;
  483. }
  484. @keyframes flowArc {
  485. 0% { stroke-dashoffset: 0; }
  486. 100% { stroke-dashoffset: -14; }
  487. }
  488. /* 轨道粒子公转 */
  489. .orbit-particle {
  490. animation: orbitParticle 8s linear infinite;
  491. transform-origin: 150px 150px;
  492. }
  493. @keyframes orbitParticle {
  494. 0% { opacity: 0; transform: rotate(0deg) translateX(138px) rotate(0deg); }
  495. 12% { opacity: 0.6; }
  496. 88% { opacity: 0.6; }
  497. 100% { opacity: 0; transform: rotate(360deg) translateX(138px) rotate(-360deg); }
  498. }
  499. /* 中心节点 — 金色"家"徽章 */
  500. .orbit-center-node {
  501. position: relative;
  502. z-index: 3;
  503. width: 72px;
  504. height: 72px;
  505. border-radius: 50%;
  506. background: linear-gradient(135deg, #FFD700, #F59E0B);
  507. display: flex;
  508. align-items: center;
  509. justify-content: center;
  510. box-shadow:
  511. 0 0 20px rgba(245, 158, 11, 0.3),
  512. 0 0 60px rgba(245, 158, 11, 0.1);
  513. animation: pulseCenter 3s ease-in-out infinite;
  514. }
  515. .center-text {
  516. font-size: 24px;
  517. font-weight: 700;
  518. color: #fff;
  519. letter-spacing: 2px;
  520. text-shadow: 0 1px 4px rgba(0,0,0,0.15);
  521. }
  522. @keyframes pulseCenter {
  523. 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); }
  524. 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); }
  525. }
  526. /* 五维节点公转 */
  527. .orbit-node {
  528. position: absolute;
  529. z-index: 2;
  530. width: 52px;
  531. height: 52px;
  532. border-radius: 50%;
  533. background: var(--node-color);
  534. border: 2px solid rgba(255, 255, 255, 0.2);
  535. display: flex;
  536. flex-direction: column;
  537. align-items: center;
  538. justify-content: center;
  539. box-shadow: 0 0 12px rgba(0,0,0,0.15), inset 0 1px 2px rgba(255,255,255,0.15);
  540. animation: orbitRotate 15s linear infinite;
  541. animation-delay: var(--orbit-delay);
  542. left: calc(50% - 26px);
  543. top: calc(50% - 26px);
  544. transform-origin: center;
  545. }
  546. /* 轨道公转: 绕中心公转 125px 半径 */
  547. @keyframes orbitRotate {
  548. 0% { transform: rotate(var(--orbit-angle)) translateX(125px) rotate(calc(-1 * var(--orbit-angle))); }
  549. 100% { transform: rotate(calc(var(--orbit-angle) + 360deg)) translateX(125px) rotate(calc(-1 * var(--orbit-angle) - 360deg)); }
  550. }
  551. .node-icon {
  552. font-size: 14px;
  553. line-height: 1;
  554. margin-bottom: 1px;
  555. }
  556. .node-label {
  557. font-size: 11px;
  558. font-weight: 700;
  559. color: #fff;
  560. line-height: 1;
  561. text-shadow: 0 1px 2px rgba(0,0,0,0.1);
  562. }
  563. /* 图例 */
  564. .orbit-legend {
  565. position: absolute;
  566. bottom: -10px;
  567. left: 50%;
  568. transform: translateX(-50%);
  569. z-index: 2;
  570. display: flex;
  571. align-items: center;
  572. gap: 4px;
  573. font-size: 9px;
  574. opacity: 0.4;
  575. color: #fff;
  576. letter-spacing: 1px;
  577. }
  578. .legend-item {
  579. display: flex;
  580. align-items: center;
  581. gap: 4px;
  582. }
  583. .legend-item svg {
  584. display: block;
  585. }
  586. /* -------- 核心主张 -------- */
  587. .brand-mission {
  588. flex-shrink: 0;
  589. text-align: center;
  590. margin-bottom: 8px;
  591. }
  592. .mission-title {
  593. font-size: 20px;
  594. font-weight: 700;
  595. letter-spacing: 2px;
  596. margin: 0 0 4px;
  597. line-height: 1.4;
  598. }
  599. .mission-title .highlight {
  600. background: linear-gradient(135deg, #FF8C42, #F59E0B);
  601. -webkit-background-clip: text;
  602. -webkit-text-fill-color: transparent;
  603. background-clip: text;
  604. }
  605. .mission-desc {
  606. font-size: 13px;
  607. opacity: 0.55;
  608. letter-spacing: 1px;
  609. margin: 0;
  610. font-weight: 300;
  611. }
  612. /* -------- 底部数据 + 角色 -------- */
  613. .brand-footer {
  614. flex-shrink: 0;
  615. width: 100%;
  616. margin-top: 4px;
  617. }
  618. .stats-row {
  619. display: flex;
  620. align-items: center;
  621. justify-content: center;
  622. gap: 0;
  623. margin-bottom: 10px;
  624. }
  625. .stat-item {
  626. display: flex;
  627. flex-direction: column;
  628. align-items: center;
  629. padding: 0 16px;
  630. }
  631. .stat-num {
  632. font-size: 22px;
  633. font-weight: 700;
  634. background: linear-gradient(135deg, #FFE066, #F59E0B);
  635. -webkit-background-clip: text;
  636. -webkit-text-fill-color: transparent;
  637. background-clip: text;
  638. line-height: 1.2;
  639. }
  640. .stat-label {
  641. font-size: 10px;
  642. opacity: 0.5;
  643. letter-spacing: 1px;
  644. margin-top: 1px;
  645. }
  646. .stat-divider {
  647. width: 1px;
  648. height: 28px;
  649. background: rgba(255, 255, 255, 0.12);
  650. }
  651. .role-tags {
  652. display: flex;
  653. gap: 10px;
  654. justify-content: center;
  655. }
  656. .role-tag {
  657. display: inline-block;
  658. padding: 3px 14px;
  659. font-size: 11px;
  660. border: 1px solid;
  661. border-radius: 20px;
  662. opacity: 0.5;
  663. transition: opacity 0.3s;
  664. letter-spacing: 1px;
  665. }
  666. .role-tag:hover {
  667. opacity: 0.9;
  668. }
  669. /* ==================== 右侧表单区 ==================== */
  670. .form-panel {
  671. flex: 1;
  672. display: flex;
  673. align-items: center;
  674. justify-content: center;
  675. background: #F5FAFE;
  676. padding: 40px;
  677. }
  678. .form-card {
  679. width: 100%;
  680. max-width: 400px;
  681. background: #fff;
  682. border-radius: 20px;
  683. padding: 44px 40px 36px;
  684. box-shadow:
  685. 0 4px 24px rgba(74, 155, 215, 0.06),
  686. 0 1px 4px rgba(0, 0, 0, 0.04);
  687. transition: box-shadow 0.3s;
  688. }
  689. .form-card:hover {
  690. box-shadow:
  691. 0 8px 40px rgba(74, 155, 215, 0.10),
  692. 0 1px 4px rgba(0, 0, 0, 0.04);
  693. }
  694. .form-header {
  695. text-align: center;
  696. margin-bottom: 36px;
  697. }
  698. .header-icon-wrap {
  699. width: 52px;
  700. height: 52px;
  701. border-radius: 50%;
  702. background: linear-gradient(135deg, #E8F4FA 0%, #D6EAF8 100%);
  703. display: inline-flex;
  704. align-items: center;
  705. justify-content: center;
  706. margin-bottom: 16px;
  707. color: #3A7CC4;
  708. }
  709. .header-icon-wrap .header-svg {
  710. display: block;
  711. }
  712. .form-title {
  713. font-size: 22px;
  714. font-weight: 600;
  715. color: #1E293B;
  716. margin: 0 0 6px;
  717. letter-spacing: 2px;
  718. }
  719. .form-desc {
  720. font-size: 13px;
  721. color: #94A3B8;
  722. margin: 0;
  723. letter-spacing: 1px;
  724. }
  725. .login-form {
  726. margin-bottom: 4px;
  727. }
  728. .login-form >>> .el-input__inner {
  729. height: 44px;
  730. line-height: 44px;
  731. border-radius: 10px;
  732. border-color: #E2E8F0;
  733. background: #F8FAFC;
  734. transition: all 0.25s;
  735. font-size: 14px;
  736. padding-left: 42px;
  737. font-family: inherit;
  738. }
  739. .login-form >>> .el-input__inner:focus {
  740. border-color: #4A9BD7;
  741. background: #fff;
  742. box-shadow: 0 0 0 3px rgba(74, 155, 215, 0.10);
  743. }
  744. .login-form >>> .el-input__inner:hover {
  745. border-color: #8FC5E8;
  746. }
  747. .login-form >>> .el-input__prefix {
  748. left: 0;
  749. width: 42px;
  750. display: flex;
  751. align-items: center;
  752. justify-content: center;
  753. }
  754. .input-icon {
  755. font-size: 18px;
  756. color: #94A3B8;
  757. transition: color 0.25s;
  758. }
  759. .login-form >>> .el-input.is-focused .input-icon {
  760. color: #4A9BD7;
  761. }
  762. .login-form >>> .el-form-item {
  763. margin-bottom: 22px;
  764. }
  765. .captcha-item {
  766. margin-bottom: 18px !important;
  767. }
  768. .login-btn {
  769. width: 100%;
  770. height: 44px;
  771. font-size: 16px;
  772. font-weight: 500;
  773. letter-spacing: 6px;
  774. border-radius: 10px;
  775. background: linear-gradient(135deg, #4A9BD7, #3A7CC4);
  776. border: none;
  777. transition: all 0.3s;
  778. font-family: inherit;
  779. }
  780. .login-btn:hover {
  781. background: linear-gradient(135deg, #5BABE8, #4A86CE);
  782. box-shadow: 0 4px 16px rgba(74, 155, 215, 0.35);
  783. transform: translateY(-1px);
  784. }
  785. .login-btn:active {
  786. transform: translateY(0);
  787. }
  788. .login-btn.is-disabled {
  789. background: #C0C4CC !important;
  790. box-shadow: none !important;
  791. }
  792. .form-footer {
  793. text-align: center;
  794. margin-top: 8px;
  795. }
  796. .copyright {
  797. font-size: 12px;
  798. color: #B2BEC3;
  799. letter-spacing: 1px;
  800. }
  801. /* ==================== 响应式 ==================== */
  802. @media screen and (max-width: 900px) {
  803. .brand-panel { display: none; }
  804. .form-panel {
  805. flex: 1;
  806. padding: 24px;
  807. background: linear-gradient(160deg, #1A0F00, #3D2B1F);
  808. }
  809. .form-card {
  810. box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
  811. }
  812. .form-card:hover {
  813. box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
  814. }
  815. }
  816. @media screen and (max-width: 480px) {
  817. .form-panel {
  818. padding: 0;
  819. align-items: stretch;
  820. }
  821. .form-card {
  822. max-width: none;
  823. border-radius: 0;
  824. padding: 36px 24px 24px;
  825. box-shadow: none;
  826. min-height: 100vh;
  827. display: flex;
  828. flex-direction: column;
  829. justify-content: center;
  830. }
  831. .form-card:hover {
  832. box-shadow: none;
  833. }
  834. .form-header {
  835. margin-bottom: 28px;
  836. }
  837. }
  838. </style>