houzhongjian
2024-12-04 bb203eb72ee4604be8c9272cc583ecb9e393aeb8
提交 | 用户 | 时间
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     ],
6cd12b 336     icon: [
D 337       {required: true, message: 'icon不能为空', trigger: 'blur'}
338     ],
9ec4bd 339   })
340
341   const formRef = ref() // 表单 Ref
342
343   /** 提交表单 */
344   const submitForm = async () => {
345     // 校验表单
346     if (!formRef) return
347     const valid = await formRef.value.validate()
348     if (!valid) return
349     // 模型方法校验
350     if (formData.value.modelMethods?.length <= 0) {
351       message.error('模型方法为空')
352       return
353     }
354     // 模型方法名称校验
355     if (formData.value.modelMethods.some(e => e.methodName === undefined || e.methodName === '' || e.dataLength === undefined || e.dataLength === null)) {
356       message.error('存在不合法模型方法名')
357       return
358     }
359     // 提交请求
360     formLoading.value = true
361     try {
362       const data = formData.value as unknown as MpkApi.MpkVO
363       if (formType.value === 'create') {
364         await MpkApi.createMpk(data)
365         message.success(t('common.createSuccess'))
366       } else {
367         await MpkApi.updateMpk(data)
368         message.success(t('common.updateSuccess'))
369       }
370     } finally {
371       formLoading.value = false
372     }
373     // router.push({path:'/model/mpk'})
374     router.back()
375   }
376
377   /** 重置表单 */
378   const resetForm = () => {
379     formData.value = {
380       id: undefined,
381       pyName: undefined,
382       pyChineseName: undefined,
383       pkgName: undefined,
1e8d99 384       pyType: 'predict',
9ec4bd 385       className: undefined,
386       pyModule: undefined,
387       icon: undefined,
388       menuName: undefined,
389       groupName: undefined,
390       remark: undefined,
391       modelMethods: [],
392       filePath: undefined
393     }
394     formRef.value?.resetFields()
1e8d99 395   }
396
397   const handleChange = function () {
398
9ec4bd 399   }
400
401   const addRow = function () {
402     formData.value.modelMethods.push({
403       id: generateUUID(),
404       methodName: undefined,
405       dataLength: 1,
406       model: 0,
407       resultKey: undefined,
408       methodSettings: []
409     })
410   }
411   const deleteRow = function (index) {
412     formData.value.modelMethods.splice(index, 1)
413   }
414
415   const fileList = ref([]) // 文件列表
416   const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/mpk/file/upload'
417   const uploadLoading = ref(false) // 表单的加载中
418   const uploadHeaders = ref() // 上传 Header 头
419   const beforeUpload = function (file) {
420     // 提交请求
421     uploadHeaders.value = {
422       Authorization: 'Bearer ' + getAccessToken(),
423       'tenant-id': getTenantId()
424     }
425     uploadLoading.value = true
426     return true;
427   }
428   const submitFormError = (): void => {
429     message.error('上传失败!')
430     uploadLoading.value = false
431   }
432   const submitFormSuccess = (response: any) => {
433     if (response.code !== 0) {
434       message.error(response.msg)
435       uploadLoading.value = false
436       return
437     }
438     const data = response.data;
439     formData.value.filePath = data.filePath
de019e 440     formData.value.pyName = data.fileName
9ec4bd 441     message.success('上传成功')
442     uploadLoading.value = false
443   }
444
445   onMounted(async () => {
446     const id = formData.value.id;
447     const type = id ? 'edit' : 'create'
d944f6 448     actionType.value = type
9ec4bd 449     title.value = t('action.' + type)
450     formType.value = type
451     resetForm()
1e8d99 452     pyTypeChange()
9ec4bd 453     // 修改时,设置数据
454     if (id) {
455       formLoading.value = true
456       try {
9b24e4 457         debugger
9ec4bd 458         formData.value = await MpkApi.getMpk(id)
9b24e4 459         debugger
9ec4bd 460       } finally {
461         formLoading.value = false
462       }
463     }
1e8d99 464
465     // 加载图标列表
466     iconList.value = await MpkIconApi.getList()
467
d944f6 468     pkgNameList.value = await MpkPackApi.getList()
469
1e8d99 470     // 加载菜单,分组
471     treeData.value = await MpkMenuApi.getTree()
9ec4bd 472   })
473
474   const pyTypeChange = () => {
475     if (formData.value.pyType === 'predict') {
476       formData.value.modelMethods = [
477         {
478           id: generateUUID(),
479           methodName: 'train',
480           dataLength: 1,
481           model: 0,
f7a1fe 482           resultKey: 'result',
9ec4bd 483           methodSettings: []
484         },
485         {
486
487           id: generateUUID(),
488           methodName: 'predict',
489           dataLength: 1,
490           model: 1,
491           resultKey: undefined,
492           methodSettings: []
493         }
494       ]
495     }else {
496       formData.value.modelMethods = [
497         {
498           id: generateUUID(),
499           methodName: 'schedul',
500           dataLength: 1,
501           model: 0,
502           resultKey: undefined,
503           methodSettings: []
504         }
505       ]
506     }
507   }
508 </script>
509
510 <style scoped lang="scss">
511 </style>