潘志宝
2024-09-20 9ec4bd04bcce1ef1e6f8286a12f339d2ae9566db
git mpk
已添加12个文件
2230 ■■■■■ 文件已修改
src/views/model/mpk/file/MpkForm.vue 464 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/MpkGenerator.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/MpkGeneratorHistory.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/MpkRun.vue 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/SettingForm.vue 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/index.vue 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/project/ProjectForm.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/project/ProjectPackage.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/project/ProjectPackageHistory.vue 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/project/ProjectPackageModelDialog.vue 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/project/SelectForm.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/project/index.vue 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/MpkForm.vue
对比新文件
@@ -0,0 +1,464 @@
<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="20">
            <el-form-item label="模型名称" prop="pyName">
              <el-input disabled v-model="formData.pyName" 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"
              :before-upload="beforeUpload"
              :headers="uploadHeaders"
              :on-error="submitFormError"
              :on-success="submitFormSuccess"
              accept=".pyd"
            >
              <el-button type="primary">
                <Icon icon="ep:upload"/>
                模型上传
              </el-button>
            </el-upload>
          </el-col>
        </el-row>
        <el-row :gutter="8">
          <el-col :span="12">
            <el-form-item label="模型中文名称" prop="pyChineseName">
              <el-input v-model="formData.pyChineseName" placeholder=""/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="模型类型" prop="pyType">
              <el-select
                v-model="formData.pyType"
                placeholder="请选择"
                @change="pyTypeChange"
              >
                <el-option
                  v-for="dict in getDictOptions(DICT_TYPE.MODEL_TYPE)"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="8">
          <el-col :span="12">
            <el-form-item label="包名" prop="pkgName">
              <el-input v-model="formData.pkgName" placeholder=""/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="模型路径" prop="pyModule">
              <el-input v-model="formData.pyModule" placeholder=""/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="8">
          <el-col :span="12">
            <el-form-item label="所属菜单" prop="menuName">
              <el-input v-model="formData.menuName" placeholder=""/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="所属组" prop="groupName">
              <el-input v-model="formData.groupName" placeholder=""/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="8">
          <el-col :span="12">
            <el-form-item label="icon" prop="icon">
              <el-input v-model="formData.icon" placeholder=""/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <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="250">
            <template #default="scope">
              <el-input 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 size="small" step-strictly v-model="scope.row.dataLength" :min="1"
                               :max="50"/>
            </template>
          </el-table-column>
          <el-table-column
            prop=""
            label="是否有model"
            align="center">
            <template #default="scope">
              <el-switch size="small" v-model="scope.row.model" :active-value="1"
                         :inactive-value="0"/>
            </template>
          </el-table-column>
          <el-table-column
            prop=""
            label="结果key"
            align="center">
            <template #default="scope">
              <el-input size="small" v-model="scope.row.resultKey"/>
            </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="key" prop="settingKey"/>
                  <el-table-column align="center" label="参数名称" prop="name"/>
                  <el-table-column align="center" label="参数默认值" prop="value"/>
                  <el-table-column align="center" label="输入类型" prop="type">
                    <template #default="props">
                      <div class="flex file-row justify-center items-center">
                        {{props.row.type}}
                        <div class="ml-8px" v-if="props.row.type === 'select'">
                          <el-popover placement="left" :width="400">
                            <template #reference>
                              <el-button size="small" link type="primary">
                                <Icon icon="ep:more" />
                              </el-button>
                            </template>
                            <el-table width="50%" :data="props.row.settingSelects" border size="small">
                              <el-table-column align="center" label="key" prop="selectKey"/>
                              <el-table-column align="center" label="name" prop="name"/>
                            </el-table>
                          </el-popover>
                        </div>
                      </div>
                    </template>
                  </el-table-column>
                  <el-table-column align="center" label="参数类型" prop="valueType"/>
                  <el-table-column align="center" label="最大值" prop="max"/>
                  <el-table-column align="center" label="最小值" prop="min"/>
                  <!--                <el-table-column align="center" label="选项" width="50">-->
                  <!--                  <template #default="props">-->
                  <!--                    <div v-if="props.row.type === 'select'">-->
                  <!--                      <el-popover placement="left" :width="400">-->
                  <!--                        <template #reference>-->
                  <!--                          <Icon icon="ep:more" />-->
                  <!--                        </template>-->
                  <!--                        <el-table width="50%" :data="props.row.settingSelects" border size="small">-->
                  <!--                          <el-table-column align="center" label="key" prop="selectKey"/>-->
                  <!--                          <el-table-column align="center" label="name" prop="name"/>-->
                  <!--                        </el-table>-->
                  <!--                      </el-popover>-->
                  <!--                    </div>-->
                  <!--                  </template>-->
                  <!--                </el-table-column>-->
                  <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="danger"
                        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 MpkApi from '@/api/mpk/mpk'
  import {FormRules} from 'element-plus'
  import {getAccessToken, getTenantId} from "@/utils/auth";
  import SettingForm from './SettingForm.vue'
  import {generateUUID} from "@/utils";
  const {t} = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const title = ref('') // 弹窗的标题
  const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  const formType = ref('') // 表单的类型:create - 新增;update - 修改
  const route = useRoute() // 路由
  const router = useRouter();
  /** settingForm弹窗 */
  const settingFormRef = ref()
  // 添加setting
  const addSetting = (methodSettings) => {
    settingFormRef.value.open(undefined,methodSettings)
  }
  // 修改setting
  const updateSetting = (info) => {
    settingFormRef.value.open(info)
  }
  const methodExpandedRowKeys = ref([])
  const methodExpandChange = async (row: any, expandedRows: any[]) => {
    methodExpandedRowKeys.value = expandedRows.map(e => e.id)
  }
  const formData = ref({
    id: route.params.id,
    pyChineseName: undefined,
    pyName: undefined,
    pkgName: undefined,
    pyType: undefined,
    className: undefined,
    pyModule: undefined,
    icon: undefined,
    menuName: undefined,
    groupName: undefined,
    remark: undefined,
    modelMethods: [],
    filePath: undefined,
  })
  const formRules = reactive<FormRules>({
    pyName: [
      {required: true, message: '模型名称不能为空,请上传模型文件', trigger: 'blur'}
    ],
    pyChineseName: [
      {required: true, message: '模型中文名称不能为空', trigger: 'blur'}
    ],
    pyType: [
      {required: true, message: '模型类型不能为空', trigger: 'blur'}
    ],
    pkgName: [
      {required: true, message: '包名不能为空', trigger: 'blur'}
    ],
    className: [
      {required: true, message: '类名不能为空', trigger: 'blur'}
    ],
    pyModule: [
      {required: true, message: '模型路径不能为空', trigger: 'blur'}
    ],
    menuName: [
      {required: true, message: '所属目录不能为空', 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 as unknown as MpkApi.MpkVO
      if (formType.value === 'create') {
        await MpkApi.createMpk(data)
        message.success(t('common.createSuccess'))
      } else {
        await MpkApi.updateMpk(data)
        message.success(t('common.updateSuccess'))
      }
    } finally {
      formLoading.value = false
    }
    // router.push({path:'/model/mpk'})
    router.back()
  }
  /** 重置表单 */
  const resetForm = () => {
    formData.value = {
      id: undefined,
      pyName: undefined,
      pyChineseName: undefined,
      pkgName: undefined,
      pyType: undefined,
      className: undefined,
      pyModule: undefined,
      icon: undefined,
      menuName: undefined,
      groupName: undefined,
      remark: undefined,
      modelMethods: [],
      filePath: undefined
    }
    formRef.value?.resetFields()
  }
  const addRow = function () {
    formData.value.modelMethods.push({
      id: generateUUID(),
      methodName: undefined,
      dataLength: 1,
      model: 0,
      resultKey: undefined,
      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/mpk/file/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.filePath = data.filePath
    formData.value.pyName = data.fileName.replace('.pyd', '')
    message.success('上传成功')
    uploadLoading.value = false
  }
  onMounted(async () => {
    const id = formData.value.id;
    const type = id ? 'edit' : 'create'
    title.value = t('action.' + type)
    formType.value = type
    resetForm()
    // 修改时,设置数据
    if (id) {
      formLoading.value = true
      try {
        formData.value = await MpkApi.getMpk(id)
      } finally {
        formLoading.value = false
      }
    }
  })
  const pyTypeChange = () => {
    if (formData.value.pyType === 'predict') {
      formData.value.modelMethods = [
        {
          id: generateUUID(),
          methodName: 'train',
          dataLength: 1,
          model: 0,
          resultKey: undefined,
          methodSettings: []
        },
        {
          id: generateUUID(),
          methodName: 'predict',
          dataLength: 1,
          model: 1,
          resultKey: undefined,
          methodSettings: []
        }
      ]
      debugger
    }else {
      formData.value.modelMethods = [
        {
          id: generateUUID(),
          methodName: 'schedul',
          dataLength: 1,
          model: 0,
          resultKey: undefined,
          methodSettings: []
        }
      ]
    }
  }
</script>
<style scoped lang="scss">
</style>
src/views/model/mpk/file/MpkGenerator.vue
对比新文件
@@ -0,0 +1,49 @@
<template>
  <Dialog v-model="dialogVisible" :title="dialogTitle">
    <el-input
      type="textarea"
      :rows="4"
      placeholder="备注"
      v-model="remark"/>
    <div style="width: 100%;display: flex;flex-direction: row;justify-content: end;margin-top: 16px">
      <el-button @click="generatorCode()" type="primary">生成</el-button>
    </div>
  </Dialog>
</template>
<script lang="ts" setup>
  import * as MpkApi from '@/api/mpk/mpk'
  import download from "@/utils/download";
  import {formatToDateString} from "@/utils/dateUtil";
  const { t } = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('生成代码') // 弹窗的标题
  const remark = ref('')
  const id = ref()
  const zipFileName = ref()
  /** 打开弹窗 */
  const open = async (modelId: string,pyName: string) => {
    dialogVisible.value = true
    id.value = modelId;
    zipFileName.value = pyName + '_' + formatToDateString(new Date()) + '.zip';
    remark.value = "";
  }
  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  /** 提交表单 */
  const generatorCode = async () => {
    const param = {
      'id': id.value,
      'remark': remark.value,
      'zipFileName': zipFileName.value
    }
    const data = await MpkApi.generatorCode(param)
    download.zip(data, zipFileName.value)
    dialogVisible.value = false
  }
</script>
src/views/model/mpk/file/MpkGeneratorHistory.vue
对比新文件
@@ -0,0 +1,109 @@
<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 prop="startTime">
          <el-date-picker
            v-model="queryParams.startTime"
            type="datetime"
            value-format="YYYY-MM-DD HH:mm:ss"
            placeholder="选择日期时间"/>
        </el-form-item>
        <el-form-item prop="endTime">
          <el-date-picker
            v-model="queryParams.endTime"
            type="datetime"
            value-format="YYYY-MM-DD HH:mm:ss"
            placeholder="选择日期时间"/>
        </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 label="文件名" header-align="center" align="center">
          <template #default="scope">
            <a style="cursor: pointer;color: #409eff" @click="downloadHistory(scope.row.id,scope.row.fileName)">{{ scope.row.fileName }}</a>
          </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注"  header-align="center" align="center"/>
        <el-table-column prop="createTime" label="生成时间" :formatter="dateFormatter" header-align="center" align="center" width="200"/>
      </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 HistoryApi from "@/api/mpk/mpkHistory";
  import { dateFormatter } from '@/utils/formatTime'
  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,
    mdkId: '',
    startTime: undefined,
    endTime: undefined,
  })
  /** 打开弹窗 */
  const open = async (mdkId: String) => {
    dialogVisible.value = true
    queryParams.mdkId = mdkId;
    queryParams.startTime = undefined;
    queryParams.endTime = undefined;
    getList()
  }
  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  const getList = async () => {
    loading.value = true
    try {
      const data = await HistoryApi.getPage(queryParams)
      list.value = data.list
      total.value = data.total
    } finally {
      loading.value = false
    }
  }
  const downloadHistory = async (id,fileName) => {
    const param = {
      'id': id,
    }
    const data = await HistoryApi.download(param)
    download.zip(data, fileName)
  }
</script>
src/views/model/mpk/file/MpkRun.vue
对比新文件
@@ -0,0 +1,243 @@
<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-form-item label="全类名" style="width: 90%" prop="className">
        <el-input v-model="formData.className" placeholder=""/>
      </el-form-item>
      <el-form-item label="方法名" prop="methodName">
        <el-select v-model="formData.methodName" @change="methodChange" style="width: 240px">
          <el-option
            v-for="item in methodList"
            :key="item.id"
            :label="item.methodName"
            :value="item.methodName"
          />
        </el-select>
      </el-form-item>
      <el-divider content-position="left">模型参数信息</el-divider>
      <el-row :gutter="20">
        <el-col :span="2" style="margin-bottom: 10px;margin-left: 20px">
          <el-button tag="a" href="/template/模型参数导入模板.xlsx" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button>
        </el-col>
        <el-col :span="2" style="margin-bottom: 10px;">
          <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>
        </el-col>
      </el-row>
      <el-row v-for="(item,index) in formData.datas" :key="index" :gutter="20">
        <el-col :span="20">
          <el-form-item :label="'参数_' + (index)" required style="width: 100%">
            <el-input v-model="formData.datas[index]" placeholder="" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row v-if="hasModel" :gutter="20">
        <el-col :span="20">
          <el-form-item label="model" required style="width: 100%">
            <el-input v-model="formData.model" 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" 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" 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"
              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 MpkApi from '@/api/mpk/mpk'
  import {FormRules} from "element-plus";
  import {getAccessToken, getTenantId} from "@/utils/auth";
  const { t } = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('模型运行') // 弹窗的标题
  const formData = reactive({
    className: '',
    methodName: '',
    datas: [],
    modelSettings: [],
    model: undefined
  })
  // 模型方法下拉列表
  const methodList = ref([])
  const hasModel = ref(false)
  /** 打开弹窗 */
  const open = async (row) => {
    dialogVisible.value = true
    formData.className = row.pkgName + '.impl.' + row.pyName + 'Impl';
    const mpk = await MpkApi.getMpk(row.id)
    methodList.value = mpk.modelMethods
    formData.methodName = mpk.modelMethods[0].methodName
    formData.datas = []
    for (let i = 0 ; i < mpk.modelMethods[0].dataLength ; i++) {
     formData.datas[i] = '[[]]'
    }
    hasModel.value = mpk.modelMethods[0].model === 1
  }
  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) {
    formData.datas = []
    for (let i = 0 ; i < methodList.value.find(e => e.methodName === value)?.dataLength ; i++) {
      formData.datas[i] = '[[]]'
    }
    hasModel.value = methodList.value.find(e => e.methodName === value)?.model === 1
  }
  const fileList = ref([]) // 文件列表
  const importUrl =
    import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/mpk/api/import'
  const formLoading = ref(false) // 表单的加载中
  const uploadHeaders = ref() // 上传 Header 头
  /** 上传错误提示 */
  const submitFormError = (): void => {
    message.error('导入失败,请检查导入文件!')
    formLoading.value = false
  }
  const submitFormSuccess = (response: any) => {
    if (response.code !== 0) {
      message.error(response.msg)
      formLoading.value = false
      return
    }
    const datas = response.data;
    for (let i=0;i<formData.datas.length;i++) {
      formData.datas[i] = datas[i]
    }
    message.success('导入成功')
    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 () => {
// 校验表单
    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
      data.hasModel = hasModel.value
      if (data.hasModel && data.model) {
        data.model = {model_path:data.model}
      }else {
        data.model = undefined
      }
      modelRunResult.value = await MpkApi.modelRun(data)
      modelRunloading.value = false
      message.success('运行成功')
    } finally {
      modelRunloading.value = false
    }
  }
</script>
src/views/model/mpk/file/SettingForm.vue
对比新文件
@@ -0,0 +1,270 @@
<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="key" prop="settingKey">
            <el-input v-model="formData.settingKey" placeholder=""/>
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="参数名称" prop="name">
            <el-input v-model="formData.name" placeholder=""/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="8">
        <el-col :span="12">
          <el-form-item label="输入类型" prop="type">
            <el-select v-model="formData.type" @change="typeChange">
              <el-option
                v-for="item in getDictOptions(DICT_TYPE.MODEL_METHOD_SETTING_TYPE)"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </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"
                :disabled="valueTypeSelectDisabled(item.value)"
              />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <div v-if="formData.type === 'input'">
        <el-row :gutter="8">
          <el-col :span="8">
            <el-form-item label="默认值" prop="value">
              <el-input v-model="formData.value" placeholder=""/>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="最大值" prop="max">
              <el-input-number v-model="formData.max" placeholder=""/>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="最小值" prop="min">
              <el-input-number v-model="formData.min" placeholder=""/>
            </el-form-item>
          </el-col>
        </el-row>
      </div>
      <div v-if="formData.type === 'select'">
        <el-divider content-position="left">选项信息</el-divider>
        <el-row :gutter="8">
          <el-col :span="4">
            <el-button type="primary" size="small" @click="addRow()">新增</el-button>
          </el-col>
        </el-row>
        <el-table :data="formData.settingSelects" border>
          <el-table-column
            prop=""
            label="key"
            align="center">
            <template #default="scope">
              <el-input size="small" v-model="scope.row.selectKey" maxlength="50" clearable />
            </template>
          </el-table-column>
          <el-table-column
            prop=""
            label="name"
            align="center">
            <template #default="scope">
              <el-input size="small" v-model="scope.row.name" 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"
                link
              >删除</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </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 * as ProjectApi from '@/api/mpk/project'
  import * as MpkApi from '@/api/mpk/mpk'
  import {FormRules} from 'element-plus'
  import {generateUUID} from "@/utils";
  import {func} from "vue-types";
  const {t} = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('参数设置') // 弹窗的标题
  const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  const formData = ref({
    settingKey: undefined,
    name: undefined,
    type: undefined,
    valueType: undefined,
    value: undefined,
    max: undefined,
    min: undefined,
    settingSelects: []
  })
  const formRules = reactive<FormRules>({
    settingKey: [
      {required: true, message: 'key不能为空', trigger: 'blur'},
    ],
    name: [
      {required: true, message: '参数名称不能为空', trigger: 'blur'},
    ],
    type: [
      {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
    //校验select
    if (formData.value.type === 'select') {
      if (formData.value.settingSelects?.length === 0) {
        message.error('选项为空');
        return
      }
    }
    // 提交请求
    formLoading.value = true
    try {
      if (infoRef) {
        // 修改
        for (let key in formData.value) {
          infoRef[key] = formData.value[key];
        }
      }else {
        // 新增
        methodSettingsRef.push({...formData.value})
      }
      dialogVisible.value = false
    } finally {
      formLoading.value = false
    }
  }
  /** 重置表单 */
  const resetForm = () => {
    formData.value = {
      settingKey: undefined,
      name: undefined,
      type: undefined,
      valueType: undefined,
      value: undefined,
      max: undefined,
      min: undefined,
      settingSelects: []
    }
    formRef.value?.resetFields()
  }
  const valueTypeSelectDisabled = (value) => {
    const type = formData.value.type;
    switch (type) {
      case "input":
        if (['int','decimal','decimalArray','string'].includes(value)) {
          return false
        }else {
          return true
        }
      case "file":
        if (['file'].includes(value)) {
          return false
        }else {
          return true
        }
      case "select":
        if (['int','string'].includes(value)) {
          return false
        }else {
          return true
        }
      default :
        return true
    }
  }
  const typeChange = () => {
    formData.value.valueType = undefined
    formData.value.value = undefined
    formData.value.max = undefined
    formData.value.min = undefined
    formData.value.settingSelects = []
  }
  const addRow = function () {
    formData.value.settingSelects.push({
      selectKey: '',
      name: ''
    })
  }
  const deleteRow = function (index) {
    formData.value.settingSelects.splice(index, 1)
  }
</script>
src/views/model/mpk/file/index.vue
对比新文件
@@ -0,0 +1,209 @@
<template>
  <!-- 搜索工作栏 -->
  <ContentWrap>
    <el-form
      class="-mb-15px"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="68px"
    >
      <el-form-item label="模型名称" prop="pyName">
        <el-input
          v-model="queryParams.pyName"
          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="'/mpk/form'">
            <el-button type="primary" plain v-hasPermi="['mpk:file: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="pyChineseName" label="模型中文名称"/>
      <el-table-column prop="pyName" label="模型名称"/>
      <el-table-column prop="pyType" label="模型类型" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/>
      <el-table-column prop="menuName" label="所属菜单" width="120px"/>
      <el-table-column prop="groupName" label="所属组" width="120px"/>
      <el-table-column prop="remark" label="备注" width="200px"/>
      <el-table-column prop="createDate" label="创建时间" :formatter="dateFormatter" width="170px"/>
      <el-table-column label="操作" align="center" width="200px">
        <template #default="scope">
          <div class="flex items-center justify-center">
            <router-link :to="'/mpk/form/' + scope.row.id">
              <el-button type="primary" link v-hasPermi="['mpk:file:update']">
                <Icon icon="ep:edit"/>修改
              </el-button>
            </router-link>
            <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['mpk:file: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="generatorCode"
                    >
                      生成代码
                    </el-dropdown-item>
                    <el-dropdown-item
                      command="generatorHistory"
                    >
                      生成历史
                    </el-dropdown-item>
                    <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.pageSize"
      v-model:page="queryParams.page"
      :total="total"
      @pagination="getList"
    />
  </ContentWrap>
  <MpkGenerator ref="mpkGenerator"/>
  <GeneratorCodeHistory ref="generatorCodeHistory"/>
  <MpkRun ref="mpkRun"/>
</template>
<script lang="ts" setup>
  import {dateFormatter} from '@/utils/formatTime'
  import * as MpkApi from '@/api/mpk/mpk'
  import MpkGenerator from './MpkGenerator.vue'
  import GeneratorCodeHistory from './MpkGeneratorHistory.vue'
  import MpkRun from './MpkRun.vue'
  import * as UserApi from "@/api/system/user";
  import { DICT_TYPE, getDictLabel } from '@/utils/dict'
  import {useAppStoreWithOut} from "@/store/modules/app";
  defineOptions({name: 'MpkFile'})
  const message = useMessage() // 消息弹窗
  const {t} = useI18n() // 国际化
  const loading = ref(true) // 列表的加载中
  const total = ref(0) // 列表的总页数
  const list = ref([]) // 字典表格数据
  const queryParams = reactive({
    page: 1,
    pageSize: 10,
    pyName: ''
  })
  const queryFormRef = ref() // 搜索的表单
  const getList = async () => {
    loading.value = true
    try {
      const data = await MpkApi.getPage(queryParams)
      list.value = data.list
      total.value = data.total
    } finally {
      loading.value = false
    }
  }
  /** 操作分发 */
  const handleCommand = (command: string, row) => {
    switch (command) {
      case 'generatorCode':
        generatorCode(row.id,row.pyName)
        break
      case 'generatorHistory':
        generatorHistory(row.id)
        break
      case 'mpkRunDialog':
        mpkRunDialog(row)
        break
      default:
        break
    }
  }
  const mpkGenerator = ref();
  const generatorCode = (id,pyName) => {
    mpkGenerator.value.open(id,pyName);
  }
  const generatorCodeHistory = ref();
  const generatorHistory = (id) => {
    generatorCodeHistory.value.open(id);
  }
  const mpkRun = ref();
  const mpkRunDialog = (row) => {
    mpkRun.value.open(row);
  }
  /** 搜索按钮操作 */
  const handleQuery = () => {
    getList()
  }
  /** 重置按钮操作 */
  const resetQuery = () => {
    queryParams.page = 1
    queryFormRef.value.resetFields()
    handleQuery()
  }
  /** 删除按钮操作 */
  const handleDelete = async (id: number) => {
    try {
      // 删除的二次确认
      await message.delConfirm()
      // 发起删除
      await MpkApi.deleteMpk(id)
      message.success(t('common.delSuccess'))
      // 刷新列表
      await getList()
    } catch {
    }
  }
  onActivated((to) => {
    getList()
  })
  /** 初始化 **/
  onMounted(async () => {
    await getList()
  })
</script>
src/views/model/mpk/project/ProjectForm.vue
对比新文件
@@ -0,0 +1,153 @@
<template>
  <Dialog v-model="dialogVisible" :title="dialogTitle">
    <el-form
      ref="formRef"
      v-loading="formLoading"
      :model="formData"
      :rules="formRules"
      label-width="80px"
    >
      <el-row :gutter="20">
        <el-col :span="20">
          <el-form-item label="项目名称" prop="projectName" style="width: 100%">
            <el-input v-model="formData.projectName" placeholder="" style="width: 100%"/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="20">
          <el-form-item label="项目编码" prop="projectCode" style="width: 100%">
            <el-input v-model="formData.projectCode" placeholder="" style="width: 100%"/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col>
          <el-form-item label="关联模型" prop="models">
            <el-transfer :props="{key: 'id',label: 'pyName'}" :titles="['未选模型', '已选模型']" target-order="unshift" filterable :filter-method="filterMethod" v-model="formData.models" :data="modelList">
              <template #default="{ option }">
                <span :title="option.pyName">{{ option.pyName}}</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/mpk/project'
  import * as MpkApi from '@/api/mpk/mpk'
  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,
  })
  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
      }
      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 MpkApi.list()
  }
  // 模型筛选
  const filterMethod = function (query, item) {
    return item.pyName.toLowerCase().indexOf(query.toLowerCase()) !== -1
  }
</script>
<style scoped>
  :deep(.el-transfer-panel) {
    width: 35%;
  }
</style>
src/views/model/mpk/project/ProjectPackage.vue
对比新文件
@@ -0,0 +1,96 @@
<template>
  <Dialog v-model="dialogVisible" :title="dialogTitle">
    <el-form
      ref="formRef"
      v-loading="formLoading"
      :model="formData"
      :rules="formRules"
      label-width="80px"
    >
      <el-row>
        <el-col :span="8">
          <el-form-item label="版本号" prop="version">
            <el-input v-model="formData.version" placeholder="请输入版本号"/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="更新日志" prop="log">
            <el-input type="textarea" :rows="4" placeholder="请输入更新日志" v-model="formData.log"/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row justify="end">
        <el-col :span="3">
          <el-button @click="packageProject()" type="primary">打包</el-button>
        </el-col>
      </el-row>
    </el-form>
  </Dialog>
</template>
<script lang="ts" setup>
  import * as ProjectApi from '@/api/mpk/project'
  import download from "@/utils/download";
  import {FormRules} from "element-plus";
  import {formatToDateString} from "@/utils/dateUtil";
  const { t } = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('打包') // 弹窗的标题
  const formLoading = ref(false) // 表单的加载中
  const formRules = reactive<FormRules>({
    version: [
      {required: true, message: '版本号不能为空', trigger: 'blur'},
    ],
    log: [
      {required: true, message: '更新日志不能为空', trigger: 'blur'},
    ]
  })
  const formData = reactive({
    log: undefined,
    projectId: undefined,
    projectName: undefined,
    projectCode: undefined,
    ids: undefined,
    version: undefined,
  })
  /** 打开弹窗 */
  const open = async (projectId,projectName,projectCode,ids) => {
    dialogVisible.value = true
    formData.projectId = projectId
    formData.projectName = projectName
    formData.projectCode = projectCode
    formData.ids = ids
    formData.log = undefined
    formData.version = 'V'
  }
  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  const formRef = ref() // 表单 Ref
  /** 提交表单 */
  const packageProject = async () => {
    // 校验表单
    try {
      if (!formRef) return
      const valid = await formRef.value.validate()
      if (!valid) return
      formLoading.value = true
      formData.zipFileName = 'IAILMPK.' + formData.projectCode + '.' + formatToDateString(new Date()) + '.zip'
      const data = await ProjectApi.packageProject(formData)
      debugger
      download.zip(data, formData.zipFileName)
      formLoading.value = false
      dialogVisible.value = false
    } catch (e) {
      formLoading.value = false
    }
  }
</script>
src/views/model/mpk/project/ProjectPackageHistory.vue
对比新文件
@@ -0,0 +1,155 @@
<template>
  <ContentWrap>
    <el-form
      class="-mb-15px"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="68px"
    >
      <el-form-item label="项目名称" prop="projectId">
        <el-select v-model="queryParams.projectId" class="!w-240px">
          <el-option
            v-for="item in projectList"
            :key="item.id"
            :label="item.projectName"
            :value="item.id"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="开始时间" prop="startTime">
        <el-date-picker
          v-model="queryParams.startTime"
          type="datetime"
          value-format="YYYY-MM-DD HH:mm:ss"
          placeholder="选择日期时间"/>
      </el-form-item>
      <el-form-item label="结束时间" prop="endTime">
        <el-date-picker
          v-model="queryParams.endTime"
          type="datetime"
          value-format="YYYY-MM-DD HH:mm:ss"
          placeholder="选择日期时间"/>
      </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-form-item>
    </el-form>
  </ContentWrap>
  <!-- 列表 -->
  <ContentWrap>
    <el-table v-loading="loading" :data="list">
      <el-table-column label="文件名" header-align="center" align="center" width="400">
        <template #default="scope">
          <a style="cursor: pointer;color: #409eff"
             @click="downloadHistory(scope.row.filePath,scope.row.fileName)">{{ scope.row.fileName
            }}</a>
        </template>
      </el-table-column>
      <el-table-column prop="version" label="版本号" header-align="center" align="center" width="200"/>
      <el-table-column prop="log" label="更新日志" header-align="center" align="center"/>
      <el-table-column prop="createTime" label="打包时间" :formatter="dateFormatter" header-align="center" align="center" width="200"/>
      <el-table-column label="操作" align="center" width="230px">
        <template #default="scope">
          <el-button
            link
            type="primary"
            @click="viewPackageModel(scope.row.id)"
          >
            <Icon icon="ep:link"/>
            查看关联模型
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </ContentWrap>
  <PackageModel ref="packageModelRef"/>
</template>
<script lang="ts" setup>
  import download from "@/utils/download";
  import * as PackageHistoryApi from '@/api/mpk/projectPackageHistory'
  import * as ProjectApi from '@/api/mpk/project'
  import {dateFormatter} from '@/utils/formatTime'
  import PackageModel from './ProjectPackageModelDialog.vue'
  defineOptions({name: 'ProjectPackageHistory'})
  const message = useMessage() // 消息弹窗
  const {t} = useI18n() // 国际化
  const route = useRoute() // 路由
  const loading = ref(true) // 列表的加载中
  const total = ref(0) // 列表的总页数
  const list = ref([]) // 列表的数据
  const queryParams = reactive({
    pageNo: 1,
    pageSize: 10,
    projectId: route.params.projectId,
    startTime: undefined,
    endTime: undefined,
  })
  const queryFormRef = ref() // 搜索的表单
  const projectList = ref() // 字典类型的列表
  /** 查询列表 */
  const getList = async () => {
    loading.value = true
    try {
      const data = await PackageHistoryApi.getPage(queryParams)
      list.value = data.list
      total.value = data.total
    } finally {
      loading.value = false
    }
  }
  /** 搜索按钮操作 */
  const handleQuery = () => {
    queryParams.pageNo = 1
    getList()
  }
  /** 重置按钮操作 */
  const resetQuery = () => {
    queryFormRef.value.resetFields()
    handleQuery()
  }
  const downloadHistory = async (filePath,fileName) => {
    const param = {
      filePath,
      fileName,
    }
    const data = await PackageHistoryApi.download(param)
    download.zip(data, fileName)
  }
  const packageModelRef = ref()
  const viewPackageModel = (id) => {
    packageModelRef.value.open('package',id)
  }
  /** 初始化 **/
  onMounted(async () => {
    await getList()
    // 查询字典(精简)列表
    projectList.value = await ProjectApi.list()
  })
</script>
src/views/model/mpk/project/ProjectPackageModelDialog.vue
对比新文件
@@ -0,0 +1,119 @@
<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="pyName">
          <el-input
            v-model="queryParams.pyName"
            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="pyName" label="模型名称"/>
        <el-table-column prop="pkgName" label="包名"/>
        <el-table-column prop="pyModule" label="模型路径" width="300px"/>
        <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="methodName" />
              <el-table-column align="center" label="参数长度" prop="dataLength" />
              <el-table-column align="center" label="是否有model" prop="model" :formatter="(row,column,cellValue) => cellValue === 1 ? 'true' : 'false'" />
            </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 projectHistoryApi from '@/api/mpk/projectPackageHistory'
  import * as projectApi from '@/api/mpk/project'
  import { dateFormatter } from '@/utils/formatTime'
  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,
    packageHistoryId: undefined,
    projectId: undefined,
    pyName: undefined,
  })
  const dialogType = ref('')
  /** 打开弹窗 */
  const open = async (type: String,id: String) => {
    dialogVisible.value = true
    dialogType.value = type
    if (dialogType.value === 'package') {
      queryParams.packageHistoryId = id;
    }else if (dialogType.value === 'project') {
      queryParams.projectId = id;
    }
    queryParams.pyName = undefined;
    getList()
  }
  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  const getList = async () => {
    loading.value = true
    try {
      let data = undefined;
      if (dialogType.value === 'package') {
        data = await projectHistoryApi.getPackageModel(queryParams)
        data.list.forEach(e => {
          e.modelMethods = JSON.parse(e.methodInfo)
        })
      }else if (dialogType.value === 'project') {
        data = await projectApi.getProjectModel(queryParams)
      }
      list.value = data.list
      total.value = data.total
    } finally {
      loading.value = false
    }
  }
