提交代码
This commit is contained in:
		
							parent
							
								
									7bd8f1d02f
								
							
						
					
					
						commit
						4014e2918a
					
				| 
						 | 
				
			
			@ -9,6 +9,14 @@ export function listManager(query) {
 | 
			
		|||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function listAll(query) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/ai/model/listAll',
 | 
			
		||||
    method: 'get',
 | 
			
		||||
    params: query
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 查询镜像管理详细
 | 
			
		||||
export function getManager(id) {
 | 
			
		||||
  return request({
 | 
			
		||||
| 
						 | 
				
			
			@ -42,3 +50,15 @@ export function delManager(id) {
 | 
			
		|||
    method: 'post'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function uploadFile(formData) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/ai/mirror/uploadFile',
 | 
			
		||||
    method: 'post',
 | 
			
		||||
    data: formData,
 | 
			
		||||
    timeout: 600000, // 设置超时时间为 10 秒(10000 毫秒)
 | 
			
		||||
    headers: {
 | 
			
		||||
      'Content-Type': 'application/x-www-form-urlencoded'
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
export default {
 | 
			
		||||
  bind(el, binding) {
 | 
			
		||||
    const debounce = (func, delay) => {
 | 
			
		||||
      let timer;
 | 
			
		||||
      return function (...args) {
 | 
			
		||||
        clearTimeout(timer);
 | 
			
		||||
        timer = setTimeout(() => func.apply(this, args), delay);
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    el.addEventListener('click', debounce(() => {
 | 
			
		||||
      binding.value();
 | 
			
		||||
    }, binding.arg || 300)); // 默认延迟300ms
 | 
			
		||||
  },
 | 
			
		||||
  unbind(el) {
 | 
			
		||||
    el.removeEventListener('click', el._debounceClick);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,9 @@ import DictTag from '@/components/DictTag'
 | 
			
		|||
import VueMeta from 'vue-meta'
 | 
			
		||||
// 字典数据组件
 | 
			
		||||
import DictData from '@/components/DictData'
 | 
			
		||||
 | 
			
		||||
import debounce from './directives/debounce';
 | 
			
		||||
// 全局方法挂载
 | 
			
		||||
Vue.directive('debounce', debounce);
 | 
			
		||||
// 全局方法挂载
 | 
			
		||||
Vue.prototype.getDicts = getDicts
 | 
			
		||||
Vue.prototype.getConfigKey = getConfigKey
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,7 +94,7 @@
 | 
			
		|||
        </div>
 | 
			
		||||
      </el-form>
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <el-button type="primary" @click="submitForm">确 定</el-button>
 | 
			
		||||
        <el-button type="primary" v-debounce:500="submitForm">确 定</el-button>
 | 
			
		||||
        <el-button @click="cancel">取 消</el-button>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <el-button type="primary" @click="submitForm">确 定</el-button>
 | 
			
		||||
        <el-button type="primary" v-debounce:500="submitForm">确 定</el-button>
 | 
			
		||||
        <el-button @click="cancel">取 消</el-button>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@
 | 
			
		|||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <el-button type="primary" @click="submitForm">确 定</el-button>
 | 
			
		||||
        <el-button type="primary" v-debounce:500="submitForm">确 定</el-button>
 | 
			
		||||
        <el-button @click="cancel">取 消</el-button>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,7 +57,6 @@
 | 
			
		|||
          <dict-tag :options="dict.type.ai_annotate_type" :value="scope.row.annotationType"/>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column label="已审核数量" align="center" show-overflow-tooltip prop="auditedCount" min-width="100" />
 | 
			
		||||
      <el-table-column label="操作" align="center" min-width="100" class-name="small-padding fixed-width" >
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,12 +28,21 @@
 | 
			
		|||
      :visible.sync="drawer"
 | 
			
		||||
      :before-close="handleClose"
 | 
			
		||||
      :close-on-click-modal="false"
 | 
			
		||||
      :wrapper-closable="false">
 | 
			
		||||
      :wrapper-closable="false"
 | 
			
		||||
    >
 | 
			
		||||
      <!-- 显示所有文件的上传进度 -->
 | 
			
		||||
      <div v-for="([filename, progress], index) in Object.entries(uploadProgress)" :key="index" style="padding-left: 20px;padding-right: 20px">
 | 
			
		||||
        <div style="display: flex;flex-direction: row;align-items: center;justify-content: flex-start;margin-bottom: 10px">
 | 
			
		||||
          <i v-if="progress ===100 && !uploadFailed[filename]" style="color: #13ce66;font-size: 18px" class="el-icon-success"></i>
 | 
			
		||||
          <i v-if="progress !==100 && !uploadFailed[filename]" style="font-size: 18px;color: rgba(110,106,106,0.3)" class="el-icon-upload"></i>
 | 
			
		||||
      <div v-for="([filename, progress], index) in Object.entries(uploadProgress)" :key="index"
 | 
			
		||||
           style="padding-left: 20px;padding-right: 20px"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          style="display: flex;flex-direction: row;align-items: center;justify-content: flex-start;margin-bottom: 10px"
 | 
			
		||||
        >
 | 
			
		||||
          <i v-if="progress ===100 && !uploadFailed[filename]" style="color: #13ce66;font-size: 18px"
 | 
			
		||||
             class="el-icon-success"
 | 
			
		||||
          ></i>
 | 
			
		||||
          <i v-if="progress !==100 && !uploadFailed[filename]" style="font-size: 18px;color: rgba(110,106,106,0.3)"
 | 
			
		||||
             class="el-icon-upload"
 | 
			
		||||
          ></i>
 | 
			
		||||
          <i v-if="uploadFailed[filename]" style="font-size: 18px;color: red" class="el-icon-warning"></i>
 | 
			
		||||
          <span style="margin-left: 2%;font-size: 18px">{{ filename }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -52,19 +61,19 @@ export default {
 | 
			
		|||
  props: {
 | 
			
		||||
    parentId: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    fileUrl: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: true,
 | 
			
		||||
      required: true
 | 
			
		||||
    },
 | 
			
		||||
    disabled: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: false,
 | 
			
		||||
      default: false
 | 
			
		||||
    },
 | 
			
		||||
    getList: {
 | 
			
		||||
      type: Function,
 | 
			
		||||
    },
 | 
			
		||||
      type: Function
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
| 
						 | 
				
			
			@ -75,136 +84,141 @@ export default {
 | 
			
		|||
      uploadFailed: {}, // 存储上传失败的文件
 | 
			
		||||
      queue: [], // 文件上传队列
 | 
			
		||||
      maxConcurrentUploads: 5, // 最大并发上传数
 | 
			
		||||
      uploadsNum:{},
 | 
			
		||||
    };
 | 
			
		||||
      uploadsNum: {}
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  beforeMount() {
 | 
			
		||||
    this.resetComponent();
 | 
			
		||||
    this.resetComponent()
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.resetComponent(); // 在组件挂载完成后调用
 | 
			
		||||
    this.resetComponent() // 在组件挂载完成后调用
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    handleClose(done) {
 | 
			
		||||
      this.$modal
 | 
			
		||||
        .confirm('您确认要关闭吗?关闭后,未完成上传的文件将停止上传。')
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          this.queue = []; // 清空队列
 | 
			
		||||
          done();
 | 
			
		||||
          this.queue = [] // 清空队列
 | 
			
		||||
          done()
 | 
			
		||||
        })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          this.resetComponent();
 | 
			
		||||
          this.getList && this.getList(); // 重新加载文件列表
 | 
			
		||||
          this.resetComponent()
 | 
			
		||||
          this.getList && this.getList() // 重新加载文件列表
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => {
 | 
			
		||||
        })
 | 
			
		||||
        .catch(() => {});
 | 
			
		||||
    },
 | 
			
		||||
    // 重置组件状态
 | 
			
		||||
    resetComponent() {
 | 
			
		||||
      this.fileList = [];
 | 
			
		||||
      this.uploadProgress = {};
 | 
			
		||||
      this.uploadFailed = {};
 | 
			
		||||
      this.queue = [];
 | 
			
		||||
      this.uploadsNum = {};
 | 
			
		||||
      this.fileList = []
 | 
			
		||||
      this.uploadProgress = {}
 | 
			
		||||
      this.uploadFailed = {}
 | 
			
		||||
      this.queue = []
 | 
			
		||||
      this.uploadsNum = {}
 | 
			
		||||
    },
 | 
			
		||||
    // 自定义上传请求
 | 
			
		||||
    customUpload(uploadFile) {
 | 
			
		||||
      this.drawer = true;
 | 
			
		||||
      const file = uploadFile.file;
 | 
			
		||||
      this.$set(this.uploadProgress, file.name, 0); // 更新文件上传进度
 | 
			
		||||
      this.drawer = true
 | 
			
		||||
      const file = uploadFile.file
 | 
			
		||||
      this.$set(this.uploadProgress, file.name, 0) // 更新文件上传进度
 | 
			
		||||
      // 将文件添加到队列
 | 
			
		||||
      this.queue.push(file);
 | 
			
		||||
      this.startNextUpload();
 | 
			
		||||
      this.queue.push(file)
 | 
			
		||||
      this.startNextUpload()
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 处理文件夹上传
 | 
			
		||||
    handleChange(file, fileList) {
 | 
			
		||||
      // 监听文件夹上传,遍历文件夹中的文件
 | 
			
		||||
      if (file && file.webkitRelativePath) {
 | 
			
		||||
        const folderPath = file.webkitRelativePath.split('/')[0]; // 文件夹路径
 | 
			
		||||
        const filesInFolder = fileList.filter(f => f.webkitRelativePath.startsWith(folderPath));
 | 
			
		||||
        const folderPath = file.webkitRelativePath.split('/')[0] // 文件夹路径
 | 
			
		||||
        const filesInFolder = fileList.filter(f => f.webkitRelativePath.startsWith(folderPath))
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 分片上传
 | 
			
		||||
    uploadChunks(file) {
 | 
			
		||||
      if (!this.drawer) return; // 检查是否需要继续上传
 | 
			
		||||
      this.$set(this.uploadsNum, file.name, 0);
 | 
			
		||||
      const totalChunks = Math.ceil(file.size / this.chunkSize);
 | 
			
		||||
      let currentChunk = 0;
 | 
			
		||||
      if (!this.drawer) return // 检查是否需要继续上传
 | 
			
		||||
      this.$set(this.uploadsNum, file.name, 0)
 | 
			
		||||
      const totalChunks = Math.ceil(file.size / this.chunkSize)
 | 
			
		||||
      let currentChunk = 0
 | 
			
		||||
      const uploadNextChunk = () => {
 | 
			
		||||
        if (!this.drawer) 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', file.name);
 | 
			
		||||
        formData.append('chunk', currentChunk + 1);
 | 
			
		||||
        formData.append('totalChunks', totalChunks);
 | 
			
		||||
        formData.append('parentId', this.parentId);
 | 
			
		||||
        formData.append('fileUrl', this.fileUrl);
 | 
			
		||||
        if (!this.drawer) 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', file.name)
 | 
			
		||||
        formData.append('chunk', currentChunk + 1)
 | 
			
		||||
        formData.append('totalChunks', totalChunks)
 | 
			
		||||
        formData.append('parentId', this.parentId)
 | 
			
		||||
        formData.append('fileUrl', this.fileUrl)
 | 
			
		||||
 | 
			
		||||
        uploadFiles(formData)
 | 
			
		||||
          .then(() => {
 | 
			
		||||
            currentChunk++;
 | 
			
		||||
            const progress = Math.floor((currentChunk / totalChunks) * 100);
 | 
			
		||||
            this.$set(this.uploadsNum, file.name, progress);
 | 
			
		||||
            this.$set(this.uploadProgress, file.name, progress);
 | 
			
		||||
            currentChunk++
 | 
			
		||||
            const progress = Math.floor((currentChunk / totalChunks) * 100)
 | 
			
		||||
            this.$set(this.uploadsNum, file.name, progress)
 | 
			
		||||
            this.$set(this.uploadProgress, file.name, progress)
 | 
			
		||||
            if (currentChunk < totalChunks) {
 | 
			
		||||
              uploadNextChunk();
 | 
			
		||||
              uploadNextChunk()
 | 
			
		||||
            } else {
 | 
			
		||||
              this.startNextUpload();
 | 
			
		||||
              this.checkAllUploadsComplete();
 | 
			
		||||
              this.startNextUpload()
 | 
			
		||||
              this.checkAllUploadsComplete()
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          .catch(() => {
 | 
			
		||||
            this.$set(this.uploadFailed, file.name, true);
 | 
			
		||||
            this.startNextUpload();
 | 
			
		||||
          });
 | 
			
		||||
      };
 | 
			
		||||
            this.$set(this.uploadFailed, file.name, true)
 | 
			
		||||
            this.startNextUpload()
 | 
			
		||||
          })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      uploadNextChunk();
 | 
			
		||||
      uploadNextChunk()
 | 
			
		||||
    },
 | 
			
		||||
    checkAllUploadsComplete() {
 | 
			
		||||
      const allComplete = Object.values(this.uploadProgress).every(progress => progress === 100);
 | 
			
		||||
      const allComplete = Object.values(this.uploadProgress).every(progress => progress === 100)
 | 
			
		||||
      if (allComplete) {
 | 
			
		||||
        this.$message.success("所有文件上传完成!");
 | 
			
		||||
        this.drawer = false; // 自动关闭弹窗
 | 
			
		||||
        this.getList && this.getList(); // 重新加载列表
 | 
			
		||||
        this.resetComponent();
 | 
			
		||||
        this.$message.success('所有文件上传完成!')
 | 
			
		||||
        this.drawer = false // 自动关闭弹窗
 | 
			
		||||
        this.getList && this.getList() // 重新加载列表
 | 
			
		||||
        this.resetComponent()
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    // 启动队列中的下一个文件上传(控制并发)
 | 
			
		||||
    startNextUpload() {
 | 
			
		||||
      if (!this.drawer) return; // 如果停止上传,则不启动新任务
 | 
			
		||||
      if (!this.drawer) return // 如果停止上传,则不启动新任务
 | 
			
		||||
 | 
			
		||||
      Object.keys(this.uploadsNum).forEach((fileName) => {
 | 
			
		||||
        if (this.uploadsNum[fileName] === 100) {
 | 
			
		||||
          delete this.uploadsNum[fileName];
 | 
			
		||||
          delete this.uploadsNum[fileName]
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (this.queue.length === 0 || Object.values(this.uploadsNum).length >= this.maxConcurrentUploads) {
 | 
			
		||||
        return;
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      const nextFile = this.queue.shift();
 | 
			
		||||
      this.uploadChunks(nextFile);
 | 
			
		||||
      const nextFile = this.queue.shift()
 | 
			
		||||
      this.uploadChunks(nextFile)
 | 
			
		||||
    },
 | 
			
		||||
    beforeUpload(file) {
 | 
			
		||||
      const maxSize = 2 * 1024 * 1024*1024; // 限制文件最大为 5MB
 | 
			
		||||
      const maxSize = 2 * 1024 * 1024 * 1024 // 限制文件最大为 5MB
 | 
			
		||||
      if (file.size > maxSize) {
 | 
			
		||||
        this.$message.error('文件大小不能超过 2GB');
 | 
			
		||||
        return false; // 阻止上传
 | 
			
		||||
        this.$message.error('文件大小不能超过 2GB')
 | 
			
		||||
        return false // 阻止上传
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true; // 允许上传
 | 
			
		||||
      console.log(file)
 | 
			
		||||
      if (file.size <= 0) {
 | 
			
		||||
        this.$message.error('文件大小不正确')
 | 
			
		||||
        return false // 阻止上传
 | 
			
		||||
      }
 | 
			
		||||
      return true // 允许上传
 | 
			
		||||
    },
 | 
			
		||||
    // 处理超出文件数限制的情况
 | 
			
		||||
    handleExceed(files, fileList) {
 | 
			
		||||
      this.$message.warning(`最多只能选择50个文件,当前选择了 ${files.length + fileList.length} 个文件`);
 | 
			
		||||
      this.$message.warning(`最多只能选择50个文件,当前选择了 ${files.length + fileList.length} 个文件`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,39 +1,67 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
 | 
			
		||||
    <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" placeholder="请输入镜像名称"/>
 | 
			
		||||
          <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-input v-model="form.ownModel" placeholder="请输入所属模型"/>
 | 
			
		||||
          <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-input v-model="form.runEnvironment" type="textarea" placeholder="请输入内容"/>
 | 
			
		||||
          <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-input v-model="form.modelFrame" placeholder="请输入模型框架"/>
 | 
			
		||||
          <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-input v-model="form.language" placeholder="请输入语言"/>
 | 
			
		||||
          <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="镜像文件path" prop="mirrorPath">
 | 
			
		||||
          <el-input v-model="form.mirrorPath" placeholder="请输入镜像文件path"/>
 | 
			
		||||
        <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="remark">
 | 
			
		||||
          <el-input v-model="form.remark" placeholder="请输入备注"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="是否删除" prop="isActive">
 | 
			
		||||
          <el-input v-model="form.isActive" placeholder="请输入是否删除"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="数据集id" prop="datasetId">
 | 
			
		||||
          <el-input v-model="form.datasetId" placeholder="请输入数据集id"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="模型文件名" prop="modelFileName">
 | 
			
		||||
          <el-input v-model="form.modelFileName" placeholder="请输入模型文件名"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="使用手册文件名" prop="manualFileName">
 | 
			
		||||
          <el-input v-model="form.manualFileName" placeholder="请输入使用手册文件名"/>
 | 
			
		||||
        <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">
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +73,7 @@
 | 
			
		|||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { getManager, addManager, updateManager, uploadFile } from '@/api/dataCenter/model'
 | 
			
		||||
import { getManager, addManager, updateManager, uploadFile, listAll } from '@/api/dataCenter/mirror'
 | 
			
		||||
import { datasetList } from '@/api/dataCenter/dataSet'
 | 
			
		||||
import { generateUUID } from '../../../../utils/configure'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,22 +97,47 @@ export default {
 | 
			
		|||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      datasetList: [],
 | 
			
		||||
      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: {
 | 
			
		||||
        modelName: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
 | 
			
		||||
        modelVersion: [{
 | 
			
		||||
        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' }],
 | 
			
		||||
        modelType: [{ required: true, message: '请选择模型类型', 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' }]
 | 
			
		||||
        modelFileName: [{ required: true, message: '请上传镜像文件', trigger: 'blur' }],
 | 
			
		||||
        manualFileName: [{ required: true, message: '请上传接口文档', trigger: 'blur' }]
 | 
			
		||||
      },
 | 
			
		||||
      currentUpload: { modelFile: null, manualFile: null } // 为每个文件设置独立的上传任务
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -92,13 +145,14 @@ export default {
 | 
			
		|||
  watch: {
 | 
			
		||||
    isOpen(newVal) {
 | 
			
		||||
      if (newVal) {
 | 
			
		||||
        datasetList({}).then(response => {
 | 
			
		||||
          this.datasetList = response.data
 | 
			
		||||
        })
 | 
			
		||||
        console.log(this.mirrorId)
 | 
			
		||||
        if (this.mirrorId) {
 | 
			
		||||
          getManager(this.mirrorId).then(response => {
 | 
			
		||||
            this.form = response.data
 | 
			
		||||
          })
 | 
			
		||||
          listAll().then(response => {
 | 
			
		||||
            this.modelList = response.data
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -153,10 +207,10 @@ export default {
 | 
			
		|||
                uploadNextChunk()
 | 
			
		||||
              } else {
 | 
			
		||||
                if (field === 'modelFile') {
 | 
			
		||||
                  this.form.modelPath = res.data.fileUrl
 | 
			
		||||
                  this.form.mirrorPath = res.data.fileUrl
 | 
			
		||||
                }
 | 
			
		||||
                if (field === 'manualFile') {
 | 
			
		||||
                  this.form.modelManual = res.data.fileUrl
 | 
			
		||||
                  this.form.documentPath = res.data.fileUrl
 | 
			
		||||
                }
 | 
			
		||||
                resolve() // 上传完成,返回成功
 | 
			
		||||
              }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,17 +56,56 @@
 | 
			
		|||
 | 
			
		||||
    <el-table v-loading="loading" :data="managerList" @selection-change="handleSelectionChange">
 | 
			
		||||
      <el-table-column type="selection" width="55" align="center"/>
 | 
			
		||||
      <el-table-column type="index" label="序号" align="center" :index="indexMethod" width="50"/>
 | 
			
		||||
      <el-table-column label="镜像名称" align="center" prop="mirrorName"/>
 | 
			
		||||
      <el-table-column label="所属模型" align="center" prop="ownModel"/>
 | 
			
		||||
      <el-table-column label="运行环境" align="center" prop="runEnvironment"/>
 | 
			
		||||
      <el-table-column label="模型框架" align="center" prop="modelFrame"/>
 | 
			
		||||
      <el-table-column label="语言" align="center" prop="language"/>
 | 
			
		||||
      <el-table-column label="镜像文件path" align="center" prop="mirrorPath"/>
 | 
			
		||||
      <el-table-column label="备注" align="center" prop="remark"/>
 | 
			
		||||
      <el-table-column label="是否删除" align="center" prop="isActive"/>
 | 
			
		||||
      <el-table-column label="数据集id" align="center" prop="datasetId"/>
 | 
			
		||||
      <el-table-column label="模型文件名" align="center" prop="modelFileName"/>
 | 
			
		||||
      <el-table-column label="使用手册文件名" align="center" prop="manualFileName"/>
 | 
			
		||||
      <el-table-column label="镜像版本" align="center" prop="mirrorVersion"/>
 | 
			
		||||
      <el-table-column label="所属模型" align="center" prop="datasetId"/>
 | 
			
		||||
      <el-table-column label="运行环境" align="center" prop="runEnvironment">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <span>{{
 | 
			
		||||
              (runEnvironmentList.find(item => item.value == scope.row.runEnvironment) || {}).label || 'Unknown'
 | 
			
		||||
            }}</span>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column label="架构类型" align="center" prop="modelFrame">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <dict-tag :options="dict.type.ai_frame_type" :value="scope.row.modelFrame"/>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column label="语言" align="center" prop="language">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <span>{{ (languageList.find(item => item.value == scope.row.language) || {}).label || 'Unknown' }}</span>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column label="镜像文件" align="center" prop="mirrorPath">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
            size="mini"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @click="handleDownload(scope.row.mirrorPath,scope.row.modelFileName)"
 | 
			
		||||
            v-hasPermi="['model:manager:edit']"
 | 
			
		||||
          >下载
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column label="接口文档" align="center" prop="modelFrame">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
            size="mini"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @click="handleDownload(scope.row.documentPath,scope.row.manualFileName)"
 | 
			
		||||
            v-hasPermi="['model:manager:edit']"
 | 
			
		||||
          >下载
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button
 | 
			
		||||
            size="mini"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @click="handleSee(scope.row.documentPath)"
 | 
			
		||||
            v-hasPermi="['model:manager:remove']"
 | 
			
		||||
          >查看
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </template>
 | 
			
		||||
      </el-table-column>
 | 
			
		||||
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
| 
						 | 
				
			
			@ -98,19 +137,46 @@
 | 
			
		|||
    />
 | 
			
		||||
 | 
			
		||||
    <!-- 添加或修改镜像管理对话框 -->
 | 
			
		||||
    <custom-dialog :title="title" :get-list="getList" :open="open" :model-id="mirrorId" @dialog-cancel="handleCancel"/>
 | 
			
		||||
    <custom-dialog :title="title" :get-list="getList" :open="open" :mirror-id="mirrorId" @dialog-cancel="handleCancel"/>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { listManager, delManager } from '@/api/dataCenter/mirror'
 | 
			
		||||
import customDialog from '../model/child/customDialog.vue'
 | 
			
		||||
import customDialog from '../mirror/child/customDialog.vue'
 | 
			
		||||
import { Base64 } from 'js-base64'
 | 
			
		||||
import { encryptCBC } from '../../../utils/aescbc'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'Mirror',
 | 
			
		||||
  dicts: ['ai_annotate_type', 'ai_frame_type'],
 | 
			
		||||
  components: { customDialog },
 | 
			
		||||
  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' }
 | 
			
		||||
      ],
 | 
			
		||||
      mirrorId: null,
 | 
			
		||||
      // 遮罩层
 | 
			
		||||
      loading: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +208,9 @@ export default {
 | 
			
		|||
    this.getList()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    indexMethod(index) {
 | 
			
		||||
      return (this.queryParams.pageNum - 1) * this.queryParams.pageSize + index + 1
 | 
			
		||||
    },
 | 
			
		||||
    /** 查询镜像管理列表 */
 | 
			
		||||
    getList() {
 | 
			
		||||
      this.loading = true
 | 
			
		||||
| 
						 | 
				
			
			@ -176,7 +245,7 @@ export default {
 | 
			
		|||
    /** 修改按钮操作 */
 | 
			
		||||
    handleUpdate(row) {
 | 
			
		||||
      this.mirrorId = row.id || this.ids[0]
 | 
			
		||||
      this.title = '添加'
 | 
			
		||||
      this.title = '修改'
 | 
			
		||||
      this.open = true
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -190,6 +259,51 @@ export default {
 | 
			
		|||
        this.$modal.msgSuccess('删除成功')
 | 
			
		||||
      }).catch(() => {
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    handleCancel() {
 | 
			
		||||
      this.open = false
 | 
			
		||||
    },
 | 
			
		||||
    handleDownload(url, fileName) {
 | 
			
		||||
      // 创建一个 XMLHttpRequest 对象
 | 
			
		||||
      const xhr = new XMLHttpRequest()
 | 
			
		||||
      // 设置响应类型为 blob,这样能处理二进制数据等各种类型的文件内容
 | 
			
		||||
      xhr.responseType = 'blob'
 | 
			
		||||
      // 监听请求的加载完成事件
 | 
			
		||||
      xhr.onload = function() {
 | 
			
		||||
        if (this.status === 200) {
 | 
			
		||||
          // 获取响应内容(以 blob 形式)
 | 
			
		||||
          const blob = this.response
 | 
			
		||||
          // 创建一个用于下载的链接元素
 | 
			
		||||
          const downloadLink = document.createElement('a')
 | 
			
		||||
          // 为链接元素生成一个临时的 URL,指向 blob 数据
 | 
			
		||||
          downloadLink.href = URL.createObjectURL(blob)
 | 
			
		||||
          // 设置下载链接的下载属性(指定文件名)
 | 
			
		||||
          downloadLink.download = fileName
 | 
			
		||||
          // 模拟点击链接来触发下载
 | 
			
		||||
          downloadLink.click()
 | 
			
		||||
          // 释放创建的临时 URL 资源
 | 
			
		||||
          URL.revokeObjectURL(downloadLink.href)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // 监听请求的错误事件
 | 
			
		||||
      xhr.onerror = function() {
 | 
			
		||||
        console.error('下载请求出现错误')
 | 
			
		||||
      }
 | 
			
		||||
      // 打开请求,使用 GET 方法去获取资源
 | 
			
		||||
      xhr.open('GET', `${JSON.parse(localStorage.getItem('minIoUrl')).minioUrl}${url}`, true)
 | 
			
		||||
      // 发送请求
 | 
			
		||||
      xhr.send()
 | 
			
		||||
    },
 | 
			
		||||
    handleSee(url) {
 | 
			
		||||
      let b64Encoded = Base64.encode(`${JSON.parse(localStorage.getItem('minIoUrl')).minioUrl}${url}`)
 | 
			
		||||
      window.open(`http://192.168.0.14:8012/onlinePreview?url=${b64Encoded}&token=${this.generateToken()}`, '_blank')
 | 
			
		||||
    },
 | 
			
		||||
    generateToken() {
 | 
			
		||||
      // 获取当前时间戳(以秒为单位)
 | 
			
		||||
      const currentTimeStamp = Math.floor(Date.now())
 | 
			
		||||
      // 将时间戳转换为字符串
 | 
			
		||||
      const timeStampString = currentTimeStamp.toString()
 | 
			
		||||
      return encryptCBC(timeStampString)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue