You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
12 KiB
377 lines
12 KiB
<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>
|
|
|