</script>
src/views/model/mpk/project/SelectForm.vue
对比新文件
@@ -0,0 +1,135 @@
<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="key" prop="selectKey">
            <el-input v-model="formData.settingKey" placeholder=""/>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="8">
        <el-col :span="12">
          <el-form-item label="name" prop="name">
            <el-input v-model="formData.name" 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 * as ProjectApi from '@/api/mpk/project'
  import * as MpkApi from '@/api/mpk/mpk'
  import {FormRules} from 'element-plus'
  import {generateUUID} from "@/utils";
  const {t} = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('选项设置') // 弹窗的标题
  const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  const formData = ref({
    settingKey: undefined,
    name: undefined,
    type: undefined,
    valueType: undefined,
    value: undefined,
    max: undefined,
    min: undefined,
  })
  const formRules = reactive<FormRules>({
    settingKey: [
      {required: true, message: 'key不能为空', trigger: 'blur'},
    ],
    name: [
      {required: true, message: '参数名称不能为空', trigger: 'blur'},
    ],
    type: [
      {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];
        }
      }else {
        // 新增
        const info = {...formData.value};
        info.id = generateUUID()
        methodSettingsRef.push(info)
      }
      dialogVisible.value = false
    } finally {
      formLoading.value = false
    }
  }
  /** 重置表单 */
  const resetForm = () => {
    formData.value = {
      settingKey: undefined,
      name: undefined,
      type: undefined,
      valueType: undefined,
      value: undefined,
      max: undefined,
      min: undefined,
    }
    formRef.value?.resetFields()
  }
