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