first commit

This commit is contained in:
2025-11-24 16:06:44 +08:00
commit e57d497751
165 changed files with 59349 additions and 0 deletions

163
src/stores/app.js Normal file
View File

@@ -0,0 +1,163 @@
export const useAppStore = defineStore('app', () => {
// 应用状态
const sidebarCollapsed = ref(false)
const mobileSidebarOpen = ref(false)
const theme = ref('light')
const language = ref('zh-CN')
const loading = ref(false)
const notifications = ref([])
const isMobile = ref(false)
// 计算属性:根据设备类型判断侧边栏状态
const sidebarVisible = computed(() => {
if (isMobile.value) {
return mobileSidebarOpen.value
}
return !sidebarCollapsed.value
})
// 切换侧边栏
const toggleSidebar = () => {
if (isMobile.value) {
mobileSidebarOpen.value = !mobileSidebarOpen.value
} else {
sidebarCollapsed.value = !sidebarCollapsed.value
localStorage.setItem('sidebarCollapsed', JSON.stringify(sidebarCollapsed.value))
}
}
// 设置侧边栏状态
const setSidebarCollapsed = (collapsed) => {
if (isMobile.value) {
mobileSidebarOpen.value = !collapsed
} else {
sidebarCollapsed.value = collapsed
localStorage.setItem('sidebarCollapsed', JSON.stringify(collapsed))
}
}
// 关闭移动端侧边栏
const closeMobileSidebar = () => {
if (isMobile.value) {
mobileSidebarOpen.value = false
}
}
// 设置移动端状态
const setMobileState = (mobile) => {
isMobile.value = mobile
if (mobile) {
mobileSidebarOpen.value = false
}
}
// 切换主题
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
localStorage.setItem('theme', theme.value)
}
// 设置主题
const setTheme = (newTheme) => {
theme.value = newTheme
localStorage.setItem('theme', newTheme)
}
// 设置语言
const setLanguage = (lang) => {
language.value = lang
localStorage.setItem('language', lang)
}
// 设置加载状态
const setLoading = (status) => {
loading.value = status
}
// 添加通知
const addNotification = (notification) => {
const id = Date.now().toString()
notifications.value.push({
id,
...notification,
timestamp: new Date()
})
// 自动移除通知5秒后
setTimeout(() => {
removeNotification(id)
}, 5000)
}
// 移除通知
const removeNotification = (id) => {
const index = notifications.value.findIndex(n => n.id === id)
if (index > -1) {
notifications.value.splice(index, 1)
}
}
// 清空所有通知
const clearNotifications = () => {
notifications.value = []
}
// 初始化应用配置
const initAppConfig = () => {
// 从localStorage恢复配置
const savedTheme = localStorage.getItem('theme')
const savedLanguage = localStorage.getItem('language')
const savedSidebarCollapsed = localStorage.getItem('sidebarCollapsed')
if (savedTheme) theme.value = savedTheme
if (savedLanguage) language.value = savedLanguage
if (savedSidebarCollapsed) sidebarCollapsed.value = JSON.parse(savedSidebarCollapsed)
// 检测移动端
const checkMobile = () => {
const mobile = window.innerWidth <= 768
setMobileState(mobile)
}
// 初始检测
checkMobile()
// 监听窗口大小变化
window.addEventListener('resize', checkMobile)
// 返回清理函数
return () => {
window.removeEventListener('resize', checkMobile)
}
}
return {
// 状态
sidebarCollapsed,
mobileSidebarOpen,
theme,
language,
loading,
notifications,
isMobile,
// 计算属性
sidebarVisible,
// 方法
toggleSidebar,
setSidebarCollapsed,
closeMobileSidebar,
setMobileState,
toggleTheme,
setTheme,
setLanguage,
setLoading,
addNotification,
removeNotification,
clearNotifications,
initAppConfig
}
})

11
src/stores/counter.js Normal file
View File

@@ -0,0 +1,11 @@
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

501
src/stores/user.js Normal file
View File