</script>
src/views/model/mpk/project/index.vue
对比新文件
@@ -0,0 +1,228 @@
<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="packageModel"
                    >
                      <el-button link>打包</el-button>
                    </el-dropdown-item>
                    <el-dropdown-item
                    >
                      <router-link :to="'/project/package/history/' + scope.row.id">
                        <el-button link>打包历史</el-button>
                      </router-link>
                    </el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
            </div>
          </div>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <Pagination
      v-model:limit="queryParams.pageSize"
      v-model:page="queryParams.page"
      :total="total"
      @pagination="getList"
    />
  </ContentWrap>
  <!-- 表单弹窗:添加/修改 -->
  <ProjectForm ref="formRef" @success="getList"/>
  <ProjectPackage ref="projectPackageRef"/>
  <RelevanceModel ref="relevanceModelRef"/>
</template>
<script lang="ts" setup>
  import {dateFormatter} from '@/utils/formatTime'
  import * as ProjectApi from '@/api/mpk/project'
  import ProjectForm from './ProjectForm.vue'
  import ProjectPackage from './ProjectPackage.vue'
  import RelevanceModel from './ProjectPackageModelDialog.vue'
  defineOptions({name: 'MpkProject'})
  const message = useMessage() // 消息弹窗
  const {t} = useI18n() // 国际化
  const loading = ref(true) // 列表的加载中
  const total = ref(0) // 列表的总页数
  const list = ref([]) // 字典表格数据
  const queryParams = reactive({
    page: 1,
    pageSize: 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 'packageModel':
        packageModel(row.id, row.projectName, row.projectCode, row.models)
        break
      default:
        break
    }
  }
  //打包
  const projectPackageRef = ref();
  const packageModel = (projectId, projectName, projectCode, models) => {
    let ids = models.map(e => e.id);
    if (ids && ids.length > 0) {
      projectPackageRef.value.open(projectId, projectName, projectCode, ids.join(","));
    } else {
      message.error("请先为项目添加模型!")
    }
  }
  /** 搜索按钮操作 */
  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('project',id)
  }
  /** 初始化 **/
  onMounted(async () => {
    await getList()
  })
</script>