dengzedong
2024-11-27 9b24e4927f3fa29dd96597a1f918b88e6f304682
提交 | 用户 | 时间
9ec4bd 1 <template>
2   <div class="p-16px" style="background-color: #ffffff">
3     <el-header>
4       {{title}}
5     </el-header>
6     <el-main>
7       <el-form
8         ref="formRef"
9         v-loading="formLoading"
10         :model="formData"
11         :rules="formRules"
12         label-width="120px"
13       >
14         <el-divider content-position="left">模型信息</el-divider>
15         <el-row :gutter="8">
1e8d99 16           <el-col :span="12">
17             <el-form-item label="模型类型" prop="pyType">
18               <el-radio-group v-model="formData.pyType" @change="pyTypeChange">
d944f6 19                 <el-radio-button :disabled="actionType == 'edit'"
1e8d99 20                   v-for="dict in getDictOptions(DICT_TYPE.MODEL_TYPE)"
21                   :key="dict.label"
22                   :label="dict.value"
23                 >
24                   {{ dict.label }}
25                 </el-radio-button>
26               </el-radio-group>
27             </el-form-item>
28           </el-col>
29         </el-row>
30         <el-row :gutter="8">
d944f6 31           <el-col :span="12">
1e8d99 32             <el-form-item label="模型文件" prop="pyName">
9ec4bd 33               <el-input disabled v-model="formData.pyName" placeholder=""/>
34             </el-form-item>
35           </el-col>
36           <el-col :span="4">
37             <el-upload
38               ref="uploadRef"
39               v-model:file-list="fileList"
40               :show-file-list="false"
41               :action="importUrl"
42               :auto-upload="true"
43               :disabled="uploadLoading"
de019e 44               v-loading="uploadLoading"
9ec4bd 45               :before-upload="beforeUpload"
46               :headers="uploadHeaders"
47               :on-error="submitFormError"
48               :on-success="submitFormSuccess"
de019e 49               accept=".py"
9ec4bd 50             >
de019e 51               <el-tooltip content="上传.py算法文件" placement="top" effect="light">
b8017e 52                 <el-button type="primary">
53                   <Icon icon="ep:upload"/>
54                   模型上传
55                 </el-button>
56               </el-tooltip>
9ec4bd 57             </el-upload>
58           </el-col>
59         </el-row>
60         <el-row :gutter="8">
61           <el-col :span="12">
1e8d99 62             <el-form-item label="模型名称" prop="pyChineseName">
9ec4bd 63               <el-input v-model="formData.pyChineseName" placeholder=""/>
64             </el-form-item>
65           </el-col>
1e8d99 66
9ec4bd 67         </el-row>
68         <el-row :gutter="8">
69           <el-col :span="12">
70             <el-form-item label="包名" prop="pkgName">
d944f6 71               <el-select v-model="formData.pkgName" clearable filterable placeholder="请选择包名">
72                 <el-option
73                   v-for="item in pkgNameList"
74                   :key="item.packName"
75                   :label="item.packName"
76                   :value="item.packName"
77                 >
78                   <span style="float: left">{{ item.packName}}</span>
79                   <span
80                     style="
81                       float: right;
82                       color: var(--el-text-color-secondary);
83                       font-size: 13px;">
84                     {{ item.packDesc}}
85                   </span>
86                 </el-option>
87               </el-select>
9ec4bd 88             </el-form-item>
89           </el-col>
90         </el-row>
91         <el-row :gutter="8">
92           <el-col :span="12">
1e8d99 93             <el-form-item label="所属目录" prop="menuAndGroup">
94               <el-cascader
95                 style="width: 100%;"
96                 v-model="formData.menuAndGroup"
97                 :options="treeData"
98                 @change="handleChange"
99               />
9ec4bd 100             </el-form-item>
101           </el-col>
102         </el-row>
103         <el-row :gutter="8">
104           <el-col :span="12">
1e8d99 105             <el-form-item label="图标" prop="icon">
106               <el-select v-model="formData.icon" clearable filterable placeholder="请选择图标">
107                 <el-option
108                   v-for="item in iconList"
109                   :key="item.iconName"
110                   :label="item.iconName"
111                   :value="item.iconName"
112                 >
113                   <span style="float: left">{{ item.iconName}}</span>
114                   <span
115                       style="
116                       float: right;
117                       color: var(--el-text-color-secondary);
118                       font-size: 13px;">
963828 119                     <img :src="staticDir + 'SimtreeUnitImage/' + item.iconName" style="height: 24px;" :alt=" item.iconDesc" />
1e8d99 120                   </span>
121                 </el-option>
122               </el-select>
9ec4bd 123             </el-form-item>
124           </el-col>
125         </el-row>
126         <el-row :gutter="20">
d944f6 127           <el-col :span="12">
9ec4bd 128             <el-form-item label="备注" prop="remark">
129               <el-input v-model="formData.remark" placeholder="" type="textarea"/>
130             </el-form-item>
131           </el-col>
132         </el-row>
133         <el-divider content-position="left">模型方法</el-divider>
134         <el-row :gutter="20">
135           <el-col :span="4">
136             <el-button type="primary" size="small" @click="addRow()" >新增</el-button>
137           </el-col>
138         </el-row>
139         <el-table :data="formData.modelMethods" border
140                   @expand-change="methodExpandChange" :expand-row-keys="methodExpandedRowKeys" :row-key="row => row.id">
141           <el-table-column
142             prop=""
143             label="方法名"
144             align="center"
145             width="250">
146             <template #default="scope">
147               <el-input size="small" v-model="scope.row.methodName" placeholder=""/>
148             </template>
149           </el-table-column>
150           <el-table-column
151             prop=""
152             label="输入个数"
153             align="center">
154             <template #default="scope">
155               <el-input-number size="small" step-strictly v-model="scope.row.dataLength" :min="1"
156                                :max="50"/>
157             </template>
158           </el-table-column>
159           <el-table-column
160             prop=""
161             label="是否有model"
162             align="center">
163             <template #default="scope">
164               <el-switch size="small" v-model="scope.row.model" :active-value="1"
165                          :inactive-value="0"/>
166             </template>
167           </el-table-column>
168           <el-table-column
169             prop=""
170             label="结果key"
171             align="center">
172             <template #default="scope">
173               <el-input size="small" v-model="scope.row.resultKey"/>
174             </template>
175           </el-table-column>
176           <el-table-column label="方法参数" type="expand" width="100px">
177             <template #default="props">
178               <div class="m-16px">
179                 <el-button type="primary" size="small" @click="addSetting(props.row.methodSettings)">新增参数</el-button>
180                 <el-table :data="props.row.methodSettings" border size="small">
181                   <el-table-column align="center" label="key" prop="settingKey"/>
182                   <el-table-column align="center" label="参数名称" prop="name"/>
183                   <el-table-column align="center" label="参数默认值" prop="value"/>
184                   <el-table-column align="center" label="输入类型" prop="type">
185                     <template #default="props">
186                       <div class="flex file-row justify-center items-center">
187                         {{props.row.type}}
188                         <div class="ml-8px" v-if="props.row.type === 'select'">
189                           <el-popover placement="left" :width="400">
190                             <template #reference>
191                               <el-button size="small" link type="primary">
192                                 <Icon icon="ep:more" />
193                               </el-button>
194                             </template>
195                             <el-table width="50%" :data="props.row.settingSelects" border size="small">
196                               <el-table-column align="center" label="key" prop="selectKey"/>
197                               <el-table-column align="center" label="name" prop="name"/>
198                             </el-table>
199                           </el-popover>
200                         </div>
201                       </div>
202                     </template>
203                   </el-table-column>
204                   <el-table-column align="center" label="参数类型" prop="valueType"/>
205                   <el-table-column align="center" label="最大值" prop="max"/>
206                   <el-table-column align="center" label="最小值" prop="min"/>
207                   <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">
208                     <template #default="scope">
209                       <el-button
210                         @click="updateSetting(scope.row)"
211                         key="danger"
b8017e 212                         type="primary"
627a6b 213                         :disabled="scope.row.settingKey === 'pyFile'"
9ec4bd 214                         link
215                       >修改
216                       </el-button>
217                       <el-button
218                         @click="deleteSetting(props.row.methodSettings,scope.$index)"
219                         key="danger"
220                         type="danger"
627a6b 221                         :disabled="scope.row.settingKey === 'pyFile'"
9ec4bd 222                         link
223                       >删除
224                       </el-button>
225                     </template>
226                   </el-table-column>
227                 </el-table>
228               </div>
229             </template>
230           </el-table-column>
231           <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">
232             <template #default="scope">
233               <el-button
234                 @click="deleteRow(scope.$index)"
235                 key="danger"
236                 type="danger"
237                 link
238               >删除
239               </el-button>
240             </template>
241           </el-table-column>
242         </el-table>
243       </el-form>
244     </el-main>
245     <el-footer>
246       <div class="flex flex-row justify-end items-center">
247         <el-button type="primary" @click="submitForm">确 定</el-button>
248       </div>
249     </el-footer>
250   </div>
251   <SettingForm ref="settingFormRef"/>
252 </template>
253 <script lang="ts" setup>
1e8d99 254   import {DICT_TYPE, getDictOptions, getIntDictOptions} from '@/utils/dict'
aff5c9 255   import * as MpkApi from '@/api/model/mpk/mpk'
1e8d99 256   import * as MpkIconApi from '@/api/model/mpk/icon'
d944f6 257   import * as MpkPackApi from '@/api/model/mpk/pack'
1e8d99 258   import * as MpkMenuApi from '@/api/model/mpk/menu'
9ec4bd 259   import {FormRules} from 'element-plus'
260   import {getAccessToken, getTenantId} from "@/utils/auth";
261   import SettingForm from './SettingForm.vue'
262   import {generateUUID} from "@/utils";
263
264   const {t} = useI18n() // 国际化
265   const message = useMessage() // 消息弹窗
266   const title = ref('') // 弹窗的标题
d944f6 267   const actionType = ref('') // 操作类型
9ec4bd 268   const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
269   const formType = ref('') // 表单的类型:create - 新增;update - 修改
270   const route = useRoute() // 路由
271   const router = useRouter();
272
963828 273   const staticDir = ref(import.meta.env.VITE_STATIC_DIR)
274
1e8d99 275   const treeData = ref([])
276   const iconList = ref([] as MpkIconApi.MpkIconVO)
d944f6 277   const pkgNameList = ref([] as MpkPackApi.MpkPackVO)
1e8d99 278
279 /** settingForm弹窗 */
9ec4bd 280   const settingFormRef = ref()
281   // 添加setting
282   const addSetting = (methodSettings) => {
283     settingFormRef.value.open(undefined,methodSettings)
284   }
285
286   // 修改setting
287   const updateSetting = (info) => {
288     settingFormRef.value.open(info)
289   }
f7a1fe 290   // 删除setting
D 291   const deleteSetting = (methodSettings,index) => {
292     methodSettings.splice(index, 1);
293   }
9ec4bd 294
295   const methodExpandedRowKeys = ref([])
296   const methodExpandChange = async (row: any, expandedRows: any[]) => {
297     methodExpandedRowKeys.value = expandedRows.map(e => e.id)
298   }
299
300   const formData = ref({
301     id: route.params.id,
302     pyChineseName: undefined,
303     pyName: undefined,
304     pkgName: undefined,
1e8d99 305     pyType: 'predict',
9ec4bd 306     className: undefined,
307     pyModule: undefined,
308     icon: undefined,
309     menuName: undefined,
310     groupName: undefined,
311     remark: undefined,
312     modelMethods: [],
313     filePath: undefined,
1e8d99 314     menuAndGroup: [],
9ec4bd 315   })
316
317   const formRules = reactive<FormRules>({
318     pyName: [
319       {required: true, message: '模型名称不能为空,请上传模型文件', trigger: 'blur'}
320     ],
321     pyChineseName: [
322       {required: true, message: '模型中文名称不能为空', trigger: 'blur'}
323     ],
324     pyType: [
325       {required: true, message: '模型类型不能为空', trigger: 'blur'}
326     ],
327     pkgName: [
328       {required: true, message: '包名不能为空', trigger: 'blur'}
329     ],
330     className: [
331       {required: true, message: '类名不能为空', trigger: 'blur'}
332     ],
1e8d99 333     menuAndGroup: [
9ec4bd 334       {required: true, message: '所属目录不能为空', trigger: 'blur'}
335     ],
336   })
337
338   const formRef = ref() // 表单 Ref
339
340   /** 提交表单 */
341   const submitForm = async () => {
342     // 校验表单
343     if (!formRef) return
344     const valid = await formRef.value.validate()
345     if (!valid) return
346     // 模型方法校验
347     if (formData.value.modelMethods?.length <= 0) {
348       message.error('模型方法为空')
349       return
350     }
351     // 模型方法名称校验
352     if (formData.value.modelMethods.some(e => e.methodName === undefined || e.methodName === '' || e.dataLength === undefined || e.dataLength === null)) {
353       message.error('存在不合法模型方法名')
354       return
355     }
356     // 提交请求
357     formLoading.value = true
358     try {
359       const data = formData.value as unknown as MpkApi.MpkVO
360       if (formType.value === 'create') {
361         await MpkApi.createMpk(data)
362         message.success(t('common.createSuccess'))
363       } else {
364         await MpkApi.updateMpk(data)
365         message.success(t('common.updateSuccess'))
366       }
367     } finally {
368       formLoading.value = false
369     }
370     // router.push({path:'/model/mpk'})
371     router.back()
372   }
373
374   /** 重置表单 */
375   const resetForm = () => {
376     formData.value = {
377       id: undefined,
378       pyName: undefined,
379       pyChineseName: undefined,
380       pkgName: undefined,
1e8d99 381       pyType: 'predict',
9ec4bd 382       className: undefined,
383       pyModule: undefined,
384       icon: undefined,
385       menuName: undefined,
386       groupName: undefined,
387       remark: undefined,
388       modelMethods: [],
389       filePath: undefined
390     }
391     formRef.value?.resetFields()
1e8d99 392   }
393
394   const handleChange = function () {
395
9ec4bd 396   }
397
398   const addRow = function () {
399     formData.value.modelMethods.push({
400       id: generateUUID(),
401       methodName: undefined,
402       dataLength: 1,
403       model: 0,
404       resultKey: undefined,
405       methodSettings: []
406     })
407   }
408   const deleteRow = function (index) {
409     formData.value.modelMethods.splice(index, 1)
410   }
411
412   const fileList = ref([]) // 文件列表
413   const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/mpk/file/upload'
414   const uploadLoading = ref(false) // 表单的加载中
415   const uploadHeaders = ref() // 上传 Header 头
416   const beforeUpload = function (file) {
417     // 提交请求
418     uploadHeaders.value = {
419       Authorization: 'Bearer ' + getAccessToken(),
420       'tenant-id': getTenantId()
421     }
422     uploadLoading.value = true
423     return true;
424   }
425   const submitFormError = (): void => {
426     message.error('上传失败!')
427     uploadLoading.value = false
428   }
429   const submitFormSuccess = (response: any) => {
430     if (response.code !== 0) {
431       message.error(response.msg)
432       uploadLoading.value = false
433       return
434     }
435     const data = response.data;
436     formData.value.filePath = data.filePath
de019e 437     formData.value.pyName = data.fileName
9ec4bd 438     message.success('上传成功')
439     uploadLoading.value = false
440   }
441
442   onMounted(async () => {
443     const id = formData.value.id;
444     const type = id ? 'edit' : 'create'
d944f6 445     actionType.value = type
9ec4bd 446     title.value = t('action.' + type)
447     formType.value = type
448     resetForm()
1e8d99 449     pyTypeChange()
9ec4bd 450     // 修改时,设置数据
451     if (id) {
452       formLoading.value = true
453       try {
9b24e4 454         debugger
9ec4bd 455         formData.value = await MpkApi.getMpk(id)
9b24e4 456         debugger
9ec4bd 457       } finally {
458         formLoading.value = false
459       }
460     }
1e8d99 461
462     // 加载图标列表
463     iconList.value = await MpkIconApi.getList()
464
d944f6 465     pkgNameList.value = await MpkPackApi.getList()
466
1e8d99 467     // 加载菜单,分组
468     treeData.value = await MpkMenuApi.getTree()
9ec4bd 469   })
470
471   const pyTypeChange = () => {
472     if (formData.value.pyType === 'predict') {
473       formData.value.modelMethods = [
474         {
475           id: generateUUID(),
476           methodName: 'train',
477           dataLength: 1,
478           model: 0,
f7a1fe 479           resultKey: 'result',
9ec4bd 480           methodSettings: []
481         },
482         {
483
484           id: generateUUID(),
485           methodName: 'predict',
486           dataLength: 1,
487           model: 1,
488           resultKey: undefined,
489           methodSettings: []
490         }
491       ]
492     }else {
493       formData.value.modelMethods = [
494         {
495           id: generateUUID(),
496           methodName: 'schedul',
497           dataLength: 1,
498           model: 0,
499           resultKey: undefined,
500           methodSettings: []
501         }
502       ]
503     }
504   }
505 </script>
506
507 <style scoped lang="scss">
508 </style>