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> |