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

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