RBAC权限控制完善

This commit is contained in:
2026-05-06 15:24:59 +08:00
parent 9e4daf3d5e
commit bdd5b08351
22 changed files with 273 additions and 677 deletions

View File

@@ -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<string | string[]>) {
if (!hasPermission(binding.value)) {
el.parentNode?.removeChild(el)
}
},
updated(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
if (!hasPermission(binding.value)) {
el.parentNode?.removeChild(el)
}
},
})
}

View File

@@ -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')

View File

@@ -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()
})

View File

@@ -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<string>('')
const userInfo = ref<AuthUserInfo | null>(null)
const permissions = computed<PermissionCode[]>(() => 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,

View File

@@ -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<any>
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 && (

View File

@@ -35,68 +35,76 @@
<el-icon><component :is="getMenuIcon('首页')" /></el-icon>
<span>首页</span>
</el-menu-item>
<el-sub-menu index="system">
<el-sub-menu v-if="hasPermission(['sys', 'sys:user', 'sys:role', 'sys:department', 'sys:permission', 'sys:managelog', 'sys:userrole'])" index="system">
<template #title>
<el-icon><component :is="getMenuIcon('系统管理')" /></el-icon>
<span>系统信息管理</span>
</template>
<el-menu-item index="/user">
<el-menu-item v-if="hasPermission('sys:user')" index="/user">
<el-icon><component :is="getMenuIcon('用户管理')" /></el-icon>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/role">
<el-menu-item v-if="hasPermission('sys:role')" index="/role">
<el-icon><component :is="getMenuIcon('角色管理')" /></el-icon>
<span>角色管理</span>
</el-menu-item>
<el-menu-item index="/permission">
<el-menu-item v-if="hasPermission('sys:permission')" index="/permission">
<el-icon><component :is="getMenuIcon('权限管理')" /></el-icon>
<span>权限管理</span>
</el-menu-item>
<el-menu-item index="/department">
<el-menu-item v-if="hasPermission('sys:department')" index="/department">
<el-icon><component :is="getMenuIcon('部门管理')" /></el-icon>
<span>部门管理</span>
</el-menu-item>
<el-menu-item index="/manage-log">
<el-menu-item v-if="hasPermission('sys:managelog')" index="/manage-log">
<el-icon><component :is="getMenuIcon('操作日志管理')" /></el-icon>
<span>操作日志管理</span>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="island">
<el-sub-menu v-if="hasPermission(['data:plc', 'data:devparam', 'data:devparam', 'data:dev', 'data:island', 'data:sop'])" index="data">
<template #title>
<el-icon><component :is="getMenuIcon('业务管理')" /></el-icon>
<span>基础数据管理</span>
</template>
<el-menu-item index="/plc-devinfo">
<el-menu-item v-if="hasPermission('data:plc')" index="/plc-devinfo">
<el-icon><component :is="getMenuIcon('PLC设备管理')" /></el-icon>
<span>PLC设备管理</span>
</el-menu-item>
<el-menu-item index="/devparam">
<el-menu-item v-if="hasPermission('data:devparam')" index="/devparam">
<el-icon><component :is="getMenuIcon('动作参数管理')" /></el-icon>
<span>动作参数管理</span>
</el-menu-item>
<el-menu-item index="/devinfo">
<el-menu-item v-if="hasPermission('data:dev')" index="/devinfo">
<el-icon><component :is="getMenuIcon('动作管理')" /></el-icon>
<span>动作管理</span>
</el-menu-item>
<el-menu-item index="/island-info">
<el-menu-item v-if="hasPermission('data:island')" index="/island-info">
<el-icon><component :is="getMenuIcon('功能岛管理')" /></el-icon>
<span>功能岛管理</span>
</el-menu-item>
<el-menu-item index="/step-info">
<el-menu-item v-if="hasPermission('data:sop')" index="/step-info">
<el-icon><component :is="getMenuIcon('流程创建')" /></el-icon>
<span>SOP管理</span>
</el-menu-item>
<el-menu-item v-if="hasPermission('data:goods')" index="/goods-info">
<el-icon><component :is="getMenuIcon('样品管理')" /></el-icon>
<span>样品记录</span>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="flow">
<el-sub-menu v-if="hasPermission(['tb:goodcontrol', 'tb:goodrecord'])" index="tb">
<template #title>
<el-icon><component :is="getMenuIcon('流程管理')" /></el-icon>
<span>业务流程控制</span>
</template>
<el-menu-item index="/sample-injection">
<el-menu-item v-if="hasPermission('tb:goodcontrol')" index="/sample-injection">
<el-icon><component :is="getMenuIcon('进样控制')" /></el-icon>
<span>进样控制</span>
</el-menu-item>
<el-menu-item index="/goods-info">
<el-menu-item v-if="hasPermission('tb:goodrecord')" index="/goods-info">
<el-icon><component :is="getMenuIcon('样品管理')" /></el-icon>
<span>样品记录</span>
</el-menu-item>
@@ -113,7 +121,7 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
@@ -143,26 +151,18 @@ const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
// 用户信息可以从store或localStorage获取
const userInfo = ref({
const userInfo = computed(() => authStore.getUserInfo() || {
username: localStorage.getItem('username') || '管理员',
})
// 当前激活的菜单
const activeMenu = computed(() => {
return route.path
})
const activeMenu = computed(() => route.path)
// 根据菜单名称获取图标
const getMenuIcon = (menuName: string) => {
const iconMap: Record<string, any> = {
// 一级
首页: House,
系统管理: Setting,
业务管理: Grid,
流程管理: List,
// 系统管理
用户管理: User,
角色管理: Avatar,
部门管理: OfficeBuilding,
@@ -170,35 +170,35 @@ const getMenuIcon = (menuName: string) => {
职位管理: Briefcase,
操作日志管理: Document,
用户角色管理: UserFilled,
// 业务管理
PLC设备管理: Connection,
动作参数管理: Tools,
动作管理: Monitor,
功能岛管理: Grid,
// 流程管理
PLC设备控制: Operation,
样品管理: Box,
'标准流程管理': Files, // 文件图标,适合标准流程管理
'标准流程管理': Files,
'流程创建': EditPen,
进样控制: Operation,
}
return iconMap[menuName] || EditPen
}
// 处理下拉菜单命令
const hasPermission = (code: string | string[]) => {
const codes = Array.isArray(code) ? code : [code]
return authStore.hasAnyPermission(codes)
}
const handleCommand = (command: string) => {
if (command === 'logout') {
ElMessageBox.confirm('确认退出登录吗?', '提示', {
type: 'warning',
})
.then(() => {
// 清除token和用户信息
authStore.removeToken()
authStore.clearAuth()
localStorage.removeItem('username')
localStorage.removeItem('token')
localStorage.removeItem('permissions')
ElMessage.success('退出成功')
// 跳转到登录页
router.push('/login')
})
.catch(() => {})

View File

@@ -119,7 +119,7 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { User, Lock } from '@element-plus/icons-vue'
import { userlogin, userregister } from '@/api/system/user'
import { useAuthStore } from '@/stores/auth'
@@ -161,7 +161,7 @@ const loginRules: FormRules = {
}
// 注册表单验证规则
const validateConfirmPassword = (rule: any, value: string, callback: Function) => {
const validateConfirmPassword = (_rule: any, value: string, callback: Function) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== registerForm.password) {
@@ -198,18 +198,38 @@ const handleLogin = async () => {
loginLoading.value = true
try {
const response: any = await userlogin(loginForm.username, loginForm.password)
// 根据图片返回的data字段直接是token字符串
if (response.code === 0 && response.data) {
const token = response.data
// 存储token
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)
// 存储用户名
localStorage.setItem('username', loginForm.username)
ElMessage.success('登录成功')
// 跳转到首页
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(response?.message || '登录成功')
router.push('/home')
} else {
ElMessage.error(response?.message || '登录失败,未获取到令牌')
}
} catch (error: any) {
// 错误提示已在全局响应拦截中处理,这里不重复弹出

View File

@@ -61,12 +61,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'sys:departmentselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="openDrawer('create')">新增部门</el-button>
<el-button v-permission="'sys:department:add'" type="primary" @click="openDrawer('create')">新增部门</el-button>
</div>
</div>
</el-card>
@@ -106,8 +106,8 @@
/>
<el-table-column label="操作" width="180" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="'sys:department:upd'" type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button v-permission="'sys:department:del'" type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -46,12 +46,12 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'data:devselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-button v-permission="'data:dev:add'" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增动作
</el-button>
@@ -105,15 +105,15 @@
</el-table-column>
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button type="success" size="small" link @click="handleConfigParams(row)">
<el-button v-permission="'data:dev:setparameters'" type="success" size="small" link @click="handleConfigParams(row)">
<el-icon><Setting /></el-icon>
配置参数
</el-button>
<el-button type="primary" size="small" link @click="handleEdit(row)">
<el-button v-permission="'data:dev:upd'" type="primary" size="small" link @click="handleEdit(row)">
<el-icon><Edit /></el-icon>
编辑
</el-button>
<el-button type="danger" size="small" link @click="handleDelete(row)">
<el-button v-permission="'data:dev:del'" type="danger" size="small" link @click="handleDelete(row)">
<el-icon><Delete /></el-icon>
删除
</el-button>

View File

@@ -18,12 +18,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'data:plcselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-button v-permission="'data:plc:add'" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增PLC设备
</el-button>
@@ -84,6 +84,7 @@
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
v-permission="'data:plc:upd'"
type="primary"
size="small"
link
@@ -93,6 +94,7 @@
编辑
</el-button>
<el-button
v-permission="'data:plc:del'"
type="danger"
size="small"
link

View File

@@ -34,12 +34,12 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'data:devparam:select'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-button v-permission="'data:devparam:add'" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增动作参数
</el-button>
@@ -124,6 +124,7 @@
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button
v-permission="'data:devparam:upd'"
type="primary"
size="small"
link
@@ -133,6 +134,7 @@
编辑
</el-button>
<el-button
v-permission="'data:devparam:del'"
type="danger"
size="small"
link

View File

@@ -18,12 +18,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'tb:goodcontrolselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-button v-permission="'tb:goodcontroladd'" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增流程
</el-button>
@@ -97,6 +97,7 @@
<el-table-column label="操作" width="260" fixed="right">
<template #default="scope">
<el-button
v-permission="'tb:goodcontrolupdSOP'"
:type="scope.row.hasWorkflow ? 'success' : 'warning'"
link
:loading="scope.row.workflowLoading"
@@ -104,9 +105,9 @@
>
{{ scope.row.hasWorkflow ? '编辑流程' : '配置流程' }}
</el-button>
<el-button type="info" link @click="handleSelectGoods(scope.row)">选择样品</el-button>
<el-button type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="'tb:goodcontrolchooseSC'" type="info" link @click="handleSelectGoods(scope.row)">选择样品</el-button>
<el-button v-permission="'tb:goodcontrolupd'" type="primary" link @click="handleEdit(scope.row)">编辑</el-button>
<el-button v-permission="'tb:goodcontroldel'" type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -46,7 +46,7 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'tb:goodcontrolselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
@@ -130,7 +130,7 @@
/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleViewDetail(row)">详情</el-button>
<el-button v-permission="'tb:goodrecord:detail'" type="primary" link @click="handleViewDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -18,12 +18,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'data:islandselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="handleAdd">
<el-button v-permission="'data:island:add'" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增功能岛
</el-button>
@@ -73,11 +73,11 @@
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">
<el-dropdown-item v-permission="'data:island:upd'" command="edit">
<el-icon><Edit /></el-icon>
编辑
</el-dropdown-item>
<el-dropdown-item command="delete" divided>
<el-dropdown-item v-permission="'data:island:del'" command="delete" divided>
<el-icon><Delete /></el-icon>
删除
</el-dropdown-item>

View File

@@ -56,7 +56,7 @@
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'sys:managelogselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
@@ -84,7 +84,7 @@
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="openDetailDialog(scope.row)">详情</el-button>
<el-button v-permission="'sys:managelog:detail'" type="primary" link @click="openDetailDialog(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -48,12 +48,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'sys:permissionselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="openDrawer('create')">新增权限</el-button>
<el-button v-permission="'sys:permission:add'" type="primary" @click="openDrawer('create')">新增权限</el-button>
</div>
</div>
</el-card>
@@ -66,8 +66,8 @@
<el-table-column prop="parentId" label="上级权限" min-width="180" :formatter="formatParentCell" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="'sys:permission:upd'" type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button v-permission="'sys:permission:del'" type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -25,12 +25,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'sys:positionselect'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="openDrawer('create')">新增职位</el-button>
<el-button v-permission="'sys:positionadd'" type="primary" @click="openDrawer('create')">新增职位</el-button>
</div>
</div>
</el-card>
@@ -64,8 +64,8 @@
/>
<el-table-column label="操作" width="180" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="'sys:positionupd'" type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button v-permission="'sys:positiondel'" type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -25,12 +25,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'sys:role'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="openDrawer('create')">新增角色</el-button>
<el-button v-permission="'sys:role:add'" type="primary" @click="openDrawer('create')">新增角色</el-button>
</div>
</div>
</el-card>
@@ -53,9 +53,9 @@
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button type="success" link @click="openPermissionDialog(scope.row)">配置权限</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="'sys:role:upd'" type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button v-permission="'sys:role:rolepermission'" type="success" link @click="openPermissionDialog(scope.row)">配置权限</el-button>
<el-button v-permission="'sys:role:del'" type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -142,6 +142,7 @@
<template #default="scope">
<div class="operation-actions">
<el-button
v-permission="'tb:goodcontrol:chooseSOP'"
class="flow-select-btn"
:type="isFlowSelected(scope.row) ? 'success' : 'primary'"
:plain="isFlowSelected(scope.row)"
@@ -150,10 +151,10 @@
>
{{ isFlowSelected(scope.row) ? '已选择' : '选择' }}
</el-button>
<el-button type="success" link @click="handleConfigWorkflow(scope.row)">编辑SOP</el-button>
<el-button type="info" link @click="handleViewWorkflow(scope.row)">查看SOP</el-button>
<el-button type="warning" link @click="handleEditFlow(scope.row)">编辑</el-button>
<el-button type="danger" link @click="handleDeleteFlow(scope.row)">删除</el-button>
<el-button v-permission="'tb:goodcontrol:updSOP'" type="success" link @click="handleConfigWorkflow(scope.row)">编辑SOP</el-button>
<el-button v-permission="'tb:goodcontrol:viewSOP'" type="info" link @click="handleViewWorkflow(scope.row)">查看SOP</el-button>
<el-button v-permission="'tb:goodcontrol:upd'" type="warning" link @click="handleEditFlow(scope.row)">编辑</el-button>
<el-button v-permission="'tb:goodcontrol:del'" type="danger" link @click="handleDeleteFlow(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
@@ -275,6 +276,7 @@
<!-- 操作按钮区域 -->
<div class="card-footer">
<el-button
v-permission="'tb:goodcontrol:model'"
v-if="device.ipAddr"
type="info"
size="small"
@@ -286,6 +288,7 @@
{{ Number(device.realRunModel ?? 0) === 0 ? '自动模式' : '手动模式' }}
</el-button>
<el-button
v-permission="'tb:goodcontrol:connection'"
v-if="device.status === 0 || device.status === undefined || device.status === null"
type="success"
size="small"
@@ -295,6 +298,7 @@
连接
</el-button>
<el-button
v-permission="'tb:goodcontrol:execute'"
v-else-if="device.status === 1"
type="primary"
size="small"
@@ -304,6 +308,7 @@
执行
</el-button>
<el-button
v-permission="'tb:goodcontrol:stop'"
v-else-if="device.status === 2"
type="info"
size="small"
@@ -314,6 +319,7 @@
停止
</el-button>
<el-button
v-permission="'tb:goodcontrol:reset'"
v-else-if="device.status === 4"
type="danger"
size="small"

View File

@@ -86,6 +86,7 @@
<div v-if="!hasFlowId" class="flow-input-item flow-confirm-item">
<span class="flow-input-label">&nbsp;</span>
<el-button
v-permission="'data:sop:add'"
type="primary"
@click="confirmFlowInfo"
:loading="confirmingFlow"
@@ -134,10 +135,10 @@
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">
<el-dropdown-item v-permission="'tb:goodcontrolupd'" command="edit">
<el-icon><Edit /></el-icon>编辑
</el-dropdown-item>
<el-dropdown-item command="delete" divided>
<el-dropdown-item v-permission="'tb:goodcontroldel'" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
@@ -189,8 +190,8 @@
<div class="action-buttons">
<el-button plain @click="goFlowList">退出</el-button>
<template v-if="!isViewMode">
<el-button type="primary" plain @click="clearWorkflow">清空</el-button>
<el-button type="primary" @click="saveWorkflow" :loading="savingWorkflow">保存</el-button>
<el-button v-permission="'data:sop:clear'" type="primary" plain @click="clearWorkflow">清空</el-button>
<el-button v-permission="'data:sop:save'" type="primary" @click="saveWorkflow" :loading="savingWorkflow">保存</el-button>
</template>
</div>
</div>

View File

@@ -1,532 +0,0 @@
<template>
<div class="user-role-page">
<el-breadcrumb separator="/" style="margin-bottom: 16px">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>系统管理</el-breadcrumb-item>
<el-breadcrumb-item>用户角色管理</el-breadcrumb-item>
</el-breadcrumb>
<el-card class="search-card" shadow="never">
<div class="search-bar">
<el-form :inline="true" :model="queryForm" label-width="80px">
<el-form-item label="用户">
<el-select
v-model="queryForm.userId"
filterable
clearable
placeholder="请选择用户"
style="width: 200px"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.userName || item.nicke || item.nickName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="角色">
<el-select
v-model="queryForm.roleId"
filterable
clearable
placeholder="请选择角色"
style="width: 200px"
>
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.roleName || item.roleCode"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="openDrawer('create')">新增用户角色关联</el-button>
</div>
</div>
</el-card>
<el-card shadow="never">
<el-table
:data="tableData"
stripe
border
style="width: 100%"
v-loading="loading"
>
<el-table-column type="index" label="序号" width="70" />
<el-table-column
prop="userId"
label="用户名"
min-width="150"
>
<template #default="scope">
{{ getUserName(scope.row.userId) }}
</template>
</el-table-column>
<el-table-column
prop="roleId"
label="角色名"
min-width="150"
>
<template #default="scope">
{{ getRoleName(scope.row.roleId) }}
</template>
</el-table-column>
<el-table-column
prop="remark"
label="备注"
min-width="200"
:formatter="formatCell"
/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</el-card>
<el-drawer
v-model="drawerVisible"
title="新增用户角色关联"
direction="rtl"
:size="500"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
class="drawer-form"
>
<el-form-item label="用户" prop="userId">
<el-select
v-model="form.userId"
filterable
clearable
placeholder="请选择用户"
style="width: 100%"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.userName || item.nicke || item.nickName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="角色" prop="roleId">
<el-select
v-model="form.roleId"
filterable
clearable
placeholder="请选择角色"
style="width: 100%"
>
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.roleName || item.roleCode"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注"
:rows="3"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<div class="drawer-footer">
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitForm">
保存
</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { userRoleAdd, userRoleDel, userRoleList, userRoleByUserId } from '@/api/system/user-role'
import { userselect } from '@/api/system/user'
import { rleselect } from '@/api/system/role'
interface UserRoleItem {
id?: number | string
userId: number | string
roleId: number | string
remark?: string
}
interface UserItem {
id: number | string
userName?: string
nicke?: string
nickName?: string
}
interface RoleItem {
id: number | string
roleName?: string
roleCode?: string
}
const loading = ref(false)
const submitLoading = ref(false)
const tableData = ref<UserRoleItem[]>([])
const total = ref(0)
const userOptions = ref<UserItem[]>([])
const roleOptions = ref<RoleItem[]>([])
const userMap = ref<Map<number | string, string>>(new Map())
const roleMap = ref<Map<number | string, string>>(new Map())
const queryForm = reactive({
userId: null as number | string | null,
roleId: null as number | string | null,
})
const pagination = reactive({
pageNum: 1,
pageSize: 10,
})
const drawerVisible = ref(false)
const formRef = ref<FormInstance>()
const form = reactive<UserRoleItem>({
userId: '',
roleId: '',
remark: '',
})
const rules: FormRules = {
userId: [{ required: true, message: '请选择用户', trigger: 'change' }],
roleId: [{ required: true, message: '请选择角色', trigger: 'change' }],
}
const resetForm = () => {
form.userId = ''
form.roleId = ''
form.remark = ''
formRef.value?.clearValidate()
}
const formatCell = (_row: any, _column: any, value: any) => {
if (value === 0) return 0
return value === undefined || value === null || value === '' ? '暂无' : value
}
// 获取用户名
const getUserName = (userId: number | string | null | undefined) => {
if (!userId) return '暂无'
return userMap.value.get(userId) || '暂无'
}
// 获取角色名
const getRoleName = (roleId: number | string | null | undefined) => {
if (!roleId) return '暂无'
return roleMap.value.get(roleId) || '暂无'
}
// 加载用户列表
const loadUsers = async () => {
try {
const res: any = await userselect({})
const data = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []
userOptions.value = data
userMap.value.clear()
data.forEach((user: UserItem) => {
if (user.id) {
const userName = user.userName || user.nicke || user.nickName || ''
userMap.value.set(user.id, userName)
}
})
} catch (error) {
console.error('load user list error', error)
ElMessage.error('加载用户列表失败')
userOptions.value = []
}
}
// 加载角色列表
const loadRoles = async () => {
try {
const res: any = await rleselect({})
const data = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []
roleOptions.value = data
roleMap.value.clear()
data.forEach((role: RoleItem) => {
if (role.id) {
const roleName = role.roleName || role.roleCode || ''
roleMap.value.set(role.id, roleName)
}
})
} catch (error) {
console.error('load role list error', error)
ElMessage.error('加载角色列表失败')
roleOptions.value = []
}
}
// 加载用户角色关联列表
const loadList = async () => {
loading.value = true
try {
// 确保用户和角色列表已加载
if (userOptions.value.length === 0) {
await loadUsers()
}
if (roleOptions.value.length === 0) {
await loadRoles()
}
const params: any = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
}
if (queryForm.userId) {
params.userId = queryForm.userId
}
if (queryForm.roleId) {
params.roleId = queryForm.roleId
}
const res: any = await userRoleList(params)
console.log('用户角色关联列表API返回数据:', res)
let pageData = res?.data
if (!pageData && (res?.records || res?.total !== undefined)) {
pageData = res
}
const recordsFromData =
pageData?.records ||
pageData?.list ||
pageData?.rows ||
pageData?.items ||
(Array.isArray(pageData?.data) ? pageData.data : undefined)
const records = Array.isArray(recordsFromData)
? recordsFromData
: Array.isArray(pageData)
? pageData
: Array.isArray(res?.data)
? res.data
: []
const totalValue =
pageData?.total ??
pageData?.count ??
pageData?.totalCount ??
res?.total ??
(Array.isArray(records) ? records.length : 0)
tableData.value = records
total.value = Number(totalValue) || 0
console.log('解析后的数据:', {
records: records.length,
total: total.value,
firstRecord: records[0],
})
} catch (error: any) {
console.error('load user role list error', error)
const errorMsg = error?.message || error?.msg || '加载用户角色关联列表失败'
ElMessage.error(errorMsg)
tableData.value = []
total.value = 0
} finally {
loading.value = false
}
}
const handleSearch = () => {
pagination.pageNum = 1
loadList()
}
const resetSearch = () => {
queryForm.userId = null
queryForm.roleId = null
handleSearch()
}
const handleSizeChange = (size: number) => {
pagination.pageSize = size
loadList()
}
const handleCurrentChange = (page: number) => {
pagination.pageNum = page
loadList()
}
const openDrawer = async (mode: 'create') => {
resetForm()
drawerVisible.value = true
// 加载用户和角色数据
await Promise.all([loadUsers(), loadRoles()])
}
const submitForm = () => {
formRef.value?.validate(async (valid) => {
if (!valid) return
// 检查用户是否已拥有该角色
try {
const res: any = await userRoleByUserId(form.userId)
const data = res?.data ?? res ?? {}
// 兼容多种返回格式
const userRoles = Array.isArray(data)
? data
: Array.isArray(data.data)
? data.data
: Array.isArray(data.records)
? data.records
: Array.isArray(data.list)
? data.list
: []
// 检查是否已存在该角色关联
const existingRole = userRoles.find((item: any) => item.roleId === form.roleId)
if (existingRole) {
// 获取用户名和角色名
const userName = getUserName(form.userId)
const roleName = getRoleName(form.roleId)
ElMessage.warning(`用户【${userName}】已拥有【${roleName}】角色,无需重复分配`)
return
}
} catch (error: any) {
console.error('check user role error', error)
// 如果检查失败,继续执行新增操作,让后端处理重复情况
}
submitLoading.value = true
try {
const payload: any = {
userId: form.userId,
roleId: form.roleId,
remark: form.remark || '',
}
await userRoleAdd(payload)
ElMessage.success('新增成功')
drawerVisible.value = false
loadList()
} catch (error: any) {
console.error('submit user role error', error)
const errorMsg = error?.message || error?.msg || '新增失败'
ElMessage.error(errorMsg)
} finally {
submitLoading.value = false
}
})
}
const handleDelete = (row: UserRoleItem) => {
if (!row.id) {
ElMessage.warning('缺少关联ID')
return
}
const userName = getUserName(row.userId)
const roleName = getRoleName(row.roleId)
ElMessageBox.confirm(`确认删除用户「${userName}」与角色「${roleName}」的关联吗?`, '提示', {
type: 'warning',
})
.then(async () => {
if (row.id == null) {
ElMessage.warning('缺少关联ID无法删除')
return
}
try {
await userRoleDel(row.id)
ElMessage.success('删除成功')
loadList()
} catch (error: any) {
console.error('delete user role error', error)
const errorMsg = error?.message || error?.msg || '删除失败'
ElMessage.error(errorMsg)
}
})
.catch(() => {})
}
onMounted(() => {
loadUsers()
loadRoles()
loadList()
})
</script>
<style scoped>
.user-role-page {
display: flex;
flex-direction: column;
gap: 12px;
}
.search-card {
padding-bottom: 4px;
}
.search-bar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.drawer-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@@ -17,12 +17,12 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button v-permission="'sys:user'" type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="toolbar">
<el-button type="primary" @click="openDrawer('create')">新增用户</el-button>
<el-button v-permission="'sys:user:add'" type="primary" @click="openDrawer('create')">新增用户</el-button>
</div>
</div>
</el-card>
@@ -92,9 +92,9 @@
/>
<el-table-column label="操作" width="250" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button type="success" link @click="openRoleDialog(scope.row)">分配角色</el-button>
<el-button type="danger" link @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="'sys:user:upd'" type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
<el-button v-permission="'sys:user:userrole'" type="success" link @click="openRoleDialog(scope.row)">分配角色</el-button>
<el-button v-permission="'sys:user:del'" type="danger" link @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>