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

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