import { defineStore } from 'pinia' import { computed, ref } from 'vue' import { AUTH_API, TOKEN_KEY, REFRESH_TOKEN_KEY, USER_INFO_KEY, PAGE_PATHS } from '@/config/api' import { request, parseResponseJson } from '@/utils/request' import { buildMiniEncryptedLoginBody } from '@/utils/loginRsa' export const useUserStore = defineStore('user', () => { const token = ref('') const refreshToken = ref('') const userInfo = ref(null) const isLoading = ref(false) const menuAll = ref(false) const menuCodes = ref([]) function normalizeUserInfo(raw) { if (!raw || typeof raw !== 'object') return raw || null const avatarUrl = raw.avatarUrl != null && String(raw.avatarUrl).trim() ? String(raw.avatarUrl).trim() : raw.avatar_url != null && String(raw.avatar_url).trim() ? String(raw.avatar_url).trim() : '' return { ...raw, avatarUrl } } const isLoggedIn = computed(() => !!token.value && !!userInfo.value) const userRole = computed(() => userInfo.value?.role || '') /** 登录成功默认进入工作台 Tab */ const homePath = computed(() => PAGE_PATHS.workbench) function checkLoginStatus() { const savedToken = uni.getStorageSync(TOKEN_KEY) const savedUserInfo = uni.getStorageSync(USER_INFO_KEY) const savedRefreshToken = uni.getStorageSync(REFRESH_TOKEN_KEY) const normalizedToken = typeof savedToken === 'string' ? savedToken.trim() : savedToken ? String(savedToken).trim() : '' let normalizedUserInfo = null if (typeof savedUserInfo === 'string') { try { normalizedUserInfo = normalizeUserInfo(JSON.parse(savedUserInfo)) } catch (error) { normalizedUserInfo = null } } else if (savedUserInfo && typeof savedUserInfo === 'object') { normalizedUserInfo = normalizeUserInfo(savedUserInfo) } if (normalizedToken && normalizedUserInfo) { token.value = normalizedToken refreshToken.value = typeof savedRefreshToken === 'string' ? savedRefreshToken : '' userInfo.value = normalizedUserInfo return } token.value = '' refreshToken.value = '' userInfo.value = null } function setLoginInfo(data) { token.value = data.token || '' refreshToken.value = data.refreshToken || '' userInfo.value = normalizeUserInfo(data.userInfo || null) uni.setStorageSync(TOKEN_KEY, token.value) uni.setStorageSync(REFRESH_TOKEN_KEY, refreshToken.value) uni.setStorageSync(USER_INFO_KEY, userInfo.value) } async function fetchMenuCodes() { if (!token.value || !String(token.value).trim()) { menuAll.value = false menuCodes.value = [] return } try { const res = await request({ url: AUTH_API.MENU_CODES, method: 'GET' }) const result = parseResponseJson(res) if (!result || result.code !== 200 || !result.data) { menuAll.value = false menuCodes.value = [] return } const data = result.data menuAll.value = !!(data.all || data.isSuperAdmin) menuCodes.value = Array.isArray(data.menuCodes) ? data.menuCodes.map((c) => String(c || '').trim()).filter(Boolean) : [] } catch { menuAll.value = false menuCodes.value = [] } } async function login(params) { isLoading.value = true try { const encryptedBody = await buildMiniEncryptedLoginBody(params.username, params.password) const res = await request({ url: AUTH_API.LOGIN, method: 'POST', data: encryptedBody }) const result = parseResponseJson(res) if (result && result.code === 200 && result.data) { setLoginInfo({ token: result.data.token, refreshToken: result.data.refreshToken, userInfo: result.data.userInfo }) await fetchMenuCodes() return true } uni.showToast({ title: (result && result.message) || '登录失败', icon: 'none' }) return false } catch (error) { uni.showToast({ title: (error && error.message) || '网络请求失败', icon: 'none' }) return false } finally { isLoading.value = false } } /** 手机号快捷登录;loading 由登录页控制,此处不改 isLoading */ async function wechatLogin(params) { try { const res = await request({ url: AUTH_API.WECHAT_LOGIN, method: 'POST', data: { loginCode: params.loginCode, phoneCode: params.phoneCode } }) const result = parseResponseJson(res) if (result && result.code === 200 && result.data) { setLoginInfo({ token: result.data.token, refreshToken: result.data.refreshToken, userInfo: result.data.userInfo }) await fetchMenuCodes() return true } uni.showToast({ title: (result && result.message) || '登录失败', icon: 'none' }) return false } catch (e) { uni.showToast({ title: (e && e.message) || '登录失败', icon: 'none' }) return false } } /** 网页扫码:通知已扫;不写本地登录态 */ async function webQrScanned(sessionId) { const res = await request({ url: AUTH_API.WEB_QR_SCANNED, method: 'POST', data: { sessionId } }) const result = parseResponseJson(res) return !!(result && result.code === 200) } /** 网页扫码:手机号授权确认;不写本地登录态、不跳转工作台 */ async function webQrConfirm(params) { const res = await request({ url: AUTH_API.WEB_QR_CONFIRM, method: 'POST', data: { sessionId: params.sessionId, loginCode: params.loginCode, phoneCode: params.phoneCode } }) const result = parseResponseJson(res) if (result && result.code === 200) { return { ok: true, message: (result.data && result.data.message) || '电脑端即将登录,可关闭本页' } } return { ok: false, message: (result && result.message) || '授权失败' } } function clearLoginInfo() { token.value = '' refreshToken.value = '' userInfo.value = null uni.removeStorageSync(TOKEN_KEY) uni.removeStorageSync(REFRESH_TOKEN_KEY) uni.removeStorageSync(USER_INFO_KEY) menuAll.value = false menuCodes.value = [] } async function logout() { try { await request({ url: AUTH_API.LOGOUT, method: 'POST' }) } catch (error) { // ignore } finally { clearLoginInfo() uni.reLaunch({ url: PAGE_PATHS.login }) } } function navigateToHome() { uni.reLaunch({ url: homePath.value }) } function updateUserInfo(info) { if (!userInfo.value || !info) return userInfo.value = normalizeUserInfo({ ...userInfo.value, ...info }) uni.setStorageSync(USER_INFO_KEY, userInfo.value) } async function refreshUserInfo() { if (!token.value || !String(token.value).trim()) return try { const res = await request({ url: AUTH_API.CURRENT_USER, method: 'GET' }) const result = parseResponseJson(res) if (result.code !== 200 || !result.data) return userInfo.value = normalizeUserInfo(result.data) uni.setStorageSync(USER_INFO_KEY, userInfo.value) } catch { // ignore } } return { token, refreshToken, userInfo, isLoading, menuAll, menuCodes, isLoggedIn, userRole, homePath, checkLoginStatus, login, wechatLogin, webQrScanned, webQrConfirm, logout, clearLoginInfo, navigateToHome, updateUserInfo, refreshUserInfo, fetchMenuCodes } })