feat(uniapp): 集成 Pinia 状态管理并实现用户认证流程

This commit is contained in:
augushong
2026-02-01 12:01:37 +08:00
parent ae6b3f1b67
commit 1a88ff286e
14 changed files with 242 additions and 5228 deletions

9
.gitignore vendored
View File

@@ -40,3 +40,12 @@ result.txt
test.php
/.env.prod
extend/base/common/command/curd/migrate_output.php
# source 目录:多端工程依赖与构建产物
/source/**/node_modules
/source/**/dist
/source/**/.hbuilderx
/source/**/.idea
/source/**/.vscode
.trae/documents/新增 source 目录与 uni-appCLI基础工程.md
.trae/documents/uni-app 增加 env_request_utils 与用户状态机制.md

View File

@@ -3,6 +3,9 @@
// !当前文件的内容应当与 /extend/base/admin/middleware.php 的内容一致,然后再根据实际情况设置
// 全局中间件定义文件
use think\middleware\AllowCrossDomain;
return [
// Session初始化
@@ -14,4 +17,6 @@ return [
// Csrf安全校验
\app\admin\middleware\CsrfMiddleware::class,
AllowCrossDomain::class,
];

View File

@@ -520,12 +520,12 @@ class AdminControllerBase extends BaseController
!in_array($currentController, $adminConfig['no_login_controller']) &&
!in_array($currentNode, $adminConfig['no_login_node'])
) {
empty($adminId) && $this->error('请先登录后台', [], __url('admin/login/index', ['back_url' => $back_url]));
empty($adminId) && $this->error('请先登录后台', [], __url('admin/login/index', ['back_url' => $back_url]), 40101);
// 判断是否登录过期
if ($expireTime !== true && time() > $expireTime) {
session('admin', null);
$this->error('登录已过期,请重新登录', [], __url('admin/login/index', ['back_url' => $back_url]));
$this->error('登录已过期,请重新登录', [], __url('admin/login/index', ['back_url' => $back_url]), 40102);
}
}

View File

@@ -0,0 +1,2 @@
legacy-peer-deps=true

File diff suppressed because it is too large Load Diff

View File

@@ -52,6 +52,7 @@
"@dcloudio/uni-mp-weixin": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-xhs": "3.0.0-4080720251210001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001",
"pinia": "2.1.7",
"uview-plus": "^3.3.65",
"ului.css": "0.0.1",
"vue": "^3.4.21",

View File

@@ -0,0 +1,23 @@
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export function omitEmpty(obj) {
const result = {}
if (!obj || typeof obj !== 'object') return result
Object.keys(obj).forEach((key) => {
const value = obj[key]
if (value === '' || value === null || value === undefined) return
result[key] = value
})
return result
}
export function toQueryString(params) {
const data = omitEmpty(params)
const parts = Object.keys(data).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
return parts.join('&')
}

View File

@@ -0,0 +1,5 @@
export const env = {
apiBaseUrl: 'http://127.0.0.1:8000',
timeout: 60000,
}

View File

