dengzedong
2025-02-27 3933d80769776a812cb73cbf6762762959297c09
matlab 模型管理 项目管理
已修改2个文件
已添加9个文件
1570 ■■■■■ 文件已修改
src/api/model/matlab/mlModel.ts 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/matlab/project.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/remaining.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | 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/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/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',
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>