From 4a859a6d69984c77fa8166255c65f5a94eb0bd71 Mon Sep 17 00:00:00 2001 From: 潘志宝 <979469083@qq.com> Date: 星期五, 28 二月 2025 17:34:55 +0800 Subject: [PATCH] Merge branch 'master' of http://dlindusit.com:53929/r/iailab-plat-ui-vue3 --- src/views/data/ind/item/CalIndDefineForm.vue | 23 src/views/data/ind/item/IndHistoryChart.vue | 169 +++++ src/views/model/matlab/model/index.vue | 188 +++++ src/views/model/mpk/file/MpkRun.vue | 61 + src/views/data/ind/item/IndCurrentData.vue | 2 src/views/data/ind/item/AtomIndDefineForm.vue | 17 src/views/model/matlab/model/MatlabModelForm.vue | 394 +++++++++++ src/views/model/matlab/project/MatlabProjectModelDialog.vue | 101 +++ src/utils/download.ts | 5 src/api/model/matlab/project.ts | 33 + src/views/data/ind/item/index.vue | 17 src/views/model/matlab/model/MatlabModelSettingForm.vue | 144 ++++ src/views/model/mpk/file/index.vue | 17 src/router/modules/remaining.ts | 23 src/api/data/ind/item/item.ts | 9 src/views/data/ind/item/DerIndDefineForm.vue | 17 src/views/model/matlab/project/MatlabProjectForm.vue | 156 ++++ src/api/model/matlab/mlModel.ts | 29 src/views/model/pre/item/MmPredictItemForm.vue | 23 src/views/model/matlab/model/MatlabRun.vue | 279 ++++++++ src/views/model/matlab/project/index.vue | 221 ++++++ src/utils/dict.ts | 5 src/api/model/sche/model/index.ts | 1 src/views/model/sche/model/ScheduleModelForm.vue | 15 src/api/model/mpk/mpk.ts | 4 25 files changed, 1,928 insertions(+), 25 deletions(-) diff --git a/src/api/data/ind/item/item.ts b/src/api/data/ind/item/item.ts index ed09f65..0bb8833 100644 --- a/src/api/data/ind/item/item.ts +++ b/src/api/data/ind/item/item.ts @@ -27,6 +27,11 @@ itemCategory: string } +export type IndValueParam = { + itemNo: string + startDate: string + endTime: string +} // 查询列表 export const getItemPage = (params: PageParam) => { @@ -68,3 +73,7 @@ export const getItemCurrentData = (itemNo: string) => { return request.get({ url: '/data/api/query-ind/default-value?itemNo=' + itemNo}) } + +export const getItemValueData = (params: IndValueParam) => { + return request.get({ url: '/data/ind-item-value/getList', params}) +} diff --git a/src/api/model/matlab/mlModel.ts b/src/api/model/matlab/mlModel.ts new file mode 100644 index 0000000..980d10f --- /dev/null +++ b/src/api/model/matlab/mlModel.ts @@ -0,0 +1,29 @@ +import request from '@/config/axios' + +export const getPage = async (params: PageParam) => { + return await request.get({ url: '/model/matlab/model/page', params }) +} + +export const get = async (id: number) => { + return await request.get({ url: '/model/matlab/model/' + id }) +} + +export const create = async (data) => { + return await request.post({ url: '/model/matlab/model', data: data }) +} + +export const update = async (params) => { + return await request.put({ url: '/model/matlab/model', data: params }) +} + +export const deleteModel = async (id: number) => { + return await request.delete({ url: '/model/matlab/model?id=' + id }) +} + +export const list = (params) => { + return request.get({ url: '/model/matlab/model/list', params}) +} + +export const test = (data) => { + return request.post({ url: '/model/matlab/model/test', data}) +} diff --git a/src/api/model/matlab/project.ts b/src/api/model/matlab/project.ts new file mode 100644 index 0000000..225b952 --- /dev/null +++ b/src/api/model/matlab/project.ts @@ -0,0 +1,33 @@ +import request from '@/config/axios' + +export const getPage = async (params) => { + return await request.get({ url: '/model/matlab/project/page', params }) +} + +export const getProject = async (id: number) => { + return await request.get({ url: '/model/matlab/project/' + id }) +} + +export const createProject = async (data) => { + return await request.post({ url: '/model/matlab/project', data: data }) +} + +export const updateProject = async (params) => { + return await request.put({ url: '/model/matlab/project', data: params }) +} + +export const deleteProject = async (id: number) => { + return await request.delete({ url: '/model/matlab/project?id=' + id }) +} + +export const list = () => { + return request.get({ url: '/model/packageProject/project/list'}) +} + +export const getProjectModel = async (params) => { + return await request.get({ url: '/model/matlab/project/getProjectModel', params }) +} + +export const publish = async (data) => { + return await request.post({ url: '/model/matlab/project/publish', data }) +} diff --git a/src/api/model/mpk/mpk.ts b/src/api/model/mpk/mpk.ts index dc2782f..b0b880b 100644 --- a/src/api/model/mpk/mpk.ts +++ b/src/api/model/mpk/mpk.ts @@ -42,6 +42,10 @@ return request.post({ url: '/model/mpk/api/test', data: params }) } +export const saveModel = (data) => { + return request.downloadByPost({ url: '/model/mpk/api/saveModel', data }) +} + export const list = (params) => { return request.get({ url: '/model/mpk/file/list', params}) } diff --git a/src/api/model/sche/model/index.ts b/src/api/model/sche/model/index.ts index 51a9ce7..39a1184 100644 --- a/src/api/model/sche/model/index.ts +++ b/src/api/model/sche/model/index.ts @@ -165,5 +165,6 @@ 'MergeItem': mergeItemList, 'PLAN': planList, 'IND': indList, + 'IND_ASCII': indList, } } diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index f603ca5..c47f1a7 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -447,6 +447,29 @@ } ] }, + { + path: '/matlab', + component: Layout, + name: 'matlab', + meta: { + hidden: true + }, + children: [ + { + path: 'model/form/:id?', + component: () => import('@/views/model/matlab/model/MatlabModelForm.vue'), + name: 'MatlabModelForm', + meta: { + title: 'Matlab模型表单', + noCache: true, + hidden: true, + canTo: true, + icon: '', + activeMenu: '/matlab/model' + } + } + ] + }, ] export default remainingRouter diff --git a/src/utils/dict.ts b/src/utils/dict.ts index ce15a93..053411e 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -166,6 +166,8 @@ PRED_GRANULARITY = 'pred_granularity', ITEM_RUN_STATUS = 'item_run_status', RESULT_TYPE = 'result_type', + MATLAB_PLATFORM= 'matlab_platform', + MATLAB_VERSION= 'matlab_version', // ========== DATA - 数据平台模块 ========== DATA_FIELD_TYPE = 'data_field_type', TAG_DATA_TYPE = 'tag_data_type', @@ -189,5 +191,6 @@ MODEL_RESULT_TYPE = 'model_result_type', DATA_QUALITY = 'data_quality', ARC_TYPE = 'arc_type', - ARC_CALCULATE_TYPE = 'arc_calculate_type' + ARC_CALCULATE_TYPE = 'arc_calculate_type', + SOLIDIFY_FLAG = 'ind_solidify_flag' } diff --git a/src/utils/download.ts b/src/utils/download.ts index 1d07484..943374b 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -50,7 +50,10 @@ a.download = 'image.png' a.click() } - } + }, + downloadFile: (data: Blob, fileName: string) => { + download0(data, fileName, 'application/octet-stream') + }, } export default download diff --git a/src/views/data/ind/item/AtomIndDefineForm.vue b/src/views/data/ind/item/AtomIndDefineForm.vue index 97f2e34..6368c02 100644 --- a/src/views/data/ind/item/AtomIndDefineForm.vue +++ b/src/views/data/ind/item/AtomIndDefineForm.vue @@ -59,6 +59,22 @@ <el-input v-model="formData.unit"/> </el-form-item> </el-col> + <el-col :span="6"> + <el-form-item label="固化标识" prop="solidifyFlag"> + <el-select v-model="formData.solidifyFlag" + clearable + filterable + allow-create + placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.SOLIDIFY_FLAG)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> </el-row> <el-row> <el-col :span="12"> @@ -143,6 +159,7 @@ timeRange: '', timeGranularity: '', remark: '', + solidifyFlag: '', atomItem:{ dataSource:'', dataSet: '', diff --git a/src/views/data/ind/item/CalIndDefineForm.vue b/src/views/data/ind/item/CalIndDefineForm.vue index 52d78cc..28a1235 100644 --- a/src/views/data/ind/item/CalIndDefineForm.vue +++ b/src/views/data/ind/item/CalIndDefineForm.vue @@ -44,19 +44,35 @@ </el-col> </el-row> <el-row> - <el-col :span="8"> + <el-col :span="6"> <el-form-item label="指标精度" prop="precision"> <el-input v-model="formData.precision"/> </el-form-item> </el-col> - <el-col :span="8"> + <el-col :span="6"> <el-form-item label="转换系数" prop="coefficient"> <el-input v-model="formData.coefficient"/> </el-form-item> </el-col> - <el-col :span="8"> + <el-col :span="6"> <el-form-item label="数量单位" prop="unit"> <el-input v-model="formData.unit"/> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="固化标识" prop="solidifyFlag"> + <el-select v-model="formData.solidifyFlag" + clearable + filterable + allow-create + placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.SOLIDIFY_FLAG)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> </el-form-item> </el-col> </el-row> @@ -163,6 +179,7 @@ timeRange: '', timeGranularity: '', remark: '', + solidifyFlag:'', calItem: { id: '', expression: '', diff --git a/src/views/data/ind/item/DerIndDefineForm.vue b/src/views/data/ind/item/DerIndDefineForm.vue index dc35975..eccf7c0 100644 --- a/src/views/data/ind/item/DerIndDefineForm.vue +++ b/src/views/data/ind/item/DerIndDefineForm.vue @@ -80,6 +80,22 @@ <el-input v-model="formData.unit"/> </el-form-item> </el-col> + <el-col :span="6"> + <el-form-item label="固化标识" prop="solidifyFlag"> + <el-select v-model="formData.solidifyFlag" + clearable + filterable + allow-create + placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.SOLIDIFY_FLAG)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> </el-row> <el-row> <el-col :span="6"> @@ -188,6 +204,7 @@ businessType: '', timeRange: '', timeGranularity: '', + solidifyFlag:'', atomItem: { id: '', itemId: '', diff --git a/src/views/data/ind/item/IndCurrentData.vue b/src/views/data/ind/item/IndCurrentData.vue index 629cdec..624ca10 100644 --- a/src/views/data/ind/item/IndCurrentData.vue +++ b/src/views/data/ind/item/IndCurrentData.vue @@ -40,6 +40,6 @@ defineExpose({open}) // 提供 open 方法,用于打开弹窗 const getData = async() =>{ - dataForm.value.itemCurrentData = await ItemApi.getItemCurrentData(dataForm.value.itemNo); + dataForm.value.itemCurrentData = JSON.stringify(await ItemApi.getItemCurrentData(dataForm.value.itemNo)); } </script> diff --git a/src/views/data/ind/item/IndHistoryChart.vue b/src/views/data/ind/item/IndHistoryChart.vue new file mode 100644 index 0000000..e41ec10 --- /dev/null +++ b/src/views/data/ind/item/IndHistoryChart.vue @@ -0,0 +1,169 @@ +<template> + <el-dialog + title="历史值" + :close-on-click-modal="false" + width="50%" + v-model="visible" + > + <el-form + :inline="true" + :model="dataForm" + @keydown.enter="getDataList()" + > + <el-form-item label="开始时间"> + <el-date-picker + size="mini" + v-model="dataForm.startTime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + type="datetime" + :clearable="false" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="结束时间"> + <el-date-picker + size="mini" + v-model="dataForm.endTime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + type="datetime" + :clearable="false" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item> + <el-button @click="getDataList()">查询</el-button> + </el-form-item> + </el-form> + <div ref="chartDom" class="result-chart" v-loading="loading"></div> + </el-dialog> +</template> + +<script lang="ts" setup> +import {ref} from 'vue'; +import * as echarts from 'echarts'; +import * as ItemApi from '@/api/data/ind/item/item' +import {getYMDHM0} from "@/utils/dateUtil" +import download from "@/utils/download"; + +const message = useMessage() // 消息弹窗 +const visible = ref(false); +const chartDom = ref(null); +let myChart = null; +const chartParams = reactive({ + itemNo: undefined, + startTime: undefined, + endTime: undefined, +}) +const dataForm = ref({ + id: "", + itemNo: "", + itemName: "", + startTime: getYMDHM0(new Date() - 60 * 60 * 1000), + endTime: "", +}); + +/** 打开弹窗 */ +const open = async (row: object) => { + visible.value = true + dataForm.value.id = row.id; + dataForm.value.itemNo = row.itemNo; + dataForm.value.itemName = row.itemName; + getDataList(); +} + +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +const loading = ref(false) + +async function getDataList() { + visible.value = true; + loading.value = true + if (dataForm.value.id) { + try { + chartParams.itemNo = dataForm.value.itemNo; + chartParams.startTime = dataForm.value.startTime; + chartParams.endTime = dataForm.value.endTime; + const result = await ItemApi.getItemValueData(chartParams) + loading.value = false + let xData = result.map(obj => obj['dataTime']); + let yData = result.map(obj => obj['dataValue']); + let data = xData.map((x, index) => [x, yData[index]]); + let seriesData = [] + seriesData.push({ + name: dataForm.value.itemName, + type: "line", + data: data, + showSymbol: true, + smooth: false, + lineStyle: { + normal: { + color: "#5B8FF9", + width: 1, + }, + }, + }); + + myChart = echarts.init(chartDom.value); + const option = { + title: { + text: dataForm.value.itemName, + top: 0, + left: "1%", + textStyle: { + fontSize: 14, + }, + }, + tooltip: { + trigger: "axis", + axisPointer: { + type: "line", + lineStyle: { + color: "#cccccc", + width: "1", + type: "dashed", + }, + }, + }, + legend: { + show: false, + top: 10, + }, + grid: { + top: 30, + left: "3%", + right: "5%", + bottom: 10, + containLabel: true, + }, + xAxis: { + type: "category", + boundaryGap: false, + data: xData, + }, + yAxis: { + type: "value", + }, + dataZoom: [ + { + type: "inside", + }, + ], + series: seriesData, + }; + myChart.setOption(option); + } catch (error) { + console.error(error) + } + } +} + +</script> +<style> +.el-select { + width: 100%; +} + +.result-chart { + height: 500px; +} +</style> diff --git a/src/views/data/ind/item/index.vue b/src/views/data/ind/item/index.vue index f677122..3a3c9a9 100644 --- a/src/views/data/ind/item/index.vue +++ b/src/views/data/ind/item/index.vue @@ -91,11 +91,16 @@ 修改 </el-button> <el-button - v-hasPermi="['data:ind-item:update']" link type="primary" @click="getCurrentData(scope.row.itemNo)"> 当前值 + </el-button> + <el-button + link + type="primary" + @click="getHistoryData(scope.row)"> + 历史值 </el-button> <el-button v-hasPermi="['data:ind-item:delete']" @@ -124,7 +129,8 @@ <DerIndDefineForm ref="derFormRef" @success="getList" /> <CalIndDefineForm ref="calFormRef" @success="getList" /> <SelectItemType ref="itemTypeSel"/> - <IndCurrentData ref="indCurrentData" /> + <IndCurrentData ref="indCurrentData"/> + <IndHistoryChart ref="indHistoryChart"/> </template> <script lang="ts" setup> @@ -139,6 +145,7 @@ import * as ItemApi from '@/api/data/ind/item/item' import * as CategoryApi from "@/api/data/ind/category"; import IndCurrentData from './IndCurrentData.vue' + import IndHistoryChart from './IndHistoryChart.vue' import {handleTree} from "@/utils/tree"; @@ -219,11 +226,15 @@ } } const indCurrentData = ref() - const getCurrentData = (itemNo: string) => { indCurrentData.value.open(itemNo) } + const indHistoryChart = ref() + const getHistoryData = (raw: object) => { + indHistoryChart.value.open(raw) + } + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { diff --git a/src/views/model/matlab/model/MatlabModelForm.vue b/src/views/model/matlab/model/MatlabModelForm.vue new file mode 100644 index 0000000..8cd37be --- /dev/null +++ b/src/views/model/matlab/model/MatlabModelForm.vue @@ -0,0 +1,394 @@ +<template> + <div class="p-16px" style="background-color: #ffffff"> + <el-header> + {{title}} + </el-header> + <el-main> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="120px" + > + <el-divider content-position="left">模型信息</el-divider> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="模型类型" prop="modelType"> + <el-radio-group v-model="formData.modelType"> + <el-radio-button :disabled="actionType == 'edit'" + v-for="dict in getDictOptions(DICT_TYPE.MODEL_TYPE)" + :key="dict.label" + :label="dict.value" + > + {{ dict.label }} + </el-radio-button> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="模型名称" prop="modelName"> + <el-input v-model="formData.modelName" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="模型文件" prop="modelFileName"> + <el-input disabled v-model="formData.modelFileName" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="4"> + <el-upload + ref="uploadRef" + v-model:file-list="fileList" + :show-file-list="false" + :action="importUrl" + :auto-upload="true" + :disabled="uploadLoading" + v-loading="uploadLoading" + :before-upload="beforeUpload" + :headers="uploadHeaders" + :on-error="submitFormError" + :on-success="submitFormSuccess" + accept=".jar" + > + <el-tooltip content="上传算法封装.jar文件" placement="top" effect="light"> + <el-button type="primary"> + <Icon icon="ep:upload"/> + 模型上传 + </el-button> + </el-tooltip> + </el-upload> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="6"> + <el-form-item label="MATLAB平台" prop="matlabPlatform"> + <el-select v-model="formData.matlabPlatform"> + <el-option + v-for="item in getDictOptions(DICT_TYPE.MATLAB_PLATFORM)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="MATLAB版本" prop="matlabVersion"> + <el-select v-model="formData.matlabVersion"> + <el-option + v-for="item in getDictOptions(DICT_TYPE.MATLAB_VERSION)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="" type="textarea"/> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">模型方法</el-divider> +<!-- <el-row :gutter="20">--> +<!-- <el-col :span="4">--> +<!-- <el-button type="primary" size="small" @click="addRow()" >新增</el-button>--> +<!-- </el-col>--> +<!-- </el-row>--> + <el-table :data="formData.modelMethods" border + @expand-change="methodExpandChange" :expand-row-keys="methodExpandedRowKeys" :row-key="row => row.id"> + <el-table-column + prop="" + label="全类名" + align="center" + width="400"> + <template #default="scope"> + <el-input disabled size="small" v-model="scope.row.className" placeholder=""/> + </template> + </el-table-column> + <el-table-column + prop="" + label="方法名" + align="center" + width="250"> + <template #default="scope"> + <el-input disabled size="small" v-model="scope.row.methodName" placeholder=""/> + </template> + </el-table-column> + <el-table-column + prop="" + label="数据长度" + align="center"> + <template #default="scope"> + <el-input-number disabled size="small" step-strictly v-model="scope.row.dataLength" :min="1" + :max="50" value-on-clear="min"/> + </template> + </el-table-column> + <el-table-column + prop="" + label="输出长度" + align="center"> + <template #default="scope"> + <el-input-number disabled size="small" step-strictly v-model="scope.row.outLength" :min="1" + :max="50" value-on-clear="min"/> + </template> + </el-table-column> + <el-table-column label="方法参数" type="expand" width="100px"> + <template #default="props"> + <div class="m-16px"> + <el-button type="primary" size="small" @click="addSetting(props.row.methodSettings)">新增参数</el-button> + <el-table :data="props.row.methodSettings" border size="small"> + <el-table-column align="center" label="参数名称" prop="name"/> + <el-table-column align="center" label="key" prop="settingKey"/> + <el-table-column align="center" label="value" prop="settingValue"/> + <el-table-column align="center" label="参数类型" prop="valueType"/> + <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100"> + <template #default="scope"> + <el-button + @click="updateSetting(scope.row)" + key="danger" + type="primary" + link + >修改 + </el-button> + <el-button + @click="deleteSetting(props.row.methodSettings,scope.$index)" + key="danger" + type="danger" + link + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + </div> + </template> + </el-table-column> + <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100"> + <template #default="scope"> + <el-button + @click="deleteRow(scope.$index)" + key="danger" + type="danger" + link + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + </el-form> + </el-main> + <el-footer> + <div class="flex flex-row justify-end items-center"> + <el-button type="primary" @click="submitForm">确 定</el-button> + </div> + </el-footer> + </div> + <SettingForm ref="settingFormRef"/> +</template> +<script lang="ts" setup> + import {DICT_TYPE,getDictOptions} from '@/utils/dict'; + import * as MatlabApi from '@/api/model/matlab/mlModel' + import {FormRules} from 'element-plus' + import {getAccessToken, getTenantId} from "@/utils/auth"; + import SettingForm from './MatlabModelSettingForm.vue' + import {generateUUID} from "@/utils"; + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + const title = ref('') // 弹窗的标题 + const actionType = ref('') // 操作类型 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formType = ref('') // 表单的类型:create - 新增;update - 修改 + const route = useRoute() // 路由 + const router = useRouter(); + + const staticDir = ref(import.meta.env.VITE_STATIC_DIR) + +/** settingForm弹窗 */ + const settingFormRef = ref() + // 添加setting + const addSetting = (methodSettings) => { + settingFormRef.value.open(undefined,methodSettings) + } + + // 修改setting + const updateSetting = (info) => { + settingFormRef.value.open(info) + } + // 删除setting + const deleteSetting = (methodSettings,index) => { + methodSettings.splice(index, 1); + } + + const methodExpandedRowKeys = ref([]) + const methodExpandChange = async (row: any, expandedRows: any[]) => { + methodExpandedRowKeys.value = expandedRows.map(e => e.id) + } + + const formData = ref({ + id: route.params.id, + modelName: undefined, + modelFileName: undefined, + modelFilePath: undefined, + modelType: 'predict', + matlabPlatform: undefined, + matlabVersion: undefined, + remark: undefined, + modelMethods: [], + }) + + const formRules = reactive<FormRules>({ + modelFileName: [ + {required: true, message: '模型名称不能为空,请上传模型jar文件', trigger: 'blur'} + ], + modelName: [ + {required: true, message: '模型中文名称不能为空', trigger: 'blur'} + ], + modelType: [ + {required: true, message: '模型类型不能为空', trigger: 'blur'} + ], + matlabPlatform: [ + {required: true, message: 'MATLAB平台不能为空', trigger: 'blur'} + ], + matlabVersion: [ + {required: true, message: 'MATLAB版本不能为空', trigger: 'blur'} + ], + }) + + const formRef = ref() // 表单 Ref + + /** 提交表单 */ + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 模型方法校验 + if (formData.value.modelMethods?.length <= 0) { + message.error('模型方法为空') + return + } + // 模型方法名称校验 + if (formData.value.modelMethods.some(e => e.methodName === undefined || e.methodName === '' || e.dataLength === undefined || e.dataLength === null)) { + message.error('存在不合法模型方法名') + return + } + // 提交请求 + formLoading.value = true + try { + const data = formData.value + if (formType.value === 'create') { + await MatlabApi.create(data) + message.success(t('common.createSuccess')) + } else { + await MatlabApi.update(data) + message.success(t('common.updateSuccess')) + } + } finally { + formLoading.value = false + } + // router.push({path:'/model/mpk'}) + router.back() + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + id: undefined, + modelFileName: undefined, + modelName: undefined, + modelType: 'predict', + remark: undefined, + modelMethods: [], + modelFilePath: undefined + } + formRef.value?.resetFields() + } + + const handleChange = function () { + + } + + const addRow = function () { + formData.value.modelMethods.push({ + id: generateUUID(), + className: undefined, + methodName: undefined, + dataLength: 1, + outLength: 1, + methodSettings: [] + }) + } + const deleteRow = function (index) { + formData.value.modelMethods.splice(index, 1) + } + + const fileList = ref([]) // 文件列表 + const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/matlab/model/upload' + const uploadLoading = ref(false) // 表单的加载中 + const uploadHeaders = ref() // 上传 Header 头 + const beforeUpload = function (file) { + // 提交请求 + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + uploadLoading.value = true + return true; + } + const submitFormError = (): void => { + message.error('上传失败!') + uploadLoading.value = false + } + const submitFormSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + uploadLoading.value = false + return + } + const data = response.data; + formData.value.modelFilePath = data.filePath + formData.value.modelFileName = data.fileName + const modelMethods = (data.classInfos || []).flatMap(e => { + return (e.methodInfos || []).map(m => { + return { ...m,id: generateUUID(), className: e.className,methodSettings: [] }; + }); + }); + + formData.value.modelMethods = modelMethods + message.success('上传成功') + uploadLoading.value = false + } + + onMounted(async () => { + const id = formData.value.id; + const type = id ? 'edit' : 'create' + actionType.value = type + title.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + debugger + formData.value = await MatlabApi.get(id) + debugger + } finally { + formLoading.value = false + } + } + }) +</script> + +<style scoped lang="scss"> +</style> diff --git a/src/views/model/matlab/model/MatlabModelSettingForm.vue b/src/views/model/matlab/model/MatlabModelSettingForm.vue new file mode 100644 index 0000000..27c8931 --- /dev/null +++ b/src/views/model/matlab/model/MatlabModelSettingForm.vue @@ -0,0 +1,144 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="参数名称" prop="name"> + <el-input v-model="formData.name" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="参数类型" prop="valueType"> + <el-select v-model="formData.valueType"> + <el-option + v-for="item in getDictOptions(DICT_TYPE.MODEL_METHOD_SETTING_VALUE_TYPE)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="key" prop="settingKey"> + <el-input v-model="formData.settingKey" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="value" prop="settingValue"> + <el-input v-model="formData.settingValue" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import {DICT_TYPE,getDictOptions} from '@/utils/dict'; + import {FormRules} from 'element-plus' + + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('参数设置') // 弹窗的标题 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formData = ref({ + settingKey: undefined, + settingValue: undefined, + name: undefined, + valueType: undefined + }) + + + const formRules = reactive<FormRules>({ + settingKey: [ + {required: true, message: 'key不能为空', trigger: 'blur'}, + ], + settingValue: [ + {required: true, message: 'value不能为空', trigger: 'blur'}, + ], + name: [ + {required: true, message: '参数名称不能为空', trigger: 'blur'}, + ], + valueType: [ + {required: true, message: '参数类型不能为空', trigger: 'blur'}, + ] + }) + + const formRef = ref() // 表单 Ref + + let methodSettingsRef = undefined + let infoRef = undefined + /** 打开弹窗 */ + const open = async (info,methodSettings) => { + dialogVisible.value = true + resetForm() + // 修改时,设置数据 + if (info) { + infoRef = info + formLoading.value = true + try { + formData.value = {...info} + } finally { + formLoading.value = false + } + }else { + methodSettingsRef = methodSettings + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + + // 数据回调 + const emit = defineEmits(['addSettingCallback']) + /** 提交表单 */ + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + + // 提交请求 + formLoading.value = true + try { + if (infoRef) { + // 修改 + for (let key in formData.value) { + infoRef[key] = formData.value[key]; + } + infoRef = undefined; + }else { + // 新增 + methodSettingsRef.push({...formData.value}) + } + dialogVisible.value = false + } finally { + formLoading.value = false + } + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + settingKey: undefined, + settingValue: undefined, + name: undefined, + valueType: undefined, + } + formRef.value?.resetFields() + } +</script> diff --git a/src/views/model/matlab/model/MatlabRun.vue b/src/views/model/matlab/model/MatlabRun.vue new file mode 100644 index 0000000..b43dc11 --- /dev/null +++ b/src/views/model/matlab/model/MatlabRun.vue @@ -0,0 +1,279 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + class="-mb-15px" + :model="formData" + ref="formRef" + :inline="true" + :rules="formRules" + label-width="68px" + v-loading="formLoading" + > + <el-form-item style="width: 100%"> + <el-divider content-position="left">模型信息</el-divider> + </el-form-item> + <el-row> + <el-col :span="24"> + <el-form-item label="全类名" style="width: 100%" prop="className"> + <el-input disabled v-model="formData.className" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="方法名" prop="methodName"> + <el-select v-model="formData.methodId" @change="methodChange" style="width: 240px"> + <el-option + v-for="item in methodList" + :key="item.id" + :label="item.className + '.' + item.methodName + '()'" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">模型参数信息</el-divider> + <div style="display:flex;flex-direction: row;align-items: center;margin-bottom: 6px"> + <el-button tag="a" :href="staticDir + '/template/模型参数导入模板.xlsx'" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button> + <el-upload + ref="uploadRef" + v-model:file-list="fileList" + :show-file-list="false" + :action="importUrl" + :auto-upload="true" + :disabled="formLoading" + :before-upload="beforeUpload" + :headers="uploadHeaders" + :on-error="submitFormError" + :on-success="submitFormSuccess" + accept=".xlsx" + > + <el-button type="primary" size="small" link>参数导入</el-button> + </el-upload> + </div> + <el-row v-for="(item,index) in datas" :key="index" :gutter="20"> + <el-col :span="24"> + <el-form-item :label="'参数_' + (index)" required style="width: 100%"> + <el-input type="textarea" :disabled="true" :rows="3" v-model="datas[index]" placeholder="" /> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">模型设置信息</el-divider> +<!-- <el-row :gutter="20">--> +<!-- <el-col :span="4">--> +<!-- <el-button type="primary" size="small" @click="addRow()">新增</el-button>--> +<!-- </el-col>--> +<!-- </el-row>--> + <el-table :data="formData.modelSettings" border> + <el-table-column + prop="" + label="参数key" + align="center"> + <template #default="scope"> + <el-input size="small" v-model="scope.row.settingKey" :disabled="true" maxlength="50" clearable /> + </template> + </el-table-column> + <el-table-column + prop="" + label="参数名称" + align="center"> + <template #default="scope"> + <el-input size="small" v-model="scope.row.name" :disabled="true" maxlength="50" clearable /> + </template> + </el-table-column> + <el-table-column + prop="" + label="参数value" + align="center"> + <template #default="scope"> + <el-input size="small" v-model="scope.row.settingValue" :disabled="scope.row.settingKey === 'pyFile'" maxlength="50" clearable /> + </template> + </el-table-column> +<!-- <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">--> +<!-- <template #default="scope">--> +<!-- <el-button--> +<!-- @click="deleteRow(scope.$index)"--> +<!-- key="danger"--> +<!-- type="danger"--> +<!-- :disabled="scope.row.settingKey === 'pyFile'"--> +<!-- link--> +<!-- >删除</el-button>--> +<!-- </template>--> +<!-- </el-table-column>--> + </el-table> + <el-divider content-position="left">模型运行结果</el-divider> + <el-input v-model="modelRunResult" placeholder="" rows="4" type="textarea" /> + <div style="display: flex;flex-direction: row;justify-content: end;margin-top: 16px"> + <el-button :loading="modelRunloading" type="primary" @click="modelRun()">运行</el-button> + </div> + </el-form> + </Dialog> +</template> +<script lang="ts" setup> + import * as MlModelApi from '@/api/model/matlab/mlModel' + import {FormRules} from "element-plus"; + import {getAccessToken, getTenantId} from "@/utils/auth"; + import download from "@/utils/download"; + const staticDir = ref(import.meta.env.VITE_STATIC_DIR) + + const { t } = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('模型运行') // 弹窗的标题 + + const formData = reactive({ + modelFileName: '', + className: '', + methodName: '', + methodId: '', + uuids: [], + modelSettings: [], + outLength: 1 + }) + + const datas = ref([]) + + // 模型方法下拉列表 + const methodList = ref([]) + + /** 打开弹窗 */ + const open = async (row) => { + dialogVisible.value = true + formData.modelFileName = row.modelFileName + const matlabModel = await MlModelApi.get(row.id) + methodList.value = matlabModel.modelMethods + formData.className = matlabModel.modelMethods[0].className + formData.methodId = matlabModel.modelMethods[0].id + formData.methodName = matlabModel.modelMethods[0].methodName + formData.outLength = matlabModel.modelMethods[0].outLength + datas.value = [] + formData.uuids = []; + for (let i = 0 ; i < matlabModel.modelMethods[0].dataLength ; i++) { + datas.value[i] = '[[]]'; + formData.uuids[i] = ''; + } + + // 回显参数 + if (matlabModel.modelMethods[0].methodSettings && matlabModel.modelMethods[0].methodSettings.length > 0) { + formData.modelSettings = matlabModel.modelMethods[0].methodSettings + } + + } + defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + + const formRules = reactive<FormRules>({ + methodName: [ + {required: true, message: '方法名不能为空', trigger: 'blur'} + ], + className: [ + {required: true, message: '全类名不能为空', trigger: 'blur'} + ] + }) + + const addRow = function () { + formData.modelSettings.push({ + settingKey: '', + settingValue: '' + }) + } + const deleteRow = function (index) { + formData.modelSettings.splice(index, 1) + } + const methodChange = function (value) { + datas.value = [] + formData.uuids = []; + var method = methodList.value.find(e => e.id === value); + formData.methodName = method.methodName + formData.className = method.className + for (let i = 0 ; i < method?.dataLength ; i++) { + datas.value[i] = '[[]]'; + formData.uuids[i] = ''; + } + // 回显参数 + if (method.methodSettings && method.methodSettings.length > 0) { + formData.modelSettings = method.methodSettings + }else { + formData.modelSettings = [] + } + } + + const fileList = ref([]) // 文件列表 + const importUrl = + import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/matlab/model/importData' + const formLoading = ref(false) // 表单的加载中 + const uploadHeaders = ref() // 上传 Header 头 + /** 上传错误提示 */ + const submitFormError = (): void => { + message.error('导入失败,请检查导入文件!') + formLoading.value = false + } + const submitFormSuccess = (response: any) => { + try { + if (response.code !== 0) { + message.error(response.msg) + return + } + const data = response.data; + if (datas.value.length > data.length) { + message.error("导入数据长度为" + data.length + ",应≥" + datas.value.length) + return + } + for (let i = 0; i < datas.value.length; i++) { + datas.value[i] = data[i].data + formData.uuids[i] = data[i].uuid; + } + message.success('导入成功') + } finally { + formLoading.value = false + } + } + const beforeUpload = function (file) { + // 提交请求 + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + formLoading.value = true + return true; + } + + // 模型运行结果 + const modelRunResult = ref('') + // 模型运行loading + const modelRunloading = ref(false) + // 表单 Ref + const formRef = ref() + // 运行 + const modelRun = async () => { + modelRunResult.value = '' + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + + + // 提交请求 + modelRunloading.value = true + try { + const data = { + ...formData + } + + //处理modelSettings + // let settingsPredict = {}; + // data.modelSettings.forEach(e => { + // settingsPredict[e.settingKey] = e.settingValue; + // }) + // data.modelSettings = settingsPredict + + let result = await MlModelApi.test(data) + + modelRunResult.value = result; + message.success('运行成功') + } finally { + modelRunloading.value = false + } + } +</script> diff --git a/src/views/model/matlab/model/index.vue b/src/views/model/matlab/model/index.vue new file mode 100644 index 0000000..9f17b86 --- /dev/null +++ b/src/views/model/matlab/model/index.vue @@ -0,0 +1,188 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + @submit.prevent + > + <el-form-item label="模型名称" prop="pyChineseName"> + <el-input + v-model="queryParams.modelName" + placeholder="请输入模型名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item label="模型文件" prop="modelFileName"> + <el-input + v-model="queryParams.modelFileName" + placeholder="请输入模型文件名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <div class="ml-12px"> + <router-link :to="'/matlab/model/form'"> + <el-button type="primary" plain v-hasPermi="['ml:model:create']"> + <Icon icon="ep:plus" class="mr-5px"/>新增</el-button> + </router-link> + </div> + + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="modelName" label="模型名称" header-align="center" align="center" min-width="100" /> + <el-table-column prop="modelFileName" label="模型文件" header-align="center" align="center" min-width="250"/> + <el-table-column prop="modelType" label="模型类型" header-align="center" align="center" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/> + <el-table-column prop="matlabPlatform" label="matlab平台" header-align="center" align="center"/> + <el-table-column prop="matlabVersion" label="matlab版本" header-align="center" align="center"/> +<!-- <el-table-column prop="remark" label="备注" header-align="center" align="center" min-width="100px"/>--> + <el-table-column prop="createDate" label="创建时间" header-align="center" align="center" :formatter="dateFormatter" width="180px"/> + <el-table-column prop="updateDate" label="修改时间" header-align="center" align="center" :formatter="dateFormatter" width="180px"/> + <el-table-column label="操作" align="center" width="200px"> + <template #default="scope"> + <div class="flex items-center justify-center"> + <router-link :to="'/matlab/model/form/' + scope.row.id"> + <el-button type="primary" link v-hasPermi="['ml:model:update']"> + <Icon icon="ep:edit"/>修改 + </el-button> + </router-link> + <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ml:model:delete']"> + <Icon icon="ep:delete"/>删除 + </el-button> + <div class="pl-12px"> + <el-dropdown @command="(command) => handleCommand(command, scope.row)" trigger="click"> + <el-button type="primary" link> + <Icon icon="ep:d-arrow-right" /> 更多 + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item + command="mpkRunDialog" + > + 运行 + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </div> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <MatlabRun ref="matlabRun" /> +</template> +<script lang="ts" setup> + import {dateFormatter} from '@/utils/formatTime' + import * as MlModelApi from '@/api/model/matlab/mlModel' + import { DICT_TYPE, getDictLabel } from '@/utils/dict' + import MatlabRun from './MatlabRun.vue' + + defineOptions({name: 'MatlabModel'}) + + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + page: 1, + limit: 10, + modelName: '', + modelFileName: '' + }) + const queryFormRef = ref() // 搜索的表单 + + const getList = async () => { + loading.value = true + try { + const data = await MlModelApi.getPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } + } + + /** 操作分发 */ + const handleCommand = (command: string, row) => { + switch (command) { + case 'mpkRunDialog': + matlabRunDialog(row) + break + default: + break + } + } + + /** 搜索按钮操作 */ + const handleQuery = () => { + getList() + } + + /** 重置按钮操作 */ + const resetQuery = () => { + queryParams.page = 1 + queryFormRef.value.resetFields() + handleQuery() + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MlModelApi.deleteModel(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + const matlabRun = ref(); + const matlabRunDialog = (row) => { + matlabRun.value.open(row); + } + + onActivated((to) => { + getList() + }) + + /** 初始化 **/ + onMounted(async () => { + await getList() + }) +</script> diff --git a/src/views/model/matlab/project/MatlabProjectForm.vue b/src/views/model/matlab/project/MatlabProjectForm.vue new file mode 100644 index 0000000..e5b379d --- /dev/null +++ b/src/views/model/matlab/project/MatlabProjectForm.vue @@ -0,0 +1,156 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row :gutter="20"> + <el-col :span="10"> + <el-form-item label="项目名称" prop="projectName"> + <el-input v-model="formData.projectName" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="10"> + <el-form-item label="项目编码" prop="projectCode"> + <el-input v-model="formData.projectCode" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="24"> + <el-form-item label="关联模型" prop="models"> + <el-transfer style="width: 100%" :props="{key: 'id',label: 'modelFileName'}" :titles="['未选模型', '已选模型']" target-order="unshift" filterable :filter-method="filterMethod" v-model="formData.models" :data="modelList"> + <template #default="{ option }"> + <span :title="option.modelFileName + '【' + option.modelName + '】'">{{ option.modelFileName}}</span> + </template> + </el-transfer> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import * as ProjectApi from '@/api/model/matlab/project' + import * as MlModelApi from '@/api/model/matlab/mlModel' + import {FormRules} from 'element-plus' + + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('') // 弹窗的标题 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formType = ref('') // 表单的类型:create - 新增;update - 修改 + const formData = ref({ + id: undefined, + projectName: undefined, + projectCode: undefined, + models: undefined, + }) + + + const formRules = reactive<FormRules>({ + projectName: [ + {required: true, message: '项目名称不能为空', trigger: 'blur'}, + ], + projectCode: [ + {required: true, message: '项目编码不能为空', trigger: 'blur'}, + ], + }) + + const formRef = ref() // 表单 Ref + + /** 打开弹窗 */ + const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + getModelList(); + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + const data = await ProjectApi.getProject(id) + data.models = data.models.map(e => e.id) + formData.value = { + ...data + } + } finally { + formLoading.value = false + } + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + /** 提交表单 */ + const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = { + ...formData.value + } + if (data.models && data.models.length > 0) { + data.models = data.models.map(e => { + return {id: e} + }) + } + if (formType.value === 'create') { + await ProjectApi.createProject(data) + message.success(t('common.createSuccess')) + } else { + await ProjectApi.updateProject(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + id: undefined, + projectName: undefined, + projectCode: undefined, + } + formRef.value?.resetFields() + } + + // 所有模型列表 + const modelList = ref([]) + const getModelList = async () => { + modelList.value = await MlModelApi.list({}) + } + + // 模型筛选 + const filterMethod = function (query, item) { + return item.modelFileName.toLowerCase().indexOf(query.toLowerCase()) !== -1 + } +</script> + +<style scoped> + :deep(.el-transfer-panel) { + width: 40%; + } +</style> diff --git a/src/views/model/matlab/project/MatlabProjectModelDialog.vue b/src/views/model/matlab/project/MatlabProjectModelDialog.vue new file mode 100644 index 0000000..3d950aa --- /dev/null +++ b/src/views/model/matlab/project/MatlabProjectModelDialog.vue @@ -0,0 +1,101 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="80%"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="模型名称" prop="modelFileName"> + <el-input + v-model="queryParams.modelFileName" + placeholder="请输入模型名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="getList"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + border + > + <el-table-column prop="modelName" label="模型中文名称"/> + <el-table-column prop="modelFileName" label="模型名称"/> + <el-table-column prop="modelType" label="模型类型" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/> + <el-table-column prop="remark" label="备注" width="300px"/> + <el-table-column label="模型方法" type="expand" width="100px"> + <template #default="props"> + <el-table :data="props.row.modelMethods"> + <el-table-column align="center" label="全类名" prop="className" /> + <el-table-column align="center" label="方法名" prop="methodName" /> + <el-table-column align="center" label="参数长度" prop="dataLength" /> + <el-table-column align="center" label="输出长度" prop="outLength" /> + </el-table> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + </Dialog> +</template> +<script lang="ts" setup> + import download from "@/utils/download"; + import * as projectApi from '@/api/model/matlab/project' + import { dateFormatter } from '@/utils/formatTime' + import { DICT_TYPE, getDictLabel } from '@/utils/dict' + + + const { t } = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('关联模型') // 弹窗的标题 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + page: 1, + pageSize: 10, + projectId: undefined, + modelFileName: undefined, + }) + + /** 打开弹窗 */ + const open = async (projectId: String) => { + dialogVisible.value = true + + queryParams.projectId = projectId; + getList() + } + defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + + const getList = async () => { + loading.value = true + try { + let data = await projectApi.getProjectModel(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } + } +</script> diff --git a/src/views/model/matlab/project/index.vue b/src/views/model/matlab/project/index.vue new file mode 100644 index 0000000..8f1799a --- /dev/null +++ b/src/views/model/matlab/project/index.vue @@ -0,0 +1,221 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="项目名称" prop="projectName"> + <el-input + v-model="queryParams.projectName" + placeholder="请输入项目名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item label="项目编码" prop="projectCode"> + <el-input + v-model="queryParams.projectCode" + placeholder="请输入项目编码" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['mpk:project:create']" + > + <Icon icon="ep:plus" class="mr-5px"/> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="projectName" label="项目名称"/> + <el-table-column prop="projectCode" label="项目编码"/> + <el-table-column prop="createTime" label="创建时间" :formatter="dateFormatter" width="300px"/> + <el-table-column label="操作" align="center" width="300px"> + <template #default="scope"> + <div class="flex items-center justify-center"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['mpk:project:update']" + > + <Icon icon="ep:edit"/> + 修改 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['mpk:project:delete']" + > + <Icon icon="ep:delete"/> + 删除 + </el-button> + <el-button + link + type="primary" + @click="viewRelevanceModel(scope.row.id)" + > + <Icon icon="ep:link"/> + 查看关联模型 + </el-button> + <div class="pl-12px"> + <el-dropdown @command="(command) => handleCommand(command, scope.row)" + trigger="click"> + <el-button type="primary" link> + <Icon icon="ep:d-arrow-right"/> + 更多 + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item + command="publish" + > + <el-button link>发布</el-button> + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </div> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <ProjectForm ref="formRef" @success="getList"/> + <!-- 关联模型 --> + <RelevanceModel ref="relevanceModelRef"/> +</template> +<script lang="ts" setup> + import {dateFormatter} from '@/utils/formatTime' + import * as ProjectApi from '@/api/model/matlab/project' + import ProjectForm from './MatlabProjectForm.vue' + import RelevanceModel from './MatlabProjectModelDialog.vue' + + defineOptions({name: 'MatlabProject'}) + + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + page: 1, + limit: 10, + projectName: '', + projectCode: '' + }) + const queryFormRef = ref() // 搜索的表单 + + const getList = async () => { + loading.value = true + try { + const data = await ProjectApi.getPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } + } + + /** 操作分发 */ + const handleCommand = (command: string, row) => { + switch (command) { + case 'publish': + publish(row.id,row.projectName) + break + default: + break + } + } + + // 发布 + const publish = async (projectId,projectName) => { + // 发布的二次确认 + await message.confirm('确认发布 ' + projectName) + + // 发布 + await ProjectApi.publish({projectId}) + + message.success('发布成功'); + } + + /** 搜索按钮操作 */ + const handleQuery = () => { + getList() + } + + /** 重置按钮操作 */ + const resetQuery = () => { + queryParams.page = 1 + queryFormRef.value.resetFields() + handleQuery() + } + + /** 添加/修改操作 */ + const formRef = ref() + const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ProjectApi.deleteProject(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + // 查看关联模型 + const relevanceModelRef = ref() + const viewRelevanceModel = (id) => { + relevanceModelRef.value.open(id) + } + + /** 初始化 **/ + onMounted(async () => { + await getList() + }) +</script> diff --git a/src/views/model/mpk/file/MpkRun.vue b/src/views/model/mpk/file/MpkRun.vue index 2b9039c..908d883 100644 --- a/src/views/model/mpk/file/MpkRun.vue +++ b/src/views/model/mpk/file/MpkRun.vue @@ -110,6 +110,7 @@ <!-- </el-table-column>--> </el-table> <el-divider content-position="left">模型运行结果</el-divider> + <el-button type="primary" size="small" link @click="saveModel" v-if="showSaveModel && formData.methodName === 'train'">下载模型(.miail)</el-button> <el-input v-model="modelRunResult" placeholder="" rows="4" type="textarea" /> <div style="display: flex;flex-direction: row;justify-content: end;margin-top: 16px"> <el-button :loading="modelRunloading" type="primary" @click="modelRun()">运行</el-button> @@ -121,6 +122,7 @@ import * as MpkApi from '@/api/model/mpk/mpk' import {FormRules} from "element-plus"; import {getAccessToken, getTenantId} from "@/utils/auth"; + import download from "@/utils/download"; const staticDir = ref(import.meta.env.VITE_STATIC_DIR) const { t } = useI18n() // 国际化 @@ -167,6 +169,7 @@ return e; }) } + } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 @@ -260,7 +263,8 @@ // 运行 const modelRun = async () => { modelRunResult.value = '' -// 校验表单 + showSaveModel.value = false + // 校验表单 if (!formRef) return const valid = await formRef.value.validate() if (!valid) return @@ -302,10 +306,63 @@ data.model = undefined } - modelRunResult.value = await MpkApi.modelRun(data) + let result = await MpkApi.modelRun(data) + + modelRunResult.value = result; message.success('运行成功') + // 训练方法 + if (formData.methodName === 'train') { + result = JSON.parse(result); + // 返回结果正确 + if (result?.status_code === '100' && result?.models?.model_path) { + // 有预测方法 + if (methodList.value.some(e => e.methodName === 'predict')) { + saveModelParams.modelResult = result + saveModelParams.model = result?.models + showSaveModel.value = true + } + } + } } finally { modelRunloading.value = false } } + + const showSaveModel = ref(false) + + const saveModelParams = reactive({ + pyName: '', + className: '', + methodName: '', + uuids: [], + modelSettings: [], + predModelSettings: [], + hasModel: false, + model: undefined, + modelResult: undefined, + dataLength: undefined, + resultKey: undefined, + }) + + const saveModel = async () => { + saveModelParams.className = formData.className + saveModelParams.pyName = formData.pyName + saveModelParams.modelSettings = formData.modelSettings + const predMethod = methodList.value.find(e => e.methodName === 'predict'); + saveModelParams.methodName = predMethod.methodName + saveModelParams.resultKey = predMethod.resultKey + //predModelSettings + if (predMethod.methodSettings && predMethod.methodSettings.length > 0) { + saveModelParams.predModelSettings = predMethod.methodSettings.map(e => { + e.settingValue = e.value; + return e; + }) + } + saveModelParams.hasModel = predMethod.model === 1 + + saveModelParams.dataLength = predMethod.dataLength + + const data = await MpkApi.saveModel(saveModelParams) + download.downloadFile(data, saveModelParams.pyName + '.miail') + } </script> diff --git a/src/views/model/mpk/file/index.vue b/src/views/model/mpk/file/index.vue index a3d2bde..ff8f81e 100644 --- a/src/views/model/mpk/file/index.vue +++ b/src/views/model/mpk/file/index.vue @@ -1,7 +1,7 @@ <template> <el-row :gutter="20"> <!-- 左侧树 --> - <el-col :span="4" :xs="24"> + <el-col :span="3" :xs="24"> <ContentWrap class="h-1/1"> <el-tree style="max-width: 600px" @@ -13,7 +13,7 @@ /> </ContentWrap> </el-col> - <el-col :span="20" :xs="24"> + <el-col :span="21" :xs="24"> <!-- 搜索工作栏 --> <ContentWrap> <el-form @@ -67,13 +67,14 @@ :data="list" row-key="id" > - <el-table-column prop="pyChineseName" label="模型名称" header-align="center" align="left" min-width="100" /> - <el-table-column prop="pyName" label="模型文件" header-align="center" align="left" min-width="300"/> + <el-table-column prop="pyChineseName" label="模型名称" header-align="center" align="center" min-width="100" /> + <el-table-column prop="pyName" label="模型文件" header-align="center" align="center" min-width="200"/> <el-table-column prop="pyType" label="模型类型" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/> - <el-table-column prop="menuName" label="所属菜单" min-width="120px"/> - <el-table-column prop="groupName" label="所属组" min-width="120px"/> - <el-table-column prop="remark" label="备注" min-width="100px"/> - <el-table-column prop="createDate" label="创建时间" :formatter="dateFormatter" width="180px"/> +<!-- <el-table-column prop="menuName" label="所属菜单" min-width="120px"/>--> +<!-- <el-table-column prop="groupName" label="所属组" min-width="120px"/>--> +<!-- <el-table-column prop="remark" label="备注" min-width="100px"/>--> + <el-table-column prop="createDate" label="创建时间" align="center" :formatter="dateFormatter" width="180px"/> + <el-table-column prop="updateDate" label="修改时间" align="center" :formatter="dateFormatter" width="180px"/> <el-table-column label="操作" align="center" width="200px"> <template #default="scope"> <div class="flex items-center justify-center"> diff --git a/src/views/model/pre/item/MmPredictItemForm.vue b/src/views/model/pre/item/MmPredictItemForm.vue index 52eae1e..a583075 100644 --- a/src/views/model/pre/item/MmPredictItemForm.vue +++ b/src/views/model/pre/item/MmPredictItemForm.vue @@ -732,18 +732,30 @@ return } - let flag = false dataForm.value.mmItemOutputList.forEach(e => { if (e.resultstr == undefined || e.resultstr === '' || e.resultType == undefined || e.resultType === '' || (e.resultType === 2 && (e.resultIndex == undefined || e.resultIndex === '')) || (e.iscumulant === 1 && e.cumuldivisor == undefined) ) { - message.error("模型输出数据异常") - flag = true - return + message.error("输出数据异常") + throw new Error('输出数据异常'); } }) - if (flag) return + + //校验模型输入 + dataForm.value.mmModelParamList.forEach(e => { + if (e.modelparamid == undefined || e.modelparamid == '') { + message.error("输入数据异常") + throw new Error('输入数据异常'); + } + // ind_ascii类型输出的序号必须是1,且所在端口序号最大为1(一个ind_ascii类型输入独占一个端口) + if (e.modelparamtype === 'IND_ASCII') { + if (e.modelparamorder != 1 || dataForm.value.mmModelParamList.filter(p => p.modelparamportorder === e.modelparamportorder).length != 1) { + message.error("输入数据异常:IND_ASCII类型输入独占一个端口") + throw new Error('输入数据异常:IND_ASCII类型输入独占一个端口'); + } + } + }) } if (dataForm.value.itemtypename === 'MergeItem') { if (expressionList.value == undefined || expressionList.value.length <= 1) { @@ -761,7 +773,6 @@ }) if (flag) return } - // 提交请求 formLoading.value = true diff --git a/src/views/model/sche/model/ScheduleModelForm.vue b/src/views/model/sche/model/ScheduleModelForm.vue index 90a536d..5cdf9e9 100644 --- a/src/views/model/sche/model/ScheduleModelForm.vue +++ b/src/views/model/sche/model/ScheduleModelForm.vue @@ -456,6 +456,21 @@ if (!formRef) return const valid = await formRef.value.validate() if (!valid) return + //校验模型输入 + formData.value.paramList.forEach(e => { + if (e.modelparamid == undefined || e.modelparamid == '') { + message.error("输入数据异常") + throw new Error('输入数据异常'); + } + // ind_ascii类型输出的序号必须是1,且所在端口序号最大为1(一个ind_ascii类型输入独占一个端口) + if (e.modelparamtype === 'IND_ASCII') { + if (e.modelparamorder != 1 || formData.value.paramList.filter(p => p.modelparamportorder === e.modelparamportorder).length != 1) { + message.error("输入数据异常:IND_ASCII类型输入独占一个端口") + throw new Error('输入数据异常:IND_ASCII类型输入独占一个端口'); + } + } + }) + // 提交请求 formLoading.value = true try { -- Gitblit v1.9.3