潘志宝
2025-02-28 4a859a6d69984c77fa8166255c65f5a94eb0bd71
Merge branch 'master' of http://dlindusit.com:53929/r/iailab-plat-ui-vue3
已修改15个文件
已添加10个文件
1953 ■■■■■ 文件已修改
src/api/data/ind/item/item.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/matlab/mlModel.ts 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/matlab/project.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/mpk/mpk.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/sche/model/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/remaining.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/download.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/AtomIndDefineForm.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/CalIndDefineForm.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/DerIndDefineForm.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/IndCurrentData.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/IndHistoryChart.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/index.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/MatlabModelForm.vue 394 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/MatlabModelSettingForm.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/MatlabRun.vue 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/index.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/project/MatlabProjectForm.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/project/MatlabProjectModelDialog.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/project/index.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/MpkRun.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/index.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/item/MmPredictItemForm.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/sche/model/ScheduleModelForm.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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})
}
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})
}
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 })
}
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})
}
src/api/model/sche/model/index.ts
@@ -165,5 +165,6 @@
    'MergeItem': mergeItemList,
    'PLAN': planList,
    'IND': indList,
    'IND_ASCII': indList,
  }
}
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
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'
}
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
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: '',
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: '',
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: '',
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>
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>
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 {
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>
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>
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>
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>
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>
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>
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>
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>
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">
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
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 {