@@ -0,0 +1,501 @@
/**
* 用户状态管理 Store
*
* 后端响应格式说明:
* 所有API响应都被包装在标准格式中
* {
* "success": true,
* "data": { ... }, // 真正的数据在这里
* "message": "操作成功",
* "requestId": "",
* "timestamp": 1752469413
* }
*
* 使用示例:
*
* // 在组件中使用
* import { useUserStore } from '@/stores/user'
*
* const userStore = useUserStore()
*
* // 登录
* const loginData = {
* method: 'sms', // 或 'password'
* phone: '13800138000',
* code: '123456', // 短信登录时需要
* password: 'password123' // 密码登录时需要
* }
* const result = await userStore.login(loginData)
* if (result.success) {
* console.log('登录成功')
* // result.data 包含: { user, access_token, token_type, expires_in, login_method }
* }
*
* // 注册
* const registerData = {
* phone: '13800138000',
* password: 'password123',
* confirmPassword: 'password123',
* code: '123456'
* }
* const result = await userStore.register(registerData)
* if (result.success) {
* // result.data 包含注册成功的数据
* }
*
* // 发送验证码
* const result = await userStore.sendCode('13800138000', 'register')
* if (result.success) {
* // result.data 包含验证码发送结果
* }
*
* // 重置密码
* const resetData = {
* phone: '13800138000',
* newPassword: 'newpassword123',
* confirmNewPassword: 'newpassword123',
* code: '123456'
* }
* const result = await userStore.resetPassword(resetData)
* if (result.success) {
* // result.data 包含重置密码结果
* }
*
* // 获取用户信息
* const result = await userStore.fetchUserProfile()
* if (result.success) {
* // result.data 包含用户信息: { id, phone, created_at, updated_at }
* }
*
* // 修改密码
* const passwordData = {
* oldPassword: 'oldpassword123',
* newPassword: 'newpassword123',
* confirmNewPassword: 'newpassword123',
* code: '123456'
* }
* const result = await userStore.changePassword(passwordData)
* if (result.success) {
* // result.data 包含修改密码结果
* }
*
* // 登出
* userStore.logout()
*
* // 检查登录状态
* const isAuth = await userStore.checkAuth()
*
* // 响应式状态
* console.log(userStore.isLoggedIn) // 是否已登录
* console.log(userStore.userInfo) // 用户信息: { id, phone, created_at, updated_at }
* console.log(userStore.loading) // 加载状态
*/
import { userApi } from '@/api'
import router from '@/router'
import { authEventBus } from '@/utils/request'
import { clearLocalVersions, saveLocalVersions, VERSION_CONFIG, versionChecker } from '@/utils/version'
import { ElMessage } from 'element-plus'
export const useUserStore = defineStore('user', () => {
// 状态
const user = ref(null)
const accessToken = ref(localStorage.getItem('access_token') || '')
const tokenType = ref(localStorage.getItem('token_type') || 'Bearer')
const isAuthenticated = ref(false)
const loading = ref(false)
// 新增:初始化标志
const initialized = ref(false)
// 版本管理
const tokenVersion = ref(localStorage.getItem('token_version') || VERSION_CONFIG.TOKEN_VERSION)
const appVersion = ref(localStorage.getItem('app_version') || VERSION_CONFIG.APP_VERSION)
// 计算属性
const isLoggedIn = computed(() => {
return isAuthenticated.value && !!accessToken.value
})
const userInfo = computed(() => user.value)
// 新增:用户类型相关计算属性
const isAdmin = computed(() => {
return user.value?.user_type === 'admin'
})
const userType = computed(() => {
return user.value?.user_type || 'user'
})
// 新增:用户认证状态计算属性
const isCertified = computed(() => {
return user.value?.is_certified || false
})
// 检查用户信息是否完整
const isUserInfoComplete = computed(() => {
return user.value &&
user.value.id &&
user.value.phone &&
user.value.user_type !== undefined
})
// 强制刷新用户信息
const refreshUserProfile = async () => {
if (!accessToken.value) {
return { success: false, error: '未登录' }
}
loading.value = true
try {
const result = await fetchUserProfile()
return result
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
const hasRole = (role) => {
return userType.value === role
}
// 监听认证错误事件
const handleAuthError = (message) => {
console.log('用户store收到认证错误事件:', message)
logout()
}
// 处理版本退出登录事件
const handleVersionLogout = (event) => {
console.log('收到版本退出登录事件:', event.detail)
logout()
ElMessage.error('系统已更新,请重新登录')
router.push('/auth/login')
}
// 处理版本刷新事件
const handleVersionRefresh = (event) => {
console.log('收到版本刷新事件:', event.detail)
saveLocalVersions({ appVersion: VERSION_CONFIG.APP_VERSION })
ElMessage.success('应用已更新,正在刷新页面...')
setTimeout(() => {
window.location.reload()
}, 1500)
}
// 版本检查
const checkVersions = () => {
const currentTokenVersion = localStorage.getItem('token_version') || VERSION_CONFIG.TOKEN_VERSION
const currentAppVersion = localStorage.getItem('app_version') || VERSION_CONFIG.APP_VERSION
// 检查token版本
if (currentTokenVersion !== VERSION_CONFIG.TOKEN_VERSION) {
console.warn('Token版本不匹配需要重新登录', {
current: currentTokenVersion,
expected: VERSION_CONFIG.TOKEN_VERSION
})
// 清除所有认证相关数据
logout()
// 显示提示信息
ElMessage.error('系统已更新,请重新登录')
// 跳转到登录页面
router.push('/auth/login')
return false
}
// 检查应用版本
if (currentAppVersion !== VERSION_CONFIG.APP_VERSION) {
console.log('应用版本已更新,刷新页面', {
current: currentAppVersion,
expected: VERSION_CONFIG.APP_VERSION
})
// 更新本地存储的应用版本
saveLocalVersions({ appVersion: VERSION_CONFIG.APP_VERSION })
// 显示提示信息
ElMessage.success('应用已更新,正在刷新页面...')
// 延迟刷新页面
setTimeout(() => {
window.location.reload()
}, 1500)
return false
}
return true
}
// 登录
const login = async (loginData) => {
loading.value = true
try {
let response
if (loginData.method === 'sms') {
response = await userApi.loginWithSMS({
phone: loginData.phone,
code: loginData.code
})
} else {
response = await userApi.loginWithPassword({
phone: loginData.phone,
password: loginData.password
})
}
// 后端返回格式: { success: true, data: { user, access_token, token_type, ... }, message, ... }
// 真正的数据在 response.data 中
const responseData = response.data
// 保存token和用户信息
accessToken.value = responseData.access_token
tokenType.value = responseData.token_type
user.value = responseData.user
isAuthenticated.value = true
// 保存到localStorage
localStorage.setItem('access_token', responseData.access_token)
localStorage.setItem('token_type', responseData.token_type)
saveLocalVersions({ tokenVersion: VERSION_CONFIG.TOKEN_VERSION })
// 登录成功后,主动获取最新的用户信息以确保数据完整
try {
const profileResult = await fetchUserProfile()
if (profileResult.success) {
console.log('登录后用户信息更新成功')
// 如果获取到更完整的用户信息,更新用户数据
if (profileResult.data) {
user.value = { ...user.value, ...profileResult.data }
}
}
} catch (profileError) {
console.warn('登录后获取用户信息失败:', profileError)
// 即使获取用户信息失败,也不影响登录流程
}
return { success: true, data: responseData }
} catch (error) {
console.error('登录失败:', error)
return { success: false, error }
} finally {
loading.value = false
}
}
// 注册
const register = async (registerData) => {
loading.value = true
try {
const response = await userApi.register({
phone: registerData.phone,
password: registerData.password,
confirm_password: registerData.confirmPassword,
code: registerData.code
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 重置密码
const resetPassword = async (resetData) => {
loading.value = true
try {
const response = await userApi.resetPassword({
phone: resetData.phone,
new_password: resetData.newPassword,
confirm_new_password: resetData.confirmNewPassword,
code: resetData.code
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 发送验证码
const sendCode = async (phone, scene) => {
try {
const response = await userApi.sendCode({
phone,
scene
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
}
}
// 获取用户信息
const fetchUserProfile = async () => {
loading.value = true
try {
const response = await userApi.getProfile()
// 后端返回格式: { success: true, data: {...}, message, ... }
const userData = response.data
user.value = userData
isAuthenticated.value = true
// 记录认证状态变化
if (userData.is_certified !== undefined) {
console.log('用户认证状态:', userData.is_certified ? '已认证' : '未认证')
}
return { success: true, data: userData }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 修改密码
const changePassword = async (passwordData) => {
loading.value = true
try {
const response = await userApi.changePassword({
old_password: passwordData.oldPassword,
new_password: passwordData.newPassword,
confirm_new_password: passwordData.confirmNewPassword,
code: passwordData.code
})
// 后端返回格式: { success: true, data: {...}, message, ... }
return { success: true, data: response.data }
} catch (error) {
return { success: false, error }
} finally {
loading.value = false
}
}
// 登出
const logout = () => {
// 清除状态
user.value = null
accessToken.value = ''
tokenType.value = 'Bearer'
isAuthenticated.value = false
loading.value = false
// 清除localStorage
localStorage.removeItem('access_token')
localStorage.removeItem('token_type')
clearLocalVersions()
}
// 检查登录状态
const checkAuth = async () => {
if (!accessToken.value) {
return false
}
try {
const result = await fetchUserProfile()
return result.success
} catch (error) {
logout()
return false
}
}
// 初始化
const init = async () => {
// 监听认证错误事件
authEventBus.onAuthError(handleAuthError)
// 监听版本更新事件
window.addEventListener('version:logout', handleVersionLogout)
window.addEventListener('version:refresh', handleVersionRefresh)
// 进行版本检查
if (!checkVersions()) {
return
}
if (accessToken.value && !user.value) {
// 有token但无用户信息自动拉取
loading.value = true
try {
const result = await fetchUserProfile()
isAuthenticated.value = result.success
// 如果认证成功,启动版本检查器
if (result.success) {
versionChecker.startAutoCheck()
}
} catch {
isAuthenticated.value = false
logout()
} finally {
loading.value = false
initialized.value = true
}
} else {
// 如果已经认证,启动版本检查器
if (isAuthenticated.value) {
versionChecker.startAutoCheck()
}
initialized.value = true
}
}
// 清理
const cleanup = () => {
// 停止版本检查器
versionChecker.stopAutoCheck()
// 移除事件监听器
window.removeEventListener('version:logout', handleVersionLogout)
window.removeEventListener('version:refresh', handleVersionRefresh)
}
return {
// 状态
user,
accessToken,
tokenType,
isAuthenticated,
loading,
initialized, // 新增
// 计算属性
isLoggedIn,
userInfo,
isAdmin,
userType,
isCertified, // 新增
isUserInfoComplete, // 新增
hasRole,
// 方法
login,
register,
resetPassword,
sendCode,
fetchUserProfile,
changePassword,
logout,
checkAuth,
init,
cleanup,
refreshUserProfile // 新增
}
})