Browse Source

feat: 更新模型信息接口,调整时间参数类型,优化数据表结构

pull/22/head
Jiale 6 months ago
parent
commit
8d14d6db91
  1. 2
      .vscode/settings.json
  2. 3
      package.json
  3. 38
      pnpm-lock.yaml
  4. 6
      src/api/alert/exa/index.ts
  5. 6
      src/api/alert/model/model/models.ts
  6. 4
      src/api/alert/model/models.ts
  7. 2
      src/utils/lib/echarts.ts
  8. 3
      src/views/model/list/step/Step3.vue
  9. 52
      src/views/model/train/data.tsx
  10. 565
      src/views/model/train/index.vue

2
.vscode/settings.json

@ -120,7 +120,7 @@
"source.organizeImports": "never",
"source.fixAll.stylelint": "explicit"
},
"editor.defaultFormatter": "stylelint.vscode-stylelint"
"editor.defaultFormatter": "octref.vetur"
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",

3
package.json

@ -59,7 +59,7 @@
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"echarts": "^5.4.3",
"echarts": "^5.6.0",
"lodash-es": "^4.17.21",
"moment": "^2.30.1",
"nprogress": "^0.2.0",
@ -74,6 +74,7 @@
"vditor": "^3.9.6",
"video.js": "^7.21.5",
"vue": "^3.3.8",
"vue-echarts": "^7.0.3",
"vue-i18n": "^9.6.5",
"vue-json-pretty": "^2.2.4",
"vue-router": "4.2.5",

38
pnpm-lock.yaml

@ -54,7 +54,7 @@ dependencies:
specifier: ^1.11.10
version: 1.11.13
echarts:
specifier: ^5.4.3
specifier: ^5.6.0
version: 5.6.0
lodash-es:
specifier: ^4.17.21
@ -98,6 +98,9 @@ dependencies:
vue:
specifier: ^3.3.8
version: 3.5.13(typescript@5.8.3)
vue-echarts:
specifier: ^7.0.3
version: 7.0.3(@vue/runtime-core@3.5.13)(echarts@5.6.0)(vue@3.5.13)
vue-i18n:
specifier: ^9.6.5
version: 9.14.4(vue@3.5.13)
@ -10046,6 +10049,21 @@ packages:
fsevents: 2.3.3
dev: true
/vue-demi@0.13.11(vue@3.5.13):
resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.5.13(typescript@5.8.3)
dev: false
/vue-demi@0.14.10(vue@3.5.13):
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
@ -10061,6 +10079,24 @@ packages:
vue: 3.5.13(typescript@5.8.3)
dev: false
/vue-echarts@7.0.3(@vue/runtime-core@3.5.13)(echarts@5.6.0)(vue@3.5.13):
resolution: {integrity: sha512-/jSxNwOsw5+dYAUcwSfkLwKPuzTQ0Cepz1LxCOpj2QcHrrmUa/Ql0eQqMmc1rTPQVrh2JQ29n2dhq75ZcHvRDw==}
peerDependencies:
'@vue/runtime-core': ^3.0.0
echarts: ^5.5.1
vue: ^2.7.0 || ^3.1.1
peerDependenciesMeta:
'@vue/runtime-core':
optional: true
dependencies:
'@vue/runtime-core': 3.5.13
echarts: 5.6.0
vue: 3.5.13(typescript@5.8.3)
vue-demi: 0.13.11(vue@3.5.13)
transitivePeerDependencies:
- '@vue/composition-api'
dev: false
/vue-eslint-parser@9.4.3(eslint@8.57.1):
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
engines: {node: ^14.17.0 || >=16.0.0}

6
src/api/alert/exa/index.ts

