Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,52 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 电子锁信息 */ |
|||
export interface Lock { |
|||
id?: number; // 主键ID
|
|||
lockNumber?: number; // 编号
|
|||
lockName?: string; // 名称
|
|||
lockStatus?: number; // 状态
|
|||
lockType?: number; // 锁具类型
|
|||
lockEnableStatus?: number; // 启用状态: 0=未启用, 1=已启用
|
|||
lockLastChargeTime?: string | Dayjs; // 上次充电时间
|
|||
lockBluetoothId?: string; // 蓝牙ID
|
|||
} |
|||
|
|||
// 电子锁 API
|
|||
export const LockApi = { |
|||
// 查询电子锁分页
|
|||
getLockPage: async (params: any) => { |
|||
return await request.get({ url: `/electron/lock/page`, params }) |
|||
}, |
|||
|
|||
// 查询电子锁详情
|
|||
getLock: async (id: number) => { |
|||
return await request.get({ url: `/electron/lock/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增电子锁
|
|||
createLock: async (data: Lock) => { |
|||
return await request.post({ url: `/electron/lock/create`, data }) |
|||
}, |
|||
|
|||
// 修改电子锁
|
|||
updateLock: async (data: Lock) => { |
|||
return await request.put({ url: `/electron/lock/update`, data }) |
|||
}, |
|||
|
|||
// 删除电子锁
|
|||
deleteLock: async (id: number) => { |
|||
return await request.delete({ url: `/electron/lock/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除电子锁 */ |
|||
deleteLockList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/electron/lock/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出电子锁 Excel
|
|||
exportLock: async (params) => { |
|||
return await request.download({ url: `/electron/lock/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 电子锁操作记录信息 */ |
|||
export interface LockWorkRecord { |
|||
id?: number; // 主键ID
|
|||
operatorId?: number; // 操作人ID
|
|||
lockId?: number; // 电子锁ID
|
|||
isolationPlanItemDetailId?: number; // 关联的子项详情ID (某些操作可能不关联)
|
|||
recordType?: number; // 记录类型
|
|||
signaturePath?: string; // 操作签名 (图片路径)
|
|||
beforePhotoPath?: string; // 操作前照片 (图片路径)
|
|||
afterPhotoPath?: string; // 操作后照片 (图片路径)
|
|||
gpsCoordinates?: string; // 操作GPS坐标
|
|||
} |
|||
|
|||
// 电子锁操作记录 API
|
|||
export const LockWorkRecordApi = { |
|||
// 查询电子锁操作记录分页
|
|||
getLockWorkRecordPage: async (params: any) => { |
|||
return await request.get({ url: `/electron/lock-word-record/page`, params }) |
|||
}, |
|||
|
|||
// 查询电子锁操作记录详情
|
|||
getLockWorkRecord: async (id: number) => { |
|||
return await request.get({ url: `/electron/lock-word-record/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增电子锁操作记录
|
|||
createLockWorkRecord: async (data: LockWorkRecord) => { |
|||
return await request.post({ url: `/electron/lock-word-record/create`, data }) |
|||
}, |
|||
|
|||
// 修改电子锁操作记录
|
|||
updateLockWorkRecord: async (data: LockWorkRecord) => { |
|||
return await request.put({ url: `/electron/lock-word-record/update`, data }) |
|||
}, |
|||
|
|||
// 删除电子锁操作记录
|
|||
deleteLockWorkRecord: async (id: number) => { |
|||
return await request.delete({ url: `/electron/lock-word-record/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除电子锁操作记录 */ |
|||
deleteLockWorkRecordList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/electron/lock-word-record/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出电子锁操作记录 Excel
|
|||
exportLockWorkRecord: async (params) => { |
|||
return await request.download({ url: `/electron/lock-word-record/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 指导书与隔离点关联信息 */ |
|||
export interface IsolationPoint { |
|||
id?: number; // id
|
|||
guideId?: number; // 隔离指导书ID
|
|||
isolationPointId?: number; // 隔离点ID
|
|||
} |
|||
|
|||
// 指导书与隔离点关联 API
|
|||
export const IsolationPointApi = { |
|||
// 查询指导书与隔离点关联分页
|
|||
getIsolationPointPage: async (params: any) => { |
|||
return await request.get({ url: `/guide/isolation-point/page`, params }) |
|||
}, |
|||
|
|||
// 查询指导书与隔离点关联详情
|
|||
getIsolationPoint: async (id: number) => { |
|||
return await request.get({ url: `/guide/isolation-point/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增指导书与隔离点关联
|
|||
createIsolationPoint: async (data: IsolationPoint) => { |
|||
return await request.post({ url: `/guide/isolation-point/create`, data }) |
|||
}, |
|||
|
|||
// 修改指导书与隔离点关联
|
|||
updateIsolationPoint: async (data: IsolationPoint) => { |
|||
return await request.put({ url: `/guide/isolation-point/update`, data }) |
|||
}, |
|||
|
|||
// 删除指导书与隔离点关联
|
|||
deleteIsolationPoint: async (id: number) => { |
|||
return await request.delete({ url: `/guide/isolation-point/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除指导书与隔离点关联 */ |
|||
deleteIsolationPointList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/guide/isolation-point/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出指导书与隔离点关联 Excel
|
|||
exportIsolationPoint: async (params) => { |
|||
return await request.download({ url: `/guide/isolation-point/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 隔离指导书信息 */ |
|||
export interface LockGuide { |
|||
id?: number; // 主键ID
|
|||
name?: string; // 指导书名称
|
|||
code?: string; // 指导书编码
|
|||
operatorId?: number; // 操作人ID
|
|||
operatorHelperId?: number; // 操作协助人ID
|
|||
verifierId?: number; // 验证人ID
|
|||
verifierHelperId?: number; // 验证协助人ID
|
|||
guideContent?: string; // 工作内容和范围
|
|||
guideLockNums?: number; // 所需设备锁数量
|
|||
isolationPointIds?: number[]; // 关联隔离点ID
|
|||
} |
|||
|
|||
// 隔离指导书 API
|
|||
export const LockGuideApi = { |
|||
// 查询隔离指导书分页
|
|||
getLockGuidePage: async (params: any) => { |
|||
return await request.get({ url: `/guide/lock-guide/page`, params }) |
|||
}, |
|||
|
|||
// 查询隔离指导书详情
|
|||
getLockGuide: async (id: number) => { |
|||
return await request.get({ url: `/guide/lock-guide/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增隔离指导书
|
|||
createLockGuide: async (data: LockGuide) => { |
|||
return await request.post({ url: `/guide/lock-guide/create`, data }) |
|||
}, |
|||
|
|||
// 修改隔离指导书
|
|||
updateLockGuide: async (data: LockGuide) => { |
|||
return await request.put({ url: `/guide/lock-guide/update`, data }) |
|||
}, |
|||
|
|||
// 删除隔离指导书
|
|||
deleteLockGuide: async (id: number) => { |
|||
return await request.delete({ url: `/guide/lock-guide/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除隔离指导书 */ |
|||
deleteLockGuideList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/guide/lock-guide/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出隔离指导书 Excel
|
|||
exportLockGuide: async (params) => { |
|||
return await request.download({ url: `/guide/lock-guide/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 检修任务信息 */ |
|||
export interface Plan { |
|||
id: number; // 主键ID
|
|||
ipName?: string; // 任务名称
|
|||
status?: number; // 状态
|
|||
} |
|||
|
|||
// 检修任务 API
|
|||
export const PlanApi = { |
|||
// 查询检修任务分页
|
|||
getPlanPage: async (params: any) => { |
|||
return await request.get({ url: `/isolation/plan/page`, params }) |
|||
}, |
|||
|
|||
// 查询检修任务详情
|
|||
getPlan: async (id: number) => { |
|||
return await request.get({ url: `/isolation/plan/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增检修任务
|
|||
createPlan: async (data: Plan) => { |
|||
return await request.post({ url: `/isolation/plan/create`, data }) |
|||
}, |
|||
|
|||
// 修改检修任务
|
|||
updatePlan: async (data: Plan) => { |
|||
return await request.put({ url: `/isolation/plan/update`, data }) |
|||
}, |
|||
|
|||
// 删除检修任务
|
|||
deletePlan: async (id: number) => { |
|||
return await request.delete({ url: `/isolation/plan/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除检修任务 */ |
|||
deletePlanList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/isolation/plan/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出检修任务 Excel
|
|||
exportPlan: async (params) => { |
|||
return await request.download({ url: `/isolation/plan/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 检修任务子项信息 */ |
|||
export interface PlanItem { |
|||
id?: number; // 主键ID
|
|||
isolationPlanId?: number; // 检修任务ID
|
|||
guideId?: number; // 隔离指导书ID
|
|||
operatorId?: number; // 集中挂牌人ID
|
|||
operatorHelperId?: number; // 集中挂牌协助人ID
|
|||
verifierId?: number; // 验证人ID
|
|||
verifierHelperId?: number; // 验证协助人ID
|
|||
status?: number; // 子项状态: 0=未完成, 1=已完成
|
|||
} |
|||
|
|||
// 检修任务子项 API
|
|||
export const PlanItemApi = { |
|||
// 查询检修任务子项分页
|
|||
getPlanItemPage: async (params: any) => { |
|||
return await request.get({ url: `/isolation/plan-item/page`, params }) |
|||
}, |
|||
|
|||
// 查询检修任务子项详情
|
|||
getPlanItem: async (id: number) => { |
|||
return await request.get({ url: `/isolation/plan-item/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增检修任务子项
|
|||
createPlanItem: async (data: PlanItem) => { |
|||
return await request.post({ url: `/isolation/plan-item/create`, data }) |
|||
}, |
|||
|
|||
// 修改检修任务子项
|
|||
updatePlanItem: async (data: PlanItem) => { |
|||
return await request.put({ url: `/isolation/plan-item/update`, data }) |
|||
}, |
|||
|
|||
// 删除检修任务子项
|
|||
deletePlanItem: async (id: number) => { |
|||
return await request.delete({ url: `/isolation/plan-item/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除检修任务子项 */ |
|||
deletePlanItemList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/isolation/plan-item/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出检修任务子项 Excel
|
|||
exportPlanItem: async (params) => { |
|||
return await request.download({ url: `/isolation/plan-item/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 检修任务子项详情信息 */ |
|||
export interface PlanItemDetail { |
|||
id?: number; // 主键ID
|
|||
isolationPlanItemId?: number; // 检修任务子项ID
|
|||
isolationPointId?: number; // 隔离点ID
|
|||
lockId?: number; // 电子锁ID
|
|||
lockStatus?: number; // 锁状态: 0=未上锁, 1=已上锁, 2=已解锁
|
|||
} |
|||
|
|||
// 检修任务子项详情 API
|
|||
export const PlanItemDetailApi = { |
|||
// 查询检修任务子项详情分页
|
|||
getPlanItemDetailPage: async (params: any) => { |
|||
return await request.get({ url: `/isolation/plan-item-detail/page`, params }) |
|||
}, |
|||
|
|||
// 查询检修任务子项详情详情
|
|||
getPlanItemDetail: async (id: number) => { |
|||
return await request.get({ url: `/isolation/plan-item-detail/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增检修任务子项详情
|
|||
createPlanItemDetail: async (data: PlanItemDetail) => { |
|||
return await request.post({ url: `/isolation/plan-item-detail/create`, data }) |
|||
}, |
|||
|
|||
// 修改检修任务子项详情
|
|||
updatePlanItemDetail: async (data: PlanItemDetail) => { |
|||
return await request.put({ url: `/isolation/plan-item-detail/update`, data }) |
|||
}, |
|||
|
|||
// 删除检修任务子项详情
|
|||
deletePlanItemDetail: async (id: number) => { |
|||
return await request.delete({ url: `/isolation/plan-item-detail/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除检修任务子项详情 */ |
|||
deletePlanItemDetailList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/isolation/plan-item-detail/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出检修任务子项详情 Excel
|
|||
exportPlanItemDetail: async (params) => { |
|||
return await request.download({ url: `/isolation/plan-item-detail/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 个人生命锁信息 */ |
|||
export interface PlanLifeLock { |
|||
id: number; // 主键ID
|
|||
isolationPlanItemDetailId?: number; // 子项详情ID
|
|||
userId?: number; // 上锁人ID
|
|||
lockType?: number; // 生命锁类型
|
|||
lockStatus?: number; // 锁定状态: 0=未上锁, 1=已上锁
|
|||
lockTime?: number; // 上锁时间
|
|||
unlockTime?: number; // 解锁时间
|
|||
} |
|||
|
|||
// 个人生命锁 API
|
|||
export const PlanLifeLockApi = { |
|||
// 查询个人生命锁分页
|
|||
getPlanLifeLockPage: async (params: any) => { |
|||
return await request.get({ url: `/isolation/plan-life-lock/page`, params }) |
|||
}, |
|||
|
|||
// 查询个人生命锁详情
|
|||
getPlanLifeLock: async (id: number) => { |
|||
return await request.get({ url: `/isolation/plan-life-lock/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增个人生命锁
|
|||
createPlanLifeLock: async (data: PlanLifeLock) => { |
|||
return await request.post({ url: `/isolation/plan-life-lock/create`, data }) |
|||
}, |
|||
|
|||
// 修改个人生命锁
|
|||
updatePlanLifeLock: async (data: PlanLifeLock) => { |
|||
return await request.put({ url: `/isolation/plan-life-lock/update`, data }) |
|||
}, |
|||
|
|||
// 删除个人生命锁
|
|||
deletePlanLifeLock: async (id: number) => { |
|||
return await request.delete({ url: `/isolation/plan-life-lock/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除个人生命锁 */ |
|||
deletePlanLifeLockList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/isolation/plan-life-lock/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出个人生命锁 Excel
|
|||
exportPlanLifeLock: async (params) => { |
|||
return await request.download({ url: `/isolation/plan-life-lock/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
import request from '@/config/axios' |
|||
import type { Dayjs } from 'dayjs'; |
|||
|
|||
/** 隔离点信息 */ |
|||
export interface Point { |
|||
id?: number; // 主键ID
|
|||
ipType?: string; // 隔离点类型
|
|||
ipName?: string; // 隔离点名称
|
|||
ipLocation?: string; // 隔离点位置
|
|||
ipNumber?: number; // 隔离点编号
|
|||
status?: number; // 隔离点状态
|
|||
guideLockNums?: number; // 电子锁数量
|
|||
} |
|||
|
|||
// 隔离点 API
|
|||
export const PointApi = { |
|||
// 查询隔离点分页
|
|||
getPointPage: async (params: any) => { |
|||
return await request.get({ url: `/isolation/point/page`, params }) |
|||
}, |
|||
|
|||
// 查询隔离点详情
|
|||
getPoint: async (id: number) => { |
|||
return await request.get({ url: `/isolation/point/get?id=` + id }) |
|||
}, |
|||
|
|||
// 新增隔离点
|
|||
createPoint: async (data: Point) => { |
|||
return await request.post({ url: `/isolation/point/create`, data }) |
|||
}, |
|||
|
|||
// 修改隔离点
|
|||
updatePoint: async (data: Point) => { |
|||
return await request.put({ url: `/isolation/point/update`, data }) |
|||
}, |
|||
|
|||
// 删除隔离点
|
|||
deletePoint: async (id: number) => { |
|||
return await request.delete({ url: `/isolation/point/delete?id=` + id }) |
|||
}, |
|||
|
|||
/** 批量删除隔离点 */ |
|||
deletePointList: async (ids: number[]) => { |
|||
return await request.delete({ url: `/isolation/point/delete-list?ids=${ids.join(',')}` }) |
|||
}, |
|||
|
|||
// 导出隔离点 Excel
|
|||
exportPoint: async (params) => { |
|||
return await request.download({ url: `/isolation/point/export-excel`, params }) |
|||
} |
|||
} |
@ -0,0 +1,66 @@ |
|||
import request from '@/config/axios' |
|||
|
|||
|
|||
|
|||
export const getAllLock = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/electron/lock/page`, params }) |
|||
} |
|||
|
|||
export const getAllIsolationPoint = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/isolation/point/page`, params }) |
|||
} |
|||
export const getAllIsolationPlan = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/isolation/plan/page`, params }) |
|||
} |
|||
export const getAllGuidance = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/guide/lock-guide/page`, params }) |
|||
} |
|||
export const getAllGuidanceIsolationPoint = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/guide/isolation-point/page`, params }) |
|||
} |
|||
export const getAllPlanItem = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/isolation/plan-item/page`, params }) |
|||
} |
|||
export const getAllPlanItemDetail = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/isolation/plan-item-detail/page`, params }) |
|||
} |
|||
export const getAllPlanLifeLock = (params: PageParam = { pageSize: 9999, pageNo: 1 }) => { |
|||
return request.get({ url: `/isolation/plan-life-lock/page`, params }) |
|||
} |
|||
const baseUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL |
|||
export const getAgentConfigSignature = (url: string) => { |
|||
return fetch(`${baseUrl}/js/weixin/getAgentConfigSignature?url=${url}`).then(async res => { |
|||
return await res.json() |
|||
}) |
|||
} |
|||
|
|||
export const getConfigSignature = (url: string) => { |
|||
return fetch(`${baseUrl}/js/weixin/getConfigSignature?url=${url}`).then(async res => { |
|||
return await res.json() |
|||
}) |
|||
} |
|||
|
|||
export const getAllFormattedIsolationPlan = () => { |
|||
return request.get({ url: `/isolation/plan/planListAll` }) |
|||
} |
|||
|
|||
|
|||
// 获取所有基础数据
|
|||
export const getAllBaseData = () => request.get({ url: `isolation/point/getListAll` }) |
|||
|
|||
|
|||
// 查询用户管理列表
|
|||
export const getAllUser = () => request.get({ url: 'system/user/list-all-simple' }) |
|||
|
|||
// 根据隔离点获取相关记录
|
|||
export const getIsolationPointDetail = (id: number) => request.get({ url: `isolation/point/getPointListAll`, params: { id } }) |
|||
|
|||
export const bindLock = (data: { planItemDetailId: number; lockId: number }) => request.put({ url: `isolation/point/bindlock`, data }) |
|||
|
|||
export const lockAction = (data: { planItemDetailId: number, operateRecordId: number }) => request.put({ url: `isolation/point/createLock`, data }) |
|||
|
|||
export const verifyLockAction = (data: { planItemDetailId: number, verifyRecordId: number }) => request.put({ url: `isolation/point/verifyLock`, data }) |
|||
|
|||
export const verifyUnlockAction = (data: { planItemDetailId: number, lifelockId: number }) => request.put({ url: `isolation/point/verifyUnLock`, data }) |
|||
|
|||
export const unLockAction = (data: { planItemDetailId: number, planId: number, lifelockId: number }) => request.put({ url: `isolation/point/unLock`, data }) |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1,208 @@ |
|||
<template> |
|||
<el-dialog |
|||
v-model="visible" |
|||
title="任务隔离点详情" |
|||
width="80%" |
|||
:before-close="handleClose" |
|||
destroy-on-close |
|||
> |
|||
<div class="detail-container"> |
|||
<div class="detail-header mb-5"> |
|||
<div class="title">隔离点: {{ isolationPoint?.ipName }}</div> |
|||
<div class="title" v-show="isolationPlanId">检修任务: {{ isolationPlan?.ipName }}</div> |
|||
</div> |
|||
<el-table :data="allDetail" style="width: 100%" border> |
|||
<el-table-column label="关联任务" align="center" prop="planId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.isolationPlans.find((item) => item.id === scope.row.planId)?.ipName }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="任务状态" align="center" prop="lockStatus"> |
|||
<template #default="scope"> |
|||
<DictTag |
|||
:type="DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS" |
|||
:value="scope.row.lockStatus" |
|||
size="small" |
|||
/> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="电子锁" align="center" prop="lock.lockName"> |
|||
<template #default="scope"> |
|||
<template v-if="scope.row.lock"> |
|||
{{ scope.row.lock?.lockName }} |
|||
<DictTag |
|||
:type="DICT_TYPE.LOCK_STATUS" |
|||
:value="scope.row.lock.lockStatus" |
|||
size="small" |
|||
/> |
|||
</template> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="集中挂牌人" align="center" prop="operatorId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.users.find((user) => user.id === scope.row.item?.operatorId)?.nickname }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="集中挂牌协助人" align="center" prop="operatorHelperId"> |
|||
<template #default="scope"> |
|||
{{ |
|||
elLockStore.users.find((user) => user.id === scope.row.item?.operatorHelperId) |
|||
?.nickname |
|||
}} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="验证人" align="center" prop="verifierId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.users.find((user) => user.id === scope.row.item?.verifierId)?.nickname }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="验证协助人" align="center" prop="verifierHelperId"> |
|||
<template #default="scope"> |
|||
{{ |
|||
elLockStore.users.find((user) => user.id === scope.row.item?.verifierHelperId) |
|||
?.nickname |
|||
}} |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<div class="chart-container" ref="detailPanel"></div> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
<script lang="ts" setup> |
|||
import { ref, watch, nextTick, defineExpose, computed } from 'vue' |
|||
import LogicFlow from '@logicflow/core' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import '@logicflow/core/lib/style/index.css' |
|||
import { PlanItemDetail } from '@/api/isolation/planitemdetail' |
|||
import { DICT_TYPE } from '@/utils/dict' |
|||
import { Lock } from '@/api/electron/lock' |
|||
import { PlanItem } from '@/api/isolation/planitem' |
|||
import LockPointNodeAdapter from '@/components/LogicFlow/LockPointNodeAdapter' |
|||
|
|||
defineOptions({ name: 'DetaiPointModal' }) |
|||
const elLockStore = useElLockStore() |
|||
const { isolationPointId, isolationPlanId } = defineProps<{ |
|||
isolationPointId: number | undefined |
|||
isolationPlanId: number | undefined |
|||
}>() |
|||
|
|||
const detailPanel = ref<HTMLElement | null>(null) |
|||
const visible = ref(false) |
|||
let lf: LogicFlow | null = null |
|||
let allDetail = ref<PlanItemDetail & { lock?: Lock; item?: PlanItem }[]>([]) |
|||
|
|||
const initChart = async () => { |
|||
if (!detailPanel.value) return |
|||
|
|||
// 销毁之前的实例 |
|||
if (lf) { |
|||
lf.destroy() |
|||
lf = null |
|||
} |
|||
|
|||
await nextTick() |
|||
|
|||
lf = new LogicFlow({ |
|||
container: detailPanel.value, |
|||
width: detailPanel.value.clientWidth, |
|||
height: detailPanel.value.clientHeight |
|||
}) |
|||
|
|||
// 注册自定义Vue节点 |
|||
lf.register(LockPointNodeAdapter) |
|||
// 渲染节点数据 |
|||
renderNodeData() |
|||
|
|||
// 自适应画布 |
|||
lf.fitView() |
|||
} |
|||
|
|||
const renderNodeData = () => { |
|||
if (!lf || !allDetail.value.length) return |
|||
const nodes = [ |
|||
{ |
|||
properties: { |
|||
planId: isolationPlanId, |
|||
pointId: isolationPointId, |
|||
expand: true |
|||
}, |
|||
type: 'lock-point-node', |
|||
x: 0, |
|||
y: 0 |
|||
} |
|||
] |
|||
|
|||
// 渲染图形数据 |
|||
lf.render({ nodes, edges: [] }) |
|||
requestAnimationFrame(() => { |
|||
lf?.fitView() |
|||
setTimeout(() => lf?.fitView(), 50) |
|||
setTimeout(() => lf?.fitView(), 200) |
|||
}) |
|||
} |
|||
const isolationPoint = computed(() => { |
|||
return elLockStore.isolationPoints.find((item) => item.id === isolationPointId) |
|||
}) |
|||
const isolationPlan = computed(() => { |
|||
return elLockStore.isolationPlans.find((item) => item.id === isolationPlanId) |
|||
}) |
|||
const show = () => { |
|||
visible.value = true |
|||
allDetail.value = elLockStore.planItemDetails |
|||
.filter( |
|||
(item) => |
|||
(isolationPlanId ? item.planId === isolationPlanId : true) && |
|||
item.isolationPointId === isolationPointId |
|||
) |
|||
.map((item) => { |
|||
return { |
|||
...item, |
|||
lock: elLockStore.locks.find((lock) => lock.id === item.lockId), |
|||
item: elLockStore.planItems.find((i) => i.id === item.isolationPlanItemId) |
|||
} |
|||
}) |
|||
nextTick(() => { |
|||
initChart() |
|||
}) |
|||
} |
|||
|
|||
const handleClose = () => { |
|||
visible.value = false |
|||
if (lf) { |
|||
lf.destroy() |
|||
lf = null |
|||
} |
|||
} |
|||
|
|||
watch([() => isolationPlanId, () => isolationPointId], ([newPlanId, newPointId]) => { |
|||
if (newPlanId && newPointId) { |
|||
show() |
|||
} |
|||
}) |
|||
|
|||
defineExpose({ |
|||
show, |
|||
handleClose |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.detail-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
.detail-header { |
|||
display: flex; |
|||
gap: 3rem; |
|||
.title { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
color: #333; |
|||
} |
|||
} |
|||
} |
|||
.chart-container { |
|||
min-height: 25rem; |
|||
} |
|||
</style> |
@ -0,0 +1,390 @@ |
|||
<template> |
|||
<div |
|||
class="node-container" |
|||
:class="{ horizontal: useHorizontal, locked: IsolationPoint?.status == 1 }" |
|||
> |
|||
<div class="header"> |
|||
<div class="title">隔离点</div> |
|||
<div class="point-name"> |
|||
{{ IsolationPoint?.ipName || '—' }} |
|||
</div> |
|||
<el-switch v-model="useHorizontal" size="small" class="ml-auto" /> |
|||
</div> |
|||
<div class="section" v-if="affectedPlans.length"> |
|||
<div class="section-title">关联任务</div> |
|||
<div class="task-list"> |
|||
<div class="task-card" v-for="plan in affectedPlans" :key="plan!.id"> |
|||
<div class="task-row"> |
|||
<span class="label">任务名称</span> |
|||
<span class="value">{{ plan!.ipName }}</span> |
|||
</div> |
|||
<div class="task-row"> |
|||
<span class="label">任务状态</span> |
|||
<DictTag size="small" :type="DICT_TYPE.LOCK_PLAN_ITEM_STATUS" :value="plan!.status!" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="section"> |
|||
<div class="section-title">人员</div> |
|||
<div class="person-rows"> |
|||
<div class="person-row"> |
|||
<span class="label">集中挂牌人</span> |
|||
<div class="user-tags"> |
|||
<span v-for="user in operatorUsers" :key="user.id" class="tag tag--info">{{ |
|||
user.nickname |
|||
}}</span> |
|||
<span v-if="!operatorUsers.length" class="placeholder">—</span> |
|||
</div> |
|||
</div> |
|||
<div class="person-row" v-if="operatorHelperUsers.length"> |
|||
<span class="label">集中挂牌协助人</span> |
|||
<div class="user-tags"> |
|||
<span v-for="user in operatorHelperUsers" :key="user.id" class="tag tag--info">{{ |
|||
user.nickname |
|||
}}</span> |
|||
</div> |
|||
</div> |
|||
<div class="person-row"> |
|||
<span class="label">验证人</span> |
|||
<div class="user-tags"> |
|||
<span v-for="user in verifierUsers" :key="user.id" class="tag tag--success">{{ |
|||
user.nickname |
|||
}}</span> |
|||
<span v-if="!verifierUsers.length" class="placeholder">—</span> |
|||
</div> |
|||
</div> |
|||
<div class="person-row" v-if="verifierHelperUsers.length"> |
|||
<span class="label">验证协助人</span> |
|||
<div class="user-tags"> |
|||
<span v-for="user in verifierHelperUsers" :key="user.id" class="tag tag--success">{{ |
|||
user.nickname |
|||
}}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="section" v-if="locks.length"> |
|||
<div class="section-title">电子锁</div> |
|||
<div class="lock-list"> |
|||
<div class="lock-item" v-for="lock in locks" :key="lock!.id"> |
|||
{{ lock!.lockName }} |
|||
<DictTag :type="DICT_TYPE.LOCK_STATUS" :value="lock!.lockStatus!" size="small" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="section"> |
|||
<div class="section-title">个人生命锁</div> |
|||
<div class="lifelock-list"> |
|||
<div class="lifelock-item" v-for="detail in details" :key="detail.id"> |
|||
<div class="lifelock-users" v-if="detail.affectedUsers && detail.affectedUsers.length"> |
|||
<div class="lifelock-user" v-for="lifelock in detail.affectedUsers" :key="lifelock.id"> |
|||
<span class="user-name">{{ |
|||
getUserById(lifelock.userId)?.nickname || '未知用户' |
|||
}}</span> |
|||
<DictTag |
|||
size="small" |
|||
:type="DICT_TYPE.LOCK_LIFE_LOCK_STATUS" |
|||
:value="lifelock.lockStatus ?? 0" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { computed, ref } from 'vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { isEmpty } from '@/utils/is' |
|||
import { DICT_TYPE, getDictLabel } from '@/utils/dict' |
|||
import { UserVO } from '@/api/system/user' |
|||
const elLockStore = useElLockStore() |
|||
interface Props { |
|||
pointId?: number |
|||
planId?: number |
|||
expand?: boolean |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
pointId: undefined, |
|||
planId: undefined, |
|||
expand: false |
|||
}) |
|||
const affectedPlans = computed(() => { |
|||
if (props.planId) { |
|||
const plan = elLockStore.isolationPlans.find((item) => item.id === props.planId) |
|||
return plan ? [plan] : [] |
|||
} |
|||
const planIds = new Set( |
|||
elLockStore.planItemDetails |
|||
.filter((item) => item.isolationPointId === props.pointId) |
|||
.map((item) => item.planId) |
|||
) |
|||
return [...planIds] |
|||
.map((id) => elLockStore.isolationPlans.find((p) => p.id === id)) |
|||
.filter((i) => !isEmpty(i)) |
|||
.sort((a, b) => a!.id - b!.id) |
|||
}) |
|||
const details = computed(() => { |
|||
const activePlanIds = new Set(affectedPlans.value.map((p) => p!.id)) |
|||
return elLockStore.planItemDetails |
|||
.filter( |
|||
(detail) => detail.isolationPointId === props.pointId && activePlanIds.has(detail.planId!) |
|||
) |
|||
.map((detail) => { |
|||
const lock = elLockStore.planLifeLocks.find((l) => l.isolationPlanItemDetailId === detail.id) |
|||
const affectedUsers = elLockStore.planLifeLocks.filter( |
|||
(l) => l.isolationPlanItemDetailId === detail.id && l.lockType == 5 |
|||
) |
|||
const item = elLockStore.planItems.find((i) => i.id === detail.isolationPlanItemId) |
|||
return { ...detail, lock, affectedUsers, item } |
|||
}) |
|||
}) |
|||
const IsolationPoint = computed(() => { |
|||
return elLockStore.isolationPoints.find((item) => item.id === (props.pointId ?? -1)) |
|||
}) |
|||
|
|||
const locks = computed(() => { |
|||
return details.value |
|||
.map((d) => d.lockId) |
|||
.filter((i) => !isEmpty(i)) |
|||
.map((i) => elLockStore.locks.find((l) => l.id === i)) |
|||
}) |
|||
|
|||
// 用户缓存,避免频繁 find |
|||
const userMap = computed(() => { |
|||
const map = new Map<number, UserVO>() |
|||
elLockStore.users.forEach((u) => map.set(u.id as number, u)) |
|||
return map |
|||
}) |
|||
|
|||
const getUserById = (id?: number) => (id != null ? userMap.value.get(id) : undefined) |
|||
|
|||
const dedupeUsers = (users: (UserVO | undefined)[]) => { |
|||
const seen = new Set<number>() |
|||
const result: UserVO[] = [] |
|||
users.forEach((u) => { |
|||
if (u && !seen.has(u.id as number)) { |
|||
seen.add(u.id as number) |
|||
result.push(u) |
|||
} |
|||
}) |
|||
return result |
|||
} |
|||
|
|||
const operatorUsers = computed(() => |
|||
dedupeUsers(details.value.map((d) => getUserById(d.item?.operatorId))) |
|||
) |
|||
const operatorHelperUsers = computed(() => |
|||
dedupeUsers(details.value.map((d) => getUserById(d.item?.operatorHelperId))) |
|||
) |
|||
const verifierUsers = computed(() => |
|||
dedupeUsers(details.value.map((d) => getUserById(d.item?.verifierId))) |
|||
) |
|||
const verifierHelperUsers = computed(() => |
|||
dedupeUsers(details.value.map((d) => getUserById(d.item?.verifierHelperId))) |
|||
) |
|||
const useHorizontal = ref(props.expand) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.node-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8px; |
|||
padding: 8px; |
|||
background: #ffffffcc; |
|||
border: 1px solid #00f369; |
|||
border-radius: 6px; |
|||
backdrop-filter: blur(2px); |
|||
} |
|||
|
|||
.header { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
} |
|||
.title { |
|||
font-size: 12px; |
|||
color: #909399; |
|||
} |
|||
.point-name { |
|||
font-weight: 600; |
|||
color: #303133; |
|||
} |
|||
|
|||
.section { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 6px; |
|||
} |
|||
.section-title { |
|||
font-size: 12px; |
|||
color: #606266; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.task-list { |
|||
display: grid; |
|||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); |
|||
gap: 6px; |
|||
} |
|||
.task-card { |
|||
border: 1px solid #ebeef5; |
|||
border-radius: 4px; |
|||
padding: 6px; |
|||
background: #fff; |
|||
} |
|||
.task-row { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
gap: 6px; |
|||
font-size: 11px; |
|||
padding: 1px 0; |
|||
} |
|||
.label { |
|||
text-wrap: nowrap; |
|||
color: #909399; |
|||
} |
|||
.value { |
|||
color: #303133; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.person-rows { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 4px; |
|||
} |
|||
.person-row { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
} |
|||
.user-tags { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
gap: 4px; |
|||
} |
|||
|
|||
.tag { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
padding: 0 6px; |
|||
font-size: 11px; |
|||
border-radius: 20px; |
|||
border: 1px solid transparent; |
|||
} |
|||
.tag--info { |
|||
color: #409eff; |
|||
background: #ecf5ff; |
|||
border-color: #b3d8ff; |
|||
} |
|||
.tag--success { |
|||
color: #67c23a; |
|||
background: #f0f9eb; |
|||
border-color: #c2e7b0; |
|||
} |
|||
.tag--warning { |
|||
color: #e6a23c; |
|||
background: #fdf6ec; |
|||
border-color: #f3d19e; |
|||
} |
|||
.tag--danger { |
|||
color: #f56c6c; |
|||
background: #fef0f0; |
|||
border-color: #fbc4c4; |
|||
} |
|||
|
|||
.lifelock-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 4px; |
|||
} |
|||
.lifelock-users { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 4px; |
|||
} |
|||
.lifelock-user { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
.user-name { |
|||
color: #303133; |
|||
} |
|||
|
|||
.placeholder { |
|||
color: #c0c4cc; |
|||
} |
|||
|
|||
/* 横向布局样式 */ |
|||
.node-container.horizontal { |
|||
flex-direction: row; |
|||
flex-wrap: wrap; |
|||
align-items: flex-start; |
|||
gap: 12px; |
|||
max-width: 1000px; |
|||
min-width: 800px; |
|||
} |
|||
|
|||
.node-container.horizontal .header { |
|||
width: 100%; |
|||
flex-shrink: 0; |
|||
border-bottom: 1px solid #e6e8eb; |
|||
padding-bottom: 8px; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.node-container.horizontal .section { |
|||
flex: 1; |
|||
min-width: 180px; |
|||
max-width: 250px; |
|||
border: 1px solid #ebeef5; |
|||
border-radius: 4px; |
|||
padding: 8px; |
|||
background: #fafafa; |
|||
} |
|||
|
|||
.node-container.horizontal .task-list { |
|||
grid-template-columns: 1fr; |
|||
} |
|||
|
|||
.node-container.horizontal .person-rows { |
|||
gap: 6px; |
|||
} |
|||
|
|||
.node-container.horizontal .person-row { |
|||
flex-direction: column; |
|||
align-items: flex-start; |
|||
gap: 4px; |
|||
} |
|||
|
|||
.node-container.horizontal .user-tags { |
|||
width: 100%; |
|||
} |
|||
|
|||
.node-container.horizontal .lifelock-item { |
|||
margin-bottom: 4px; |
|||
} |
|||
.lock-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 4px; |
|||
} |
|||
.lock-item { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.locked { |
|||
border-color: #f56c6c; |
|||
} |
|||
</style> |
@ -0,0 +1,129 @@ |
|||
import { HtmlNode, HtmlNodeModel } from '@logicflow/core' |
|||
import { createApp, h, App as VueApp } from 'vue' |
|||
import LockPointVueNode from './LockPointNode.vue' |
|||
|
|||
class LockPointNodeModel extends HtmlNodeModel { |
|||
setAttributes() { |
|||
// 设置节点的默认属性
|
|||
this.text.editable = false // 禁用默认文本编辑
|
|||
this.text.value = '' // 清空默认文本
|
|||
this.draggable = false |
|||
// 初始占位,后续通过 ResizeObserver 自适应
|
|||
this.width = 400 |
|||
this.height = 200 |
|||
} |
|||
|
|||
/** |
|||
* 获取节点样式 |
|||
*/ |
|||
getNodeStyle() { |
|||
const style = super.getNodeStyle() |
|||
return { |
|||
...style, |
|||
stroke: 'transparent', |
|||
fill: 'transparent' |
|||
} |
|||
} |
|||
} |
|||
|
|||
class LockPointNode extends HtmlNode { |
|||
private vueApp: VueApp | null = null |
|||
private resizeObserver: ResizeObserver | null = null |
|||
|
|||
/** |
|||
* 设置HTML内容 |
|||
*/ |
|||
setHtml(rootEl: SVGForeignObjectElement) { |
|||
const node = this.props.model |
|||
const pointId = node.properties.pointId |
|||
|
|||
// 创建Vue应用实例
|
|||
this.vueApp = createApp({ |
|||
components: { |
|||
LockPointVueNode |
|||
}, |
|||
render: () => h(LockPointVueNode, { |
|||
...node.properties |
|||
}) |
|||
}) |
|||
|
|||
// 创建容器元素
|
|||
const container = document.createElement('div') |
|||
container.id = `lock-point-node-${pointId}` |
|||
container.style.position = 'relative' |
|||
container.style.pointerEvents = 'auto' |
|||
container.style.display = 'inline-block' |
|||
|
|||
// 挂载Vue应用
|
|||
this.vueApp.mount(container) |
|||
rootEl.appendChild(container) |
|||
|
|||
// 使用 ResizeObserver 监听容器尺寸变化,并同步到模型与 foreignObject
|
|||
const syncSize = () => { |
|||
const width = Math.ceil(container.offsetWidth) |
|||
const height = Math.ceil(container.offsetHeight) |
|||
if (!width || !height) return |
|||
|
|||
if (this.props.model.width !== width || this.props.model.height !== height) { |
|||
this.props.model.width = width |
|||
this.props.model.height = height |
|||
rootEl.setAttribute('width', String(width)) |
|||
rootEl.setAttribute('height', String(height)) |
|||
} |
|||
} |
|||
|
|||
// 初次挂载后同步一次
|
|||
requestAnimationFrame(syncSize) |
|||
this.resizeObserver = new ResizeObserver(() => { |
|||
syncSize() |
|||
}) |
|||
this.resizeObserver.observe(container) |
|||
} |
|||
/** |
|||
* 组件销毁时清理Vue应用 |
|||
*/ |
|||
destroy() { |
|||
if (this.resizeObserver) { |
|||
this.resizeObserver.disconnect() |
|||
this.resizeObserver = null |
|||
} |
|||
if (this.vueApp) { |
|||
this.vueApp.unmount() |
|||
this.vueApp = null |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 导出节点配置
|
|||
export default { |
|||
type: 'lock-point-node', |
|||
view: LockPointNode, |
|||
model: LockPointNodeModel |
|||
} |
|||
|
|||
// 导出节点类型
|
|||
export const LockPointNodeType = 'lock-point-node' |
|||
|
|||
// 导出节点属性接口
|
|||
export interface LockPointNodeProperties { |
|||
pointId?: number |
|||
planId?: number |
|||
} |
|||
|
|||
// 导出创建节点的辅助函数
|
|||
export function createLockPointNode( |
|||
id: string, |
|||
x: number, |
|||
y: number, |
|||
properties: LockPointNodeProperties = {} |
|||
) { |
|||
return { |
|||
id, |
|||
type: LockPointNodeType, |
|||
x, |
|||
y, |
|||
properties: { |
|||
...properties |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,126 @@ |
|||
import { defineStore } from 'pinia' |
|||
import { store } from '@/store' |
|||
import { UserVO } from '@/api/system/user' |
|||
import { |
|||
getAllUser, |
|||
getAllBaseData |
|||
} from '@/api/lock' |
|||
import { Lock } from '@/api/electron/lock' |
|||
import { Point } from '@/api/isolation/point' |
|||
import { Plan } from '@/api/isolation/plan' |
|||
import { LockGuide } from '@/api/guide/lockguide' |
|||
import { IsolationPoint } from '@/api/guide/isolationpoint' |
|||
import { PlanItem } from '@/api/isolation/planitem' |
|||
import { PlanItemDetail } from '@/api/isolation/planitemdetail' |
|||
import { PlanLifeLock } from '@/api/isolation/planlifelock' |
|||
import { cloneDeep } from 'lodash-es' |
|||
interface LockState { |
|||
users: UserVO[] |
|||
locks: Lock[] |
|||
isolationPoints: Point[] |
|||
isolationPlans: Plan[] |
|||
lockGuides: LockGuide[] |
|||
isolationPointGuides: IsolationPoint[] |
|||
planItems: PlanItem[] |
|||
planItemDetails: (PlanItemDetail & { planId?: number })[] |
|||
planLifeLocks: (PlanLifeLock & { planId?: number; itemId?: number })[] |
|||
plan2DetailTree: any[] |
|||
plan2ItemTree: any[] |
|||
} |
|||
|
|||
export const useElLockStore = defineStore('elLock', { |
|||
state: (): LockState => { |
|||
return { |
|||
users: [], |
|||
locks: [], |
|||
isolationPoints: [], |
|||
isolationPlans: [], |
|||
lockGuides: [], |
|||
isolationPointGuides: [], |
|||
planItems: [], |
|||
planItemDetails: [], |
|||
planLifeLocks: [], |
|||
plan2DetailTree: [],//plan item itemdetail lock lifelock
|
|||
plan2ItemTree: [], //plan item
|
|||
} |
|||
}, |
|||
getters: {}, |
|||
actions: { |
|||
async init() { |
|||
await this.getUsers() |
|||
await this.getBaseData() |
|||
this.getPlan2DetailTree() |
|||
}, |
|||
async getUsers() { |
|||
const res = await getAllUser() |
|||
this.users = res |
|||
}, |
|||
async getBaseData() { |
|||
try { |
|||
const res = await getAllBaseData() |
|||
this.isolationPoints = res.pointList |
|||
this.locks = res.lockList |
|||
this.planItems = res.planItemList |
|||
this.planItemDetails = res.planItemDetailList.map((detail: PlanItemDetail) => { |
|||
const item: PlanItem | undefined = this.planItems.find(item => item.id === detail.isolationPlanItemId) |
|||
return { |
|||
...detail, |
|||
planId: item?.isolationPlanId, |
|||
} |
|||
}) |
|||
this.planLifeLocks = res.planLifeLockList.map((lock: PlanLifeLock) => { |
|||
const detail: PlanItemDetail | undefined = this.planItemDetails.find(detail => detail.id === lock.isolationPlanItemDetailId) |
|||
const item: PlanItem | undefined = this.planItems.find(item => item.id === detail?.isolationPlanItemId) |
|||
return { |
|||
...lock, |
|||
planId: item?.isolationPlanId, |
|||
itemId: item?.id, |
|||
} |
|||
}) |
|||
this.isolationPlans = res.planList |
|||
this.lockGuides = res.lockGuideList |
|||
this.isolationPointGuides = res.isolationPointList |
|||
} catch (error) { |
|||
this.isolationPoints = [] |
|||
this.locks = [] |
|||
this.planItems = [] |
|||
this.planItemDetails = [] |
|||
this.planLifeLocks = [] |
|||
this.isolationPlans = [] |
|||
this.lockGuides = [] |
|||
this.isolationPointGuides = [] |
|||
} |
|||
}, |
|||
getPlan2DetailTree() { |
|||
const planlist = cloneDeep(this.isolationPlans).filter(plan => plan.status == 0) |
|||
const itemlist = cloneDeep(this.planItems) |
|||
const detaillist = cloneDeep(this.planItemDetails) |
|||
const lifelocklist = cloneDeep(this.planLifeLocks) |
|||
planlist.forEach((plan: any) => { |
|||
const items = itemlist.filter(item => item.isolationPlanId === plan.id) |
|||
plan.planItem = items |
|||
items.forEach((item: any) => { |
|||
const details = detaillist.filter(detail => detail.isolationPlanItemId === item.id).map(deteail => { |
|||
const lock = this.locks.find(lock => lock.id === deteail.lockId) |
|||
const isolationPoint = this.isolationPoints.find(point => point.id === deteail.isolationPointId) |
|||
return { |
|||
...deteail, |
|||
lock, |
|||
isolationPoint |
|||
} |
|||
}) |
|||
item.planItemDetail = details |
|||
details.forEach((detail: any) => { |
|||
const lifelock = lifelocklist.filter(lifelock => lifelock.isolationPlanItemDetailId === detail.id) |
|||
detail.itemLifeLock = lifelock |
|||
}) |
|||
}) |
|||
}) |
|||
this.plan2DetailTree = planlist |
|||
} |
|||
}, |
|||
persist: true |
|||
}) |
|||
export const useElLockStoreWithOut = () => { |
|||
return useElLockStore(store) |
|||
} |
@ -0,0 +1,565 @@ |
|||
/** |
|||
* 蓝牙锁通信工具模块 |
|||
* 提供蓝牙锁设备的通信接口和指令处理功能 |
|||
*/ |
|||
|
|||
// 声明微信小程序API类型
|
|||
declare const wx: { |
|||
onBLECharacteristicValueChange: (callback: (res: { value: ArrayBuffer }) => void) => void; |
|||
writeBLECharacteristicValue: (options: { |
|||
deviceId: string; |
|||
serviceId: string; |
|||
characteristicId: string; |
|||
value: ArrayBuffer; |
|||
success?: (res: any) => void; |
|||
fail?: (error: any) => void; |
|||
}) => void; |
|||
}; |
|||
|
|||
// 全局变量
|
|||
let deviceId = ''; |
|||
const serviceId = '0000FFF0-0000-1000-8000-00805F9B34FB'; |
|||
const characteristicId = '0000FFF2-0000-1000-8000-00805F9B34FB'; |
|||
let callback: any = ''; |
|||
let taskTime = 10; |
|||
|
|||
/** |
|||
* 创建指令 |
|||
* @param command 指令代码 |
|||
* @param data 数据内容 |
|||
* @returns 构建好的指令数组 |
|||
*/ |
|||
function createInstruct(command: number, data: number[]): number[] { |
|||
const header = [0xaa, 0xbb]; |
|||
let instruction: number[] = []; |
|||
let payload: number[] = []; |
|||
|
|||
instruction = instruction.concat(header); |
|||
payload.push(command); |
|||
payload = payload.concat(data); |
|||
payload.splice(payload.length + 2); |
|||
payload = doCrc(payload); |
|||
instruction = instruction.concat(payload); |
|||
|
|||
return instruction; |
|||
} |
|||
|
|||
/** |
|||
* 执行CRC校验 |
|||
* @param data 需要校验的数据 |
|||
* @returns 添加CRC后的数据 |
|||
*/ |
|||
function doCrc(data: number[]): number[] { |
|||
const crcResult = crc16ab(data, true, false); |
|||
return data.concat(crcResult); |
|||
} |
|||
|
|||
/** |
|||
* 字符串转Uint8数组 |
|||
* @param str 输入字符串 |
|||
* @returns Uint8数组 |
|||
*/ |
|||
function strToUint8(str: string): number[] { |
|||
const result: number[] = []; |
|||
for (let i = 0; i < str.length; i++) { |
|||
result[i] = str.charCodeAt(i); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* CRC16校验算法 |
|||
* @param data 数据数组 |
|||
* @param reverseResult 是否反转结果 |
|||
* @param isHexString 是否为十六进制字符串 |
|||
* @returns CRC校验结果 |
|||
*/ |
|||
function crc16ab(data: any[], reverseResult: boolean, isHexString: boolean): number[] { |
|||
let crc = 0xffff; |
|||
const polynomial = 0x1021; |
|||
|
|||
for (let i = 0; i < data.length; i++) { |
|||
const byte = isHexString ? parseInt(data[i], 16) : data[i]; |
|||
for (let bit = 0; bit < 8; bit++) { |
|||
const bitValue = (byte >> (7 - bit) & 1) === 1; |
|||
const crcBit = (crc >> 15 & 1) === 1; |
|||
crc <<= 1; |
|||
if (crcBit !== bitValue) { |
|||
crc ^= polynomial; |
|||
} |
|||
} |
|||
} |
|||
|
|||
const high = (crc & 0xff00) >> 8; |
|||
const low = crc & 0xff; |
|||
return reverseResult ? [low, high] : [high, low]; |
|||
} |
|||
|
|||
/** |
|||
* 数组分块 |
|||
* @param array 原数组 |
|||
* @param chunkSize 每块大小 |
|||
* @returns 分块后的数组 |
|||
*/ |
|||
function arrayChunks(array: any[], chunkSize: number): any[] { |
|||
const chunks: any[] = []; |
|||
for (let i = 0; i < array.length; i += chunkSize) { |
|||
chunks.push(array.slice(i, i + chunkSize)); |
|||
} |
|||
return chunks; |
|||
} |
|||
|
|||
/** |
|||
* 获取CRC十六进制值 |
|||
* @param data 数据 |
|||
* @returns 十六进制CRC值 |
|||
*/ |
|||
function getCrcHex(data: any[]): string { |
|||
const crcArray = crc16ab(data, true, true); |
|||
const hexString = ab2hex(crcArray).join(''); |
|||
return hexString; |
|||
} |
|||
|
|||
/** |
|||
* 数组转十六进制 |
|||
* @param arrayBuffer 数组缓冲区 |
|||
* @returns 十六进制字符串数组 |
|||
*/ |
|||
function ab2hex(arrayBuffer: ArrayBuffer | number[]): string[] { |
|||
const hexArray = Array.prototype.map.call( |
|||
new Uint8Array(arrayBuffer), |
|||
function(byte: number): string { |
|||
return ('00' + byte.toString(16)).slice(-2); |
|||
} |
|||
) as string[]; |
|||
return hexArray; |
|||
} |
|||
|
|||
/** |
|||
* 十六进制转数组 |
|||
* @param hexString 十六进制字符串 |
|||
* @returns 数组 |
|||
*/ |
|||
function hex2ab(hexString: string[]): number[] { |
|||
const result: number[] = []; |
|||
for (let i = 0; i < hexString.length; i++) { |
|||
result.push(parseInt(hexString[i], 16)); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 处理指令 |
|||
* @param instructData 指令数据 |
|||
*/ |
|||
function dealInstruct(instructData: any): void { |
|||
const hexData = ab2hex(instructData); |
|||
|
|||
while (hexData.indexOf('aa') >= 0) { |
|||
const startIndex = hexData.indexOf('aa'); |
|||
const length = parseInt(hexData[startIndex + 2], 16); |
|||
const instructHex = hexData.slice(startIndex, length + 3); |
|||
callback(resolutionInstruct(instructHex)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查数据有效性 |
|||
* @param data 数据 |
|||
* @returns 是否有效 |
|||
*/ |
|||
function checkValidity(data: any[]): boolean { |
|||
if (data.length < 2) return false; |
|||
|
|||
const chunks = arrayChunks(data, data.length - 2); |
|||
const crcData = chunks.pop(); |
|||
if (!crcData) return false; |
|||
const receivedCrc = crcData[0] + crcData[1]; |
|||
const payload = chunks.pop(); |
|||
if (!payload) return false; |
|||
const calculatedCrc = getCrcHex(payload); |
|||
|
|||
return receivedCrc === calculatedCrc; |
|||
} |
|||
|
|||
/** |
|||
* 初始化蓝牙通信 |
|||
* @param deviceIdParam 设备ID |
|||
* @param callbackParam 回调函数 |
|||
*/ |
|||
function init(deviceIdParam: string, callbackParam: any): void { |
|||
deviceId = deviceIdParam; |
|||
callback = callbackParam; |
|||
wx.onBLECharacteristicValueChange(function(res: any) { |
|||
dealInstruct(res.value); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 解析指令 |
|||
* @param instructArray 指令数组 |
|||
* @returns 解析结果 |
|||
*/ |
|||
function resolutionInstruct(instructArray: string[]): any { |
|||
if (instructArray.shift() != 'aa') return setCallData('-1', '-1', '数据错误'); |
|||
if (instructArray.shift() != 'bb') return setCallData('-1', '-1', '数据错误'); |
|||
|
|||
if (checkValidity(instructArray)) { |
|||
const command = parseInt(instructArray[1], 16); |
|||
|
|||
if (command == 0x8) { |
|||
// 锁状态指令
|
|||
if (instructArray[2] == '01') return setCallData('08', '01', '手柄打开'); |
|||
else if (instructArray[2] == '00') return setCallData('08', '00', '手柄关闭'); |
|||
} else if (command == 0x7) { |
|||
// 开锁指令
|
|||
if (instructArray[2] == '01') return setCallData('07', '01', '开锁成功'); |
|||
else if (instructArray[2] == '02') return setCallData('07', '00', '钥匙开锁'); |
|||
} else if (command == 0x12) { |
|||
// 设置锁ID指令
|
|||
if (instructArray[2] == '01') return setCallData('12', '01', '修改成功'); |
|||
else if (instructArray[2] == '02') return setCallData('12', '00', '修改失败'); |
|||
} else if (command == 0x11) { |
|||
// 获取锁ID指令
|
|||
if (instructArray[2] == '01') { |
|||
const idData = instructArray.slice(3, 7); |
|||
const idArray = hexToAb(idData); |
|||
const lockId = byteToInt(idArray); |
|||
return setCallData('11', lockId, '获取成功'); |
|||
} |
|||
} else if (command == 0x10) { |
|||
// 发送开锁密钥指令
|
|||
if (instructArray[2] == '01') return setCallData('10', '01', '开锁成功'); |
|||
} else if (command == 0x5) { |
|||
// 设置蓝牙名称指令
|
|||
return instructArray[2] == '01' ? setCallData('05', '01', '修改成功') : setCallData('05', '00', '修改失败'); |
|||
} else if (command == 0xa) { |
|||
// 获取当前时间指令
|
|||
const timeData = instructArray.slice(2, 7); |
|||
setTaskTime(timeData, taskTime, () => {}); |
|||
} else if (command == 0x22) { |
|||
// 设置任务时间指令
|
|||
return instructArray[2] == '01' ? setCallData('22', '01', '修改成功') : setCallData('22', '00', '修改失败'); |
|||
} else if (command == 0x23) { |
|||
// 设置任务权限指令
|
|||
return instructArray[2] == '01' ? setCallData('23', '01', '修改成功') : setCallData('23', '00', '修改失败'); |
|||
} else if (command == 0x2a) { |
|||
// 清除任务权限指令
|
|||
return instructArray[2] == '01' ? setCallData('2A', '01', '清除成功') : setCallData('2A', '00', '清除失败'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置回调数据 |
|||
* @param code 状态码 |
|||
* @param data 数据 |
|||
* @param msg 消息 |
|||
* @returns 回调数据对象 |
|||
*/ |
|||
function setCallData(code: string, data: any, msg: string): any { |
|||
const callbackData = { |
|||
'code': code, |
|||
'data': data, |
|||
'msg': msg |
|||
}; |
|||
return callbackData; |
|||
} |
|||
|
|||
/** |
|||
* 提交指令 |
|||
* @param instruction 指令数据 |
|||
* @param callback 回调函数 |
|||
*/ |
|||
function submitInstructions(instruction: number[], callback: any): void { |
|||
const buffer = new Uint8Array(instruction).buffer; |
|||
|
|||
if (buffer.byteLength > 0) { |
|||
wx.writeBLECharacteristicValue({ |
|||
'deviceId': deviceId, |
|||
'serviceId': serviceId, |
|||
'characteristicId': characteristicId, |
|||
'value': buffer, |
|||
'success'(res: any) { |
|||
callback(res); |
|||
}, |
|||
'fail'(error: any) { |
|||
callback(error); |
|||
} |
|||
}); |
|||
} else { |
|||
callback('参数错误'); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查回调函数 |
|||
* @param callback 回调函数 |
|||
* @returns 是否为有效的函数 |
|||
*/ |
|||
function checkCallback(callback: any): boolean { |
|||
return typeof callback == 'function'; |
|||
} |
|||
|
|||
/** |
|||
* 开锁指令 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function openLock(callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const data = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; |
|||
const instruction = createInstruct(0x7, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送开锁密钥 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function sendOpenKey(callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const data = [0x32, 0x0, 0xff, 0xff, 0xff, 0xff]; |
|||
const instruction = createInstruct(0x10, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取锁状态 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function lockStatus(callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const data: number[] = []; |
|||
const instruction = createInstruct(0x8, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 发送心跳 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function sendHeart(callback: any): any { |
|||
const data: number[] = []; |
|||
const instruction = createInstruct(0xff, data); |
|||
return submitInstructions(instruction, callback); |
|||
} |
|||
|
|||
/** |
|||
* 设置蓝牙名称 |
|||
* @param name 蓝牙名称 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function setBluetoothName(name: string, callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const nameData = strToUint8(name); |
|||
const instruction = createInstruct(0x5, nameData); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取锁ID |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function getLockId(callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const data = [0xff, 0xff, 0xff, 0xff]; |
|||
const instruction = createInstruct(0x11, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置锁ID |
|||
* @param lockId 锁ID |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function setLockId(lockId: number, callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const padding = [0xff, 0xff, 0xff, 0xff]; |
|||
const idBytes = intToByte4(lockId); |
|||
const data = idBytes.concat(padding); |
|||
const instruction = createInstruct(0x12, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 开始任务时间 |
|||
* @param time 时间参数 |
|||
* @param callback 回调函数 |
|||
*/ |
|||
function startTaskTime(time: number, callback: any): void { |
|||
taskTime = time; |
|||
getCurrentTime(callback); |
|||
} |
|||
|
|||
/** |
|||
* 获取当前时间 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function getCurrentTime(callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const data: number[] = []; |
|||
const instruction = createInstruct(0xa, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置任务时间 |
|||
* @param timeHex 时间十六进制 |
|||
* @param timeValue 时间值 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function setTaskTime(timeHex: string[], timeValue: number, callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const timeData = hex2ab(timeHex); |
|||
const timeBytes = intToByte4R(timeValue); |
|||
const data = timeData.concat(timeBytes).concat([0xff]); |
|||
const instruction = createInstruct(0x22, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置任务权限 |
|||
* @param taskId 任务ID |
|||
* @param permission 权限值 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function setTaskPrem(taskId: number, permission: number, callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const padding = [0xff, 0xff, 0xff, 0xff]; |
|||
const idBytes = intToByte4(taskId); |
|||
const data = idBytes.concat(padding); |
|||
data.push(permission); |
|||
const instruction = createInstruct(0x23, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 清除任务权限 |
|||
* @param callback 回调函数 |
|||
* @returns 执行结果 |
|||
*/ |
|||
function cleanTaskPrem(callback: any): any { |
|||
const isValidCallback = checkCallback(callback); |
|||
if (isValidCallback) { |
|||
const data: number[] = []; |
|||
const instruction = createInstruct(0x2a, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return '参数错误'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 整数转4字节数组(大端序) |
|||
* @param value 整数值 |
|||
* @returns 字节数组 |
|||
*/ |
|||
function intToByte4(value: number): number[] { |
|||
const bytes: number[] = []; |
|||
bytes[3] = value & 0xff; |
|||
bytes[2] = (value >> 8) & 0xff; |
|||
bytes[1] = (value >> 16) & 0xff; |
|||
bytes[0] = (value >> 24) & 0xff; |
|||
return bytes; |
|||
} |
|||
|
|||
/** |
|||
* 整数转4字节数组(小端序) |
|||
* @param value 整数值 |
|||
* @returns 字节数组 |
|||
*/ |
|||
function intToByte4R(value: number): number[] { |
|||
const bytes: number[] = []; |
|||
bytes[0] = value & 0xff; |
|||
bytes[1] = (value >> 8) & 0xff; |
|||
bytes[2] = (value >> 16) & 0xff; |
|||
bytes[3] = (value >> 24) & 0xff; |
|||
return bytes; |
|||
} |
|||
|
|||
/** |
|||
* 字节数组转整数 |
|||
* @param bytes 字节数组 |
|||
* @returns 整数值 |
|||
*/ |
|||
function byteToInt(bytes: number[]): number { |
|||
const byte0 = bytes[3] & 0xff; |
|||
const byte1 = bytes[2] & 0xff; |
|||
const byte2 = bytes[1] & 0xff; |
|||
const byte3 = bytes[0] & 0xff; |
|||
return (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0; |
|||
} |
|||
|
|||
/** |
|||
* 十六进制数组转数组 |
|||
* @param hexArray 十六进制数组 |
|||
* @returns 数字数组 |
|||
*/ |
|||
function hexToAb(hexArray: string[]): number[] { |
|||
const result: number[] = []; |
|||
for (let i = 0; i < hexArray.length; i++) { |
|||
result.push(parseInt(hexArray[i], 16)); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
// 模块导出
|
|||
module.exports = { |
|||
'init': init, |
|||
'openLock': openLock, |
|||
'lockStatus': lockStatus, |
|||
'sendHeart': sendHeart, |
|||
'setBluetoothName': setBluetoothName, |
|||
'sendOpenKey': sendOpenKey, |
|||
'getLockId': getLockId, |
|||
'setLockId': setLockId, |
|||
'startTaskTime': startTaskTime, |
|||
'setTaskPrem': setTaskPrem, |
|||
'cleanTaskPrem': cleanTaskPrem |
|||
}; |
@ -0,0 +1,334 @@ |
|||
var deviceId = ''; |
|||
var serviceId = '0000FFF0-0000-1000-8000-00805F9B34FB'; |
|||
var characteristicId = '0000FFF2-0000-1000-8000-00805F9B34FB'; |
|||
var callback = ''; |
|||
var taskTime = 10; |
|||
|
|||
function createInstruct(command, data) { |
|||
let header = [0xaa, 0xbb]; |
|||
let instruction = []; |
|||
let payload = []; |
|||
instruction = instruction.concat(header); |
|||
payload.push(command); |
|||
payload = payload.concat(data); |
|||
payload.push(payload.length + 2); |
|||
payload = doCrc(payload); |
|||
instruction = instruction.concat(payload); |
|||
return instruction; |
|||
} |
|||
|
|||
function doCrc(data) { |
|||
let crc = crc16ab(data, true, false); |
|||
data = data.concat(crc); |
|||
return data; |
|||
} |
|||
|
|||
function strToUint8(str) { |
|||
let result = []; |
|||
for (let i = 0; i < str.length; i++) { |
|||
result[i] = str.charCodeAt(i); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
function crc16ab(data, swapBytes, isHex) { |
|||
let crc = 0xffff; |
|||
let polynomial = 0x1021; |
|||
for (let i = 0; i < data.length; i++) { |
|||
let byte = isHex ? parseInt(data[i], 16) : data[i]; |
|||
for (let j = 0; j < 8; j++) { |
|||
let bit = (byte >> (7 - j) & 0x1) === 0x1; |
|||
let crcBit = (crc >> 15 & 0x1) === 0x1; |
|||
crc <<= 1; |
|||
if (crcBit ^ bit) crc ^= polynomial; |
|||
} |
|||
} |
|||
let highByte = (crc & 0xff00) >> 8; |
|||
let lowByte = crc & 0xff; |
|||
return swapBytes ? [lowByte, highByte] : [highByte, lowByte]; |
|||
} |
|||
|
|||
function arrayChunks(array, size) { |
|||
let chunks = []; |
|||
for (let i = 0; i < array.length; i += size) { |
|||
chunks.push(array.slice(i, i + size)); |
|||
} |
|||
return chunks; |
|||
} |
|||
|
|||
function getCrcHex(data) { |
|||
let crc = crc16ab(data, true, true); |
|||
let hex = ab2hex(crc).join(''); |
|||
return hex; |
|||
} |
|||
|
|||
function ab2hex(buffer) { |
|||
let hexArray = Array.prototype.map.call(new Uint8Array(buffer), function(byte) { |
|||
return ('00' + byte.toString(16)).slice(-2); |
|||
}); |
|||
return hexArray; |
|||
} |
|||
|
|||
function hex2ab(hexArray) { |
|||
let result = Array.prototype.map.call(new Uint8Array(hexArray), function(byte) { |
|||
return parseInt(byte, 16); |
|||
}); |
|||
return result; |
|||
} |
|||
|
|||
function dealInstruct(data) { |
|||
let hexData = ab2hex(data); |
|||
while (hexData.indexOf('aa') >= 0) { |
|||
let headerIndex = hexData.indexOf('aa'); |
|||
let length = parseInt(hexData[headerIndex + 2], 16); |
|||
let instruction = hexData.slice(headerIndex, length + 3); |
|||
callback(resolutionInstruct(instruction)); |
|||
} |
|||
} |
|||
|
|||
function checkValidity(data) { |
|||
if (data.length < 2) return false; |
|||
let chunks = arrayChunks(data, data.length - 2); |
|||
let crcBytes = chunks.pop(); |
|||
let crcHex = crcBytes[0] + crcBytes[1]; |
|||
let dataToCheck = chunks.pop(); |
|||
let calculatedCrc = getCrcHex(dataToCheck); |
|||
return crcHex === calculatedCrc; |
|||
} |
|||
|
|||
function init(device, cb) { |
|||
deviceId = device; |
|||
callback = cb; |
|||
wx.onBLECharacteristicValueChange(function(res) { |
|||
dealInstruct(res.value); |
|||
}); |
|||
} |
|||
|
|||
function resolutionInstruct(instruction) { |
|||
if (instruction.shift() !== 'aa') return setCallData('-1', '-1', 'Data error'); |
|||
if (instruction.pop() !== 'bb') return setCallData('-1', '-1', 'Data error'); |
|||
if (checkValidity(instruction)) { |
|||
let command = parseInt(instruction[1], 16); |
|||
if (command === 0x8) { |
|||
if (instruction[2] === '01') return setCallData('08', '01', 'Lock opened successfully'); |
|||
else if (instruction[2] === '00') return setCallData('08', '00', 'Lock failed to open'); |
|||
} else if (command === 0x7) { |
|||
if (instruction[2] === '01') return setCallData('07', '01', 'Handle opened'); |
|||
else if (instruction[2] === '00') return setCallData('07', '00', 'Handle closed'); |
|||
} else if (command === 0x12) { |
|||
if (instruction[2] === '01') return setCallData('12', '01', 'Modification successful'); |
|||
else if (instruction[2] === '00') return setCallData('12', '00', 'Modification failed'); |
|||
} else if (command === 0x11) { |
|||
if (instruction[2] === '01') { |
|||
let lockIdBytes = instruction.slice(3, 7); |
|||
lockIdBytes = hexToAb(lockIdBytes); |
|||
let lockId = byteToInt(lockIdBytes); |
|||
return setCallData('11', lockId, 'Retrieved successfully'); |
|||
} |
|||
} else if (command === 0x10) { |
|||
if (instruction[2] === '01') return setCallData('10', '01', 'Key unlock successful'); |
|||
} else if (command === 0x5) { |
|||
return instruction[2] === '01' ? setCallData('05', '01', 'Modification successful') : setCallData('05', '00', 'Modification failed'); |
|||
} else if (command === 0xa) { |
|||
let timeData = instruction.slice(2, 7); |
|||
setTaskTime(timeData, taskTime, () => {}); |
|||
} else if (command === 0x22) { |
|||
return instruction[2] === '01' ? setCallData('22', '01', 'Modification successful') : setCallData('22', '00', 'Modification failed'); |
|||
} else if (command === 0x23) { |
|||
return instruction[2] === '01' ? setCallData('23', '01', 'Modification successful') : setCallData('23', '00', 'Modification failed'); |
|||
} else if (command === 0x2a) { |
|||
return instruction[2] === '01' ? setCallData('2A', '01', 'Clear successful') : setCallData('2A', '00', 'Clear failed'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function setCallData(code, data, msg) { |
|||
return { code: code, data: data, msg: msg }; |
|||
} |
|||
|
|||
function submitInstructions(instruction, callback) { |
|||
let buffer = new Uint8Array(instruction).buffer; |
|||
if (buffer !== '') { |
|||
wx.writeBLECharacteristicValue({ |
|||
deviceId: deviceId, |
|||
serviceId: serviceId, |
|||
characteristicId: characteristicId, |
|||
value: buffer, |
|||
success(res) { callback(res); }, |
|||
fail(err) { callback(err); } |
|||
}); |
|||
} else { |
|||
callback('Parameter error'); |
|||
} |
|||
} |
|||
|
|||
function checkCallback(cb) { |
|||
return typeof cb === 'function'; |
|||
} |
|||
|
|||
function openLock(callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; |
|||
let instruction = createInstruct(0x7, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function sendOpenKey(callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = [0x32, 0x0, 0xff, 0xff, 0xff, 0xff]; |
|||
let instruction = createInstruct(0x10, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function lockStatus(callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = []; |
|||
let instruction = createInstruct(0x8, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function sendHeart(callback) { |
|||
let data = []; |
|||
let instruction = createInstruct(0xff, data); |
|||
return submitInstructions(instruction, callback); |
|||
} |
|||
|
|||
function setBluetoothName(name, callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = strToUint8(name); |
|||
let instruction = createInstruct(0x5, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function getLockId(callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = [0xff, 0xff, 0xff, 0xff]; |
|||
let instruction = createInstruct(0x11, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function setLockId(lockId, callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = [0xff, 0xff, 0xff, 0xff]; |
|||
let lockIdBytes = intToByte4(lockId); |
|||
lockIdBytes = lockIdBytes.concat(data); |
|||
let instruction = createInstruct(0x12, lockIdBytes); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function startTaskTime(time, callback) { |
|||
taskTime = time; |
|||
getCurrentTime(callback); |
|||
} |
|||
|
|||
function getCurrentTime(callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = []; |
|||
let instruction = createInstruct(0xa, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function setTaskTime(timeData, time, callback) { |
|||
if (checkCallback(callback)) { |
|||
let timeBytes = hex2ab(timeData); |
|||
let timeValue = intToByte4R(time); |
|||
timeBytes = timeBytes.concat(timeValue); |
|||
timeBytes = timeBytes.concat([0xff]); |
|||
let instruction = createInstruct(0x22, timeBytes); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function setTaskPrem(param1, param2, callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = [0xff, 0xff, 0xff, 0xff]; |
|||
let param1Bytes = intToByte4(param1); |
|||
param1Bytes = param1Bytes.concat(data); |
|||
param1Bytes.push(param2); |
|||
let instruction = createInstruct(0x23, param1Bytes); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function cleanTaskPrem(callback) { |
|||
if (checkCallback(callback)) { |
|||
let data = []; |
|||
let instruction = createInstruct(0x2a, data); |
|||
return submitInstructions(instruction, callback); |
|||
} else { |
|||
return 'Parameter error'; |
|||
} |
|||
} |
|||
|
|||
function intToByte4(number) { |
|||
let bytes = []; |
|||
bytes[3] = number & 0xff; |
|||
bytes[2] = (number >> 8) & 0xff; |
|||
bytes[1] = (number >> 16) & 0xff; |
|||
bytes[0] = (number >> 24) & 0xff; |
|||
return bytes; |
|||
} |
|||
|
|||
function intToByte4R(number) { |
|||
let bytes = []; |
|||
bytes[0] = number & 0xff; |
|||
bytes[1] = (number >> 8) & 0xff; |
|||
bytes[2] = (number >> 16) & 0xff; |
|||
bytes[3] = (number >> 24) & 0xff; |
|||
return bytes; |
|||
} |
|||
|
|||
function byteToInt(bytes) { |
|||
let b0 = bytes[3] & 0xff; |
|||
let b1 = bytes[2] & 0xff; |
|||
let b2 = bytes[1] & 0xff; |
|||
let b3 = bytes[0] & 0xff; |
|||
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0; |
|||
} |
|||
|
|||
function hexToAb(hexArray) { |
|||
let result = []; |
|||
for (let i = 0; i < hexArray.length; i++) { |
|||
result.push(parseInt(hexArray[i], 16)); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
module.exports = { |
|||
init, |
|||
openLock, |
|||
lockStatus, |
|||
sendHeart, |
|||
setBluetoothName, |
|||
sendOpenKey, |
|||
getLockId, |
|||
setLockId, |
|||
startTaskTime, |
|||
setTaskPrem, |
|||
cleanTaskPrem |
|||
}; |
@ -0,0 +1,300 @@ |
|||
import { useAppStore } from '@/store/modules/app' |
|||
import { useUserStore } from '@/store/modules/user' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
const elLockStore = useElLockStore() |
|||
const appStore = useAppStore() |
|||
import ww from '@/utils/ww' |
|||
import { LockApi } from '@/api/electron/lock' |
|||
import { LockWorkRecordApi } from '@/api/electron/lockworkcord' |
|||
|
|||
import { bindLock as bindLockApi } from '@/api/lock' |
|||
import { ElMessage, ElMessageBox } from 'element-plus' |
|||
import download from '@/utils/download' |
|||
import { updateFile } from '@/api/infra/file' |
|||
|
|||
|
|||
type Location = { |
|||
latitude: number |
|||
longitude: number |
|||
accuracy: number |
|||
} |
|||
export const getCurrentLocation = async (): Promise<Location> => { |
|||
if (appStore.isWorkWechat) { |
|||
const res = await ww.getLocation() |
|||
return { |
|||
latitude: res.latitude, |
|||
longitude: res.longitude, |
|||
accuracy: res.accuracy |
|||
} |
|||
} else if (window.navigator.geolocation) { |
|||
return new Promise((resolve, reject) => { |
|||
window.navigator.geolocation.getCurrentPosition((position) => { |
|||
resolve({ |
|||
latitude: position.coords.latitude, |
|||
longitude: position.coords.longitude, |
|||
accuracy: position.coords.accuracy |
|||
}) |
|||
}, (error) => { |
|||
ElMessage.error('获取位置失败') |
|||
resolve({ |
|||
latitude: 0, |
|||
longitude: 0, |
|||
accuracy: 0 |
|||
}) |
|||
}) |
|||
}) |
|||
} else { |
|||
return Promise.resolve({ |
|||
latitude: 0, |
|||
longitude: 0, |
|||
accuracy: 0 |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// 统一执行绑定逻辑:校验锁状态 -> 获取位置 -> 记录工单 -> 批量更新状态
|
|||
async function performBind(detail: any, lockId: number, operatorId: number): Promise<void> { |
|||
// 校验锁具状态
|
|||
const lock = await LockApi.getLock(lockId) |
|||
if (!lock) { |
|||
ElMessage.error('锁具不存在') |
|||
return |
|||
} |
|||
if (lock.lockStatus != 2) { |
|||
ElMessage.error('锁具状态异常 无法绑定') |
|||
return |
|||
} |
|||
|
|||
// 获取当前位置
|
|||
let location: Location |
|||
try { |
|||
location = await getCurrentLocation() |
|||
} catch (_) { |
|||
ElMessage.error('获取位置失败,无法绑定') |
|||
return |
|||
} |
|||
|
|||
|
|||
|
|||
// // 批量更新业务状态
|
|||
// await Promise.all([
|
|||
// PointApi.updatePoint({
|
|||
// id: detail.isolationPointId,
|
|||
// status: 1 // 已锁定
|
|||
// }),
|
|||
// LockApi.updateLock({
|
|||
// id: lockId,
|
|||
// lockStatus: 7 // 已绑定
|
|||
// }),
|
|||
// PlanItemDetailApi.updatePlanItemDetail({
|
|||
// id: detail.id,
|
|||
// lockId: lockId,
|
|||
// lockStatus: 1 // 未上锁
|
|||
// })
|
|||
// ])
|
|||
await bindLockApi({ planItemDetailId: detail.id, lockId }) |
|||
// 创建工单记录
|
|||
await LockWorkRecordApi.createLockWorkRecord({ |
|||
operatorId: Number(operatorId), |
|||
lockId: lockId, |
|||
isolationPlanItemDetailId: detail.id, |
|||
recordType: 2, // 未绑定
|
|||
gpsCoordinates: `${location.latitude},${location.longitude}` |
|||
}) |
|||
ElMessage.success('绑定成功') |
|||
} |
|||
|
|||
|
|||
export const bindLock = async (detail: any) => { |
|||
try { |
|||
if (appStore.isWorkWechat) { |
|||
const currentUserId = useUserStore().getUser.id |
|||
// 扫描二维码获取锁具ID
|
|||
const scanRes = await (ww as any).scanQRCode({ |
|||
needResult: true, |
|||
scanType: ['qrCode'] |
|||
}) |
|||
const lockId = Number((scanRes && scanRes.resultStr) || NaN) |
|||
if (!lockId || Number.isNaN(lockId)) { |
|||
ElMessage.error('二维码内容无效') |
|||
return |
|||
} |
|||
await performBind(detail, lockId, currentUserId) |
|||
} else { |
|||
const lockOptions = elLockStore.locks.filter(i => i.lockEnableStatus == 1 && i.lockStatus == 2).map((lock) => ({ |
|||
label: lock.lockName, |
|||
number: lock.lockNumber, |
|||
value: lock.id |
|||
})) |
|||
|
|||
// 检查是否有可用的锁具
|
|||
if (lockOptions.length === 0) { |
|||
ElMessage.error('暂无可用的锁具') |
|||
return |
|||
} |
|||
|
|||
// 如果只有一个锁具,直接使用
|
|||
if (lockOptions.length === 1) { |
|||
const lockId = lockOptions[0]?.value |
|||
if (lockId) { |
|||
await performBind(detail, lockId, useUserStore().getUser.id) |
|||
} |
|||
return |
|||
} |
|||
|
|||
// 有多个锁具时,让用户选择
|
|||
const lockOptionsHtml = lockOptions.map((option, index) => |
|||
`<option value="${option.value}">${option.number}. ${option.label}</option>` |
|||
).join('') |
|||
|
|||
let selectedLockId: string | null = null |
|||
|
|||
try { |
|||
await ElMessageBox.confirm( |
|||
`<div>
|
|||
<p style="margin-bottom: 10px;">请选择要绑定的锁具:</p> |
|||
<select id="lockSelector" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> |
|||
<option value="">-- 请选择锁具 --</option> |
|||
${lockOptionsHtml} |
|||
</select> |
|||
</div>`,
|
|||
'选择锁具', |
|||
{ |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
dangerouslyUseHTMLString: true, |
|||
beforeClose: (action, _instance, done) => { |
|||
if (action === 'confirm') { |
|||
const selector = document.getElementById('lockSelector') as HTMLSelectElement |
|||
const selectedValue = selector?.value |
|||
|
|||
if (!selectedValue) { |
|||
ElMessage.warning('请选择一个锁具') |
|||
return false |
|||
} |
|||
|
|||
selectedLockId = selectedValue |
|||
} |
|||
done() |
|||
} |
|||
} |
|||
) |
|||
|
|||
// 用户点击确定后执行绑定
|
|||
if (selectedLockId) { |
|||
await performBind(detail, Number(selectedLockId), useUserStore().getUser.id) |
|||
} |
|||
} catch (error) { |
|||
// 用户取消选择,不做任何操作
|
|||
console.log('用户取消选择锁具') |
|||
} |
|||
} |
|||
} catch (error) { |
|||
ElMessage.error('绑定失败,请重试') |
|||
throw error |
|||
} |
|||
} |
|||
// 将 localId 转换为 File(先尝试 getLocalImgData,失败则用 Canvas 回退)
|
|||
async function convertLocalIdToFile(localId: string): Promise<File> { |
|||
// 优先:getLocalImgData(iOS 常见)
|
|||
if ((ww as any).getLocalImgData) { |
|||
try { |
|||
const localDataRes = await (ww as any).getLocalImgData({ localId }) |
|||
const base64 = String((localDataRes && localDataRes.localData) || '') |
|||
const dataUrl = base64.startsWith('data:') ? base64 : `data:image/jpeg;base64,${base64}` |
|||
return download.base64ToFile(dataUrl, 'photo') |
|||
} catch (_) { /* fallback to canvas */ } |
|||
} |
|||
|
|||
// 回退:使用 Canvas 生成 base64 再转 File(Android 常见)
|
|||
return await new Promise<File>((resolve, reject) => { |
|||
const img = new Image() |
|||
img.crossOrigin = 'anonymous' |
|||
img.onload = () => { |
|||
try { |
|||
const canvas = document.createElement('canvas') |
|||
canvas.width = img.width |
|||
canvas.height = img.height |
|||
const ctx = canvas.getContext('2d') |
|||
if (!ctx) throw new Error('canvas context 获取失败') |
|||
ctx.drawImage(img, 0, 0, img.width, img.height) |
|||
const dataUrl = canvas.toDataURL('image/jpeg') |
|||
resolve(download.base64ToFile(dataUrl, 'photo')) |
|||
} catch (err) { |
|||
reject(err) |
|||
} |
|||
} |
|||
img.onerror = (err) => reject(err) |
|||
img.src = localId |
|||
}) |
|||
} |
|||
|
|||
// 创建文件选择器(非企业微信环境使用)
|
|||
async function selectFile(): Promise<File> { |
|||
return new Promise((resolve, reject) => { |
|||
const input = document.createElement('input') |
|||
input.type = 'file' |
|||
input.accept = 'image/*' |
|||
input.style.display = 'none' |
|||
|
|||
input.onchange = (event) => { |
|||
const target = event.target as HTMLInputElement |
|||
const file = target.files?.[0] |
|||
document.body.removeChild(input) |
|||
|
|||
if (file) { |
|||
resolve(file) |
|||
} else { |
|||
reject(new Error('未选择文件')) |
|||
} |
|||
} |
|||
|
|||
input.oncancel = () => { |
|||
document.body.removeChild(input) |
|||
reject(new Error('用户取消选择')) |
|||
} |
|||
|
|||
document.body.appendChild(input) |
|||
input.click() |
|||
}) |
|||
} |
|||
|
|||
export const getPhoto = async () => { |
|||
try { |
|||
if (appStore.isWorkWechat) { |
|||
// 放宽企业微信JSSDK类型限制,兼容不同终端返回与参数
|
|||
const chooseRes = await (ww as any).chooseImage({ |
|||
count: 1, |
|||
sizeType: ['original'], |
|||
sourceType: ['camera'], |
|||
defaultCameraMode: 'normal', |
|||
isSaveToAlbum: false |
|||
} as any) as { |
|||
localIds?: string[] |
|||
localId?: string |
|||
tempFiles?: Array<{ file?: File }> |
|||
} |
|||
|
|||
// 企业微信返回可能是 tempFiles 或 localIds/localId
|
|||
const directFile: File | undefined = (chooseRes as any)?.tempFiles?.[0]?.file |
|||
const localId: string | undefined = chooseRes?.localIds?.[0] || (chooseRes as any)?.localId |
|||
|
|||
const file: File | null = directFile |
|||
? directFile |
|||
: (localId ? await convertLocalIdToFile(localId) : null) |
|||
|
|||
if (!file) throw new Error('无法获取拍照文件') |
|||
|
|||
const uploadRes = await updateFile({ file }) |
|||
return uploadRes && uploadRes.data |
|||
} else { |
|||
// 非企业微信环境:选择文件并上传
|
|||
const file = await selectFile() |
|||
const uploadRes = await updateFile({ file }) |
|||
return uploadRes && uploadRes.data |
|||
} |
|||
} catch (err) { |
|||
ElMessage.error('文件上传失败') |
|||
throw err |
|||
} |
|||
} |
@ -0,0 +1,225 @@ |
|||
<template> |
|||
<div class="mobile-container"> |
|||
<div>位置权限:{{ locationPermission ? '是' : '否' }}</div> |
|||
<div>蓝牙权限:{{ bluetoothPermission ? '是' : '否' }}</div> |
|||
<div>相机权限:{{ cameraPermission ? '是' : '否' }}</div> |
|||
<div class="mobile-header"> |
|||
<el-input v-model="searchText" placeholder="搜索隔离点或任务名称" prefix-icon="Search" clearable class="search-input" /> |
|||
<el-button type="primary" @click="$emit('refresh')" class="refresh-btn">刷新</el-button> |
|||
</div> |
|||
<div class="plan-list"> |
|||
<PlanCard v-for="isolationPlan in filteredPlans" :key="isolationPlan.id" :isolation-plan="isolationPlan" |
|||
:expanded-plan="expandedPlans.includes(isolationPlan.id)" :expanded-points="expandedPoints" |
|||
:isolation-points="isolationPoints" :locks="locks" :users="users" :current-user-id="currentUserId" |
|||
:is-bluetooth-supported="isBluetoothSupported" :plan-id="isolationPlan[0]?.isolationPlanId" |
|||
@toggle-plan="togglePlan" @toggle-point="togglePoint" @bind-lock="$emit('bindLock', $event)" |
|||
@lock-operation="$emit('lockOperation', $event)" @verify-lock="$emit('verifyLock', $event)" |
|||
@unlock-verify="$emit('unlockVerify', $event)" @unlock-operation="$emit('unlockOperation', $event)" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, toRefs } from 'vue' |
|||
import { Refresh, Search } from '@element-plus/icons-vue' |
|||
import PlanCard from './PlanCard.vue' |
|||
import { useUserStore } from '@/store/modules/user' |
|||
|
|||
// Props |
|||
const props = defineProps({ |
|||
isolationPlanList: { |
|||
type: Object, |
|||
required: true |
|||
}, |
|||
isolationPoints: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
locks: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
users: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
currentUserId: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
isBluetoothSupported: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}) |
|||
|
|||
// Emits |
|||
const emit = defineEmits([ |
|||
'refresh', |
|||
'bindLock', |
|||
'lockOperation', |
|||
'unlockOperation', |
|||
'verifyLock', |
|||
'unlockVerify', |
|||
'unlockOperation' |
|||
]) |
|||
|
|||
// Reactive props |
|||
const { isolationPlanList, isolationPoints, locks, users, currentUserId } = toRefs(props) |
|||
|
|||
// 本地状态 |
|||
const searchText = ref('') |
|||
const expandedPlans = ref([]) |
|||
const expandedPoints = ref([]) |
|||
|
|||
|
|||
// 计算属性 |
|||
const filteredPlans = computed(() => { |
|||
let plans = Object.values(isolationPlanList.value) |
|||
plans = plans.filter((plan) => { |
|||
return plan.some((item) => |
|||
[item.operatorId, item.verifierId, item.operatorHelperId, item.verifierHelperId].some( |
|||
(val) => val === currentUserId.value |
|||
) |
|||
) |
|||
}) |
|||
if (!searchText.value) return plans |
|||
return plans.filter((plan) => |
|||
plan.some((item) => |
|||
[item.isolationPointId, item.isolationPlanName].some((val) => |
|||
val?.toString().toLowerCase().includes(searchText.value.toLowerCase()) |
|||
) |
|||
) |
|||
) |
|||
}) |
|||
const togglePlan = (planId) => { |
|||
const index = expandedPlans.value.indexOf(planId) |
|||
if (index > -1) { |
|||
expandedPlans.value.splice(index, 1) |
|||
} else { |
|||
expandedPlans.value.push(planId) |
|||
} |
|||
} |
|||
|
|||
const togglePoint = ({ pointId, planId }) => { |
|||
const compositeKey = `${planId}_${pointId}` |
|||
const index = expandedPoints.value.indexOf(compositeKey) |
|||
if (index > -1) { |
|||
expandedPoints.value.splice(index, 1) |
|||
} else { |
|||
expandedPoints.value.push(compositeKey) |
|||
} |
|||
} |
|||
|
|||
// 暴露方法和数据给父组件 |
|||
defineExpose({ |
|||
searchText, |
|||
expandedPlans, |
|||
expandedPoints, |
|||
filteredPlans, |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.mobile-container { |
|||
padding: 16px; |
|||
background-color: #f5f5f5; |
|||
min-height: 100vh; |
|||
} |
|||
|
|||
.mb-4 { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.mobile-header { |
|||
display: flex; |
|||
gap: 12px; |
|||
margin-bottom: 16px; |
|||
align-items: center; |
|||
} |
|||
|
|||
.search-input { |
|||
flex: 1; |
|||
} |
|||
|
|||
.refresh-btn { |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.status-summary { |
|||
display: flex; |
|||
gap: 12px; |
|||
margin-bottom: 16px; |
|||
padding: 16px; |
|||
background: white; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.summary-item { |
|||
flex: 1; |
|||
text-align: center; |
|||
} |
|||
|
|||
.summary-value { |
|||
font-size: 24px; |
|||
font-weight: bold; |
|||
color: #409eff; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.summary-value.locked { |
|||
color: #67c23a; |
|||
} |
|||
|
|||
.summary-value.unlocked { |
|||
color: #f56c6c; |
|||
} |
|||
|
|||
.summary-label { |
|||
font-size: 14px; |
|||
color: #666; |
|||
} |
|||
|
|||
.plan-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12px; |
|||
} |
|||
|
|||
@media (max-width: 480px) { |
|||
.mobile-container { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.mobile-header { |
|||
flex-direction: column; |
|||
gap: 8px; |
|||
} |
|||
|
|||
.refresh-btn { |
|||
width: 100%; |
|||
} |
|||
|
|||
.status-summary { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.summary-value { |
|||
font-size: 20px; |
|||
} |
|||
} |
|||
|
|||
.mobile-container .el-tag { |
|||
border-radius: 12px; |
|||
} |
|||
|
|||
.mobile-container .el-button { |
|||
border-radius: 6px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.mobile-container .el-input { |
|||
border-radius: 6px; |
|||
} |
|||
</style> |
@ -0,0 +1,377 @@ |
|||
<template> |
|||
<div class="pc-group-lock"> |
|||
<!-- 顶部工具栏 --> |
|||
<div class="toolbar"> |
|||
<el-input v-model="searchText" placeholder="搜索隔离点或计划名称" prefix-icon="Search" clearable class="toolbar__search" /> |
|||
<el-switch v-model="onlyMine" active-text="只看与我相关" inactive-text="全部" class="toolbar__switch" /> |
|||
<el-button type="primary" @click="$emit('refresh')">刷新</el-button> |
|||
</div> |
|||
|
|||
<template v-for="(isolationPlan, idx) in filteredPlans" :key="idx"> |
|||
<div class="table-card"> |
|||
<div class="table-card__header"> |
|||
<div class="title"> |
|||
<span class="name">{{ isolationPlan[0]?.isolationPlanName }}</span> |
|||
<el-tag type="info" size="small">{{ isolationPlan[0]?.isolationPlanId }}</el-tag> |
|||
</div> |
|||
<div class="meta"> |
|||
<span>隔离点: {{ countPoint(isolationPlan) }}</span> |
|||
<span class="divider"></span> |
|||
<span>任务: {{ isolationPlan.length }}</span> |
|||
</div> |
|||
</div> |
|||
<el-table :data="isolationPlan" :stripe="true" :show-overflow-tooltip="true" :header-row-style="headerRowStyle" |
|||
:row-key="(row) => row.id" border :span-method="(r) => arraySpanMethod(r, isolationPlan)"> |
|||
<el-table-column label="编号" align="center" prop="isolationPlanItemId" width="80" /> |
|||
|
|||
<el-table-column label="隔离点" align="center" prop="isolationPointId" min-width="180"> |
|||
<template #default="scope"> |
|||
{{ getPointName(scope.row.isolationPointId) }} |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="电子锁" align="center" prop="lockId" min-width="160"> |
|||
<template #default="scope"> |
|||
{{ getLockName(scope.row.lockId) }} |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="子项状态" align="center" prop="lockStatus" width="140"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS" :value="scope.row.lockStatus" /> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="集中挂牌人" align="center" prop="operatorId" min-width="160"> |
|||
<template #default="scope"> |
|||
{{users.find((user) => user.id === scope.row.operatorId)?.nickname}} |
|||
<small class="ml-4">{{ getLifeLockStatus(scope.row.lifeLock, 1) }}</small> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="协助人" align="center" prop="operatorHelperId" min-width="160"> |
|||
<template #default="scope"> |
|||
<div v-if="scope.row.operatorHelperId"> |
|||
{{users.find((user) => user.id === scope.row.operatorHelperId)?.nickname}} |
|||
<small class="ml-4">{{ getLifeLockStatus(scope.row.lifeLock, 2) }}</small> |
|||
</div> |
|||
<span v-else class="text-muted">-</span> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="验证人" align="center" prop="verifierId" min-width="160"> |
|||
<template #default="scope"> |
|||
{{users.find((user) => user.id === scope.row.verifierId)?.nickname}} |
|||
<small class="ml-4">{{ getLifeLockStatus(scope.row.lifeLock, 3) }}</small> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="验证协助" align="center" prop="verifierHelperId" min-width="160"> |
|||
<template #default="scope"> |
|||
<div v-if="scope.row.verifierHelperId"> |
|||
{{users.find((user) => user.id === scope.row.verifierHelperId)?.nickname}} |
|||
<small class="ml-4">{{ getLifeLockStatus(scope.row.lifeLock, 4) }}</small> |
|||
</div> |
|||
<span v-else class="text-muted">-</span> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="受影响人" align="center" min-width="200"> |
|||
<template #default="scope"> |
|||
<div v-if="getAffectedUsers(scope.row).length"> |
|||
<el-space wrap> |
|||
<el-tag v-for="aff in getAffectedUsers(scope.row)" :key="aff.id" size="small" effect="plain"> |
|||
{{users.find((u) => u.id === aff.userId)?.nickname}} |
|||
<span class="ml-4"> |
|||
<DictTag :type="DICT_TYPE.LOCK_LIFE_LOCK_STATUS" :value="aff.lockStatus" /> |
|||
</span> |
|||
</el-tag> |
|||
</el-space> |
|||
</div> |
|||
<span v-else class="text-muted">-</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="生命锁邀请码" align="center" min-width="100"> |
|||
<template #default="scope"> |
|||
<div v-if="scope.row.lockStatus == 3 && scope.row.lockId" class="qrcode-container"> |
|||
<Qrcode :text="getInviteCode(scope.row)" width="100" /> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="操作" align="center" fixed="right" width="360"> |
|||
<template #default="scope"> |
|||
<el-space> |
|||
<el-button v-show="scope.row.lockStatus == 0 && !scope.row.lockId && isOperator(scope.row)" |
|||
type="primary" size="small" @click="$emit('bindLock', scope.row)"> |
|||
绑定 |
|||
</el-button> |
|||
<el-button v-show="scope.row.lockStatus == 1 && scope.row.lockId && isOperator(scope.row)" |
|||
type="primary" size="small" @click="$emit('lockOperation', scope.row)"> |
|||
上锁 |
|||
</el-button> |
|||
<el-button v-show="scope.row.lockStatus == 2 && scope.row.lockId && isVerifier(scope.row)" |
|||
type="primary" size="small" @click="$emit('verifyLock', scope.row)"> |
|||
验证 |
|||
</el-button> |
|||
<el-button v-show="scope.row.lockStatus == 3 && scope.row.lockId && isVerifier(scope.row)" |
|||
type="warning" size="small" :disabled="!checkUnlockVerify(scope.row)" |
|||
@click="$emit('unlockVerify', scope.row)"> |
|||
解锁验证 |
|||
</el-button> |
|||
<el-button v-show="scope.row.lockStatus == 4 && scope.row.lockId && isOperator(scope.row)" |
|||
type="success" size="small" @click="$emit('unlockOperation', scope.row)"> |
|||
解锁 |
|||
</el-button> |
|||
</el-space> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, toRefs } from 'vue' |
|||
import { DICT_TYPE, getDictLabel } from '@/utils/dict' |
|||
import { Qrcode } from '@/components/Qrcode' |
|||
const BASE_URL = import.meta.env.VITE_BASE_URL |
|||
// Emits |
|||
defineEmits([ |
|||
'refresh', |
|||
'bindLock', |
|||
'lockOperation', |
|||
'verifyLock', |
|||
'unlockVerify', |
|||
'unlockOperation' |
|||
]) |
|||
|
|||
// Props |
|||
const props = defineProps({ |
|||
isolationPlanList: { |
|||
type: Object, |
|||
required: true |
|||
}, |
|||
isolationPoints: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
locks: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
users: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
currentUserId: { |
|||
type: Number, |
|||
default: 0 |
|||
} |
|||
}) |
|||
|
|||
// Reactive props |
|||
const { isolationPlanList, isolationPoints, locks, users, currentUserId } = toRefs(props) |
|||
|
|||
// 本地筛选状态 |
|||
const searchText = ref('') |
|||
const onlyMine = ref(true) |
|||
|
|||
// 过滤后的计划分组(数组) |
|||
const filteredPlans = computed(() => { |
|||
let plans = Object.values(isolationPlanList.value) |
|||
if (onlyMine.value) { |
|||
plans = plans.filter((plan) => |
|||
plan.some((item) => |
|||
[item.operatorId, item.verifierId, item.operatorHelperId, item.verifierHelperId].some( |
|||
(id) => id === currentUserId.value |
|||
) |
|||
) |
|||
) |
|||
} |
|||
if (!searchText.value) return plans |
|||
const keyword = searchText.value.toLowerCase() |
|||
return plans.filter((plan) => |
|||
plan.some((item) => { |
|||
const pointName = getPointName(item.isolationPointId) |
|||
const planName = item.isolationPlanName || '' |
|||
return pointName.toLowerCase().includes(keyword) || planName.toLowerCase().includes(keyword) |
|||
}) |
|||
) |
|||
}) |
|||
|
|||
// 统计隔离点数量 |
|||
const countPoint = (plan) => { |
|||
const set = new Set(plan.map((i) => i.isolationPointId)) |
|||
return set.size |
|||
} |
|||
|
|||
// 表格样式方法 |
|||
const headerRowStyle = ({ rowIndex }) => { |
|||
if (rowIndex === 0) { |
|||
return { backgroundColor: '#f5f7fa' } |
|||
} |
|||
return {} |
|||
} |
|||
|
|||
// 表格合并方法 |
|||
const arraySpanMethod = ({ rowIndex, column }, isolationPlan) => { |
|||
const getSpanRow = (plan) => { |
|||
const list = [] |
|||
plan.forEach((item, index) => { |
|||
if (list.length === 0) { |
|||
list.push({ rowindex: index, spanRowNum: 1, isolationPlanItemId: item.isolationPlanItemId }) |
|||
} else { |
|||
const last = list[list.length - 1] |
|||
if (last.isolationPlanItemId === item.isolationPlanItemId) { |
|||
last.spanRowNum++ |
|||
} else { |
|||
list.push({ |
|||
rowindex: index, |
|||
spanRowNum: 1, |
|||
isolationPlanItemId: item.isolationPlanItemId |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
return list |
|||
} |
|||
|
|||
const sameIsolationPlanItemIdColumns = ['isolationPlanItemId'] |
|||
if (sameIsolationPlanItemIdColumns.includes(column.property)) { |
|||
const spanRowList = getSpanRow(isolationPlan) |
|||
const row = spanRowList.find((item) => item.rowindex === rowIndex) |
|||
if (row) return { rowspan: row.spanRowNum, colspan: 1 } |
|||
return { rowspan: 0, colspan: 0 } |
|||
} |
|||
return [1, 1] |
|||
} |
|||
|
|||
// 角色/名称/状态等工具函数 |
|||
const isOperator = (item) => |
|||
item.operatorId === currentUserId.value || item.operatorHelperId === currentUserId.value |
|||
const isVerifier = (item) => |
|||
item.verifierId === currentUserId.value || item.verifierHelperId === currentUserId.value |
|||
const getPointName = (pointId) => |
|||
isolationPoints.value.find((p) => p.id === pointId)?.ipName || `隔离点 ${pointId}` |
|||
const getLockName = (lockId) => |
|||
lockId ? locks.value.find((l) => l.id === lockId)?.lockName || `锁 ${lockId}` : '待绑定' |
|||
const getAffectedUsers = (item) => item.lifeLock?.filter((l) => l.lockType == 5) || [] |
|||
const getLifeLockStatus = (lifeLock, type) => { |
|||
if (Array.isArray(lifeLock)) { |
|||
const lock = lifeLock.find((i) => i.lockType == type) |
|||
if (lock) return getDictLabel(DICT_TYPE.LOCK_LIFE_LOCK_STATUS, lock.lockStatus) |
|||
} else if (lifeLock) { |
|||
return getDictLabel(DICT_TYPE.LOCK_LIFE_LOCK_STATUS, lifeLock.lockStatus) |
|||
} |
|||
return '未挂锁' |
|||
} |
|||
const checkUnlockVerify = (item) => getAffectedUsers(item).every((lock) => lock.lockStatus == 0) |
|||
const getInviteCode = (item) => { |
|||
return BASE_URL + '/isolationplan/isolationplan/lifelock?itemid=' + item.id |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.pc-group-lock { |
|||
padding: 12px; |
|||
} |
|||
|
|||
/* 工具栏 */ |
|||
.toolbar { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12px; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.toolbar__search { |
|||
flex: 1; |
|||
} |
|||
|
|||
.toolbar__switch { |
|||
margin-left: auto; |
|||
} |
|||
|
|||
/* 卡片化表格容器 */ |
|||
.table-card { |
|||
background: #fff; |
|||
border: 1px solid #ebeef5; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
overflow: hidden; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); |
|||
} |
|||
|
|||
.table-card__header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 12px 16px; |
|||
background: #f8f9fa; |
|||
border-bottom: 1px solid #ebeef5; |
|||
} |
|||
|
|||
.table-card__header .title { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 10px; |
|||
} |
|||
|
|||
.table-card__header .name { |
|||
font-weight: 600; |
|||
color: #303133; |
|||
} |
|||
|
|||
.table-card__header .meta { |
|||
color: #909399; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 10px; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.table-card__header .divider { |
|||
display: inline-block; |
|||
width: 1px; |
|||
height: 12px; |
|||
background: #e4e7ed; |
|||
} |
|||
|
|||
/* PC端表格样式可以在这里自定义 */ |
|||
:deep(.el-table) { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
:deep(.el-table th) { |
|||
background-color: #f5f7fa; |
|||
color: #606266; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
:deep(.el-table td) { |
|||
padding: 12px 0; |
|||
} |
|||
|
|||
:deep(.el-table--border .el-table__cell) { |
|||
border-right: 1px solid #ebeef5; |
|||
} |
|||
|
|||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) { |
|||
background-color: #fafafa; |
|||
} |
|||
|
|||
.ml-4 { |
|||
margin-left: 4px; |
|||
} |
|||
|
|||
.text-muted { |
|||
color: #909399; |
|||
} |
|||
.qrcode-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
</style> |
@ -0,0 +1,670 @@ |
|||
<template> |
|||
<div class="plan-card"> |
|||
<!-- 计划头部 --> |
|||
<div class="plan-header" @click="togglePlan()"> |
|||
<div class="plan-title"> |
|||
<span class="plan-name">{{ isolationPlan[0]?.isolationPlanName }} </span> |
|||
<span class="plan-role"> ({{ currentUserRole }}) </span> |
|||
</div> |
|||
<el-icon class="expand-icon" :class="{ expanded: isExpanded }"> |
|||
<ArrowDown /> |
|||
</el-icon> |
|||
</div> |
|||
|
|||
<el-collapse-transition> |
|||
<div v-show="isExpanded" class="plan-content"> |
|||
<!-- 人员信息 --> |
|||
<div class="plan-personnel-info"> |
|||
<div class="section-title"> |
|||
<el-icon> |
|||
<User /> |
|||
</el-icon>责任人员 |
|||
</div> |
|||
<div class="personnel-list"> |
|||
<div v-for="role in personnelRoles" :key="role.key" class="personnel-item" |
|||
v-show="getUserName(isolationPlan[0], role.key) !== '未指定'"> |
|||
<div class="personnel-info"> |
|||
<span class="personnel-role">{{ role.label }}:</span> |
|||
<span class="personnel-name">{{ getUserName(isolationPlan[0], role.key) }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 隔离点详情 --> |
|||
<div class="isolation-points-section"> |
|||
<div class="section-title"> |
|||
<el-icon> |
|||
<Location /> |
|||
</el-icon>隔离点详情 |
|||
</div> |
|||
<template v-for="(pointGroup, pointId) in groupByIsolationPoint(isolationPlan)" :key="pointId"> |
|||
<div class="point-card"> |
|||
<div class="point-header" @click="togglePoint(pointId)"> |
|||
<div class="point-info"> |
|||
<div class="point-name">{{ getPointName(pointId) }}</div> |
|||
<DictTag :type="DICT_TYPE.LOCK_ISOLATION_POINT_STATUS" |
|||
:value="elLockStore.isolationPoints.find((p) => p.id == pointId)?.status" /> |
|||
</div> |
|||
<el-icon class="expand-icon" :class="{ expanded: expandedPoints.includes(`${planId}_${pointId}`) }"> |
|||
<ArrowDown /> |
|||
</el-icon> |
|||
</div> |
|||
<el-collapse-transition> |
|||
<div v-show="expandedPoints.includes(`${planId}_${pointId}`)" class="point-content"> |
|||
<div class="locks-section"> |
|||
<div v-for="item in pointGroup" :key="item.id" class="lock-item"> |
|||
<div class="lock-header"> |
|||
<div class="lock-info"> |
|||
<div class="lock-name"> |
|||
<el-icon> |
|||
<Lock /> |
|||
</el-icon>{{ getLockName(item.lockId) }} |
|||
</div> |
|||
</div> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS" :value="item.lockStatus" /> |
|||
</div> |
|||
|
|||
<!-- 受影响人员 --> |
|||
<div v-if="getAffectedUsers(item).length" class="affected-section"> |
|||
<div class="affected-title"> |
|||
<el-icon> |
|||
<UserFilled /> |
|||
</el-icon>受影响人员 |
|||
</div> |
|||
<div class="affected-list"> |
|||
<div v-for="affected in getAffectedUsers(item)" :key="affected.id" class="affected-item"> |
|||
<span class="affected-name">{{ |
|||
users.find((u) => u.id === affected.userId)?.nickname |
|||
}}</span> |
|||
<DictTag :type="DICT_TYPE.LOCK_LIFE_LOCK_STATUS" :value="affected.lockStatus" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 锁操作 --> |
|||
<div class="lock-actions"> |
|||
<el-button v-show="item.lockStatus == 0 && !item.lockId && isOperator(item)" type="primary" |
|||
size="small" @click="$emit('bindLock', item)"> |
|||
<el-icon> |
|||
<Lock /> |
|||
</el-icon>绑定 |
|||
</el-button> |
|||
<el-button v-show="item.lockStatus == 1 && item.lockId && isOperator(item)" type="primary" |
|||
size="small" @click="$emit('lockOperation', item)"> |
|||
<el-icon> |
|||
<Lock /> |
|||
</el-icon>上锁 |
|||
</el-button> |
|||
<el-button v-show="item.lockStatus == 2 && item.lockId && isVerifier(item)" type="primary" |
|||
size="small" @click="$emit('verifyLock', item)"> |
|||
<el-icon> |
|||
<Lock /> |
|||
</el-icon>验证 |
|||
</el-button> |
|||
<div v-show="item.lockStatus == 3 && item.lockId"> |
|||
<div class="qrcode-container"> |
|||
<Qrcode :text="getInviteCode(item)" /> |
|||
</div> |
|||
</div> |
|||
<el-button v-show="item.lockStatus == 3 && item.lockId && isVerifier(item)" type="primary" |
|||
size="small" @click="$emit('unlockVerify', item)" :disabled="!checkUnlockVerify(item)"> |
|||
<el-icon> |
|||
<Lock /> |
|||
</el-icon>解锁验证 |
|||
</el-button> |
|||
<el-button v-show="item.lockStatus == 4 && item.lockId && isOperator(item)" type="success" |
|||
size="small" @click="$emit('unlockOperation', item)"> |
|||
<el-icon> |
|||
<Unlock /> |
|||
</el-icon>解锁 |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</el-collapse-transition> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</div> |
|||
</el-collapse-transition> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed, toRefs, watch } from 'vue' |
|||
import { ArrowDown, User, Location, UserFilled, Lock, Unlock } from '@element-plus/icons-vue' |
|||
import { DICT_TYPE, getDictLabel } from '@/utils/dict' |
|||
import { useUserStore } from '@/store/modules/user' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { Qrcode } from '@/components/Qrcode' |
|||
// 0 未绑定锁 1 未上锁 2 已上锁未验证 3 已上锁已验证 4 解锁已验证 5 已解锁 |
|||
// Props |
|||
const props = defineProps({ |
|||
isolationPlan: { |
|||
type: Array, |
|||
required: true |
|||
}, |
|||
expandedPlan: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
expandedPoints: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
isolationPoints: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
locks: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
users: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
currentUserId: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
isBluetoothSupported: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
planId: { |
|||
type: Number, |
|||
default: 0 |
|||
} |
|||
}) |
|||
const BASE_URL = import.meta.env.VITE_BASE_URL |
|||
// Emits |
|||
const emit = defineEmits([ |
|||
'togglePlan', |
|||
'togglePoint', |
|||
'bindLock', // 绑定锁 |
|||
'lockOperation', // 上锁 |
|||
'verifyLock', // 验证 |
|||
'unlockVerify', // 解锁验证 |
|||
'unlockOperation' // 解锁 |
|||
]) |
|||
|
|||
// Reactive props |
|||
const { |
|||
isolationPlan, |
|||
expandedPlan, |
|||
expandedPoints, |
|||
isolationPoints, |
|||
locks, |
|||
users, |
|||
planId, |
|||
currentUserId |
|||
} = toRefs(props) |
|||
const elLockStore = useElLockStore() |
|||
|
|||
// 本地状态 |
|||
const isExpanded = ref(expandedPlan.value) |
|||
|
|||
// 人员角色配置 |
|||
const personnelRoles = [ |
|||
{ key: 'operatorId', label: '集中挂牌人', type: '1' }, |
|||
{ key: 'operatorHelperId', label: '挂牌协助人', type: '2' }, |
|||
{ key: 'verifierId', label: '验证人', type: '3' }, |
|||
{ key: 'verifierHelperId', label: '验证协助人', type: '4' } |
|||
] |
|||
|
|||
// 当前用户角色 |
|||
const currentUserRole = computed(() => { |
|||
let role = '' |
|||
personnelRoles.forEach((item) => { |
|||
if (isolationPlan.value.find((p) => p[item.key] === currentUserId.value)) { |
|||
role += item.label |
|||
} |
|||
}) |
|||
return role |
|||
}) |
|||
|
|||
// 方法 |
|||
const togglePlan = () => { |
|||
isExpanded.value = !isExpanded.value |
|||
emit('togglePlan', planId.value) |
|||
} |
|||
|
|||
const togglePoint = (pointId) => { |
|||
emit('togglePoint', { pointId, planId: planId.value }) |
|||
} |
|||
|
|||
// 数据分组和获取 |
|||
const groupByIsolationPoint = (isolationPlan) => { |
|||
let list = isolationPlan.reduce( |
|||
(groups, item) => { |
|||
const pointId = item.isolationPointId |
|||
if (!groups[pointId]) groups[pointId] = [] |
|||
groups[pointId].push(item) |
|||
return groups |
|||
}, |
|||
|
|||
{} |
|||
) |
|||
return list |
|||
} |
|||
|
|||
const isOperator = (item) => { |
|||
return item.operatorId === currentUserId.value || item.operatorHelperId === currentUserId.value |
|||
} |
|||
|
|||
const isVerifier = (item) => { |
|||
return item.verifierId === currentUserId.value || item.verifierHelperId === currentUserId.value |
|||
} |
|||
|
|||
const getUserName = (item, key) => |
|||
users.value.find((user) => user.id === item?.[key])?.nickname || '未指定' |
|||
|
|||
const getPointName = (pointId) => |
|||
elLockStore.isolationPoints.find((p) => p.id == pointId)?.ipName |
|||
|
|||
const getLockName = (lockId) => |
|||
lockId ? locks.value.find((l) => l.id === lockId)?.lockName || `锁 ${lockId}` : '待绑定' |
|||
|
|||
const getAffectedUsers = (item) => item.lifeLock?.filter((l) => l.lockType == '5') || [] |
|||
|
|||
const getLockStatus = (lifeLock, type) => { |
|||
if (Array.isArray(lifeLock)) { |
|||
let lock = lifeLock.find((item) => item.lockType == type) |
|||
if (lock) { |
|||
return getDictLabel(DICT_TYPE.LOCK_LIFE_LOCK_STATUS, lock.lockStatus) |
|||
} |
|||
} else { |
|||
return getDictLabel(DICT_TYPE.LOCK_LIFE_LOCK_STATUS, lifeLock.lockStatus) |
|||
} |
|||
return '未挂锁' |
|||
} |
|||
|
|||
// 状态计算辅助函数 |
|||
const calculateStatus = (items, checkFn) => { |
|||
const results = items.map(checkFn) |
|||
const lockedCount = results.filter(Boolean).length |
|||
const totalCount = results.length |
|||
|
|||
return { |
|||
type: lockedCount === 0 ? 'danger' : lockedCount === totalCount ? 'success' : 'warning', |
|||
text: |
|||
lockedCount === 0 |
|||
? '未锁定' |
|||
: lockedCount === totalCount |
|||
? '全部锁定' |
|||
: `${lockedCount}/${totalCount} 锁定` |
|||
} |
|||
} |
|||
const getInviteCode = (item) => { |
|||
return BASE_URL + '/isolationplan/isolationplan/lifelock?itemid=' + item.id |
|||
} |
|||
const getLockStatusType = (status) => |
|||
({ |
|||
1: 'success', |
|||
2: 'warning' |
|||
})[status] || 'danger' |
|||
|
|||
const getLockStatusTagType = (lifeLock, type) => { |
|||
// 锁状态 |
|||
const status = getLockStatus(lifeLock, type) |
|||
return status === '未挂锁' ? 'danger' : status.includes('已锁') ? 'success' : 'warning' |
|||
} |
|||
|
|||
const checkUnlockVerify = (item) => { |
|||
let affectedUsers = getAffectedUsers(item) |
|||
return affectedUsers.every((lock) => lock.lockStatus == 0) |
|||
} |
|||
|
|||
// 监听prop变化 |
|||
watch( |
|||
() => props.expandedPlan, |
|||
(newVal) => { |
|||
isExpanded.value = newVal |
|||
} |
|||
) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.plan-card { |
|||
background: white; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
overflow: hidden; |
|||
transition: box-shadow 0.2s ease; |
|||
} |
|||
|
|||
.plan-card:hover { |
|||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
|||
} |
|||
|
|||
.plan-header { |
|||
padding: 16px; |
|||
background: #f8f9fa; |
|||
border-bottom: 1px solid #ebeef5; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.plan-title { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
gap: 12px; |
|||
flex: 1; |
|||
} |
|||
|
|||
.plan-name { |
|||
font-weight: 600; |
|||
font-size: 16px; |
|||
color: #303133; |
|||
} |
|||
|
|||
.plan-role { |
|||
font-size: 12px; |
|||
color: #909399; |
|||
} |
|||
|
|||
.expand-icon { |
|||
transition: transform 0.3s ease; |
|||
color: #909399; |
|||
} |
|||
|
|||
.expand-icon.expanded { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
.plan-content { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.plan-personnel-info { |
|||
background: white; |
|||
border-radius: 6px; |
|||
padding: 8px 12px; |
|||
margin-bottom: 12px; |
|||
border: 1px solid #e4e7ed; |
|||
} |
|||
|
|||
.isolation-points-section { |
|||
background: white; |
|||
border-radius: 8px; |
|||
padding: 16px; |
|||
margin-bottom: 16px; |
|||
border: 1px solid #e4e7ed; |
|||
} |
|||
|
|||
.personnel-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.personnel-item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 6px 8px; |
|||
background: #f8f9fa; |
|||
border-radius: 4px; |
|||
border: 1px solid #ebeef5; |
|||
} |
|||
|
|||
.personnel-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
flex: 1; |
|||
} |
|||
|
|||
.personnel-role { |
|||
font-size: 12px; |
|||
color: #909399; |
|||
font-weight: 500; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.personnel-name { |
|||
font-size: 13px; |
|||
color: #303133; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.section-title { |
|||
font-weight: 600; |
|||
color: #303133; |
|||
margin-bottom: 12px; |
|||
padding-bottom: 6px; |
|||
border-bottom: 1px solid #ebeef5; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.plan-personnel-info .section-title { |
|||
margin-bottom: 8px; |
|||
padding-bottom: 4px; |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.section-title .el-icon { |
|||
color: #409eff; |
|||
} |
|||
|
|||
.point-card { |
|||
background: #fafbfc; |
|||
border-radius: 6px; |
|||
margin-bottom: 12px; |
|||
border: 1px solid #e4e7ed; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.point-card:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.point-header { |
|||
padding: 12px 16px; |
|||
background: #f5f7fa; |
|||
cursor: pointer; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.point-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12px; |
|||
flex: 1; |
|||
} |
|||
|
|||
.point-name { |
|||
font-weight: 600; |
|||
font-size: 15px; |
|||
color: #303133; |
|||
} |
|||
|
|||
.point-content { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.locks-section { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.lock-item { |
|||
background: white; |
|||
border-radius: 6px; |
|||
padding: 12px; |
|||
border: 1px solid #ebeef5; |
|||
} |
|||
|
|||
.lock-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.lock-info { |
|||
flex: 1; |
|||
} |
|||
|
|||
.lock-name { |
|||
font-weight: 600; |
|||
color: #303133; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.lock-name .el-icon { |
|||
color: #909399; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.lock-id { |
|||
font-size: 12px; |
|||
color: #909399; |
|||
} |
|||
|
|||
.affected-section { |
|||
margin: 12px 0; |
|||
padding: 12px; |
|||
background: #f8f9fa; |
|||
border-radius: 6px; |
|||
border: 1px solid #ebeef5; |
|||
} |
|||
|
|||
.affected-title { |
|||
font-size: 14px; |
|||
color: #606266; |
|||
margin-bottom: 8px; |
|||
font-weight: 500; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.affected-title .el-icon { |
|||
color: #f56c6c; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.affected-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.affected-item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 6px 8px; |
|||
background: white; |
|||
border-radius: 4px; |
|||
border: 1px solid #ebeef5; |
|||
} |
|||
|
|||
.affected-name { |
|||
color: #303133; |
|||
font-weight: 500; |
|||
flex: 1; |
|||
} |
|||
|
|||
.lock-actions { |
|||
display: flex; |
|||
gap: 8px; |
|||
justify-content: center; |
|||
padding-top: 12px; |
|||
border-top: 1px solid #ebeef5; |
|||
} |
|||
|
|||
.lock-actions .el-button { |
|||
flex: 1; |
|||
max-width: 100px; |
|||
} |
|||
|
|||
@media (max-width: 480px) { |
|||
.plan-header { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.plan-name { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.plan-personnel-info { |
|||
padding: 6px 8px; |
|||
} |
|||
|
|||
.isolation-points-section { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.personnel-list { |
|||
gap: 4px; |
|||
} |
|||
|
|||
.personnel-item { |
|||
padding: 4px 6px; |
|||
} |
|||
|
|||
.personnel-role { |
|||
font-size: 11px; |
|||
} |
|||
|
|||
.personnel-name { |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.plan-personnel-info .section-title { |
|||
font-size: 12px; |
|||
margin-bottom: 6px; |
|||
} |
|||
|
|||
.point-header { |
|||
padding: 10px 12px; |
|||
} |
|||
|
|||
.point-content { |
|||
padding: 8px; |
|||
} |
|||
|
|||
.lock-item { |
|||
padding: 10px; |
|||
} |
|||
|
|||
.lock-actions { |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.lock-actions .el-button { |
|||
max-width: none; |
|||
} |
|||
} |
|||
|
|||
.el-button+.el-button { |
|||
margin-left: 0; |
|||
} |
|||
|
|||
.qrcode-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
width: 100%; |
|||
height: 100%; |
|||
border: 1px solid #ebeef5; |
|||
border-radius: 6px; |
|||
} |
|||
</style> |
@ -0,0 +1,283 @@ |
|||
<template> |
|||
<div> |
|||
<!-- 移动端显示 --> |
|||
<template v-if="appStore.mobile"> |
|||
<!-- 蓝牙操作面板 --> |
|||
<!-- <BluetoothPanel @lock-response="handleLockResponse" /> --> |
|||
|
|||
<!-- 移动端主内容 --> |
|||
<MobileGroupLock v-loading="loading" :isolation-plan-list="isolationPlanList" |
|||
:isolation-points="elLockStore.isolationPoints" :locks="elLockStore.locks" :users="elLockStore.users" |
|||
:current-user-id="currentUserId" :is-bluetooth-supported="bluetooth.isSupport" @refresh="refreshData" |
|||
@bind-lock="handleBindLock" @lock-operation="handleLockOperation" @unlock-operation="handleUnlockOperation" |
|||
@verify-lock="handleVerifyLock" @unlock-verify="handleUnlockVerify" /> |
|||
</template> |
|||
|
|||
<!-- PC端显示 --> |
|||
<template v-else> |
|||
<PCGroupLock :isolation-plan-list="isolationPlanList" :isolation-points="elLockStore.isolationPoints" |
|||
:locks="elLockStore.locks" :users="elLockStore.users" :current-user-id="currentUserId" @refresh="refreshData" |
|||
@bind-lock="handleBindLock" @lock-operation="handleLockOperation" @verify-lock="handleVerifyLock" |
|||
@unlock-verify="handleUnlockVerify" @unlock-operation="handleUnlockOperation" /> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
<script setup> |
|||
import { ref, onMounted, onActivated } from 'vue' |
|||
import ww from '@/utils/ww' |
|||
import { useAppStore } from '@/store/modules/app' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { ElMessage, ElMessageBox } from 'element-plus' |
|||
import { useUserStore } from '@/store/modules/user' |
|||
import { PlanLifeLockApi } from '@/api/isolation/planlifelock' |
|||
import { LockApi } from '@/api/electron/lock' |
|||
import { LockWorkRecordApi } from '@/api/electron/lockworkcord' |
|||
import { PointApi } from '@/api/isolation/point' |
|||
import { PlanItemDetailApi } from '@/api/isolation/planitemdetail' |
|||
import { PlanItemApi } from '@/api/isolation/planitem' |
|||
import { PlanApi } from '@/api/isolation/plan' |
|||
import { updateFile } from '@/api/infra/file' |
|||
import dayjs from 'dayjs' |
|||
import download from '@/utils/download' |
|||
import { getCurrentLocation, bindLock, getPhoto } from '@/utils/lock' |
|||
import { lockAction, verifyLockAction, verifyUnlockAction, unLockAction } from '@/api/lock' |
|||
// 导入子组件 |
|||
import BluetoothPanel from './components/BluetoothPanel.vue' |
|||
import MobileGroupLock from './components/MobileGroupLock.vue' |
|||
import PCGroupLock from './components/PCGroupLock.vue' |
|||
|
|||
const locationPermission = ref(false) |
|||
const bluetoothPermission = ref(false) |
|||
const cameraPermission = ref(false) |
|||
|
|||
onMounted(async () => { |
|||
let location = await getCurrentLocation() |
|||
locationPermission.value = location.latitude != 0 && location.longitude != 0 |
|||
if (appStore.mobile && appStore.isWorkWechat) { |
|||
bluetoothPermission.value = await ww.bluetooth.checkSupport() |
|||
} |
|||
}) |
|||
defineOptions({ name: 'Grouplock' }) |
|||
|
|||
// Store |
|||
const appStore = useAppStore() |
|||
const elLockStore = useElLockStore() |
|||
const userStore = useUserStore() |
|||
|
|||
// 数据状态 |
|||
const isolationPlanList = ref({}) |
|||
const bluetooth = ww.bluetooth |
|||
// 当前用户id |
|||
const currentUserId = computed(() => { |
|||
return useUserStore().getUser.id |
|||
}) |
|||
let loading = ref(false) |
|||
// 组件挂载 |
|||
onMounted(() => { |
|||
refreshData() |
|||
}) |
|||
|
|||
onActivated(() => { |
|||
refreshData() |
|||
}) |
|||
|
|||
|
|||
// 数据处理方法 |
|||
const getItemDetailList = (isolationPlan) => { |
|||
let list = [] |
|||
isolationPlan.planItem.forEach((item) => { |
|||
item.planItemDetail.forEach((detail) => { |
|||
list.push({ |
|||
isolationPlanId: isolationPlan.id, |
|||
isolationPlanName: isolationPlan.ipName, |
|||
...item, |
|||
...detail, |
|||
lifeLock: detail.itemLifeLock |
|||
}) |
|||
}) |
|||
}) |
|||
return list |
|||
} |
|||
|
|||
const refreshData = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
isolationPlanList.value = elLockStore.plan2DetailTree.reduce((acc, item) => { |
|||
acc[item.id] = getItemDetailList(item) |
|||
return acc |
|||
}, {}) |
|||
} catch (error) { |
|||
ElMessage.error('数据刷新失败') |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
const handleLockOperation = async (item) => { |
|||
// 检查锁具状态 |
|||
let lock = await LockApi.getLock(item.lockId) |
|||
if (lock.lockStatus != 7) { |
|||
ElMessage.error('锁具状态异常 无法锁定') |
|||
refreshData() |
|||
return |
|||
} |
|||
// 获取地理位置 获取上锁照片 |
|||
let location = await getCurrentLocation() |
|||
let photo = await getPhoto() |
|||
LockWorkRecordApi.createLockWorkRecord({ |
|||
operatorId: currentUserId.value, |
|||
lockId: item.lockId, |
|||
isolationPlanItemDetailId: item.id, |
|||
recordType: 3, //上锁 |
|||
afterPhotoPath: photo, |
|||
gpsCoordinates: `${location.latitude},${location.longitude}` |
|||
}).then((res) => { |
|||
// 修改锁具状态 修改任务子项详情状态 修改隔离点状态 |
|||
let promiseList = [ |
|||
lockAction({ planItemDetailId: item.id, operateRecordId: res }) |
|||
] |
|||
Promise.all(promiseList).then((res) => { |
|||
ElMessage.success('上锁成功') |
|||
refreshData() |
|||
}).catch((err) => { |
|||
ElMessage.error('上锁失败') |
|||
refreshData() |
|||
}) |
|||
}) |
|||
} |
|||
const handleUnlockOperation = async (detail) => { |
|||
// 检查锁具状态 |
|||
let lock = await LockApi.getLock(detail.lockId) |
|||
if (lock.lockStatus != 3) { |
|||
ElMessage.error('锁具状态异常 无法解锁') |
|||
refreshData() |
|||
return |
|||
} |
|||
let location = await getCurrentLocation() |
|||
let photo = await getPhoto() |
|||
const lifelock = elLockStore.planLifeLocks.find( |
|||
(item) => |
|||
item.isolationPlanItemDetailId == detail.id && |
|||
item.userId == currentUserId.value && |
|||
item.lockStatus == 1 && |
|||
item.lockType == 1 |
|||
) |
|||
if (!lifelock) { |
|||
ElMessage.error('未找到生命锁') |
|||
refreshData() |
|||
return |
|||
} |
|||
LockWorkRecordApi.createLockWorkRecord({ |
|||
operatorId: currentUserId.value, |
|||
lockId: detail.lockId, |
|||
isolationPlanItemDetailId: detail.id, |
|||
recordType: 14, //解锁 |
|||
afterPhotoPath: photo, |
|||
gpsCoordinates: `${location.latitude},${location.longitude}` |
|||
}).then((res) => { |
|||
// 修改锁具状态 修改任务子项详情状态 修改隔离点状态 修改生命锁状态 |
|||
let promiseList = [ |
|||
unLockAction({ planItemDetailId: detail.id, planId: detail.planId, lifelockId: lifelock.id }) |
|||
] |
|||
Promise.all(promiseList).then((res) => { |
|||
ElMessage.success('解锁成功') |
|||
refreshData() |
|||
}).catch((err) => { |
|||
ElMessage.error('解锁失败') |
|||
refreshData() |
|||
}) |
|||
}) |
|||
} |
|||
const handleBindLock = (item) => { |
|||
bindLock(item).then(() => { |
|||
ElMessage.success('绑定成功') |
|||
refreshData() |
|||
}).catch((err) => { |
|||
ElMessage.error('绑定失败') |
|||
refreshData() |
|||
}) |
|||
} |
|||
|
|||
const handleVerifyLock = async (item) => { |
|||
// 获取地理位置 获取上锁照片 |
|||
try { |
|||
let location = await getCurrentLocation() |
|||
let photo = await getPhoto() |
|||
} catch (err) { |
|||
ElMessage.error('获取照片失败,无法验证') |
|||
refreshData() |
|||
return |
|||
} |
|||
LockWorkRecordApi.createLockWorkRecord({ |
|||
operatorId: currentUserId.value, |
|||
lockId: item.lockId, |
|||
isolationPlanItemDetailId: item.id, |
|||
recordType: 17, //验证 |
|||
afterPhotoPath: photo, |
|||
gpsCoordinates: `${location.latitude},${location.longitude}` |
|||
}).then((res) => { |
|||
// 修改修改任务子项详情状态 发送已验证通知 |
|||
let promiseList = [ |
|||
verifyLockAction({ planItemDetailId: item.id, verifyRecordId: res }) |
|||
] |
|||
Promise.all(promiseList).then((res) => { |
|||
ElMessage.success('验证成功') |
|||
refreshData() |
|||
}).catch((err) => { |
|||
ElMessage.error('验证失败') |
|||
refreshData() |
|||
}) |
|||
}) |
|||
} |
|||
const handleUnlockVerify = async (item) => { |
|||
// 获取地理位置 |
|||
ElMessageBox.confirm('确定要验证解锁吗?', '解锁确认', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
type: 'warning' |
|||
}).then(async (res) => { |
|||
if (res === 'confirm') { |
|||
let location = await getCurrentLocation() |
|||
const lifelock = elLockStore.planLifeLocks.find( |
|||
(lifelock) => |
|||
lifelock.isolationPlanItemDetailId == item.id && |
|||
lifelock.userId == currentUserId.value && |
|||
lifelock.lockStatus == 1 && |
|||
lifelock.lockType == 3 |
|||
) |
|||
if (!lifelock) { |
|||
ElMessage.error('未找到生命锁') |
|||
refreshData() |
|||
return |
|||
} |
|||
LockWorkRecordApi.createLockWorkRecord({ |
|||
operatorId: currentUserId.value, |
|||
lockId: item.lockId, |
|||
isolationPlanItemDetailId: item.id, |
|||
recordType: 18, //解锁验证 |
|||
gpsCoordinates: `${location.latitude},${location.longitude}` |
|||
}).then((res) => { |
|||
const promiseList = [ |
|||
verifyUnlockAction({ planItemDetailId: item.id, lifelockId: lifelock.id }) |
|||
] |
|||
Promise.all(promiseList).then((res) => { |
|||
ElMessage.success('验证成功') |
|||
refreshData() |
|||
}).catch((err) => { |
|||
ElMessage.error('验证失败') |
|||
refreshData() |
|||
}) |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 处理锁响应数据 |
|||
const handleLockResponse = (data) => { |
|||
// 可以在这里处理需要在主组件层面处理的响应 |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
/* 主组件样式 - 只保留容器级别的样式 */ |
|||
</style> |
@ -0,0 +1,115 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="140px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="隔离指导书" prop="guideId"> |
|||
<el-select v-model="formData.guideId" placeholder="请选择隔离指导书" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.lockGuides" |
|||
:key="item.id" |
|||
:label="item.guideContent" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点" prop="isolationPointId"> |
|||
<el-select v-model="formData.isolationPointId" placeholder="请选择隔离点" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.isolationPoints" |
|||
:key="item.id" |
|||
:label="item.ipName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { IsolationPointApi, IsolationPoint } from '@/api/guide/isolationpoint' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 指导书与隔离点关联 表单 */ |
|||
defineOptions({ name: 'IsolationPointForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
guideId: undefined, |
|||
isolationPointId: undefined |
|||
}) |
|||
const formRules = reactive({ |
|||
guideId: [{ required: true, message: '隔离指导书ID不能为空', trigger: 'blur' }], |
|||
isolationPointId: [{ required: true, message: '隔离点ID不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await IsolationPointApi.getIsolationPoint(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as IsolationPoint |
|||
if (formType.value === 'create') { |
|||
await IsolationPointApi.createIsolationPoint(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await IsolationPointApi.updateIsolationPoint(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
guideId: undefined, |
|||
isolationPointId: undefined |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,257 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="100px" |
|||
> |
|||
<el-form-item label="隔离指导书" prop="guideId"> |
|||
<el-select |
|||
v-model="queryParams.guideId" |
|||
placeholder="请输入隔离指导书" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.lockGuides" |
|||
:key="item.id" |
|||
:label="item.guideContent" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点" prop="isolationPointId"> |
|||
<el-select |
|||
v-model="queryParams.isolationPointId" |
|||
placeholder="请输入隔离点" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.isolationPoints" |
|||
:key="item.id" |
|||
:label="item.ipName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['guide:isolation-point:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['guide:isolation-point:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['guide:isolation-point:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="隔离指导书" align="center" prop="guideId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.lockGuides.find((item) => item.id === scope.row.guideId)?.guideContent }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="隔离点" align="center" prop="isolationPointId"> |
|||
<template #default="scope"> |
|||
{{ |
|||
elLockStore.isolationPoints.find((item) => item.id === scope.row.isolationPointId) |
|||
?.ipName |
|||
}} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="创建时间" |
|||
align="center" |
|||
prop="createTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['guide:isolation-point:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['guide:isolation-point:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<IsolationPointForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { IsolationPointApi, IsolationPoint } from '@/api/guide/isolationpoint' |
|||
import IsolationPointForm from './IsolationPointForm.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
/** 指导书与隔离点关联 列表 */ |
|||
defineOptions({ name: 'IsolationPoint' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
const elLockStore = useElLockStore() |
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<IsolationPoint[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
guideId: undefined, |
|||
isolationPointId: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await IsolationPointApi.getIsolationPointPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await IsolationPointApi.deleteIsolationPoint(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除指导书与隔离点关联 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await IsolationPointApi.deleteIsolationPointList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: IsolationPoint[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await IsolationPointApi.exportIsolationPoint(queryParams) |
|||
download.excel(data, '指导书与隔离点关联.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,232 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="68px" |
|||
> |
|||
<el-form-item label="隔离指导书" prop="guideId"> |
|||
<el-input |
|||
v-model="queryParams.guideId" |
|||
placeholder="请输入隔离指导书ID" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点ID" prop="isolationPointId"> |
|||
<el-input |
|||
v-model="queryParams.isolationPointId" |
|||
placeholder="请输入隔离点ID" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['guide:isolation-point:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['guide:isolation-point:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['guide:isolation-point:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="id" align="center" prop="id" /> |
|||
<el-table-column label="隔离指导书ID" align="center" prop="guideId" /> |
|||
<el-table-column label="隔离点ID" align="center" prop="isolationPointId" /> |
|||
<el-table-column |
|||
label="创建时间" |
|||
align="center" |
|||
prop="createTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['guide:isolation-point:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['guide:isolation-point:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<IsolationPointForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { IsolationPointApi, IsolationPoint } from '@/api/guide/isolationpoint' |
|||
import IsolationPointForm from './IsolationPointForm.vue' |
|||
|
|||
/** 指导书与隔离点关联 列表 */ |
|||
defineOptions({ name: 'IsolationPoint' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
|
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<IsolationPoint[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
guideId: undefined, |
|||
isolationPointId: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
const data = await IsolationPointApi.getIsolationPointPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await IsolationPointApi.deleteIsolationPoint(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除指导书与隔离点关联 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await IsolationPointApi.deleteIsolationPointList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: IsolationPoint[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await IsolationPointApi.exportIsolationPoint(queryParams) |
|||
download.excel(data, '指导书与隔离点关联.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,157 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="150px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="指导书名称" prop="name"> |
|||
<el-input v-model="formData.name" placeholder="请输入指导书名称" /> |
|||
</el-form-item> |
|||
<el-form-item label="指导书编码" prop="code"> |
|||
<el-input v-model="formData.code" placeholder="请输入指导书编码" /> |
|||
</el-form-item> |
|||
<el-form-item label="工作内容和范围" prop="guideContent"> |
|||
<el-input v-model="formData.guideContent" type="textarea" :rows="10" /> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌人" prop="operatorId"> |
|||
<el-select v-model="formData.operatorId" clearable filterable> |
|||
<el-option |
|||
v-for="item in options.userOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌协助人" prop="operatorHelperId"> |
|||
<el-select v-model="formData.operatorHelperId" clearable filterable> |
|||
<el-option |
|||
v-for="item in options.userOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证人" prop="verifierId"> |
|||
<el-select v-model="formData.verifierId" clearable filterable> |
|||
<el-option |
|||
v-for="item in options.userOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证协助人" prop="verifierHelperId"> |
|||
<el-select v-model="formData.verifierHelperId" clearable filterable> |
|||
<el-option |
|||
v-for="item in options.userOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { LockGuideApi, LockGuide } from '@/api/guide/lockguide' |
|||
|
|||
/** 隔离指导书 表单 */ |
|||
defineOptions({ name: 'LockGuideForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
const { options } = defineProps({ |
|||
options: { |
|||
type: Object, |
|||
default: () => ({}) |
|||
} |
|||
}) |
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
name: undefined, |
|||
code: undefined, |
|||
guideContent: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined |
|||
}) |
|||
const formRules = reactive({ |
|||
guideContent: [{ required: true, message: '工作内容不能为空', trigger: 'blur' }], |
|||
name: [{ required: true, message: '指导书名称不能为空', trigger: 'blur' }], |
|||
code: [{ required: true, message: '指导书编码不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await LockGuideApi.getLockGuide(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as LockGuide |
|||
if (formType.value === 'create') { |
|||
await LockGuideApi.createLockGuide(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await LockGuideApi.updateLockGuide(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
name: undefined, |
|||
code: undefined, |
|||
guideContent: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,259 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="150px" v-loading="formLoading"> |
|||
<el-form-item label="指导书名称" prop="name"> |
|||
<el-input v-model="formData.name" placeholder="请输入指导书名称" :disabled="formLoading" maxlength="100" |
|||
show-word-limit /> |
|||
</el-form-item> |
|||
<el-form-item label="指导书编码" prop="code"> |
|||
<el-input v-model="formData.code" placeholder="请输入指导书编码" :disabled="formLoading" maxlength="50" |
|||
show-word-limit /> |
|||
</el-form-item> |
|||
<el-form-item label="工作内容和范围" prop="guideContent"> |
|||
<el-input v-model="formData.guideContent" type="textarea" :rows="10" placeholder="请输入工作内容和范围" |
|||
:disabled="formLoading" maxlength="2000" show-word-limit /> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌人" prop="operatorId"> |
|||
<el-select v-model="formData.operatorId" clearable filterable placeholder="请选择集中挂牌人" :disabled="formLoading"> |
|||
<el-option v-for="item in options.userOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌协助人" prop="operatorHelperId"> |
|||
<el-select v-model="formData.operatorHelperId" clearable filterable placeholder="请选择集中挂牌协助人" |
|||
:disabled="formLoading"> |
|||
<el-option v-for="item in options.userOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证人" prop="verifierId"> |
|||
<el-select v-model="formData.verifierId" clearable filterable placeholder="请选择验证人" :disabled="formLoading"> |
|||
<el-option v-for="item in options.userOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证协助人" prop="verifierHelperId"> |
|||
<el-select v-model="formData.verifierHelperId" clearable filterable placeholder="请选择验证协助人" |
|||
:disabled="formLoading"> |
|||
<el-option v-for="item in options.userOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="关联隔离点"> |
|||
<el-select v-model="formData.isolationPointIds" clearable filterable multiple placeholder="请选择关联的隔离点" |
|||
:disabled="formLoading"> |
|||
<el-option v-for="item in elLockStore.isolationPoints" :key="item.id" |
|||
:label="item.ipName + '-' + item.ipLocation +'('+item.ipNumber+')'" :value="item.id" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="需要所锁数量"> |
|||
{{ lockNums }} |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :loading="formLoading" :disabled="formLoading"> |
|||
{{ formLoading ? (isCreating ? '创建中...' : '更新中...') : '确 定' }} |
|||
</el-button> |
|||
<el-button @click="dialogVisible = false" :disabled="formLoading"> 取 消 </el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { LockGuideApi, LockGuide } from '@/api/guide/lockguide' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { IsolationPointApi } from '@/api/guide/isolationpoint' |
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 隔离指导书 表单 */ |
|||
defineOptions({ name: 'LockGuideForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
const { options } = defineProps({ |
|||
options: { |
|||
type: Object, |
|||
default: () => ({}) |
|||
} |
|||
}) |
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
name: undefined, |
|||
code: undefined, |
|||
guideContent: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined, |
|||
isolationPointIds: [] as number[] |
|||
}) |
|||
const formRules = reactive({ |
|||
guideContent: [{ required: true, message: '工作内容不能为空', trigger: 'blur' }], |
|||
name: [{ required: true, message: '指导书名称不能为空', trigger: 'blur' }], |
|||
code: [{ required: true, message: '指导书编码不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
const oldIsolationPointIds = ref<number[]>([]) |
|||
|
|||
// 计算属性:判断是否有隔离点变化 |
|||
const hasIsolationPointChanges = computed(() => { |
|||
const currentIds = formData.value.isolationPointIds || [] |
|||
const oldIds = oldIsolationPointIds.value |
|||
return ( |
|||
currentIds.length !== oldIds.length || |
|||
!currentIds.every((id) => oldIds.includes(id)) || |
|||
!oldIds.every((id) => currentIds.includes(id)) |
|||
) |
|||
}) |
|||
|
|||
const lockNums = computed(() => { |
|||
return formData.value.isolationPointIds?.reduce((acc, curr) => { |
|||
const point = elLockStore.isolationPoints.find((item) => item.id === curr) |
|||
return acc + (point?.guideLockNums || 0) |
|||
}, 0) |
|||
}) |
|||
// 计算属性:是否正在创建 |
|||
const isCreating = computed(() => formType.value === 'create') |
|||
|
|||
// 计算属性:是否正在更新 |
|||
const isUpdating = computed(() => formType.value === 'update') |
|||
|
|||
/** 获取指导书关联的隔离点ID列表 */ |
|||
const getGuideIsolationPointIds = (guideId: number): number[] => { |
|||
return elLockStore.isolationPointGuides |
|||
.filter((item) => item.guideId === guideId) |
|||
.map((item) => item.isolationPointId) as number[] |
|||
} |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
try { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
|
|||
// 修改时,设置数据 |
|||
if (id && type === 'update') { |
|||
formLoading.value = true |
|||
|
|||
// 并行获取数据 |
|||
const [guideData] = await Promise.all([LockGuideApi.getLockGuide(id)]) |
|||
|
|||
formData.value = guideData |
|||
const pointIds = getGuideIsolationPointIds(id) |
|||
formData.value.isolationPointIds = pointIds |
|||
oldIsolationPointIds.value = [...pointIds] // 创建副本避免引用问题 |
|||
} |
|||
} catch (error) { |
|||
console.error('打开表单失败:', error) |
|||
message.error('获取数据失败,请重试') |
|||
dialogVisible.value = false |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 处理隔离点关联 */ |
|||
const handleIsolationPoints = async ( |
|||
guideId: number, |
|||
addPoints: number[], |
|||
deletePoints: number[] |
|||
) => { |
|||
const operations: Promise<any>[] = [] |
|||
|
|||
// 删除不再关联的隔离点 |
|||
if (deletePoints.length > 0) { |
|||
const deleteOperations = deletePoints.map((id: number) => { |
|||
const point = elLockStore.isolationPointGuides.find( |
|||
(item) => item.isolationPointId === id && item.guideId === guideId |
|||
) |
|||
return point ? IsolationPointApi.deleteIsolationPoint(point.id!) : Promise.resolve() |
|||
}) |
|||
operations.push(...deleteOperations) |
|||
} |
|||
|
|||
// 添加新关联的隔离点 |
|||
if (addPoints.length > 0) { |
|||
const addOperations = addPoints.map((id: number) => |
|||
IsolationPointApi.createIsolationPoint({ |
|||
guideId: guideId, |
|||
isolationPointId: id |
|||
}) |
|||
) |
|||
operations.push(...addOperations) |
|||
} |
|||
|
|||
return Promise.all(operations) |
|||
} |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
try { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
|
|||
formLoading.value = true |
|||
|
|||
// 计算隔离点变化 |
|||
const currentPointIds = formData.value.isolationPointIds || [] |
|||
const addPoints = currentPointIds.filter((id) => !oldIsolationPointIds.value.includes(id)) |
|||
const deletePoints = oldIsolationPointIds.value.filter((id) => !currentPointIds.includes(id)) |
|||
|
|||
const data = formData.value as unknown as LockGuide |
|||
let guideId: number |
|||
|
|||
if (isCreating.value) { |
|||
// 创建指导书 |
|||
guideId = await LockGuideApi.createLockGuide(data) |
|||
|
|||
// 处理隔离点关联(仅添加) |
|||
if (currentPointIds.length > 0) { |
|||
await handleIsolationPoints(guideId, currentPointIds, []) |
|||
} |
|||
|
|||
message.success(t('common.createSuccess')) |
|||
} else if (isUpdating.value) { |
|||
// 更新指导书 |
|||
guideId = data.id! |
|||
|
|||
// 先更新指导书基本信息 |
|||
await LockGuideApi.updateLockGuide(data) |
|||
|
|||
// 处理隔离点关联变化(仅在有变化时处理) |
|||
if (hasIsolationPointChanges.value) { |
|||
await handleIsolationPoints(guideId, addPoints, deletePoints) |
|||
} |
|||
|
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
|
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} catch (error) { |
|||
console.error('提交表单失败:', error) |
|||
message.error(formType.value === 'create' ? '创建失败,请重试' : '更新失败,请重试') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
name: undefined, |
|||
code: undefined, |
|||
guideContent: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined, |
|||
isolationPointIds: [] |
|||
} |
|||
formRef.value?.resetFields() |
|||
oldIsolationPointIds.value = [] |
|||
} |
|||
</script> |
@ -0,0 +1,244 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="120px" |
|||
> |
|||
<el-form-item label="工作内容" prop="guideContent"> |
|||
<el-input |
|||
v-model="queryParams.guideContent" |
|||
placeholder="请输入工作内容" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['guide:lock-guide:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['guide:lock-guide:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['guide:lock-guide:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="指导书名称" align="center" prop="name" /> |
|||
<el-table-column label="指导书编码" align="center" prop="code" /> |
|||
<el-table-column label="工作内容和范围" align="center" min-width="300px" prop="guideContent" /> |
|||
<!-- <el-table-column label="集中挂牌人" align="center" prop="operatorId"> |
|||
<template #default="scope"> |
|||
{{ options.userOptions.find((user) => user.value === scope.row.operatorId)?.label }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="集中挂牌协助人" align="center" prop="operatorHelperId"> |
|||
<template #default="scope"> |
|||
{{ options.userOptions.find((user) => user.value === scope.row.operatorHelperId)?.label }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="验证人" align="center" prop="verifierId"> |
|||
<template #default="scope"> |
|||
{{ options.userOptions.find((user) => user.value === scope.row.verifierId)?.label }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="验证协助人" align="center" prop="verifierHelperId"> |
|||
<template #default="scope"> |
|||
{{ options.userOptions.find((user) => user.value === scope.row.verifierHelperId)?.label }} |
|||
</template> |
|||
</el-table-column> --> |
|||
|
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['guide:lock-guide:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['guide:lock-guide:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<LockGuideForm ref="formRef" @success="getList" :options="options" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { LockGuideApi, LockGuide } from '@/api/guide/lockguide' |
|||
import LockGuideForm from './LockGuideFormNew.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
const elLockStore = useElLockStore() |
|||
/** 隔离指导书 列表 */ |
|||
defineOptions({ name: 'LockGuide' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
const options = ref({ |
|||
userOptions: elLockStore.users.map((item) => ({ |
|||
label: item.nickname, |
|||
value: item.id |
|||
})) |
|||
}) |
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<LockGuide[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
guideContent: undefined, |
|||
guideLockNums: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await LockGuideApi.getLockGuidePage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await LockGuideApi.deleteLockGuide(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除隔离指导书 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await LockGuideApi.deleteLockGuideList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: LockGuide[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await LockGuideApi.exportLockGuide(queryParams) |
|||
download.excel(data, '隔离指导书.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,903 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="appStore.mobile ? '95%' : '80%'"> |
|||
<!-- 桌面端表单 --> |
|||
<el-form v-if="!appStore.mobile" ref="formRef" :model="formData" :rules="formRules" label-width="100px" |
|||
v-loading="formLoading"> |
|||
<el-form-item label="任务名称" prop="ipName" v-if="formType === 'create'"> |
|||
<el-input v-model="formData.ipName" placeholder="请输入任务名称"> |
|||
<template #prefix v-if="formType === 'create'"> |
|||
<span class="text-12px">{{ currentDay }}</span> |
|||
</template> |
|||
</el-input> |
|||
</el-form-item> |
|||
<div v-else class="flex items-center justify-center"> |
|||
<span class="text-16px font-bold">{{ formData.ipName }}</span> |
|||
</div> |
|||
<div class="plan-item-container"> |
|||
<div class="flex gap-2 justify-items-end"> |
|||
<el-select v-if="formType === 'create'" placeholder="请选择检修任务指导书" clearable @change="addPlanItem" filterable |
|||
v-model="selectedGuideId"> |
|||
<el-option v-for="item in availableGuides" :key="item.id" :label="item.name" :value="item.id" /> |
|||
</el-select> |
|||
</div> |
|||
<el-form-item prop="planItems" class="mt-5 w-full" label-width="0"> |
|||
<el-table :data="formData.planItems" class="w-full" border> |
|||
<el-table-column type="expand" v-if="formType !== 'create'"> |
|||
<template #default="props"> |
|||
<div> |
|||
<el-table :data="props.row.planItemDetails"> |
|||
<el-table-column label="电子锁" align="center" prop="lock.lockName"> |
|||
<template #default="scope"> |
|||
<template v-if="scope.row.lock"> |
|||
{{ scope.row.lock?.lockName }} |
|||
<el-icon v-if="scope.row.lock?.lockStatus == 1"> |
|||
<Lock /> |
|||
</el-icon> |
|||
<el-icon v-else> |
|||
<Unlock /> |
|||
</el-icon> |
|||
</template> |
|||
<template v-else> 未绑定电子锁 </template> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="隔离点" align="center" prop="isolationPoint.ipName"> |
|||
<template #default="scope"> |
|||
{{ scope.row.isolationPoint.ipName }} |
|||
<DictTag :type="DICT_TYPE.LOCK_ISOLATION_POINT_STATUS" |
|||
:value="scope.row.isolationPoint.status" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="任务状态" align="center" prop="lockStatus"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS" :value="scope.row.lockStatus" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="受影响人" align="center" prop="lifeLock"> |
|||
<template #default="scope"> |
|||
<div v-if="scope.row.lifeLock"> |
|||
<div v-for="item in scope.row.lifeLock" :key="item.id"> |
|||
{{ |
|||
elLockStore.users.find((user) => user.id === item.userId)?.nickname |
|||
}} |
|||
<DictTag :type="DICT_TYPE.LOCK_LIFE_LOCK_STATUS" :value="item.lockStatus" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="检修任务指导书" align="center" prop="guideId" width="220px"> |
|||
<template #default="scope"> |
|||
{{ getGuideName(scope.row.guideId) }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="任务状态" align="center" prop="status" v-if="formType != 'create'"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_ITEM_STATUS" :value="scope.row.status" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="集中挂牌人" align="center" prop="operatorId" width="150px"> |
|||
<template #default="scope"> |
|||
<el-select v-if="formType === 'create'" v-model="scope.row.operatorId" placeholder="请选择集中挂牌人" |
|||
:disabled="formType !== 'create'" clearable filterable> |
|||
<el-option v-for="item in elLockStore.users" :key="item.id" :label="item.nickname" :value="item.id" /> |
|||
</el-select> |
|||
<span v-else>{{ |
|||
elLockStore.users.find((user) => user.id === scope.row.operatorId)?.nickname |
|||
}}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="集中挂牌协助人" align="center" prop="operatorHelperId" width="150px"> |
|||
<template #default="scope"> |
|||
<el-select v-if="formType === 'create'" v-model="scope.row.operatorHelperId" placeholder="请选择集中挂牌协助人" |
|||
:disabled="formType !== 'create'" clearable filterable> |
|||
<el-option v-for="item in elLockStore.users" :key="item.id" :label="item.nickname" :value="item.id" /> |
|||
</el-select> |
|||
<span v-else>{{ |
|||
elLockStore.users.find((user) => user.id === scope.row.operatorHelperId)?.nickname |
|||
}}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="验证人" align="center" prop="verifierId" width="150px"> |
|||
<template #default="scope"> |
|||
<el-select v-if="formType === 'create'" v-model="scope.row.verifierId" placeholder="请选择验证人" |
|||
:disabled="formType !== 'create'" clearable filterable> |
|||
<el-option v-for="item in elLockStore.users" :key="item.id" :label="item.nickname" :value="item.id" /> |
|||
</el-select> |
|||
<span v-else>{{ |
|||
elLockStore.users.find((user) => user.id === scope.row.verifierId)?.nickname |
|||
}}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="验证协助人" align="center" prop="verifierHelperId" width="150px"> |
|||
<template #default="scope"> |
|||
<el-select v-if="formType === 'create'" v-model="scope.row.verifierHelperId" placeholder="请选择验证协助人" |
|||
:disabled="formType !== 'create'" clearable filterable> |
|||
<el-option v-for="item in elLockStore.users" :key="item.id" :label="item.nickname" :value="item.id" /> |
|||
</el-select> |
|||
<span v-else>{{ |
|||
elLockStore.users.find((user) => user.id === scope.row.verifierHelperId)?.nickname |
|||
}}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="隔离点" align="center"> |
|||
<template #default="scope"> |
|||
<div v-for="point in getGuidePoints(scope.row.guideId)" :key="point?.id"> |
|||
{{ point?.ipName + '-' + point?.ipLocation + '(' + point?.ipNumber + ')' }} |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="锁数量" align="center" width="50px"> |
|||
<template #default="scope"> |
|||
{{ getGuideLockNums(scope.row.guideId) }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" align="center" width="100" v-if="formType === 'create'"> |
|||
<template #default="scope"> |
|||
<el-button type="danger" @click="deletePlanItem(scope.$index)">删除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</el-form-item> |
|||
</div> |
|||
</el-form> |
|||
|
|||
<!-- 移动端表单 --> |
|||
<el-form v-else ref="formRef" :model="formData" :rules="formRules" label-width="0" v-loading="formLoading" |
|||
class="mobile-form"> |
|||
<!-- 任务名称 --> |
|||
<div class="mobile-form-section"> |
|||
<div class="mobile-form-title">任务名称</div> |
|||
<el-form-item prop="ipName" v-if="formType === 'create'"> |
|||
<el-input v-model="formData.ipName" placeholder="请输入任务名称" class="mobile-input"> |
|||
<template #prefix> |
|||
<span class="text-12px">{{ currentDay }}</span> |
|||
</template> |
|||
</el-input> |
|||
</el-form-item> |
|||
<div v-else class="mobile-display-value"> |
|||
<span class="text-16px font-bold">{{ formData.ipName }}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 添加指导书选择 --> |
|||
<div class="mobile-form-section" v-if="formType === 'create'"> |
|||
<div class="mobile-form-title">添加检修任务指导书</div> |
|||
<el-select v-model="selectedGuideId" placeholder="请选择检修任务指导书" clearable @change="addPlanItem" |
|||
filterable class="mobile-select w-full"> |
|||
<el-option v-for="item in availableGuides" :key="item.id" :label="item.name" :value="item.id" /> |
|||
</el-select> |
|||
</div> |
|||
|
|||
<!-- 计划项列表 --> |
|||
<div class="mobile-form-section"> |
|||
<div class="mobile-form-title">检修任务子项</div> |
|||
<el-form-item prop="planItems" class="w-full"> |
|||
<div v-if="formData.planItems.length === 0" class="mobile-empty-state"> |
|||
<el-empty description="暂无检修任务子项" /> |
|||
</div> |
|||
|
|||
<!-- 计划项卡片列表 --> |
|||
<div v-else class="mobile-plan-items"> |
|||
<div v-for="(item, index) in formData.planItems" :key="index" class="mobile-plan-item-card"> |
|||
<div class="mobile-card-header"> |
|||
<div class="mobile-card-title"> |
|||
<el-icon class="mr-2"><Document /></el-icon> |
|||
{{ getGuideName(item.guideId) }} |
|||
</div> |
|||
<el-button v-if="formType === 'create'" type="danger" size="small" @click="deletePlanItem(index)" |
|||
class="mobile-delete-btn"> |
|||
删除 |
|||
</el-button> |
|||
</div> |
|||
|
|||
<div class="mobile-card-content"> |
|||
<!-- 集中挂牌人 --> |
|||
<div class="mobile-form-row"> |
|||
<div class="mobile-label">集中挂牌人</div> |
|||
<div class="mobile-value"> |
|||
<el-select v-if="formType === 'create'" v-model="item.operatorId" placeholder="请选择集中挂牌人" |
|||
clearable filterable class="mobile-select w-full"> |
|||
<el-option v-for="user in elLockStore.users" :key="user.id" :label="user.nickname" :value="user.id" /> |
|||
</el-select> |
|||
<span v-else class="mobile-display-text"> |
|||
{{ elLockStore.users.find((user) => user.id === item.operatorId)?.nickname || '未选择' }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 集中挂牌协助人 --> |
|||
<div class="mobile-form-row"> |
|||
<div class="mobile-label">集中挂牌协助人</div> |
|||
<div class="mobile-value"> |
|||
<el-select v-if="formType === 'create'" v-model="item.operatorHelperId" placeholder="请选择集中挂牌协助人" |
|||
clearable filterable class="mobile-select w-full"> |
|||
<el-option v-for="user in elLockStore.users" :key="user.id" :label="user.nickname" :value="user.id" /> |
|||
</el-select> |
|||
<span v-else class="mobile-display-text"> |
|||
{{ elLockStore.users.find((user) => user.id === item.operatorHelperId)?.nickname || '未选择' }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 验证人 --> |
|||
<div class="mobile-form-row"> |
|||
<div class="mobile-label">验证人</div> |
|||
<div class="mobile-value"> |
|||
<el-select v-if="formType === 'create'" v-model="item.verifierId" placeholder="请选择验证人" |
|||
clearable filterable class="mobile-select w-full"> |
|||
<el-option v-for="user in elLockStore.users" :key="user.id" :label="user.nickname" :value="user.id" /> |
|||
</el-select> |
|||
<span v-else class="mobile-display-text"> |
|||
{{ elLockStore.users.find((user) => user.id === item.verifierId)?.nickname || '未选择' }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 验证协助人 --> |
|||
<div class="mobile-form-row"> |
|||
<div class="mobile-label">验证协助人</div> |
|||
<div class="mobile-value"> |
|||
<el-select v-if="formType === 'create'" v-model="item.verifierHelperId" placeholder="请选择验证协助人" |
|||
clearable filterable class="mobile-select w-full"> |
|||
<el-option v-for="user in elLockStore.users" :key="user.id" :label="user.nickname" :value="user.id" /> |
|||
</el-select> |
|||
<span v-else class="mobile-display-text"> |
|||
{{ elLockStore.users.find((user) => user.id === item.verifierHelperId)?.nickname || '未选择' }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 任务状态 --> |
|||
<div class="mobile-form-row" v-if="formType != 'create'"> |
|||
<div class="mobile-label">任务状态</div> |
|||
<div class="mobile-value"> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_ITEM_STATUS" :value="item.status || 0" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 隔离点信息 --> |
|||
<div class="mobile-form-row"> |
|||
<div class="mobile-label">隔离点</div> |
|||
<div class="mobile-value"> |
|||
<div v-for="point in getGuidePoints(item.guideId)" :key="point?.id" class="mobile-point-item"> |
|||
{{ point?.ipName + '-' + point?.ipLocation + '(' + point?.ipNumber + ')' }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 需要锁数量 --> |
|||
<div class="mobile-form-row"> |
|||
<div class="mobile-label">需要锁数量</div> |
|||
<div class="mobile-value"> |
|||
<span class="mobile-lock-count">{{ getGuideLockNums(item.guideId) }}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 查看模式下的详情展开 --> |
|||
<div v-if="formType !== 'create' && item.planItemDetails?.length" class="mobile-details-section"> |
|||
<div class="mobile-details-title">详细任务</div> |
|||
<div v-for="detail in item.planItemDetails" :key="detail.id" class="mobile-detail-item"> |
|||
<div class="mobile-detail-row"> |
|||
<span class="mobile-detail-label">电子锁:</span> |
|||
<span class="mobile-detail-value"> |
|||
<template v-if="detail.lock"> |
|||
{{ detail.lock?.lockName }} |
|||
<el-icon v-if="detail.lock?.lockStatus == 1" class="ml-1"> |
|||
<Lock /> |
|||
</el-icon> |
|||
<el-icon v-else class="ml-1"> |
|||
<Unlock /> |
|||
</el-icon> |
|||
</template> |
|||
<template v-else>未绑定电子锁</template> |
|||
</span> |
|||
</div> |
|||
<div class="mobile-detail-row"> |
|||
<span class="mobile-detail-label">隔离点:</span> |
|||
<span class="mobile-detail-value"> |
|||
{{ detail.isolationPoint.ipName }} |
|||
<DictTag :type="DICT_TYPE.LOCK_ISOLATION_POINT_STATUS" :value="detail.isolationPoint.status || 0" /> |
|||
</span> |
|||
</div> |
|||
<div class="mobile-detail-row"> |
|||
<span class="mobile-detail-label">任务状态:</span> |
|||
<span class="mobile-detail-value"> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS" :value="detail.lockStatus || 0" /> |
|||
</span> |
|||
</div> |
|||
<div v-if="detail.lifeLock?.length" class="mobile-detail-row"> |
|||
<span class="mobile-detail-label">受影响人:</span> |
|||
<div class="mobile-detail-value"> |
|||
<div v-for="lifeLock in detail.lifeLock" :key="lifeLock.id" class="mobile-life-lock-item"> |
|||
{{ elLockStore.users.find((user) => user.id === lifeLock.userId)?.nickname }} |
|||
<DictTag :type="DICT_TYPE.LOCK_LIFE_LOCK_STATUS" :value="lifeLock.lockStatus || 0" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</el-form-item> |
|||
</div> |
|||
</el-form> |
|||
|
|||
<div v-if="formType != 'create' && !appStore.mobile" ref="flowRef" class="flex justify-center h-xl"> </div> |
|||
<template #footer> |
|||
<template v-if="formType === 'create'"> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading" class="mobile-submit-btn">确 定</el-button> |
|||
<el-button @click="dialogVisible = false" class="mobile-cancel-btn">取 消</el-button> |
|||
</template> |
|||
<template v-else> |
|||
<el-button @click="dialogVisible = false" type="primary" class="mobile-close-btn">关 闭</el-button> |
|||
</template> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { PlanApi, Plan } from '@/api/isolation/plan' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { PlanItemApi } from '@/api/isolation/planitem' |
|||
import { PlanItemDetailApi } from '@/api/isolation/planitemdetail' |
|||
import { DICT_TYPE } from '@/utils/dict' |
|||
import { Document, Lock, Unlock } from '@element-plus/icons-vue' |
|||
import dayjs from 'dayjs' |
|||
import LogicFlow from '@logicflow/core' |
|||
import '@logicflow/core/lib/style/index.css' |
|||
import LockPointNodeAdapter from '@/components/LogicFlow/LockPointNodeAdapter' |
|||
import { useAppStore } from '@/store/modules/app' |
|||
// 类型定义 |
|||
interface PlanItem { |
|||
guideId: number |
|||
operatorId?: number |
|||
operatorHelperId?: number |
|||
verifierId?: number |
|||
verifierHelperId?: number |
|||
status?: number |
|||
planItemDetails?: any[] |
|||
} |
|||
|
|||
interface FormData { |
|||
id?: number |
|||
ipName?: string |
|||
status?: number |
|||
planItems: PlanItem[] |
|||
} |
|||
const currentDay = dayjs().format('YYYYMMDD') |
|||
type FormType = 'create' | 'update' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 检修任务 表单 */ |
|||
defineOptions({ name: 'PlanForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
const appStore = useAppStore() |
|||
// 响应式数据 |
|||
const dialogVisible = ref(false) |
|||
const dialogTitle = ref('') |
|||
const formLoading = ref(false) |
|||
const formType = ref<FormType>('create') |
|||
const formData = ref<FormData>({ |
|||
id: undefined, |
|||
ipName: undefined, |
|||
status: 0, |
|||
planItems: [] |
|||
}) |
|||
const formRef = ref() |
|||
const selectedGuideId = ref<number | undefined>(undefined) |
|||
const flowRef = ref() |
|||
let lf: LogicFlow | null = null |
|||
let datas = ref<any[]>([]) |
|||
// 计算属性 - 简化模板表达式 |
|||
const availableGuides = computed(() => |
|||
elLockStore.lockGuides.filter( |
|||
(item) => !formData.value.planItems?.some((planItem) => planItem.guideId === item.id) |
|||
) |
|||
) |
|||
|
|||
const getGuideName = (guideId: number) => |
|||
elLockStore.lockGuides.find((item) => item.id === guideId)?.name |
|||
|
|||
const getGuidePoints = (guideId: number) => |
|||
elLockStore.isolationPointGuides |
|||
.filter((item) => item.guideId === guideId) |
|||
.map((i) => elLockStore.isolationPoints.find((p) => p.id === i.isolationPointId)) |
|||
|
|||
const getGuideLockNums = (guideId: number) => |
|||
getGuidePoints(guideId).reduce((acc, curr) => acc + (curr?.guideLockNums || 0), 0) |
|||
|
|||
// 表单验证 |
|||
const validatePlanItems = (_rule: any, value: PlanItem[], callback: any) => { |
|||
if (value.length === 0) { |
|||
return callback(new Error('检修任务子项不能为空')) |
|||
} |
|||
|
|||
for (const item of value) { |
|||
if (!item.operatorId) { |
|||
return callback(new Error('集中挂牌人不能为空')) |
|||
} |
|||
if (!item.verifierId) { |
|||
return callback(new Error('验证人不能为空')) |
|||
} |
|||
} |
|||
callback() |
|||
} |
|||
|
|||
const initChart = async () => { |
|||
if (appStore.mobile) return |
|||
if (!flowRef.value) return |
|||
|
|||
// 销毁之前的实例 |
|||
if (lf) { |
|||
lf.destroy() |
|||
lf = null |
|||
} |
|||
|
|||
await nextTick() |
|||
|
|||
lf = new LogicFlow({ |
|||
container: flowRef.value, |
|||
width: flowRef.value.clientWidth, |
|||
height: flowRef.value.clientHeight |
|||
}) |
|||
|
|||
// 注册自定义Vue节点 |
|||
lf.register(LockPointNodeAdapter) |
|||
// 渲染节点数据 |
|||
renderNodeData() |
|||
// 自适应画布 |
|||
lf.fitView() |
|||
} |
|||
const renderNodeData = () => { |
|||
getPlanData(formData.value.id!) |
|||
if (!lf || !datas.value.length) return |
|||
const nodes = datas.value.map((item) => ({ |
|||
...item, |
|||
type: 'lock-point-node' |
|||
})) |
|||
lf.render({ nodes, edges: [] }) |
|||
requestAnimationFrame(() => { |
|||
lf?.fitView() |
|||
setTimeout(() => lf?.fitView(), 50) |
|||
setTimeout(() => lf?.fitView(), 200) |
|||
}) |
|||
} |
|||
const formRules = reactive({ |
|||
ipName: [{ required: true, message: '计划名称不能为空', trigger: 'blur' }], |
|||
planItems: [ |
|||
{ required: true, message: '检修任务子项不能为空', trigger: 'blur' }, |
|||
{ validator: validatePlanItems, trigger: 'blur' } |
|||
] |
|||
}) |
|||
|
|||
// 业务逻辑函数 |
|||
/** |
|||
* 创建计划项详情 |
|||
*/ |
|||
async function createPlanItemDetails(planItemId: number, guideId: number): Promise<void> { |
|||
const isolationPoints = elLockStore.isolationPointGuides.filter( |
|||
(item) => item.guideId === guideId |
|||
) |
|||
|
|||
const detailPromises = isolationPoints.map(async (point) => { |
|||
const lockNums = |
|||
elLockStore.isolationPoints.find((i) => i.id === point.isolationPointId)?.guideLockNums || 0 |
|||
const lockPromises = Array.from({ length: lockNums }, () => |
|||
PlanItemDetailApi.createPlanItemDetail({ |
|||
isolationPlanItemId: planItemId, |
|||
isolationPointId: point.isolationPointId, |
|||
lockStatus: 0 |
|||
}) |
|||
) |
|||
|
|||
return Promise.all(lockPromises) |
|||
}) |
|||
|
|||
await Promise.all(detailPromises) |
|||
} |
|||
|
|||
/** |
|||
* 创建计划项 |
|||
*/ |
|||
async function createPlanItems(planId: number): Promise<void> { |
|||
const itemPromises = formData.value.planItems.map(async (item) => { |
|||
const planItemId = await PlanItemApi.createPlanItem({ |
|||
isolationPlanId: planId, |
|||
guideId: item.guideId, |
|||
operatorId: item.operatorId, |
|||
operatorHelperId: item.operatorHelperId, |
|||
verifierId: item.verifierId, |
|||
verifierHelperId: item.verifierHelperId, |
|||
status: 0 |
|||
}) |
|||
|
|||
await createPlanItemDetails(planItemId, item.guideId) |
|||
}) |
|||
|
|||
await Promise.all(itemPromises) |
|||
} |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: FormType, id?: number) => { |
|||
dialogVisible.value = true |
|||
if (id) { |
|||
dialogTitle.value = '查看检修任务' |
|||
} else { |
|||
dialogTitle.value = '添加检修任务' |
|||
} |
|||
formType.value = type |
|||
resetForm() |
|||
|
|||
// 修改时,设置数据 |
|||
if (id && type === 'update') { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await PlanApi.getPlan(id) |
|||
nextTick(() => { |
|||
initChart() |
|||
}) |
|||
let planItems = elLockStore.planItems.filter((item) => item.isolationPlanId === id) |
|||
formData.value.planItems = planItems.map((item) => { |
|||
return { |
|||
guideId: item.guideId!, |
|||
operatorId: item.operatorId, |
|||
operatorHelperId: item.operatorHelperId, |
|||
verifierId: item.verifierId, |
|||
verifierHelperId: item.verifierHelperId, |
|||
status: item.status, |
|||
planItemDetails: elLockStore.planItemDetails |
|||
.filter((detail) => detail.isolationPlanItemId === item.id) |
|||
.map((detail) => { |
|||
return { |
|||
...detail, |
|||
lock: elLockStore.locks.find((lock) => lock.id === detail.lockId), |
|||
isolationPoint: elLockStore.isolationPoints.find( |
|||
(point) => point.id === detail.isolationPointId |
|||
), |
|||
lifeLock: elLockStore.planLifeLocks.filter( |
|||
(lock) => lock.isolationPlanItemDetailId === detail.id && lock.lockType == 5 |
|||
) |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
} catch (error) { |
|||
message.error('获取计划数据失败') |
|||
console.error('获取计划数据失败:', error) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
const getPlanData = (id: number) => { |
|||
let details = elLockStore.planItemDetails.filter((item) => item.planId == id) |
|||
let points = Array.from(new Set(details.map((i) => i.isolationPointId))) |
|||
let data = points.map((i, index) => ({ |
|||
properties: { |
|||
planId: id, |
|||
pointId: i, |
|||
expand: points.length > 2 ? false : true |
|||
}, |
|||
type: 'lock-point-node', |
|||
x: points.length > 2 ? 300 * index : 800 * index, |
|||
y: 0 |
|||
})) |
|||
datas.value = data |
|||
return data |
|||
} |
|||
|
|||
defineExpose({ open }) |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) |
|||
|
|||
const submitForm = async () => { |
|||
try { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
|
|||
formLoading.value = true |
|||
const data = formData.value as unknown as Plan |
|||
|
|||
let planId: number |
|||
if (formType.value === 'create') { |
|||
planId = await PlanApi.createPlan({ ...data, ipName: currentDay + '-' + data.ipName }) |
|||
message.success(t('common.createSuccess')) |
|||
|
|||
// 只有新建时才需要创建计划项 |
|||
if (formData.value.planItems.length > 0) { |
|||
await createPlanItems(planId) |
|||
} |
|||
} else { |
|||
await PlanApi.updatePlan(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
|
|||
dialogVisible.value = false |
|||
emit('success') |
|||
} catch (error) { |
|||
message.error('保存失败,请重试') |
|||
console.error('提交表单失败:', error) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
ipName: undefined, |
|||
status: 0, |
|||
planItems: [] |
|||
} |
|||
selectedGuideId.value = undefined |
|||
formRef.value?.resetFields() |
|||
} |
|||
|
|||
/** |
|||
* 添加计划项 |
|||
*/ |
|||
const addPlanItem = () => { |
|||
if (!selectedGuideId.value) { |
|||
message.error('请选择检修任务指导书') |
|||
return |
|||
} |
|||
|
|||
const guide = elLockStore.lockGuides.find((item) => item.id === selectedGuideId.value) |
|||
if (!guide) { |
|||
message.error('指导书信息不存在') |
|||
return |
|||
} |
|||
|
|||
const newPlanItem: PlanItem = { |
|||
guideId: selectedGuideId.value, |
|||
operatorId: guide.operatorId, |
|||
operatorHelperId: guide.operatorHelperId, |
|||
verifierId: guide.verifierId, |
|||
verifierHelperId: guide.verifierHelperId, |
|||
status: 0, |
|||
planItemDetails: [] |
|||
} |
|||
if (!formData.value.planItems) { |
|||
formData.value.planItems = [] |
|||
} |
|||
formData.value.planItems.push(newPlanItem) |
|||
console.log(formData.value.planItems) |
|||
selectedGuideId.value = undefined |
|||
} |
|||
|
|||
/** |
|||
* 删除计划项 |
|||
*/ |
|||
const deletePlanItem = (index: number) => { |
|||
if (index >= 0 && index < formData.value.planItems.length) { |
|||
formData.value.planItems.splice(index, 1) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.mobile-form { |
|||
.mobile-form-section { |
|||
margin-bottom: 20px; |
|||
padding: 16px; |
|||
background: #f8f9fa; |
|||
border-radius: 8px; |
|||
|
|||
.mobile-form-title { |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
color: #303133; |
|||
margin-bottom: 12px; |
|||
padding-bottom: 8px; |
|||
border-bottom: 1px solid #e4e7ed; |
|||
} |
|||
} |
|||
|
|||
.mobile-input, |
|||
.mobile-select { |
|||
width: 100%; |
|||
|
|||
:deep(.el-input__wrapper) { |
|||
border-radius: 6px; |
|||
} |
|||
} |
|||
|
|||
.mobile-display-value { |
|||
padding: 12px; |
|||
background: #fff; |
|||
border-radius: 6px; |
|||
border: 1px solid #e4e7ed; |
|||
} |
|||
|
|||
.mobile-empty-state { |
|||
padding: 40px 20px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.mobile-plan-items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
} |
|||
|
|||
.mobile-plan-item-card { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
border: 1px solid #e4e7ed; |
|||
overflow: hidden; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
|
|||
.mobile-card-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 16px; |
|||
background: #f5f7fa; |
|||
border-bottom: 1px solid #e4e7ed; |
|||
|
|||
.mobile-card-title { |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
color: #303133; |
|||
} |
|||
|
|||
.mobile-delete-btn { |
|||
padding: 6px 12px; |
|||
font-size: 12px; |
|||
} |
|||
} |
|||
|
|||
.mobile-card-content { |
|||
padding: 16px; |
|||
} |
|||
} |
|||
|
|||
.mobile-form-row { |
|||
display: flex; |
|||
flex-direction: column; |
|||
margin-bottom: 16px; |
|||
gap: 8px; |
|||
|
|||
.mobile-label { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
color: #606266; |
|||
min-width: 80px; |
|||
} |
|||
|
|||
.mobile-value { |
|||
flex: 1; |
|||
} |
|||
|
|||
.mobile-display-text { |
|||
padding: 8px 12px; |
|||
background: #f5f7fa; |
|||
border-radius: 4px; |
|||
color: #303133; |
|||
display: inline-block; |
|||
min-height: 32px; |
|||
line-height: 16px; |
|||
} |
|||
|
|||
.mobile-point-item { |
|||
padding: 4px 8px; |
|||
background: #e1f3d8; |
|||
border-radius: 4px; |
|||
margin-bottom: 4px; |
|||
font-size: 12px; |
|||
color: #67c23a; |
|||
} |
|||
|
|||
.mobile-lock-count { |
|||
display: inline-block; |
|||
padding: 4px 12px; |
|||
background: #409eff; |
|||
color: #fff; |
|||
border-radius: 12px; |
|||
font-weight: 600; |
|||
font-size: 14px; |
|||
} |
|||
} |
|||
|
|||
.mobile-details-section { |
|||
margin-top: 16px; |
|||
padding-top: 16px; |
|||
border-top: 1px solid #e4e7ed; |
|||
|
|||
.mobile-details-title { |
|||
font-size: 14px; |
|||
font-weight: 600; |
|||
color: #303133; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.mobile-detail-item { |
|||
background: #f8f9fa; |
|||
border-radius: 6px; |
|||
padding: 12px; |
|||
margin-bottom: 12px; |
|||
|
|||
.mobile-detail-row { |
|||
display: flex; |
|||
margin-bottom: 8px; |
|||
align-items: flex-start; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.mobile-detail-label { |
|||
font-size: 12px; |
|||
color: #909399; |
|||
min-width: 60px; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.mobile-detail-value { |
|||
flex: 1; |
|||
font-size: 12px; |
|||
color: #303133; |
|||
word-break: break-all; |
|||
} |
|||
} |
|||
|
|||
.mobile-life-lock-item { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
margin-bottom: 4px; |
|||
padding: 4px 8px; |
|||
background: #fff; |
|||
border-radius: 4px; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 移动端按钮样式优化 |
|||
.mobile-submit-btn, |
|||
.mobile-cancel-btn, |
|||
.mobile-close-btn { |
|||
min-width: 80px; |
|||
height: 40px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
// 响应式调整 |
|||
@media (max-width: 768px) { |
|||
.mobile-form { |
|||
.mobile-form-section { |
|||
padding: 12px; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.mobile-plan-item-card { |
|||
.mobile-card-header { |
|||
padding: 12px; |
|||
} |
|||
|
|||
.mobile-card-content { |
|||
padding: 12px; |
|||
} |
|||
} |
|||
|
|||
.mobile-form-row { |
|||
margin-bottom: 12px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,92 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="100px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="任务名称" prop="ipName"> |
|||
<el-input v-model="formData.ipName" placeholder="请输入任务名称" /> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { PlanApi, Plan } from '@/api/isolation/plan' |
|||
|
|||
/** 检修任务 表单 */ |
|||
defineOptions({ name: 'PlanForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
ipName: undefined |
|||
}) |
|||
const formRules = reactive({ |
|||
ipName: [{ required: true, message: '计划名称不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await PlanApi.getPlan(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as Plan |
|||
if (formType.value === 'create') { |
|||
await PlanApi.createPlan(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await PlanApi.updatePlan(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
ipName: undefined |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,228 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="68px" |
|||
> |
|||
<el-form-item label="任务名称" prop="ipName"> |
|||
<el-input |
|||
v-model="queryParams.ipName" |
|||
placeholder="请输入任务名称" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['isolation:plan:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 添加检修任务 |
|||
</el-button> |
|||
<!-- |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['isolation:plan:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['isolation:plan:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> --> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="任务名称" align="center" prop="ipName" /> |
|||
<el-table-column label="任务状态" align="center" prop="status"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_PLAN_STATUS" :value="scope.row.status" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="创建时间" |
|||
align="center" |
|||
prop="createTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['isolation:plan:update']" |
|||
> |
|||
查看任务 |
|||
</el-button> |
|||
<!-- <el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['isolation:plan:delete']" |
|||
> |
|||
删除 |
|||
</el-button> --> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<PlanForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { PlanApi, Plan } from '@/api/isolation/plan' |
|||
import PlanForm from './PlanForm.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE } from '@/utils/dict' |
|||
|
|||
/** 检修任务 列表 */ |
|||
defineOptions({ name: 'Plan' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
const elLockStore = useElLockStore() |
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<Plan[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
ipName: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await PlanApi.getPlanPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await PlanApi.deletePlan(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除检修任务 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await PlanApi.deletePlanList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: Plan[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await PlanApi.exportPlan(queryParams) |
|||
download.excel(data, '检修任务.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,188 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="120px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="检修任务" prop="isolationPlanId"> |
|||
<el-select v-model="formData.isolationPlanId" placeholder="请选择检修任务" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.isolationPlans" |
|||
:key="item.id" |
|||
:label="item.ipName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离指导书" prop="guideId"> |
|||
<el-select v-model="formData.guideId" placeholder="请选择隔离指导书" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.lockGuides" |
|||
:key="item.id" |
|||
:label="item.guideContent" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌人" prop="operatorId"> |
|||
<el-select v-model="formData.operatorId" placeholder="请选择集中挂牌人" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌协助人" prop="operatorHelperId"> |
|||
<el-select |
|||
v-model="formData.operatorHelperId" |
|||
placeholder="请选择集中挂牌协助人" |
|||
filterable |
|||
clearable |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证人" prop="verifierId"> |
|||
<el-select v-model="formData.verifierId" placeholder="请选择验证人" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证协助人" prop="verifierHelperId"> |
|||
<el-select |
|||
v-model="formData.verifierHelperId" |
|||
placeholder="请选择验证协助人" |
|||
filterable |
|||
clearable |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="状态" prop="status"> |
|||
<el-select v-model="formData.status" placeholder="请选择状态"> |
|||
<el-option |
|||
v-for="item in getIntDictOptions(DICT_TYPE.LOCK_PLAN_ITEM_STATUS)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { PlanItemApi, PlanItem } from '@/api/isolation/planitem' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 检修任务子项 表单 */ |
|||
defineOptions({ name: 'PlanItemForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
isolationPlanId: undefined, |
|||
guideId: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined, |
|||
status: 0 |
|||
}) |
|||
const formRules = reactive({ |
|||
isolationPlanId: [{ required: true, message: '检修任务不能为空', trigger: 'blur' }], |
|||
guideId: [{ required: true, message: '隔离指导书不能为空', trigger: 'blur' }], |
|||
operatorId: [{ required: true, message: '集中挂牌人不能为空', trigger: 'blur' }], |
|||
verifierId: [{ required: true, message: '验证人不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await PlanItemApi.getPlanItem(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as PlanItem |
|||
if (formType.value === 'create') { |
|||
await PlanItemApi.createPlanItem(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await PlanItemApi.updatePlanItem(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
isolationPlanId: undefined, |
|||
guideId: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined, |
|||
status: 0 |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,343 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="120px" |
|||
> |
|||
<el-form-item label="检修任务" prop="isolationPlanId"> |
|||
<el-select |
|||
v-model="queryParams.isolationPlanId" |
|||
placeholder="请选择检修任务" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.isolationPlans" |
|||
:key="item.id" |
|||
:label="item.ipName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离指导书" prop="guideId"> |
|||
<el-select |
|||
v-model="queryParams.guideId" |
|||
placeholder="请选择隔离指导书" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.lockGuides" |
|||
:key="item.id" |
|||
:label="item.guideContent" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌人" prop="operatorId"> |
|||
<el-select |
|||
v-model="queryParams.operatorId" |
|||
placeholder="请选择集中挂牌人" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="集中挂牌协助人" prop="operatorHelperId"> |
|||
<el-select |
|||
v-model="queryParams.operatorHelperId" |
|||
placeholder="请选择集中挂牌协助人" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证人" prop="verifierId"> |
|||
<el-select |
|||
v-model="queryParams.verifierId" |
|||
placeholder="请选择验证人" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="验证协助人" prop="verifierHelperId"> |
|||
<el-select |
|||
v-model="queryParams.verifierHelperId" |
|||
placeholder="请选择验证协助人" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="子项状态" prop="status"> |
|||
<el-select |
|||
v-model="queryParams.status" |
|||
placeholder="请选择子项状态" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in getDictOptions(DICT_TYPE.LOCK_PLAN_ITEM_STATUS)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['isolation:plan-item:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['isolation:plan-item:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['isolation:plan-item:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="检修任务" align="center" prop="isolationPlanId" /> |
|||
<el-table-column label="隔离指导书" align="center" prop="guideId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.lockGuides.find((item) => item.id === scope.row.guideId)?.guideContent }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="子项状态" align="center" prop="status"> |
|||
<template #default="scope"> |
|||
{{ getDictLabel(DICT_TYPE.LOCK_PLAN_ITEM_STATUS, scope.row.status) }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="创建时间" |
|||
align="center" |
|||
prop="createTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['isolation:plan-item:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['isolation:plan-item:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<PlanItemForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { PlanItemApi, PlanItem } from '@/api/isolation/planitem' |
|||
import PlanItemForm from './PlanItemForm.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE, getDictLabel, getDictOptions } from '@/utils/dict' |
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 检修任务子项 列表 */ |
|||
defineOptions({ name: 'PlanItem' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
|
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<PlanItem[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
isolationPlanId: undefined, |
|||
guideId: undefined, |
|||
operatorId: undefined, |
|||
operatorHelperId: undefined, |
|||
verifierId: undefined, |
|||
verifierHelperId: undefined, |
|||
status: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await PlanItemApi.getPlanItemPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await PlanItemApi.deletePlanItem(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除检修任务子项 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await PlanItemApi.deletePlanItemList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: PlanItem[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await PlanItemApi.exportPlanItem(queryParams) |
|||
download.excel(data, '检修任务子项.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,144 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="140px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="检修任务子项" prop="isolationPlanItemId"> |
|||
<el-select |
|||
v-model="formData.isolationPlanItemId" |
|||
placeholder="请选择检修任务子项" |
|||
filterable |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.planItems" |
|||
:key="item.id" |
|||
:label="item.id" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点" prop="isolationPointId"> |
|||
<el-select v-model="formData.isolationPointId" placeholder="请选择隔离点" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.isolationPoints" |
|||
:key="item.id" |
|||
:label="item.ipName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="电子锁" prop="lockId"> |
|||
<el-select v-model="formData.lockId" placeholder="请选择电子锁" filterable> |
|||
<el-option |
|||
v-for="item in elLockStore.locks" |
|||
:key="item.id" |
|||
:label="item.lockName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="状态" prop="lockStatus"> |
|||
<el-radio-group v-model="formData.lockStatus"> |
|||
<el-radio |
|||
v-for="item in getDictOptions(DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { PlanItemDetailApi, PlanItemDetail } from '@/api/isolation/planitemdetail' |
|||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 检修任务子项详情 表单 */ |
|||
defineOptions({ name: 'PlanItemDetailForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
isolationPlanItemId: undefined, |
|||
isolationPointId: undefined, |
|||
lockId: undefined, |
|||
lockStatus: 0 |
|||
}) |
|||
const formRules = reactive({ |
|||
isolationPlanItemId: [{ required: true, message: '检修任务子项不能为空', trigger: 'blur' }], |
|||
isolationPointId: [{ required: true, message: '隔离点不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await PlanItemDetailApi.getPlanItemDetail(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as PlanItemDetail |
|||
if (formType.value === 'create') { |
|||
await PlanItemDetailApi.createPlanItemDetail(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await PlanItemDetailApi.updatePlanItemDetail(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
isolationPlanItemId: undefined, |
|||
isolationPointId: undefined, |
|||
lockId: undefined, |
|||
lockStatus: 0 |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,294 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="68px" |
|||
> |
|||
<el-form-item label="隔离点" prop="isolationPointId"> |
|||
<el-select |
|||
v-model="queryParams.isolationPointId" |
|||
placeholder="请选择隔离点" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.isolationPoints" |
|||
:key="item.id" |
|||
:label="item.ipName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="电子锁" prop="lockId"> |
|||
<el-select |
|||
v-model="queryParams.lockId" |
|||
placeholder="请选择电子锁" |
|||
clearable |
|||
filterable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.locks" |
|||
:key="item.id" |
|||
:label="item.lockName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="锁状态" prop="lockStatus"> |
|||
<el-select |
|||
v-model="queryParams.lockStatus" |
|||
placeholder="请选择锁状态" |
|||
clearable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in getDictOptions(DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['isolation:plan-item-detail:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['isolation:plan-item-detail:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['isolation:plan-item-detail:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="任务名称" align="center"> |
|||
<template #default="scope"> |
|||
{{ getPlanName(scope.row.isolationPlanItemId) }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="隔离点" align="center" prop="isolationPointId"> |
|||
<template #default="scope"> |
|||
{{ |
|||
elLockStore.isolationPoints.find((item) => item.id === scope.row.isolationPointId) |
|||
?.ipName |
|||
}} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="电子锁" align="center" prop="lockId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.locks.find((item) => item.id === scope.row.lockId)?.lockName }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="任务状态" align="center" prop="lockStatus"> |
|||
<template #default="scope"> |
|||
{{ getDictLabel(DICT_TYPE.LOCK_PLAN_ITEM_DETAIL_STATUS, scope.row.lockStatus) }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="创建时间" |
|||
align="center" |
|||
prop="createTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['isolation:plan-item-detail:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['isolation:plan-item-detail:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<PlanItemDetailForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { PlanItemDetailApi, PlanItemDetail } from '@/api/isolation/planitemdetail' |
|||
import PlanItemDetailForm from './PlanItemDetailForm.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE, getDictLabel, getDictOptions } from '@/utils/dict' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 检修任务子项详情 列表 */ |
|||
defineOptions({ name: 'PlanItemDetail' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
|
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<PlanItemDetail[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
isolationPlanItemId: undefined, |
|||
isolationPointId: undefined, |
|||
lockId: undefined, |
|||
lockStatus: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await PlanItemDetailApi.getPlanItemDetailPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
const getPlanName = (id: number) => { |
|||
let planItem = elLockStore.planItems.find((item) => item.id === id) |
|||
let plan = elLockStore.isolationPlans.find((item) => item.id === planItem?.isolationPlanId) |
|||
return plan?.ipName |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await PlanItemDetailApi.deletePlanItemDetail(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除检修任务子项详情 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await PlanItemDetailApi.deletePlanItemDetailList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: PlanItemDetail[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await PlanItemDetailApi.exportPlanItemDetail(queryParams) |
|||
download.excel(data, '检修任务子项详情.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,162 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="100px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="子项详情" prop="isolationPlanItemDetailId"> |
|||
<el-select v-model="formData.isolationPlanItemDetailId" placeholder="请选择子项详情"> |
|||
<el-option |
|||
v-for="item in elLockStore.planItemDetails" |
|||
:key="item.id" |
|||
:label="item.id" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="上锁人" prop="userId"> |
|||
<el-select v-model="formData.userId" placeholder="请选择上锁人"> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="生命锁类型" prop="lockType"> |
|||
<el-select v-model="formData.lockType" placeholder="请选择生命锁类型"> |
|||
<el-option |
|||
v-for="item in getDictOptions(DICT_TYPE.LOCK_LIFE_LOCK_TYPE)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="锁定状态" prop="lockStatus"> |
|||
<el-radio-group v-model="formData.lockStatus"> |
|||
<el-radio |
|||
v-for="item in getDictOptions(DICT_TYPE.LOCK_LIFE_LOCK_STATUS)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
<el-form-item label="上锁时间" prop="lockTime"> |
|||
<el-date-picker |
|||
v-model="formData.lockTime" |
|||
type="date" |
|||
value-format="x" |
|||
placeholder="选择上锁时间" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="解锁时间" prop="unlockTime"> |
|||
<el-date-picker |
|||
v-model="formData.unlockTime" |
|||
type="date" |
|||
value-format="x" |
|||
placeholder="选择解锁时间" |
|||
/> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { PlanLifeLockApi, PlanLifeLock } from '@/api/isolation/planlifelock' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 个人生命锁 表单 */ |
|||
defineOptions({ name: 'PlanLifeLockForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
isolationPlanItemDetailId: undefined, |
|||
userId: undefined, |
|||
lockType: undefined, |
|||
lockStatus: undefined, |
|||
lockTime: undefined, |
|||
unlockTime: undefined |
|||
}) |
|||
const formRules = reactive({ |
|||
isolationPlanItemDetailId: [{ required: true, message: '子项详情ID不能为空', trigger: 'blur' }], |
|||
userId: [{ required: true, message: '上锁人ID不能为空', trigger: 'blur' }], |
|||
lockType: [{ required: true, message: '生命锁类型不能为空', trigger: 'change' }], |
|||
lockStatus: [{ required: true, message: '锁定状态: 0=未上锁, 1=已上锁不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await PlanLifeLockApi.getPlanLifeLock(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as PlanLifeLock |
|||
if (formType.value === 'create') { |
|||
await PlanLifeLockApi.createPlanLifeLock(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await PlanLifeLockApi.updatePlanLifeLock(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
isolationPlanItemDetailId: undefined, |
|||
userId: undefined, |
|||
lockType: undefined, |
|||
lockStatus: undefined, |
|||
lockTime: undefined, |
|||
unlockTime: undefined |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,305 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="90px" |
|||
> |
|||
<el-form-item label="上锁人" prop="userId"> |
|||
<el-select |
|||
v-model="queryParams.userId" |
|||
placeholder="请选择上锁人" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="生命锁类型" prop="lockType"> |
|||
<el-select |
|||
v-model="queryParams.lockType" |
|||
placeholder="请选择生命锁类型" |
|||
clearable |
|||
class="!w-240px" |
|||
> |
|||
<el-option label="请选择字典生成" value="" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="锁定状态" prop="lockStatus"> |
|||
<el-select |
|||
v-model="queryParams.lockStatus" |
|||
placeholder="请选择锁定状态" |
|||
clearable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in getDictOptions(DICT_TYPE.LOCK_LIFE_LOCK_STATUS)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="上锁时间" prop="lockTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.lockTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="解锁时间" prop="unlockTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.unlockTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="创建时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<!-- <el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['isolation:plan-life-lock:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['isolation:plan-life-lock:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['isolation:plan-life-lock:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> --> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="上锁人" align="center" prop="userId"> |
|||
<template #default="scope"> |
|||
{{ elLockStore.users.find((item) => item.id === scope.row.userId)?.nickname }} |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="生命锁类型" align="center" prop="lockType"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_LIFE_LOCK_TYPE" :value="scope.row.lockType" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="锁定状态" align="center" prop="lockStatus"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_LIFE_LOCK_STATUS" :value="scope.row.lockStatus" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="上锁时间" |
|||
align="center" |
|||
prop="lockTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column |
|||
label="解锁时间" |
|||
align="center" |
|||
prop="unlockTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<!-- <el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['isolation:plan-life-lock:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['isolation:plan-life-lock:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> --> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<PlanLifeLockForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { PlanLifeLockApi, PlanLifeLock } from '@/api/isolation/planlifelock' |
|||
import PlanLifeLockForm from './PlanLifeLockForm.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE, getDictLabel, getDictOptions } from '@/utils/dict' |
|||
|
|||
const elLockStore = useElLockStore() |
|||
|
|||
/** 个人生命锁 列表 */ |
|||
defineOptions({ name: 'PlanLifeLock' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
|
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<PlanLifeLock[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
isolationPlanItemDetailId: undefined, |
|||
userId: undefined, |
|||
lockType: undefined, |
|||
lockStatus: undefined, |
|||
lockTime: [], |
|||
unlockTime: [], |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await PlanLifeLockApi.getPlanLifeLockPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await PlanLifeLockApi.deletePlanLifeLock(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除个人生命锁 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await PlanLifeLockApi.deletePlanLifeLockList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: PlanLifeLock[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await PlanLifeLockApi.exportPlanLifeLock(queryParams) |
|||
download.excel(data, '个人生命锁.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,125 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading"> |
|||
<el-form-item label="隔离点类型" prop="ipType"> |
|||
<el-select v-model="formData.ipType" placeholder="请选择隔离点类型"> |
|||
<el-option |
|||
v-for="item in getIntDictOptions(DICT_TYPE.LOCK_ISOLATION_TYPE)" :key="item.value" |
|||
:label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点名称" prop="ipName"> |
|||
<el-input v-model="formData.ipName" placeholder="请输入隔离点名称" /> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点位置" prop="ipLocation"> |
|||
<el-input v-model="formData.ipLocation" placeholder="请输入隔离点位置" /> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点编号" prop="ipNumber"> |
|||
<el-input v-model="formData.ipNumber" placeholder="请输入隔离点编号" /> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点状态" prop="status"> |
|||
<el-select v-model="formData.status" placeholder="请选择隔离点状态" disabled> |
|||
<el-option |
|||
v-for="item in getIntDictOptions(DICT_TYPE.LOCK_ISOLATION_POINT_STATUS)" :key="item.value" |
|||
:label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="电子锁数量" prop="guideLockNums"> |
|||
<el-input-number |
|||
v-model="formData.guideLockNums" :precision="0" :step="1" :min="1" :max="1" |
|||
placeholder="请输入电子锁数量" /> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { PointApi, Point } from '@/api/isolation/point' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
|
|||
/** 隔离点 表单 */ |
|||
defineOptions({ name: 'PointForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
ipType: undefined, |
|||
ipName: undefined, |
|||
ipLocation: undefined, |
|||
ipNumber: undefined, |
|||
status: 0, |
|||
guideLockNums: 1 |
|||
}) |
|||
const formRules = reactive({ |
|||
ipType: [{ required: true, message: '隔离点类型不能为空', trigger: 'change' }], |
|||
ipName: [{ required: true, message: '隔离点名称不能为空', trigger: 'blur' }], |
|||
ipNumber: [{ required: true, message: '隔离点编号不能为空', trigger: 'blur' }], |
|||
guideLockNums: [{ required: true, message: '电子锁数量不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await PointApi.getPoint(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as Point |
|||
if (formType.value === 'create') { |
|||
await PointApi.createPoint(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await PointApi.updatePoint(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
ipType: undefined, |
|||
ipName: undefined, |
|||
ipLocation: undefined, |
|||
ipNumber: undefined, |
|||
status: 0, |
|||
guideLockNums: 1 |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,254 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="140px" |
|||
> |
|||
<el-form-item label="隔离点类型" prop="ipType"> |
|||
<el-select |
|||
v-model="queryParams.ipType" |
|||
placeholder="请选择隔离点类型" |
|||
clearable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in getIntDictOptions(DICT_TYPE.LOCK_ISOLATION_TYPE)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点名称" prop="ipName"> |
|||
<el-input |
|||
v-model="queryParams.ipName" |
|||
placeholder="请输入隔离点名称" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点位置" prop="ipLocation"> |
|||
<el-input |
|||
v-model="queryParams.ipLocation" |
|||
placeholder="请输入隔离点位置" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="隔离点编号" prop="ipNumber"> |
|||
<el-input |
|||
v-model="queryParams.ipNumber" |
|||
placeholder="请输入隔离点编号" |
|||
clearable |
|||
@keyup.enter="handleQuery" |
|||
class="!w-240px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 搜索 </el-button> |
|||
<el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['isolation:point:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['isolation:point:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['isolation:point:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="隔离点类型" align="center" prop="ipType" /> |
|||
<el-table-column label="隔离点名称" align="center" prop="ipName" /> |
|||
<el-table-column label="隔离点位置" align="center" prop="ipLocation" /> |
|||
<el-table-column label="隔离点编号" align="center" prop="ipNumber" /> |
|||
<el-table-column label="隔离点状态" align="center" prop="status"> |
|||
<template #default="scope"> |
|||
<DictTag :type="DICT_TYPE.LOCK_ISOLATION_POINT_STATUS" :value="scope.row.status" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="锁数量" align="center" prop="guideLockNums" width="80px" /> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button link type="primary" @click="openForm('detail', scope.row.id)"> |
|||
查看 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['isolation:point:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['isolation:point:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<PointForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import download from '@/utils/download' |
|||
import { PointApi, Point } from '@/api/isolation/point' |
|||
import PointForm from './PointForm.vue' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
import { getDictLabel } from '@/utils/dict' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
|
|||
/** 隔离点 列表 */ |
|||
defineOptions({ name: 'Point' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
const elLockStore = useElLockStore() |
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<Point[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
ipType: undefined, |
|||
ipName: undefined, |
|||
ipLocation: undefined, |
|||
ipNumber: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await PointApi.getPointPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await PointApi.deletePoint(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除隔离点 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await PointApi.deletePointList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: Point[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await PointApi.exportPoint(queryParams) |
|||
download.excel(data, '隔离点.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,124 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading"> |
|||
<el-form-item label="编号" prop="lockNumber"> |
|||
<el-input v-model="formData.lockNumber" placeholder="请输入编号" /> |
|||
</el-form-item> |
|||
<el-form-item label="名称" prop="lockName"> |
|||
<el-input v-model="formData.lockName" placeholder="请输入名称" /> |
|||
</el-form-item> |
|||
<el-form-item label="锁具类型" prop="lockType"> |
|||
<el-select v-model="formData.lockType" placeholder="请选择锁具类型"> |
|||
<el-option v-for="item in getIntDictOptions(DICT_TYPE.LOCK_TYPE)" :key="item.value" :label="item.label" |
|||
:value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="状态" prop="lockStatus"> |
|||
<el-select v-model="formData.lockStatus" placeholder="请选择锁具状态" disabled> |
|||
<el-option v-for="item in getIntDictOptions(DICT_TYPE.LOCK_STATUS)" :key="item.value" :label="item.label" |
|||
:value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="启用状态" prop="lockEnableStatus"> |
|||
<el-radio-group v-model="formData.lockEnableStatus" :disabled="formData.lockStatus != 2"> |
|||
<el-radio v-for="item in getIntDictOptions(DICT_TYPE.LOCK_ENABLE_STATUS)" :key="item.value" |
|||
:value="item.value"> |
|||
{{ item.label }} |
|||
</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
<!-- <el-form-item label="蓝牙标识" prop="lockBluetoothId"> |
|||
<el-input v-model="formData.lockBluetoothId" placeholder="请输入蓝牙ID" /> |
|||
</el-form-item> --> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { LockApi, Lock } from '@/api/electron/lock' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
/** 电子锁 表单 */ |
|||
defineOptions({ name: 'LockForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
lockNumber: undefined, |
|||
lockName: undefined, |
|||
lockStatus: 2, |
|||
lockType: undefined, |
|||
lockEnableStatus: 1, |
|||
lockBluetoothId: undefined |
|||
}) |
|||
const formRules = reactive({ |
|||
lockNumber: [{ required: true, message: '编号不能为空', trigger: 'blur' }], |
|||
lockName: [{ required: true, message: '名称不能为空', trigger: 'blur' }], |
|||
lockEnableStatus: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
|
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await LockApi.getLock(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as Lock |
|||
if (formType.value === 'create') { |
|||
await LockApi.createLock(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await LockApi.updateLock(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
lockNumber: undefined, |
|||
lockName: undefined, |
|||
lockStatus: 2, |
|||
lockType: undefined, |
|||
lockEnableStatus: 1, |
|||
lockBluetoothId: undefined |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,208 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> |
|||
<el-form-item label="编号" prop="lockNumber"> |
|||
<el-input v-model="queryParams.lockNumber" placeholder="请输入编号" clearable @keyup.enter="handleQuery" |
|||
class="!w-240px" /> |
|||
</el-form-item> |
|||
<el-form-item label="名称" prop="lockName"> |
|||
<el-input v-model="queryParams.lockName" placeholder="请输入名称" clearable @keyup.enter="handleQuery" |
|||
class="!w-240px" /> |
|||
</el-form-item> |
|||
<el-form-item label="状态" prop="lockStatus"> |
|||
<el-select v-model="queryParams.lockStatus" placeholder="请选择状态" clearable class="!w-240px"> |
|||
<el-option v-for="item in getIntDictOptions(DICT_TYPE.LOCK_STATUS)" :key="item.value" :label="item.label" |
|||
:value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="锁具类型" prop="lockType"> |
|||
<el-select v-model="queryParams.lockType" placeholder="请选择锁具类型" clearable class="!w-240px"> |
|||
<el-option v-for="item in getIntDictOptions(DICT_TYPE.LOCK_TYPE)" :key="item.value" :label="item.label" |
|||
:value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="启用状态" prop="lockEnableStatus"> |
|||
<el-select v-model="queryParams.lockEnableStatus" placeholder="请选择启用状态" clearable class="!w-240px"> |
|||
<el-option v-for="item in getIntDictOptions(DICT_TYPE.LOCK_ENABLE_STATUS)" :key="item.value" |
|||
:label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"> |
|||
<Icon icon="ep:search" class="mr-5px" /> 搜索 |
|||
</el-button> |
|||
<el-button @click="resetQuery"> |
|||
<Icon icon="ep:refresh" class="mr-5px" /> 重置 |
|||
</el-button> |
|||
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['electron:lock:create']"> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button type="success" plain @click="handleExport" :loading="exportLoading" |
|||
v-hasPermi="['electron:lock:export']"> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button type="danger" plain :disabled="isEmpty(checkedIds)" @click="handleDeleteBatch" |
|||
v-hasPermi="['electron:lock:delete']"> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table row-key="id" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange"> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="编号" align="center" prop="lockNumber" /> |
|||
<el-table-column label="名称" align="center" prop="lockName" /> |
|||
<el-table-column label="状态" align="center" prop="lockStatus"> |
|||
<template #default="scope"> |
|||
<dict-tag :type="DICT_TYPE.LOCK_STATUS" :value="scope.row.lockStatus" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="锁具类型" align="center" prop="lockType"> |
|||
<template #default="scope"> |
|||
<dict-tag :type="DICT_TYPE.LOCK_TYPE" :value="scope.row.lockType" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="启用状态" align="center" prop="lockEnableStatus"> |
|||
<template #default="scope"> |
|||
<dict-tag :type="DICT_TYPE.LOCK_ENABLE_STATUS" :value="scope.row.lockEnableStatus" /> |
|||
</template> |
|||
</el-table-column> |
|||
<!-- <el-table-column label="上次充电时间" align="center" prop="lockLastChargeTime" :formatter="dateFormatter" |
|||
width="180px" /> --> |
|||
<!-- <el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" /> --> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button link type="primary" @click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['electron:lock:update']"> |
|||
编辑 |
|||
</el-button> |
|||
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['electron:lock:delete']"> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" /> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<LockForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { LockApi, Lock } from '@/api/electron/lock' |
|||
import LockForm from './LockForm.vue' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
|
|||
/** 电子锁 列表 */ |
|||
defineOptions({ name: 'Lock' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
const elLockStore = useElLockStore() |
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<Lock[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
lockNumber: undefined, |
|||
lockName: undefined, |
|||
lockStatus: undefined, |
|||
lockType: undefined, |
|||
lockEnableStatus: undefined |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
await elLockStore.init() |
|||
const data = await LockApi.getLockPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await LockApi.deleteLock(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch { } |
|||
} |
|||
|
|||
/** 批量删除电子锁 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await LockApi.deleteLockList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch { } |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: Lock[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await LockApi.exportLock(queryParams) |
|||
download.excel(data, '电子锁.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
@ -0,0 +1,85 @@ |
|||
<template> |
|||
<el-table :data="activedPoint" class="w-full" border> |
|||
<el-table-column label="隔离点名称" align="center" prop="ipName"> |
|||
<template #default="scope"> |
|||
<el-link type="primary" @click="handleDetail(scope)" :underline="false"> |
|||
{{ scope.row.ipName }} |
|||
</el-link> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column |
|||
v-for="point in activePlan" |
|||
align="center" |
|||
:key="point.id" |
|||
:label="point.ipName" |
|||
:prop="'' + point.id" |
|||
> |
|||
<template #default="scope"> |
|||
<el-link |
|||
type="primary" |
|||
@click="handleDetail(scope)" |
|||
:underline="false" |
|||
v-if="getDetailStatus(scope).total > 0" |
|||
> |
|||
<span class="text-green-500 pr-1"> {{ getDetailStatus(scope).completed }} </span> |
|||
<span class="text-gray-500">/ {{ getDetailStatus(scope).total }} </span> |
|||
</el-link> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<DetailPointModal |
|||
:isolation-point-id="selectedCell.isolationPointId" |
|||
:isolation-plan-id="selectedCell.isolationPlanId" |
|||
ref="detailPointModalRef" |
|||
/> |
|||
</template> |
|||
<script lang="ts" setup> |
|||
import { nextTick } from 'vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import DetailPointModal from '@/components/Lock/DetailPointModal.vue' |
|||
defineOptions({ name: 'LockChart' }) |
|||
const elLockStore = useElLockStore() |
|||
let activedPoint = elLockStore.isolationPoints.filter((item) => item.status == 1) |
|||
let activePlan = elLockStore.isolationPlans.filter((item) => item.status == 0) |
|||
const getDetailStatus = ({ row, column }) => |
|||
elLockStore.planItemDetails |
|||
.filter((detail) => { |
|||
if (detail.isolationPointId != row.id) { |
|||
return false |
|||
} |
|||
let item = elLockStore.planItems.find((item) => item.id == detail.isolationPlanItemId) |
|||
if (item && item.isolationPlanId == column.property) { |
|||
return true |
|||
} |
|||
}) |
|||
.reduce( |
|||
(acc, cur) => { |
|||
acc.total++ |
|||
if (cur.lockStatus == 5) { |
|||
acc.completed++ |
|||
} |
|||
return acc |
|||
}, |
|||
{ |
|||
total: 0, |
|||
completed: 0 |
|||
} |
|||
) |
|||
const detailPointModalRef = ref() |
|||
const selectedCell = ref<{ isolationPointId?: number; isolationPlanId?: number }>({ |
|||
isolationPointId: undefined, |
|||
isolationPlanId: undefined |
|||
}) |
|||
const handleDetail = ({ row, column }) => { |
|||
selectedCell.value.isolationPointId = row.id |
|||
if (column.property != 'ipName') { |
|||
selectedCell.value.isolationPlanId = +column.property |
|||
} else { |
|||
selectedCell.value.isolationPlanId = undefined |
|||
} |
|||
// 调用detailPanel的show方法显示弹窗 |
|||
nextTick(() => { |
|||
detailPointModalRef.value?.show() |
|||
}) |
|||
} |
|||
</script> |
@ -0,0 +1,171 @@ |
|||
<template> |
|||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="formData" |
|||
:rules="formRules" |
|||
label-width="100px" |
|||
v-loading="formLoading" |
|||
> |
|||
<el-form-item label="操作人" prop="operatorId"> |
|||
<el-select v-model="formData.operatorId" placeholder="请选择操作人"> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="电子锁" prop="lockId"> |
|||
<el-select v-model="formData.lockId" placeholder="请选择电子锁"> |
|||
<el-option |
|||
v-for="item in elLockStore.locks" |
|||
:key="item.id" |
|||
:label="item.lockName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="子项详情" prop="isolationPlanItemDetailId"> |
|||
<el-input v-model="formData.isolationPlanItemDetailId" placeholder="请选择关联的子项详情" /> |
|||
</el-form-item> |
|||
<el-form-item label="记录类型" prop="recordType"> |
|||
<el-select v-model="formData.recordType" placeholder="请选择记录类型"> |
|||
<el-option |
|||
v-for="item in getIntDictOptions(DICT_TYPE.RECORD_TYPE)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="操作签名" prop="signaturePath"> |
|||
<UploadFile |
|||
v-model="formData.signaturePath" |
|||
:file-type="['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']" |
|||
:limit="1" |
|||
:file-size="100" |
|||
class="min-w-80px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="操作前照片" prop="beforePhotoPath"> |
|||
<UploadFile |
|||
v-model="formData.beforePhotoPath" |
|||
:file-type="['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']" |
|||
:limit="1" |
|||
:file-size="100" |
|||
class="min-w-80px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="操作后照片" prop="afterPhotoPath"> |
|||
<UploadFile |
|||
v-model="formData.afterPhotoPath" |
|||
:file-type="['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']" |
|||
:limit="1" |
|||
:file-size="100" |
|||
class="min-w-80px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="操作GPS坐标" prop="gpsCoordinates"> |
|||
<el-input v-model="formData.gpsCoordinates" placeholder="请输入操作GPS坐标" /> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|||
<el-button @click="dialogVisible = false">取 消</el-button> |
|||
</template> |
|||
</Dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { LockWorkRecordApi, LockWorkRecord } from '@/api/electron/lockworkcord' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import UploadFile from '@/components/UploadFile/src/UploadFile.vue' |
|||
|
|||
/** 电子锁操作记录 表单 */ |
|||
defineOptions({ name: 'LockWorkRecordForm' }) |
|||
|
|||
const { t } = useI18n() // 国际化 |
|||
const message = useMessage() // 消息弹窗 |
|||
const elLockStore = useElLockStore() // 电子锁 store |
|||
|
|||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|||
const dialogTitle = ref('') // 弹窗的标题 |
|||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|||
const formData = ref({ |
|||
id: undefined, |
|||
operatorId: undefined, |
|||
lockId: undefined, |
|||
isolationPlanItemDetailId: undefined, |
|||
recordType: undefined, |
|||
signaturePath: '', |
|||
beforePhotoPath: '', |
|||
afterPhotoPath: '', |
|||
gpsCoordinates: undefined |
|||
}) |
|||
const formRules = reactive({ |
|||
operatorId: [{ required: true, message: '操作人不能为空', trigger: 'blur' }], |
|||
lockId: [{ required: true, message: '电子锁不能为空', trigger: 'blur' }], |
|||
recordType: [{ required: true, message: '记录类型不能为空', trigger: 'change' }] |
|||
}) |
|||
const formRef = ref() // 表单 Ref |
|||
/** 打开弹窗 */ |
|||
const open = async (type: string, id?: number) => { |
|||
dialogVisible.value = true |
|||
dialogTitle.value = t('action.' + type) |
|||
formType.value = type |
|||
resetForm() |
|||
// 修改时,设置数据 |
|||
if (id) { |
|||
formLoading.value = true |
|||
try { |
|||
formData.value = await LockWorkRecordApi.getLockWorkRecord(id) |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
} |
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|||
|
|||
/** 提交表单 */ |
|||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|||
const submitForm = async () => { |
|||
// 校验表单 |
|||
await formRef.value.validate() |
|||
// 提交请求 |
|||
formLoading.value = true |
|||
try { |
|||
const data = formData.value as unknown as LockWorkRecord |
|||
if (formType.value === 'create') { |
|||
await LockWorkRecordApi.createLockWorkRecord(data) |
|||
message.success(t('common.createSuccess')) |
|||
} else { |
|||
await LockWorkRecordApi.updateLockWorkRecord(data) |
|||
message.success(t('common.updateSuccess')) |
|||
} |
|||
dialogVisible.value = false |
|||
// 发送操作成功的事件 |
|||
emit('success') |
|||
} finally { |
|||
formLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 重置表单 */ |
|||
const resetForm = () => { |
|||
formData.value = { |
|||
id: undefined, |
|||
operatorId: undefined, |
|||
lockId: undefined, |
|||
isolationPlanItemDetailId: undefined, |
|||
recordType: undefined, |
|||
signaturePath: '', |
|||
beforePhotoPath: '', |
|||
afterPhotoPath: '', |
|||
gpsCoordinates: undefined |
|||
} |
|||
formRef.value?.resetFields() |
|||
} |
|||
</script> |
@ -0,0 +1,296 @@ |
|||
<template> |
|||
<ContentWrap> |
|||
<!-- 搜索工作栏 --> |
|||
<el-form |
|||
class="-mb-15px" |
|||
:model="queryParams" |
|||
ref="queryFormRef" |
|||
:inline="true" |
|||
label-width="68px" |
|||
> |
|||
<el-form-item label="操作人" prop="operatorId"> |
|||
<el-select |
|||
style="width: 240px" |
|||
v-model="queryParams.operatorId" |
|||
placeholder="请选择操作人" |
|||
clearable |
|||
filterable |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.users" |
|||
:key="item.id" |
|||
:label="item.nickname" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="电子锁" prop="lockId"> |
|||
<el-select |
|||
style="width: 240px" |
|||
v-model="queryParams.lockId" |
|||
placeholder="请选择电子锁" |
|||
clearable |
|||
filterable |
|||
> |
|||
<el-option |
|||
v-for="item in elLockStore.locks" |
|||
:key="item.id" |
|||
:label="item.lockName" |
|||
:value="item.id" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="记录类型" prop="recordType"> |
|||
<el-select |
|||
v-model="queryParams.recordType" |
|||
placeholder="请选择记录类型" |
|||
clearable |
|||
class="!w-240px" |
|||
> |
|||
<el-option |
|||
v-for="item in getIntDictOptions(DICT_TYPE.RECORD_TYPE)" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="记录时间" prop="createTime"> |
|||
<el-date-picker |
|||
v-model="queryParams.createTime" |
|||
value-format="YYYY-MM-DD HH:mm:ss" |
|||
type="daterange" |
|||
start-placeholder="开始日期" |
|||
end-placeholder="结束日期" |
|||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
|||
class="!w-220px" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
|||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
|||
<el-button |
|||
type="primary" |
|||
plain |
|||
@click="openForm('create')" |
|||
v-hasPermi="['electron:lock-word-record:create']" |
|||
> |
|||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
|||
</el-button> |
|||
<el-button |
|||
type="success" |
|||
plain |
|||
@click="handleExport" |
|||
:loading="exportLoading" |
|||
v-hasPermi="['electron:lock-word-record:export']" |
|||
> |
|||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
|||
</el-button> |
|||
<el-button |
|||
type="danger" |
|||
plain |
|||
:disabled="isEmpty(checkedIds)" |
|||
@click="handleDeleteBatch" |
|||
v-hasPermi="['electron:lock-word-record:delete']" |
|||
> |
|||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</ContentWrap> |
|||
|
|||
<!-- 列表 --> |
|||
<ContentWrap> |
|||
<el-table |
|||
row-key="id" |
|||
v-loading="loading" |
|||
:data="list" |
|||
:stripe="true" |
|||
:show-overflow-tooltip="true" |
|||
@selection-change="handleRowCheckboxChange" |
|||
> |
|||
<el-table-column type="selection" width="55" /> |
|||
<el-table-column label="操作人" align="center" prop="operatorId"> |
|||
<template #default="scope"> |
|||
<el-tag>{{ |
|||
elLockStore.users.find((item) => item.id === scope.row.operatorId)?.nickname |
|||
}}</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="电子锁" align="center" prop="lockId"> |
|||
<template #default="scope"> |
|||
<el-tag>{{ |
|||
elLockStore.locks.find((item) => item.id === scope.row.lockId)?.lockName |
|||
}}</el-tag> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="记录类型" align="center" prop="recordType"> |
|||
<template #default="scope"> |
|||
<dict-tag :type="DICT_TYPE.RECORD_TYPE" :value="scope.row.recordType" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作签名" align="center" prop="signaturePath"> |
|||
<template #default="scope"> |
|||
<el-image v-if="scope.row.signaturePath" :src="scope.row.signaturePath" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作前照片" align="center" prop="beforePhotoPath"> |
|||
<template #default="scope"> |
|||
<el-image v-if="scope.row.beforePhotoPath" :src="scope.row.beforePhotoPath" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作后照片" align="center" prop="afterPhotoPath"> |
|||
<template #default="scope"> |
|||
<el-image v-if="scope.row.afterPhotoPath" :src="scope.row.afterPhotoPath" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="GPS坐标" align="center" prop="gpsCoordinates" /> |
|||
<el-table-column |
|||
label="记录时间" |
|||
align="center" |
|||
prop="createTime" |
|||
:formatter="dateFormatter" |
|||
width="180px" |
|||
/> |
|||
<el-table-column label="操作" align="center" min-width="120px"> |
|||
<template #default="scope"> |
|||
<el-button |
|||
link |
|||
type="primary" |
|||
@click="openForm('update', scope.row.id)" |
|||
v-hasPermi="['electron:lock-word-record:update']" |
|||
> |
|||
编辑 |
|||
</el-button> |
|||
<el-button |
|||
link |
|||
type="danger" |
|||
@click="handleDelete(scope.row.id)" |
|||
v-hasPermi="['electron:lock-word-record:delete']" |
|||
> |
|||
删除 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!-- 分页 --> |
|||
<Pagination |
|||
:total="total" |
|||
v-model:page="queryParams.pageNo" |
|||
v-model:limit="queryParams.pageSize" |
|||
@pagination="getList" |
|||
/> |
|||
</ContentWrap> |
|||
|
|||
<!-- 表单弹窗:添加/修改 --> |
|||
<LockWorkRecordForm ref="formRef" @success="getList" /> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { isEmpty } from '@/utils/is' |
|||
import { dateFormatter } from '@/utils/formatTime' |
|||
import download from '@/utils/download' |
|||
import { LockWorkRecordApi, LockWorkRecord } from '@/api/electron/lockworkcord' |
|||
import LockWorkRecordForm from './LockWorkRecordForm.vue' |
|||
import { useElLockStore } from '@/store/modules/elLock' |
|||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
|||
|
|||
/** 电子锁操作记录 列表 */ |
|||
defineOptions({ name: 'LockWorkRecord' }) |
|||
|
|||
const message = useMessage() // 消息弹窗 |
|||
const { t } = useI18n() // 国际化 |
|||
const elLockStore = useElLockStore() |
|||
const loading = ref(true) // 列表的加载中 |
|||
const list = ref<LockWorkRecord[]>([]) // 列表的数据 |
|||
const total = ref(0) // 列表的总页数 |
|||
const queryParams = reactive({ |
|||
pageNo: 1, |
|||
pageSize: 10, |
|||
operatorId: undefined, |
|||
lockId: undefined, |
|||
recordType: undefined, |
|||
createTime: [] |
|||
}) |
|||
const queryFormRef = ref() // 搜索的表单 |
|||
const exportLoading = ref(false) // 导出的加载中 |
|||
|
|||
/** 查询列表 */ |
|||
const getList = async () => { |
|||
loading.value = true |
|||
try { |
|||
const data = await LockWorkRecordApi.getLockWorkRecordPage(queryParams) |
|||
list.value = data.list |
|||
total.value = data.total |
|||
} finally { |
|||
loading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.pageNo = 1 |
|||
getList() |
|||
} |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value.resetFields() |
|||
handleQuery() |
|||
} |
|||
|
|||
/** 添加/修改操作 */ |
|||
const formRef = ref() |
|||
const openForm = (type: string, id?: number) => { |
|||
formRef.value.open(type, id) |
|||
} |
|||
|
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (id: number) => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
// 发起删除 |
|||
await LockWorkRecordApi.deleteLockWorkRecord(id) |
|||
message.success(t('common.delSuccess')) |
|||
// 刷新列表 |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
/** 批量删除电子锁操作记录 */ |
|||
const handleDeleteBatch = async () => { |
|||
try { |
|||
// 删除的二次确认 |
|||
await message.delConfirm() |
|||
await LockWorkRecordApi.deleteLockWorkRecordList(checkedIds.value) |
|||
message.success(t('common.delSuccess')) |
|||
await getList() |
|||
} catch {} |
|||
} |
|||
|
|||
const checkedIds = ref<number[]>([]) |
|||
const handleRowCheckboxChange = (records: LockWorkRecord[]) => { |
|||
checkedIds.value = records.map((item) => item.id) |
|||
} |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = async () => { |
|||
try { |
|||
// 导出的二次确认 |
|||
await message.exportConfirm() |
|||
// 发起导出 |
|||
exportLoading.value = true |
|||
const data = await LockWorkRecordApi.exportLockWorkRecord(queryParams) |
|||
download.excel(data, '电子锁操作记录.xls') |
|||
} catch { |
|||
} finally { |
|||
exportLoading.value = false |
|||
} |
|||
} |
|||
|
|||
/** 初始化 **/ |
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |