feat(auth): 添加用户认证功能模块
- 创建登录页面组件,包含账号密码输入和验证功能 - 创建注册页面组件,支持用户注册和信息填写验证 - 添加用户状态管理store,实现token和用户信息的本地存储 - 集成用户认证API接口,包括登录、注册、信息获取等功能 - 实现用户信息管理页面,支持个人信息修改和密码更改 - 添加表单验证规则,确保输入数据格式正确性 - 实现登录状态持久化和自动恢复功能
This commit is contained in:
76
src/api/user.js
Normal file
76
src/api/user.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import request from './request'
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
export function register(data) {
|
||||
return request({
|
||||
url: '/erp/user/register',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/erp/user/login',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息(通过Token)
|
||||
*/
|
||||
export function getCurrentUser() {
|
||||
return request({
|
||||
url: '/erp/user/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
export function getCurrentUserInfo() {
|
||||
return request({
|
||||
url: '/erp/user/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
export function updateUserInfo(data) {
|
||||
return request({
|
||||
url: '/erp/user/info',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
export function changePassword(data) {
|
||||
return request({
|
||||
url: '/erp/user/change-password',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/erp/user/logout',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
74
src/store/user.js
Normal file
74
src/store/user.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
/**
|
||||
* 用户状态管理
|
||||
*/
|
||||
const state = reactive({
|
||||
// 用户信息
|
||||
user: null,
|
||||
// Token
|
||||
token: null,
|
||||
// 是否已登录
|
||||
isLoggedIn: false
|
||||
})
|
||||
|
||||
// 从localStorage恢复用户信息
|
||||
function initUser() {
|
||||
const token = localStorage.getItem('token')
|
||||
const userInfo = localStorage.getItem('userInfo')
|
||||
|
||||
if (token && userInfo) {
|
||||
try {
|
||||
state.token = token
|
||||
state.user = JSON.parse(userInfo)
|
||||
state.isLoggedIn = true
|
||||
} catch (e) {
|
||||
console.error('恢复用户信息失败:', e)
|
||||
clearUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置用户信息和Token
|
||||
function setUser(user, token) {
|
||||
state.user = user
|
||||
state.token = token
|
||||
state.isLoggedIn = true
|
||||
|
||||
// 保存到localStorage
|
||||
if (token) {
|
||||
localStorage.setItem('token', token)
|
||||
}
|
||||
if (user) {
|
||||
localStorage.setItem('userInfo', JSON.stringify(user))
|
||||
}
|
||||
}
|
||||
|
||||
// 清除用户信息
|
||||
function clearUser() {
|
||||
state.user = null
|
||||
state.token = null
|
||||
state.isLoggedIn = false
|
||||
|
||||
// 清除localStorage
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('userInfo')
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
function updateUser(user) {
|
||||
state.user = { ...state.user, ...user }
|
||||
localStorage.setItem('userInfo', JSON.stringify(state.user))
|
||||
}
|
||||
|
||||
// 初始化
|
||||
initUser()
|
||||
|
||||
export default {
|
||||
state,
|
||||
setUser,
|
||||
clearUser,
|
||||
updateUser,
|
||||
initUser
|
||||
}
|
||||
|
||||
164
src/views/Login.vue
Normal file
164
src/views/Login.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<el-card class="login-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h2>用户登录</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
label-width="100px"
|
||||
@submit.prevent="handleLogin"
|
||||
>
|
||||
<el-form-item label="账号" prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入账号(3-50个字符)"
|
||||
clearable
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码(6-20个字符)"
|
||||
show-password
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
style="width: 100%"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<div class="login-footer">
|
||||
<span>还没有账号?</span>
|
||||
<el-link type="primary" @click="goToRegister">
|
||||
立即注册
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { login } from '../api/user'
|
||||
import userStore from '../store/user'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const loginFormRef = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const loginRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!loginFormRef.value) return
|
||||
|
||||
await loginFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await login({
|
||||
username: loginForm.username,
|
||||
password: loginForm.password
|
||||
})
|
||||
|
||||
if (response.code === '0000' && response.data) {
|
||||
// 保存用户信息和Token
|
||||
userStore.setUser(response.data, response.data.token)
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
|
||||
// 跳转到首页或之前访问的页面
|
||||
const redirect = router.currentRoute.value.query.redirect || '/manage/product'
|
||||
router.push(redirect)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
// 错误消息已在request拦截器中显示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goToRegister = () => {
|
||||
router.push('/register')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.login-footer .el-link {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
245
src/views/Register.vue
Normal file
245
src/views/Register.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="register-container">
|
||||
<el-card class="register-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h2>用户注册</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="registerFormRef"
|
||||
:model="registerForm"
|
||||
:rules="registerRules"
|
||||
label-width="100px"
|
||||
@submit.prevent="handleRegister"
|
||||
>
|
||||
<el-form-item label="账号" prop="username">
|
||||
<el-input
|
||||
v-model="registerForm.username"
|
||||
placeholder="请输入账号(3-50个字符,只能包含字母、数字和下划线)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码(6-20个字符)"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="店铺号" prop="storeCode">
|
||||
<el-input
|
||||
v-model="registerForm.storeCode"
|
||||
placeholder="请输入店铺号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户名称" prop="nickName">
|
||||
<el-input
|
||||
v-model="registerForm.nickName"
|
||||
placeholder="请输入用户名称(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input
|
||||
v-model="registerForm.phone"
|
||||
placeholder="请输入手机号(可选,格式:1开头11位数字)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input
|
||||
v-model="registerForm.email"
|
||||
placeholder="请输入邮箱(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleRegister"
|
||||
style="width: 100%"
|
||||
>
|
||||
注册
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<div class="register-footer">
|
||||
<span>已有账号?</span>
|
||||
<el-link type="primary" @click="goToLogin">
|
||||
立即登录
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { register } from '../api/user'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const registerFormRef = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const registerForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
storeCode: '',
|
||||
nickName: '',
|
||||
phone: '',
|
||||
email: ''
|
||||
})
|
||||
|
||||
const validatePhone = (rule, value, callback) => {
|
||||
if (value && value.trim() !== '') {
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(value)) {
|
||||
callback(new Error('手机号格式不正确,应为1开头11位数字'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
if (value && value.trim() !== '') {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(value)) {
|
||||
callback(new Error('邮箱格式不正确'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const registerRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' },
|
||||
{ min: 3, max: 50, message: '账号长度必须在3-50个字符之间', trigger: 'blur' },
|
||||
{ pattern: /^[a-zA-Z0-9_]+$/, message: '账号只能包含字母、数字和下划线', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度必须在6-20个字符之间', trigger: 'blur' }
|
||||
],
|
||||
storeCode: [
|
||||
{ required: true, message: '请输入店铺号', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ validator: validatePhone, trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ validator: validateEmail, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!registerFormRef.value) return
|
||||
|
||||
await registerFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await register({
|
||||
username: registerForm.username,
|
||||
password: registerForm.password,
|
||||
storeCode: registerForm.storeCode,
|
||||
nickName: registerForm.nickName || undefined,
|
||||
phone: registerForm.phone || undefined,
|
||||
email: registerForm.email || undefined
|
||||
})
|
||||
|
||||
if (response.code === '0000') {
|
||||
ElMessage.success('注册成功')
|
||||
|
||||
// 注册成功后跳转到登录页
|
||||
setTimeout(() => {
|
||||
router.push('/login')
|
||||
}, 1000)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error)
|
||||
// 错误消息已在request拦截器中显示
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goToLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.register-card {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.register-footer {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.register-footer .el-link {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.register-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
width: 80px !important;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
margin-left: 80px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
443
src/views/UserProfile.vue
Normal file
443
src/views/UserProfile.vue
Normal file
@@ -0,0 +1,443 @@
|
||||
<template>
|
||||
<div class="user-profile">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="header-left">
|
||||
<el-button
|
||||
type="text"
|
||||
:icon="ArrowLeft"
|
||||
@click="goBack"
|
||||
class="back-button"
|
||||
>
|
||||
返回
|
||||
</el-button>
|
||||
</div>
|
||||
<h2>用户信息</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 基本信息 -->
|
||||
<el-tab-pane label="基本信息" name="info">
|
||||
<el-form
|
||||
ref="infoFormRef"
|
||||
:model="userForm"
|
||||
:rules="infoRules"
|
||||
label-width="120px"
|
||||
style="max-width: 600px; margin-top: 20px"
|
||||
>
|
||||
<el-form-item label="账号">
|
||||
<el-input v-model="userInfo.username" disabled />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户名称" prop="nickName">
|
||||
<el-input
|
||||
v-model="userForm.nickName"
|
||||
placeholder="请输入用户名称(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input
|
||||
v-model="userForm.phone"
|
||||
placeholder="请输入手机号(可选,格式:1开头11位数字)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input
|
||||
v-model="userForm.email"
|
||||
placeholder="请输入邮箱(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="店铺号" prop="storeCode">
|
||||
<el-input
|
||||
v-model="userForm.storeCode"
|
||||
placeholder="请输入店铺号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="infoLoading"
|
||||
@click="handleUpdateInfo"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="resetInfoForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<el-tab-pane label="修改密码" name="password">
|
||||
<el-form
|
||||
ref="passwordFormRef"
|
||||
:model="passwordForm"
|
||||
:rules="passwordRules"
|
||||
label-width="120px"
|
||||
style="max-width: 600px; margin-top: 20px"
|
||||
>
|
||||
<el-form-item label="旧密码" prop="oldPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.oldPassword"
|
||||
type="password"
|
||||
placeholder="请输入旧密码"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.newPassword"
|
||||
type="password"
|
||||
placeholder="请输入新密码(6-20个字符)"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认新密码" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入新密码"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="passwordLoading"
|
||||
@click="handleChangePassword"
|
||||
>
|
||||
修改密码
|
||||
</el-button>
|
||||
<el-button @click="resetPasswordForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 账户信息 -->
|
||||
<el-tab-pane label="账户信息" name="account">
|
||||
<el-descriptions :column="1" border style="max-width: 600px; margin-top: 20px">
|
||||
<el-descriptions-item label="账号">
|
||||
{{ userInfo.username }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="用户名称">
|
||||
{{ userInfo.nickName || '未设置' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">
|
||||
{{ userInfo.phone || '未设置' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">
|
||||
{{ userInfo.email || '未设置' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="店铺号">
|
||||
{{ userInfo.storeCode }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="userInfo.status === 'ACTIVE' ? 'success' : 'danger'">
|
||||
{{ userInfo.status === 'ACTIVE' ? '激活' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后登录时间">
|
||||
{{ formatDateTime(userInfo.lastLoginTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后登录IP">
|
||||
{{ userInfo.lastLoginIp || '未知' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDateTime(userInfo.createTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { getCurrentUserInfo, updateUserInfo, changePassword } from '../api/user'
|
||||
import userStore from '../store/user'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const activeTab = ref('info')
|
||||
const infoFormRef = ref(null)
|
||||
const passwordFormRef = ref(null)
|
||||
const infoLoading = ref(false)
|
||||
const passwordLoading = ref(false)
|
||||
|
||||
const userInfo = ref({
|
||||
id: null,
|
||||
username: '',
|
||||
nickName: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
storeCode: '',
|
||||
status: '',
|
||||
lastLoginTime: null,
|
||||
lastLoginIp: '',
|
||||
createTime: null
|
||||
})
|
||||
|
||||
const userForm = reactive({
|
||||
nickName: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
storeCode: ''
|
||||
})
|
||||
|
||||
const passwordForm = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const validatePhone = (rule, value, callback) => {
|
||||
if (value && value.trim() !== '') {
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(value)) {
|
||||
callback(new Error('手机号格式不正确,应为1开头11位数字'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
if (value && value.trim() !== '') {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(value)) {
|
||||
callback(new Error('邮箱格式不正确'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value !== passwordForm.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const infoRules = {
|
||||
phone: [
|
||||
{ validator: validatePhone, trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ validator: validateEmail, trigger: 'blur' }
|
||||
],
|
||||
storeCode: [
|
||||
{ required: true, message: '请输入店铺号', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
const passwordRules = {
|
||||
oldPassword: [
|
||||
{ required: true, message: '请输入旧密码', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度必须在6-20个字符之间', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 加载用户信息
|
||||
const loadUserInfo = async () => {
|
||||
try {
|
||||
const response = await getCurrentUserInfo()
|
||||
if (response.code === '0000' && response.data) {
|
||||
userInfo.value = response.data
|
||||
|
||||
// 填充表单
|
||||
userForm.nickName = response.data.nickName || ''
|
||||
userForm.phone = response.data.phone || ''
|
||||
userForm.email = response.data.email || ''
|
||||
userForm.storeCode = response.data.storeCode || ''
|
||||
|
||||
// 更新store中的用户信息
|
||||
userStore.updateUser(response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
const handleUpdateInfo = async () => {
|
||||
if (!infoFormRef.value) return
|
||||
|
||||
await infoFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
|
||||
infoLoading.value = true
|
||||
try {
|
||||
const response = await updateUserInfo({
|
||||
nickName: userForm.nickName || undefined,
|
||||
phone: userForm.phone || undefined,
|
||||
email: userForm.email || undefined,
|
||||
storeCode: userForm.storeCode
|
||||
})
|
||||
|
||||
if (response.code === '0000') {
|
||||
ElMessage.success('更新成功')
|
||||
// 重新加载用户信息
|
||||
await loadUserInfo()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新用户信息失败:', error)
|
||||
} finally {
|
||||
infoLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
const handleChangePassword = async () => {
|
||||
if (!passwordFormRef.value) return
|
||||
|
||||
await passwordFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
|
||||
passwordLoading.value = true
|
||||
try {
|
||||
const response = await changePassword({
|
||||
oldPassword: passwordForm.oldPassword,
|
||||
newPassword: passwordForm.newPassword
|
||||
})
|
||||
|
||||
if (response.code === '0000') {
|
||||
ElMessage.success('密码修改成功')
|
||||
resetPasswordForm()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error)
|
||||
} finally {
|
||||
passwordLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置基本信息表单
|
||||
const resetInfoForm = () => {
|
||||
userForm.nickName = userInfo.value.nickName || ''
|
||||
userForm.phone = userInfo.value.phone || ''
|
||||
userForm.email = userInfo.value.email || ''
|
||||
userForm.storeCode = userInfo.value.storeCode || ''
|
||||
infoFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 重置密码表单
|
||||
const resetPasswordForm = () => {
|
||||
passwordForm.oldPassword = ''
|
||||
passwordForm.newPassword = ''
|
||||
passwordForm.confirmPassword = ''
|
||||
passwordFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime) => {
|
||||
if (!dateTime) return '未知'
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUserInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-profile {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
color: #409eff;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.user-profile {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user