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