Browse Source

Merge pull request 'cjl-new' (#64) from cjl-new into master

Reviewed-on: http://120.26.116.243:3000/root/alert-front/pulls/64
pull/67/head
chenjiale 1 month ago
parent
commit
39ef32db3b
  1. 3
      src/api/alert/model/models.ts
  2. 163
      src/views/model/components/PointTransfer.vue
  3. 26
      src/views/model/components/pointTransferHelper.ts
  4. 311
      src/views/model/list/ModelCard.vue
  5. 192
      src/views/model/list/step/Step3.vue
  6. 48
      src/views/model/list/step/data.tsx
  7. 144
      src/views/model/train/index.vue

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

@ -5,6 +5,7 @@ enum Api {
MODEL_CARD_LIST = '/alert/model/card/list',
MODEL_INFO = '/alert/model/info',
MODEL_SAVE = '/alert/model/',
MODEL_DELETE = '/alert/model/',
MODEL_DATA = '/alert/model/data/',
CALCULATE_BACK = '/alert/model/data/calculate/',
OPTIMISTIC = '/alert/optimistic',
@ -28,6 +29,8 @@ export function modelSaveApi(params?: any) {
return defHttp.post<number>({ url: Api.MODEL_SAVE, data: params })
}
export const modelDeleteApi = (id: number | string) => defHttp.delete<boolean>({ url: `${Api.MODEL_DELETE}${id}` })
export function modelDataApi(id: any, params: any) {
return defHttp.get<any>({ url: Api.MODEL_DATA + id, params })
}

163
src/views/model/components/PointTransfer.vue

@ -0,0 +1,163 @@
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { Transfer } from 'ant-design-vue'
import type { TransferItem } from 'ant-design-vue'
import { pointListApi } from '@/api/alert/model/select'
import { buildPointKeyFromInfo, buildPointTitle, parsePointKey } from './pointTransferHelper'
interface PointTransferItem extends TransferItem {
key: string
title: string
}
const props = defineProps({
selectedKeys: {
type: Array as PropType<string[]>,
default: () => [],
},
listStyle: {
type: Object as PropType<Record<string, string>>,
default: () => ({
width: '500px',
height: '450px',
}),
},
titles: {
type: Array as PropType<[string, string]>,
default: () => ['可选测点', '已选测点'],
},
showSearch: {
type: Boolean,
default: true,
},
prefillPoints: {
type: Array as PropType<Array<Partial<ReturnType<typeof parsePointKey>>>>,
default: () => [],
},
})
const emit = defineEmits<{
(e: 'update:selectedKeys', value: string[]): void
(e: 'change', value: ReturnType<typeof parsePointKey>[]): void
}>()
const dataSource = ref<PointTransferItem[]>([])
const targetKeys = ref<string[]>([...props.selectedKeys])
const dataMap = computed(() => new Map(dataSource.value.map(item => [item.key, item])))
const prefilledItems = computed<PointTransferItem[]>(() => {
return (props.prefillPoints || []).map((info) => {
const key = buildPointKeyFromInfo(info)
const parsed = parsePointKey(key)
return {
key,
title: buildPointTitle(parsed, key),
}
})
})
function mergeUnique(items: PointTransferItem[]) {
const map = new Map<string, PointTransferItem>()
items.forEach((item) => {
if (item.key === undefined || item.key === null)
return
const key = String(item.key)
if (!map.has(key))
map.set(key, { ...item, key })
})
dataSource.value = Array.from(map.values())
}
function ensureSelectedVisible(keys: string[]) {
mergeUnique(prefilledItems.value)
const missing: PointTransferItem[] = []
keys.forEach((key) => {
const k = String(key)
if (!dataMap.value.has(k)) {
const info = parsePointKey(k)
missing.push({
key: k,
title: buildPointTitle(info, k),
})
}
})
if (missing.length)
mergeUnique([...dataSource.value, ...missing])
}
async function fetchPoints(keyword = '') {
try {
const res = await pointListApi({ keyword })
const fetched = Array.isArray(res)
? res.map((item: any) => {
const key = item?.id
const info = parsePointKey(key)
return {
key: String(key),
title: item?.name || buildPointTitle(info, String(key)),
}
})
: []
const selectedItems = targetKeys.value.map((key) => {
const saved = dataMap.value.get(key)
if (saved)
return saved
const info = parsePointKey(key)
return { key, title: buildPointTitle(info, key) }
})
mergeUnique([...selectedItems, ...fetched])
}
catch (error) {
console.error('获取测点列表失败', error)
}
}
watch(
() => props.selectedKeys,
(val) => {
targetKeys.value = [...val]
ensureSelectedVisible(val)
},
{ immediate: true },
)
function handleChange(nextTargetKeys: string[]) {
targetKeys.value = nextTargetKeys
emit('update:selectedKeys', nextTargetKeys)
emit('change', nextTargetKeys.map(key => parsePointKey(key)))
}
function handleSearch(direction: 'left' | 'right', value: string) {
if (direction === 'left')
fetchPoints(value)
}
function filterOption(inputValue: string, option?: PointTransferItem) {
if (!option?.title)
return false
return option.title.toLowerCase().includes((inputValue || '').toLowerCase())
}
function renderItem(item: PointTransferItem) {
return item.title || (item as any).label || item.key
}
onMounted(() => {
fetchPoints()
})
</script>
<template>
<Transfer
:target-keys="targetKeys"
:data-source="dataSource"
:titles="titles"
:list-style="listStyle"
:show-search="showSearch"
:filter-option="filterOption"
:render="renderItem"
@search="handleSearch"
@change="handleChange"
/>
</template>

26
src/views/model/components/pointTransferHelper.ts

@ -0,0 +1,26 @@
export interface PointKeyInfo {
description: string
pointId: string
unit?: string
Lower?: string
Upper?: string
}
export function parsePointKey(key: string | number | undefined | null): PointKeyInfo {
const safeKey = key !== undefined && key !== null ? String(key) : ''
const [description = '', pointId = '', unit = '', Lower = '', Upper = ''] = safeKey.split('|')
return { description, pointId, unit, Lower, Upper }
}
export function buildPointKeyFromInfo(info: Partial<PointKeyInfo>): string {
const { description = '', pointId = '', unit = '', Lower = '', Upper = '' } = info || {}
return [description, pointId, unit, Lower, Upper].join('|')
}
export function buildPointTitle(info: PointKeyInfo, fallback: string): string {
if (info.description && info.pointId)
return `${info.description} (${info.pointId})`
if (info.description)
return info.description
return fallback
}

311
src/views/model/list/ModelCard.vue

@ -1,14 +1,15 @@
<script lang="ts" setup>
import { Card } from 'ant-design-vue'
import { Card, Dropdown, Menu, Popconfirm } from 'ant-design-vue'
import type { PropType } from 'vue'
import { ref, watch } from 'vue'
import type { ModelItem } from './data'
import CreateModel from './CreateModel.vue'
import Icon from '@/components/Icon/index'
import { modelCardListApi } from '@/api/alert/model/models'
import { modelCardListApi, modelDeleteApi } from '@/api/alert/model/models'
import type { ModelQueryParams } from '@/api/alert/model/model/models'
import { useGo } from '@/hooks/web/usePage'
import { useDrawer } from '@/components/Drawer'
import { useMessage } from '@/hooks/web/useMessage'
const props = defineProps({
loading: {
@ -23,6 +24,7 @@ const props = defineProps({
})
const [registerDraw, { openDrawer }] = useDrawer()
const { createMessage } = useMessage()
//
const go = useGo()
@ -32,75 +34,125 @@ function changeModel(id, version?) {
}
const modelCardList = ref<Array<ModelItem>>([])
const lastQuery = ref<ModelQueryParams | null>(null)
const colors = ['#8dc63f', '#dbb09e']
const statusStr = ['未下装', '已下装']
const icons = ['material-symbols:lock-open-right-outline', 'material-symbols:lock-outline']
const statusIcons = ['material-symbols:lock-open', 'material-symbols:lock']
function buildQuery(value: any): ModelQueryParams {
return {
unitId: value?.unit,
typeId: value?.type,
systemId: value?.system,
name: value?.name,
}
}
async function loadModelList(value: any) {
const queryParams = buildQuery(value)
lastQuery.value = queryParams
const modelList = await modelCardListApi(queryParams)
if (!modelList) {
modelCardList.value = []
return
}
const cardList: ModelItem[] = []
for (const modelCard of modelList) {
const statusIndex = Number(modelCard.status) || 0
const card: ModelItem = {
id: modelCard.id,
title: modelCard.name,
version: modelCard.version,
icon: statusIcons[statusIndex],
value: 1,
total: 1,
color: colors[statusIndex],
status: statusStr[statusIndex],
creator: modelCard.creator,
createTime: modelCard.createTime,
description: modelCard.name,
headStyle: {},
bodyStyle: {},
statusColor: colors[statusIndex],
algorithm: modelCard.algorithm,
}
cardList.push(card)
}
modelCardList.value = cardList
}
watch(
() => props.selectData,
async (value) => {
const queryParams: ModelQueryParams = {
unitId: value?.unit,
typeId: value?.type,
systemId: value?.system,
name: value?.name,
}
const modelList = await modelCardListApi(queryParams)
if (!modelList) {
modelCardList.value = []
return
}
const cardList: ModelItem[] = []
for (const modelCard of modelList) {
// modelCard.status = 0;
const card: ModelItem = {
id: modelCard.id,
title: modelCard.name,
version: modelCard.version,
icon: icons[modelCard.status],
value: 1,
total: 1,
color: colors[modelCard.status],
status: statusStr[modelCard.status],
creator: modelCard.creator,
createTime: modelCard.createTime,
description: modelCard.name,
headStyle: {
backgroundColor: colors[modelCard.status],
fontSize: '20px',
},
bodyStyle: {
borderColor: colors[modelCard.status],
borderWidth: '1px',
},
}
cardList.push(card)
}
modelCardList.value = cardList
await loadModelList(value)
},
{ immediate: true },
)
async function confirmDelete(id: number | string) {
await modelDeleteApi(id)
createMessage.success('删除成功')
await loadModelList(props.selectData)
}
</script>
<template>
<div class="enter-y">
<div class="grid gap-4 md:grid-cols-4">
<div class="card-grid">
<template v-for="item in modelCardList" :key="item.title">
<Card
size="small"
:loading="loading"
:title="item.title"
:hoverable="true"
:body-style="item.bodyStyle"
:head-style="item.headStyle"
@click="changeModel(item.id, item.version)"
>
<template #extra>
<Icon :icon="item.icon" :size="30" color="white" />
<Dropdown :trigger="['contextmenu']">
<Card
size="small"
:loading="loading"
:hoverable="true"
class="model-card"
:style="{ borderLeft: `6px solid ${item.statusColor}` }"
@click="changeModel(item.id, item.version)"
>
<div class="card-top">
<div class="card-title">
{{ item.title }}
</div>
<div class="card-tags">
<span class="status-icon" :style="{ color: item.statusColor }">
<Icon :icon="item.icon" :size="20" />
</span>
</div>
</div>
<div class="card-meta">
<span class="meta-item">
<Icon icon="ic:baseline-person" :size="16" class="meta-icon" />
{{ item.creator || '未知' }}
</span>
<span class="meta-item">
<Icon icon="solar:calendar-bold" :size="16" class="meta-icon" />
{{ item.createTime || '--' }}
</span>
</div>
<div class="card-footer">
<span class="algo-pill">
{{ item.algorithm || '未知' }}
</span>
<span class="tag version" :style="{ backgroundColor: item.statusColor, color: '#fff' }">
{{ item.version || 'v-test' }}
</span>
</div>
</Card>
<template #overlay>
<Menu>
<Menu.Item key="delete">
<Popconfirm
title="确认删除该模型?"
ok-text="删除"
ok-type="danger"
@confirm="() => confirmDelete(item.id)"
>
<span>删除</span>
</Popconfirm>
</Menu.Item>
</Menu>
</template>
<div class="grid p-2 px-5 md:grid-cols-3">
<span>创建人: {{ item.creator }}</span><span>创建时间: {{ item.createTime }}</span><span>模型状态: {{ item.status }}</span>
</div>
</Card>
</Dropdown>
</template>
<Card v-show="systemId != null" size="small" class="icon-card" :hoverable="true" @click="openDrawer(true)">
<Icon icon="ic:sharp-add" :size="80" color="#a7a9a7" />
@ -110,11 +162,142 @@ watch(
</div>
</template>
<style>
.icon-card {
display: flex;
align-items: center;
justify-content: center;
background-color: #f2f2f2;
}
<style scoped>
.card-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 20px;
padding: 8px 0;
}
.model-card {
padding: 14px 14px 12px;
background: linear-gradient(180deg, #fdfdfd 0%, #f7f8fa 100%);
border: 1px solid #d0d7de;
border-radius: 8px;
box-shadow: 0 6px 16px rgb(15 23 42 / 8%);
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.model-card:hover {
box-shadow: 0 12px 32px rgb(15 23 42 / 12%);
transform: translateY(-2px);
}
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.card-title {
padding-right: 8px;
overflow: hidden;
font-size: 18px;
font-weight: 600;
line-height: 1.4;
color: #1f2937;
text-overflow: ellipsis;
word-break: keep-all;
white-space: nowrap;
}
.card-tags {
display: inline-flex;
gap: 8px;
align-items: center;
}
.tag.version {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-size: 12px;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 0.3px;
border-radius: 999px;
}
.status-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background-color: rgb(0 0 0 / 6%);
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 8px;
}
.card-meta {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
font-size: 12px;
color: #6b7280;
}
.meta-item {
display: inline-flex;
gap: 6px;
align-items: center;
min-width: 0;
}
.meta-icon {
color: #9ca3af;
}
.card-footer {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
padding-top: 10px;
font-size: 13px;
color: #4b5563;
}
.footer-text.strong {
font-weight: 600;
}
.footer-divider {
flex: 1;
height: 1px;
border-top: 1px dashed #e5e7eb;
}
.algo-pill {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-weight: 700;
color: #1d4ed8;
letter-spacing: 0.3px;
background: #e0e7ff;
border: 1px solid #c7d2fe;
border-radius: 10px;
}
.icon-card {
display: flex;
align-items: center;
justify-content: center;
min-height: 180px;
background-color: #f3f4f6;
border: 1px dashed #d1d5db;
border-radius: 12px;
transition: border-color 0.2s ease, background 0.2s ease;
}
.icon-card:hover {
background-color: #eef2ff;
border-color: #4c7af0;
}
</style>

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

@ -1,20 +1,19 @@
<script lang="ts">
import type { PropType } from "vue";
import { computed, defineComponent, ref, toRaw, unref } from "vue";
import { Alert, Descriptions, Divider } from "ant-design-vue";
import type { Recordable } from "@vben/types";
import { useDebounceFn } from "@vueuse/core";
import { step3Schemas } from "./data";
import { ApiSelect, BasicForm, useForm } from "@/components/Form";
import { Button } from "@/components/Button";
import { pointListApi } from "@/api/alert/model/select";
import { modelSaveApi } from "@/api/alert/model/models";
import type { PropType } from 'vue'
import { defineComponent, ref, toRaw, watch } from 'vue'
import { Alert, Descriptions, Divider } from 'ant-design-vue'
import type { Recordable } from '@vben/types'
import { step3Schemas } from './data'
import { BasicForm, useForm } from '@/components/Form'
import PointTransfer from '@/views/model/components/PointTransfer.vue'
import { parsePointKey, buildPointKeyFromInfo } from '@/views/model/components/pointTransferHelper'
import { modelSaveApi } from '@/api/alert/model/models'
import { useMessage } from '@/hooks/web/useMessage'
export default defineComponent({
components: {
Button,
BasicForm,
ApiSelect,
PointTransfer,
[Alert.name]: Alert,
[Divider.name]: Divider,
[Descriptions.name]: Descriptions,
@ -22,140 +21,92 @@ export default defineComponent({
},
props: {
beforeData: {
type: Object as PropType<Record<string, any> | null | undefined>,
type: Object as PropType<Recordable | null | undefined>,
},
systemId: {
type: Number,
},
},
emits: ["next", "prev"],
emits: ['next', 'prev'],
setup(props, { emit }) {
const [
register,
{ appendSchemaByField, removeSchemaByField, validate, setProps },
] = useForm({
const { createMessage } = useMessage()
const selectedKeys = ref<string[]>([])
const [register, { validate, setProps }] = useForm({
labelWidth: 100,
schemas: step3Schemas,
actionColOptions: {
span: 14,
},
resetButtonOptions: {
text: "上一步",
text: '上一步',
},
submitButtonOptions: {
text: "创建模型",
text: '创建模型',
},
resetFunc: customResetFunc,
submitFunc: customSubmitFunc,
});
const n = ref(4);
function add() {
appendSchemaByField(
[
{
field: `point${n.value}`,
component: "Input",
label: `参数${n.value}`,
required: true,
slot: "remoteSearch",
colProps: {
span: 23,
},
},
{
field: `${n.value}`,
component: "Input",
label: " ",
slot: "add",
colProps: {
span: 1,
},
},
],
""
);
n.value++;
}
})
function del(field) {
removeSchemaByField([`point${field}`, `${field}`]);
n.value--;
}
watch(
() => props.beforeData?.pointInfo,
(val) => {
if (Array.isArray(val))
selectedKeys.value = val.map(item => buildPointKeyFromInfo(item))
},
{ immediate: true },
)
async function customResetFunc() {
emit("prev");
emit('prev')
}
async function customSubmitFunc() {
try {
const values = await validate();
const pointInfo = [];
for (const key in values) {
if (key.startsWith("point")) {
const point = values[key].split("|");
const p = {
description: point[0],
pointId: point[1],
unit: point[2],
Lower: point[3],
Upper: point[4],
dead: true,
limit: false,
};
pointInfo.push(p);
}
await validate()
if (!selectedKeys.value.length) {
createMessage.warning('请选择模型参数')
return
}
const modelInfo = toRaw(props.beforeData);
modelInfo.pointInfo = pointInfo;
const modelId = await modelSaveApi(modelInfo);
const pointInfo = selectedKeys.value.map((key) => {
const point = parsePointKey(key)
return {
description: point.description,
pointId: point.pointId,
unit: point.unit,
Lower: point.Lower,
Upper: point.Upper,
dead: true,
limit: false,
}
})
const modelInfo = toRaw(props.beforeData) || {}
modelInfo.pointInfo = pointInfo
setProps({
submitButtonOptions: {
loading: true,
},
});
setTimeout(() => {
setProps({
submitButtonOptions: {
loading: false,
},
});
emit("next", modelId);
}, 1500);
} catch (error) {
console.error(error);
})
const modelId = await modelSaveApi(modelInfo)
emit('next', modelId)
}
}
const keyword = ref<string>("");
const searchParams = computed<Recordable>(() => {
return { keyword: unref(keyword) };
});
function onSearch(value: string) {
keyword.value = value;
}
async function searchPoint(params) {
if (params.keyword) {
const data = await pointListApi(params);
return data;
catch (error) {
console.error(error)
}
finally {
setProps({
submitButtonOptions: {
loading: false,
},
})
}
}
return {
register,
del,
add,
onSearch: useDebounceFn(onSearch, 300),
searchParams,
searchPoint,
handleReset: () => {
keyword.value = "";
},
};
selectedKeys,
}
},
});
})
</script>
<template>
@ -180,30 +131,19 @@ export default defineComponent({
</a-descriptions>
<a-divider />
<BasicForm @register="register">
<template #remoteSearch="{ model, field }">
<ApiSelect
v-model:value="model[field]"
:api="searchPoint"
show-search
:filter-option="false"
result-field="list"
label-field="name"
value-field="id"
:params="searchParams"
@search="onSearch"
<template #pointTransfer>
<PointTransfer
v-model:selected-keys="selectedKeys"
:prefill-points="beforeData?.pointInfo || []"
/>
</template>
<template #add="{ field }">
<Button v-if="Number(field) === 0" @click="add"> + </Button>
<Button v-if="field > 0" @click="del(field)"> - </Button>
</template>
</BasicForm>
</div>
</template>
<style lang="less" scoped>
.step3 {
width: 450px;
max-width: 1100px;
margin: 0 auto;
}
</style>

48
src/views/model/list/step/data.tsx

@ -83,51 +83,13 @@ export const step2Schemas: FormSchema[] = [
export const step3Schemas: FormSchema[] = [
{
field: 'point0',
field: 'pointTransfer',
component: 'Input',
label: '参数1',
required: true,
slot: 'remoteSearch',
componentProps: {
style: { width: '180px' },
},
label: '参数选择',
slot: 'pointTransfer',
colProps: {
span: 23,
},
},
{
field: 'point1',
component: 'Input',
label: '参数2',
required: true,
slot: 'remoteSearch',
componentProps: {
style: { width: '180px' },
},
colProps: {
span: 23,
},
},
{
field: 'point2',
component: 'Input',
label: '参数3',
required: true,
slot: 'remoteSearch',
componentProps: {
style: { width: '180px' },
},
colProps: {
span: 23,
},
},
{
field: '0',
component: 'Input',
label: ' ',
slot: 'add',
colProps: {
span: 1,
span: 24,
},
labelWidth: 120,
},
]

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

@ -25,7 +25,6 @@ import {
Steps,
Table,
Tabs,
Transfer,
} from 'ant-design-vue'
import VueECharts from 'vue-echarts'
import { pointTableSchema, sampleInfoTableSchema } from './data'
@ -45,9 +44,10 @@ import type { AssessReportSimple } from '@/api/alert/model/model/assessReportMod
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'
import PointTransfer from '../components/PointTransfer.vue'
import { buildPointKeyFromInfo, parsePointKey } from '../components/pointTransferHelper'
export default defineComponent({
components: {
@ -72,12 +72,12 @@ export default defineComponent({
AInputNumber: InputNumber,
AButton: Button,
ASpin: Spin,
ATransfer: Transfer,
ARow: Row,
ACol: Col,
ASelect: Select,
ASpace: Space,
Icon,
PointTransfer,
},
setup() {
const { createMessage } = useMessage()
@ -475,6 +475,10 @@ export default defineComponent({
}, 500)
function handleDeleteTrainTime(index: number) {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可修改训练时间')
return
}
if (!model.value?.trainTime)
return
//
@ -500,12 +504,20 @@ export default defineComponent({
updateModelInfoDebounced()
}
async function clearModel() {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可清除训练结果')
return
}
model.value.para = null
updateModelInfoDebounced()
await getHistory()
}
async function trainModel() {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可训练模型')
return
}
const modelInfo = model.value
if (!modelInfo || !modelInfo.id) {
console.error('模型信息不完整,无法训练')
@ -612,7 +624,6 @@ export default defineComponent({
dayjs(sorted[0].st),
dayjs(sorted[sorted.length - 1].et),
]
historyTime.value = trainRange
await getTestData(trainRange)
}
else {
@ -664,6 +675,10 @@ export default defineComponent({
}
function openPointModal(index, record) {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可编辑测点参数')
return
}
// index
const pageIndex = index
// index
@ -672,14 +687,21 @@ export default defineComponent({
)
openEditPointModal.value = true
pointEditRecord = record
const normalizeBool = (val: any) => {
if (val === true || val === '是' || val === 1 || val === '1')
return true
if (val === false || val === '否' || val === 0 || val === '0')
return false
return !!val
}
editForm.value = {
index: globalIndex,
Upper: record?.Upper ?? '',
Lower: record?.Lower ?? '',
lowerBound: record?.lowerBound ?? '',
upperBound: record?.upperBound ?? '',
dead: !!record?.dead,
limit: !!record?.limit,
dead: normalizeBool(record?.dead),
limit: normalizeBool(record?.limit),
}
}
@ -688,8 +710,13 @@ export default defineComponent({
alarmname: '全工况运行',
})
const openEditModeModal = ref<boolean>(false)
const normalizeVersion = (val?: string | null) => (val ? String(val).trim().toLowerCase() : '')
function openEditMode() {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可修改模式')
return
}
openEditModeModal.value = true
mode.value = {
alarmcondition: model.value?.alarmmodelset?.alarmcondition || '1=1',
@ -713,62 +740,31 @@ export default defineComponent({
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() {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可修改模型')
return
}
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})`,
editModelForm.value.selectedKeys = (model.value?.pointInfo || []).map(item => buildPointKeyFromInfo({
description: item.description ?? item.Description,
pointId: item.pointId ?? item.PointId,
unit: item.unit ?? item.Unit,
Lower: item.Lower ?? item.lower,
Upper: item.Upper ?? item.upper,
}))
console.log('transferData:', transferData.value)
openEditModelModal.value = true
}
function handleEditModelOk() {
if (!canEditModel.value) {
createMessage.warning('非草稿版本不可修改模型')
return
}
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('|')
const { description, pointId, unit, Lower, Upper } = parsePointKey(key)
return {
description,
pointId,
@ -794,9 +790,14 @@ export default defineComponent({
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 isVTestVersion = computed(() => {
const version = normalizeVersion(model.value?.Cur_Version || model.value?.version)
return version === '' || version === 'v-test'
})
const canEditModel = computed(() => {
const version = normalizeVersion(selectedVersion.value || model.value?.Cur_Version || model.value?.version)
return version === '' || version === 'v-test'
})
const isBottomed = computed(() => model.value?.btmState === '已下装')
const reportColumns = [
{ title: '评估时间', dataIndex: 'assessTime' },
@ -982,9 +983,7 @@ export default defineComponent({
createMessage.error('创建草稿版本失败')
}
}
const showTrainActions = computed(
() => !isBottomed.value || isVTestVersion.value || selectedVersion.value === 'v-test',
)
const showTrainActions = computed(() => canEditModel.value)
function goAssessReport(reportId?: number | null | Event) {
// PointerEvent
@ -996,6 +995,7 @@ export default defineComponent({
}
return {
normalizeVersion,
pointTable,
model,
selectedVersion,
@ -1028,8 +1028,6 @@ export default defineComponent({
clearModel,
openEditModelModal,
editModelForm,
transferData,
handleTransferSearch,
openEditModel,
handleEditModelOk,
handleEditModelCancel,
@ -1055,6 +1053,7 @@ export default defineComponent({
createDraftVersion,
goAssessReport,
showTrainActions,
canEditModel,
}
},
})
@ -1161,7 +1160,7 @@ export default defineComponent({
:bordered="false"
style="margin-top: 16px; margin-bottom: -20px"
>
<a-button size="large" @click="openEditMode">
<a-button size="large" :disabled="!canEditModel" @click="openEditMode">
{{ model?.alarmmodelset?.alarmname || '全工况运行' }}
</a-button>
</a-card>
@ -1172,21 +1171,28 @@ export default defineComponent({
<BasicTable @register="trainTimeTable">
<template #action="{ record, index }">
<a-button
v-if="showTrainActions"
type="link"
danger
@click="handleDeleteTrainTime(index)"
>
删除
</a-button>
<span v-else>--</span>
</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
v-if="showTrainActions"
type="primary"
@click="openPointModal(index, record)"
>
编辑
</a-button>
<span v-else>--</span>
</template>
</BasicTable>
</a-tab-pane>
@ -1398,21 +1404,13 @@ export default defineComponent({
</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="['可选测点', '已选测点']"
<PointTransfer
v-model:selected-keys="editModelForm.selectedKeys"
:prefill-points="model?.pointInfo || []"
:list-style="{
width: '500px',
height: '450px',
}"
@search="handleTransferSearch"
/>
</div>
</a-form>

Loading…
Cancel
Save