初始化=商城+金融,用于演示.

This commit is contained in:
2025-11-28 16:43:16 +08:00
commit 08580b07ad
117 changed files with 20012 additions and 0 deletions

20
src/store/index.ts Normal file
View File

@@ -0,0 +1,20 @@
import { createPinia, setActivePinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化
const store = createPinia()
store.use(
createPersistedState({
storage: {
getItem: uni.getStorageSync,
setItem: uni.setStorageSync,
},
}),
)
// 立即激活 Pinia 实例, 这样即使在 app.use(store)之前调用 store 也能正常工作 解决APP端白屏问题
setActivePinia(store)
export default store
// 模块统一导出
export * from './token'
export * from './user'

290
src/store/token.ts Normal file
View File

@@ -0,0 +1,290 @@
import type {
ILoginForm,
} from '@/api/login'
import type { IAuthLoginRes } from '@/api/types/login'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue' // 修复:导入 computed
import {
login as _login,
logout as _logout,
refreshToken as _refreshToken,
wxLogin as _wxLogin,
getWxCode,
} from '@/api/login'
import { isDoubleTokenRes, isSingleTokenRes } from '@/api/types/login'
import { isDoubleTokenMode } from '@/utils'
import { useUserStore } from './user'
// 初始化状态
const tokenInfoState = isDoubleTokenMode
? {
accessToken: '',
accessExpiresIn: 0,
refreshToken: '',
refreshExpiresIn: 0,
}
: {
token: '',
expiresIn: 0,
}
export const useTokenStore = defineStore(
'token',
() => {
// 定义用户信息
const tokenInfo = ref<IAuthLoginRes>({ ...tokenInfoState })
// 设置用户信息
const setTokenInfo = (val: IAuthLoginRes) => {
tokenInfo.value = val
// 计算并存储过期时间
const now = Date.now()
if (isSingleTokenRes(val)) {
// 单token模式
const expireTime = now + val.expiresIn * 1000
uni.setStorageSync('accessTokenExpireTime', expireTime)
}
else if (isDoubleTokenRes(val)) {
// 双token模式
const accessExpireTime = now + val.accessExpiresIn * 1000
const refreshExpireTime = now + val.refreshExpiresIn * 1000
uni.setStorageSync('accessTokenExpireTime', accessExpireTime)
uni.setStorageSync('refreshTokenExpireTime', refreshExpireTime)
}
}
/**
* 判断token是否过期
*/
const isTokenExpired = computed(() => {
if (!tokenInfo.value) {
return true
}
const now = Date.now()
const expireTime = uni.getStorageSync('accessTokenExpireTime')
if (!expireTime)
return true
return now >= expireTime
})
/**
* 判断refreshToken是否过期
*/
const isRefreshTokenExpired = computed(() => {
if (!isDoubleTokenMode)
return true
const now = Date.now()
const refreshExpireTime = uni.getStorageSync('refreshTokenExpireTime')
if (!refreshExpireTime)
return true
return now >= refreshExpireTime
})
/**
* 登录成功后处理逻辑
* @param tokenInfo 登录返回的token信息
*/
async function _postLogin(tokenInfo: IAuthLoginRes) {
setTokenInfo(tokenInfo)
const userStore = useUserStore()
await userStore.fetchUserInfo()
}
/**
* 用户登录
* 有的时候后端会用一个接口返回token和用户信息有的时候会分开2个接口一个获取token一个获取用户信息
* 各有利弊看业务场景和系统复杂度这里使用2个接口返回的来模拟
* @param loginForm 登录参数
* @returns 登录结果
*/
const login = async (loginForm: ILoginForm) => {
try {
const res = await _login(loginForm)
console.log('普通登录-res: ', res)
await _postLogin(res)
uni.showToast({
title: '登录成功',
icon: 'success',
})
return res
}
catch (error) {
console.error('登录失败:', error)
uni.showToast({
title: '登录失败,请重试',
icon: 'error',
})
throw error
}
}
/**
* 微信登录
* 有的时候后端会用一个接口返回token和用户信息有的时候会分开2个接口一个获取token一个获取用户信息
* 各有利弊看业务场景和系统复杂度这里使用2个接口返回的来模拟
* @returns 登录结果
*/
const wxLogin = async () => {
try {
// 获取微信小程序登录的code
const code = await getWxCode()
console.log('微信登录-code: ', code)
const res = await _wxLogin(code)
console.log('微信登录-res: ', res)
await _postLogin(res)
uni.showToast({
title: '登录成功',
icon: 'success',
})
return res
}
catch (error) {
console.error('微信登录失败:', error)
uni.showToast({
title: '微信登录失败,请重试',
icon: 'error',
})
throw error
}
}
/**
* 退出登录 并 删除用户信息
*/
const logout = async () => {
try {
// TODO 实现自己的退出登录逻辑
await _logout()
}
catch (error) {
console.error('退出登录失败:', error)
}
finally {
// 无论成功失败都需要清除本地token信息
// 清除存储的过期时间
uni.removeStorageSync('accessTokenExpireTime')
uni.removeStorageSync('refreshTokenExpireTime')
console.log('退出登录-清除用户信息')
tokenInfo.value = { ...tokenInfoState }
uni.removeStorageSync('token')
const userStore = useUserStore()
userStore.clearUserInfo()
}
}
/**
* 刷新token
* @returns 刷新结果
*/
const refreshToken = async () => {
if (!isDoubleTokenMode) {
console.error('单token模式不支持刷新token')
throw new Error('单token模式不支持刷新token')
}
try {
// 安全检查确保refreshToken存在
if (!isDoubleTokenRes(tokenInfo.value) || !tokenInfo.value.refreshToken) {
throw new Error('无效的refreshToken')
}
const refreshToken = tokenInfo.value.refreshToken
const res = await _refreshToken(refreshToken)
console.log('刷新token-res: ', res)
setTokenInfo(res)
return res
}
catch (error) {
console.error('刷新token失败:', error)
throw error
}
}
/**
* 获取有效的token
* 注意在computed中不直接调用异步函数只做状态判断
* 实际的刷新操作应由调用方处理
*/
const getValidToken = computed(() => {
// token已过期返回空
if (isTokenExpired.value) {
return ''
}
if (!isDoubleTokenMode) {
return isSingleTokenRes(tokenInfo.value) ? tokenInfo.value.token : ''
}
else {
return isDoubleTokenRes(tokenInfo.value) ? tokenInfo.value.accessToken : ''
}
})
/**
* 检查是否有登录信息不考虑token是否过期
*/
const hasLoginInfo = computed(() => {
if (!tokenInfo.value) {
return false
}
if (isDoubleTokenMode) {
return isDoubleTokenRes(tokenInfo.value) && !!tokenInfo.value.accessToken
}
else {
return isSingleTokenRes(tokenInfo.value) && !!tokenInfo.value.token
}
})
/**
* 检查是否已登录且token有效
*/
const hasValidLogin = computed(() => {
console.log('hasValidLogin', hasLoginInfo.value, !isTokenExpired.value)
return hasLoginInfo.value && !isTokenExpired.value
})
/**
* 尝试获取有效的token如果过期且可刷新则刷新token
* @returns 有效的token或空字符串
*/
const tryGetValidToken = async (): Promise<string> => {
if (!getValidToken.value && isDoubleTokenMode && !isRefreshTokenExpired.value) {
try {
await refreshToken()
return getValidToken.value
}
catch (error) {
console.error('尝试刷新token失败:', error)
return ''
}
}
return getValidToken.value
}
return {
// 核心API方法
login,
wxLogin,
logout,
// 认证状态判断(最常用的)
hasLogin: hasValidLogin,
// 内部系统使用的方法
refreshToken,
tryGetValidToken,
validToken: getValidToken,
// 调试或特殊场景可能需要直接访问的信息
tokenInfo,
setTokenInfo,
}
},
{
// 添加持久化配置确保刷新页面后token信息不丢失
persist: true,
},
)

61
src/store/user.ts Normal file
View File

@@ -0,0 +1,61 @@
import type { IUserInfoRes } from '@/api/types/login'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import {
getUserInfo,
} from '@/api/login'
// 初始化状态
const userInfoState: IUserInfoRes = {
userId: -1,
username: '',
nickname: '',
avatar: '/static/images/default-avatar.png',
}
export const useUserStore = defineStore(
'user',
() => {
// 定义用户信息
const userInfo = ref<IUserInfoRes>({ ...userInfoState })
// 设置用户信息
const setUserInfo = (val: IUserInfoRes) => {
console.log('设置用户信息', val)
// 若头像为空 则使用默认头像
if (!val.avatar) {
val.avatar = userInfoState.avatar
}
userInfo.value = val
}
const setUserAvatar = (avatar: string) => {
userInfo.value.avatar = avatar
console.log('设置用户头像', avatar)
console.log('userInfo', userInfo.value)
}
// 删除用户信息
const clearUserInfo = () => {
userInfo.value = { ...userInfoState }
uni.removeStorageSync('user')
}
/**
* 获取用户信息
*/
const fetchUserInfo = async () => {
const res = await getUserInfo()
setUserInfo(res)
return res
}
return {
userInfo,
clearUserInfo,
fetchUserInfo,
setUserInfo,
setUserAvatar,
}
},
{
persist: true,
},
)