动作参数取值范围+角色配置权限
This commit is contained in:
1
rc_autoplc_front/.env
Normal file
1
rc_autoplc_front/.env
Normal file
@@ -0,0 +1 @@
|
||||
VITE_API_URL=http://223.71.122.54:9090
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/public/Laboratory.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>中核404内照射上位机中控系统</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
1898
rc_autoplc_front/package-lock.json
generated
1898
rc_autoplc_front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,15 @@
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"axios": "^1.13.2",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"vue": "^3.5.25",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -27,6 +31,7 @@
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"sass-embedded": "^1.97.3",
|
||||
"typescript": "~5.9.0",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-vue-devtools": "^8.0.5",
|
||||
|
||||
BIN
rc_autoplc_front/public/Laboratory.ico
Normal file
BIN
rc_autoplc_front/public/Laboratory.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
53
rc_autoplc_front/src/api/system/dicdata.ts
Normal file
53
rc_autoplc_front/src/api/system/dicdata.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function dicdataadd(data: any) {
|
||||
return request({
|
||||
url: '/sysDicData/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function dicdatadel(id: string | number) {
|
||||
return request({
|
||||
url: `/sysDicData/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function dicdataupd(data: any) {
|
||||
return request({
|
||||
url: '/sysDicData/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function dicdatalist(data: any) {
|
||||
return request({
|
||||
url: '/sysDicData/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function dicdatabyid(id: string | number) {
|
||||
return request({
|
||||
url: `/sysDicData/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function dicdataall() {
|
||||
return request({
|
||||
url: `/sysDicData/list`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function dicdatabydicid(dicId : string | number) {
|
||||
return request({
|
||||
url: `/sysDicData/listByDicId/${dicId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
46
rc_autoplc_front/src/api/system/dictype.ts
Normal file
46
rc_autoplc_front/src/api/system/dictype.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function dictypeadd(data: any) {
|
||||
return request({
|
||||
url: '/sysDicType/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function dictypedel(id: string | number) {
|
||||
return request({
|
||||
url: `/sysDicType/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function dictypeupd(data: any) {
|
||||
return request({
|
||||
url: '/sysDicType/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function dictypelist(data: any) {
|
||||
return request({
|
||||
url: '/sysDicType/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function dictypebyid(id: string | number) {
|
||||
return request({
|
||||
url: `/sysDicType/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function dictypeall() {
|
||||
return request({
|
||||
url: `/sysDicType/list`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
47
rc_autoplc_front/src/api/system/permission.ts
Normal file
47
rc_autoplc_front/src/api/system/permission.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function permissionadd(data: any) {
|
||||
return request({
|
||||
url: '/permission/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function permissiondel(id: string | number) {
|
||||
return request({
|
||||
url: `/permission/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function permissionupd(data: any) {
|
||||
return request({
|
||||
url: '/permission/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function permissionlist(data: any) {
|
||||
return request({
|
||||
url: '/permission/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function permissionbyid(id: string | number) {
|
||||
return request({
|
||||
url: `/permission/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function permissionselect(data?: any) {
|
||||
return request({
|
||||
url: '/permission/list',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
38
rc_autoplc_front/src/api/system/role-permission.ts
Normal file
38
rc_autoplc_front/src/api/system/role-permission.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 给角色添加权限
|
||||
export function rolePermissionAdd(data: any) {
|
||||
return request({
|
||||
url: '/rolePermission/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 清空某个角色的所有权限
|
||||
export function rolePermissionClearByRoleId(roleId: string | number) {
|
||||
return request({
|
||||
url: `/rolePermission/clearByRoleId/${roleId}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// 删除单条角色权限关联
|
||||
export function rolePermissionDel(roleId: string | number, permissionId: string | number) {
|
||||
return request({
|
||||
url: `/rolePermission/del`,
|
||||
method: 'delete',
|
||||
params: {
|
||||
roleId,
|
||||
permissionId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 根据角色ID查询已绑定的权限ID列表
|
||||
export function rolePermissionListByRoleId(roleId: string | number) {
|
||||
return request({
|
||||
url: `/rolePermission/listByRoleId/${roleId}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
@@ -44,4 +44,26 @@ export function userbyid(id: string | number) {
|
||||
url: `/user/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function userlogin(username: string, password: string) {
|
||||
return request({
|
||||
url: '/user/login',
|
||||
method: 'post',
|
||||
params: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function userregister(username: string, password: string) {
|
||||
return request({
|
||||
url: '/user/register',
|
||||
method: 'post',
|
||||
params: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -44,4 +44,28 @@ export function devparamselect(data: any) {
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
// 为参数增加设备的devId
|
||||
export function devparamaddDevId(devId: number, paramId: number) {
|
||||
return request({
|
||||
url: '/devParam/addDevId',
|
||||
method: 'post',
|
||||
params: {
|
||||
devId,
|
||||
paramId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 为参数减少设备的devId
|
||||
export function devparamdelDevId(devId: number, paramId: number) {
|
||||
return request({
|
||||
url: '/devParam/delDevId',
|
||||
method: 'post',
|
||||
params: {
|
||||
devId,
|
||||
paramId,
|
||||
},
|
||||
})
|
||||
}
|
||||
39
rc_autoplc_front/src/api/tb/flowinfo.ts
Normal file
39
rc_autoplc_front/src/api/tb/flowinfo.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function flowInfoadd(data: any) {
|
||||
return request({
|
||||
url: '/flowInfo/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function flowInfodel(id: string | number) {
|
||||
return request({
|
||||
url: `/flowInfo/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function flowInfoupd(data: any) {
|
||||
return request({
|
||||
url: '/flowInfo/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function flowInfolist(data: any) {
|
||||
return request({
|
||||
url: '/flowInfo/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function flowInfobyid(id: string | number) {
|
||||
return request({
|
||||
url: `/flowInfo/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
47
rc_autoplc_front/src/api/tb/goodsflowstatus.ts
Normal file
47
rc_autoplc_front/src/api/tb/goodsflowstatus.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function goodsFlowStatusadd(data: any) {
|
||||
return request({
|
||||
url: '/goodsFlowStatus/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsFlowStatusdel(id: string | number) {
|
||||
return request({
|
||||
url: `/goodsFlowStatus/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsFlowStatusupd(data: any) {
|
||||
return request({
|
||||
url: '/goodsFlowStatus/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsFlowStatuslist(data: any) {
|
||||
return request({
|
||||
url: '/goodsFlowStatus/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsFlowStatusselect(data: any) {
|
||||
return request({
|
||||
url: '/goodsFlowStatus/list',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsFlowStatusbyid(id: string | number) {
|
||||
return request({
|
||||
url: `/goodsFlowStatus/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
39
rc_autoplc_front/src/api/tb/goodsinfo.ts
Normal file
39
rc_autoplc_front/src/api/tb/goodsinfo.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function goodsInfoadd(data: any) {
|
||||
return request({
|
||||
url: '/goodsInfo/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsInfodel(id: string | number) {
|
||||
return request({
|
||||
url: `/goodsInfo/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsInfoupd(data: any) {
|
||||
return request({
|
||||
url: '/goodsInfo/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsInfolist(data: any) {
|
||||
return request({
|
||||
url: '/goodsInfo/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function goodsInfobyid(id: string | number) {
|
||||
return request({
|
||||
url: `/goodsInfo/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
121
rc_autoplc_front/src/api/tb/plcdevicecontrol.ts
Normal file
121
rc_autoplc_front/src/api/tb/plcdevicecontrol.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 连接PLC设备
|
||||
export function plcConnect(data: any) {
|
||||
return request({
|
||||
url: '/plc/connect',
|
||||
method: 'post',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 调用PLC设备执行动作
|
||||
export function plcDoAction(data: any) {
|
||||
return request({
|
||||
url: '/plc/doAction',
|
||||
method: 'post',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取全部PLC连接对象
|
||||
export function plcGetAll() {
|
||||
return request({
|
||||
url: '/plc/getAll',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// PLC设备获取模式值
|
||||
export function plcGetModel(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/getModel',
|
||||
method: 'get',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
|
||||
// 根据IP地址查询PLC连接对象
|
||||
export function plcGetPlcByIp(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/getPlcByIp',
|
||||
method: 'get',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
|
||||
// 暂停运行PLC
|
||||
export function plcPause(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/pause',
|
||||
method: 'post',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
|
||||
// PLC设备获取状态值
|
||||
export function plcPlcStatus(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/plcStatus',
|
||||
method: 'get',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取主 PLC 状态对象(Swagger: GET /plc/mastertPlcStatus)
|
||||
export function masterPlcStatus() {
|
||||
return request({
|
||||
url: '/plc/mastertPlcStatus',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
// 别名:更贴近接口语义
|
||||
export function plcMasterPlcStatus() {
|
||||
return masterPlcStatus()
|
||||
}
|
||||
|
||||
// PLC设备复位故障
|
||||
export function plcResetError(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/resetError',
|
||||
method: 'post',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
|
||||
// 运行PLC
|
||||
export function plcRun(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/run',
|
||||
method: 'post',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
|
||||
// 为样品执行国标
|
||||
export function plcRunFlow(data: any) {
|
||||
return request({
|
||||
url: '/plc/runFlow',
|
||||
method: 'post',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// PLC设备设置模式
|
||||
export function plcSetModel(data: any) {
|
||||
return request({
|
||||
url: '/plc/setModel',
|
||||
method: 'post',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 停止PLC
|
||||
export function plcStop(ipAddr: string) {
|
||||
return request({
|
||||
url: '/plc/stop',
|
||||
method: 'post',
|
||||
params: { ipAddr }
|
||||
})
|
||||
}
|
||||
63
rc_autoplc_front/src/api/tb/stepinfo.ts
Normal file
63
rc_autoplc_front/src/api/tb/stepinfo.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function stepInfoadd(data: any) {
|
||||
return request({
|
||||
url: '/stepInfo/add',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfoaddlist(data: any) {
|
||||
return request({
|
||||
url: '/stepInfo/batchAdd',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfodel(id: string | number) {
|
||||
return request({
|
||||
url: `/stepInfo/del/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfodellist(data: any[]) {
|
||||
return request({
|
||||
url: '/stepInfo/batchDel',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfoupd(data: any) {
|
||||
return request({
|
||||
url: '/stepInfo/update',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfoupdlist(data: any) {
|
||||
return request({
|
||||
url: '/stepInfo/batchUpdate',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfolist(data: any) {
|
||||
return request({
|
||||
url: '/stepInfo/listPage',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
}
|
||||
|
||||
export function stepInfobyid(id: string | number) {
|
||||
return request({
|
||||
url: `/stepInfo/getById/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
BIN
rc_autoplc_front/src/assets/image/plc.jpg
Normal file
BIN
rc_autoplc_front/src/assets/image/plc.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 299 KiB |
@@ -1,12 +1,30 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('../views/Login.vue'),
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('../views/Layout.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/home',
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('../views/Home.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
@@ -22,6 +40,11 @@ const router = createRouter({
|
||||
name: 'department',
|
||||
component: () => import('../views/department/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/permission',
|
||||
name: 'permission',
|
||||
component: () => import('../views/permission/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/position',
|
||||
name: 'position',
|
||||
@@ -42,6 +65,11 @@ const router = createRouter({
|
||||
name: 'island-info',
|
||||
component: () => import('../views/islandInfo/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/devparam',
|
||||
name: 'devparam',
|
||||
component: () => import('../views/devparam/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/devinfo',
|
||||
name: 'devinfo',
|
||||
@@ -52,20 +80,69 @@ const router = createRouter({
|
||||
name: 'plc-devinfo',
|
||||
component: () => import('../views/devinfo/plc.vue'),
|
||||
},
|
||||
{
|
||||
path: '/goods-info',
|
||||
name: 'goods-info',
|
||||
component: () => import('../views/goods/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/flow-info',
|
||||
name: 'flow-info',
|
||||
component: () => import('../views/flowinfo/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/step-info',
|
||||
name: 'step-info',
|
||||
component: () => import('../views/stepinfo/index.vue'),
|
||||
},
|
||||
{
|
||||
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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// 添加全局路由守卫
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// const token = localStorage.getItem('token')
|
||||
router.beforeEach((to, _from, next) => {
|
||||
const authStore = useAuthStore()
|
||||
const token = authStore.getToken()
|
||||
|
||||
// if (to.path !== '/login' && !token) {
|
||||
// next('/login')
|
||||
// } else {
|
||||
// next()
|
||||
// }
|
||||
// })
|
||||
// 如果访问登录页
|
||||
if (to.path === '/login') {
|
||||
// 已登录状态下访问登录页,自动跳转到首页
|
||||
if (token) {
|
||||
next('/home')
|
||||
ElMessage.success('已登录,自动跳转')
|
||||
} else {
|
||||
// 未登录,正常进入登录页
|
||||
next()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 访问需要认证的页面,但未登录(无token)
|
||||
if (to.meta.requiresAuth && !token) {
|
||||
ElMessage.warning('请先登录')
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果访问根路径,重定向到首页
|
||||
if (to.path === '/') {
|
||||
next('/home')
|
||||
return
|
||||
}
|
||||
|
||||
// 已登录且访问合法页面,正常放行
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// import router from './index' // 导入你的路由实例(与index.ts对应)
|
||||
// import { ElMessage } from 'element-plus'
|
||||
// import { useAuthStore } from '@/stores/auth' // 你的Pinia auth store路径
|
||||
|
||||
// // 路由前置守卫:每次路由跳转前执行
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// const authStore = useAuthStore() // 获取auth store实例
|
||||
// const token = authStore.getToken() // 调用你的getToken()方法获取token
|
||||
|
||||
// // 1. 如果访问登录页
|
||||
// if (to.path === '/login') {
|
||||
// // 已登录状态下访问登录页,自动跳转到首页/dashboard
|
||||
// if (token) {
|
||||
// next('/dashboard')
|
||||
// ElMessage.success('已登录,自动跳转')
|
||||
// } else {
|
||||
// // 未登录,正常进入登录页
|
||||
// next()
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// // 2. 访问非登录页,但未登录(无token)
|
||||
// if (!token) {
|
||||
// ElMessage.warning('请先登录')
|
||||
// next('/login') // 强制跳转到登录页
|
||||
// return
|
||||
// }
|
||||
|
||||
// // 3. 访问根路径,自动跳转到dashboard
|
||||
// if (to.path === '/') {
|
||||
// next('/dashboard')
|
||||
// return
|
||||
// }
|
||||
|
||||
// // 4. 已登录且访问合法页面,正常放行
|
||||
// next()
|
||||
// })
|
||||
@@ -30,6 +30,38 @@ const TokenManager = {
|
||||
// 删除默认请求头
|
||||
delete request.defaults.headers.common['Authorization']
|
||||
},
|
||||
|
||||
// 初始化token(应用启动时调用)
|
||||
initToken() {
|
||||
const token = this.getToken()
|
||||
if (token) {
|
||||
request.defaults.headers.common['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// 应用启动时初始化token
|
||||
TokenManager.initToken()
|
||||
|
||||
// 登录过期提示防重复标志
|
||||
let isLoginExpiredShown = false
|
||||
|
||||
// 处理登录过期(防重复提示)
|
||||
const handleLoginExpired = () => {
|
||||
if (!isLoginExpiredShown) {
|
||||
isLoginExpiredShown = true
|
||||
// 清除token
|
||||
TokenManager.removeToken()
|
||||
// 显示提示消息
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
// 跳转到登录页
|
||||
router.replace('/login').finally(() => {
|
||||
// 延迟重置标志位,确保跳转完成后再允许下次提示
|
||||
setTimeout(() => {
|
||||
isLoginExpiredShown = false
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
@@ -61,28 +93,65 @@ request.interceptors.response.use(
|
||||
|
||||
// 处理业务错误码
|
||||
if (data.code === 302) {
|
||||
// 清除token
|
||||
TokenManager.removeToken()
|
||||
// 跳转到登录页
|
||||
router.replace('/login')
|
||||
// 处理登录过期(防重复提示)
|
||||
handleLoginExpired()
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
// 其他错误码
|
||||
if (data.code !== 0 && data.code !== undefined) {
|
||||
ElMessage.error(data.message || data.msg || '请求失败')
|
||||
// 检查是否是状态刷新相关的请求,如果是则静默处理某些错误
|
||||
const url = response.config?.url || ''
|
||||
const isStatusRefreshRequest = url.includes('/plc/plcStatus') || url.includes('/plc/getModel')
|
||||
const errorMsg = data.message || data.msg || ''
|
||||
|
||||
// 如果是状态刷新请求,且错误消息包含特定关键词,则静默处理
|
||||
if (isStatusRefreshRequest && (
|
||||
errorMsg.includes('停止失败') ||
|
||||
errorMsg.includes('未连接') ||
|
||||
errorMsg.includes('连接失败') ||
|
||||
errorMsg.includes('获取状态失败') ||
|
||||
errorMsg.includes('获取模式失败')
|
||||
)) {
|
||||
// 静默处理,不弹出错误提示
|
||||
console.debug('状态刷新请求错误(已静默处理):', errorMsg)
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
ElMessage.error(errorMsg || '请求失败')
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
return data
|
||||
},
|
||||
(error) => {
|
||||
// 处理401未授权错误
|
||||
if (error.response?.status === 401) {
|
||||
// 处理登录过期(防重复提示)
|
||||
handleLoginExpired()
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 检查是否是状态刷新相关的请求
|
||||
const url = error.config?.url || ''
|
||||
const isStatusRefreshRequest = url.includes('/plc/plcStatus') || url.includes('/plc/getModel')
|
||||
const errorMsg = error.response?.data?.message || error.response?.data?.msg || '请求失败'
|
||||
|
||||
// 如果是状态刷新请求,且错误消息包含特定关键词,则静默处理
|
||||
if (isStatusRefreshRequest && (
|
||||
errorMsg.includes('停止失败') ||
|
||||
errorMsg.includes('未连接') ||
|
||||
errorMsg.includes('连接失败') ||
|
||||
errorMsg.includes('获取状态失败') ||
|
||||
errorMsg.includes('获取模式失败')
|
||||
)) {
|
||||
// 静默处理,不弹出错误提示
|
||||
console.debug('状态刷新请求错误(已静默处理):', errorMsg)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 网络错误或其他错误
|
||||
ElMessage.error(
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.msg ||
|
||||
'请求失败'
|
||||
)
|
||||
ElMessage.error(errorMsg)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
56
rc_autoplc_front/src/views/Home.vue
Normal file
56
rc_autoplc_front/src/views/Home.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<div class="welcome-content">
|
||||
<h1 class="welcome-title">欢迎使用中核404内照射上位机中控系统</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 首页组件,未来将展示数字孪生监控大屏
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-container {
|
||||
width: 100%;
|
||||
height: calc(92vh - 25px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
text-align: center;
|
||||
color: #080101;
|
||||
padding: 40px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 48px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
letter-spacing: 2px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.welcome-title {
|
||||
font-size: 32px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- 顶部导航栏 -->
|
||||
<el-header class="layout-header">
|
||||
<div class="header-left">
|
||||
<h1 class="project-title">北京融创智能仪器管理系统</h1>
|
||||
<h1 class="project-title">中核404内照射上位机中控系统</h1>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<el-dropdown @command="handleCommand">
|
||||
@@ -31,52 +31,74 @@
|
||||
router
|
||||
:collapse="false"
|
||||
>
|
||||
<el-menu-item index="/home">
|
||||
<el-icon><component :is="getMenuIcon('首页')" /></el-icon>
|
||||
<span>首页</span>
|
||||
</el-menu-item>
|
||||
<el-sub-menu index="system">
|
||||
<template #title>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>系统管理</span>
|
||||
<el-icon><component :is="getMenuIcon('系统管理')" /></el-icon>
|
||||
<span>系统信息管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/user">
|
||||
<el-icon><User /></el-icon>
|
||||
<el-icon><component :is="getMenuIcon('用户管理')" /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/role">
|
||||
<el-icon><Avatar /></el-icon>
|
||||
<el-icon><component :is="getMenuIcon('角色管理')" /></el-icon>
|
||||
<span>角色管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/permission">
|
||||
<el-icon><component :is="getMenuIcon('权限管理')" /></el-icon>
|
||||
<span>权限管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/department">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
<el-icon><component :is="getMenuIcon('部门管理')" /></el-icon>
|
||||
<span>部门管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/position">
|
||||
<el-icon><Briefcase /></el-icon>
|
||||
<span>职位管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/manage-log">
|
||||
<el-icon><Document /></el-icon>
|
||||
<el-icon><component :is="getMenuIcon('操作日志管理')" /></el-icon>
|
||||
<span>操作日志管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/user-role">
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<span>用户角色管理</span>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="island">
|
||||
<template #title>
|
||||
<el-icon><Grid /></el-icon>
|
||||
<span>业务管理</span>
|
||||
<el-icon><component :is="getMenuIcon('业务管理')" /></el-icon>
|
||||
<span>基础数据管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/island-info">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>功能岛管理</span>
|
||||
<el-menu-item index="/plc-devinfo">
|
||||
<el-icon><component :is="getMenuIcon('PLC设备管理')" /></el-icon>
|
||||
<span>PLC设备管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/devparam">
|
||||
<el-icon><component :is="getMenuIcon('动作参数管理')" /></el-icon>
|
||||
<span>动作参数管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/devinfo">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
<span>设备管理</span>
|
||||
<el-icon><component :is="getMenuIcon('动作管理')" /></el-icon>
|
||||
<span>动作管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/plc-devinfo">
|
||||
<el-icon><Connection /></el-icon>
|
||||
<span>PLC设备管理</span>
|
||||
<el-menu-item index="/island-info">
|
||||
<el-icon><component :is="getMenuIcon('功能岛管理')" /></el-icon>
|
||||
<span>功能岛管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/step-info">
|
||||
<el-icon><component :is="getMenuIcon('流程创建')" /></el-icon>
|
||||
<span>SOP管理</span>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="flow">
|
||||
<template #title>
|
||||
<el-icon><component :is="getMenuIcon('流程管理')" /></el-icon>
|
||||
<span>业务流程控制</span>
|
||||
</template>
|
||||
<el-menu-item index="/sample-injection">
|
||||
<el-icon><component :is="getMenuIcon('进样控制')" /></el-icon>
|
||||
<span>进样控制</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/goods-info">
|
||||
<el-icon><component :is="getMenuIcon('样品管理')" /></el-icon>
|
||||
<span>样品记录</span>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
@@ -94,7 +116,27 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { User, Setting, Avatar, OfficeBuilding, Briefcase, Document, CaretBottom, UserFilled, Grid, Monitor, Connection, Plus } from '@element-plus/icons-vue'
|
||||
import {
|
||||
User,
|
||||
Setting,
|
||||
Avatar,
|
||||
OfficeBuilding,
|
||||
Briefcase,
|
||||
Document,
|
||||
CaretBottom,
|
||||
UserFilled,
|
||||
Key,
|
||||
Grid,
|
||||
Monitor,
|
||||
Connection,
|
||||
List,
|
||||
EditPen,
|
||||
Files,
|
||||
Box,
|
||||
Operation,
|
||||
Tools,
|
||||
House,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -111,6 +153,40 @@ const activeMenu = computed(() => {
|
||||
return route.path
|
||||
})
|
||||
|
||||
// 根据菜单名称获取图标
|
||||
const getMenuIcon = (menuName: string) => {
|
||||
const iconMap: Record<string, any> = {
|
||||
// 一级
|
||||
首页: House,
|
||||
系统管理: Setting,
|
||||
业务管理: Grid,
|
||||
流程管理: List,
|
||||
|
||||
// 系统管理
|
||||
用户管理: User,
|
||||
角色管理: Avatar,
|
||||
部门管理: OfficeBuilding,
|
||||
权限管理: Key,
|
||||
职位管理: Briefcase,
|
||||
操作日志管理: Document,
|
||||
用户角色管理: UserFilled,
|
||||
|
||||
// 业务管理
|
||||
PLC设备管理: Connection,
|
||||
动作参数管理: Tools,
|
||||
动作管理: Monitor,
|
||||
功能岛管理: Grid,
|
||||
|
||||
// 流程管理
|
||||
PLC设备控制: Operation,
|
||||
样品管理: Box,
|
||||
'标准流程管理': Files, // 文件图标,适合标准流程管理
|
||||
'流程创建': EditPen,
|
||||
进样控制: Operation,
|
||||
}
|
||||
return iconMap[menuName] || EditPen
|
||||
}
|
||||
|
||||
// 处理下拉菜单命令
|
||||
const handleCommand = (command: string) => {
|
||||
if (command === 'logout') {
|
||||
@@ -122,8 +198,8 @@ const handleCommand = (command: string) => {
|
||||
authStore.removeToken()
|
||||
localStorage.removeItem('username')
|
||||
ElMessage.success('退出成功')
|
||||
// 跳转到登录页(如果有登录页)
|
||||
// router.push('/login')
|
||||
// 跳转到登录页
|
||||
router.push('/login')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
@@ -132,7 +208,7 @@ const handleCommand = (command: string) => {
|
||||
|
||||
<style scoped>
|
||||
.layout-container {
|
||||
height: 100vh;
|
||||
height: 98vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -207,7 +283,9 @@ const handleCommand = (command: string) => {
|
||||
.layout-main {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
317
rc_autoplc_front/src/views/Login.vue
Normal file
317
rc_autoplc_front/src/views/Login.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<div class="login-header">
|
||||
<h2>中核404内照射上位机中控系统</h2>
|
||||
<p>用户登录</p>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
label-width="0"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入用户名(2-16位的字母、数字、下划线或减号)"
|
||||
size="large"
|
||||
:prefix-icon="User"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
:prefix-icon="Lock"
|
||||
show-password
|
||||
clearable
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<div class="button-row">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
class="login-button"
|
||||
:loading="loginLoading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
<el-button
|
||||
size="large"
|
||||
class="register-button"
|
||||
@click="showRegisterDialog = true"
|
||||
>
|
||||
注册
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 注册弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showRegisterDialog"
|
||||
title="用户注册"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="registerFormRef"
|
||||
:model="registerForm"
|
||||
:rules="registerRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input
|
||||
v-model="registerForm.username"
|
||||
placeholder="2-16位的字母、数字、下划线或减号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="registerForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="registerForm.confirmPassword"
|
||||
type="password"
|
||||
placeholder="请再次输入密码"
|
||||
show-password
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showRegisterDialog = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="registerLoading"
|
||||
@click="handleRegister"
|
||||
>
|
||||
注册
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 { User, Lock } from '@element-plus/icons-vue'
|
||||
import { userlogin, userregister } from '@/api/system/user'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 登录表单
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
const loginLoading = ref(false)
|
||||
|
||||
// 注册表单
|
||||
const registerFormRef = ref<FormInstance>()
|
||||
const registerForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
const registerLoading = ref(false)
|
||||
const showRegisterDialog = ref(false)
|
||||
|
||||
// 登录表单验证规则
|
||||
const loginRules: FormRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_-]{2,16}$/,
|
||||
message: '用户名必须是2-16位的字母、数字、下划线或减号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
// 注册表单验证规则
|
||||
const validateConfirmPassword = (rule: any, value: string, callback: Function) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== registerForm.password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const registerRules: FormRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_-]{2,16}$/,
|
||||
message: '用户名必须是2-16位的字母、数字、下划线或减号',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
// 处理登录
|
||||
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)
|
||||
|
||||
// 根据图片,返回的data字段直接是token字符串
|
||||
if (response.code === 0 && response.data) {
|
||||
const token = response.data
|
||||
// 存储token
|
||||
authStore.setToken(token)
|
||||
// 存储用户名
|
||||
localStorage.setItem('username', loginForm.username)
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
// 跳转到首页
|
||||
router.push('/home')
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 错误提示已在全局响应拦截中处理,这里不重复弹出
|
||||
console.warn('login error:', error)
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理注册
|
||||
const handleRegister = async () => {
|
||||
if (!registerFormRef.value) return
|
||||
|
||||
await registerFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
registerLoading.value = true
|
||||
try {
|
||||
const response: any = await userregister(registerForm.username, registerForm.password)
|
||||
|
||||
if (response.code === 0) {
|
||||
ElMessage.success('注册成功,请登录')
|
||||
// 关闭注册弹窗
|
||||
showRegisterDialog.value = false
|
||||
// 清空注册表单
|
||||
if (registerFormRef.value) {
|
||||
registerFormRef.value.resetFields()
|
||||
}
|
||||
// 自动填充登录表单的用户名
|
||||
loginForm.username = registerForm.username
|
||||
} else {
|
||||
ElMessage.error(response.message || '注册失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '注册失败')
|
||||
} finally {
|
||||
registerLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.login-box {
|
||||
width: 450px;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.login-form :deep(.el-form-item) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-button,
|
||||
.register-button {
|
||||
flex: 1 1 0;
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="dept-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>
|
||||
<div class="dept-container">
|
||||
<!-- 左侧树形控件 -->
|
||||
<el-card class="tree-card" shadow="never">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="plc-devinfo-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>PLC设备管理</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<!-- 搜索栏 -->
|
||||
<el-card class="search-card" shadow="never">
|
||||
<div class="search-bar">
|
||||
@@ -12,18 +17,6 @@
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备状态">
|
||||
<el-select
|
||||
v-model="queryForm.status"
|
||||
placeholder="请选择设备状态"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option label="空闲" :value="0" />
|
||||
<el-option label="运行" :value="1" />
|
||||
<el-option label="故障" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
@@ -73,18 +66,11 @@
|
||||
{{ row.protocolType || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="company" label="公司名" min-width="120" show-overflow-tooltip>
|
||||
<el-table-column prop="company" label="设备类型" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.company || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)" size="small">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="devDesc" label="描述" min-width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.devDesc || '暂无' }}
|
||||
@@ -156,7 +142,7 @@
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备型号">
|
||||
<el-form-item label="设备型号" prop="devModel">
|
||||
<el-input
|
||||
v-model="formData.devModel"
|
||||
disabled
|
||||
@@ -186,12 +172,11 @@
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名" prop="company">
|
||||
<el-input
|
||||
v-model="formData.company"
|
||||
placeholder="请输入公司名"
|
||||
clearable
|
||||
/>
|
||||
<el-form-item label="设备类型" prop="company">
|
||||
<el-radio-group v-model="formData.company">
|
||||
<el-radio label="主站">主站</el-radio>
|
||||
<el-radio label="从站">从站</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="devDesc">
|
||||
<el-input
|
||||
@@ -247,7 +232,6 @@ const total = ref(0)
|
||||
// 查询表单
|
||||
const queryForm = reactive({
|
||||
devName: '',
|
||||
status: undefined as number | undefined,
|
||||
})
|
||||
|
||||
// 分页
|
||||
@@ -268,19 +252,108 @@ const formData = reactive({
|
||||
devName: '',
|
||||
devModel: 'PLC',
|
||||
ipAddr: '',
|
||||
port: undefined as number | undefined,
|
||||
protocolType: '',
|
||||
company: '',
|
||||
port: 502 as number | undefined,
|
||||
protocolType: 'Modelbus',
|
||||
company: '从站',
|
||||
devDesc: '',
|
||||
remark: '',
|
||||
status: 0, // 状态自动设置为0
|
||||
status: 0, // 状态默认为0
|
||||
runModel: 0, // 运行模式默认为0
|
||||
})
|
||||
|
||||
// IP地址校验函数
|
||||
const validateIpAddr = (_rule: any, value: any, callback: any) => {
|
||||
if (!value || String(value).trim() === '') {
|
||||
callback(new Error('请输入IP地址'))
|
||||
return
|
||||
}
|
||||
|
||||
const ipStr = String(value).trim().toLowerCase()
|
||||
|
||||
// 禁止本机IP格式
|
||||
if (ipStr === 'localhost' || ipStr === '127.0.0.1') {
|
||||
callback(new Error('不允许使用本机IP地址(localhost或127.0.0.1)'))
|
||||
return
|
||||
}
|
||||
|
||||
// IPv4地址格式校验:xxx.xxx.xxx.xxx,每段0-255
|
||||
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
|
||||
const match = ipStr.match(ipv4Regex)
|
||||
|
||||
if (!match) {
|
||||
callback(new Error('请输入有效的IP地址格式(如:192.168.1.1)'))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查每段数字是否在0-255范围内
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const segment = match[i]
|
||||
if (!segment) {
|
||||
callback(new Error('IP地址格式错误'))
|
||||
return
|
||||
}
|
||||
const num = parseInt(segment, 10)
|
||||
if (num < 0 || num > 255) {
|
||||
callback(new Error('IP地址每段数字应在0-255范围内'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 再次检查是否为127.0.0.1(防止用户输入其他格式的127.0.0.1)
|
||||
if (match[1] === '127' && match[2] === '0' && match[3] === '0' && match[4] === '1') {
|
||||
callback(new Error('不允许使用本机IP地址(127.0.0.1)'))
|
||||
return
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
devName: [
|
||||
{ required: true, message: '请输入设备名称', trigger: 'blur' },
|
||||
],
|
||||
devModel: [
|
||||
{ required: true, message: '设备型号不能为空', trigger: 'blur' },
|
||||
],
|
||||
ipAddr: [
|
||||
{ required: true, validator: validateIpAddr, trigger: ['blur', 'change'] },
|
||||
],
|
||||
port: [
|
||||
{ required: true, message: '请输入端口', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, max: 65535, message: '端口范围应在1-65535之间', trigger: 'blur' },
|
||||
],
|
||||
protocolType: [
|
||||
{ required: true, message: '请输入协议类型', trigger: 'blur' },
|
||||
],
|
||||
company: [
|
||||
{
|
||||
validator: (_rule: any, value: any, callback: any) => {
|
||||
if (!value || String(value).trim() === '') {
|
||||
callback(new Error('请输入设备类型'))
|
||||
return
|
||||
}
|
||||
const normalized = String(value).trim()
|
||||
if (normalized !== '主站' && normalized !== '从站') {
|
||||
callback(new Error('设备类型只能为主站或从站'))
|
||||
return
|
||||
}
|
||||
if (normalized === '主站') {
|
||||
const conflict = deviceList.value.find((item: any) => {
|
||||
const isMaster = item?.company === '主站'
|
||||
const isSame = isEdit.value && formData.id && String(item.id) === String(formData.id)
|
||||
return isMaster && !isSame
|
||||
})
|
||||
if (conflict) {
|
||||
callback(new Error('主站PLC只能有一个,请先修改或删除已有主站设备'))
|
||||
return
|
||||
}
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// 获取序号
|
||||
@@ -288,21 +361,6 @@ const getIndex = (index: number) => {
|
||||
return (pagination.pageNum - 1) * pagination.pageSize + index + 1
|
||||
}
|
||||
|
||||
// 获取状态类型
|
||||
const getStatusType = (status: number | null | undefined) => {
|
||||
if (status === 0) return 'success' // 空闲 - 绿色
|
||||
if (status === 1) return 'warning' // 运行 - 黄色
|
||||
if (status === 4) return 'danger' // 故障 - 红色
|
||||
return 'info'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: number | null | undefined) => {
|
||||
if (status === 0) return '空闲'
|
||||
if (status === 1) return '运行'
|
||||
if (status === 4) return '故障'
|
||||
return '未知'
|
||||
}
|
||||
|
||||
// 获取设备列表(只查询devModel为PLC的设备)
|
||||
const getDeviceList = async () => {
|
||||
@@ -318,10 +376,6 @@ const getDeviceList = async () => {
|
||||
if (queryForm.devName) {
|
||||
params.devName = queryForm.devName
|
||||
}
|
||||
// 设备状态精确查询
|
||||
if (queryForm.status !== undefined && queryForm.status !== null) {
|
||||
params.status = queryForm.status
|
||||
}
|
||||
|
||||
const res: any = await devInfolist(params)
|
||||
|
||||
@@ -366,7 +420,6 @@ const handleSearch = () => {
|
||||
// 重置查询
|
||||
const resetSearch = () => {
|
||||
queryForm.devName = ''
|
||||
queryForm.status = undefined
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
@@ -396,12 +449,13 @@ const resetForm = () => {
|
||||
formData.devName = ''
|
||||
formData.devModel = 'PLC'
|
||||
formData.ipAddr = ''
|
||||
formData.port = undefined
|
||||
formData.protocolType = ''
|
||||
formData.company = ''
|
||||
formData.port = 502
|
||||
formData.protocolType = 'Modelbus'
|
||||
formData.company = '从站'
|
||||
formData.devDesc = ''
|
||||
formData.remark = ''
|
||||
formData.status = 0
|
||||
formData.status = 0 // 状态默认为0
|
||||
formData.runModel = 0 // 运行模式默认为0
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
@@ -417,12 +471,13 @@ const handleEdit = async (item: any) => {
|
||||
formData.devName = data.devName ?? ''
|
||||
formData.devModel = data.devModel ?? 'PLC'
|
||||
formData.ipAddr = data.ipAddr ?? ''
|
||||
formData.port = data.port ?? undefined
|
||||
formData.protocolType = data.protocolType ?? ''
|
||||
formData.company = data.company ?? ''
|
||||
formData.port = data.port ?? 502
|
||||
formData.protocolType = data.protocolType ?? 'Modelbus'
|
||||
formData.company = data.company ?? '从站'
|
||||
formData.devDesc = data.devDesc ?? ''
|
||||
formData.remark = data.remark ?? ''
|
||||
formData.status = data.status ?? 0
|
||||
formData.runModel = data.runModel ?? 0
|
||||
|
||||
isEdit.value = true
|
||||
drawerTitle.value = '编辑PLC设备'
|
||||
@@ -475,25 +530,36 @@ const handleSubmit = async () => {
|
||||
await formRef.value.validate()
|
||||
submitting.value = true
|
||||
|
||||
// 构建提交数据,只包含非空字段(根据后端要求:非空则入库/更新)
|
||||
// 构建提交数据
|
||||
const submitData: any = {
|
||||
devName: formData.devName,
|
||||
devModel: 'PLC', // 设备型号固定为PLC
|
||||
status: 0, // 状态自动设置为0
|
||||
ipAddr: formData.ipAddr, // 必填项
|
||||
port: formData.port, // 必填项
|
||||
protocolType: formData.protocolType, // 必填项
|
||||
status: isEdit.value ? formData.status : 0, // 新增时状态为0,编辑时使用原值
|
||||
runModel: isEdit.value ? formData.runModel : 0, // 新增时运行模式为0,编辑时使用原值
|
||||
}
|
||||
|
||||
// 添加其他字段(如果非空)
|
||||
if (formData.ipAddr) {
|
||||
submitData.ipAddr = formData.ipAddr
|
||||
}
|
||||
if (formData.port !== undefined && formData.port !== null) {
|
||||
submitData.port = formData.port
|
||||
}
|
||||
if (formData.protocolType) {
|
||||
submitData.protocolType = formData.protocolType
|
||||
}
|
||||
// 添加其他可选字段(如果非空)
|
||||
if (formData.company) {
|
||||
submitData.company = formData.company
|
||||
const normalized = String(formData.company).trim()
|
||||
if (normalized !== '主站' && normalized !== '从站') {
|
||||
ElMessage.warning('设备类型只能为主站或从站')
|
||||
return
|
||||
}
|
||||
if (normalized === '主站') {
|
||||
const conflict = deviceList.value.find((item: any) => {
|
||||
const isMaster = item?.company === '主站'
|
||||
const isSame = isEdit.value && formData.id && String(item.id) === String(formData.id)
|
||||
return isMaster && !isSame
|
||||
})
|
||||
if (conflict) {
|
||||
ElMessage.warning('主站PLC只能有一个,请先修改或删除已有主站设备')
|
||||
return
|
||||
}
|
||||
}
|
||||
submitData.company = normalized
|
||||
}
|
||||
if (formData.devDesc) {
|
||||
submitData.devDesc = formData.devDesc
|
||||
|
||||
1576
rc_autoplc_front/src/views/devparam/index.vue
Normal file
1576
rc_autoplc_front/src/views/devparam/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1091
rc_autoplc_front/src/views/flowinfo/index.vue
Normal file
1091
rc_autoplc_front/src/views/flowinfo/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1056
rc_autoplc_front/src/views/goods/index.vue
Normal file
1056
rc_autoplc_front/src/views/goods/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="island-info-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">
|
||||
@@ -37,6 +42,7 @@
|
||||
v-for="(item, index) in islandList"
|
||||
:key="item.id"
|
||||
class="island-card"
|
||||
:style="{ animationDelay: `${index * 50}ms` }"
|
||||
>
|
||||
<!-- 左侧步骤编号和过滤器图标 -->
|
||||
<div class="card-left">
|
||||
@@ -87,8 +93,8 @@
|
||||
<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"
|
||||
:page-sizes="[9]"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
background
|
||||
@current-change="handleCurrentChange"
|
||||
@@ -109,7 +115,7 @@
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
label-width="120px"
|
||||
label-position="left"
|
||||
>
|
||||
<el-form-item label="功能岛名称" prop="islandName">
|
||||
@@ -127,6 +133,16 @@
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="PLC映射地址" prop="plcAddr">
|
||||
<el-input-number
|
||||
v-model="formData.plcAddr"
|
||||
placeholder="请输入PLC映射地址"
|
||||
:min="0"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="功能岛Logo">
|
||||
<div class="logo-preview">
|
||||
<el-icon class="preview-icon" :size="60">
|
||||
@@ -144,41 +160,39 @@
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="绑定设备">
|
||||
<div class="bind-device-wrapper">
|
||||
<el-button type="primary" @click="handleBindDevice">
|
||||
<el-icon><Plus /></el-icon>
|
||||
绑定设备
|
||||
</el-button>
|
||||
<div v-if="boundDevices.length > 0" class="bound-devices-table-wrapper">
|
||||
<el-table
|
||||
:data="boundDevices"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
style="width: 100%; margin-top: 12px"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" :index="getBoundDeviceIndex" />
|
||||
<el-table-column prop="devName" label="设备名称" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.devName || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)" size="small">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="devDesc" label="描述" min-width="150" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.devDesc || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-form-item v-if="isEdit" label="动作信息">
|
||||
<div class="device-list-wrapper">
|
||||
<el-table
|
||||
v-if="deviceList.length > 0"
|
||||
v-loading="deviceLoading"
|
||||
:data="deviceList"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" :index="getDeviceIndex" />
|
||||
<el-table-column prop="devName" label="动作名称" width="180" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.devName || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="plcAddr" label="PLC映射地址" width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.plcAddr ?? '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)" size="small">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="deviceList.length === 0 && !deviceLoading" class="no-devices">
|
||||
该功能岛暂无动作
|
||||
</div>
|
||||
<div v-else class="no-bound-devices">暂无绑定设备</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -192,66 +206,6 @@
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 绑定设备对话框 -->
|
||||
<el-dialog
|
||||
v-model="bindDeviceDialogVisible"
|
||||
:title="isEdit ? '绑定/解绑设备' : '绑定设备'"
|
||||
width="800px"
|
||||
:before-close="handleBindDialogClose"
|
||||
>
|
||||
<el-table
|
||||
ref="deviceTableRef"
|
||||
v-loading="deviceLoading"
|
||||
:data="deviceListForBind"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
@selection-change="handleDeviceSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column type="index" label="序号" width="60" :index="getDeviceIndex" />
|
||||
<el-table-column prop="devName" label="设备名称" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.devName || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="devModel" label="设备型号" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.devModel || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ipAddr" label="IP地址" min-width="120" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.ipAddr || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="port" label="端口" width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.port !== null && row.port !== undefined ? row.port : '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="protocolType" label="协议" min-width="100" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.protocolType || '暂无' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)" size="small">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleBindDialogClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSaveDeviceBind" :loading="savingDeviceBind">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -281,6 +235,23 @@ import {
|
||||
Monitor,
|
||||
Cpu,
|
||||
DataAnalysis,
|
||||
Upload,
|
||||
Download,
|
||||
MagicStick,
|
||||
TakeawayBox,
|
||||
CollectionTag,
|
||||
Position,
|
||||
Dish,
|
||||
Bowl,
|
||||
HomeFilled,
|
||||
Odometer,
|
||||
Sunrise,
|
||||
ScaleToOriginal,
|
||||
// 下面这些图标在项目其它页面已使用过,确保依赖里一定存在
|
||||
Document,
|
||||
Files,
|
||||
List,
|
||||
EditPen,
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
islandInfoadd,
|
||||
@@ -289,13 +260,12 @@ import {
|
||||
islandInfolist,
|
||||
islandInfobyid,
|
||||
} from '@/api/tb/islandinfo'
|
||||
import { devselect, devInfoupd } from '@/api/tb/devinfo'
|
||||
import { devselect } from '@/api/tb/devinfo'
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const deviceLoading = ref(false)
|
||||
const savingDeviceBind = ref(false)
|
||||
|
||||
// 功能岛列表
|
||||
const islandList = ref<any[]>([])
|
||||
@@ -309,7 +279,7 @@ const queryForm = reactive({
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 9,
|
||||
})
|
||||
|
||||
// 抽屉相关
|
||||
@@ -323,15 +293,12 @@ const formData = reactive({
|
||||
id: undefined,
|
||||
islandName: '',
|
||||
islandCode: '',
|
||||
plcAddr: undefined as number | undefined,
|
||||
islandDesc: '',
|
||||
})
|
||||
|
||||
// 绑定设备相关
|
||||
const bindDeviceDialogVisible = ref(false)
|
||||
const deviceListForBind = ref<any[]>([])
|
||||
const deviceTableRef = ref()
|
||||
const selectedDevices = ref<any[]>([])
|
||||
const boundDevices = ref<any[]>([])
|
||||
// 动作列表相关
|
||||
const deviceList = ref<any[]>([])
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
@@ -341,50 +308,59 @@ const formRules = {
|
||||
islandCode: [
|
||||
{ required: true, message: '请输入功能岛编码', trigger: 'blur' },
|
||||
],
|
||||
plcAddr: [
|
||||
{ required: true, message: '请输入PLC映射地址', trigger: 'change' },
|
||||
],
|
||||
}
|
||||
|
||||
// 获取功能岛图标(根据名称自动生成)
|
||||
const getIslandIcon = (name: string) => {
|
||||
if (!name) return Box
|
||||
|
||||
// 根据名称关键词匹配图标
|
||||
|
||||
const nameLower = name.toLowerCase()
|
||||
if (nameLower.includes('加液') || nameLower.includes('ph') || nameLower.includes('涡旋')) {
|
||||
return Watermelon
|
||||
} else if (nameLower.includes('水浴') || nameLower.includes('恒温')) {
|
||||
return Sunny
|
||||
} else if (nameLower.includes('震荡')) {
|
||||
return Connection
|
||||
} else if (nameLower.includes('超声')) {
|
||||
return Histogram
|
||||
} else if (nameLower.includes('离心')) {
|
||||
return RefreshRight
|
||||
} else if (nameLower.includes('移液')) {
|
||||
return Aim
|
||||
} else if (nameLower.includes('萃取')) {
|
||||
return Goblet
|
||||
} else if (nameLower.includes('氮吹')) {
|
||||
return WindPower
|
||||
} else if (nameLower.includes('过膜') || nameLower.includes('过滤')) {
|
||||
return Filter
|
||||
} else if (nameLower.includes('人工')) {
|
||||
return User
|
||||
} else if (nameLower.includes('系统') || nameLower.includes('管理')) {
|
||||
return Setting
|
||||
} else if (nameLower.includes('工具')) {
|
||||
return Tools
|
||||
} else if (nameLower.includes('数据') || nameLower.includes('分析')) {
|
||||
return DataAnalysis
|
||||
} else if (nameLower.includes('监控') || nameLower.includes('显示')) {
|
||||
return Monitor
|
||||
} else if (nameLower.includes('处理') || nameLower.includes('计算')) {
|
||||
return Cpu
|
||||
} else if (nameLower.includes('网格') || nameLower.includes('布局')) {
|
||||
return Grid
|
||||
}
|
||||
|
||||
// 默认图标
|
||||
return Box
|
||||
|
||||
// 预设功能岛与唯一图标的映射(避免重复)
|
||||
const iconRules = [
|
||||
{ keywords: ['称重', '称重岛', '称量', '重量'], icon: ScaleToOriginal },
|
||||
{ keywords: ['共沉淀', '共沉淀岛', '沉淀'], icon: CollectionTag },
|
||||
{ keywords: ['过柱', '过柱岛', '柱'], icon: Histogram },
|
||||
{ keywords: ['电沉积', '电沉积岛', '沉积'], icon: Cpu },
|
||||
{ keywords: ['烧白', '烧白岛', '焚烧', '灰化'], icon: Sunrise },
|
||||
{ keywords: ['定容', '定容岛', '补液', '补容'], icon: Goblet },
|
||||
{ keywords: ['涡旋'], icon: MagicStick },
|
||||
{ keywords: ['加液', '加样'], icon: Watermelon },
|
||||
{ keywords: ['进样'], icon: Upload },
|
||||
{ keywords: ['分液'], icon: TakeawayBox },
|
||||
{ keywords: ['浓缩'], icon: DataAnalysis },
|
||||
{ keywords: ['移上清'], icon: Position },
|
||||
{ keywords: ['取液'], icon: Download },
|
||||
{ keywords: ['金属浴', '金属'], icon: Bowl },
|
||||
{ keywords: ['干燥', '硫酸钠'], icon: Files },
|
||||
{ keywords: ['温室', '静置'], icon: HomeFilled },
|
||||
{ keywords: ['ph', '酸碱'], icon: Odometer },
|
||||
{ keywords: ['出样'], icon: List },
|
||||
{ keywords: ['水浴', '恒温'], icon: Sunny },
|
||||
{ keywords: ['震荡'], icon: Operation },
|
||||
{ keywords: ['超声'], icon: Connection },
|
||||
{ keywords: ['离心'], icon: RefreshRight },
|
||||
{ keywords: ['移液'], icon: Aim },
|
||||
{ keywords: ['萃取'], icon: Dish },
|
||||
{ keywords: ['氮吹'], icon: WindPower },
|
||||
{ keywords: ['过膜', '过滤'], icon: Filter },
|
||||
{ keywords: ['人工'], icon: User },
|
||||
{ keywords: ['系统', '管理'], icon: Setting },
|
||||
{ keywords: ['工具'], icon: EditPen },
|
||||
{ keywords: ['数据', '分析'], icon: Document },
|
||||
{ keywords: ['监控', '显示'], icon: Monitor },
|
||||
{ keywords: ['处理', '计算'], icon: Tools },
|
||||
{ keywords: ['网格', '布局'], icon: Grid },
|
||||
]
|
||||
|
||||
const matched = iconRules.find(rule =>
|
||||
rule.keywords.some(keyword => nameLower.includes(keyword.toLowerCase()))
|
||||
)
|
||||
|
||||
return matched ? matched.icon : Box
|
||||
}
|
||||
|
||||
// 处理名称输入
|
||||
@@ -408,16 +384,11 @@ const getStatusText = (status: number | null | undefined) => {
|
||||
return '未知'
|
||||
}
|
||||
|
||||
// 获取设备序号
|
||||
// 获取动作序号
|
||||
const getDeviceIndex = (index: number) => {
|
||||
return index + 1
|
||||
}
|
||||
|
||||
// 获取已绑定设备序号
|
||||
const getBoundDeviceIndex = (index: number) => {
|
||||
return index + 1
|
||||
}
|
||||
|
||||
// 获取功能岛列表
|
||||
const getIslandList = async () => {
|
||||
try {
|
||||
@@ -494,9 +465,9 @@ const resetForm = () => {
|
||||
formData.id = undefined
|
||||
formData.islandName = ''
|
||||
formData.islandCode = ''
|
||||
formData.plcAddr = undefined
|
||||
formData.islandDesc = ''
|
||||
boundDevices.value = []
|
||||
selectedDevices.value = []
|
||||
deviceList.value = []
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
@@ -519,12 +490,13 @@ const handleEdit = async (item: any) => {
|
||||
formData.id = data.id ?? item.id
|
||||
formData.islandName = data.islandName ?? data.name ?? ''
|
||||
formData.islandCode = data.islandCode ?? data.code ?? ''
|
||||
formData.plcAddr = data.plcAddr !== undefined && data.plcAddr !== null ? Number(data.plcAddr) : undefined
|
||||
// 优先使用后端的 desc 字段,兼容其他可能的字段名
|
||||
formData.islandDesc = data.desc ?? data.islandDesc ?? data.description ?? ''
|
||||
|
||||
// 加载已绑定的设备
|
||||
// 加载该功能岛下的动作列表
|
||||
if (formData.id) {
|
||||
await loadBoundDevices(formData.id)
|
||||
await loadDeviceList(formData.id)
|
||||
}
|
||||
|
||||
isEdit.value = true
|
||||
@@ -541,39 +513,51 @@ const handleEdit = async (item: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载已绑定的设备
|
||||
const loadBoundDevices = async (islandId: number | string) => {
|
||||
// 加载该功能岛下的动作列表
|
||||
const loadDeviceList = async (islandId: number | string) => {
|
||||
try {
|
||||
deviceLoading.value = true
|
||||
const res: any = await devselect({})
|
||||
const allDevices = res?.data ?? res ?? []
|
||||
if (Array.isArray(allDevices)) {
|
||||
boundDevices.value = allDevices.filter((device: any) =>
|
||||
device.islandId && (device.islandId === islandId || device.islandId === String(islandId))
|
||||
)
|
||||
// 过滤出该功能岛下的动作,并排除PLC动作
|
||||
deviceList.value = allDevices.filter((device: any) => {
|
||||
const devModel = device.devModel || ''
|
||||
const isNotPlc = devModel.toUpperCase() !== 'PLC'
|
||||
const isBelongToIsland = device.islandId && (device.islandId === islandId || device.islandId === String(islandId))
|
||||
return isNotPlc && isBelongToIsland
|
||||
})
|
||||
} else {
|
||||
deviceList.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载已绑定设备失败:', error)
|
||||
boundDevices.value = []
|
||||
console.error('加载动作列表失败:', error)
|
||||
deviceList.value = []
|
||||
} finally {
|
||||
deviceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除功能岛
|
||||
const handleDelete = async (item: any) => {
|
||||
try {
|
||||
// 检查是否绑定了设备
|
||||
// 检查是否绑定了动作
|
||||
const res: any = await devselect({})
|
||||
const allDevices = res?.data ?? res ?? []
|
||||
const boundDevicesList = Array.isArray(allDevices)
|
||||
? allDevices.filter((device: any) =>
|
||||
device.islandId && (device.islandId === item.id || device.islandId === String(item.id))
|
||||
)
|
||||
? allDevices.filter((device: any) => {
|
||||
const devModel = device.devModel || ''
|
||||
const isNotPlc = devModel.toUpperCase() !== 'PLC'
|
||||
const isBelongToIsland = device.islandId && (device.islandId === item.id || device.islandId === String(item.id))
|
||||
return isNotPlc && isBelongToIsland
|
||||
})
|
||||
: []
|
||||
|
||||
if (boundDevicesList.length > 0) {
|
||||
const deviceNames = boundDevicesList.map((d: any) => d.devName || '未命名设备').join('、')
|
||||
const deviceNames = boundDevicesList.map((d: any) => d.devName || '未命名动作').join('、')
|
||||
const islandName = item.islandName || item.name || '未命名'
|
||||
await ElMessageBox.alert(
|
||||
`${islandName}已绑定${deviceNames},请先完成设备解绑`,
|
||||
`${islandName}下还有${deviceNames}等${boundDevicesList.length}个动作,请先完成动作解绑`,
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
@@ -623,11 +607,11 @@ const handleSubmit = async () => {
|
||||
const submitData: any = {
|
||||
islandName: formData.islandName,
|
||||
islandCode: formData.islandCode,
|
||||
plcAddr: formData.plcAddr,
|
||||
desc: formData.islandDesc || '', // 描述字段映射为 desc
|
||||
}
|
||||
|
||||
let res: any
|
||||
let newIslandId: any = null
|
||||
|
||||
if (isEdit.value && formData.id) {
|
||||
// 编辑 - 需要传入ID,其他字段可选(非空则更新)
|
||||
@@ -635,42 +619,12 @@ const handleSubmit = async () => {
|
||||
id: formData.id,
|
||||
...submitData,
|
||||
})
|
||||
newIslandId = formData.id
|
||||
} else {
|
||||
// 新增
|
||||
res = await islandInfoadd(submitData)
|
||||
// 获取新增后的ID
|
||||
if (res.code === '0' || res.code === 0) {
|
||||
newIslandId = res.data?.id || res.id
|
||||
}
|
||||
}
|
||||
|
||||
if (res.code === '0' || res.code === 0) {
|
||||
// 如果是新增模式且有绑定的设备,需要更新设备的islandId
|
||||
if (!isEdit.value && newIslandId && boundDevices.value.length > 0) {
|
||||
try {
|
||||
const bindPromises = boundDevices.value.map((device: any) => {
|
||||
return devInfoupd({
|
||||
id: device.id,
|
||||
devName: device.devName, // 必填字段
|
||||
islandId: newIslandId,
|
||||
})
|
||||
})
|
||||
const bindResults = await Promise.all(bindPromises)
|
||||
// 检查是否有失败的请求
|
||||
const failedResults = bindResults.filter((res: any) => {
|
||||
return res && (res.code !== '0' && res.code !== 0 && !res.success)
|
||||
})
|
||||
if (failedResults.length > 0) {
|
||||
console.warn('部分设备绑定失败:', failedResults)
|
||||
// 不阻止功能岛创建成功,只提示警告
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('绑定设备失败:', error)
|
||||
// 不阻止功能岛创建成功,只记录错误
|
||||
}
|
||||
}
|
||||
|
||||
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
|
||||
drawerVisible.value = false
|
||||
getIslandList()
|
||||
@@ -687,167 +641,6 @@ const handleSubmit = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 打开绑定设备对话框
|
||||
const handleBindDevice = async () => {
|
||||
try {
|
||||
deviceLoading.value = true
|
||||
const res: any = await devselect({})
|
||||
const allDevices = res?.data ?? res ?? []
|
||||
|
||||
if (Array.isArray(allDevices)) {
|
||||
if (isEdit.value && formData.id) {
|
||||
// 编辑模式:显示未绑定的设备 + 已绑定该功能岛的设备
|
||||
deviceListForBind.value = allDevices.filter((device: any) =>
|
||||
!device.islandId ||
|
||||
device.islandId === formData.id ||
|
||||
device.islandId === String(formData.id) ||
|
||||
device.islandId === 0 ||
|
||||
device.islandId === '0'
|
||||
)
|
||||
} else {
|
||||
// 新增模式:只显示未绑定的设备
|
||||
deviceListForBind.value = allDevices.filter((device: any) =>
|
||||
!device.islandId ||
|
||||
device.islandId === 0 ||
|
||||
device.islandId === '0'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
deviceListForBind.value = []
|
||||
}
|
||||
|
||||
bindDeviceDialogVisible.value = true
|
||||
|
||||
// 等待DOM更新后设置已选中的设备
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
if (deviceTableRef.value && isEdit.value && formData.id) {
|
||||
// 编辑模式:默认选中已绑定的设备
|
||||
const boundDeviceIds = boundDevices.value.map((d: any) => d.id)
|
||||
deviceListForBind.value.forEach((device: any) => {
|
||||
if (boundDeviceIds.includes(device.id)) {
|
||||
deviceTableRef.value.toggleRowSelection(device, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取设备列表失败:', error)
|
||||
ElMessage.error('获取设备列表失败')
|
||||
} finally {
|
||||
deviceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 设备选择变化
|
||||
const handleDeviceSelectionChange = (selection: any[]) => {
|
||||
selectedDevices.value = selection
|
||||
}
|
||||
|
||||
// 保存设备绑定
|
||||
const handleSaveDeviceBind = async () => {
|
||||
try {
|
||||
savingDeviceBind.value = true
|
||||
|
||||
// 获取当前选中的设备ID列表
|
||||
const selectedDeviceIds = selectedDevices.value.map((d: any) => d.id)
|
||||
|
||||
if (isEdit.value && formData.id) {
|
||||
// 编辑模式:立即保存绑定关系
|
||||
// 获取之前已绑定的设备ID列表
|
||||
const previousBoundDeviceIds = boundDevices.value.map((d: any) => d.id)
|
||||
|
||||
// 需要绑定的设备(新增的)
|
||||
const devicesToBind = selectedDeviceIds.filter((id: any) => !previousBoundDeviceIds.includes(id))
|
||||
|
||||
// 需要解绑的设备(取消选中的)
|
||||
const devicesToUnbind = previousBoundDeviceIds.filter((id: any) => !selectedDeviceIds.includes(id))
|
||||
|
||||
// 执行绑定操作
|
||||
const bindPromises = devicesToBind.map((deviceId: any) => {
|
||||
const device = deviceListForBind.value.find((d: any) => d.id === deviceId)
|
||||
if (device) {
|
||||
// 包含设备名称等必填字段
|
||||
return devInfoupd({
|
||||
id: deviceId,
|
||||
devName: device.devName, // 必填字段
|
||||
islandId: formData.id,
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
// 执行解绑操作
|
||||
const unbindPromises = devicesToUnbind.map((deviceId: any) => {
|
||||
const device = deviceListForBind.value.find((d: any) => d.id === deviceId) ||
|
||||
boundDevices.value.find((d: any) => d.id === deviceId)
|
||||
if (device) {
|
||||
// 包含设备名称等必填字段
|
||||
return devInfoupd({
|
||||
id: deviceId,
|
||||
devName: device.devName, // 必填字段
|
||||
islandId: 0, // 解绑时设置为0
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
// 执行所有操作
|
||||
const allPromises = [...bindPromises, ...unbindPromises]
|
||||
if (allPromises.length > 0) {
|
||||
const results = await Promise.all(allPromises)
|
||||
// 检查是否有失败的请求
|
||||
const failedResults = results.filter((res: any) => {
|
||||
if (!res) return false
|
||||
// 检查响应数据(可能是直接返回的数据对象)
|
||||
const data = res.data || res
|
||||
return data && (data.code !== '0' && data.code !== 0 && data.code !== undefined && !data.success)
|
||||
})
|
||||
if (failedResults.length > 0) {
|
||||
const firstFailed = failedResults[0]
|
||||
const errorData = firstFailed?.data || firstFailed
|
||||
throw new Error(errorData?.message || errorData?.msg || '部分设备绑定/解绑失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 更新已绑定设备列表
|
||||
await loadBoundDevices(formData.id)
|
||||
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
// 新增模式:只更新本地列表,不调用接口(等创建功能岛后再绑定)
|
||||
boundDevices.value = deviceListForBind.value.filter((device: any) =>
|
||||
selectedDeviceIds.includes(device.id)
|
||||
)
|
||||
ElMessage.success('已选择设备,将在创建功能岛后自动绑定')
|
||||
}
|
||||
|
||||
bindDeviceDialogVisible.value = false
|
||||
} catch (error: any) {
|
||||
console.error('保存设备绑定失败:', error)
|
||||
// 处理不同类型的错误
|
||||
let errorMessage = '保存设备绑定失败'
|
||||
if (error?.message) {
|
||||
errorMessage = error.message
|
||||
} else if (error?.response?.data?.message) {
|
||||
errorMessage = error.response.data.message
|
||||
} else if (error?.response?.data?.msg) {
|
||||
errorMessage = error.response.data.msg
|
||||
} else if (typeof error === 'string') {
|
||||
errorMessage = error
|
||||
}
|
||||
ElMessage.error(errorMessage)
|
||||
} finally {
|
||||
savingDeviceBind.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭绑定设备对话框
|
||||
const handleBindDialogClose = () => {
|
||||
bindDeviceDialogVisible.value = false
|
||||
selectedDevices.value = []
|
||||
if (deviceTableRef.value) {
|
||||
deviceTableRef.value.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭抽屉
|
||||
const handleDrawerClose = () => {
|
||||
@@ -931,6 +724,8 @@ onMounted(() => {
|
||||
min-height: 95px;
|
||||
height: 95px;
|
||||
border: 1px solid rgba(64, 158, 255, 0.1);
|
||||
animation: slideUpIn 0.4s ease-out forwards;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
|
||||
@@ -1062,15 +857,15 @@ onMounted(() => {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.bind-device-wrapper {
|
||||
:deep(.el-form-item__label) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-list-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bound-devices-table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-bound-devices {
|
||||
.no-devices {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
@@ -1081,10 +876,16 @@ onMounted(() => {
|
||||
border: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
// 功能岛卡片动画:从下往上滑动
|
||||
@keyframes slideUpIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,68 +1,94 @@
|
||||
<template>
|
||||
<div class="manage-log-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="90px">
|
||||
<el-form-item label="日志名称">
|
||||
<el-input
|
||||
v-model="queryForm.logName"
|
||||
placeholder="输入日志名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="日志类型">
|
||||
<el-input
|
||||
<el-select
|
||||
v-model="queryForm.logType"
|
||||
placeholder="输入日志类型"
|
||||
placeholder="请选择日志类型"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
style="width: 280px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in logTypeOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建人ID">
|
||||
<el-input
|
||||
|
||||
<el-form-item label="操作人">
|
||||
<el-select
|
||||
v-model="queryForm.createId"
|
||||
placeholder="输入创建人ID"
|
||||
placeholder="请选择操作人"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.userName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="录入时间">
|
||||
<el-date-picker
|
||||
v-model="queryForm.logWritetimeRange"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</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 :data="tableData" stripe border style="width: 100%" v-loading="loading">
|
||||
<el-table-column type="index" label="序号" width="80" />
|
||||
<el-table-column prop="logName" label="日志名称" min-width="150" />
|
||||
<el-table-column prop="logType" label="日志类型" min-width="120" />
|
||||
<el-table-column prop="logContent" label="日志内容" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="logType" label="日志类型" min-width="170" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span class="log-type-text">{{ scope.row.logType || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="logContent" label="日志内容" min-width="430" show-overflow-tooltip />
|
||||
<el-table-column prop="createId" label="操作人" min-width="140">
|
||||
<template #default="scope">
|
||||
{{ getUserName(scope.row.createId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="logWritetime" label="日志记录时间" min-width="180">
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.logWritetime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<el-table-column label="操作" width="120" 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 type="primary" link @click="openDetailDialog(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.pageNum"
|
||||
@@ -77,105 +103,74 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:title="drawerTitle"
|
||||
direction="rtl"
|
||||
:size="500"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
class="drawer-form"
|
||||
>
|
||||
<el-form-item v-if="isEdit" label="日志ID">
|
||||
<el-input v-model="form.id" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志名称" prop="logName">
|
||||
<el-input v-model="form.logName" placeholder="请输入日志名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志类型" prop="logType">
|
||||
<el-input v-model="form.logType" placeholder="请输入日志类型" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志内容" prop="logContent">
|
||||
<el-input
|
||||
v-model="form.logContent"
|
||||
type="textarea"
|
||||
placeholder="请输入日志内容"
|
||||
:rows="4"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="日志记录时间" prop="logWritetime">
|
||||
<el-date-picker
|
||||
v-model="form.logWritetime"
|
||||
type="datetime"
|
||||
placeholder="选择日志记录时间"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DDTHH:mm:ss.SSSZ"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</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>
|
||||
<el-dialog v-model="detailVisible" title="日志详情" width="700px">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="日志编号">{{ detailData.id ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="日志类型">{{ detailData.logType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="日志内容">{{ detailData.logContent || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">
|
||||
{{ getUserName(detailData.createId) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="日志记录时间">
|
||||
{{ formatDateTime(detailData.logWritetime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDateTime(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDateTime(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<el-button @click="drawerVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitForm">
|
||||
{{ isEdit ? '更新' : '提交' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</el-dialog>
|
||||
</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 {
|
||||
managelogadd,
|
||||
managelogbyid,
|
||||
managelogdel,
|
||||
manageloglist,
|
||||
managelogupd,
|
||||
} from '@/api/system/manage-log'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { managelogbyid, manageloglist } from '@/api/system/manage-log'
|
||||
import { userselect } from '@/api/system/user'
|
||||
|
||||
interface LogItem {
|
||||
id?: string | number
|
||||
logName: string
|
||||
logType: string
|
||||
logContent: string
|
||||
logName?: string
|
||||
logType?: string
|
||||
logContent?: string
|
||||
logWritetime?: string
|
||||
remark?: string
|
||||
createId?: string
|
||||
createId?: string | number
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
updateId?: string
|
||||
updateId?: string | number
|
||||
}
|
||||
|
||||
interface UserOption {
|
||||
id: string | number
|
||||
userName: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const tableData = ref<LogItem[]>([])
|
||||
const total = ref(0)
|
||||
const userOptions = ref<UserOption[]>([])
|
||||
const userMap = ref<Map<string, string>>(new Map())
|
||||
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref<LogItem>({})
|
||||
|
||||
const logTypeOptions = [
|
||||
'登录日志',
|
||||
'样品执行sop操作日志',
|
||||
'基础数据修改日志(sop序列修改日志)',
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
logName: '',
|
||||
logType: '',
|
||||
createId: '',
|
||||
createId: '' as string | number | '',
|
||||
logWritetimeRange: [] as string[],
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
@@ -183,31 +178,11 @@ const pagination = reactive({
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
const drawerVisible = ref(false)
|
||||
const drawerTitle = ref('新增日志')
|
||||
const isEdit = ref(false)
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive<LogItem>({
|
||||
id: undefined,
|
||||
logName: '',
|
||||
logType: '',
|
||||
logContent: '',
|
||||
logWritetime: '',
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
logName: [{ required: true, message: '请输入日志名称', trigger: 'blur' }],
|
||||
logType: [{ required: true, message: '请输入日志类型', trigger: 'blur' }],
|
||||
logContent: [{ required: true, message: '请输入日志内容', trigger: 'blur' }],
|
||||
logWritetime: [{ required: true, message: '请选择日志记录时间', trigger: 'change' }],
|
||||
}
|
||||
|
||||
const formatDateTime = (dateTime?: string) => {
|
||||
if (!dateTime) return '-'
|
||||
try {
|
||||
const date = new Date(dateTime)
|
||||
if (Number.isNaN(date.getTime())) return dateTime
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
@@ -220,14 +195,78 @@ const formatDateTime = (dateTime?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = undefined
|
||||
form.logName = ''
|
||||
form.logType = ''
|
||||
form.logContent = ''
|
||||
form.logWritetime = ''
|
||||
form.remark = ''
|
||||
formRef.value?.clearValidate()
|
||||
const getUserName = (userId?: string | number) => {
|
||||
if (userId === undefined || userId === null || userId === '') return '-'
|
||||
return userMap.value.get(String(userId)) || String(userId)
|
||||
}
|
||||
|
||||
const normalizeDateTimeParam = (value?: string | Date) => {
|
||||
if (!value) return ''
|
||||
|
||||
// 统一为后端 LocalDateTime 可解析格式:yyyy-MM-dd HH:mm:ss
|
||||
// 兼容 Date / ISO / 带毫秒 / 带时区等格式
|
||||
if (value instanceof Date) {
|
||||
const year = value.getFullYear()
|
||||
const month = String(value.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(value.getDate()).padStart(2, '0')
|
||||
const hours = String(value.getHours()).padStart(2, '0')
|
||||
const minutes = String(value.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(value.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
const raw = String(value).trim().replace('T', ' ').replace('Z', '')
|
||||
|
||||
// 优先提取标准 yyyy-MM-dd HH:mm:ss(自动去掉毫秒/时区尾巴)
|
||||
const matched = raw.match(/\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/)
|
||||
if (matched?.[0]) return matched[0]
|
||||
|
||||
// 兜底:尝试使用 Date 解析
|
||||
const parsed = new Date(raw)
|
||||
if (!Number.isNaN(parsed.getTime())) {
|
||||
const year = parsed.getFullYear()
|
||||
const month = String(parsed.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(parsed.getDate()).padStart(2, '0')
|
||||
const hours = String(parsed.getHours()).padStart(2, '0')
|
||||
const minutes = String(parsed.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(parsed.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
return raw
|
||||
}
|
||||
|
||||
const loadUserOptions = async () => {
|
||||
try {
|
||||
const res: any = await userselect({})
|
||||
const data = res?.data ?? res ?? {}
|
||||
|
||||
const users = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data.data)
|
||||
? data.data
|
||||
: Array.isArray(data.records)
|
||||
? data.records
|
||||
: Array.isArray(data.list)
|
||||
? data.list
|
||||
: []
|
||||
|
||||
const options: UserOption[] = users
|
||||
.filter((item: any) => item?.id !== undefined && item?.id !== null)
|
||||
.map((item: any) => ({
|
||||
id: item.id,
|
||||
userName: item.userName || item.username || `用户${item.id}`,
|
||||
}))
|
||||
|
||||
userOptions.value = options
|
||||
userMap.value.clear()
|
||||
options.forEach((item) => {
|
||||
userMap.value.set(String(item.id), item.userName)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('load user options error', error)
|
||||
ElMessage.error('加载操作人下拉失败')
|
||||
}
|
||||
}
|
||||
|
||||
const loadList = async () => {
|
||||
@@ -237,16 +276,17 @@ const loadList = async () => {
|
||||
pageNum: pagination.pageNum,
|
||||
pageSize: pagination.pageSize,
|
||||
}
|
||||
|
||||
// 只添加有值的查询参数
|
||||
if (queryForm.logName) params.logName = queryForm.logName
|
||||
|
||||
if (queryForm.logType) params.logType = queryForm.logType
|
||||
if (queryForm.createId) params.createId = queryForm.createId
|
||||
if (queryForm.createId !== '' && queryForm.createId !== null) params.createId = queryForm.createId
|
||||
if (queryForm.logWritetimeRange?.length === 2) {
|
||||
params.logWritetimeStart = normalizeDateTimeParam(queryForm.logWritetimeRange[0])
|
||||
params.logWritetimeEnd = normalizeDateTimeParam(queryForm.logWritetimeRange[1])
|
||||
}
|
||||
|
||||
const res: any = await manageloglist(params)
|
||||
const data = res?.data ?? res ?? {}
|
||||
|
||||
// 兼容多种返回格式:{data:{records,total}} | {records,total} | {data:[]} | []
|
||||
const recordsFromData =
|
||||
data.records ||
|
||||
data.list ||
|
||||
@@ -260,8 +300,7 @@ const loadList = async () => {
|
||||
? data
|
||||
: []
|
||||
|
||||
const totalValue =
|
||||
data.total ?? data.count ?? data.totalCount ?? records.length ?? 0
|
||||
const totalValue = data.total ?? data.count ?? data.totalCount ?? records.length ?? 0
|
||||
|
||||
tableData.value = records
|
||||
total.value = Number(totalValue) || 0
|
||||
@@ -279,9 +318,9 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
queryForm.logName = ''
|
||||
queryForm.logType = ''
|
||||
queryForm.createId = ''
|
||||
queryForm.logWritetimeRange = []
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
@@ -295,123 +334,27 @@ const handleCurrentChange = (page: number) => {
|
||||
loadList()
|
||||
}
|
||||
|
||||
const openDrawer = async (mode: 'create' | 'edit', row?: LogItem) => {
|
||||
resetForm()
|
||||
isEdit.value = mode === 'edit'
|
||||
drawerTitle.value = isEdit.value ? '编辑日志' : '新增日志'
|
||||
drawerVisible.value = true
|
||||
|
||||
if (isEdit.value && row?.id != null) {
|
||||
try {
|
||||
const res: any = await managelogbyid(row.id)
|
||||
const data = res?.data || res || {}
|
||||
form.id = data.id ?? row.id
|
||||
form.logName = data.logName ?? row.logName ?? ''
|
||||
form.logType = data.logType ?? row.logType ?? ''
|
||||
form.logContent = data.logContent ?? row.logContent ?? ''
|
||||
form.logWritetime = data.logWritetime ?? row.logWritetime ?? ''
|
||||
form.remark = data.remark ?? row.remark ?? ''
|
||||
} catch (error) {
|
||||
ElMessage.error('获取日志详情失败')
|
||||
drawerVisible.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将时间格式转换为ISO 8601格式
|
||||
const formatToISO = (dateTime?: string) => {
|
||||
if (!dateTime) return new Date().toISOString()
|
||||
try {
|
||||
// 如果已经是ISO格式,直接返回
|
||||
if (dateTime.includes('T') && dateTime.includes('Z')) {
|
||||
return dateTime
|
||||
}
|
||||
// 如果是其他格式,转换为ISO格式
|
||||
const date = new Date(dateTime)
|
||||
return date.toISOString()
|
||||
} catch {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
const submitForm = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
// 获取当前时间(ISO 8601格式)
|
||||
const now = new Date().toISOString()
|
||||
|
||||
// 获取用户ID(可以从localStorage或其他地方获取,这里先设为0)
|
||||
const userId = 0 // 可以根据实际情况从用户store或localStorage获取
|
||||
|
||||
// 准备提交数据,按照后端Swagger要求补充所有字段
|
||||
const submitData: any = {
|
||||
logName: form.logName,
|
||||
logType: form.logType,
|
||||
logContent: form.logContent || '',
|
||||
remark: form.remark || '',
|
||||
logWritetime: formatToISO(form.logWritetime),
|
||||
createId: userId,
|
||||
updateId: userId,
|
||||
createTime: now,
|
||||
updateTime: now,
|
||||
delSign: false, // 新增时delSign为false
|
||||
}
|
||||
|
||||
// 如果是编辑,需要包含id
|
||||
if (isEdit.value && form.id) {
|
||||
submitData.id = Number(form.id)
|
||||
} else {
|
||||
// 新增时id设为0
|
||||
submitData.id = 0
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
await managelogupd(submitData)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await managelogadd(submitData)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
drawerVisible.value = false
|
||||
loadList()
|
||||
} catch (error: any) {
|
||||
console.error('submit log error', error)
|
||||
const errorMsg = error?.response?.data?.message || error?.message || (isEdit.value ? '更新失败' : '新增失败')
|
||||
ElMessage.error(errorMsg)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (row: LogItem) => {
|
||||
if (!row.id) {
|
||||
ElMessage.warning('缺少日志ID')
|
||||
const openDetailDialog = async (row: LogItem) => {
|
||||
if (!row?.id) {
|
||||
detailData.value = row
|
||||
detailVisible.value = true
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm(`确认删除日志「${row.logName}」吗?`, '提示', {
|
||||
type: 'warning',
|
||||
})
|
||||
.then(async () => {
|
||||
if (row.id == null) {
|
||||
ElMessage.warning('缺少日志ID,无法删除')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await managelogdel(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
loadList()
|
||||
} catch (error) {
|
||||
console.error('delete log error', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
try {
|
||||
const res: any = await managelogbyid(row.id)
|
||||
detailData.value = res?.data ?? res ?? row
|
||||
} catch (error) {
|
||||
console.error('load log detail error', error)
|
||||
detailData.value = row
|
||||
ElMessage.warning('获取详情失败,已展示当前行数据')
|
||||
} finally {
|
||||
detailVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await loadUserOptions()
|
||||
loadList()
|
||||
})
|
||||
</script>
|
||||
@@ -435,27 +378,14 @@ onMounted(() => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.drawer-form {
|
||||
padding-right: 10px;
|
||||
.log-type-text {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
504
rc_autoplc_front/src/views/permission/index.vue
Normal file
504
rc_autoplc_front/src/views/permission/index.vue
Normal file
@@ -0,0 +1,504 @@
|
||||
<template>
|
||||
<div class="permission-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>
|
||||
|
||||
<div class="permission-container">
|
||||
<el-card class="tree-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="tree-header">
|
||||
<span>权限信息</span>
|
||||
<el-button v-if="selectedPermissionId" type="text" size="small" @click="handleTreeReset">
|
||||
显示全部
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="treeData"
|
||||
:props="treeProps"
|
||||
node-key="id"
|
||||
:default-expand-all="false"
|
||||
:highlight-current="true"
|
||||
@node-click="handleTreeNodeClick"
|
||||
v-loading="treeLoading"
|
||||
>
|
||||
<template #default="{ node }">
|
||||
<span class="tree-node">
|
||||
<span>{{ node.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
|
||||
<div class="list-container">
|
||||
<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-input
|
||||
v-model="queryForm.permissionName"
|
||||
placeholder="输入权限名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</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" class="table-card">
|
||||
<el-table :data="tableData" stripe border style="width: 100%" v-loading="loading">
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<el-table-column prop="permissionName" label="权限名称" min-width="180" :formatter="formatCell" />
|
||||
<el-table-column prop="permissionCode" label="权限标识" min-width="180" :formatter="formatCell" />
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer v-model="drawerVisible" :title="drawerTitle" direction="rtl" :size="420">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" class="drawer-form">
|
||||
<el-form-item v-if="isEdit" label="权限ID">
|
||||
<el-input v-model="form.id" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限名称" prop="permissionName">
|
||||
<el-input v-model="form.permissionName" placeholder="请输入权限名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限标识" prop="permissionCode">
|
||||
<el-input v-model="form.permissionCode" placeholder="请输入权限标识" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上级权限" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="treeOptions"
|
||||
:props="treeSelectProps"
|
||||
check-strictly
|
||||
placeholder="请选择上级权限"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</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">
|
||||
{{ isEdit ? '更新' : '提交' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { permissionadd, permissionbyid, permissiondel, permissionlist, permissionselect, permissionupd } from '@/api/system/permission'
|
||||
|
||||
interface PermissionItem {
|
||||
id?: number | string
|
||||
permissionName: string
|
||||
permissionCode: string
|
||||
parentId?: number | string | null
|
||||
parentName?: string
|
||||
children?: PermissionItem[]
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const treeLoading = ref(false)
|
||||
const tableData = ref<PermissionItem[]>([])
|
||||
const treeData = ref<PermissionItem[]>([])
|
||||
const total = ref(0)
|
||||
const selectedPermissionId = ref<number | string | null>(null)
|
||||
const treeRef = ref()
|
||||
|
||||
const queryForm = reactive({
|
||||
permissionName: '',
|
||||
parentId: 0 as number | string,
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
|
||||
const drawerVisible = ref(false)
|
||||
const drawerTitle = ref('新增权限')
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive<PermissionItem>({
|
||||
id: undefined,
|
||||
permissionName: '',
|
||||
permissionCode: '',
|
||||
parentId: null,
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
permissionName: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
|
||||
permissionCode: [{ required: true, message: '请输入权限标识', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const treeProps = { children: 'children', label: 'permissionName' }
|
||||
const treeSelectProps = { value: 'id', label: 'permissionName', children: 'children' }
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = undefined
|
||||
form.permissionName = ''
|
||||
form.permissionCode = ''
|
||||
form.parentId = null
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
const formatCell = (_row: any, _column: any, value: any) => {
|
||||
return value === undefined || value === null || value === '' ? '暂无' : value
|
||||
}
|
||||
|
||||
const flattenTree = (nodes: PermissionItem[] = [], result: PermissionItem[] = []): PermissionItem[] => {
|
||||
nodes.forEach((node) => {
|
||||
result.push(node)
|
||||
if (node.children?.length) flattenTree(node.children, result)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const buildTree = (list: PermissionItem[]): PermissionItem[] => {
|
||||
const map = new Map<string | number, PermissionItem>()
|
||||
const roots: PermissionItem[] = []
|
||||
list.forEach((item) => {
|
||||
map.set(item.id as string | number, { ...item, children: [] })
|
||||
})
|
||||
list.forEach((item) => {
|
||||
const node = map.get(item.id as string | number)
|
||||
if (!node) return
|
||||
if (item.parentId !== null && item.parentId !== undefined && map.has(item.parentId as string | number)) {
|
||||
map.get(item.parentId as string | number)!.children!.push(node)
|
||||
} else {
|
||||
roots.push(node)
|
||||
}
|
||||
})
|
||||
return roots
|
||||
}
|
||||
|
||||
const normalizeTree = (data: any): PermissionItem[] => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.data)) return data.data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
return []
|
||||
}
|
||||
|
||||
const loadTree = async () => {
|
||||
treeLoading.value = true
|
||||
try {
|
||||
const res: any = await permissionselect({})
|
||||
const data = res?.data ?? res
|
||||
const nodes = normalizeTree(data)
|
||||
treeData.value = nodes.some((item) => Array.isArray(item.children)) ? nodes : buildTree(nodes)
|
||||
} catch (error) {
|
||||
console.error('load permission tree error', error)
|
||||
ElMessage.error('加载权限树失败')
|
||||
} finally {
|
||||
treeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTreeNodeClick = (data: PermissionItem) => {
|
||||
selectedPermissionId.value = data.id || null
|
||||
queryForm.parentId = data.id || 0
|
||||
pagination.pageNum = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const handleTreeReset = () => {
|
||||
selectedPermissionId.value = null
|
||||
queryForm.parentId = 0
|
||||
treeRef.value?.setCurrentKey(null)
|
||||
pagination.pageNum = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const loadList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
pageNum: pagination.pageNum,
|
||||
pageSize: pagination.pageSize,
|
||||
}
|
||||
if (queryForm.permissionName?.trim()) {
|
||||
params.permissionName = queryForm.permissionName.trim()
|
||||
}
|
||||
params.parentId = queryForm.parentId ?? 0
|
||||
|
||||
const res: any = await permissionlist(params)
|
||||
const data = res?.data ?? res ?? {}
|
||||
const recordsFromData =
|
||||
data.records || data.list || data.rows || data.items || (Array.isArray(data.data) ? data.data : undefined)
|
||||
|
||||
const records = Array.isArray(recordsFromData)
|
||||
? recordsFromData
|
||||
: Array.isArray(data)
|
||||
? data
|
||||
: []
|
||||
|
||||
const allTreeNodes = flattenTree(treeData.value, [])
|
||||
const idNameMap = new Map(allTreeNodes.map((item) => [item.id as string | number, item.permissionName]))
|
||||
|
||||
tableData.value = records.map((item: PermissionItem) => ({
|
||||
...item,
|
||||
parentName: item.parentName || (item.parentId != null ? idNameMap.get(item.parentId as string | number) : '暂无'),
|
||||
}))
|
||||
total.value = Number(data.total ?? data.count ?? data.totalCount ?? records.length ?? 0) || 0
|
||||
} catch (error) {
|
||||
console.error('load permission list error', error)
|
||||
tableData.value = []
|
||||
total.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.pageNum = 1
|
||||
loadList()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
queryForm.permissionName = ''
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size
|
||||
loadList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.pageNum = page
|
||||
loadList()
|
||||
}
|
||||
|
||||
const treeOptions = computed(() => {
|
||||
const filterCurrent = (nodes: PermissionItem[]): any[] => {
|
||||
return nodes
|
||||
.filter((node) => node.id !== form.id)
|
||||
.map((node) => ({
|
||||
id: node.id,
|
||||
permissionName: node.permissionName,
|
||||
children: node.children ? filterCurrent(node.children) : undefined,
|
||||
}))
|
||||
}
|
||||
return filterCurrent(treeData.value)
|
||||
})
|
||||
|
||||
const openDrawer = async (mode: 'create' | 'edit', row?: PermissionItem) => {
|
||||
resetForm()
|
||||
isEdit.value = mode === 'edit'
|
||||
drawerTitle.value = isEdit.value ? '编辑权限' : '新增权限'
|
||||
drawerVisible.value = true
|
||||
await loadTree()
|
||||
|
||||
if (isEdit.value && row?.id != null) {
|
||||
try {
|
||||
const res: any = await permissionbyid(row.id)
|
||||
const data = res?.data || res || {}
|
||||
form.id = data.id ?? row.id
|
||||
form.permissionName = data.permissionName ?? row.permissionName
|
||||
form.permissionCode = data.permissionCode ?? row.permissionCode
|
||||
form.parentId = data.parentId ?? row.parentId ?? null
|
||||
} catch (error) {
|
||||
ElMessage.error('获取权限详情失败')
|
||||
drawerVisible.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submitForm = () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload: any = {
|
||||
...form,
|
||||
}
|
||||
if (payload.parentId === undefined || payload.parentId === null || payload.parentId === '') {
|
||||
payload.parentId = 0
|
||||
}
|
||||
if (isEdit.value) {
|
||||
await permissionupd(payload)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await permissionadd(payload)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
drawerVisible.value = false
|
||||
await loadTree()
|
||||
loadList()
|
||||
} catch (error: any) {
|
||||
console.error('submit permission error', error)
|
||||
ElMessage.error(error?.message || error?.msg || (isEdit.value ? '更新失败' : '新增失败'))
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (row: PermissionItem) => {
|
||||
if (!row.id) {
|
||||
ElMessage.warning('缺少权限ID')
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm(`确认删除权限「${row.permissionName}」吗?`, '提示', { type: 'warning' })
|
||||
.then(async () => {
|
||||
try {
|
||||
await permissiondel(row.id as string | number)
|
||||
ElMessage.success('删除成功')
|
||||
await loadTree()
|
||||
loadList()
|
||||
} catch (error: any) {
|
||||
console.error('delete permission error', error)
|
||||
ElMessage.error(error?.message || error?.msg || '删除失败,可能已被角色绑定')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const formatParentCell = (row: PermissionItem) => {
|
||||
return row.parentName || '暂无'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTree()
|
||||
loadList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.permission-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.permission-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tree-card {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tree-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tree-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
padding-bottom: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-table) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
1127
rc_autoplc_front/src/views/plcdevicecontrol/index.vue
Normal file
1127
rc_autoplc_front/src/views/plcdevicecontrol/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="position-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">
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="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">
|
||||
@@ -39,11 +44,17 @@
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="180" />
|
||||
<el-table-column prop="roleName" label="角色名称" min-width="*" />
|
||||
<el-table-column prop="roleCode" label="角色编码" min-width="*" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<el-table-column prop="roleName" label="角色名称" min-width="160" />
|
||||
<el-table-column prop="roleCode" label="角色编码" min-width="160" />
|
||||
<el-table-column prop="remark" label="备注" min-width="240" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.remark || row.remarks || '暂无' }}
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -104,14 +115,45 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog
|
||||
v-model="permissionDialogVisible"
|
||||
:title="permissionDialogTitle"
|
||||
width="780px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="permission-dialog-tree">
|
||||
<el-tree
|
||||
ref="permissionTreeRef"
|
||||
:data="permissionTreeData"
|
||||
:props="permissionTreeProps"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:check-strictly="false"
|
||||
:expand-on-click-node="false"
|
||||
v-loading="permissionTreeLoading"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<el-button @click="permissionDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="permissionSubmitLoading" @click="submitPermissionForm">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { FormInstance, FormRules, TreeInstance } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { roleadd, rolebyid, roledel, rolelist, roleupd } from '@/api/system/role'
|
||||
import { permissionselect } from '@/api/system/permission'
|
||||
import { rolePermissionAdd, rolePermissionClearByRoleId, rolePermissionListByRoleId } from '@/api/system/role-permission'
|
||||
|
||||
interface RoleItem {
|
||||
id?: number | string
|
||||
@@ -142,6 +184,14 @@ const drawerVisible = ref(false)
|
||||
const drawerTitle = ref('新增角色')
|
||||
const isEdit = ref(false)
|
||||
|
||||
const permissionDialogVisible = ref(false)
|
||||
const permissionDialogTitle = ref('权限配置')
|
||||
const permissionTreeLoading = ref(false)
|
||||
const permissionSubmitLoading = ref(false)
|
||||
const permissionTreeRef = ref<TreeInstance>()
|
||||
const permissionTreeData = ref<any[]>([])
|
||||
const currentPermissionRoleId = ref<number | string | null>(null)
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive<RoleItem>({
|
||||
id: undefined,
|
||||
@@ -155,6 +205,11 @@ const rules: FormRules = {
|
||||
roleCode: [{ required: true, message: '请输入角色编码', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const permissionTreeProps = {
|
||||
children: 'children',
|
||||
label: 'permissionName',
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = undefined
|
||||
form.roleName = ''
|
||||
@@ -269,6 +324,160 @@ const submitForm = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const normalizePermissionTree = (data: any): any[] => {
|
||||
if (Array.isArray(data)) return data
|
||||
if (Array.isArray(data?.records)) return data.records
|
||||
if (Array.isArray(data?.data)) return data.data
|
||||
if (Array.isArray(data?.list)) return data.list
|
||||
return []
|
||||
}
|
||||
|
||||
const buildPermissionTree = (list: any[]) => {
|
||||
const map = new Map<string | number, any>()
|
||||
const roots: any[] = []
|
||||
list.forEach((item) => {
|
||||
map.set(item.id, { ...item, children: [] })
|
||||
})
|
||||
list.forEach((item) => {
|
||||
const node = map.get(item.id)
|
||||
if (!node) return
|
||||
const parentId = item.parentId
|
||||
if (parentId !== null && parentId !== undefined && map.has(parentId)) {
|
||||
map.get(parentId).children.push(node)
|
||||
} else {
|
||||
roots.push(node)
|
||||
}
|
||||
})
|
||||
return roots
|
||||
}
|
||||
|
||||
const buildPermissionIndex = (nodes: any[]) => {
|
||||
const nodeMap = new Map<string | number, any>()
|
||||
const childMap = new Map<string | number, Array<string | number>>()
|
||||
|
||||
const traverse = (items: any[]) => {
|
||||
items.forEach((node) => {
|
||||
nodeMap.set(node.id, node)
|
||||
childMap.set(node.id, (node.children || []).map((child: any) => child.id))
|
||||
if (Array.isArray(node.children) && node.children.length > 0) {
|
||||
traverse(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
traverse(nodes)
|
||||
return { nodeMap, childMap }
|
||||
}
|
||||
|
||||
const getDeepestCheckedIds = (checkedIds: Array<string | number>) => {
|
||||
const { nodeMap } = buildPermissionIndex(permissionTreeData.value)
|
||||
const checkedSet = new Set(checkedIds)
|
||||
return checkedIds.filter((id) => {
|
||||
const node = nodeMap.get(id)
|
||||
if (!node) return true
|
||||
const stack = Array.isArray(node.children) ? [...node.children] : []
|
||||
while (stack.length) {
|
||||
const current = stack.pop()
|
||||
if (!current) continue
|
||||
if (checkedSet.has(current.id)) return false
|
||||
if (Array.isArray(current.children) && current.children.length > 0) {
|
||||
stack.push(...current.children)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
const collectPermissionIdsWithParents = (checkedIds: Array<string | number>) => {
|
||||
const idSet = new Set<string | number>()
|
||||
const { nodeMap } = buildPermissionIndex(permissionTreeData.value)
|
||||
|
||||
checkedIds.forEach((id) => {
|
||||
let current = nodeMap.get(id)
|
||||
while (current) {
|
||||
idSet.add(current.id)
|
||||
const parentId = current.parentId
|
||||
current = parentId !== null && parentId !== undefined ? nodeMap.get(parentId) : undefined
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(idSet)
|
||||
}
|
||||
|
||||
const loadPermissionTree = async () => {
|
||||
permissionTreeLoading.value = true
|
||||
try {
|
||||
const res: any = await permissionselect({})
|
||||
const data = res?.data ?? res
|
||||
const list = normalizePermissionTree(data)
|
||||
permissionTreeData.value = list.some((item) => Array.isArray(item.children)) ? list : buildPermissionTree(list)
|
||||
} catch (error) {
|
||||
console.error('load permission tree error', error)
|
||||
ElMessage.error('加载权限树失败')
|
||||
permissionTreeData.value = []
|
||||
} finally {
|
||||
permissionTreeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const setCheckedKeys = (keys: Array<string | number>) => {
|
||||
permissionTreeRef.value?.setCheckedKeys(keys)
|
||||
}
|
||||
|
||||
const openPermissionDialog = async (row: RoleItem) => {
|
||||
if (!row.id) {
|
||||
ElMessage.warning('缺少角色ID')
|
||||
return
|
||||
}
|
||||
currentPermissionRoleId.value = row.id
|
||||
permissionDialogTitle.value = `权限配置-${row.roleName || '未命名角色'}`
|
||||
permissionDialogVisible.value = true
|
||||
await loadPermissionTree()
|
||||
|
||||
try {
|
||||
const res: any = await rolePermissionListByRoleId(row.id)
|
||||
const data = res?.data ?? res ?? {}
|
||||
const checkedIds = Array.isArray(data)
|
||||
? data.map((item: any) => item.permissionId ?? item.id).filter((id: any) => id !== undefined && id !== null)
|
||||
: Array.isArray(data?.data)
|
||||
? data.data.map((item: any) => item.permissionId ?? item.id).filter((id: any) => id !== undefined && id !== null)
|
||||
: Array.isArray(data?.records)
|
||||
? data.records.map((item: any) => item.permissionId ?? item.id).filter((id: any) => id !== undefined && id !== null)
|
||||
: []
|
||||
setCheckedKeys(getDeepestCheckedIds(checkedIds))
|
||||
} catch (error) {
|
||||
console.error('load role permissions error', error)
|
||||
setCheckedKeys([])
|
||||
}
|
||||
}
|
||||
|
||||
const submitPermissionForm = async () => {
|
||||
if (!currentPermissionRoleId.value) {
|
||||
ElMessage.warning('缺少角色ID')
|
||||
return
|
||||
}
|
||||
const checkedKeys = permissionTreeRef.value?.getCheckedKeys(false) || []
|
||||
const leafCheckedKeys = getDeepestCheckedIds(checkedKeys)
|
||||
const permissionIds = collectPermissionIdsWithParents(leafCheckedKeys)
|
||||
permissionSubmitLoading.value = true
|
||||
try {
|
||||
await rolePermissionClearByRoleId(currentPermissionRoleId.value)
|
||||
await Promise.all(
|
||||
permissionIds.map((permissionId: string | number) =>
|
||||
rolePermissionAdd({ roleId: currentPermissionRoleId.value, permissionId })
|
||||
),
|
||||
)
|
||||
ElMessage.success('权限配置成功')
|
||||
permissionDialogVisible.value = false
|
||||
loadList()
|
||||
} catch (error: any) {
|
||||
console.error('submit role permission error', error)
|
||||
ElMessage.error(error?.message || error?.msg || '权限配置失败')
|
||||
} finally {
|
||||
permissionSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (row: RoleItem) => {
|
||||
if (!row.id) {
|
||||
ElMessage.warning('缺少角色ID')
|
||||
@@ -325,6 +534,11 @@ onMounted(() => {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.permission-dialog-tree {
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
1886
rc_autoplc_front/src/views/sampleinjection/index.vue
Normal file
1886
rc_autoplc_front/src/views/sampleinjection/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
2817
rc_autoplc_front/src/views/stepinfo/index.vue
Normal file
2817
rc_autoplc_front/src/views/stepinfo/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
||||
<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">
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="user-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">
|
||||
@@ -61,15 +66,6 @@
|
||||
{{ getDeptName(scope.row.depId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="posId"
|
||||
label="职位"
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ getPosName(scope.row.posId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="telephone"
|
||||
label="联系方式"
|
||||
@@ -94,12 +90,6 @@
|
||||
min-width="150"
|
||||
:formatter="formatCell"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
:formatter="formatCell"
|
||||
/>
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="openDrawer('edit', scope.row)">编辑</el-button>
|
||||
@@ -178,32 +168,6 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="职位" prop="posId">
|
||||
<el-select
|
||||
v-model="form.posId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择职位"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in positionOptions"
|
||||
:key="item.id"
|
||||
:label="item.posiName || item.posiCode"
|
||||
: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">
|
||||
@@ -219,17 +183,49 @@
|
||||
<el-dialog
|
||||
v-model="roleDialogVisible"
|
||||
title="分配角色"
|
||||
width="500px"
|
||||
width="760px"
|
||||
>
|
||||
<el-checkbox-group v-model="selectedRoleIds">
|
||||
<el-checkbox
|
||||
v-for="role in roleOptions"
|
||||
:key="role.id"
|
||||
:label="role.id"
|
||||
>
|
||||
{{ role.roleName || role.roleCode }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<div class="role-dialog-search">
|
||||
<el-form :inline="true" :model="roleQueryForm">
|
||||
<el-form-item label="角色名称">
|
||||
<el-input
|
||||
v-model="roleQueryForm.roleName"
|
||||
placeholder="输入角色名称"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleRoleSearch">查询</el-button>
|
||||
<el-button @click="resetRoleSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-table :data="roleOptions" border stripe v-loading="assignRoleLoading" style="width: 100%">
|
||||
<el-table-column width="50" align="center">
|
||||
<template #default="scope">
|
||||
<el-checkbox
|
||||
:model-value="selectedRoleIds.includes(scope.row.id)"
|
||||
@change="(checked: boolean) => toggleRoleSelection(scope.row.id, checked)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleName" label="角色名称" min-width="180" />
|
||||
<el-table-column prop="roleCode" label="角色编码" min-width="180" />
|
||||
<el-table-column prop="remark" label="备注" min-width="200" />
|
||||
</el-table>
|
||||
<div class="pagination role-pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="rolePagination.pageNum"
|
||||
v-model:page-size="rolePagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="roleTotal"
|
||||
background
|
||||
@current-change="handleRoleCurrentChange"
|
||||
@size-change="handleRoleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="roleDialogVisible = false">取消</el-button>
|
||||
@@ -248,9 +244,8 @@ import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useradd, userbyid, userdel, userlist, userupd } from '@/api/system/user'
|
||||
import { departtree } from '@/api/system/department'
|
||||
import { positionselect } from '@/api/system/position'
|
||||
import { userRoleAdd, userRoleByUserId, userRoleDel } from '@/api/system/user-role'
|
||||
import { rleselect } from '@/api/system/role'
|
||||
import { rolelist } from '@/api/system/role'
|
||||
|
||||
interface UserItem {
|
||||
id?: number | string
|
||||
@@ -275,16 +270,11 @@ interface DeptItem {
|
||||
children?: DeptItem[]
|
||||
}
|
||||
|
||||
interface PositionItem {
|
||||
id?: number | string
|
||||
posiName?: string
|
||||
posiCode?: string
|
||||
}
|
||||
|
||||
interface RoleItem {
|
||||
id?: number | string
|
||||
roleName?: string
|
||||
roleCode?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -292,15 +282,16 @@ const submitLoading = ref(false)
|
||||
const tableData = ref<UserItem[]>([])
|
||||
const total = ref(0)
|
||||
const deptTreeData = ref<DeptItem[]>([])
|
||||
const positionOptions = ref<PositionItem[]>([])
|
||||
const deptMap = ref<Map<number | string, string>>(new Map())
|
||||
const posMap = ref<Map<number | string, string>>(new Map())
|
||||
const roleDialogVisible = ref(false)
|
||||
const assignRoleLoading = ref(false)
|
||||
const roleOptions = ref<RoleItem[]>([])
|
||||
const selectedRoleIds = ref<(number | string)[]>([])
|
||||
const currentAssignUserId = ref<number | string | null>(null)
|
||||
const originalUserRoles = ref<any[]>([]) // 保存用户原有的角色关联列表
|
||||
const originalUserRoles = ref<any[]>([])
|
||||
const roleQueryForm = reactive({ roleName: '' })
|
||||
const rolePagination = reactive({ pageNum: 1, pageSize: 10 })
|
||||
const roleTotal = ref(0)
|
||||
|
||||
const queryForm = reactive({
|
||||
userName: '',
|
||||
@@ -327,7 +318,6 @@ const form = reactive<UserItem>({
|
||||
telephone: '',
|
||||
depId: null,
|
||||
posId: null,
|
||||
remark: '',
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
@@ -353,7 +343,6 @@ const resetForm = () => {
|
||||
form.telephone = ''
|
||||
form.depId = null
|
||||
form.posId = null
|
||||
form.remark = ''
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
@@ -432,12 +421,6 @@ const getDeptName = (depId: number | string | null | undefined) => {
|
||||
return deptMap.value.get(depId) || '暂无'
|
||||
}
|
||||
|
||||
// 获取职位名称
|
||||
const getPosName = (posId: number | string | null | undefined) => {
|
||||
if (!posId) return '暂无'
|
||||
return posMap.value.get(posId) || '暂无'
|
||||
}
|
||||
|
||||
// 加载部门树
|
||||
const loadDeptTree = async () => {
|
||||
try {
|
||||
@@ -462,36 +445,6 @@ const loadDeptTree = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载职位列表
|
||||
const loadPositionList = async () => {
|
||||
try {
|
||||
const res: any = await positionselect()
|
||||
const data = res?.data ?? res ?? {}
|
||||
|
||||
// 兼容多种返回格式
|
||||
const positions = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data.data)
|
||||
? data.data
|
||||
: Array.isArray(data.records)
|
||||
? data.records
|
||||
: Array.isArray(data.list)
|
||||
? data.list
|
||||
: []
|
||||
|
||||
positionOptions.value = positions
|
||||
posMap.value.clear()
|
||||
positions.forEach((pos: PositionItem) => {
|
||||
if (pos.id) {
|
||||
posMap.value.set(pos.id, pos.posiName || pos.posiCode || '')
|
||||
}
|
||||
})
|
||||
console.log('职位列表数据:', positionOptions.value)
|
||||
} catch (error) {
|
||||
console.error('load position list error', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载用户列表
|
||||
const loadList = async () => {
|
||||
loading.value = true
|
||||
@@ -585,8 +538,8 @@ const openDrawer = async (mode: 'create' | 'edit', row?: UserItem) => {
|
||||
drawerTitle.value = isEdit.value ? '编辑用户' : '新增用户'
|
||||
drawerVisible.value = true
|
||||
|
||||
// 加载部门和职位数据
|
||||
await Promise.all([loadDeptTree(), loadPositionList()])
|
||||
// 加载部门数据
|
||||
await loadDeptTree()
|
||||
|
||||
if (isEdit.value && row?.id != null) {
|
||||
try {
|
||||
@@ -601,7 +554,6 @@ const openDrawer = async (mode: 'create' | 'edit', row?: UserItem) => {
|
||||
form.telephone = data.telephone ?? row.telephone ?? ''
|
||||
form.depId = data.depId ?? row.depId ?? null
|
||||
form.posId = data.posId ?? row.posId ?? null
|
||||
form.remark = data.remark ?? row.remark ?? ''
|
||||
// 编辑时不设置密码
|
||||
form.password = ''
|
||||
} catch (error) {
|
||||
@@ -632,6 +584,7 @@ const submitForm = () => {
|
||||
if (!payload.posId) {
|
||||
payload.posId = null
|
||||
}
|
||||
delete payload.remark
|
||||
|
||||
if (isEdit.value) {
|
||||
await userupd(payload)
|
||||
@@ -681,13 +634,64 @@ const handleDelete = (row: UserItem) => {
|
||||
// 加载角色列表
|
||||
const loadRoleList = async () => {
|
||||
try {
|
||||
const res: any = await rleselect({})
|
||||
const data = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []
|
||||
roleOptions.value = data
|
||||
const params: any = {
|
||||
pageNum: rolePagination.pageNum,
|
||||
pageSize: rolePagination.pageSize,
|
||||
}
|
||||
if (roleQueryForm.roleName && roleQueryForm.roleName.trim()) {
|
||||
params.roleName = roleQueryForm.roleName.trim()
|
||||
}
|
||||
const res: any = await rolelist(params)
|
||||
const data = res?.data ?? res ?? {}
|
||||
const recordsFromData =
|
||||
data.records ||
|
||||
data.list ||
|
||||
data.rows ||
|
||||
data.items ||
|
||||
(Array.isArray(data.data) ? data.data : undefined)
|
||||
const records = Array.isArray(recordsFromData)
|
||||
? recordsFromData
|
||||
: Array.isArray(data)
|
||||
? data
|
||||
: []
|
||||
roleOptions.value = records
|
||||
roleTotal.value = Number(data.total ?? data.count ?? data.totalCount ?? records.length ?? 0) || 0
|
||||
} catch (error) {
|
||||
console.error('load role list error', error)
|
||||
ElMessage.error('加载角色列表失败')
|
||||
roleOptions.value = []
|
||||
roleTotal.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
const handleRoleSearch = () => {
|
||||
rolePagination.pageNum = 1
|
||||
loadRoleList()
|
||||
}
|
||||
|
||||
const resetRoleSearch = () => {
|
||||
roleQueryForm.roleName = ''
|
||||
handleRoleSearch()
|
||||
}
|
||||
|
||||
const handleRoleCurrentChange = (page: number) => {
|
||||
rolePagination.pageNum = page
|
||||
loadRoleList()
|
||||
}
|
||||
|
||||
const handleRoleSizeChange = (size: number) => {
|
||||
rolePagination.pageSize = size
|
||||
loadRoleList()
|
||||
}
|
||||
|
||||
const toggleRoleSelection = (roleId: string | number | undefined, checked: boolean) => {
|
||||
if (roleId === undefined || roleId === null) return
|
||||
const exists = selectedRoleIds.value.includes(roleId)
|
||||
if (checked && !exists) {
|
||||
selectedRoleIds.value.push(roleId)
|
||||
}
|
||||
if (!checked && exists) {
|
||||
selectedRoleIds.value = selectedRoleIds.value.filter((id) => id !== roleId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,19 +703,15 @@ const openRoleDialog = async (row: UserItem) => {
|
||||
}
|
||||
currentAssignUserId.value = row.id
|
||||
selectedRoleIds.value = []
|
||||
roleQueryForm.roleName = ''
|
||||
rolePagination.pageNum = 1
|
||||
roleDialogVisible.value = true
|
||||
|
||||
// 确保角色列表已加载
|
||||
if (roleOptions.value.length === 0) {
|
||||
await loadRoleList()
|
||||
}
|
||||
await loadRoleList()
|
||||
|
||||
// 获取用户已有的角色
|
||||
try {
|
||||
const res: any = await userRoleByUserId(row.id)
|
||||
const data = res?.data ?? res ?? {}
|
||||
|
||||
// 兼容多种返回格式
|
||||
const userRoles = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data.data)
|
||||
@@ -721,22 +721,14 @@ const openRoleDialog = async (row: UserItem) => {
|
||||
: Array.isArray(data.list)
|
||||
? data.list
|
||||
: []
|
||||
|
||||
// 保存原有的角色关联列表(包含ID)
|
||||
originalUserRoles.value = userRoles
|
||||
|
||||
// 提取已有角色的ID列表
|
||||
if (Array.isArray(userRoles) && userRoles.length > 0) {
|
||||
selectedRoleIds.value = userRoles
|
||||
.map((item: any) => item.roleId)
|
||||
.filter((id: any) => id !== undefined && id !== null)
|
||||
} else {
|
||||
selectedRoleIds.value = []
|
||||
originalUserRoles.value = []
|
||||
}
|
||||
selectedRoleIds.value = Array.isArray(userRoles)
|
||||
? userRoles
|
||||
.map((item: any) => item.roleId)
|
||||
.filter((id: any) => id !== undefined && id !== null)
|
||||
: []
|
||||
} catch (error) {
|
||||
console.error('load user roles error', error)
|
||||
// 如果获取失败,不影响弹框打开,只是不预选角色
|
||||
originalUserRoles.value = []
|
||||
}
|
||||
}
|
||||
@@ -747,46 +739,28 @@ const handleAssignRole = async () => {
|
||||
ElMessage.warning('缺少用户ID')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确认保存当前角色分配吗?', '提示', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
assignRoleLoading.value = true
|
||||
try {
|
||||
// 获取原有的角色ID列表
|
||||
const originalRoleIds = originalUserRoles.value
|
||||
.map((item: any) => item.roleId)
|
||||
.filter((id: any) => id !== undefined && id !== null)
|
||||
|
||||
// 计算需要新增的角色(新选中但原来没有的)
|
||||
const rolesToAdd = selectedRoleIds.value.filter(
|
||||
(roleId) => !originalRoleIds.includes(roleId)
|
||||
)
|
||||
|
||||
// 计算需要删除的角色(原来有但现在未选中的)
|
||||
const rolesToDelete = originalUserRoles.value.filter(
|
||||
(item: any) => !selectedRoleIds.value.includes(item.roleId)
|
||||
)
|
||||
|
||||
// 执行新增操作
|
||||
const rolesToAdd = selectedRoleIds.value.filter((roleId) => !originalRoleIds.includes(roleId))
|
||||
const rolesToDelete = originalUserRoles.value.filter((item: any) => !selectedRoleIds.value.includes(item.roleId))
|
||||
const addPromises = rolesToAdd.map((roleId) => {
|
||||
return userRoleAdd({
|
||||
userId: currentAssignUserId.value,
|
||||
roleId: roleId,
|
||||
remark: '',
|
||||
})
|
||||
return userRoleAdd({ userId: currentAssignUserId.value, roleId, remark: '' })
|
||||
})
|
||||
|
||||
// 执行删除操作
|
||||
const deletePromises = rolesToDelete.map((item: any) => {
|
||||
if (item.id) {
|
||||
return userRoleDel(item.id)
|
||||
}
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
// 等待所有操作完成
|
||||
const deletePromises = rolesToDelete.map((item: any) => item.id ? userRoleDel(item.id) : Promise.resolve())
|
||||
await Promise.all([...addPromises, ...deletePromises])
|
||||
|
||||
ElMessage.success('分配角色成功')
|
||||
roleDialogVisible.value = false
|
||||
loadList()
|
||||
} catch (error: any) {
|
||||
console.error('assign role error', error)
|
||||
const errorMsg = error?.message || error?.msg || '分配角色失败'
|
||||
@@ -798,7 +772,6 @@ const handleAssignRole = async () => {
|
||||
|
||||
onMounted(() => {
|
||||
loadDeptTree()
|
||||
loadPositionList()
|
||||
loadRoleList()
|
||||
loadList()
|
||||
})
|
||||
@@ -835,6 +808,14 @@ onMounted(() => {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.role-dialog-search {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.role-pagination {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0', // 允许外部访问
|
||||
port: 5173, // 端口号
|
||||
proxy: {
|
||||
'/plc': {
|
||||
target: env.VITE_API_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user