@ -8,8 +8,8 @@ export interface EXAPageReqVO extends PageParam {
export interface EXAHistoryReqVO {
itemName?: string
startTime?: Date
endTime?: Date
startTime?: string
endTime?: string
}
@ -77,5 +77,3 @@ export function deletePoint(ItemName: string) {
export function importTemplate() {
return defHttp.get({ url: '/alert/exa/get-import-template', responseType: 'blob' })
}

6
src/api/alert/model/model/models.ts

@ -17,14 +17,10 @@ export interface ModelInfo {
id: number
btmState: string
unitID: string
createName: string
creator: string
createTime: string
modelName: string
movingWindows: MovingWindows
steadyPoint: any[]
targetParameter: any
relationParameter: any[]
boundaryParameter: any[]
}
export interface ModelQueryParams {

4
src/api/alert/model/models.ts

@ -3,7 +3,7 @@ import { defHttp } from '@/utils/http/axios'
enum Api {
MODEL_CARD_LIST = '/alert/model/card/list',
MODEL_INFO = '/alert/model/info/',
MODEL_INFO = '/alert/model/info',
MODEL_SAVE = '/alert/model/',
MODEL_DATA = '/alert/model/data/',
CALCULATE_BACK = '/alert/model/data/calculate/',
@ -13,7 +13,7 @@ export function modelCardListApi(params?: ModelQueryParams) {
return defHttp.get<ModelCardItem[]>({ url: Api.MODEL_CARD_LIST, params })
}
export const modelInfoApi = (id: any) => defHttp.get<ModelInfo>({ url: Api.MODEL_INFO + id })
export const modelInfoApi = (id: any) => defHttp.get<ModelInfo>({ url: `${Api.MODEL_INFO}/${id}` })
export function updateModelInfo(params: any) {
return defHttp.patch<boolean>({ url: Api.MODEL_INFO, params })

2
src/utils/lib/echarts.ts

@ -4,6 +4,7 @@ import { BarChart, GaugeChart, LineChart, MapChart, PictorialBarChart, PieChart,
import {
AriaComponent,
BrushComponent,
CalendarComponent,
DataZoomComponent,
GraphicComponent,
@ -26,6 +27,7 @@ echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
BrushComponent,
PolarComponent,
AriaComponent,
ParallelComponent,

3
src/views/model/list/step/Step3.vue

@ -93,7 +93,7 @@ export default defineComponent({
const point = values[key].split('|')
const p = {
description: point[0],
targetPoint: point[1],
point: point[1],
unit: point[2],
upperlimit: point[3],
lowerlimit: point[4],
@ -102,6 +102,7 @@ export default defineComponent({
}
}
const modelInfo = toRaw(props.beforeData)
modelInfo.pointInfo = pointInfo
const modelId = await modelSaveApi(modelInfo)
setProps({
submitButtonOptions: {

52
src/views/model/train/data.tsx

@ -1,4 +1,4 @@
import { BasicColumn } from '/@/components/Table/src/types/table';
import type { BasicColumn } from '@/components/Table/src/types/table'
export const targetTableSchema: BasicColumn[] = [
{
@ -35,7 +35,7 @@ export const targetTableSchema: BasicColumn[] = [
width: 150,
dataIndex: 'realTimeValue',
},
];
]
export const boundaryTableSchema: BasicColumn[] = [
{
@ -73,25 +73,23 @@ export const boundaryTableSchema: BasicColumn[] = [
dataIndex: 'gridNumber',
edit: true,
},
];
]
export const relationTableSchema: BasicColumn[] = [
{
title: '描述',
width: 150,
dataIndex: 'description',
edit: true,
},
{
title: '点号',
width: 150,
dataIndex: 'targetPoint',
dataIndex: 'point',
},
{
title: '单位',
width: 150,
dataIndex: 'unit',
edit: true,
},
{
title: '上限 ',
@ -105,4 +103,44 @@ export const relationTableSchema: BasicColumn[] = [
dataIndex: 'lowerlimit',
edit: true,
},
];
]
export const sampleInfoTableSchema: BasicColumn[] = [
{
title: '开始时间',
width: 180,
dataIndex: 'st',
},
{
title: '结束时间',
width: 180,
dataIndex: 'et',
},
{
title: '时长(秒)',
width: 120,
dataIndex: 'duration',
},
{
title: '采样数量',
width: 120,
dataIndex: 'number',
},
{
title: '清洗样本数',
width: 120,
dataIndex: 'filter',
},
{
title: '有效样本数',
width: 120,
dataIndex: 'mode',
},
{
title: '操作',
width: 100,
dataIndex: 'action',
slots: { customRender: 'action' },
fixed: 'right',
},
]

565
src/views/model/train/index.vue

@ -1,24 +1,318 @@
<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 contentBackground>
<template #footer>
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="参数配置" />
<a-tab-pane key="2" tab="数据管理" />
<a-tab-pane key="3" tab="模型训练" />
<a-tab-pane key="4" tab="模型评估" />
</a-tabs>
</template>
<div v-show="activeKey === '1'">
<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>
<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?.createName }}</a-descriptions-item>
<a-descriptions-item label="创建时间"> {{ model?.createTime }} </a-descriptions-item>
<a-descriptions-item label="备注"> 暂无 </a-descriptions-item>
<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>
@ -26,206 +320,77 @@
<a-steps :current="1" progress-dot size="small">
<a-step title="参数配置">
<template #description>
<div> {{ model?.createName }}</div>
<p> {{ model?.createTime }}</p>
<div>{{ model?.creator }}</div>
<p>{{ model?.createTime }}</p>
</template>
</a-step>
<a-step title="生成数据">
<a-step title="训练模型">
<template #description>
<p>{{ model?.createTime }}</p>
</template>
</a-step>
<a-step title="训练模型" />
<a-step title="评估完成" />
<a-step title="运行监测" />
</a-steps>
</a-card>
<a-divider />
<a-card title="目标参数" :bordered="false"><BasicTable @register="targetTable" /></a-card>
<a-card title="相关参数" :bordered="false"><BasicTable @register="boundaryTable" /></a-card>
<a-card title="边界参数" :bordered="false"><BasicTable @register="relationTable" /></a-card>
</div>
<div v-show="activeKey === '2'">
<a-card title="模型数据">
<template #extra>
<a-button> 回算 </a-button>
</template>
<div class="grid md:grid-cols-2 gap-4">
<div
ref="chartRef1"
class="border border-gray-400"
style="width: 100%; height: 500px"
></div>
<div
ref="chartRef2"
class="border border-gray-400"
style="width: 100%; height: 500px"
></div>
<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>
<script lang="ts">
import { defineComponent, ref, Ref, onMounted, computed, watch, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { BasicTable, useTable } from '/@/components/Table';
import { PageWrapper } from '/@/components/Page';
import { Divider, Card, Descriptions, Steps, Tabs } from 'ant-design-vue';
import { targetTableSchema, relationTableSchema, boundaryTableSchema } from './data';
import { modelInfoApi } from '/@/api/benchmark/models';
import { ModelInfo } from '/@/api/benchmark/model/models';
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,
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
},
setup() {
const route = useRoute();
const id = route.params.id;
const fetchModelInfo = async () => {
const modelInfo = await modelInfoApi(id);
console.log(modelInfo);
return modelInfo;
};
const model = ref<ModelInfo | null>(null);
onMounted(async () => {
const info = await fetchModelInfo();
model.value = info;
});
const targetTableData = computed(() => {
return [model.value?.targetParameter || {}];
});
const relationTableData = computed(() => {
return model.value?.relationParameter || [];
});
const boundaryTableData = computed(() => {
return model.value?.boundaryParameter || [];
});
const [relationTable] = useTable({
columns: relationTableSchema,
pagination: false,
dataSource: relationTableData,
scroll: { y: 300 },
});
const [targetTable] = useTable({
columns: targetTableSchema,
pagination: false,
dataSource: targetTableData,
scroll: { y: 300 },
});
const [boundaryTable] = useTable({
columns: boundaryTableSchema,
pagination: false,
dataSource: boundaryTableData,
scroll: { y: 300 },
});
const activeKey = ref('1');
const chartRef1 = ref<HTMLDivElement | null>(null);
const chartRef2 = ref<HTMLDivElement | null>(null);
const { setOptions: setOptions1, resize: resize1 } = useECharts(
chartRef1 as Ref<HTMLDivElement>,
);
const { setOptions: setOptions2, resize: resize2 } = useECharts(
chartRef2 as Ref<HTMLDivElement>,
);
watch(activeKey, (newValue, _) => {
if (newValue === '2') {
console.log(activeKey);
nextTick(() => {
resize1();
resize2();
});
}
});
onMounted(() => {
setOptions1({
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
},
],
});
});
onMounted(() => {
setOptions2({
xAxis: {},
yAxis: {},
series: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.4, 6.81],
[10.0, 6.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12.0, 6.26],
[12.0, 8.84],
[7.08, 5.82],
[5.02, 5.68],
],
type: 'scatter',
},
],
});
});
return {
targetTable,
relationTable,
boundaryTable,
model,
activeKey,
chartRef1,
chartRef2,
};
},
});
</script>
<style>
.ant-card-head-title {
font-weight: bold;
}
.ant-card-head-title {
font-weight: bold;
}
.el-table .el-table__header th {
font-weight: bold;
}
.el-table .el-table__header th {
font-weight: bold;
}
</style>

Loading…
Cancel
Save