dengzedong
2025-02-21 be8574891bb59d9cc4cae97e8ac3232d05f182e9
提交 | 用户 | 时间
9ec4bd 1 <template>
2   <Dialog v-model="dialogVisible" :title="dialogTitle">
3     <el-form
4       class="-mb-15px"
5       :model="formData"
6       ref="formRef"
7       :inline="true"
8       :rules="formRules"
9       label-width="68px"
10       v-loading="formLoading"
11     >
12       <el-form-item style="width: 100%">
13         <el-divider content-position="left">模型信息</el-divider>
14       </el-form-item>
de019e 15       <el-row>
D 16         <el-col :span="24">
17           <el-form-item label="全类名" style="width: 100%" prop="className">
18             <el-input v-model="formData.className" placeholder=""/>
19           </el-form-item>
20         </el-col>
21       </el-row>
22       <el-row>
23         <el-col :span="24">
24           <el-form-item label="方法名" prop="methodName">
25             <el-select v-model="formData.methodName" @change="methodChange" style="width: 240px">
26               <el-option
27                 v-for="item in methodList"
28                 :key="item.id"
29                 :label="item.methodName"
30                 :value="item.methodName"
31               />
32             </el-select>
33           </el-form-item>
34         </el-col>
35       </el-row>
9ec4bd 36       <el-divider content-position="left">模型参数信息</el-divider>
22bece 37       <div style="display:flex;flex-direction: row;align-items: center;margin-bottom: 6px">
D 38         <el-button tag="a" :href="staticDir + '/template/模型参数导入模板.xlsx'" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button>
39         <el-upload
40           ref="uploadRef"
41           v-model:file-list="fileList"
42           :show-file-list="false"
43           :action="importUrl"
44           :auto-upload="true"
45           :disabled="formLoading"
46           :before-upload="beforeUpload"
47           :headers="uploadHeaders"
48           :on-error="submitFormError"
49           :on-success="submitFormSuccess"
50           accept=".xlsx"
51         >
52           <el-button type="primary" size="small" link>参数导入</el-button>
53         </el-upload>
54       </div>
de019e 55       <el-row v-for="(item,index) in datas" :key="index" :gutter="20">
D 56         <el-col :span="24">
9ec4bd 57           <el-form-item :label="'参数_' + (index)" required style="width: 100%">
de019e 58             <el-input type="textarea" :disabled="true" :rows="3" v-model="datas[index]" placeholder="" />
9ec4bd 59           </el-form-item>
60         </el-col>
61       </el-row>
62       <el-row v-if="hasModel" :gutter="20">
63         <el-col :span="20">
64           <el-form-item label="model" required style="width: 100%">
65             <el-input v-model="formData.model" placeholder="" />
66           </el-form-item>
67         </el-col>
68       </el-row>
69       <el-divider content-position="left">模型设置信息</el-divider>
de019e 70 <!--      <el-row :gutter="20">-->
D 71 <!--        <el-col :span="4">-->
72 <!--          <el-button type="primary" size="small" @click="addRow()">新增</el-button>-->
73 <!--        </el-col>-->
74 <!--      </el-row>-->
9ec4bd 75       <el-table :data="formData.modelSettings" border>
76         <el-table-column
77           prop=""
78           label="参数key"
79           align="center">
80           <template #default="scope">
de019e 81             <el-input size="small" v-model="scope.row.settingKey" :disabled="true" maxlength="50" clearable />
9ec4bd 82           </template>
83         </el-table-column>
84         <el-table-column
85           prop=""
22bece 86           label="参数名称"
D 87           align="center">
88           <template #default="scope">
89             <el-input size="small" v-model="scope.row.name" :disabled="true" maxlength="50" clearable />
90           </template>
91         </el-table-column>
92         <el-table-column
93           prop=""
9ec4bd 94           label="参数value"
95           align="center">
96           <template #default="scope">
627a6b 97             <el-input size="small" v-model="scope.row.settingValue" :disabled="scope.row.settingKey === 'pyFile'" maxlength="50" clearable />
9ec4bd 98           </template>
99         </el-table-column>
22bece 100 <!--        <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">-->
D 101 <!--          <template #default="scope">-->
102 <!--            <el-button-->
103 <!--              @click="deleteRow(scope.$index)"-->
104 <!--              key="danger"-->
105 <!--              type="danger"-->
106 <!--              :disabled="scope.row.settingKey === 'pyFile'"-->
107 <!--              link-->
108 <!--            >删除</el-button>-->
109 <!--          </template>-->
110 <!--        </el-table-column>-->
9ec4bd 111       </el-table>
112       <el-divider content-position="left">模型运行结果</el-divider>
be8574 113       <el-button type="primary" size="small" link @click="saveModel" v-if="showSaveModel && formData.methodName === 'train'">下载模型(.miail)</el-button>
9ec4bd 114       <el-input v-model="modelRunResult" placeholder="" rows="4" type="textarea" />
115       <div style="display: flex;flex-direction: row;justify-content: end;margin-top: 16px">
116         <el-button :loading="modelRunloading" type="primary" @click="modelRun()">运行</el-button>
117       </div>
118     </el-form>
119   </Dialog>
120 </template>
121 <script lang="ts" setup>
aff5c9 122   import * as MpkApi from '@/api/model/mpk/mpk'
9ec4bd 123   import {FormRules} from "element-plus";
124   import {getAccessToken, getTenantId} from "@/utils/auth";
be8574 125   import download from "@/utils/download";
50af0d 126   const staticDir = ref(import.meta.env.VITE_STATIC_DIR)
9ec4bd 127
128   const { t } = useI18n() // 国际化
129   const message = useMessage() // 消息弹窗
130
131   const dialogVisible = ref(false) // 弹窗的是否展示
132   const dialogTitle = ref('模型运行') // 弹窗的标题
133
134   const formData = reactive({
2bff3e 135     pyName: '',
9ec4bd 136     className: '',
137     methodName: '',
de019e 138     uuids: [],
9ec4bd 139     modelSettings: [],
140     model: undefined
141   })
de019e 142
D 143   const datas = ref([])
9ec4bd 144
145   // 模型方法下拉列表
146   const methodList = ref([])
147   const hasModel = ref(false)
148
149   /** 打开弹窗 */
150   const open = async (row) => {
151     dialogVisible.value = true
152     formData.className = row.pkgName + '.impl.' + row.pyName + 'Impl';
2bff3e 153     formData.pyName = row.pyName;
9ec4bd 154     const mpk = await MpkApi.getMpk(row.id)
155     methodList.value = mpk.modelMethods
156     formData.methodName = mpk.modelMethods[0].methodName
de019e 157     datas.value = []
D 158     formData.uuids = [];
9ec4bd 159     for (let i = 0 ; i < mpk.modelMethods[0].dataLength ; i++) {
de019e 160      datas.value[i] = '[[]]';
D 161      formData.uuids[i] = '';
9ec4bd 162     }
163     hasModel.value = mpk.modelMethods[0].model === 1
631da1 164
D 165     // 回显参数
166     if (mpk.modelMethods[0].methodSettings && mpk.modelMethods[0].methodSettings.length > 0) {
167       formData.modelSettings = mpk.modelMethods[0].methodSettings.map(e => {
de019e 168         e.settingValue = e.value;
D 169         return e;
631da1 170       })
D 171     }
be8574 172
9ec4bd 173   }
174   defineExpose({ open }) // 提供 open 方法,用于打开弹窗
175
176   const formRules = reactive<FormRules>({
177     methodName: [
178       {required: true, message: '方法名不能为空', trigger: 'blur'}
179     ],
180     className: [
181       {required: true, message: '全类名不能为空', trigger: 'blur'}
627a6b 182     ],
D 183     model: [
184       {required: true, message: 'model不能为空', trigger: 'blur'}
9ec4bd 185     ]
186   })
187
188   const addRow = function () {
189     formData.modelSettings.push({
190       settingKey: '',
191       settingValue: ''
192     })
193   }
194   const deleteRow = function (index) {
195     formData.modelSettings.splice(index, 1)
196   }
197   const methodChange = function (value) {
de019e 198     datas.value = []
D 199     formData.uuids = [];
631da1 200     var method = methodList.value.find(e => e.methodName === value);
D 201     for (let i = 0 ; i < method?.dataLength ; i++) {
de019e 202       datas.value[i] = '[[]]';
D 203       formData.uuids[i] = '';
9ec4bd 204     }
631da1 205     hasModel.value = method?.model === 1
D 206     // 回显参数
207     if (method.methodSettings && method.methodSettings.length > 0) {
208       formData.modelSettings = method.methodSettings.map(e => {
de019e 209         e.settingValue = e.value;
D 210         return e;
631da1 211       })
D 212     }else {
213       formData.modelSettings = []
214     }
9ec4bd 215   }
216
217   const fileList = ref([]) // 文件列表
218   const importUrl =
219     import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/mpk/api/import'
220   const formLoading = ref(false) // 表单的加载中
221   const uploadHeaders = ref() // 上传 Header 头
222   /** 上传错误提示 */
223   const submitFormError = (): void => {
224     message.error('导入失败,请检查导入文件!')
225     formLoading.value = false
226   }
227   const submitFormSuccess = (response: any) => {
de019e 228     try {
D 229       if (response.code !== 0) {
230         message.error(response.msg)
231         return
232       }
233       const data = response.data;
234       if (datas.value.length > data.length) {
235         message.error("导入数据长度为" + data.length + ",应≥" + datas.value.length)
236         return
237       }
238       for (let i = 0; i < datas.value.length; i++) {
239         datas.value[i] = data[i].data
240         formData.uuids[i] = data[i].uuid;
241       }
242       message.success('导入成功')
243     } finally {
9ec4bd 244       formLoading.value = false
245     }
246   }
247   const beforeUpload = function (file) {
248     // 提交请求
249     uploadHeaders.value = {
250       Authorization: 'Bearer ' + getAccessToken(),
251       'tenant-id': getTenantId()
252     }
253     formLoading.value = true
254     return true;
255   }
256
257   // 模型运行结果
258   const modelRunResult = ref('')
259   // 模型运行loading
260   const modelRunloading = ref(false)
261   // 表单 Ref
262   const formRef = ref()
263   // 运行
264   const modelRun = async () => {
afc4d5 265     modelRunResult.value = ''
be8574 266     showSaveModel.value = false
D 267   // 校验表单
9ec4bd 268     if (!formRef) return
269     const valid = await formRef.value.validate()
270     if (!valid) return
627a6b 271
D 272     if (hasModel.value) {
273       debugger
274       if (!formData.model || formData.model === '') {
275         message.error("model为必填项!")
276         return
277       }
278
279       try {
280         JSON.parse(formData.model)
281       } catch (e) {
282         message.error("model参数异常!")
283         return
284       }
285
286     }
287
288
9ec4bd 289     // 提交请求
290     modelRunloading.value = true
291     try {
292       const data = {
293         ...formData
294       }
295
296       //处理modelSettings
de019e 297       // let settingsPredict = {};
D 298       // data.modelSettings.forEach(e => {
299       //   settingsPredict[e.settingKey] = e.settingValue;
300       // })
301       // data.modelSettings = settingsPredict
9ec4bd 302       data.hasModel = hasModel.value
303       if (data.hasModel && data.model) {
de019e 304         data.model = JSON.parse(data.model)
9ec4bd 305       }else {
306         data.model = undefined
307       }
308
be8574 309       let result = await MpkApi.modelRun(data)
D 310
311       modelRunResult.value = result;
9ec4bd 312       message.success('运行成功')
be8574 313       // 训练方法
D 314       if (formData.methodName === 'train') {
315         result = JSON.parse(result);
316         // 返回结果正确
317         if (result?.status_code === '100' && result?.models?.model_path) {
318           // 有预测方法
319           if (methodList.value.some(e => e.methodName === 'predict')) {
320             saveModelParams.modelResult = result
321             saveModelParams.model = result?.models
322             showSaveModel.value = true
323           }
324         }
325       }
9ec4bd 326     } finally {
327       modelRunloading.value = false
328     }
329   }
be8574 330
D 331   const showSaveModel = ref(false)
332
333   const saveModelParams = reactive({
334     pyName: '',
335     className: '',
336     methodName: '',
337     uuids: [],
338     modelSettings: [],
339     predModelSettings: [],
340     hasModel: false,
341     model: undefined,
342     modelResult: undefined,
343     dataLength: undefined,
344     resultKey: undefined,
345   })
346
347   const saveModel = async () => {
348     saveModelParams.className = formData.className
349     saveModelParams.pyName = formData.pyName
350     saveModelParams.modelSettings = formData.modelSettings
351     const predMethod = methodList.value.find(e => e.methodName === 'predict');
352     saveModelParams.methodName = predMethod.methodName
353     saveModelParams.resultKey = predMethod.resultKey
354     //predModelSettings
355     if (predMethod.methodSettings && predMethod.methodSettings.length > 0) {
356       saveModelParams.predModelSettings = predMethod.methodSettings.map(e => {
357         e.settingValue = e.value;
358         return e;
359       })
360     }
361     saveModelParams.hasModel = predMethod.model === 1
362
363     saveModelParams.dataLength = predMethod.dataLength
364
365     const data = await MpkApi.saveModel(saveModelParams)
366     download.downloadFile(data, saveModelParams.pyName + '.miail')
367   }
9ec4bd 368 </script>