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.
486 lines
12 KiB
486 lines
12 KiB
<script lang="ts" setup>
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { Badge, Card, Empty, Modal, Tabs } from 'ant-design-vue'
|
|
|
|
import { BasicTable, TableAction, useTable } from '@/components/Table'
|
|
import { useMessage } from '@/hooks/web/useMessage'
|
|
|
|
interface InterfaceInfo {
|
|
mp_id?: string[]
|
|
}
|
|
|
|
interface InterfaceRow {
|
|
ID: number
|
|
block_description?: string
|
|
Description?: string
|
|
BLOCK_Name?: string
|
|
point_id?: string
|
|
instant_Num?: number | string
|
|
Info?: InterfaceInfo
|
|
COMPOUND?: string
|
|
CP_HOST?: string
|
|
Used?: number | string
|
|
is_update?: number | string
|
|
}
|
|
|
|
interface StatusMeta {
|
|
key: 'done' | 'pending' | 'uncovered' | 'update'
|
|
label: string
|
|
color: string
|
|
}
|
|
|
|
const { createMessage } = useMessage()
|
|
const route = useRoute()
|
|
const query = ref({
|
|
unitId: undefined as undefined | string,
|
|
typeName: undefined as undefined | string,
|
|
keyword: ''
|
|
})
|
|
|
|
const statusFilter = ref<'all' | StatusMeta['key']>('all')
|
|
|
|
const AModal = Modal
|
|
const ACard = Card
|
|
const ATabs = Tabs
|
|
const ATabPane = Tabs.TabPane
|
|
const AEmpty = Empty
|
|
|
|
const unitTitle = computed(() => {
|
|
const unitId = route.query.unitid ? decodeURIComponent(String(route.query.unitid)) : ''
|
|
return unitId ? `#${unitId}号机智能高级应用至DCS通讯接口` : '接口管理'
|
|
})
|
|
|
|
const allRows = ref<InterfaceRow[]>([])
|
|
const selectedRows = ref<InterfaceRow[]>([])
|
|
|
|
const statusList: StatusMeta[] = [
|
|
{ key: 'done', label: '已完成', color: '#fa8c16' },
|
|
{ key: 'pending', label: '未完成', color: '#1677ff' },
|
|
{ key: 'uncovered', label: '未覆盖', color: '#ff4d4f' },
|
|
{ key: 'update', label: '版本更新', color: '#52c41a' },
|
|
]
|
|
|
|
const columns = [
|
|
{ title: '序号', dataIndex: 'ID', key: 'id', width: 90 },
|
|
{ title: 'DCS目标描述', dataIndex: 'block_description', key: 'block_description', minWidth: 240 },
|
|
{ title: 'EXA描述', dataIndex: 'Description', key: 'Description', minWidth: 240 },
|
|
{ title: '模块名称', dataIndex: 'BLOCK_Name', key: 'BLOCK_Name', minWidth: 140 },
|
|
{ title: '点号', dataIndex: 'point_id', key: 'point_id', minWidth: 140 },
|
|
{ title: '实例数量', dataIndex: 'instant_Num', key: 'instant_Num', width: 100 },
|
|
{ title: '实例id', dataIndex: 'Info', key: 'instanceId', minWidth: 160 },
|
|
{ title: 'COMPOUND', dataIndex: 'COMPOUND', key: 'COMPOUND', minWidth: 120 },
|
|
{ title: 'CP_HOST', dataIndex: 'CP_HOST', key: 'CP_HOST', minWidth: 220 },
|
|
{ title: '操作', dataIndex: 'action', key: 'action', width: 240, fixed: 'right' },
|
|
]
|
|
|
|
const [registerTable, { setTableData, clearSelectedRowKeys, setSelectedRowKeys, getDataSource }] = useTable({
|
|
title: '接口列表',
|
|
columns,
|
|
rowKey: 'ID',
|
|
showTableSetting: true,
|
|
showIndexColumn: false,
|
|
pagination: {
|
|
pageSize: 15,
|
|
},
|
|
rowSelection: {
|
|
type: 'checkbox',
|
|
onChange: (_keys, rows: InterfaceRow[]) => {
|
|
selectedRows.value = rows
|
|
},
|
|
},
|
|
})
|
|
|
|
function toNumber(value: string | number | undefined) {
|
|
const num = Number(value)
|
|
return Number.isNaN(num) ? 0 : num
|
|
}
|
|
|
|
function getRowStatus(record: InterfaceRow): StatusMeta {
|
|
const isUpdate = toNumber(record.is_update) === 1
|
|
if (isUpdate)
|
|
return statusList[3]
|
|
|
|
if (toNumber(record.Used) === 1)
|
|
return statusList[0]
|
|
|
|
if (toNumber(record.instant_Num) > 0)
|
|
return statusList[1]
|
|
|
|
return statusList[2]
|
|
}
|
|
|
|
const statusCounts = computed(() => {
|
|
const result = {
|
|
done: 0,
|
|
pending: 0,
|
|
uncovered: 0,
|
|
update: 0,
|
|
}
|
|
|
|
allRows.value.forEach((row) => {
|
|
const status = getRowStatus(row)
|
|
result[status.key] += 1
|
|
})
|
|
|
|
return result
|
|
})
|
|
|
|
function getInstanceIds(record: InterfaceRow) {
|
|
if (!record.Info?.mp_id || record.Info.mp_id.length === 0)
|
|
return '-'
|
|
return record.Info.mp_id.join(',')
|
|
}
|
|
|
|
function applyFilters() {
|
|
const keyword = query.value.keyword.trim()
|
|
let rows = [...allRows.value]
|
|
|
|
if (statusFilter.value !== 'all') {
|
|
rows = rows.filter(item => getRowStatus(item).key === statusFilter.value)
|
|
}
|
|
|
|
if (keyword) {
|
|
rows = rows.filter(item =>
|
|
`${item.point_id ?? ''}${item.block_description ?? ''}${item.Description ?? ''}`.includes(keyword),
|
|
)
|
|
}
|
|
|
|
setTableData(rows)
|
|
}
|
|
|
|
function handleSearch() {
|
|
applyFilters()
|
|
}
|
|
|
|
function handleResetSelection() {
|
|
selectedRows.value = []
|
|
clearSelectedRowKeys()
|
|
}
|
|
|
|
function handleSelectAll() {
|
|
const dataSource = getDataSource()
|
|
const keys = dataSource.map((item: InterfaceRow) => item.ID)
|
|
setSelectedRowKeys(keys)
|
|
}
|
|
|
|
const bindModalOpen = ref(false)
|
|
const detailModalOpen = ref(false)
|
|
const detailRecord = ref<InterfaceRow | null>(null)
|
|
|
|
function handleBatchBind() {
|
|
if (selectedRows.value.length === 0) {
|
|
createMessage.warning('请先勾选需要批量绑定的记录')
|
|
return
|
|
}
|
|
bindModalOpen.value = true
|
|
}
|
|
|
|
function handleBatchReset() {
|
|
if (selectedRows.value.length === 0) {
|
|
createMessage.warning('请先勾选需要复位的记录')
|
|
return
|
|
}
|
|
createMessage.success('已提交批量复位请求')
|
|
}
|
|
|
|
function handleBindModel(record: InterfaceRow) {
|
|
bindModalOpen.value = true
|
|
selectedRows.value = [record]
|
|
}
|
|
|
|
function handleDetail(record: InterfaceRow) {
|
|
detailRecord.value = record
|
|
detailModalOpen.value = true
|
|
}
|
|
|
|
function handleReset(record: InterfaceRow) {
|
|
createMessage.success(`已提交${record.ID}复位请求`)
|
|
}
|
|
|
|
function handleConfirm(record: InterfaceRow) {
|
|
createMessage.success(`已提交${record.ID}确认请求`)
|
|
}
|
|
|
|
function handleStatusClick(key: StatusMeta['key']) {
|
|
statusFilter.value = key
|
|
applyFilters()
|
|
}
|
|
|
|
function handleStatusReset() {
|
|
statusFilter.value = 'all'
|
|
applyFilters()
|
|
}
|
|
|
|
onMounted(() => {
|
|
setTableData(allRows.value)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="interface-page">
|
|
<ACard class="interface-title-card">
|
|
<div class="interface-title">{{ unitTitle }}</div>
|
|
</ACard>
|
|
|
|
<ACard class="interface-legend-card">
|
|
<div class="interface-legend">
|
|
<Badge
|
|
:class="statusCounts.done === 0 ? 'runningStatus' : 'runningStatus alarm'"
|
|
:color="statusList[0].color"
|
|
text="已完成"
|
|
@click="handleStatusClick('done')"
|
|
/>
|
|
<Badge
|
|
:class="statusCounts.pending === 0 ? 'runningStatus' : 'runningStatus alarm'"
|
|
:color="statusList[1].color"
|
|
text="未完成"
|
|
@click="handleStatusClick('pending')"
|
|
/>
|
|
<Badge
|
|
:class="statusCounts.uncovered === 0 ? 'runningStatus' : 'runningStatus alarm'"
|
|
:color="statusList[2].color"
|
|
text="未覆盖"
|
|
@click="handleStatusClick('uncovered')"
|
|
/>
|
|
<Badge
|
|
:class="statusCounts.update === 0 ? 'runningStatus' : 'runningStatus alarm'"
|
|
:color="statusList[3].color"
|
|
text="版本更新"
|
|
@click="handleStatusClick('update')"
|
|
/>
|
|
</div>
|
|
</ACard>
|
|
|
|
<ACard class="interface-filter-card">
|
|
<div class="filter-row">
|
|
<a-select
|
|
v-model:value="query.unitId"
|
|
class="filter-item"
|
|
allow-clear
|
|
placeholder="2号机组"
|
|
:options="[]"
|
|
/>
|
|
<a-select
|
|
v-model:value="query.typeName"
|
|
class="filter-item"
|
|
allow-clear
|
|
placeholder="测点级"
|
|
:options="[]"
|
|
/>
|
|
<a-input
|
|
v-model:value="query.keyword"
|
|
class="filter-input"
|
|
placeholder="点号/描述"
|
|
@press-enter="handleSearch"
|
|
/>
|
|
<div class="filter-actions">
|
|
<a-button type="primary" @click="handleSearch">搜索</a-button>
|
|
<a-button @click="handleBatchBind">批量绑定</a-button>
|
|
<a-button @click="handleBatchReset">批量复位</a-button>
|
|
</div>
|
|
</div>
|
|
</ACard>
|
|
|
|
<ACard class="interface-table-card">
|
|
<BasicTable @register="registerTable">
|
|
<template #title>
|
|
<div class="table-title-bar">
|
|
<div class="table-title-left">
|
|
<a-button size="small" type="primary" @click="handleSelectAll">全选</a-button>
|
|
<a-button size="small" class="ml-2" @click="handleResetSelection">取消全选</a-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #bodyCell="{ column, record }">
|
|
<template v-if="column.key === 'id'">
|
|
<span class="status-dot" :style="{ backgroundColor: getRowStatus(record).color }" />
|
|
{{ record.ID }}
|
|
</template>
|
|
|
|
<template v-else-if="column.key === 'instanceId'">
|
|
{{ getInstanceIds(record) }}
|
|
</template>
|
|
|
|
<template v-else-if="column.key === 'action'">
|
|
<TableAction
|
|
:actions="[
|
|
{
|
|
label: '绑定模型测点',
|
|
onClick: handleBindModel.bind(null, record),
|
|
},
|
|
{
|
|
label: '详情',
|
|
onClick: handleDetail.bind(null, record),
|
|
},
|
|
{
|
|
label: '复位',
|
|
ifShow: toNumber(record.is_update) !== 1,
|
|
onClick: handleReset.bind(null, record),
|
|
},
|
|
{
|
|
label: '确定',
|
|
ifShow: toNumber(record.is_update) === 1,
|
|
onClick: handleConfirm.bind(null, record),
|
|
},
|
|
]"
|
|
/>
|
|
</template>
|
|
</template>
|
|
</BasicTable>
|
|
</ACard>
|
|
|
|
<AModal v-model:open="bindModalOpen" title="批量绑定" width="900px" :footer="null">
|
|
<div class="modal-section">
|
|
<div class="modal-toolbar">
|
|
<span>已选择 {{ selectedRows.length }} 条记录</span>
|
|
<a-button type="link" @click="handleResetSelection">清空选择</a-button>
|
|
</div>
|
|
<ATabs>
|
|
<ATabPane key="rule" tab="规则类测点">
|
|
<AEmpty description="暂无规则类测点数据" />
|
|
</ATabPane>
|
|
<ATabPane key="model" tab="模型测点">
|
|
<AEmpty description="暂无模型测点数据" />
|
|
</ATabPane>
|
|
<ATabPane key="raw" tab="原始测点">
|
|
<AEmpty description="暂无原始测点数据" />
|
|
</ATabPane>
|
|
</ATabs>
|
|
</div>
|
|
</AModal>
|
|
|
|
<AModal v-model:open="detailModalOpen" title="详情" width="900px" :footer="null">
|
|
<div class="detail-panel">
|
|
<div class="detail-row">
|
|
<span class="detail-label">模块名称:</span>
|
|
<span>{{ detailRecord?.BLOCK_Name || '-' }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">参数名称:</span>
|
|
<span>{{ detailRecord?.Description || '-' }}</span>
|
|
</div>
|
|
<AEmpty description="暂无实例详情数据" class="detail-empty" />
|
|
</div>
|
|
</AModal>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.interface-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.interface-title-card {
|
|
.interface-title {
|
|
text-align: center;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #111827;
|
|
}
|
|
}
|
|
|
|
.interface-legend-card {
|
|
.interface-legend {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 18px;
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
|
|
.interface-page :deep(.ant-card-body) {
|
|
padding: 12px 16px;
|
|
}
|
|
|
|
.interface-filter-card {
|
|
.filter-row {
|
|
display: grid;
|
|
grid-template-columns: 220px 220px minmax(240px, 1fr) auto;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.filter-item {
|
|
width: 100%;
|
|
}
|
|
|
|
.filter-input {
|
|
width: 100%;
|
|
}
|
|
|
|
.filter-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
.interface-table-card {
|
|
:deep(.ant-table-title) {
|
|
padding: 0 0 8px;
|
|
}
|
|
}
|
|
|
|
.table-title-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.modal-section {
|
|
.modal-toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
}
|
|
|
|
.detail-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.detail-label {
|
|
color: #6b7280;
|
|
}
|
|
|
|
.detail-empty {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.status-dot {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
margin-right: 6px;
|
|
}
|
|
|
|
:deep(.alarm .ant-badge-status-dot) {
|
|
animation: flash 1s linear infinite;
|
|
}
|
|
|
|
@keyframes flash {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.runningStatus {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|
|
|