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.
1414 lines
46 KiB
1414 lines
46 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, toRaw, watch } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import {
|
|
Button,
|
|
Card,
|
|
Checkbox,
|
|
Col,
|
|
Descriptions,
|
|
Divider,
|
|
Form,
|
|
FormItem,
|
|
Input,
|
|
InputNumber,
|
|
Modal,
|
|
RangePicker,
|
|
Row,
|
|
Select,
|
|
Space,
|
|
Spin,
|
|
Steps,
|
|
Table,
|
|
Tabs,
|
|
Transfer,
|
|
} from 'ant-design-vue'
|
|
import VueECharts from 'vue-echarts'
|
|
import { pointTableSchema, sampleInfoTableSchema } from './data'
|
|
import { BasicTable, useTable } from '@/components/Table'
|
|
import { PageWrapper } from '@/components/Page'
|
|
import {
|
|
bottomModelApi,
|
|
createDraftVersionApi,
|
|
modelInfoApi,
|
|
testModelApi,
|
|
trainModelApi,
|
|
updateModelInfo,
|
|
versionListApi,
|
|
} from '@/api/alert/model/models'
|
|
import { fetchAssessReportList, fetchAssessReportNames } from '@/api/alert/model/assessReport'
|
|
import type { AssessReportSimple } from '@/api/alert/model/model/assessReportModel'
|
|
import { getExaHistorys } from '@/api/alert/exa/index'
|
|
import { useECharts } from '@/hooks/web/useECharts'
|
|
import { useMessage } from '@/hooks/web/useMessage'
|
|
import { pointListApi } from '@/api/alert/model/select'
|
|
import { useGo } from '@/hooks/web/usePage'
|
|
import Icon from '@/components/Icon'
|
|
|
|
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,
|
|
ATable: Table,
|
|
ARangePicker: RangePicker,
|
|
AForm: Form,
|
|
AFormItem: FormItem,
|
|
VueECharts,
|
|
AModal: Modal,
|
|
AInput: Input,
|
|
ACheckbox: Checkbox,
|
|
AInputNumber: InputNumber,
|
|
AButton: Button,
|
|
ASpin: Spin,
|
|
ATransfer: Transfer,
|
|
ARow: Row,
|
|
ACol: Col,
|
|
ASelect: Select,
|
|
ASpace: Space,
|
|
Icon,
|
|
},
|
|
setup() {
|
|
const { createMessage } = useMessage()
|
|
const route = useRoute()
|
|
const go = useGo()
|
|
const id = route.params.id
|
|
const routeVersion = route.query.version as string | undefined
|
|
const model = ref(null)
|
|
const brushActivated = ref<Set<number>>(new Set())
|
|
const spinning = ref(false)
|
|
let trainTimeCopy = ''
|
|
const applyModelValue = (payload: any) => {
|
|
if (!payload)
|
|
return
|
|
|
|
const normalized = normalizeTrainTime(payload)
|
|
const previous = model.value || {}
|
|
const merged: any = {
|
|
...previous,
|
|
...normalized,
|
|
}
|
|
|
|
if (!Array.isArray(normalized?.trainTime))
|
|
merged.trainTime = previous?.trainTime ?? []
|
|
if (!Array.isArray(normalized?.pointInfo))
|
|
merged.pointInfo = previous?.pointInfo ?? []
|
|
if (normalized?.para === undefined && previous?.para !== undefined)
|
|
merged.para = previous.para
|
|
|
|
model.value = merged
|
|
trainTimeCopy = JSON.stringify(merged.trainTime || [])
|
|
}
|
|
|
|
const fetchModelInfo = async (version?: string) => {
|
|
const path = version ? `${id}?version=${encodeURIComponent(version)}` : (routeVersion ? `${id}?version=${encodeURIComponent(routeVersion)}` : id)
|
|
const modelInfo = await modelInfoApi(path)
|
|
applyModelValue(modelInfo)
|
|
selectedVersion.value = modelInfo?.version || modelInfo?.Cur_Version || ''
|
|
await loadReportName(selectedVersion.value)
|
|
getHistory()
|
|
}
|
|
|
|
const pointData = computed(() => {
|
|
const list = model.value?.pointInfo || []
|
|
return list.map((p: any) => ({
|
|
description: p.description ?? p.Description,
|
|
pointId: p.pointId ?? p.PointId,
|
|
unit: p.unit ?? p.Unit,
|
|
Upper: p.Upper ?? p.upper,
|
|
Lower: p.Lower ?? p.lower,
|
|
dead: p.dead === true ? '是' : '否',
|
|
limit: p.limit === true ? '是' : '否',
|
|
upperBound: p.upperBound ?? p.upperbound ?? p.upperBound,
|
|
lowerBound: p.lowerBound ?? p.lowerbound ?? p.lowerBound,
|
|
TMax: p.TMax ?? p.tMax,
|
|
TMin: p.TMin ?? p.tMin,
|
|
TAvg: p.TAvg ?? p.tAvg,
|
|
DMax: p.DMax ?? p.dMax,
|
|
DMin: p.DMin ?? p.dMin,
|
|
DAvg: p.DAvg ?? p.dAvg,
|
|
}))
|
|
})
|
|
const [pointTable] = useTable({
|
|
columns: pointTableSchema,
|
|
pagination: true,
|
|
dataSource: pointData,
|
|
scroll: { y: 300 },
|
|
})
|
|
|
|
const trainTime = computed(() => model.value?.trainTime || [])
|
|
const [trainTimeTable] = useTable({
|
|
columns: sampleInfoTableSchema,
|
|
dataSource: trainTime,
|
|
scroll: { y: 300 },
|
|
})
|
|
|
|
const updatePrincipalFromTrainTime = (trainTimeList: any[]) => {
|
|
if (!Array.isArray(trainTimeList) || !model.value)
|
|
return
|
|
// principal is the sum of mode for each trainTime entry
|
|
model.value.principal = trainTimeList.reduce((sum, item) => {
|
|
const modeVal = Number(item?.mode)
|
|
return sum + (Number.isFinite(modeVal) ? modeVal : 0)
|
|
}, 0)
|
|
}
|
|
|
|
watch(
|
|
() => model.value?.trainTime,
|
|
newTrainTime => updatePrincipalFromTrainTime(newTrainTime as any[]),
|
|
{ deep: true, immediate: true },
|
|
)
|
|
|
|
const activeKey = ref('1')
|
|
type RangeValue = [Dayjs, Dayjs]
|
|
const currentDate: Dayjs = dayjs('2023-10-29 00:00:00')
|
|
const lastMonthDate: Dayjs = dayjs('2023-10-28 16:00:00')
|
|
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
|
|
spinning.value = true
|
|
if (model.value.para) {
|
|
await getTestData()
|
|
}
|
|
else {
|
|
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.pointId)
|
|
.join(','),
|
|
interval: model.value.sampling,
|
|
}
|
|
const history = await getExaHistorys(params)
|
|
historyList.value = history.map((item, index) => {
|
|
const point = model.value?.pointInfo[index]
|
|
return {
|
|
data: [item],
|
|
name: `${index + 1}.${point?.description}(${point?.pointId})`,
|
|
}
|
|
})
|
|
echartsRefs.value = Array.from({ length: historyList.value.length })
|
|
brushActivated.value = new Set()
|
|
}
|
|
|
|
spinning.value = false
|
|
}
|
|
|
|
async function getTestData(range?: RangeValue) {
|
|
const timeRange = range || historyTime.value
|
|
if (!timeRange)
|
|
return
|
|
const params = {
|
|
Model_id: id,
|
|
version: model.value?.version ? model.value?.version : 'v-test',
|
|
Test_Data: {
|
|
time: timeRange
|
|
.map(t => dayjs(t).format('YYYY-MM-DD HH:mm:ss'))
|
|
.join(','),
|
|
points: model.value.pointInfo.map(t => t.pointId).join(','),
|
|
interval: model.value.sampling * 1000,
|
|
},
|
|
}
|
|
const result = await testModelApi(params)
|
|
const sampleData = result?.sampleData ?? result?.SampleData ?? []
|
|
const reconData = result?.reconData ?? result?.ReconData ?? []
|
|
if (!Array.isArray(sampleData) || !Array.isArray(reconData) || sampleData.length === 0 || reconData.length === 0) {
|
|
historyList.value = []
|
|
createMessage.warning('未获取到测试数据')
|
|
return
|
|
}
|
|
const xData = generateTimeList(
|
|
timeRange,
|
|
model.value.sampling * 1000,
|
|
)
|
|
historyList.value = sampleData.map((item, index) => {
|
|
const point = model.value?.pointInfo[index]
|
|
return {
|
|
data: [
|
|
(Array.isArray(item) ? item : []).map((t, i) => {
|
|
return [xData[i], t]
|
|
}),
|
|
(Array.isArray(reconData[index]) ? reconData[index] : []).map((t, i) => {
|
|
return [xData[i], t]
|
|
}),
|
|
],
|
|
name: `${index + 1}.${point?.description}(${point?.pointId})`,
|
|
}
|
|
})
|
|
brushActivated.value = new Set()
|
|
}
|
|
|
|
function generateTimeList(time: RangeValue, intervalMs: number) {
|
|
const [t1, t2] = time
|
|
const count = Math.floor(t2.diff(t1, 'millisecond') / intervalMs) + 1
|
|
return Array.from({ length: count }, (_, i) => t1.add(i * intervalMs, 'millisecond').valueOf())
|
|
}
|
|
|
|
function normalizeTrainTime(modelInfo: any) {
|
|
if (!modelInfo?.trainTime || !Array.isArray(modelInfo.trainTime))
|
|
return modelInfo
|
|
const samplingMs = (modelInfo.sampling || 0) * 1000
|
|
modelInfo.trainTime = modelInfo.trainTime.map((item: any) => {
|
|
const durationSec = item.duration || Math.max(0, (dayjs(item.et).valueOf() - dayjs(item.st).valueOf()) / 1000)
|
|
const number = item.number !== undefined && item.number !== '' ? item.number : (samplingMs > 0 ? Math.floor((durationSec * 1000) / samplingMs) : '')
|
|
const filter = item.filter !== undefined && item.filter !== '' ? item.filter : 0
|
|
const mode = item.mode !== undefined && item.mode !== '' ? item.mode : (number !== '' ? Math.max(0, number - filter) : '')
|
|
return {
|
|
...item,
|
|
duration: durationSec,
|
|
number,
|
|
filter,
|
|
mode,
|
|
}
|
|
})
|
|
return modelInfo
|
|
}
|
|
|
|
function getOption(item: any) {
|
|
const name = ['测量值', '模型值', '']
|
|
const color = ['blue', '#ff6f00', 'red']
|
|
const yIndex = [0, 0, 1]
|
|
const option = {
|
|
xAxis: {
|
|
type: 'time',
|
|
axisLabel: {
|
|
formatter(value) {
|
|
const date = new Date(value)
|
|
return date.toLocaleString()
|
|
},
|
|
},
|
|
},
|
|
yAxis: [{ type: 'value' }, { type: 'value', max: 10, show: false }],
|
|
series: item.data.map((item, index) => {
|
|
return {
|
|
data: item,
|
|
type: 'line',
|
|
smooth: true,
|
|
symbol: 'none',
|
|
name: name[index],
|
|
color: color[index],
|
|
yAxisIndex: yIndex[index],
|
|
}
|
|
}),
|
|
legend: {},
|
|
dataZoom: [{}],
|
|
brush: {
|
|
toolbox: ['lineX'],
|
|
xAxisIndex: 0,
|
|
brushType: 'lineX',
|
|
},
|
|
grid: [
|
|
{
|
|
left: 60,
|
|
right: 50,
|
|
bottom: 50,
|
|
top: 30,
|
|
},
|
|
],
|
|
}
|
|
return option
|
|
}
|
|
|
|
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
|
|
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).format('YYYY-MM-DD HH:mm:ss'),
|
|
dayjs(row.et).format('YYYY-MM-DD HH:mm:ss'),
|
|
],
|
|
}))
|
|
chart.dispatchAction({
|
|
type: 'brush',
|
|
areas,
|
|
})
|
|
isInitBrush.value = true
|
|
brushActivated.value.add(index)
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await fetchModelInfo()
|
|
})
|
|
const onBrushSelected = debounce((params) => {
|
|
if (isInitBrush.value) {
|
|
isInitBrush.value = false
|
|
return
|
|
}
|
|
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 [stRaw, etRaw] = area.coordRange
|
|
const st = typeof stRaw === 'string' ? dayjs(stRaw).valueOf() : stRaw
|
|
const et = typeof etRaw === 'string' ? dayjs(etRaw).valueOf() : etRaw
|
|
console.log('Selected area:', { st, et }, area)
|
|
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: '', // 有效样本数(如有数据可补充)
|
|
}
|
|
})
|
|
console.log('Selected train time:', trainTime)
|
|
if (trainTimeCopy === JSON.stringify(trainTime))
|
|
return
|
|
model.value.trainTime = trainTime
|
|
trainTimeCopy = JSON.stringify(trainTime)
|
|
echartsRefs.value.forEach((chart, index) => {
|
|
if (chart) {
|
|
chart.dispatchAction({
|
|
type: 'brush',
|
|
areas,
|
|
})
|
|
isInitBrush.value = true
|
|
}
|
|
})
|
|
updateModelInfoDebounced()
|
|
}
|
|
}, 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(() => {
|
|
const val = toRaw(model.value)
|
|
if (val && val.id)
|
|
updateModelInfo(val)
|
|
}, 500)
|
|
|
|
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
|
|
}
|
|
})
|
|
updateModelInfoDebounced()
|
|
}
|
|
async function clearModel() {
|
|
model.value.para = null
|
|
updateModelInfoDebounced()
|
|
await getHistory()
|
|
}
|
|
|
|
async function trainModel() {
|
|
const modelInfo = model.value
|
|
if (!modelInfo || !modelInfo.id) {
|
|
console.error('模型信息不完整,无法训练')
|
|
return
|
|
}
|
|
const pointInfo = modelInfo.pointInfo || []
|
|
if (pointInfo.length === 0) {
|
|
console.error('模型参数点信息为空,无法训练')
|
|
return
|
|
}
|
|
const params = {
|
|
conditon: modelInfo.alarmmodelset?.alarmcondition || '1=1',
|
|
Hyper_para: {
|
|
percent: modelInfo.rate,
|
|
},
|
|
Train_Data: {
|
|
points: pointInfo.map(item => item.pointId).join(','),
|
|
dead: pointInfo.map(item => (item.dead ? '1' : '0')).join(','),
|
|
limit: pointInfo.map(item => (item.limit ? '1' : '0')).join(','),
|
|
uplow: pointInfo
|
|
.map(
|
|
item =>
|
|
`${item.Upper ? item.Upper : null},${
|
|
item.Lower ? item.Lower : null
|
|
}`,
|
|
)
|
|
.join(';'),
|
|
interval: modelInfo.sampling * 1000,
|
|
time: modelInfo.trainTime
|
|
.map(item => `${item.st},${item.et}`)
|
|
.join(';'),
|
|
},
|
|
type: 'PCA',
|
|
smote_config: [],
|
|
smote: true,
|
|
}
|
|
spinning.value = true
|
|
try {
|
|
const response = await trainModelApi(params)
|
|
model.value.para = response
|
|
|
|
const modelInfoDetail = response.Model_info || response.modelInfo
|
|
if (modelInfoDetail) {
|
|
model.value.principal = modelInfoDetail.K ?? modelInfoDetail.k ?? model.value.principal
|
|
model.value.precision = modelInfoDetail.R ? `${(modelInfoDetail.R * 100).toFixed(2)}%` : model.value.precision
|
|
|
|
const pxMax = modelInfoDetail.Train_X_max || modelInfoDetail.trainXMax || []
|
|
const pxMin = modelInfoDetail.Train_X_min || modelInfoDetail.trainXMin || []
|
|
const pxMean = modelInfoDetail.Train_X_mean || modelInfoDetail.trainXMean || []
|
|
const pbMax = (modelInfoDetail.Train_X_bais_max || modelInfoDetail.trainXBaisMax || [])[0] || []
|
|
const pbMin = (modelInfoDetail.Train_X_bais_min || modelInfoDetail.trainXBaisMin || [])[0] || []
|
|
const pbMean = (modelInfoDetail.Train_X_bais_mean || modelInfoDetail.trainXBaisMean || [])[0] || []
|
|
const qcul99Line = modelInfoDetail.QCUL_99_line || modelInfoDetail.qcul99Line || []
|
|
|
|
model.value.pointInfo = (model.value.pointInfo || []).map((p: any, idx: number) => {
|
|
const q99 = qcul99Line[idx]
|
|
const upperB = Number.parseFloat(p.upperBound)
|
|
const lowerB = Number.parseFloat(p.lowerBound)
|
|
return {
|
|
...p,
|
|
TMax: pxMax[idx] !== undefined ? Number(pxMax[idx]).toFixed(2) : p.TMax,
|
|
TMin: pxMin[idx] !== undefined ? Number(pxMin[idx]).toFixed(2) : p.TMin,
|
|
TAvg: pxMean[idx] !== undefined ? Number(pxMean[idx]).toFixed(2) : p.TAvg,
|
|
DMax: pbMax[idx] !== undefined ? Number(pbMax[idx]).toFixed(2) : p.DMax,
|
|
DMin: pbMin[idx] !== undefined ? Number(pbMin[idx]).toFixed(2) : p.DMin,
|
|
DAvg: pbMean[idx] !== undefined ? Number(pbMean[idx]).toFixed(2) : p.DAvg,
|
|
upperBound:
|
|
Number.isNaN(upperB) || upperB === undefined
|
|
? (q99 !== undefined ? Number(q99).toFixed(2) : p.upperBound)
|
|
: p.upperBound,
|
|
lowerBound:
|
|
Number.isNaN(lowerB) || lowerB === undefined
|
|
? (q99 !== undefined ? (-Number(q99)).toFixed(2) : p.lowerBound)
|
|
: p.lowerBound,
|
|
}
|
|
})
|
|
}
|
|
|
|
// 填充训练区间的采样/清洗/有效样本数,如果接口有总数则均摊,否则保留原值
|
|
if (Array.isArray(model.value.trainTime) && model.value.trainTime.length) {
|
|
const beforeTotal = response.BeforeCleanSamNum ?? response.beforeCleanSamNum
|
|
const afterTotal = response.AfterCleanSamNum ?? response.afterCleanSamNum
|
|
if (beforeTotal !== undefined && afterTotal !== undefined) {
|
|
const avgBefore = Math.floor(beforeTotal / model.value.trainTime.length)
|
|
const avgAfter = Math.floor(afterTotal / model.value.trainTime.length)
|
|
model.value.trainTime = model.value.trainTime.map((t: any) => ({
|
|
...t,
|
|
number: t.number || avgBefore,
|
|
filter: t.filter || Math.max(0, avgBefore - avgAfter),
|
|
mode: t.mode || avgAfter,
|
|
}))
|
|
}
|
|
}
|
|
|
|
updateModelInfoDebounced()
|
|
await updateModelInfoDebounced.flush()
|
|
// 训练完成后自动触发测试,优先使用训练时间范围
|
|
try {
|
|
if (model.value.trainTime && model.value.trainTime.length > 0) {
|
|
const sorted = [...model.value.trainTime].sort(
|
|
(a, b) => dayjs(a.st).valueOf() - dayjs(b.st).valueOf(),
|
|
)
|
|
const trainRange: RangeValue = [
|
|
dayjs(sorted[0].st),
|
|
dayjs(sorted[sorted.length - 1].et),
|
|
]
|
|
historyTime.value = trainRange
|
|
await getTestData(trainRange)
|
|
}
|
|
else {
|
|
await getTestData()
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.error('自动测试失败:', e)
|
|
}
|
|
createMessage.success('模型训练成功')
|
|
}
|
|
catch (error) {
|
|
console.error('模型训练失败:', error)
|
|
createMessage.error('模型训练失败')
|
|
}
|
|
|
|
spinning.value = false
|
|
}
|
|
const editForm = ref({
|
|
index: -1,
|
|
Upper: '',
|
|
Lower: '',
|
|
lowerBound: '',
|
|
upperBound: '',
|
|
dead: true,
|
|
limit: false,
|
|
})
|
|
const openEditPointModal = ref<boolean>(false)
|
|
let pointEditRecord = null
|
|
function editPoint() {
|
|
// 这里可以添加编辑点的逻辑
|
|
model.value.pointInfo[editForm.value.index] = {
|
|
...model.value.pointInfo[editForm.value.index],
|
|
Upper: editForm.value.Upper,
|
|
Lower: editForm.value.Lower,
|
|
lowerBound: editForm.value.lowerBound,
|
|
upperBound: editForm.value.upperBound,
|
|
dead: editForm.value.dead,
|
|
limit: editForm.value.limit,
|
|
}
|
|
updateModelInfoDebounced()
|
|
pointEditRecord.Upper = editForm.value.Upper
|
|
pointEditRecord.Lower = editForm.value.Lower
|
|
pointEditRecord.lowerBound = editForm.value.lowerBound
|
|
pointEditRecord.upperBound = editForm.value.upperBound
|
|
pointEditRecord.dead = editForm.value.dead
|
|
pointEditRecord.limit = editForm.value.limit
|
|
openEditPointModal.value = false
|
|
}
|
|
|
|
function openPointModal(index, record) {
|
|
// 当前页 index
|
|
const pageIndex = index
|
|
// 全局 index
|
|
const globalIndex = model.value.pointInfo.findIndex(
|
|
item => item.pointId === record.pointId,
|
|
)
|
|
openEditPointModal.value = true
|
|
pointEditRecord = record
|
|
editForm.value = {
|
|
index: globalIndex,
|
|
Upper: record?.Upper ?? '',
|
|
Lower: record?.Lower ?? '',
|
|
lowerBound: record?.lowerBound ?? '',
|
|
upperBound: record?.upperBound ?? '',
|
|
dead: !!record?.dead,
|
|
limit: !!record?.limit,
|
|
}
|
|
}
|
|
|
|
const mode = ref({
|
|
alarmcondition: '1=1',
|
|
alarmname: '全工况运行',
|
|
})
|
|
const openEditModeModal = ref<boolean>(false)
|
|
|
|
function openEditMode() {
|
|
openEditModeModal.value = true
|
|
mode.value = {
|
|
alarmcondition: model.value?.alarmmodelset?.alarmcondition || '1=1',
|
|
alarmname: model.value?.alarmmodelset?.alarmname || '全工况运行',
|
|
}
|
|
}
|
|
function closeEditMode() {
|
|
openEditModeModal.value = false
|
|
}
|
|
function handleEditMode() {
|
|
// 这里可以添加编辑模式的逻辑
|
|
model.value.alarmmodelset = mode.value
|
|
updateModelInfoDebounced()
|
|
closeEditMode()
|
|
}
|
|
|
|
const openEditModelModal = ref(false)
|
|
const editModelForm = ref({
|
|
sampling: 0,
|
|
rate: 0,
|
|
selectedKeys: [],
|
|
})
|
|
|
|
// 穿梭框数据源示例
|
|
const transferData = ref([]) // 穿梭框数据源
|
|
|
|
// 初始化时获取全部测点
|
|
async function fetchAllPoints(keyword = '') {
|
|
// 这里调用你的后端接口,传递 keyword
|
|
// const res = await fetchPointsApi({ keyword })
|
|
// 示例数据
|
|
const data = await pointListApi({ keyword })
|
|
console.log('Fetched points:', data)
|
|
transferData.value = transferData.value.filter(item => editModelForm.value.selectedKeys.includes(item.key))
|
|
.concat(data.map(item => ({
|
|
key: item.id,
|
|
title: item.name,
|
|
})))
|
|
console.log('Transfer data:', transferData.value)
|
|
}
|
|
onMounted(() => {
|
|
fetchAllPoints()
|
|
})
|
|
|
|
function handleTransferSearch(dir, value) {
|
|
if (dir === 'left') {
|
|
// 左侧:后端接口查询
|
|
fetchAllPoints(value)
|
|
}
|
|
else {
|
|
// 右侧:前端过滤
|
|
transferData.value = (model.value?.pointInfo || [])
|
|
.filter(item =>
|
|
editModelForm.value.selectedKeys.includes(`${item.description}|${item.pointId}|${item.unit}|${item.Lower}|${item.Upper}`)
|
|
&& (`${item.description || ''}(${item.pointId})`).includes(value),
|
|
)
|
|
.map(item => ({
|
|
key: `${item.description}|${item.pointId}|${item.unit}|${item.Lower}|${item.Upper}`,
|
|
title: `${item.description || ''} (${item.pointId})`,
|
|
}))
|
|
}
|
|
}
|
|
|
|
function openEditModel() {
|
|
editModelForm.value.sampling = model.value?.sampling || 0
|
|
editModelForm.value.rate = model.value?.rate || 0
|
|
editModelForm.value.selectedKeys = (model.value?.pointInfo || []).map(item => `${item.description}|${item.pointId}|${item.unit}|${item.Lower}|${item.Upper}`)
|
|
transferData.value = (model.value?.pointInfo || []).map(item => ({
|
|
key: `${item.description}|${item.pointId}|${item.unit}|${item.Lower}|${item.Upper}`,
|
|
title: `${item.description || ''} (${item.pointId})`,
|
|
}))
|
|
console.log('transferData:', transferData.value)
|
|
openEditModelModal.value = true
|
|
}
|
|
function handleEditModelOk() {
|
|
model.value.sampling = editModelForm.value.sampling
|
|
model.value.rate = editModelForm.value.rate
|
|
model.value.pointInfo = editModelForm.value.selectedKeys.map((key) => {
|
|
const [description, pointId, unit, Lower, Upper] = key.split('|')
|
|
return {
|
|
description,
|
|
pointId,
|
|
unit,
|
|
Lower,
|
|
Upper,
|
|
dead: true,
|
|
limit: false,
|
|
}
|
|
})
|
|
clearModel()
|
|
openEditModelModal.value = false
|
|
}
|
|
function handleEditModelCancel() {
|
|
openEditModelModal.value = false
|
|
}
|
|
|
|
const reportModalVisible = ref(false)
|
|
const reportLoading = ref(false)
|
|
const reportList = ref<AssessReportSimple[]>([])
|
|
const selectedReportId = ref<number | null>(null)
|
|
const reportName = ref<string>('暂无报告')
|
|
const reportNameOptions = ref<{ label: string; value: number }[]>([])
|
|
const reportNameSelectVisible = ref(false)
|
|
const selectedReportNameId = ref<number | null>(null)
|
|
const isVTestVersion = computed(
|
|
() => (model.value?.Cur_Version || model.value?.version) === 'v-test',
|
|
)
|
|
const isBottomed = computed(() => model.value?.btmState === '已下装')
|
|
const reportColumns = [
|
|
{ title: '评估时间', dataIndex: 'assessTime' },
|
|
{ title: '得分', dataIndex: 'score' },
|
|
{ title: '覆盖面', dataIndex: 'coverRange' },
|
|
{ title: '验证人', dataIndex: 'identifier' },
|
|
{ title: '版本', dataIndex: 'version' },
|
|
{ title: '模式', dataIndex: 'conditionName' },
|
|
]
|
|
|
|
function onSelectReport(selectedRowKeys: (string | number)[]) {
|
|
selectedReportId.value = (selectedRowKeys && selectedRowKeys.length > 0)
|
|
? Number(selectedRowKeys[0])
|
|
: null
|
|
}
|
|
|
|
function openReportSelector() {
|
|
loadReportName()
|
|
reportNameSelectVisible.value = true
|
|
}
|
|
|
|
function confirmReportSelect() {
|
|
if (selectedReportNameId.value)
|
|
goAssessReport(selectedReportNameId.value)
|
|
else
|
|
goAssessReport()
|
|
|
|
reportNameSelectVisible.value = false
|
|
}
|
|
|
|
async function openBottomModal() {
|
|
if (!model.value?.para) {
|
|
createMessage.error('模型未训练,无法下装')
|
|
return
|
|
}
|
|
if (!model.value?.trainTime || model.value.trainTime.length === 0) {
|
|
createMessage.error('模型尚未建立,无法下装')
|
|
return
|
|
}
|
|
if (model.value?.btmState === '已下装' && (model.value?.Cur_Version || model.value?.version) !== 'v-test') {
|
|
createMessage.error('模型已下装,无法重复操作')
|
|
return
|
|
}
|
|
reportLoading.value = true
|
|
try {
|
|
const version = model.value?.Cur_Version || model.value?.version || 'v-test'
|
|
// todo valid需要改回true,现在为了能下装测试先改成false
|
|
const list = await fetchAssessReportList({ modelId: id as string, version, valid: false })
|
|
reportList.value = list || []
|
|
if (!reportList.value.length) {
|
|
createMessage.error('请先完成合格并审核通过的评估报告')
|
|
return
|
|
}
|
|
selectedReportId.value = reportList.value[0]?.id ?? null
|
|
reportModalVisible.value = true
|
|
}
|
|
catch (error) {
|
|
console.error('获取评估报告失败:', error)
|
|
createMessage.error('获取评估报告失败')
|
|
}
|
|
finally {
|
|
reportLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function loadReportName(version?: string) {
|
|
try {
|
|
reportLoading.value = true
|
|
const names = await fetchAssessReportNames({ modelId: id as string, version: version || selectedVersion.value })
|
|
if (names && names.length > 0) {
|
|
reportName.value = names[0].name
|
|
selectedReportId.value = names[0].id
|
|
selectedReportNameId.value = names[0].id
|
|
reportNameOptions.value = names.map(n => ({ label: n.name, value: n.id }))
|
|
}
|
|
else {
|
|
reportName.value = '暂无报告'
|
|
selectedReportId.value = null
|
|
selectedReportNameId.value = null
|
|
reportNameOptions.value = []
|
|
}
|
|
}
|
|
catch (error) {
|
|
reportName.value = '暂无报告'
|
|
selectedReportNameId.value = null
|
|
reportNameOptions.value = []
|
|
}
|
|
finally {
|
|
reportLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function confirmBottomModel() {
|
|
if (!selectedReportId.value) {
|
|
createMessage.warning('请选择评估报告')
|
|
return
|
|
}
|
|
spinning.value = true
|
|
try {
|
|
const response = await bottomModelApi(model.value.id, selectedReportId.value)
|
|
applyModelValue(response)
|
|
createMessage.success('模型下装成功')
|
|
reportModalVisible.value = false
|
|
}
|
|
catch (error) {
|
|
console.error('模型下装失败:', error)
|
|
createMessage.error('模型下装失败')
|
|
}
|
|
finally {
|
|
spinning.value = false
|
|
}
|
|
}
|
|
|
|
const versionLoading = ref(false)
|
|
const versionList = ref<any[]>([])
|
|
const selectedVersion = ref<string>('')
|
|
const versionOptions = computed(() => {
|
|
const options = versionList.value
|
|
.filter(v => v?.version)
|
|
.map(v => ({ label: v.version, value: v.version }))
|
|
if (selectedVersion.value && !options.find(o => o.value === selectedVersion.value))
|
|
options.unshift({ label: selectedVersion.value, value: selectedVersion.value })
|
|
|
|
return options
|
|
})
|
|
const versionDropdownVisible = ref(false)
|
|
|
|
async function loadVersionList() {
|
|
versionLoading.value = true
|
|
try {
|
|
const list = await versionListApi(id)
|
|
versionList.value = list || []
|
|
}
|
|
catch (error) {
|
|
createMessage.error('获取版本列表失败')
|
|
}
|
|
finally {
|
|
versionLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function handleVersionChange(value: string) {
|
|
if (!value)
|
|
return
|
|
await fetchModelInfo(value)
|
|
}
|
|
|
|
async function createDraftVersion() {
|
|
const confirmed = await new Promise<boolean>((resolve) => {
|
|
Modal.confirm({
|
|
title: '确认创建新版本?',
|
|
content: '创建新版本后可重新训练、评估和下装。',
|
|
onOk: () => resolve(true),
|
|
onCancel: () => resolve(false),
|
|
})
|
|
})
|
|
if (!confirmed)
|
|
return
|
|
|
|
try {
|
|
await createDraftVersionApi(id)
|
|
selectedVersion.value = 'v-test'
|
|
if (model.value) {
|
|
model.value = {
|
|
...model.value,
|
|
Cur_Version: 'v-test',
|
|
version: 'v-test',
|
|
}
|
|
}
|
|
createMessage.success('已创建草稿版本 v-test,请重新训练')
|
|
versionList.value = []
|
|
await fetchModelInfo('v-test')
|
|
}
|
|
catch (error) {
|
|
createMessage.error('创建草稿版本失败')
|
|
}
|
|
}
|
|
const showTrainActions = computed(
|
|
() => !isBottomed.value || isVTestVersion.value || selectedVersion.value === 'v-test',
|
|
)
|
|
|
|
function goAssessReport(reportId?: number | null | Event) {
|
|
// 防止直接作为事件处理时收到 PointerEvent
|
|
const rid = (reportId && typeof reportId === 'object') ? null : reportId
|
|
const version = selectedVersion.value || model.value?.Cur_Version || 'v-test'
|
|
const algorithm = model.value?.algorithm || 'PCA'
|
|
const extra = rid ? `&reportId=${rid}` : ''
|
|
go(`/model/assess-report/${id}?version=${encodeURIComponent(version)}&algorithm=${algorithm}${extra}`)
|
|
}
|
|
|
|
return {
|
|
pointTable,
|
|
model,
|
|
selectedVersion,
|
|
versionOptions,
|
|
versionDropdownVisible,
|
|
reportName,
|
|
activeKey,
|
|
historyTime,
|
|
historyList,
|
|
getHistory,
|
|
getOption,
|
|
setChartRef,
|
|
setEchartsRef,
|
|
onChartFinished,
|
|
echartsRefs,
|
|
onBrushSelected,
|
|
trainTimeTable,
|
|
handleDeleteTrainTime,
|
|
openEditPointModal,
|
|
editPoint,
|
|
openPointModal,
|
|
editForm,
|
|
trainModel,
|
|
openEditModeModal,
|
|
openEditMode,
|
|
closeEditMode,
|
|
handleEditMode,
|
|
mode,
|
|
spinning,
|
|
clearModel,
|
|
openEditModelModal,
|
|
editModelForm,
|
|
transferData,
|
|
handleTransferSearch,
|
|
openEditModel,
|
|
handleEditModelOk,
|
|
handleEditModelCancel,
|
|
openBottomModal,
|
|
confirmBottomModel,
|
|
reportModalVisible,
|
|
reportList,
|
|
reportColumns,
|
|
reportLoading,
|
|
selectedReportId,
|
|
onSelectReport,
|
|
reportName,
|
|
reportNameOptions,
|
|
reportNameSelectVisible,
|
|
selectedReportNameId,
|
|
openReportSelector,
|
|
confirmReportSelect,
|
|
loadReportName,
|
|
versionList,
|
|
versionLoading,
|
|
loadVersionList,
|
|
handleVersionChange,
|
|
createDraftVersion,
|
|
goAssessReport,
|
|
showTrainActions,
|
|
}
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<PageWrapper content-background>
|
|
<div>
|
|
<a-card title="模型信息" :bordered="false">
|
|
<a-descriptions size="small" :column="4" bordered>
|
|
<a-descriptions-item label="模型名称">
|
|
{{ model?.name }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="描述">
|
|
{{ model?.description || "暂无描述" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="版本">
|
|
<template v-if="!versionDropdownVisible">
|
|
<span
|
|
style=" text-decoration: underline;cursor: pointer"
|
|
@click="() => { versionDropdownVisible = true; loadVersionList() }"
|
|
>
|
|
{{ selectedVersion || 'v-test' }}
|
|
</span>
|
|
<a-button type="link" size="small" @click="createDraftVersion">
|
|
<Icon icon="ant-design:plus-circle-outlined" />
|
|
</a-button>
|
|
</template>
|
|
<template v-else>
|
|
<a-select
|
|
v-model:value="selectedVersion"
|
|
size="small"
|
|
style="min-width: 160px"
|
|
:loading="versionLoading"
|
|
:options="versionOptions"
|
|
placeholder="选择版本"
|
|
:allow-clear="false"
|
|
@change="(v) => { handleVersionChange(v); versionDropdownVisible = false }"
|
|
@blur="versionDropdownVisible = false"
|
|
/>
|
|
</template>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="评估报告">
|
|
<span
|
|
style=" text-decoration: underline;cursor: pointer"
|
|
@click="openReportSelector"
|
|
>
|
|
{{ reportName }}
|
|
</span>
|
|
<a-button type="link" size="small" @click="goAssessReport">
|
|
<Icon icon="ant-design:plus-circle-outlined" />
|
|
</a-button>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="创建人">
|
|
{{ model?.founder }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="创建时间">
|
|
{{ model?.creatTime || model?.createTime }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="最近修改人">
|
|
{{ model?.Modifier || "暂无" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="最近修改时间">
|
|
{{ model?.modifiedTime || "暂无" }}
|
|
</a-descriptions-item>
|
|
</a-descriptions>
|
|
<a-divider />
|
|
<a-descriptions size="small" :column="4" bordered>
|
|
<a-descriptions-item label="算法">
|
|
{{ model?.algorithm || "PCA" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="训练采样间隔">
|
|
{{ model?.sampling }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="参数个数">
|
|
{{ model?.pointInfo.length || "暂无" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="最小主元贡献率">
|
|
{{ model?.rate }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="最小主元个数">
|
|
{{ model?.principal }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="模型性能">
|
|
{{ model?.precision }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="训练总时长(h)">
|
|
{{
|
|
(
|
|
model?.trainTime.reduce(
|
|
(total, item) => total + item.duration,
|
|
0,
|
|
) / 3600
|
|
).toFixed(2) || "暂无"
|
|
}}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="有效样本数">
|
|
{{ model?.principal }}
|
|
</a-descriptions-item>
|
|
</a-descriptions>
|
|
</a-card>
|
|
<a-card
|
|
title="模式"
|
|
:bordered="false"
|
|
style="margin-top: 16px; margin-bottom: -20px"
|
|
>
|
|
<a-button size="large" @click="openEditMode">
|
|
{{ model?.alarmmodelset?.alarmname || '全工况运行' }}
|
|
</a-button>
|
|
</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">
|
|
<template #action="{ record, index }">
|
|
<a-button type="primary" @click="openPointModal(index, record)">
|
|
编辑
|
|
</a-button>
|
|
</template>
|
|
</BasicTable>
|
|
</a-tab-pane>
|
|
</a-tabs>
|
|
</a-card>
|
|
|
|
<a-card title="智能训练" :bordered="false">
|
|
<div style="display: flex; align-items: center">
|
|
<a-form layout="inline" style="flex: 1">
|
|
<a-form-item label="模型预览时间范围">
|
|
<a-range-picker
|
|
v-model:value="historyTime"
|
|
show-time
|
|
@change="getHistory"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
<a-button
|
|
v-if="showTrainActions"
|
|
type="primary"
|
|
style="margin-left: auto"
|
|
@click="trainModel"
|
|
>
|
|
模型训练
|
|
</a-button>
|
|
<a-button
|
|
v-if="showTrainActions"
|
|
type="primary"
|
|
style="margin-left: 10px"
|
|
@click="clearModel"
|
|
>
|
|
清除训练结果
|
|
</a-button>
|
|
<a-button
|
|
v-if="showTrainActions"
|
|
type="primary"
|
|
style="margin-left: 6px"
|
|
@click="openEditModel"
|
|
>
|
|
修改模型
|
|
</a-button>
|
|
<a-button
|
|
v-if="showTrainActions"
|
|
danger
|
|
style="margin-left: 10px"
|
|
@click="openBottomModal"
|
|
>
|
|
下装
|
|
</a-button>
|
|
</div>
|
|
<a-divider />
|
|
<a-spin :spinning="spinning" size="large">
|
|
<div
|
|
v-for="(item, index) in historyList"
|
|
:key="index"
|
|
class="echart-box"
|
|
style="width: 100%"
|
|
>
|
|
<a-card :bordered="false" style="margin-bottom: 16px">
|
|
<template #title>
|
|
<span style="font-size: 20px">{{ item.name }}</span>
|
|
</template>
|
|
<VueECharts
|
|
:ref="(el) => setEchartsRef(el, index)"
|
|
:option="getOption(item)"
|
|
autoresize
|
|
style="width: 100%; height: 200px"
|
|
@finished="() => onChartFinished(index)"
|
|
@brush-selected="onBrushSelected"
|
|
/>
|
|
</a-card>
|
|
</div>
|
|
</a-spin>
|
|
</a-card>
|
|
</div>
|
|
<a-modal
|
|
v-model:open="reportModalVisible"
|
|
title="选择评估报告"
|
|
:confirm-loading="spinning"
|
|
@ok="confirmBottomModel"
|
|
>
|
|
<a-table
|
|
:columns="reportColumns"
|
|
:data-source="reportList"
|
|
:loading="reportLoading"
|
|
row-key="id"
|
|
:pagination="false"
|
|
size="small"
|
|
:row-selection="{
|
|
type: 'radio',
|
|
selectedRowKeys: selectedReportId ? [selectedReportId] : [],
|
|
onChange: onSelectReport,
|
|
}"
|
|
/>
|
|
</a-modal>
|
|
<a-modal
|
|
v-model:open="reportNameSelectVisible"
|
|
title="评估报告版本"
|
|
@ok="confirmReportSelect"
|
|
@cancel="() => { reportNameSelectVisible = false }"
|
|
>
|
|
<a-select
|
|
v-model:value="selectedReportNameId"
|
|
:options="reportNameOptions"
|
|
style="width: 100%"
|
|
placeholder="请选择评估报告"
|
|
:loading="reportLoading"
|
|
/>
|
|
</a-modal>
|
|
<a-modal
|
|
v-model:open="openEditPointModal"
|
|
title="更改上下限"
|
|
@ok="editPoint"
|
|
>
|
|
<a-form
|
|
:model="editForm"
|
|
:label-col="{ span: 7 }"
|
|
:wrapper-col="{ span: 15 }"
|
|
>
|
|
<a-form-item label="上限">
|
|
<a-input-number
|
|
v-model:value="editForm.Upper"
|
|
placeholder="请输入上限"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="下限">
|
|
<a-input-number
|
|
v-model:value="editForm.Lower"
|
|
placeholder="请输入下限"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="残差上限">
|
|
<a-input-number
|
|
v-model:value="editForm.upperBound"
|
|
placeholder="请输入残差上限"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="残差下限">
|
|
<a-input-number
|
|
v-model:value="editForm.lowerBound"
|
|
placeholder="请输入残差下限"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="清洗方式" style="margin-top: 16px">
|
|
<a-checkbox v-model:checked="editForm.dead">
|
|
死区清洗
|
|
</a-checkbox>
|
|
<a-checkbox v-model:checked="editForm.limit">
|
|
限值清洗
|
|
</a-checkbox>
|
|
</a-form-item>
|
|
</a-form>
|
|
</a-modal>
|
|
<a-modal
|
|
v-model:open="openEditModeModal"
|
|
title="编辑模式"
|
|
@ok="handleEditMode"
|
|
@cancel="closeEditMode"
|
|
>
|
|
<a-form
|
|
:model="mode"
|
|
:label-col="{ span: 7 }"
|
|
:wrapper-col="{ span: 15 }"
|
|
>
|
|
<a-form-item label="模式名称">
|
|
<a-input
|
|
v-model:value="mode.alarmname"
|
|
placeholder="请输入模式名称"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="报警条件">
|
|
<a-input
|
|
v-model:value="mode.alarmcondition"
|
|
placeholder="请输入报警条件"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
</a-modal>
|
|
<a-modal
|
|
v-model:open="openEditModelModal"
|
|
title="编辑模型"
|
|
width="1200px"
|
|
@ok="handleEditModelOk"
|
|
@cancel="handleEditModelCancel"
|
|
>
|
|
<a-form
|
|
:model="editModelForm"
|
|
:label-col="{ span: 7 }"
|
|
:wrapper-col="{ span: 15 }"
|
|
>
|
|
<a-row :gutter="16">
|
|
<a-col :span="12">
|
|
<a-form-item label="训练采样间隔" required>
|
|
<a-input-number
|
|
v-model:value="editModelForm.sampling"
|
|
placeholder="请输入训练采样间隔"
|
|
style="width: 100%"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-form-item label="最小主元贡献率" required>
|
|
<a-input-number
|
|
v-model:value="editModelForm.rate"
|
|
placeholder="请输入最小主元贡献率"
|
|
style="width: 100%"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
</a-row>
|
|
<div style="display: flex; justify-content: center; margin-top: 24px;margin-bottom: 10px;">
|
|
<a-transfer
|
|
v-model:target-keys="editModelForm.selectedKeys"
|
|
:data-source="transferData"
|
|
:render="item => item.title"
|
|
:row-key="(item) => item.key"
|
|
:pagination="false"
|
|
:show-search="true"
|
|
:filter="true"
|
|
search-placeholder="搜索测点"
|
|
:titles="['可选测点', '已选测点']"
|
|
:list-style="{
|
|
width: '500px',
|
|
height: '450px',
|
|
}"
|
|
@search="handleTransferSearch"
|
|
/>
|
|
</div>
|
|
</a-form>
|
|
</a-modal>
|
|
</PageWrapper>
|
|
</template>
|
|
|
|
<style>
|
|
.ant-card-head-title {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.el-table .el-table__header th {
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
|