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