284 lines
11 KiB
Vue
284 lines
11 KiB
Vue
<template>
|
|
<div>
|
|
<el-dialog :title="title" :visible.sync="isOpen" width="500px" append-to-body @close="cancel"
|
|
:close-on-click-modal="false"
|
|
>
|
|
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
|
<el-form-item label="镜像名称" prop="mirrorName">
|
|
<el-input v-model="form.mirrorName" maxlength="100" placeholder="请输入镜像名称"/>
|
|
</el-form-item>
|
|
<el-form-item label="镜像版本" prop="mirrorVersion">
|
|
<el-input v-model="form.mirrorVersion" placeholder="请输入镜像名称"/>
|
|
</el-form-item>
|
|
<el-form-item label="所属模型" prop="ownModel">
|
|
<el-select v-model="form.ownModel" placeholder="请选择所属模型" clearable style="width: 100%">
|
|
<el-option v-for="dict in modelList" :key="dict.id" :label="dict.modelName +' - '+dict.modelVersion"
|
|
:value="dict.id"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="运行环境" prop="runEnvironment">
|
|
<el-select v-model="form.runEnvironment" placeholder="请选择运行环境" clearable style="width: 100%">
|
|
<el-option v-for="dict in runEnvironmentList" :key="dict.value" :label="dict.label"
|
|
:value="dict.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="模型框架" prop="modelFrame">
|
|
<el-select v-model="form.modelFrame" placeholder="请选择模型框架" clearable style="width: 100%">
|
|
<el-option v-for="dict in dict.type.ai_frame_type" :key="dict.value" :label="dict.label"
|
|
:value="dict.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="语言" prop="language">
|
|
<el-select v-model="form.language" placeholder="请选择语言" clearable style="width: 100%">
|
|
<el-option v-for="dict in languageList" :key="dict.value" :label="dict.label"
|
|
:value="dict.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="镜像文件" prop="modelFileName">
|
|
<div class="custom-upload">
|
|
<input type="file" ref="modelInput" style="display: none" @change="handleFileChange('modelFile', $event)"
|
|
accept=".zip"
|
|
/>
|
|
<el-button size="mini" type="text" v-if="!form.modelFileName" @click="triggerFileInput('modelInput')">选择文件
|
|
</el-button>
|
|
<span style="cursor: pointer;" @click="triggerFileInput('modelInput')" v-if="form.modelFileName"
|
|
>{{ form.modelFileName }}</span>
|
|
<el-progress v-if="uploading" :percentage="uploadProgress.modelFile"/>
|
|
</div>
|
|
</el-form-item>
|
|
<el-form-item label="接口文档" prop="manualFileName">
|
|
<div class="custom-upload">
|
|
<input type="file" ref="manualInput" style="display: none"
|
|
@change="handleFileChange('manualFile', $event)" accept=".doc,.docx,.pdf"
|
|
/>
|
|
<el-button size="mini" type="text" v-if="!form.manualFileName" @click="triggerFileInput('manualInput')">
|
|
选择文件
|
|
</el-button>
|
|
<span style="cursor: pointer;" @click="triggerFileInput('manualInput')" v-if="form.manualFileName"
|
|
>{{ form.manualFileName }}<i></i></span>
|
|
<el-progress v-if="uploading" :percentage="uploadProgress.manualFile"/>
|
|
</div>
|
|
</el-form-item>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
|
<el-button @click="cancel">取 消</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { getManager, addManager, updateManager, uploadFile, listAll } from '@/api/dataCenter/mirror'
|
|
import { datasetList } from '@/api/dataCenter/dataSet'
|
|
import { generateUUID } from '../../../../utils/configure'
|
|
|
|
export default {
|
|
dicts: ['ai_annotate_type', 'ai_frame_type'],
|
|
props: {
|
|
open: { type: Boolean, required: true },
|
|
getList: { type: Function, required: true },
|
|
title: { type: String, required: true },
|
|
mirrorId: { type: [Number, null], default: 0 }
|
|
},
|
|
computed: {
|
|
isOpen: {
|
|
get() {
|
|
return this.open
|
|
},
|
|
set(value) {
|
|
this.$emit('dialog-cancel')
|
|
}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
runEnvironmentList: [{
|
|
value: '0',
|
|
label: 'CPU'
|
|
},
|
|
{
|
|
value: '1',
|
|
label: 'GPU'
|
|
}],
|
|
languageList: [
|
|
{ 'value': '0', 'label': 'Java' },
|
|
{ 'value': '1', 'label': 'Python' },
|
|
{ 'value': '2', 'label': 'JavaScript (Node.js)' },
|
|
{ 'value': '3', 'label': 'PHP' },
|
|
{ 'value': '4', 'label': 'C# (.NET)' },
|
|
{ 'value': '5', 'label': 'Ruby' },
|
|
{ 'value': '6', 'label': 'Go (Golang)' },
|
|
{ 'value': '7', 'label': 'C/C++' },
|
|
{ 'value': '8', 'label': 'Kotlin' },
|
|
{ 'value': '9', 'label': 'Rust' },
|
|
{ 'value': '10', 'label': 'Scala' },
|
|
{ 'value': '11', 'label': 'Perl' },
|
|
{ 'value': '12', 'label': 'Swift' },
|
|
{ 'value': '13', 'label': 'Elixir' }
|
|
],
|
|
modelList: [],
|
|
uploadProgress: { modelFile: 0, manualFile: 0 },
|
|
uploading: false,
|
|
chunkSize: 1024 * 1024 * 20, // 20MB
|
|
form: { modelFile: null, manualFile: null, modelFileName: '', manualFileName: '' },
|
|
rules: {
|
|
mirrorName: [{ required: true, message: '镜像名称不能为空', trigger: 'blur' }],
|
|
mirrorVersion: [{
|
|
required: true,
|
|
message: '版本号不能为空',
|
|
trigger: 'blur'
|
|
}, { pattern: /^V\d{1,3}(\.\d{1,3}){2}$/, message: '版本名称式不正确,应为 VX.Y.Z 格式', trigger: 'blur' }],
|
|
runEnvironment: [{ required: true, message: '请选择运行环境', trigger: 'blur' }],
|
|
language: [{ required: true, message: '请选择语言', trigger: 'blur' }],
|
|
modelFrame: [{ required: true, message: '请选择模型框架', trigger: 'blur' }],
|
|
modelFileName: [{ required: true, message: '请上传镜像文件', trigger: 'blur' }],
|
|
manualFileName: [{ required: true, message: '请上传接口文档', trigger: 'blur' }]
|
|
},
|
|
currentUpload: { modelFile: null, manualFile: null } // 为每个文件设置独立的上传任务
|
|
}
|
|
},
|
|
watch: {
|
|
isOpen(newVal) {
|
|
if (newVal) {
|
|
console.log(this.mirrorId)
|
|
if (this.mirrorId) {
|
|
getManager(this.mirrorId).then(response => {
|
|
this.form = response.data
|
|
})
|
|
listAll().then(response => {
|
|
this.modelList = response.data
|
|
})
|
|
}
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
triggerFileInput(refName) {
|
|
this.$refs[refName].click()
|
|
},
|
|
handleFileChange(field, event) {
|
|
const file = event.target.files[0]
|
|
if (file) {
|
|
this.form[field] = file
|
|
this.form[`${field}Name`] = file.name
|
|
}
|
|
},
|
|
resetField(field) {
|
|
this.form[field] = null
|
|
this.form[`${field}Name`] = ''
|
|
this.$refs[field].value = '' // 重置文件选择
|
|
},
|
|
uploadChunks(file, field, uuid) {
|
|
return new Promise((resolve, reject) => {
|
|
// 文件为空时直接返回
|
|
if (!file || file.size === 0) {
|
|
resolve()
|
|
}
|
|
const totalChunks = Math.ceil(file.size / this.chunkSize)
|
|
let currentChunk = 0
|
|
this.uploading = true // 标记正在上传
|
|
const controller = new AbortController() // 新增AbortController
|
|
const signal = controller.signal // 获取 signal 用于中止请求
|
|
|
|
const uploadNextChunk = () => {
|
|
if (this.uploading === false) { // 如果上传已经取消,退出
|
|
reject('Upload canceled')
|
|
return
|
|
}
|
|
const start = currentChunk * this.chunkSize
|
|
const end = Math.min(file.size, start + this.chunkSize)
|
|
const chunk = file.slice(start, end)
|
|
const formData = new FormData()
|
|
formData.append('file', chunk)
|
|
formData.append('fileName', uuid + `.${file.name.split('.').pop().toLowerCase()}`)
|
|
formData.append('chunk', currentChunk + 1)
|
|
formData.append('totalChunks', totalChunks)
|
|
uploadFile(formData, { signal }) // 在上传请求中传入 signal 用于中止
|
|
.then(res => {
|
|
currentChunk++
|
|
// 更新进度
|
|
this.uploadProgress[field] = Math.floor((currentChunk / totalChunks) * 100)
|
|
if (currentChunk < totalChunks) {
|
|
uploadNextChunk()
|
|
} else {
|
|
if (field === 'modelFile') {
|
|
this.form.mirrorPath = res.data.fileUrl
|
|
}
|
|
if (field === 'manualFile') {
|
|
this.form.documentPath = res.data.fileUrl
|
|
}
|
|
resolve() // 上传完成,返回成功
|
|
}
|
|
})
|
|
.catch(error => {
|
|
if (error.name === 'AbortError') {
|
|
console.log('Upload was aborted')
|
|
}
|
|
reject(error) // 上传失败,返回失败
|
|
this.$message.error('上传失败')
|
|
})
|
|
}
|
|
|
|
uploadNextChunk()
|
|
// 将当前上传任务保存到 currentUpload 中
|
|
this.currentUpload[field] = {
|
|
abort: () => controller.abort() // 提供 abort 方法来中止上传
|
|
}
|
|
|
|
return this.currentUpload[field] // 返回当前上传任务
|
|
})
|
|
},
|
|
submitForm() {
|
|
this.$refs.form.validate(valid => {
|
|
if (valid) {
|
|
// 上传文件后再调用新增或修改操作
|
|
// 独立上传每个文件,并确保只有所有文件上传成功后才提交表单
|
|
Promise.all([
|
|
this.uploadChunks(this.form.modelFile, 'modelFile', generateUUID()),
|
|
this.uploadChunks(this.form.manualFile, 'manualFile', generateUUID())
|
|
]).then(() => {
|
|
this.uploading = false
|
|
const action = this.form.id ? updateManager : addManager
|
|
action(this.form)
|
|
.then(() => {
|
|
this.$message.success(this.form.id ? '修改成功' : '新增成功')
|
|
this.cancel()
|
|
this.getList()
|
|
})
|
|
})
|
|
.catch(() => {
|
|
this.$message.error('文件上传失败,无法提交')
|
|
})
|
|
}
|
|
})
|
|
},
|
|
cancel() {
|
|
this.isOpen = false
|
|
this.uploading = false
|
|
// 取消当前文件的上传任务
|
|
if (this.currentUpload.modelFile && this.currentUpload.modelFile.abort) {
|
|
this.currentUpload.modelFile.abort()
|
|
}
|
|
if (this.currentUpload.manualFile && this.currentUpload.manualFile.abort) {
|
|
this.currentUpload.manualFile.abort()
|
|
}
|
|
this.resetForm()
|
|
},
|
|
resetForm() {
|
|
this.resetField('modelInput')
|
|
this.resetField('manualInput')
|
|
this.uploading = false
|
|
this.uploadProgress = { modelFile: 0, manualFile: 0 }
|
|
this.form = { modelFile: null, manualFile: null, modelFileName: '', manualFileName: '' }
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss"></style>
|