|
@@ -0,0 +1,532 @@
|
|
|
+<template>
|
|
|
+ <div class="login-container">
|
|
|
+ <div class="login-header">
|
|
|
+ <img :src="logo" alt="Logo" class="logo">
|
|
|
+ <h1>欢迎登录</h1>
|
|
|
+ </div>
|
|
|
+ <div class="login-tabs">
|
|
|
+ <div class="tab" :class="{ active: activeTab === 'phone' }" @click="activeTab = 'phone'">手机号登录</div>
|
|
|
+ <div class="tab" :class="{ active: activeTab === 'wechat' }" @click="activeTab = 'wechat'">微信登录</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="login-content">
|
|
|
+ <div class="login-form phone-form" v-show="activeTab === 'phone'">
|
|
|
+ <div class="form-group">
|
|
|
+ <div class="input-with-icon">
|
|
|
+ <el-icon>
|
|
|
+ <Iphone />
|
|
|
+ </el-icon>
|
|
|
+ <el-input v-model="phone" placeholder="请输入手机号" maxlength="11" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="form-group">
|
|
|
+ <div class="input-with-icon verification-code">
|
|
|
+ <el-icon>
|
|
|
+ <Lock />
|
|
|
+ </el-icon>
|
|
|
+ <el-input v-model="code" placeholder="请输入验证码" maxlength="6" />
|
|
|
+ <el-button class="code-btn" :disabled="codeBtnDisabled" @click="getVerificationCode">
|
|
|
+ {{ codeBtnText }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="form-group">
|
|
|
+ <el-button class="login-btn" type="primary" :loading="loading" @click="handlePhoneLogin">
|
|
|
+ 登录
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="login-form wechat-form" v-show="activeTab === 'wechat'">
|
|
|
+ <div class="qrcode-container">
|
|
|
+ <div class="qrcode" :class="{ expired: isQrcodeExpired }"
|
|
|
+ @click="isQrcodeExpired && refreshQrcode()">
|
|
|
+ <!-- 使用vue-qrcode组件 -->
|
|
|
+ <qrcode-vue :value="qrcodeValue" :width="200"/>
|
|
|
+ <!-- 过期遮罩层 -->
|
|
|
+ <div v-if="isQrcodeExpired" class="expired-mask">
|
|
|
+ <el-icon :size="40">
|
|
|
+ <Refresh />
|
|
|
+ </el-icon>
|
|
|
+ <p>点击刷新</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p class="qrcode-tip">请使用微信扫一扫登录</p>
|
|
|
+ <p class="qrcode-refresh">二维码已失效?<a href="javascript:;" @click="refreshQrcode">点击刷新</a></p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="agreement">
|
|
|
+ <el-checkbox v-model="agreement">
|
|
|
+ 我已阅读并同意<a href="javascript:;">用户协议</a>和<a href="javascript:;">隐私政策</a>
|
|
|
+ </el-checkbox>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="login-footer">
|
|
|
+
|
|
|
+ <!-- <div class="other-login">
|
|
|
+ <p>其他登录方式</p>
|
|
|
+ <div class="other-login-icons">
|
|
|
+ <el-icon class="icon-item">
|
|
|
+ <Connection />
|
|
|
+ </el-icon>
|
|
|
+ <el-icon class="icon-item">
|
|
|
+ <Share />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted, watch } from 'vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { Iphone, Lock, Connection, Share } from '@element-plus/icons-vue'
|
|
|
+import QrcodeVue from 'vue-qrcode'
|
|
|
+import logo from '@/public/icon/icon128.png'
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
+import { getCode, getQRcode, getQRcodeResult } from '@/api/index.js'
|
|
|
+// 获取user仓库实例
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+// 状态变量
|
|
|
+const activeTab = ref('phone')
|
|
|
+const isQrcodeExpired = ref(false) // 添加二维码过期状态
|
|
|
+const phone = ref('')
|
|
|
+const code = ref('')
|
|
|
+const agreement = ref(false)
|
|
|
+const loading = ref(false)
|
|
|
+const codeBtnText = ref('获取验证码')
|
|
|
+const codeBtnDisabled = ref(false)
|
|
|
+// 二维码值
|
|
|
+const qrcodeValue = ref('https://example.com/login?token=' + Date.now())
|
|
|
+let countdown = 60
|
|
|
+let timer1 = null
|
|
|
+let timer2 = null
|
|
|
+// 刷新二维码
|
|
|
+const refreshQrcode = async () => {
|
|
|
+ // const result = await getQRcode()
|
|
|
+ qrcodeValue.value = 'https://example.com/login?token=' + Date.now()
|
|
|
+}
|
|
|
+
|
|
|
+// 获取验证码
|
|
|
+const getVerificationCode = async () => {
|
|
|
+ console.log(121);
|
|
|
+
|
|
|
+ if (!validatePhone()) return
|
|
|
+
|
|
|
+ // 模拟发送验证码请求
|
|
|
+ try {
|
|
|
+ // const res = await getCode({
|
|
|
+ // phone: phone.value
|
|
|
+ // })
|
|
|
+ ElMessage.success(`验证码已发送至 ${phone.value}`)
|
|
|
+ codeBtnDisabled.value = true
|
|
|
+ let timer = setInterval(() => {
|
|
|
+ countdown--
|
|
|
+ codeBtnText.value = `${countdown}秒后重新获取`
|
|
|
+ if (countdown <= 0) {
|
|
|
+ clearInterval(timer)
|
|
|
+ codeBtnDisabled.value = false
|
|
|
+ codeBtnText.value = '获取验证码'
|
|
|
+ countdown = 60
|
|
|
+ }
|
|
|
+ }, 1000)
|
|
|
+ } catch (error) {
|
|
|
+ console.log(error);
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 手机号登录
|
|
|
+const handlePhoneLogin = async () => {
|
|
|
+ if (!validatePhone()) return
|
|
|
+ if (!code.value) {
|
|
|
+ ElMessage.warning('请输入验证码')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!agreement.value) {
|
|
|
+ ElMessage.warning('请阅读并同意用户协议和隐私政策')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ // 调用user仓库的登录方法
|
|
|
+ const loginResult = await userStore.login({
|
|
|
+ phone: phone.value,
|
|
|
+ code: code.value,
|
|
|
+ type: 'phone'
|
|
|
+ })
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('登录失败:', error)
|
|
|
+ ElMessage.error('登录失败,请稍后重试')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 微信扫码登录处理
|
|
|
+const handleWechatLogin = async (token) => {
|
|
|
+ try {
|
|
|
+ loading.value = true
|
|
|
+ // 调用user仓库的登录方法,使用微信登录类型
|
|
|
+ const loginResult = await userStore.login({
|
|
|
+ type: 'wechat',
|
|
|
+ // 这里可以传递微信登录相关的参数
|
|
|
+ wechatToken: token || qrcodeValue.value.split('token=')[1]
|
|
|
+ })
|
|
|
+
|
|
|
+ if (loginResult) {
|
|
|
+ ElMessage.success('微信登录成功')
|
|
|
+ // 登录成功后可以执行其他操作
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('微信登录失败:', error)
|
|
|
+ ElMessage.error('微信登录失败,请稍后重试')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+const checkQRcodeResult = async () => {
|
|
|
+ const res = await getQRcodeResult(qrcodeValue.value)
|
|
|
+ if (res.code === 200) {
|
|
|
+ await handleWechatLogin(res.data)
|
|
|
+ } else {
|
|
|
+ setTimeout(() => {
|
|
|
+ checkQRcodeResult()
|
|
|
+ }, 5000);
|
|
|
+ }
|
|
|
+}
|
|
|
+// 验证手机号
|
|
|
+const validatePhone = () => {
|
|
|
+ if (!phone.value) {
|
|
|
+ ElMessage.warning('请输入手机号')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if (!/^1[3-9]\d{9}$/.test(phone.value)) {
|
|
|
+ ElMessage.warning('请输入正确的手机号码')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+watch(activeTab, (newTab) => {
|
|
|
+ if (newTab === 'wechat') {
|
|
|
+ qrcodeValue.value = 'https://example.com/login?token=' + Date.now()
|
|
|
+ setTimeout(() => {
|
|
|
+ checkQRcodeResult()
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 组件挂载时的处理
|
|
|
+onMounted(() => {
|
|
|
+})
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.login-container {
|
|
|
+ max-width: 100%;
|
|
|
+ width: 100%;
|
|
|
+ background-color: #f9fafc;
|
|
|
+ padding: 1rem;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.login-header {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 36px;
|
|
|
+ padding-top: 30px;
|
|
|
+
|
|
|
+ .logo {
|
|
|
+ width: 70px;
|
|
|
+ height: 70px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
|
+ }
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.login-tabs {
|
|
|
+ display: flex;
|
|
|
+ border-bottom: 1px solid #eaedf2;
|
|
|
+ margin-bottom: 32px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 8px 8px 0 0;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
|
+
|
|
|
+ .tab {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ padding: 16px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #666;
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: #4361ee;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ &:after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ bottom: -1px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 40%;
|
|
|
+ height: 3px;
|
|
|
+ background-color: #4361ee;
|
|
|
+ border-radius: 3px 3px 0 0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover:not(.active) {
|
|
|
+ color: #4361ee;
|
|
|
+ background-color: rgba(67, 97, 238, 0.04);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.login-content {
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 0 0 8px 8px;
|
|
|
+ padding: 24px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.login-form {
|
|
|
+ .form-group {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-with-icon {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ border: 1px solid #e0e5ec;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 0 16px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ background-color: #f9fafc;
|
|
|
+
|
|
|
+ &:focus-within {
|
|
|
+ border-color: #4361ee;
|
|
|
+ box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ box-shadow: none;
|
|
|
+ padding: 0;
|
|
|
+ background-color: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__inner) {
|
|
|
+ height: 48px;
|
|
|
+ font-size: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-icon {
|
|
|
+ color: #a0aec0;
|
|
|
+ font-size: 18px;
|
|
|
+ margin-right: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .verification-code {
|
|
|
+ .code-btn {
|
|
|
+ border: none;
|
|
|
+ background: none;
|
|
|
+ color: #4361ee;
|
|
|
+ padding: 0 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ white-space: nowrap;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover:not(:disabled) {
|
|
|
+ color: #2d3fd9;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ color: #a0aec0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .login-btn {
|
|
|
+ width: 100%;
|
|
|
+ height: 48px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: linear-gradient(135deg, #4361ee, #3a56d4);
|
|
|
+ border: none;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 6px 15px rgba(67, 97, 238, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.qrcode-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 30px 0;
|
|
|
+
|
|
|
+ .qrcode {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
|
|
+ img {
|
|
|
+ width:100%
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .qrcode-tip {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .qrcode-refresh {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #718096;
|
|
|
+
|
|
|
+ a {
|
|
|
+ color: #4361ee;
|
|
|
+ text-decoration: none;
|
|
|
+ font-weight: 500;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #2d3fd9;
|
|
|
+ text-decoration: underline;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.login-footer {
|
|
|
+ margin-top: auto;
|
|
|
+ padding-top: 24px;
|
|
|
+
|
|
|
+ .agreement {
|
|
|
+ margin-bottom: 24px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #718096;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
|
|
+ background-color: #4361ee;
|
|
|
+ border-color: #4361ee;
|
|
|
+ }
|
|
|
+
|
|
|
+ a {
|
|
|
+ color: #4361ee;
|
|
|
+ text-decoration: none;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #2d3fd9;
|
|
|
+ text-decoration: underline;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .other-login {
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ p {
|
|
|
+ color: #a0aec0;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &:before,
|
|
|
+ &:after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ width: 70px;
|
|
|
+ height: 1px;
|
|
|
+ background-color: #e0e5ec;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:before {
|
|
|
+ left: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:after {
|
|
|
+ right: 40px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .other-login-icons {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 36px;
|
|
|
+
|
|
|
+ .icon-item {
|
|
|
+ font-size: 24px;
|
|
|
+ color: #718096;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ background-color: #f9fafc;
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #4361ee;
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (min-width: 768px) {
|
|
|
+ // .login-container {
|
|
|
+ // max-width: 420px;
|
|
|
+ // margin: 0 auto;
|
|
|
+ // box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
+ // border-radius: 12px;
|
|
|
+ // background-color: #fff;
|
|
|
+ // min-height: auto;
|
|
|
+ // margin-top: 40px;
|
|
|
+ // margin-bottom: 40px;
|
|
|
+ // }
|
|
|
+}
|
|
|
+</style>
|