@@ -4,9 +4,13 @@ import {
import App from "./App.vue";
import uviewPlus from 'uview-plus'
import './styles/uview.scss'
import { pinia } from './store'
import { useUserStore } from './store/user'
export function createApp() {
const app = createSSRApp(App);
app.use(uviewPlus);
app.use(pinia);
useUserStore(pinia).init();
return {
app,
};

View File

@@ -22,8 +22,8 @@
</template>
<script>
import { setToken } from '../../utils/auth'
import { request } from '../../utils/request'
import { useUserStore } from '../../store/user'
export default {
data() {
@@ -44,6 +44,7 @@ export default {
return
}
const userStore = useUserStore()
this.loading = true
try {
const res = await request({
@@ -63,7 +64,7 @@ export default {
return
}
setToken(token)
userStore.setToken(token)
uni.showToast({ title: '登录成功', icon: 'none' })
uni.navigateBack({

View File

@@ -18,18 +18,24 @@
</template>
<script>
import { clearToken, getToken } from '../../utils/auth'
import { request } from '../../utils/request'
import { useUserStore } from '../../store/user'
export default {
data() {
return {
token: '',
}
computed: {
userStore() {
return useUserStore()
},
token() {
return this.userStore.token
},
isLoggedIn() {
return this.userStore.isLoggedIn
},
},
onShow() {
this.token = getToken()
if (!this.token) {
this.userStore.init()
if (!this.isLoggedIn) {
this.goLogin()
}
},
@@ -38,7 +44,7 @@ export default {
uni.navigateTo({ url: '/pages/login/index' })
},
async logout() {
if (!this.token) {
if (!this.isLoggedIn) {
uni.showToast({ title: '当前未登录', icon: 'none' })
return
}
@@ -49,8 +55,7 @@ export default {
method: 'POST',
})
} finally {
clearToken()
this.token = ''
this.userStore.logout()
uni.showToast({ title: '已退出', icon: 'none' })
uni.switchTab({ url: '/pages/index/index' })
}

View File

@@ -0,0 +1,4 @@
import { createPinia } from 'pinia'
export const pinia = createPinia()

View File

@@ -0,0 +1,29 @@
import { defineStore } from 'pinia'
const TOKEN_KEY = 'ULTHON_ADMIN_TOKEN'
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
inited: false,
}),
getters: {
isLoggedIn: (state) => !!state.token,
},
actions: {
init() {
if (this.inited) return
this.token = uni.getStorageSync(TOKEN_KEY) || ''
this.inited = true
},
setToken(token) {
this.token = token || ''
uni.setStorageSync(TOKEN_KEY, this.token)
},
logout() {
this.token = ''
uni.removeStorageSync(TOKEN_KEY)
},
},
})

View File

@@ -1,18 +1,39 @@
import { clearToken, getToken } from './auth'
import { env } from '../config/env'
import { pinia } from '../store'
import { useUserStore } from '../store/user'
const BASE_URL = ''
const AUTH_REQUIRED_CODE = 40101
const AUTH_EXPIRED_CODE = 40102
function buildUrl(url) {
if (!url) return ''
if (/^https?:\/\//i.test(url)) return url
if (url.startsWith('/')) return `${BASE_URL}${url}`
return `${BASE_URL}/${url}`
const baseUrl = env.apiBaseUrl || ''
if (url.startsWith('/')) return `${baseUrl}${url}`
return `${baseUrl}/${url}`
}
function isLoginError(resData) {
const msg = (resData && resData.msg) || ''
const url = (resData && resData.url) || ''
return msg.includes('请先登录') || msg.includes('登录已过期') || url.includes('/admin/login')
function getCurrentRoutePath() {
try {
const pages = getCurrentPages()
const current = pages && pages[pages.length - 1]
const route = (current && (current.route || (current.$page && current.$page.fullPath))) || ''
return String(route)
} catch (e) {
return ''
}
}
function redirectToLoginIfNeeded() {
const route = getCurrentRoutePath()
if (route.includes('pages/login/index')) return
uni.navigateTo({
url: '/pages/login/index',
fail: () => {
uni.reLaunch({ url: '/pages/login/index' })
},
})
}
export function request(options) {
@@ -24,7 +45,9 @@ export function request(options) {
...(options.header || {}),
}
const token = getToken()
const userStore = useUserStore(pinia)
userStore.init()
const token = userStore.token
if (token && !header.Authorization) {
header.Authorization = `Bearer ${token}`
}
@@ -39,7 +62,7 @@ export function request(options) {
method,
data: options.data || {},
header,
timeout: options.timeout || 60000,
timeout: options.timeout || env.timeout || 60000,
success: (res) => {
const resData = res.data
@@ -61,9 +84,9 @@ export function request(options) {
return
}
if (isLoginError(resData)) {
clearToken()
uni.navigateTo({ url: '/pages/login/index' })
if (resData.code === AUTH_REQUIRED_CODE || resData.code === AUTH_EXPIRED_CODE) {
userStore.logout()
redirectToLoginIfNeeded()
}
uni.showToast({ title: resData.msg || '请求失败', icon: 'none' })
@@ -80,4 +103,3 @@ export function request(options) {
})
})
}