LoginDialog.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <template>
  2. <el-dialog
  3. v-model="dialogVisible"
  4. :title="''"
  5. width="420px"
  6. center
  7. custom-class="login-dialog"
  8. :close-on-click-modal="false"
  9. :close-on-press-escape="false"
  10. >
  11. <!-- 登录标题 -->
  12. <div class="login-header">
  13. <h2 class="login-title">欢迎回来</h2>
  14. <p class="login-subtitle">请登录您的账号</p>
  15. </div>
  16. <el-tabs v-model="activeTab" @tab-change="handleTabChange" class="login-tabs">
  17. <el-tab-pane label="账号密码登录" name="password">
  18. <el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="0">
  19. <el-form-item prop="account" class="login-form-item">
  20. <el-input
  21. v-model="passwordForm.account"
  22. placeholder="请输入账号"
  23. prefix-icon="User"
  24. clearable
  25. class="login-input"
  26. />
  27. </el-form-item>
  28. <el-form-item prop="password" class="login-form-item">
  29. <el-input
  30. v-model="passwordForm.password"
  31. type="password"
  32. placeholder="请输入密码"
  33. prefix-icon="Lock"
  34. show-password
  35. class="login-input"
  36. />
  37. </el-form-item>
  38. <!-- 增加动态验证码图片 -->
  39. <el-form-item prop="captcha" class="login-form-item">
  40. <div class="gap10">
  41. <el-input
  42. v-model="passwordForm.captcha"
  43. placeholder="请输入验证码"
  44. prefix-icon="Lock"
  45. class="login-input"
  46. />
  47. <img :src="captchaImg" alt="验证码" class="captcha-img" @click="getCaptchaFn">
  48. </div>
  49. </el-form-item>
  50. <el-form-item class="login-form-item remember-item">
  51. <!-- <el-checkbox v-model="passwordForm.remember" class="remember-checkbox">
  52. <span class="remember-text">记住密码</span>
  53. </el-checkbox> -->
  54. <el-link type="primary" href="#" :underline="false" class="forgot-link" @click="activeTab = 'reset'">忘记密码?</el-link>
  55. </el-form-item>
  56. <el-form-item class="login-form-item">
  57. <el-button
  58. type="primary"
  59. @click="handlePasswordLogin"
  60. :loading="loading"
  61. class="login-button"
  62. size="large"
  63. >
  64. 登录
  65. </el-button>
  66. </el-form-item>
  67. </el-form>
  68. </el-tab-pane>
  69. <el-tab-pane label="验证码登录" name="sms">
  70. <el-form ref="smsFormRef" :model="smsForm" :rules="smsRules" label-width="0">
  71. <el-form-item prop="account" class="login-form-item">
  72. <el-input
  73. v-model="smsForm.account"
  74. placeholder="请输入手机号或邮箱"
  75. prefix-icon="User"
  76. clearable
  77. class="login-input"
  78. />
  79. </el-form-item>
  80. <el-form-item prop="smsCode" class="login-form-item">
  81. <el-input
  82. v-model="smsForm.verifyCode"
  83. placeholder="请输入验证码"
  84. prefix-icon="Message"
  85. clearable
  86. class="login-input"
  87. >
  88. <template #append>
  89. <el-button
  90. :disabled="smsCountdown > 0"
  91. @click="sendSmsCode"
  92. style="width:100px"
  93. :class="{'countdown-btn': smsCountdown > 0}"
  94. size="small"
  95. >
  96. {{ smsCountdown > 0 ? `${smsCountdown}s` : '发送验证码' }}
  97. </el-button>
  98. </template>
  99. </el-input>
  100. </el-form-item>
  101. <el-form-item class="login-form-item">
  102. <el-button
  103. type="primary"
  104. @click="handleSmsLogin"
  105. :loading="loading"
  106. class="login-button"
  107. size="large"
  108. >
  109. 登录
  110. </el-button>
  111. </el-form-item>
  112. </el-form>
  113. </el-tab-pane>
  114. <el-tab-pane label="重置密码" name="reset">
  115. <el-form ref="resetFormRef" :model="resetForm" :rules="resetRules" label-width="0">
  116. <el-form-item prop="account" class="login-form-item">
  117. <el-input
  118. v-model="resetForm.account"
  119. placeholder="请输入手机号或邮箱"
  120. prefix-icon="User"
  121. clearable
  122. class="login-input"
  123. />
  124. </el-form-item>
  125. <el-form-item prop="smsCode" class="login-form-item">
  126. <el-input
  127. v-model="resetForm.verifyCode"
  128. placeholder="请输入验证码"
  129. prefix-icon="Message"
  130. clearable
  131. class="login-input"
  132. >
  133. <template #append>
  134. <el-button
  135. :disabled="passwordresetCountdown > 0"
  136. @click="sendPasswordresetCode"
  137. style="width:100px"
  138. :class="{'countdown-btn': passwordresetCountdown > 0}"
  139. size="small"
  140. >
  141. {{ passwordresetCountdown > 0 ? `${passwordresetCountdown}s` : '发送验证码' }}
  142. </el-button>
  143. </template>
  144. </el-input>
  145. </el-form-item>
  146. <el-form-item prop="newPassword" class="login-form-item">
  147. <el-input
  148. v-model="resetForm.newPassword"
  149. placeholder="请输入新密码"
  150. prefix-icon="Lock"
  151. show-password
  152. class="login-input"
  153. />
  154. </el-form-item>
  155. <el-form-item prop="confirmPassword" class="login-form-item">
  156. <el-input
  157. v-model="resetForm.confirmPassword"
  158. placeholder="请确认新密码"
  159. prefix-icon="Lock"
  160. show-password
  161. class="login-input"
  162. />
  163. </el-form-item>
  164. <el-form-item class="login-form-item">
  165. <el-button
  166. type="primary"
  167. @click="handleResetPassword"
  168. :loading="loading"
  169. class="login-button"
  170. size="large"
  171. >
  172. 登录
  173. </el-button>
  174. </el-form-item>
  175. </el-form>
  176. </el-tab-pane>
  177. </el-tabs>
  178. <!-- 其他登录方式 -->
  179. <div class="other-login">
  180. <div class="divider">
  181. <span class="divider-text">其他登录方式</span>
  182. </div>
  183. <div class="social-login">
  184. <el-button
  185. type="default"
  186. @click="handleWechatLogin"
  187. class="social-btn wechat-btn"
  188. >
  189. <img :src="WeChatIcon" alt="微信登录" style="width: 24px; height: 24px;">
  190. </el-button>
  191. <el-button
  192. type="default"
  193. @click="handleQqLogin"
  194. class="social-btn qq-btn"
  195. >
  196. <img :src="QQIcon" alt="微信登录" style="width: 24px; height: 24px;">
  197. </el-button>
  198. </div>
  199. </div>
  200. <!-- 注册链接 -->
  201. <div class="register-link">
  202. <span>还没有账号?</span>
  203. <el-link type="primary" href="#" :underline="false">立即注册</el-link>
  204. </div>
  205. </el-dialog>
  206. </template>
  207. <script setup>
  208. import { ref, reactive, watch, computed } from 'vue'
  209. import { ElMessage } from 'element-plus'
  210. import QQIcon from '@/assets/imgs/QQ.png'
  211. import WeChatIcon from '@/assets/imgs/WeChat.png'
  212. import { getCaptcha, loginUsername, loginPhone, loginEmail, getSmsCode, getEmailCode, resetPassword, getPasswordresetCode } from '@/api/auth.js'
  213. import { useAppStore } from '@/pinia/appStore'
  214. const appStore = useAppStore();
  215. // 正则表达式
  216. const PHONE_REGEX = /^1[3-9]\d{9}$/;
  217. const EMAIL_REGEX = /^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$/;
  218. const isPasswordPhone = computed(() => {
  219. return PHONE_REGEX.test(passwordForm.account);
  220. });
  221. const isPasswordEmail = computed(() => {
  222. return EMAIL_REGEX.test(passwordForm.account);
  223. });
  224. const emit = defineEmits(['login-success'])
  225. // 对话框可见性
  226. const dialogVisible = ref(false)
  227. // 验证码图片
  228. const captchaImg = ref('')
  229. const open = () => {
  230. dialogVisible.value = true;
  231. getCaptchaFn()
  232. }
  233. const getCaptchaFn = async (type) => {
  234. try {
  235. const res = await getCaptcha()
  236. passwordForm.uuid = res.data?.uuid;
  237. if (res.data?.img.startsWith('data:image')) {
  238. captchaImg.value = res.data?.img;
  239. } else {
  240. captchaImg.value = 'data:image/jpeg;base64,' + res.data?.img;
  241. }
  242. console.log(res)
  243. // if (res.code === 200) {
  244. // ElMessage.success('验证码获取成功')
  245. // } else {
  246. // ElMessage.error(res.msg || '验证码获取失败')
  247. // }
  248. } catch (error) {
  249. ElMessage.error(error.message || '验证码获取失败')
  250. }
  251. }
  252. // 当前激活的标签页
  253. const activeTab = ref('password')
  254. // 密码登录表单
  255. const passwordForm = reactive({
  256. account: '13925214105',
  257. password: 'zhangning13Z',
  258. captcha: '',
  259. uuid: ''
  260. })
  261. // 短信登录表单
  262. const smsForm = reactive({
  263. account: '13925214105',
  264. verifyCode: ''
  265. })
  266. // 重置密码表单
  267. const resetForm = reactive({
  268. account: '13925214105',
  269. verifyCode: '',
  270. newPassword: '',
  271. confirmPassword: ''
  272. })
  273. // 表单引用
  274. const passwordFormRef = ref(null)
  275. const smsFormRef = ref(null)
  276. const resetFormRef = ref(null)
  277. // 加载状态
  278. const loading = ref(false)
  279. // 验证码倒计时
  280. const smsCountdown = ref(0)
  281. const passwordresetCountdown = ref(0)
  282. const emailCountdown = ref(0)
  283. // 表单验证规则
  284. const passwordRules = reactive({
  285. account: [
  286. { required: true, message: '请输入账号', trigger: 'blur' },
  287. { validator: (val) => isPasswordPhone.value || isPasswordEmail.value, message: '请输入正确的(手机号或邮箱)', trigger: 'blur' }
  288. ],
  289. password: [
  290. { required: true, message: '请输入密码', trigger: 'blur' },
  291. { min: 6, message: '密码长度不能少于6个字符', trigger: 'blur' }
  292. ],
  293. captcha: [
  294. { required: true, message: '请输入验证码', trigger: 'blur' }
  295. ]
  296. })
  297. const smsRules = reactive({
  298. account: [
  299. { required: true, message: '请输入手机号', trigger: 'blur' },
  300. { validator: (rule, val) => PHONE_REGEX.test(val) || EMAIL_REGEX.test(val), message: '请输入正确的(手机号或邮箱)', trigger: 'blur' }
  301. ],
  302. verifyCode: [
  303. { required: true, message: '请输入验证码', trigger: 'blur' },
  304. { min: 6, max: 6, message: '验证码长度为6个字符', trigger: 'blur' }
  305. ]
  306. })
  307. // 重置密码表单验证规则
  308. const resetRules = reactive({
  309. account: [
  310. { required: true, message: '请输入手机号或邮箱', trigger: 'blur' },
  311. { validator: (rule, val) => PHONE_REGEX.test(val) || EMAIL_REGEX.test(val), message: '请输入正确的(手机号或邮箱)', trigger: 'blur' }
  312. ],
  313. verifyCode: [
  314. { required: true, message: '请输入验证码', trigger: 'blur' },
  315. { min: 6, max: 6, message: '验证码长度为6个字符', trigger: 'blur' }
  316. ],
  317. newPassword: [
  318. { required: true, message: '请输入新密码', trigger: 'blur' },
  319. { min: 6, message: '密码长度不能少于6个字符', trigger: 'blur' }
  320. ],
  321. confirmPassword: [
  322. { required: true, message: '请确认新密码', trigger: 'blur' },
  323. { validator: (rule, val) => val === resetForm.newPassword, message: '两次输入密码不一致', trigger: 'blur' }
  324. ]
  325. })
  326. // 处理标签页切换
  327. const handleTabChange = () => {
  328. // 切换标签页时重置表单
  329. if (activeTab.value === 'password') {
  330. passwordFormRef.value?.resetFields()
  331. } else if (activeTab.value === 'sms') {
  332. smsFormRef.value?.resetFields()
  333. }
  334. }
  335. // 发送短信验证码
  336. const sendSmsCode = async () => {
  337. if (!smsForm.account) {
  338. ElMessage.warning('请先输入手机号或邮箱')
  339. return
  340. }
  341. // 验证手机号格式
  342. if (!PHONE_REGEX.test(smsForm.account) && !EMAIL_REGEX.test(smsForm.account)) {
  343. ElMessage.warning('请输入正确的手机号或邮箱')
  344. return
  345. }
  346. let res = null;
  347. if(PHONE_REGEX.test(smsForm.account)){
  348. res = await getSmsCode({
  349. phone: smsForm.account
  350. })
  351. } else if (EMAIL_REGEX.test(smsForm.account)){
  352. res = await getEmailCode({
  353. email: smsForm.account
  354. })
  355. }
  356. if(res.code !== 200){
  357. return
  358. }
  359. // 模拟发送验证码
  360. ElMessage.success('验证码发送成功')
  361. // 开始倒计时
  362. smsCountdown.value = 60
  363. const timer = setInterval(() => {
  364. smsCountdown.value--
  365. if (smsCountdown.value <= 0) {
  366. clearInterval(timer)
  367. }
  368. }, 1000)
  369. }
  370. // 发送邮箱验证码
  371. const sendEmailCode = () => {
  372. if (!emailForm.email) {
  373. ElMessage.warning('请先输入邮箱')
  374. return
  375. }
  376. // 模拟发送验证码
  377. ElMessage.success('验证码发送成功')
  378. // 开始倒计时
  379. emailCountdown.value = 60
  380. const timer = setInterval(() => {
  381. emailCountdown.value--
  382. if (emailCountdown.value <= 0) {
  383. clearInterval(timer)
  384. }
  385. }, 1000)
  386. }
  387. // 密码登录
  388. const handlePasswordLogin = () => {
  389. passwordFormRef.value?.validate(async (valid) => {
  390. if (valid) {
  391. loading.value = true
  392. const params = {};
  393. // 根据账号类型决定参数名
  394. if (isPasswordPhone.value) {
  395. params['phone'] = passwordForm.account;
  396. } else if (isPasswordEmail.value) {
  397. params['email'] = passwordForm.account;
  398. }
  399. params['password'] = passwordForm.password;
  400. params['captcha'] = passwordForm.captcha;
  401. params['uuid'] = passwordForm.uuid;
  402. const res = await loginUsername(params);
  403. loading.value = false
  404. if (res.code === 200) {
  405. ElMessage.success('登录成功')
  406. // emit('login-success', { type: 'password', userInfo: passwordForm })
  407. dialogVisible.value = false;
  408. // 登录成功后,将token存储到localStorage
  409. setToken(res.token);
  410. }
  411. }
  412. })
  413. }
  414. // 短信登录
  415. const handleSmsLogin = () => {
  416. smsFormRef.value?.validate(async(valid) => {
  417. if (valid) {
  418. loading.value = true
  419. const params = {};
  420. let res = {};
  421. params['code'] = smsForm.verifyCode;
  422. // 根据账号类型决定参数名
  423. if (PHONE_REGEX.test(smsForm.account)) {
  424. params['phone'] = smsForm.account;
  425. res = await loginPhone(params);
  426. } else if (EMAIL_REGEX.test(smsForm.account)) {
  427. params['email'] = smsForm.account;
  428. res = await loginEmail(params);
  429. }
  430. loading.value = false
  431. if (res.code === 200) {
  432. ElMessage.success('登录成功')
  433. // emit('login-success', { type: 'sms', userInfo: smsForm })
  434. dialogVisible.value = false;
  435. // 登录成功后,将token存储到localStorage
  436. setToken(res.token);
  437. }
  438. }
  439. })
  440. }
  441. // 发送重置密码验证码
  442. const sendPasswordresetCode = async () => {
  443. if (!resetForm.account) {
  444. ElMessage.warning('请先输入手机号或邮箱')
  445. return
  446. }
  447. // 验证手机号格式
  448. if (!PHONE_REGEX.test(resetForm.account) && !EMAIL_REGEX.test(resetForm.account)) {
  449. ElMessage.warning('请输入正确的手机号或邮箱')
  450. return
  451. }
  452. let res = await getPasswordresetCode({
  453. account: resetForm.account
  454. })
  455. if(res.code !== 200){
  456. return
  457. }
  458. // 模拟发送验证码
  459. ElMessage.success('验证码发送成功')
  460. // 开始倒计时
  461. passwordresetCountdown.value = 60
  462. const timer = setInterval(() => {
  463. passwordresetCountdown.value--
  464. if (passwordresetCountdown.value <= 0) {
  465. clearInterval(timer)
  466. }
  467. }, 1000)
  468. }
  469. // 重置密码
  470. const handleResetPassword = () => {
  471. resetFormRef.value?.validate(async (valid) => {
  472. if (valid) {
  473. loading.value = true
  474. const res = await resetPassword(resetForm);
  475. loading.value = false
  476. if (res.code === 200) {
  477. ElMessage.success('密码重置成功')
  478. activeTab.value = 'password';
  479. }
  480. }
  481. })
  482. }
  483. const setToken = (token) => {
  484. appStore.UPDATE_TOKEN(token);
  485. }
  486. // 微信登录
  487. const handleWechatLogin = () => {
  488. ElMessage.info('微信登录功能开发中')
  489. }
  490. // QQ登录
  491. const handleQqLogin = () => {
  492. ElMessage.info('QQ登录功能开发中')
  493. }
  494. defineExpose({
  495. open
  496. })
  497. </script>
  498. <style scoped lang="scss">
  499. /* 登录对话框容器 */
  500. .login-dialog {
  501. .el-dialog__header {
  502. padding: 0;
  503. }
  504. .el-dialog__body {
  505. padding: 20px 30px 30px;
  506. }
  507. .el-dialog__footer {
  508. display: none;
  509. }
  510. }
  511. /* 登录标题 */
  512. .login-header {
  513. text-align: center;
  514. margin-bottom: 30px;
  515. }
  516. .login-title {
  517. font-size: 24px;
  518. font-weight: 600;
  519. color: #303133;
  520. margin-bottom: 8px;
  521. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  522. -webkit-background-clip: text;
  523. -webkit-text-fill-color: transparent;
  524. background-clip: text;
  525. }
  526. .login-subtitle {
  527. font-size: 14px;
  528. color: #909399;
  529. }
  530. /* 标签页 */
  531. .login-tabs {
  532. margin-bottom: 20px;
  533. .el-tabs__nav {
  534. justify-content: center;
  535. }
  536. .el-tabs__item {
  537. font-size: 15px;
  538. color: #606266;
  539. padding: 0 20px;
  540. }
  541. .el-tabs__item.is-active {
  542. color: #667eea;
  543. font-weight: 500;
  544. }
  545. .el-tabs__active-bar {
  546. background-color: #667eea;
  547. height: 3px;
  548. }
  549. }
  550. /* 表单样式 */
  551. .login-form-item {
  552. margin-bottom: 20px;
  553. }
  554. /* 输入框样式 */
  555. .login-input {
  556. height: 48px;
  557. border-radius: 24px;
  558. // border: 1px solid #e4e7ed;
  559. transition: all 0.3s ease;
  560. font-size: 15px;
  561. &:hover {
  562. // border-color: #667eea;
  563. // box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
  564. }
  565. &.is-focus {
  566. // border-color: #667eea;
  567. // box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
  568. }
  569. .el-input__wrapper {
  570. box-shadow: none;
  571. background-color: #fafafa;
  572. border-radius: 24px;
  573. }
  574. .el-input__inner {
  575. background-color: transparent;
  576. border: none;
  577. height: 48px;
  578. line-height: 48px;
  579. }
  580. }
  581. /* 记住密码和忘记密码 */
  582. .remember-item {
  583. display: flex;
  584. justify-content: space-between;
  585. align-items: center;
  586. margin-bottom: 25px;
  587. }
  588. .remember-checkbox {
  589. .el-checkbox__input.is-checked .el-checkbox__inner {
  590. background-color: #667eea;
  591. border-color: #667eea;
  592. }
  593. .el-checkbox__input.is-checked + .el-checkbox__label {
  594. color: #667eea;
  595. }
  596. }
  597. .remember-text {
  598. font-size: 14px;
  599. color: #606266;
  600. }
  601. .forgot-link {
  602. font-size: 14px;
  603. color: #667eea;
  604. transition: color 0.3s ease;
  605. &:hover {
  606. color: #5366d1;
  607. }
  608. }
  609. /* 登录按钮 */
  610. .login-button {
  611. width: 100%;
  612. height: 48px;
  613. border-radius: 24px;
  614. font-size: 16px;
  615. font-weight: 500;
  616. border: none;
  617. transition: all 0.3s ease;
  618. &:hover {
  619. transform: translateY(-2px);
  620. box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
  621. }
  622. &:active {
  623. transform: translateY(0);
  624. }
  625. }
  626. /* 其他登录方式 */
  627. .other-login {
  628. margin-top: 30px;
  629. }
  630. .divider {
  631. position: relative;
  632. text-align: center;
  633. margin-bottom: 20px;
  634. &::before, &::after {
  635. content: '';
  636. position: absolute;
  637. top: 50%;
  638. width: 40%;
  639. height: 1px;
  640. background-color: #ebeef5;
  641. }
  642. &::before {
  643. left: 0;
  644. }
  645. &::after {
  646. right: 0;
  647. }
  648. }
  649. .divider-text {
  650. display: inline-block;
  651. padding: 0 20px;
  652. font-size: 13px;
  653. color: #909399;
  654. background-color: #fff;
  655. }
  656. /* 社交登录按钮 */
  657. .social-login {
  658. display: flex;
  659. justify-content: center;
  660. gap: 20px;
  661. }
  662. .social-btn {
  663. width: 44px;
  664. height: 44px;
  665. border-radius: 50%;
  666. transition: all 0.3s ease;
  667. border: 1px solid #e4e7ed;
  668. &:hover {
  669. transform: translateY(-3px);
  670. box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
  671. }
  672. }
  673. .wechat-btn {
  674. color: #67c23a;
  675. &:hover {
  676. border-color: #67c23a;
  677. background-color: rgba(103, 194, 58, 0.05);
  678. }
  679. }
  680. .qq-btn {
  681. color: #409eff;
  682. &:hover {
  683. border-color: #409eff;
  684. background-color: rgba(64, 158, 255, 0.05);
  685. }
  686. }
  687. /* 注册链接 */
  688. .register-link {
  689. text-align: center;
  690. margin-top: 25px;
  691. font-size: 14px;
  692. color: #606266;
  693. .el-link {
  694. margin-left: 5px;
  695. color: #667eea;
  696. font-weight: 500;
  697. &:hover {
  698. color: #5366d1;
  699. }
  700. }
  701. }
  702. /* 验证码按钮 */
  703. .countdown-btn {
  704. background-color: #f5f7fa;
  705. color: #909399;
  706. border-color: #e4e7ed;
  707. &:hover {
  708. background-color: #e4e7ed;
  709. border-color: #dcdfe6;
  710. }
  711. }
  712. /* 响应式设计 */
  713. @media (max-width: 768px) {
  714. .login-dialog {
  715. width: 90% !important;
  716. margin: 0 auto;
  717. .el-dialog__body {
  718. padding: 20px;
  719. }
  720. }
  721. .login-title {
  722. font-size: 22px;
  723. }
  724. .login-input,
  725. .login-button {
  726. height: 44px;
  727. }
  728. }
  729. </style>