From b35b0eedbd0397a7e79a46bb48c11a3f1e0ceefb Mon Sep 17 00:00:00 2001 From: Lxq <19852720163@163.com> Date: Thu, 30 Apr 2026 15:42:03 +0800 Subject: [PATCH 1/8] . --- rc_autoplc_front/src/views/position/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rc_autoplc_front/src/views/position/index.vue b/rc_autoplc_front/src/views/position/index.vue index e12b2f0..c11968e 100644 --- a/rc_autoplc_front/src/views/position/index.vue +++ b/rc_autoplc_front/src/views/position/index.vue @@ -11,7 +11,7 @@ From bdd5b083517381aca9ce2894e9dfdc5547375908 Mon Sep 17 00:00:00 2001 From: Lxq <19852720163@163.com> Date: Wed, 6 May 2026 15:24:59 +0800 Subject: [PATCH 2/8] =?UTF-8?q?RBAC=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rc_autoplc_front/src/directives/permission.ts | 26 + rc_autoplc_front/src/main.ts | 3 +- rc_autoplc_front/src/router/index.ts | 63 ++- rc_autoplc_front/src/stores/auth.ts | 65 ++- rc_autoplc_front/src/utils/request.ts | 31 +- rc_autoplc_front/src/views/Layout.vue | 70 +-- rc_autoplc_front/src/views/Login.vue | 44 +- .../src/views/department/index.vue | 8 +- rc_autoplc_front/src/views/devinfo/index.vue | 10 +- rc_autoplc_front/src/views/devinfo/plc.vue | 6 +- rc_autoplc_front/src/views/devparam/index.vue | 6 +- rc_autoplc_front/src/views/flowinfo/index.vue | 11 +- rc_autoplc_front/src/views/goods/index.vue | 4 +- .../src/views/islandInfo/index.vue | 8 +- .../src/views/manage-log/index.vue | 4 +- .../src/views/permission/index.vue | 8 +- rc_autoplc_front/src/views/position/index.vue | 8 +- rc_autoplc_front/src/views/role/index.vue | 10 +- .../src/views/sampleinjection/index.vue | 14 +- rc_autoplc_front/src/views/stepinfo/index.vue | 9 +- .../src/views/user-role/index.vue | 532 ------------------ rc_autoplc_front/src/views/user/index.vue | 10 +- 22 files changed, 273 insertions(+), 677 deletions(-) create mode 100644 rc_autoplc_front/src/directives/permission.ts delete mode 100644 rc_autoplc_front/src/views/user-role/index.vue 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..ad2ed4d 100644 --- a/rc_autoplc_front/src/router/index.ts +++ b/rc_autoplc_front/src/router/index.ts @@ -2,6 +2,14 @@ import { createRouter, createWebHashHistory } 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), routes: [ @@ -29,81 +37,77 @@ 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' }, }, ], }, @@ -115,33 +119,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..f8a18d3 100644 --- a/rc_autoplc_front/src/utils/request.ts +++ b/rc_autoplc_front/src/utils/request.ts @@ -26,7 +26,7 @@ const TokenManager = { removeToken() { const authStore = useAuthStore() - authStore.removeToken() + authStore.clearAuth() // 删除默认请求头 delete request.defaults.headers.common['Authorization'] }, @@ -82,28 +82,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 +124,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 && ( diff --git a/rc_autoplc_front/src/views/Layout.vue b/rc_autoplc_front/src/views/Layout.vue index 5ea5e63..0f7ef22 100644 --- a/rc_autoplc_front/src/views/Layout.vue +++ b/rc_autoplc_front/src/views/Layout.vue @@ -35,68 +35,76 @@ 首页 - + + - + 用户管理 - + 角色管理 - + 权限管理 - + 部门管理 - + 操作日志管理 - + + - + PLC设备管理 - + 动作参数管理 - + 动作管理 - + 功能岛管理 - + SOP管理 + + + 样品记录 + - + + - + 进样控制 - + + 样品记录 @@ -113,7 +121,7 @@ - - - diff --git a/rc_autoplc_front/src/views/user/index.vue b/rc_autoplc_front/src/views/user/index.vue index 34490e2..fb61142 100644 --- a/rc_autoplc_front/src/views/user/index.vue +++ b/rc_autoplc_front/src/views/user/index.vue @@ -17,12 +17,12 @@ /> - 查询 + 查询 重置
- 新增用户 + 新增用户
@@ -92,9 +92,9 @@ /> From c9dc8e9b6c7f190c238bcf201b0c7f9a7c002165 Mon Sep 17 00:00:00 2001 From: Lxq <19852720163@163.com> Date: Thu, 7 May 2026 11:37:02 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rc_autoplc_front/src/utils/request.ts | 16 +++- rc_autoplc_front/src/views/Login.vue | 94 +++++++++---------- rc_autoplc_front/src/views/devparam/index.vue | 68 +++++++++----- rc_autoplc_front/src/views/goods/index.vue | 14 ++- 4 files changed, 122 insertions(+), 70 deletions(-) diff --git a/rc_autoplc_front/src/utils/request.ts b/rc_autoplc_front/src/utils/request.ts index f8a18d3..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}` }, @@ -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(() => { // 延迟重置标志位,确保跳转完成后再允许下次提示 @@ -149,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/Login.vue b/rc_autoplc_front/src/views/Login.vue index f927ac0..24793e7 100644 --- a/rc_autoplc_front/src/views/Login.vue +++ b/rc_autoplc_front/src/views/Login.vue @@ -12,7 +12,7 @@ :rules="loginRules" class="login-form" label-width="0" - @keyup.enter="handleLogin" + @submit.prevent="handleLogin" > @@ -33,7 +34,7 @@ :prefix-icon="Lock" show-password clearable - @keyup.enter="handleLogin" + @keydown.enter.prevent="handleLogin" /> @@ -191,54 +192,53 @@ const registerRules: FormRules = { // 处理登录 const handleLogin = async () => { - if (!loginFormRef.value) return - - await loginFormRef.value.validate(async (valid) => { - if (valid) { - loginLoading.value = true - try { - const response: any = await userlogin(loginForm.username, loginForm.password) - const loginData = response?.data ?? response - const token = loginData?.token || response?.token || loginData - const permissions = Array.isArray(loginData?.permissions) - ? loginData.permissions - : Array.isArray(response?.permissions) - ? response.permissions - : [] - const roles = Array.isArray(loginData?.roles) - ? loginData.roles - : Array.isArray(response?.roles) - ? response.roles - : [] - const userInfo = typeof loginData === 'object' ? loginData : { token } + if (!loginFormRef.value || loginLoading.value) return - if (token) { - authStore.setToken(token) - authStore.setUserInfo({ - ...userInfo, - username: userInfo.username || loginForm.username, - token, - roles, - permissions, - }) - authStore.setPermissions(permissions) - localStorage.setItem('username', userInfo.username || loginForm.username) - localStorage.setItem('token', token) - localStorage.setItem('permissions', JSON.stringify(permissions)) + const valid = await loginFormRef.value.validate().catch(() => false) + if (!valid) return - ElMessage.success(response?.message || '登录成功') - router.push('/home') - } else { - ElMessage.error(response?.message || '登录失败,未获取到令牌') - } - } catch (error: any) { - // 错误提示已在全局响应拦截中处理,这里不重复弹出 - console.warn('login error:', error) - } finally { - loginLoading.value = false - } + loginLoading.value = true + try { + const response: any = await userlogin(loginForm.username, loginForm.password) + const loginData = response?.data ?? response + const token = loginData?.token || response?.token || loginData + const permissions = Array.isArray(loginData?.permissions) + ? loginData.permissions + : Array.isArray(response?.permissions) + ? response.permissions + : [] + const roles = Array.isArray(loginData?.roles) + ? loginData.roles + : Array.isArray(response?.roles) + ? response.roles + : [] + const userInfo = typeof loginData === 'object' ? loginData : { token } + + if (token) { + authStore.setToken(token) + authStore.setUserInfo({ + ...userInfo, + username: userInfo.username || loginForm.username, + token, + roles, + permissions, + }) + authStore.setPermissions(permissions) + localStorage.setItem('username', userInfo.username || loginForm.username) + localStorage.setItem('token', token) + localStorage.setItem('permissions', JSON.stringify(permissions)) + + ElMessage.success('登录成功') + router.push('/home') + } else { + ElMessage.error(response?.message || '登录失败,未获取到令牌') } - }) + } catch (error: any) { + // 错误提示已在全局响应拦截中处理,这里不重复弹出 + console.warn('login error:', error) + } finally { + loginLoading.value = false + } } // 处理注册 diff --git a/rc_autoplc_front/src/views/devparam/index.vue b/rc_autoplc_front/src/views/devparam/index.vue index 7d18884..4566a6c 100644 --- a/rc_autoplc_front/src/views/devparam/index.vue +++ b/rc_autoplc_front/src/views/devparam/index.vue @@ -86,25 +86,31 @@ @@ -236,12 +242,13 @@ /> 配置 + 配置 @@ -259,11 +266,13 @@ /> 配置 + 配置 @@ -371,10 +380,11 @@ + + From 329427a1b147946c06f55be7c0f352d7a167f9f6 Mon Sep 17 00:00:00 2001 From: Lxq <19852720163@163.com> Date: Wed, 13 May 2026 10:40:20 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rc_autoplc_front/src/router/index.ts | 2 +- rc_autoplc_front/src/views/Layout.vue | 5 ++--- rc_autoplc_front/src/views/recordinfo/index.vue | 13 +++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/rc_autoplc_front/src/router/index.ts b/rc_autoplc_front/src/router/index.ts index 47ac47c..216f6be 100644 --- a/rc_autoplc_front/src/router/index.ts +++ b/rc_autoplc_front/src/router/index.ts @@ -113,7 +113,7 @@ const router = createRouter({ path: '/record-info', name: 'record-info', component: () => import('../views/recordinfo/index.vue'), - meta: { permission: 'tb:goodrecord' }, + meta: { permission: 'tb:exceptionrecord' }, }, ], }, diff --git a/rc_autoplc_front/src/views/Layout.vue b/rc_autoplc_front/src/views/Layout.vue index e6f0147..845c347 100644 --- a/rc_autoplc_front/src/views/Layout.vue +++ b/rc_autoplc_front/src/views/Layout.vue @@ -94,7 +94,7 @@ - +