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.
396 lines
11 KiB
396 lines
11 KiB
<script lang="ts">
|
|
import type { ComponentPublicInstance } from 'vue'
|
|
import type { Dayjs } from 'dayjs'
|
|
import { debounce } from 'lodash-es'
|
|
import dayjs from 'dayjs'
|
|
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import {
|
|
Card,
|
|
Descriptions,
|
|
Divider,
|
|
Form,
|
|
FormItem,
|
|
RangePicker,
|
|
Steps,
|
|
Tabs,
|
|
} from 'ant-design-vue'
|
|
import VueECharts from 'vue-echarts'
|
|
import { relationTableSchema, sampleInfoTableSchema } from './data'
|
|
import { BasicTable, useTable } from '@/components/Table'
|
|
import { PageWrapper } from '@/components/Page'
|
|
import { modelInfoApi, updateModelInfo } from '@/api/alert/model/models'
|
|
import { getExaHistorys } from '@/api/alert/exa/index'
|
|
import { useECharts } from '@/hooks/web/useECharts'
|
|
|
|
export default defineComponent({
|
|
components: {
|
|
BasicTable,
|
|
PageWrapper,
|
|
[Divider.name]: Divider,
|
|
[Card.name]: Card,
|
|
[Descriptions.name]: Descriptions,
|
|
[Descriptions.Item.name]: Descriptions.Item,
|
|
[Steps.name]: Steps,
|
|
[Steps.Step.name]: Steps.Step,
|
|
ATabs: Tabs,
|
|
ATabPane: Tabs.TabPane,
|
|
ARangePicker: RangePicker,
|
|
AForm: Form,
|
|
AFormItem: FormItem,
|
|
VueECharts,
|
|
},
|
|
setup() {
|
|
const route = useRoute()
|
|
const id = route.params.id
|
|
const model = ref(null)
|
|
const brushActivated = ref<Set<number>>(new Set())
|
|
|
|
const fetchModelInfo = async () => {
|
|
const modelInfo = await modelInfoApi(id)
|
|
model.value = modelInfo
|
|
getHistory()
|
|
}
|
|
|
|
const pointData = computed(() => model.value?.pointInfo || [])
|
|
const [pointTable] = useTable({
|
|
columns: relationTableSchema,
|
|
pagination: false,
|
|
dataSource: pointData,
|
|
scroll: { y: 300 },
|
|
})
|
|
|
|
const trainTime = computed(() => model.value?.trainTime || [])
|
|
const [trainTimeTable] = useTable({
|
|
columns: sampleInfoTableSchema,
|
|
pagination: false,
|
|
dataSource: trainTime,
|
|
scroll: { y: 300 },
|
|
})
|
|
|
|
const activeKey = ref('1')
|
|
type RangeValue = [Dayjs, Dayjs]
|
|
const currentDate: Dayjs = dayjs()
|
|
const lastMonthDate: Dayjs = currentDate.subtract(1, 'day')
|
|
const rangeValue: RangeValue = [lastMonthDate, currentDate]
|
|
const historyTime = ref<RangeValue>(rangeValue)
|
|
const historyList = ref<any[]>([])
|
|
const echartsRefs = ref<any[]>([])
|
|
|
|
async function getHistory() {
|
|
if (!historyTime.value)
|
|
return
|
|
const params = {
|
|
startTime: historyTime.value[0].format('YYYY-MM-DD HH:mm:ss'),
|
|
endTime: historyTime.value[1].format('YYYY-MM-DD HH:mm:ss'),
|
|
itemName: model.value?.pointInfo.map(item => item.point).join(','),
|
|
}
|
|
console.log(params)
|
|
const history = await getExaHistorys(params)
|
|
historyList.value = history
|
|
echartsRefs.value = Array.from({ length: historyList.value.length })
|
|
console.log('echartsRefs', echartsRefs.value)
|
|
brushActivated.value = new Set()
|
|
}
|
|
|
|
function getOption(item: any) {
|
|
return {
|
|
xAxis: {
|
|
type: 'time',
|
|
},
|
|
yAxis: { type: 'value' },
|
|
series: [{ data: item, type: 'line', smooth: true, symbol: 'none' }],
|
|
dataZoom: [{}],
|
|
brush: {
|
|
toolbox: ['lineX'],
|
|
xAxisIndex: 0,
|
|
brushType: 'lineX',
|
|
},
|
|
}
|
|
}
|
|
|
|
function setChartRef(
|
|
el: Element | ComponentPublicInstance | null,
|
|
index: number,
|
|
) {
|
|
let dom: HTMLElement | null = null
|
|
if (el) {
|
|
if ('$el' in el && (el as any).$el instanceof HTMLElement)
|
|
dom = (el as any).$el
|
|
else if (el instanceof HTMLElement)
|
|
dom = el
|
|
}
|
|
if (dom)
|
|
useECharts(ref(dom as HTMLDivElement))
|
|
}
|
|
|
|
function setEchartsRef(el: any, index: number) {
|
|
if (el)
|
|
echartsRefs.value[index] = el
|
|
}
|
|
const isInitBrush = ref(true)
|
|
function onChartFinished(index: number) {
|
|
if (brushActivated.value.has(index))
|
|
return
|
|
const chart = echartsRefs.value[index]
|
|
if (!chart)
|
|
return
|
|
console.log('chart', index, chart)
|
|
chart.dispatchAction({
|
|
type: 'takeGlobalCursor',
|
|
key: 'brush',
|
|
brushOption: {
|
|
brushType: 'lineX',
|
|
brushMode: 'multiple',
|
|
},
|
|
})
|
|
const areas = (model.value?.trainTime || []).map(row => ({
|
|
brushType: 'lineX',
|
|
xAxisIndex: 0,
|
|
coordRange: [dayjs(row.st).valueOf(), dayjs(row.et).valueOf()],
|
|
}))
|
|
chart.dispatchAction({
|
|
type: 'brush',
|
|
areas,
|
|
})
|
|
brushActivated.value.add(index)
|
|
isInitBrush.value = true
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await fetchModelInfo()
|
|
})
|
|
const onBrushSelected = debounce((params) => {
|
|
if (isInitBrush.value) {
|
|
isInitBrush.value = false
|
|
return
|
|
}
|
|
console.log('brush selected:', params.batch)
|
|
const selected = params.batch[0].selected
|
|
if (selected.length > 0) {
|
|
const areas = mergeAreas(params.batch[0].areas).map(area => ({
|
|
brushType: area.brushType,
|
|
xAxisIndex: 0,
|
|
coordRange: area.coordRange,
|
|
}))
|
|
const trainTime = areas.map((area) => {
|
|
const [st, et] = area.coordRange
|
|
return {
|
|
st: dayjs(st).format('YYYY-MM-DD HH:mm:ss'),
|
|
et: dayjs(et).format('YYYY-MM-DD HH:mm:ss'),
|
|
duration: Math.round((et - st) / 1000),
|
|
number: '', // 采样数量(如有数据可补充)
|
|
filter: '', // 清洗样本数(如有数据可补充)
|
|
mode: '', // 有效样本数(如有数据可补充)
|
|
}
|
|
})
|
|
model.value.trainTime = trainTime
|
|
echartsRefs.value.forEach((chart, index) => {
|
|
if (chart) {
|
|
chart.dispatchAction({
|
|
type: 'brush',
|
|
areas,
|
|
})
|
|
isInitBrush.value = true
|
|
}
|
|
})
|
|
}
|
|
}, 300)
|
|
|
|
function mergeAreas(areas: any[]) {
|
|
if (!areas.length)
|
|
return []
|
|
// 只合并 brushType 和 xAxisIndex 相同的区间
|
|
const sorted = [...areas].sort(
|
|
(a, b) => a.coordRange[0] - b.coordRange[0],
|
|
)
|
|
const merged: any[] = []
|
|
for (const area of sorted) {
|
|
if (
|
|
merged.length
|
|
&& merged[merged.length - 1].brushType === area.brushType
|
|
&& merged[merged.length - 1].xAxisIndex === area.xAxisIndex
|
|
&& merged[merged.length - 1].coordRange[1] >= area.coordRange[0]
|
|
) {
|
|
// 有交集或相邻,合并
|
|
merged[merged.length - 1].coordRange[1] = Math.max(
|
|
merged[merged.length - 1].coordRange[1],
|
|
area.coordRange[1],
|
|
)
|
|
}
|
|
else {
|
|
// 新区间
|
|
merged.push({
|
|
brushType: area.brushType,
|
|
xAxisIndex: area.xAxisIndex,
|
|
coordRange: [...area.coordRange],
|
|
})
|
|
}
|
|
}
|
|
return merged
|
|
}
|
|
|
|
// 防抖更新
|
|
const updateModelInfoDebounced = debounce((val) => {
|
|
if (val && val.id)
|
|
updateModelInfo(val)
|
|
}, 500)
|
|
|
|
// 监听 model 变化
|
|
watch(
|
|
model,
|
|
(val) => {
|
|
updateModelInfoDebounced(val)
|
|
},
|
|
{ deep: true },
|
|
)
|
|
|
|
function handleDeleteTrainTime(index: number) {
|
|
if (!model.value?.trainTime)
|
|
return
|
|
// 赋值新数组,确保响应式
|
|
model.value.trainTime = [
|
|
...model.value.trainTime.slice(0, index),
|
|
...model.value.trainTime.slice(index + 1),
|
|
]
|
|
// 删除后同步更新所有图表的 brush 区域
|
|
const areas = (model.value.trainTime || []).map(row => ({
|
|
brushType: 'lineX',
|
|
xAxisIndex: 0,
|
|
coordRange: [dayjs(row.st).valueOf(), dayjs(row.et).valueOf()],
|
|
}))
|
|
echartsRefs.value.forEach((chart) => {
|
|
if (chart) {
|
|
chart.dispatchAction({
|
|
type: 'brush',
|
|
areas,
|
|
})
|
|
isInitBrush.value = true
|
|
}
|
|
})
|
|
}
|
|
|
|
return {
|
|
pointTable,
|
|
model,
|
|
activeKey,
|
|
historyTime,
|
|
historyList,
|
|
getHistory,
|
|
getOption,
|
|
setChartRef,
|
|
setEchartsRef,
|
|
onChartFinished,
|
|
echartsRefs,
|
|
onBrushSelected,
|
|
trainTimeTable,
|
|
handleDeleteTrainTime,
|
|
}
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<PageWrapper content-background>
|
|
<div>
|
|
<a-card title="模型信息" :bordered="false">
|
|
<template #extra>
|
|
<a-button> 下装 </a-button>
|
|
<a-button type="primary" style="margin-left: 6px">
|
|
修改模型
|
|
</a-button>
|
|
</template>
|
|
<a-descriptions size="small" :column="2" bordered>
|
|
<a-descriptions-item label="模型名称">
|
|
{{ model?.modelName }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="创建人">
|
|
{{ model?.creator }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="创建时间">
|
|
{{ model?.createTime }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="备注">
|
|
暂无
|
|
</a-descriptions-item>
|
|
</a-descriptions>
|
|
</a-card>
|
|
|
|
<a-card title="建模进度" :bordered="false">
|
|
<a-steps :current="1" progress-dot size="small">
|
|
<a-step title="参数配置">
|
|
<template #description>
|
|
<div>{{ model?.creator }}</div>
|
|
<p>{{ model?.createTime }}</p>
|
|
</template>
|
|
</a-step>
|
|
<a-step title="训练模型">
|
|
<template #description>
|
|
<p>{{ model?.createTime }}</p>
|
|
</template>
|
|
</a-step>
|
|
<a-step title="评估完成" />
|
|
<a-step title="运行监测" />
|
|
</a-steps>
|
|
</a-card>
|
|
|
|
<a-card :bordered="false">
|
|
<a-tabs v-model:active-key="activeKey">
|
|
<a-tab-pane key="1" tab="训练采样时间">
|
|
<BasicTable @register="trainTimeTable">
|
|
<template #action="{ record, index }">
|
|
<a-button
|
|
type="link"
|
|
danger
|
|
@click="handleDeleteTrainTime(index)"
|
|
>
|
|
删除
|
|
</a-button>
|
|
</template>
|
|
</BasicTable>
|
|
</a-tab-pane>
|
|
<a-tab-pane key="2" tab="测点参数">
|
|
<BasicTable @register="pointTable" />
|
|
</a-tab-pane>
|
|
</a-tabs>
|
|
</a-card>
|
|
|
|
<a-card title="智能训练" :bordered="false">
|
|
<a-form layout="inline">
|
|
<a-form-item label="模型预览时间范围">
|
|
<a-range-picker
|
|
v-model:value="historyTime"
|
|
show-time
|
|
@change="getHistory"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
<a-divider />
|
|
<div
|
|
v-for="(item, index) in historyList"
|
|
:key="index"
|
|
class="echart-box"
|
|
>
|
|
<VueECharts
|
|
:ref="(el) => setEchartsRef(el, index)"
|
|
:option="getOption(item)"
|
|
autoresize
|
|
style="width: 100%; height: 300px"
|
|
@finished="() => onChartFinished(index)"
|
|
@brush-selected="onBrushSelected"
|
|
/>
|
|
</div>
|
|
</a-card>
|
|
</div>
|
|
</PageWrapper>
|
|
</template>
|
|
|
|
<style>
|
|
.ant-card-head-title {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.el-table .el-table__header th {
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
|