feat(auth): 实现系统登录和权限控制功能

- 新增登录页面和相关逻辑
- 添加全局权限控制守卫
- 更新订单列表和详情页面,优化用户体验- 重构部分代码以支持新功能
master
chenhao 2025-08-28 14:31:04 +08:00
parent 781d598ae7
commit 0752efd3ff
8 changed files with 87 additions and 18 deletions

3
components.d.ts vendored
View File

@ -10,8 +10,11 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
VanButton: typeof import('vant/es')['Button'] VanButton: typeof import('vant/es')['Button']
VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanEmpty: typeof import('vant/es')['Empty'] VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field'] VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
VanIcon: typeof import('vant/es')['Icon'] VanIcon: typeof import('vant/es')['Icon']
VanList: typeof import('vant/es')['List'] VanList: typeof import('vant/es')['List']
VanLoading: typeof import('vant/es')['Loading'] VanLoading: typeof import('vant/es')['Loading']

View File

@ -38,7 +38,17 @@ export const submitApproval = (params: any): Promise<AxiosResponse<ApiResponse<a
// 将所有参数添加到FormData中 // 将所有参数添加到FormData中
Object.keys(params).forEach(key => { Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null) { if (params[key] !== undefined && params[key] !== null) {
formData.append(key, params[key].toString()) // 特殊处理variables参数它应该是一个对象
if (key === 'variables' && typeof params[key] === 'object' && params[key] !== null) {
// 将variables对象的每个属性单独添加到FormData中
Object.keys(params[key]).forEach(variableKey => {
if (params[key][variableKey] !== undefined && params[key][variableKey] !== null) {
formData.append(`variables[${variableKey}]`, params[key][variableKey].toString())
}
})
} else {
formData.append(key, params[key].toString())
}
} }
}) })

View File

@ -1,7 +1,7 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { createPinia } from 'pinia' import { store, initStores } from './store'
// Vant样式 // Vant样式
import 'vant/lib/index.css' import 'vant/lib/index.css'
@ -13,7 +13,10 @@ import '@/styles/index.scss'
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) app.use(store)
app.use(router) app.use(router)
// 初始化 stores
initStores()
app.mount('#app') app.mount('#app')

View File

@ -1,17 +1,29 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/store/auth'
import { store } from '@/store'
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
path: '/', path: '/',
redirect: '/list' redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue'),
meta: {
title: '系统登录',
requiresAuth: false
}
}, },
{ {
path: '/list', path: '/list',
name: 'OrderList', name: 'OrderList',
component: () => import('@/views/List/index.vue'), component: () => import('@/views/List/index.vue'),
meta: { meta: {
title: '订单列表' title: '订单列表',
requiresAuth: true
} }
}, },
{ {
@ -19,7 +31,8 @@ const routes: RouteRecordRaw[] = [
name: 'OrderDetail', name: 'OrderDetail',
component: () => import('@/views/Detail/index.vue'), component: () => import('@/views/Detail/index.vue'),
meta: { meta: {
title: '订单详情' title: '订单详情',
requiresAuth: true
} }
} }
] ]
@ -38,7 +51,26 @@ router.beforeEach((to, from, next) => {
if (to.meta?.title) { if (to.meta?.title) {
document.title = to.meta.title as string document.title = to.meta.title as string
} }
next()
// 检查是否需要认证
const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false)
// 获取认证状态
const authStore = useAuthStore(store)
const isAuthenticated = authStore.isAuthenticated || localStorage.getItem('isAuthenticated') === 'true'
// 如果需要认证但未登录,重定向到登录页
if (requiresAuth && !isAuthenticated) {
next('/login')
}
// 如果已登录且访问登录页,重定向到列表页
else if (to.path === '/login' && isAuthenticated) {
next('/list')
}
// 其他情况正常跳转
else {
next()
}
}) })
export default router export default router

View File

@ -6,6 +6,13 @@ export interface ApiResponse<T = any> {
total?: number total?: number
} }
// 登录参数类型
export interface LoginParams {
username: string
password: string
rememberMe?: boolean
}
// 订单状态类型 // 订单状态类型
export type OrderStatus = '0' | '1' | '2' // 待审批、已审批、已拒绝 export type OrderStatus = '0' | '1' | '2' // 待审批、已审批、已拒绝

View File

@ -54,7 +54,9 @@ class HttpClient {
switch (status) { switch (status) {
case 401: case 401:
message = '未授权,请重新登录' message = '未授权,请重新登录'
// 这里可以处理登录跳转逻辑 // 清除认证信息并跳转到登录页
localStorage.removeItem('isAuthenticated')
window.location.href = '/login'
break break
case 403: case 403:
message = '拒绝访问' message = '拒绝访问'
@ -62,6 +64,12 @@ class HttpClient {
case 404: case 404:
message = '请求地址不存在' message = '请求地址不存在'
break break
case 302:
// 处理302重定向到登录页
message = '会话已过期,请重新登录'
localStorage.removeItem('isAuthenticated')
window.location.href = '/login'
break
case 500: case 500:
message = '服务器内部错误' message = '服务器内部错误'
break break

View File

@ -51,11 +51,11 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">BG</span> <span class="label">BG</span>
<span class="value">{{ currentOrderInfo.bgProperty || '' }}</span> <span class="value">{{ currentOrderInfo.bgPropertyDesc || '' }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">行业</span> <span class="label">行业</span>
<span class="value">{{ currentOrderInfo.industryType || '' }}</span> <span class="value">{{ currentOrderInfo.industryTypeDesc || '' }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">代表处</span> <span class="label">代表处</span>
@ -83,7 +83,7 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">币种</span> <span class="label">币种</span>
<span class="value">{{ currentOrderInfo.currencyType || '' }}</span> <span class="value">{{ currentOrderInfo.currencyTypeDesc || '' }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
@ -100,11 +100,11 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">公司直发</span> <span class="label">公司直发</span>
<span class="value">{{ currentOrderInfo.companyDelivery}}</span> <span class="value">{{ currentOrderInfo.companyDeliveryDesc}}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">下单通路</span> <span class="label">下单通路</span>
<span class="value">{{ currentOrderInfo.orderChannel}}</span> <span class="value">{{ currentOrderInfo.orderChannelDesc}}</span>
</div><div class="info-item"> </div><div class="info-item">
<span class="label">供货商</span> <span class="label">供货商</span>
<span class="value">{{ currentOrderInfo.supplier}}</span> <span class="value">{{ currentOrderInfo.supplier}}</span>
@ -113,7 +113,7 @@
<span class="value">{{ currentOrderInfo.partnerName}}</span> <span class="value">{{ currentOrderInfo.partnerName}}</span>
</div><div class="info-item"> </div><div class="info-item">
<span class="label">进货商类型</span> <span class="label">进货商类型</span>
<span class="value">{{ currentOrderInfo.level}}</span> <span class="value">{{ currentOrderInfo.levelDesc}}</span>
</div><div class="info-item"> </div><div class="info-item">
<span class="label">进货商联系人</span> <span class="label">进货商联系人</span>
<span class="value">{{ currentOrderInfo.partnerUserName}}</span> <span class="value">{{ currentOrderInfo.partnerUserName}}</span>
@ -579,8 +579,14 @@ const submitApproval = async () => {
showSuccessToast(currentApprovalStatus.value === 0 ? '驳回成功' : '审批通过') showSuccessToast(currentApprovalStatus.value === 0 ? '驳回成功' : '审批通过')
approvalDialogVisible.value = false approvalDialogVisible.value = false
// //
await orderStore.fetchOrderDetail(route.params.id as string) if (currentApprovalStatus.value !== 0) {
//
router.push('/list')
} else {
//
await orderStore.fetchOrderDetail(route.params.id as string)
}
} catch (error) { } catch (error) {
console.error('提交审批失败:', error) console.error('提交审批失败:', error)
showToast('提交审批失败') showToast('提交审批失败')

View File

@ -20,8 +20,8 @@
<div v-for="order in orderList" :key="order.id" class="order-item" @click="goToDetail(order.id)"> <div v-for="order in orderList" :key="order.id" class="order-item" @click="goToDetail(order.id)">
<div class="order-header"> <div class="order-header">
<div class="order-code">{{ order.orderCode }}</div> <div class="order-code">{{ order.orderCode }}</div>
<div class="status-tag" :class="getStatusClass(order.orderStatus)"> <div class="status-tag pending">
{{ formatOrderStatus(order.orderStatus) }} 待审批
</div> </div>
</div> </div>