diff --git a/rc_autoplc_front/src/api/tb/recorddeal.ts b/rc_autoplc_front/src/api/tb/recorddeal.ts new file mode 100644 index 0000000..1d0e1a9 --- /dev/null +++ b/rc_autoplc_front/src/api/tb/recorddeal.ts @@ -0,0 +1,46 @@ +import request from '@/utils/request' + +export function recordDealadd(data: any) { + return request({ + url: '/recordDeal/add', + method: 'post', + data, + }) +} + +export function recordDealdel(id: string | number) { + return request({ + url: `/recordDeal/del/${id}`, + method: 'delete', + }) +} + +export function recordDealbyid(id: string | number) { + return request({ + url: `/recordDeal/getById/${id}`, + method: 'get', + }) +} + +export function recordDeallistByRecordId(recordId: string | number) { + return request({ + url: `/recordDeal/listByRecordId/${recordId}`, + method: 'get', + }) +} + +export function recordDeallist(data: any) { + return request({ + url: '/recordDeal/listPage', + method: 'get', + params: data, + }) +} + +export function recordDealupd(data: any) { + return request({ + url: '/recordDeal/update', + method: 'put', + data, + }) +} diff --git a/rc_autoplc_front/src/api/tb/recordinfo.ts b/rc_autoplc_front/src/api/tb/recordinfo.ts new file mode 100644 index 0000000..e85b6e2 --- /dev/null +++ b/rc_autoplc_front/src/api/tb/recordinfo.ts @@ -0,0 +1,39 @@ +import request from '@/utils/request' + +export function recordInfoadd(data: any) { + return request({ + url: '/recordInfo/add', + method: 'post', + data, + }) +} + +export function recordInfodel(id: string | number) { + return request({ + url: `/recordInfo/del/${id}`, + method: 'delete', + }) +} + +export function recordInfobyid(id: string | number) { + return request({ + url: `/recordInfo/getById/${id}`, + method: 'get', + }) +} + +export function recordInfolist(data: any) { + return request({ + url: '/recordInfo/listPage', + method: 'get', + params: data, + }) +} + +export function recordInfoupd(data: any) { + return request({ + url: '/recordInfo/update', + method: 'put', + data, + }) +} diff --git a/rc_autoplc_front/src/directives/permission.ts b/rc_autoplc_front/src/directives/permission.ts new file mode 100644 index 0000000..09fefa4 --- /dev/null +++ b/rc_autoplc_front/src/directives/permission.ts @@ -0,0 +1,26 @@ +import type { App, DirectiveBinding } from 'vue' +import { useAuthStore } from '@/stores/auth' + +function hasPermission(value: unknown): boolean { + const authStore = useAuthStore() + const codes = Array.isArray(value) ? value : [value] + const normalized = codes.filter((code): code is string => typeof code === 'string' && code.length > 0) + + if (!normalized.length) return true + return authStore.hasAnyPermission(normalized) +} + +export function setupPermissionDirective(app: App) { + app.directive('permission', { + mounted(el: HTMLElement, binding: DirectiveBinding) { + if (!hasPermission(binding.value)) { + el.parentNode?.removeChild(el) + } + }, + updated(el: HTMLElement, binding: DirectiveBinding) { + if (!hasPermission(binding.value)) { + el.parentNode?.removeChild(el) + } + }, + }) +} diff --git a/rc_autoplc_front/src/main.ts b/rc_autoplc_front/src/main.ts index ce0adbf..bb25fdf 100644 --- a/rc_autoplc_front/src/main.ts +++ b/rc_autoplc_front/src/main.ts @@ -7,10 +7,10 @@ import router from './router' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import zhCn from 'element-plus/es/locale/lang/zh-cn' +import { setupPermissionDirective } from '@/directives/permission' const app = createApp(App) -// 创建 Pinia 实例并配置持久化插件 const pinia = createPinia() pinia.use(piniaPluginPersistedstate) @@ -19,5 +19,6 @@ app.use(ElementPlus, { }) app.use(pinia) app.use(router) +setupPermissionDirective(app) app.mount('#app') diff --git a/rc_autoplc_front/src/router/index.ts b/rc_autoplc_front/src/router/index.ts index 2ebc19d..216f6be 100644 --- a/rc_autoplc_front/src/router/index.ts +++ b/rc_autoplc_front/src/router/index.ts @@ -1,9 +1,17 @@ -import { createRouter, createWebHashHistory } from 'vue-router' +import { createRouter, createWebHistory } from 'vue-router' import { useAuthStore } from '@/stores/auth' import { ElMessage } from 'element-plus' +declare module 'vue-router' { + interface RouteMeta { + requiresAuth?: boolean + permission?: string | string[] + hidden?: boolean + } +} + const router = createRouter({ - history: createWebHashHistory(import.meta.env.BASE_URL), + history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/login', @@ -29,81 +37,83 @@ const router = createRouter({ path: '/user', name: 'user', component: () => import('../views/user/index.vue'), + meta: { permission: 'sys:user' }, }, { path: '/role', name: 'role', component: () => import('../views/role/index.vue'), + meta: { permission: 'sys:role' }, }, { path: '/department', name: 'department', component: () => import('../views/department/index.vue'), + meta: { permission: 'sys:department' }, }, { path: '/permission', name: 'permission', component: () => import('../views/permission/index.vue'), + meta: { permission: 'sys:permission' }, }, - { - path: '/position', - name: 'position', - component: () => import('../views/position/index.vue'), - }, + { path: '/manage-log', name: 'manage-log', component: () => import('../views/manage-log/index.vue'), + meta: { permission: 'sys:managelog' }, }, - { - path: '/user-role', - name: 'user-role', - component: () => import('../views/user-role/index.vue'), - }, + { path: '/island-info', name: 'island-info', component: () => import('../views/islandInfo/index.vue'), + meta: { permission: 'data:island' }, }, { path: '/devparam', name: 'devparam', component: () => import('../views/devparam/index.vue'), + meta: { permission: 'data:devparam' }, }, { path: '/devinfo', name: 'devinfo', component: () => import('../views/devinfo/index.vue'), + meta: { permission: 'data:dev' }, }, { path: '/plc-devinfo', name: 'plc-devinfo', component: () => import('../views/devinfo/plc.vue'), + meta: { permission: 'data:plc' }, }, { path: '/goods-info', name: 'goods-info', component: () => import('../views/goods/index.vue'), + meta: { permission: 'tb:goodrecord' }, }, - { - path: '/flow-info', - name: 'flow-info', - component: () => import('../views/flowinfo/index.vue'), - }, + { path: '/step-info', name: 'step-info', component: () => import('../views/stepinfo/index.vue'), + meta: { permission: 'data:sop' }, }, - { - path: '/plc-device-control', - name: 'plc-device-control', - component: () => import('../views/plcdevicecontrol/index.vue'), - }, + { path: '/sample-injection', name: 'sample-injection', component: () => import('../views/sampleinjection/index.vue'), + meta: { permission: 'tb:goodcontrol' }, + }, + { + path: '/record-info', + name: 'record-info', + component: () => import('../views/recordinfo/index.vue'), + meta: { permission: 'tb:exceptionrecord' }, }, ], }, @@ -115,33 +125,40 @@ router.beforeEach((to, _from, next) => { const authStore = useAuthStore() const token = authStore.getToken() - // 如果访问登录页 if (to.path === '/login') { - // 已登录状态下访问登录页,自动跳转到首页 if (token) { next('/home') ElMessage.success('已登录,自动跳转') } else { - // 未登录,正常进入登录页 next() } return } - // 访问需要认证的页面,但未登录(无token) if (to.meta.requiresAuth && !token) { ElMessage.warning('请先登录') next('/login') return } - // 如果访问根路径,重定向到首页 + const permission = to.meta.permission + if (permission) { + const allowed = Array.isArray(permission) + ? authStore.hasAnyPermission(permission) + : authStore.hasPermission(permission) + + if (!allowed) { + ElMessage.warning('暂无访问权限') + next('/home') + return + } + } + if (to.path === '/') { next('/home') return } - // 已登录且访问合法页面,正常放行 next() }) diff --git a/rc_autoplc_front/src/stores/auth.ts b/rc_autoplc_front/src/stores/auth.ts index c2d911f..e85e45e 100644 --- a/rc_autoplc_front/src/stores/auth.ts +++ b/rc_autoplc_front/src/stores/auth.ts @@ -1,8 +1,21 @@ -import { ref } from 'vue' +import { computed, ref } from 'vue' import { defineStore } from 'pinia' +export interface AuthUserInfo { + username?: string + token?: string + roles?: string[] + permissions?: string[] + [key: string]: any +} + +export type PermissionCode = string + export const useAuthStore = defineStore('auth', () => { const token = ref('') + const userInfo = ref(null) + + const permissions = computed(() => userInfo.value?.permissions || []) function setToken(newToken: string) { token.value = newToken @@ -12,15 +25,61 @@ export const useAuthStore = defineStore('auth', () => { return token.value } - function removeToken() { + function setUserInfo(info: AuthUserInfo | null) { + userInfo.value = info + if (info?.token) { + token.value = info.token + } + } + + function getUserInfo(): AuthUserInfo | null { + return userInfo.value + } + + function setPermissions(nextPermissions: PermissionCode[]) { + if (!userInfo.value) { + userInfo.value = { permissions: nextPermissions } + return + } + userInfo.value.permissions = nextPermissions + } + + function getPermissions(): PermissionCode[] { + return permissions.value + } + + function hasPermission(code: PermissionCode): boolean { + return getPermissions().includes(code) + } + + function hasAnyPermission(codes: PermissionCode[]): boolean { + if (!codes.length) return true + return codes.some(code => hasPermission(code)) + } + + function hasAllPermissions(codes: PermissionCode[]): boolean { + return codes.every(code => hasPermission(code)) + } + + function clearAuth() { token.value = '' + userInfo.value = null } return { token, + userInfo, + permissions, setToken, getToken, - removeToken, + setUserInfo, + getUserInfo, + setPermissions, + getPermissions, + hasPermission, + hasAnyPermission, + hasAllPermissions, + clearAuth, } }, { persist: true, diff --git a/rc_autoplc_front/src/utils/request.ts b/rc_autoplc_front/src/utils/request.ts index d1c3c14..ab58ed7 100644 --- a/rc_autoplc_front/src/utils/request.ts +++ b/rc_autoplc_front/src/utils/request.ts @@ -15,6 +15,7 @@ const TokenManager = { setToken(token: string) { const authStore = useAuthStore() authStore.setToken(token) + isAuthExpired = false // 同时设置默认请求头 request.defaults.headers.common['Authorization'] = `Bearer ${token}` }, @@ -26,7 +27,7 @@ const TokenManager = { removeToken() { const authStore = useAuthStore() - authStore.removeToken() + authStore.clearAuth() // 删除默认请求头 delete request.defaults.headers.common['Authorization'] }, @@ -45,15 +46,23 @@ TokenManager.initToken() // 登录过期提示防重复标志 let isLoginExpiredShown = false +let isAuthExpired = false + +const isAuthExpiredRequest = (url: string) => { + if (!isAuthExpired) return false + if (!url) return true + return !url.includes('/user/login') && !url.includes('/user/register') +} // 处理登录过期(防重复提示) const handleLoginExpired = () => { if (!isLoginExpiredShown) { isLoginExpiredShown = true + isAuthExpired = true // 清除token TokenManager.removeToken() // 显示提示消息 - ElMessage.error('登录已过期,请重新登录') + ElMessage.error('用户信息过期,请重新登录') // 跳转到登录页 router.replace('/login').finally(() => { // 延迟重置标志位,确保跳转完成后再允许下次提示 @@ -82,28 +91,27 @@ request.interceptors.request.use( // 响应拦截器 request.interceptors.response.use( (response: AxiosResponse) => { - const { data } = response + const { data } = response as AxiosResponse + const responseToken = data?.token || data?.data?.token + const responseCode = data?.code ?? data?.data?.code - // 如果返回 code 为 0 且有新 token,更新 token - if (data.code === 0 && data.token) { - TokenManager.setToken(data.token) + if (responseCode === 0 && responseToken) { + TokenManager.setToken(responseToken) } console.log('data*--', data) - // 处理业务错误码 - if (data.code === 302) { + if (responseCode === 302) { // 处理登录过期(防重复提示) handleLoginExpired() return Promise.reject(data) } - // 其他错误码 - if (data.code !== 0 && data.code !== undefined) { - // 检查是否是状态刷新相关的请求,如果是则静默处理某些错误 + const hasBusinessCode = responseCode !== undefined && responseCode !== null + if (hasBusinessCode && responseCode !== 0) { const url = response.config?.url || '' const isStatusRefreshRequest = url.includes('/plc/plcStatus') || url.includes('/plc/getModel') - const errorMsg = data.message || data.msg || '' + const errorMsg = data?.message || data?.msg || data?.data?.message || data?.data?.msg || '' // 如果是状态刷新请求,且错误消息包含特定关键词,则静默处理 if (isStatusRefreshRequest && ( @@ -125,17 +133,17 @@ request.interceptors.response.use( return data }, (error) => { - // 处理401未授权错误 - if (error.response?.status === 401) { + if (axios.isAxiosError(error) && error.response?.status === 401) { // 处理登录过期(防重复提示) handleLoginExpired() return Promise.reject(error) } - // 检查是否是状态刷新相关的请求 - const url = error.config?.url || '' + const url = axios.isAxiosError(error) ? (error.config?.url || '') : '' const isStatusRefreshRequest = url.includes('/plc/plcStatus') || url.includes('/plc/getModel') - const errorMsg = error.response?.data?.message || error.response?.data?.msg || '请求失败' + const errorMsg = axios.isAxiosError(error) + ? (error.response?.data?.message || error.response?.data?.msg || error.response?.data?.data?.message || error.response?.data?.data?.msg || '请求失败') + : '请求失败' // 如果是状态刷新请求,且错误消息包含特定关键词,则静默处理 if (isStatusRefreshRequest && ( @@ -150,6 +158,11 @@ request.interceptors.response.use( return Promise.reject(error) } + // 如果是认证过期后的后续请求,静默处理业务错误,避免弹出部门加载失败等页面提示 + if (isAuthExpiredRequest(url) && errorMsg) { + return Promise.reject(error) + } + // 网络错误或其他错误 ElMessage.error(errorMsg) return Promise.reject(error) diff --git a/rc_autoplc_front/src/views/Layout.vue b/rc_autoplc_front/src/views/Layout.vue index 5ea5e63..845c347 100644 --- a/rc_autoplc_front/src/views/Layout.vue +++ b/rc_autoplc_front/src/views/Layout.vue @@ -35,71 +35,82 @@ 首页 - + + - + 用户管理 - + 角色管理 - + 权限管理 - + 部门管理 - + 操作日志管理 - + + - + PLC设备管理 - + 动作参数管理 - + 动作管理 - + 功能岛管理 - + SOP管理 + + + 样品记录 + - + + - + 进样控制 - + 样品记录 + + + 异常记录 + @@ -113,7 +124,7 @@ + + diff --git a/rc_autoplc_front/src/views/role/index.vue b/rc_autoplc_front/src/views/role/index.vue index edef093..338bca7 100644 --- a/rc_autoplc_front/src/views/role/index.vue +++ b/rc_autoplc_front/src/views/role/index.vue @@ -25,12 +25,12 @@ /> - 查询 + 查询 重置
- 新增角色 + 新增角色
@@ -53,9 +53,9 @@ diff --git a/rc_autoplc_front/src/views/sampleinjection/index.vue b/rc_autoplc_front/src/views/sampleinjection/index.vue index 347972d..ac7d44f 100644 --- a/rc_autoplc_front/src/views/sampleinjection/index.vue +++ b/rc_autoplc_front/src/views/sampleinjection/index.vue @@ -142,6 +142,7 @@ @@ -275,6 +276,7 @@