2026-06-11 10:53:02 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<ProTable
|
|
|
|
|
|
ref="proTable"
|
|
|
|
|
|
row-key="taskId"
|
|
|
|
|
|
:columns="columns"
|
|
|
|
|
|
:request-api="getTableList"
|
2026-06-15 08:40:44 +08:00
|
|
|
|
:search-col="{ xs: 1, sm: 2, md: 2, lg: 5, xl: 5 }"
|
2026-06-11 10:53:02 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template #tableHeader>
|
2026-06-15 08:40:44 +08:00
|
|
|
|
<el-button type="primary" :icon="Plus" @click="emit('createTask')">新增</el-button>
|
2026-06-11 10:53:02 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<template #operation="{ row }">
|
2026-06-12 08:44:07 +08:00
|
|
|
|
<el-button type="danger" link :icon="Delete" @click="emit('delete', row)">删除</el-button>
|
2026-06-11 10:53:02 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</ProTable>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { computed, h, reactive, ref } from 'vue'
|
2026-06-12 08:44:07 +08:00
|
|
|
|
import { ElButton, ElDatePicker, ElTag, ElTreeSelect } from 'element-plus'
|
|
|
|
|
|
import { Delete, Plus } from '@element-plus/icons-vue'
|
2026-06-11 10:53:02 +08:00
|
|
|
|
import ProTable from '@/components/ProTable/index.vue'
|
|
|
|
|
|
import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
|
|
|
|
|
|
import type { SteadyDataView } from '@/api/steady/steadyDataView/interface'
|
|
|
|
|
|
import {
|
|
|
|
|
|
buildChecksquareTaskQueryParams,
|
2026-06-12 08:44:07 +08:00
|
|
|
|
formatChecksquareIntegrity,
|
2026-06-11 10:53:02 +08:00
|
|
|
|
formatChecksquareTaskStatus,
|
|
|
|
|
|
resolveChecksquareTaskStatusType,
|
|
|
|
|
|
resolveChecksquareText,
|
|
|
|
|
|
type ChecksquareTaskSearchParams
|
|
|
|
|
|
} from '../utils/checksquareTaskTable'
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
|
name: 'ChecksquareTaskTable'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
ledgerTree: SteadyDataView.SteadyLedgerNode[]
|
|
|
|
|
|
indicatorTree: SteadyDataView.SteadyIndicatorNode[]
|
|
|
|
|
|
requestApi: (params: SteadyDataView.SteadyChecksquareTaskQueryParams) => Promise<any>
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
createTask: []
|
|
|
|
|
|
detail: [row: SteadyDataView.SteadyChecksquareTask]
|
2026-06-12 08:44:07 +08:00
|
|
|
|
delete: [row: SteadyDataView.SteadyChecksquareTask]
|
|
|
|
|
|
viewMeasurementPoint: [row: SteadyDataView.SteadyChecksquareTask]
|
2026-06-11 10:53:02 +08:00
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const proTable = ref<ProTableInstance>()
|
|
|
|
|
|
|
|
|
|
|
|
interface ChecksquareFilterTreeNode {
|
|
|
|
|
|
label: string
|
|
|
|
|
|
value: string
|
|
|
|
|
|
disabled?: boolean
|
|
|
|
|
|
children?: ChecksquareFilterTreeNode[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeLineFilterTree = (nodes: SteadyDataView.SteadyLedgerNode[]): ChecksquareFilterTreeNode[] => {
|
|
|
|
|
|
return nodes.map(node => ({
|
|
|
|
|
|
label: node.name,
|
|
|
|
|
|
value: node.id,
|
|
|
|
|
|
disabled: node.level !== 3 || node.selectable === false,
|
|
|
|
|
|
children: node.children?.length ? normalizeLineFilterTree(node.children) : undefined
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeIndicatorFilterTree = (
|
|
|
|
|
|
nodes: SteadyDataView.SteadyIndicatorNode[],
|
|
|
|
|
|
parentKey = ''
|
|
|
|
|
|
): ChecksquareFilterTreeNode[] => {
|
|
|
|
|
|
return nodes.map((node, index) => {
|
|
|
|
|
|
const isLeaf = !node.children?.length
|
|
|
|
|
|
const value =
|
|
|
|
|
|
isLeaf && node.indicatorCode
|
|
|
|
|
|
? node.indicatorCode
|
|
|
|
|
|
: node.id || `${parentKey}${node.groupCode || node.name || 'node'}-${index}`
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: node.unit ? `${node.name}(${node.unit})` : node.name,
|
|
|
|
|
|
value,
|
|
|
|
|
|
disabled: !isLeaf || !node.indicatorCode,
|
|
|
|
|
|
children: node.children?.length ? normalizeIndicatorFilterTree(node.children, `${value}-`) : undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lineFilterTree = computed(() => normalizeLineFilterTree(props.ledgerTree))
|
|
|
|
|
|
const indicatorFilterTree = computed(() => normalizeIndicatorFilterTree(props.indicatorTree))
|
|
|
|
|
|
|
|
|
|
|
|
const splitTreeSelectValues = (value?: string) => {
|
|
|
|
|
|
return (value || '')
|
|
|
|
|
|
.split(',')
|
|
|
|
|
|
.map(item => item.trim())
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeTreeSelectValues = (value: unknown) => {
|
|
|
|
|
|
const rawValues = Array.isArray(value) ? value : value === undefined || value === null || value === '' ? [] : [value]
|
|
|
|
|
|
|
|
|
|
|
|
return Array.from(
|
|
|
|
|
|
new Set(
|
|
|
|
|
|
rawValues
|
|
|
|
|
|
.filter((item): item is string | number => typeof item === 'string' || typeof item === 'number')
|
|
|
|
|
|
.map(item => String(item).trim())
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const renderTimeRangeSearch = ({ searchParam }: { searchParam: ChecksquareTaskSearchParams }) =>
|
|
|
|
|
|
h(ElDatePicker, {
|
|
|
|
|
|
modelValue: searchParam.taskTimeRange,
|
|
|
|
|
|
type: 'datetimerange',
|
|
|
|
|
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
|
|
|
|
|
startPlaceholder: '开始时间',
|
|
|
|
|
|
endPlaceholder: '结束时间',
|
|
|
|
|
|
clearable: true,
|
|
|
|
|
|
'onUpdate:modelValue': (value: string[] | null) => {
|
|
|
|
|
|
searchParam.taskTimeRange = value || undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const renderLineSearch = ({ searchParam }: { searchParam: ChecksquareTaskSearchParams }) =>
|
|
|
|
|
|
h(ElTreeSelect, {
|
|
|
|
|
|
class: 'checksquare-search-tree-select',
|
|
|
|
|
|
style: { width: '100%' },
|
|
|
|
|
|
modelValue: splitTreeSelectValues(searchParam.lineId),
|
|
|
|
|
|
data: lineFilterTree.value,
|
|
|
|
|
|
nodeKey: 'value',
|
|
|
|
|
|
multiple: true,
|
|
|
|
|
|
showCheckbox: true,
|
|
|
|
|
|
collapseTags: true,
|
|
|
|
|
|
collapseTagsTooltip: true,
|
|
|
|
|
|
maxCollapseTags: 1,
|
|
|
|
|
|
filterable: true,
|
|
|
|
|
|
clearable: true,
|
|
|
|
|
|
defaultExpandAll: true,
|
|
|
|
|
|
checkStrictly: true,
|
|
|
|
|
|
props: { label: 'label', children: 'children', disabled: 'disabled' },
|
|
|
|
|
|
placeholder: '请选择监测点',
|
|
|
|
|
|
'onUpdate:modelValue': (value: unknown) => {
|
|
|
|
|
|
const selectedValues = normalizeTreeSelectValues(value)
|
|
|
|
|
|
searchParam.lineId = selectedValues.length ? selectedValues.join(',') : undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const renderIndicatorSearch = ({ searchParam }: { searchParam: ChecksquareTaskSearchParams }) =>
|
|
|
|
|
|
h(ElTreeSelect, {
|
|
|
|
|
|
class: 'checksquare-search-tree-select',
|
|
|
|
|
|
style: { width: '100%' },
|
|
|
|
|
|
modelValue: splitTreeSelectValues(searchParam.indicatorCode),
|
|
|
|
|
|
data: indicatorFilterTree.value,
|
|
|
|
|
|
nodeKey: 'value',
|
|
|
|
|
|
multiple: true,
|
|
|
|
|
|
showCheckbox: true,
|
|
|
|
|
|
collapseTags: true,
|
|
|
|
|
|
collapseTagsTooltip: true,
|
|
|
|
|
|
maxCollapseTags: 1,
|
|
|
|
|
|
filterable: true,
|
|
|
|
|
|
clearable: true,
|
|
|
|
|
|
defaultExpandAll: true,
|
|
|
|
|
|
checkStrictly: true,
|
|
|
|
|
|
props: { label: 'label', children: 'children', disabled: 'disabled' },
|
|
|
|
|
|
placeholder: '请选择稳态指标',
|
|
|
|
|
|
'onUpdate:modelValue': (value: unknown) => {
|
|
|
|
|
|
const selectedValues = normalizeTreeSelectValues(value)
|
|
|
|
|
|
searchParam.indicatorCode = selectedValues.length ? selectedValues.join(',') : undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const columns = reactive<ColumnProps<SteadyDataView.SteadyChecksquareTask>[]>([
|
|
|
|
|
|
{ type: 'index', fixed: 'left', width: 70, label: '序号' },
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'taskNo',
|
|
|
|
|
|
label: '任务编号',
|
|
|
|
|
|
minWidth: 180,
|
|
|
|
|
|
render: ({ row }) => resolveChecksquareText(row.taskNo)
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'lineId',
|
|
|
|
|
|
label: '监测点ID',
|
|
|
|
|
|
isShow: false,
|
|
|
|
|
|
isSetting: false,
|
|
|
|
|
|
search: {
|
|
|
|
|
|
label: '监测点',
|
|
|
|
|
|
order: 2,
|
|
|
|
|
|
render: renderLineSearch
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'lineName',
|
|
|
|
|
|
label: '监测点名称',
|
|
|
|
|
|
minWidth: 160,
|
2026-06-12 08:44:07 +08:00
|
|
|
|
render: ({ row }) =>
|
|
|
|
|
|
h(
|
|
|
|
|
|
ElButton,
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'primary',
|
|
|
|
|
|
link: true,
|
|
|
|
|
|
onClick: () => emit('viewMeasurementPoint', row)
|
|
|
|
|
|
},
|
|
|
|
|
|
() => resolveChecksquareText(row.lineName || row.lineId)
|
|
|
|
|
|
)
|
2026-06-11 10:53:02 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'indicatorCode',
|
|
|
|
|
|
label: '稳态指标',
|
|
|
|
|
|
isShow: false,
|
|
|
|
|
|
isSetting: false,
|
|
|
|
|
|
search: {
|
|
|
|
|
|
label: '稳态指标',
|
|
|
|
|
|
order: 3,
|
|
|
|
|
|
render: renderIndicatorSearch
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'timeStart',
|
|
|
|
|
|
label: '开始时间',
|
|
|
|
|
|
minWidth: 170,
|
|
|
|
|
|
render: ({ row }) => resolveChecksquareText(row.timeStart),
|
|
|
|
|
|
search: {
|
|
|
|
|
|
label: '检测时间',
|
|
|
|
|
|
key: 'taskTimeRange',
|
|
|
|
|
|
order: 1,
|
|
|
|
|
|
render: renderTimeRangeSearch
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'timeEnd',
|
|
|
|
|
|
label: '结束时间',
|
|
|
|
|
|
minWidth: 170,
|
|
|
|
|
|
render: ({ row }) => resolveChecksquareText(row.timeEnd)
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'taskStatus',
|
|
|
|
|
|
label: '任务状态',
|
|
|
|
|
|
minWidth: 110,
|
|
|
|
|
|
render: ({ row }) =>
|
|
|
|
|
|
h(
|
|
|
|
|
|
ElTag,
|
|
|
|
|
|
{ type: resolveChecksquareTaskStatusType(row.taskStatus), effect: 'plain' },
|
|
|
|
|
|
() => formatChecksquareTaskStatus(row.taskStatus)
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'itemCount',
|
|
|
|
|
|
label: '检测项数',
|
|
|
|
|
|
minWidth: 100,
|
|
|
|
|
|
align: 'center',
|
|
|
|
|
|
render: ({ row }) => resolveChecksquareText(row.itemCount)
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'abnormalItemCount',
|
|
|
|
|
|
label: '异常项数',
|
|
|
|
|
|
minWidth: 100,
|
|
|
|
|
|
align: 'center',
|
2026-06-12 08:44:07 +08:00
|
|
|
|
render: ({ row }) =>
|
|
|
|
|
|
h(
|
|
|
|
|
|
ElButton,
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'primary',
|
|
|
|
|
|
link: true,
|
|
|
|
|
|
onClick: () => emit('detail', row)
|
|
|
|
|
|
},
|
|
|
|
|
|
() => resolveChecksquareText(row.abnormalItemCount)
|
|
|
|
|
|
),
|
2026-06-11 10:53:02 +08:00
|
|
|
|
search: {
|
|
|
|
|
|
label: '异常状态',
|
|
|
|
|
|
key: 'hasAbnormal',
|
|
|
|
|
|
order: 4,
|
|
|
|
|
|
el: 'select'
|
|
|
|
|
|
},
|
|
|
|
|
|
enum: [
|
|
|
|
|
|
{ label: '存在异常', value: true },
|
|
|
|
|
|
{ label: '全部', value: false }
|
|
|
|
|
|
],
|
|
|
|
|
|
isFilterEnum: false
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-06-12 08:44:07 +08:00
|
|
|
|
prop: 'minDataIntegrity',
|
|
|
|
|
|
label: '最低完整性',
|
2026-06-11 10:53:02 +08:00
|
|
|
|
minWidth: 120,
|
|
|
|
|
|
align: 'center',
|
2026-06-15 08:40:44 +08:00
|
|
|
|
render: ({ row }) => formatChecksquareIntegrity(row.minDataIntegrity)
|
2026-06-11 10:53:02 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'createTime',
|
|
|
|
|
|
label: '创建时间',
|
|
|
|
|
|
minWidth: 170,
|
|
|
|
|
|
render: ({ row }) => resolveChecksquareText(row.createTime)
|
|
|
|
|
|
},
|
2026-06-12 08:44:07 +08:00
|
|
|
|
{ prop: 'operation', label: '操作', fixed: 'right', width: 150 }
|
2026-06-11 10:53:02 +08:00
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const getTableList = (params: ChecksquareTaskSearchParams) => {
|
|
|
|
|
|
return props.requestApi(buildChecksquareTaskQueryParams(params))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const refresh = () => {
|
|
|
|
|
|
proTable.value?.getTableList()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
refresh
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.checksquare-search-tree-select {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.checksquare-search-tree-select .el-select__wrapper) {
|
|
|
|
|
|
min-height: 32px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.checksquare-search-tree-select .el-select__selection) {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.checksquare-search-tree-select .el-select__tags-text) {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
max-width: 120px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
vertical-align: bottom;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|