refactor: 优化登录页面和用户存储逻辑
- 将API请求路径中的`/api`前缀移除,简化URL - 使用`encodeURIComponent`和`decodeURIComponent`替代`btoa`和`atob`,提高数据存储安全性 - 重构登录页面,使用Vant组件替换Element UI,优化用户体验
This commit is contained in:
parent
7a9847bb25
commit
4891d9376c
|
@ -12,7 +12,7 @@ import {
|
||||||
/** 获取临时令牌 */
|
/** 获取临时令牌 */
|
||||||
export function getTokenApi(appName: string) {
|
export function getTokenApi(appName: string) {
|
||||||
return request<ApiResponseData<TokenResponse>>({
|
return request<ApiResponseData<TokenResponse>>({
|
||||||
url: '/api/wx/login/getToken',
|
url: '/wx/login/getToken',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { appName }
|
params: { appName }
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ export function getTokenApi(appName: string) {
|
||||||
/** 获取微信登录二维码 */
|
/** 获取微信登录二维码 */
|
||||||
export function getWechatQrCodeApi(token: string) {
|
export function getWechatQrCodeApi(token: string) {
|
||||||
return request<ApiResponseData<string>>({
|
return request<ApiResponseData<string>>({
|
||||||
url: '/api/wx/login/wechat/qrcode',
|
url: '/wx/login/wechat/qrcode',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { token }
|
params: { token }
|
||||||
})
|
})
|
||||||
|
@ -30,7 +30,7 @@ export function getWechatQrCodeApi(token: string) {
|
||||||
/** 发送短信验证码 */
|
/** 发送短信验证码 */
|
||||||
export function sendSmsApi(token: string, tel: string) {
|
export function sendSmsApi(token: string, tel: string) {
|
||||||
return request<ApiResponseData<SmsSendResponse>>({
|
return request<ApiResponseData<SmsSendResponse>>({
|
||||||
url: '/api/wx/login/sendSms',
|
url: '/wx/login/sendSms',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { token, tel }
|
params: { token, tel }
|
||||||
})
|
})
|
||||||
|
@ -39,7 +39,7 @@ export function sendSmsApi(token: string, tel: string) {
|
||||||
/** 验证短信验证码 */
|
/** 验证短信验证码 */
|
||||||
export function verifySmsApi(params: VerifySmsParams) {
|
export function verifySmsApi(params: VerifySmsParams) {
|
||||||
return request<ApiResponseData<LoginData>>({
|
return request<ApiResponseData<LoginData>>({
|
||||||
url: '/api/wx/login/verifySms',
|
url: '/wx/login/verifySms',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params
|
params
|
||||||
})
|
})
|
||||||
|
@ -48,7 +48,7 @@ export function verifySmsApi(params: VerifySmsParams) {
|
||||||
/** 用户退出登录 */
|
/** 用户退出登录 */
|
||||||
export function logoutApi(token: string) {
|
export function logoutApi(token: string) {
|
||||||
return request<ApiResponseData<LogoutResponse>>({
|
return request<ApiResponseData<LogoutResponse>>({
|
||||||
url: '/api/wx/login/logout',
|
url: '/wx/login/logout',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { token }
|
params: { token }
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,168 +1,135 @@
|
||||||
<script setup>
|
<template>
|
||||||
import { ref } from 'vue'
|
<div class="login-container">
|
||||||
|
<van-form @submit="handleSubmit">
|
||||||
|
<van-field
|
||||||
|
v-model="form.tel"
|
||||||
|
name="手机号"
|
||||||
|
label="手机号"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
:rules="[{ required: true, message: '请填写手机号' }, { pattern: /^1[3-9]\d{9}$/, message: '手机号格式错误' }]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="form.vcode"
|
||||||
|
center
|
||||||
|
clearable
|
||||||
|
name="验证码"
|
||||||
|
label="验证码"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
:rules="[{ required: true, message: '请填写验证码' }]"
|
||||||
|
>
|
||||||
|
<template #button>
|
||||||
|
<van-button
|
||||||
|
size="small"
|
||||||
|
:disabled="countdown > 0"
|
||||||
|
@click="sendSms"
|
||||||
|
native-type="button"
|
||||||
|
>
|
||||||
|
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
|
||||||
|
</van-button>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<div style="margin: 16px;">
|
||||||
|
<van-button round block type="primary" native-type="submit">
|
||||||
|
立即登录
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { verifySmsApi, sendSmsApi, getTokenApi } from '@/common/apis/ab98'
|
|
||||||
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
|
|
||||||
import { showSuccessToast, showFailToast } from 'vant'
|
import { showSuccessToast, showFailToast } from 'vant'
|
||||||
|
import { getTokenApi, sendSmsApi, verifySmsApi } from '@/common/apis/ab98'
|
||||||
|
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
|
||||||
|
|
||||||
const userStore = useAb98UserStore()
|
const userStore = useAb98UserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
tel: '',
|
tel: '',
|
||||||
vcode: ''
|
vcode: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 验证规则
|
|
||||||
const rules = {
|
|
||||||
tel: [
|
|
||||||
{ required: true, message: '请输入手机号码', trigger: 'blur' },
|
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
vcode: [
|
|
||||||
{ required: true, message: '请输入验证码', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 倒计时状态
|
|
||||||
const countdown = ref(0)
|
const countdown = ref(0)
|
||||||
const canSend = ref(true)
|
const loading = ref(true)
|
||||||
|
let timer: number | null = null
|
||||||
// 发送验证码
|
|
||||||
const handleSendSms = async () => {
|
|
||||||
if (!form.value.tel) {
|
|
||||||
showFailToast('请先输入手机号码')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const { data: tokenData } = await getTokenApi('shop-web')
|
const { data } = await getTokenApi('ab98_app')
|
||||||
if (!tokenData?.success) {
|
if (data.token) {
|
||||||
showFailToast('获取token失败')
|
userStore.setToken(data.token)
|
||||||
|
} else {
|
||||||
|
showFailToast('令牌获取失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showFailToast('网络异常,请重试')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sendSms = async () => {
|
||||||
|
try {
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(form.value.tel)) {
|
||||||
|
showFailToast('手机号格式错误')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userStore.setToken(tokenData.data.token);
|
|
||||||
|
|
||||||
const { data } = await sendSmsApi(tokenData.data.token, form.value.tel)
|
const { data } = await sendSmsApi(userStore.token, form.value.tel)
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
showSuccessToast('验证码已发送')
|
|
||||||
startCountdown()
|
startCountdown()
|
||||||
|
showSuccessToast('验证码已发送')
|
||||||
} else {
|
} else {
|
||||||
showFailToast(data.message || '发送失败')
|
showFailToast(data.message || '发送失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
showFailToast('发送验证码失败')
|
showFailToast('请求异常,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始倒计时
|
|
||||||
const startCountdown = () => {
|
const startCountdown = () => {
|
||||||
canSend.value = false
|
|
||||||
countdown.value = 60
|
countdown.value = 60
|
||||||
const timer = setInterval(() => {
|
timer = window.setInterval(() => {
|
||||||
if (countdown.value <= 0) {
|
if (countdown.value <= 0 && timer) {
|
||||||
clearInterval(timer)
|
window.clearInterval(timer)
|
||||||
canSend.value = true
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
countdown.value--
|
countdown.value--
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交登录
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const { data } = await verifySmsApi({
|
||||||
token: userStore.token,
|
token: userStore.token,
|
||||||
tel: form.value.tel,
|
tel: form.value.tel,
|
||||||
vcode: form.value.vcode
|
vcode: form.value.vcode
|
||||||
}
|
})
|
||||||
|
|
||||||
const { data } = await verifySmsApi(params)
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
userStore.setTel(form.value.tel)
|
||||||
userStore.setUserInfo(data)
|
userStore.setUserInfo(data)
|
||||||
ElMessage.success('登录成功')
|
userStore.setIsLogin(true)
|
||||||
|
showSuccessToast('登录成功')
|
||||||
router.push('/')
|
router.push('/')
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('验证码错误或已过期')
|
showFailToast('验证码错误')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
ElMessage.error('登录失败')
|
console.error(err)
|
||||||
|
showFailToast('登录失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="login-container">
|
|
||||||
<el-card class="login-box">
|
|
||||||
<h2 class="title">手机验证码登录</h2>
|
|
||||||
|
|
||||||
<el-form
|
|
||||||
:model="form"
|
|
||||||
:rules="rules"
|
|
||||||
label-width="80px"
|
|
||||||
label-position="top"
|
|
||||||
>
|
|
||||||
<el-form-item label="手机号码" prop="tel">
|
|
||||||
<el-input v-model="form.tel" placeholder="请输入手机号码" />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="验证码" prop="vcode">
|
|
||||||
<div class="vcode-input">
|
|
||||||
<el-input
|
|
||||||
v-model="form.vcode"
|
|
||||||
placeholder="请输入验证码"
|
|
||||||
style="width: 60%"
|
|
||||||
/>
|
|
||||||
<el-button
|
|
||||||
:disabled="!canSend"
|
|
||||||
@click="handleSendSms"
|
|
||||||
style="margin-left: 10px; width: 35%"
|
|
||||||
>
|
|
||||||
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleSubmit"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
立即登录
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.login-container {
|
.login-container {
|
||||||
display: flex;
|
padding: 20px;
|
||||||
justify-content: center;
|
margin-top: 30%;
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box {
|
|
||||||
width: 400px;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcode-input {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -19,24 +19,24 @@ const STORAGE_KEYS = {
|
||||||
export const useAb98UserStore = defineStore("ab98User", () => {
|
export const useAb98UserStore = defineStore("ab98User", () => {
|
||||||
// 用户面部图像URL
|
// 用户面部图像URL
|
||||||
const storedFace = localStorage.getItem(STORAGE_KEYS.FACE)
|
const storedFace = localStorage.getItem(STORAGE_KEYS.FACE)
|
||||||
const face_img = ref<string>(storedFace ? atob(storedFace) : '')
|
const face_img = ref<string>(storedFace ? decodeURIComponent(storedFace) : '')
|
||||||
// 用户性别(男/女)
|
// 用户性别(男/女)
|
||||||
const storedSex = localStorage.getItem(STORAGE_KEYS.SEX)
|
const storedSex = localStorage.getItem(STORAGE_KEYS.SEX)
|
||||||
const sex = ref<string>(storedSex ? atob(storedSex) : '')
|
const sex = ref<string>(storedSex ? decodeURIComponent(storedSex) : '')
|
||||||
// 用户真实姓名
|
// 用户真实姓名
|
||||||
const storedName = localStorage.getItem(STORAGE_KEYS.NAME)
|
const storedName = localStorage.getItem(STORAGE_KEYS.NAME)
|
||||||
const name = ref<string>(storedName ? atob(storedName) : '')
|
const name = ref<string>(storedName ? decodeURIComponent(storedName) : '')
|
||||||
// AB98系统用户唯一标识
|
// AB98系统用户唯一标识
|
||||||
const storedUserId = localStorage.getItem(STORAGE_KEYS.USERID)
|
const storedUserId = localStorage.getItem(STORAGE_KEYS.USERID)
|
||||||
const userid = ref<string>(storedUserId ? atob(storedUserId) : "")
|
const userid = ref<string>(storedUserId ? decodeURIComponent(storedUserId) : "")
|
||||||
// 是否已完成注册流程
|
// 是否已完成注册流程
|
||||||
const registered = ref<boolean>(JSON.parse(localStorage.getItem(STORAGE_KEYS.REGISTERED) || "false"))
|
const registered = ref<boolean>(JSON.parse(localStorage.getItem(STORAGE_KEYS.REGISTERED) || "false"))
|
||||||
// 用户绑定手机号
|
// 用户绑定手机号
|
||||||
const storedTel = localStorage.getItem(STORAGE_KEYS.TEL)
|
const storedTel = localStorage.getItem(STORAGE_KEYS.TEL)
|
||||||
const tel = ref<string>(storedTel ? atob(storedTel) : "")
|
const tel = ref<string>(storedTel ? decodeURIComponent(storedTel) : "")
|
||||||
// 用户认证令牌
|
// 用户认证令牌
|
||||||
const storedToken = localStorage.getItem(STORAGE_KEYS.TOKEN)
|
const storedToken = localStorage.getItem(STORAGE_KEYS.TOKEN)
|
||||||
const token = ref<string>(storedToken ? atob(storedToken) : "")
|
const token = ref<string>(storedToken ? decodeURIComponent(storedToken) : "")
|
||||||
// 用户登录状态
|
// 用户登录状态
|
||||||
const isLogin = ref<boolean>(false);
|
const isLogin = ref<boolean>(false);
|
||||||
isLogin.value = tel.value ? true : false;
|
isLogin.value = tel.value ? true : false;
|
||||||
|
@ -47,17 +47,17 @@ export const useAb98UserStore = defineStore("ab98User", () => {
|
||||||
*/
|
*/
|
||||||
const setUserInfo = (data: LoginData) => {
|
const setUserInfo = (data: LoginData) => {
|
||||||
face_img.value = data.face_img
|
face_img.value = data.face_img
|
||||||
localStorage.setItem(STORAGE_KEYS.FACE, btoa(data.face_img))
|
localStorage.setItem(STORAGE_KEYS.FACE, encodeURIComponent(data.face_img))
|
||||||
sex.value = data.sex
|
sex.value = data.sex
|
||||||
localStorage.setItem(STORAGE_KEYS.SEX, btoa(data.sex))
|
localStorage.setItem(STORAGE_KEYS.SEX, encodeURIComponent(data.sex))
|
||||||
name.value = data.name
|
name.value = data.name
|
||||||
localStorage.setItem(STORAGE_KEYS.NAME, btoa(data.name))
|
localStorage.setItem(STORAGE_KEYS.NAME, encodeURIComponent(data.name))
|
||||||
userid.value = data.userid
|
userid.value = data.userid
|
||||||
localStorage.setItem(STORAGE_KEYS.USERID, btoa(data.userid))
|
localStorage.setItem(STORAGE_KEYS.USERID, encodeURIComponent(data.userid))
|
||||||
registered.value = data.registered
|
registered.value = data.registered
|
||||||
localStorage.setItem(STORAGE_KEYS.REGISTERED, JSON.stringify(data.registered))
|
localStorage.setItem(STORAGE_KEYS.REGISTERED, JSON.stringify(data.registered))
|
||||||
tel.value = data.tel
|
tel.value = data.tel
|
||||||
localStorage.setItem(STORAGE_KEYS.TEL, btoa(data.tel))
|
localStorage.setItem(STORAGE_KEYS.TEL, encodeURIComponent(data.tel))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,7 +85,7 @@ export const useAb98UserStore = defineStore("ab98User", () => {
|
||||||
* @param value - JWT格式的认证令牌
|
* @param value - JWT格式的认证令牌
|
||||||
*/
|
*/
|
||||||
const setToken = (value: string) => {
|
const setToken = (value: string) => {
|
||||||
localStorage.setItem(STORAGE_KEYS.TOKEN, btoa(value))
|
localStorage.setItem(STORAGE_KEYS.TOKEN, encodeURIComponent(value))
|
||||||
token.value = value
|
token.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue