Browse Source
feat(tabs): 添加标签页显示控制功能 新增getShowTabs计算属性来控制标签页的显示, 当路由元数据包含hideTab或路径为/interface、/run/interface时隐藏标签页 ```pull/134/head
3 changed files with 522 additions and 1 deletions
@ -0,0 +1,28 @@ |
|||||
|
import type { AppRouteModule } from '@/router/types' |
||||
|
|
||||
|
import { LAYOUT } from '@/router/constant' |
||||
|
|
||||
|
const run: AppRouteModule = { |
||||
|
path: '/run', |
||||
|
name: 'Run', |
||||
|
component: LAYOUT, |
||||
|
meta: { |
||||
|
orderNo: 30, |
||||
|
icon: 'ant-design:control-outlined', |
||||
|
title: '运行管理', |
||||
|
}, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: 'interface', |
||||
|
name: 'InterfaceManage', |
||||
|
component: () => import('@/views/run/interface/index.vue'), |
||||
|
meta: { |
||||
|
title: '接口管理', |
||||
|
icon: 'ant-design:api-outlined', |
||||
|
hideTab: true, |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
} |
||||
|
|
||||
|
export default run |
||||
@ -0,0 +1,486 @@ |
|||||
|
<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> |
||||
Loading…
Reference in new issue