|
|
@@ -0,0 +1,544 @@
|
|
|
+<template>
|
|
|
+ <div class="upload-file">
|
|
|
+ <div class="file-area">
|
|
|
+ <span class="label-text">{{ labelText }}</span>
|
|
|
+ <div class="upload-box">
|
|
|
+ <el-upload
|
|
|
+ ref="upload"
|
|
|
+ class="file-uploader"
|
|
|
+ action="no"
|
|
|
+ :accept="acceptFileType"
|
|
|
+ :multiple="limit === null || limit > 1"
|
|
|
+ :show-file-list="false"
|
|
|
+ :auto-upload="false"
|
|
|
+ :file-list="fileList"
|
|
|
+ :on-change="onFileChange"
|
|
|
+ :on-exceed="handleExceed"
|
|
|
+ :limit="limit"
|
|
|
+ >
|
|
|
+ <el-button>{{ type === 'h5_games' ? '选择Zip压缩包或单个html文件' : '选取' + labelText + '文件' }}</el-button>
|
|
|
+ </el-upload>
|
|
|
+ <el-button size="small" type="primary" @click="uploadFiles"> 上传 </el-button>
|
|
|
+ <el-button size="small" type="primary" @click="useResource"> 使用资源 </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-divider v-if="showDivider" />
|
|
|
+ <div class="upload-tip">{{ uploadTip }}</div>
|
|
|
+ <ul v-if="fileList.length > 0" slot="file-list" class="file-list">
|
|
|
+ <li v-for="(file, i) in fileList" :key="i">
|
|
|
+ <div class="file-name">
|
|
|
+ <span>
|
|
|
+ <SvgIcon v-if="iconClass" :icon-class="iconClass" size="12" />
|
|
|
+ <!-- 编辑序号和名称 -->
|
|
|
+ <template v-if="content.file_info[file.file_id] && content.file_info[file.file_id].isEdit">
|
|
|
+ <el-input v-model="content.file_info[file.file_id].xuhao" placeholder="序号" style="width: 80px" />
|
|
|
+ <el-input v-model="content.file_info[file.file_id].file_name" placeholder="名称" />
|
|
|
+ </template>
|
|
|
+ <!-- 可以编辑序号名称状态下显示序号 -->
|
|
|
+ <span v-else>{{
|
|
|
+ canEditName && file.file_id
|
|
|
+ ? content.file_info[file.file_id].xuhao + content.file_info[file.file_id].file_name
|
|
|
+ : (file.file_name ?? file.name)
|
|
|
+ }}</span>
|
|
|
+ <!-- <span>({{ file.size }})</span> -->
|
|
|
+ </span>
|
|
|
+ <el-progress
|
|
|
+ v-if="file.progress > 0 && file.progress < 100"
|
|
|
+ type="circle"
|
|
|
+ :percentage="file.progress"
|
|
|
+ :width="20"
|
|
|
+ color="#2A5AF6"
|
|
|
+ stroke-linecap="butt"
|
|
|
+ :show-text="false"
|
|
|
+ />
|
|
|
+ <span v-else-if="file.file_id"> 完成</span>
|
|
|
+ </div>
|
|
|
+ <SvgIcon icon-class="delete-black" size="12" @click="removeFile(file, i)" />
|
|
|
+ <SvgIcon
|
|
|
+ v-show="type === 'picture' && file.file_id"
|
|
|
+ icon-class="mark"
|
|
|
+ size="12"
|
|
|
+ @click="viewDialog(file.file_id)"
|
|
|
+ />
|
|
|
+ <!-- 编辑名称和序号 -->
|
|
|
+ <template v-if="canEditName && file.file_id">
|
|
|
+ <SvgIcon
|
|
|
+ v-if="content.file_info[file.file_id].isEdit"
|
|
|
+ icon-class="icon-save"
|
|
|
+ size="12"
|
|
|
+ @click="changeIsEdit(content.file_info[file.file_id])"
|
|
|
+ />
|
|
|
+ <SvgIcon v-else icon-class="icon-edit" size="12" @click="changeIsEdit(content.file_info[file.file_id])" />
|
|
|
+ </template>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+
|
|
|
+ <SelectResource
|
|
|
+ :visible.sync="visibleResource"
|
|
|
+ :project-id="projectId"
|
|
|
+ :accept="accept"
|
|
|
+ :courseware-id="coursewareId"
|
|
|
+ @selectResource="selectResource"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { fileUpload } from '@/api/app';
|
|
|
+import { conversionSize } from '@/utils/common';
|
|
|
+import { isEnable } from '@/views/book/courseware/data/common';
|
|
|
+import { getConfig, getToken } from '@/utils/auth';
|
|
|
+
|
|
|
+import SelectResource from '@/views/book/courseware/create/components/base/common/SelectResource.vue';
|
|
|
+import { SubmitFileToResourceStore } from '@/api/book';
|
|
|
+export default {
|
|
|
+ name: 'UploadFile',
|
|
|
+ components: {
|
|
|
+ SelectResource,
|
|
|
+ },
|
|
|
+ props: {
|
|
|
+ // 项目id
|
|
|
+ projectId: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ // 课件id
|
|
|
+ coursewareId: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ // 组件id
|
|
|
+ componentId: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ // 组件标签
|
|
|
+ labelText: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ // 上传支持的文件格式
|
|
|
+ acceptFileType: {
|
|
|
+ type: String,
|
|
|
+ default: '*',
|
|
|
+ },
|
|
|
+ // 提示语
|
|
|
+ uploadTip: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ // 图标
|
|
|
+ iconClass: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ fileList: {
|
|
|
+ type: Array,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ fileIdList: {
|
|
|
+ type: Array,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ fileInfoList: {
|
|
|
+ type: Array,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ type: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ singleSize: {
|
|
|
+ type: Number,
|
|
|
+ default: 100,
|
|
|
+ },
|
|
|
+ totalSize: {
|
|
|
+ type: Number,
|
|
|
+ default: 1024,
|
|
|
+ },
|
|
|
+ limit: {
|
|
|
+ type: Number,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ canEditName: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ fileInfo: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+ index: {
|
|
|
+ // 如果是二维数组里循环上传 一维索引
|
|
|
+ type: Number,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ indexs: {
|
|
|
+ // 如果是二维数组里循环上传 二维索引
|
|
|
+ type: Number,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ // 是否显示分割线
|
|
|
+ showDivider: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ curFile: null,
|
|
|
+ conversionSize,
|
|
|
+ visible: false,
|
|
|
+ content: {
|
|
|
+ file_list: this.fileList,
|
|
|
+ file_id_list: this.fileIdList,
|
|
|
+ file_info_list: this.fileInfoList,
|
|
|
+ file_info: this.fileInfo,
|
|
|
+ },
|
|
|
+ visibleResource: false,
|
|
|
+ isEnable,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ accept() {
|
|
|
+ let accept = '*';
|
|
|
+ if (this.acceptFileType.includes('.mp3')) {
|
|
|
+ accept = 'audio';
|
|
|
+ } else if (this.acceptFileType.includes('.mp4')) {
|
|
|
+ accept = 'video';
|
|
|
+ } else if (this.acceptFileType.includes('.jpg')) {
|
|
|
+ accept = 'image';
|
|
|
+ } else if (this.acceptFileType.includes('.zip')) {
|
|
|
+ accept = 'h5_game';
|
|
|
+ } else if (this.acceptFileType.includes('.txt')) {
|
|
|
+ accept = 'text';
|
|
|
+ }
|
|
|
+ return accept;
|
|
|
+ },
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ content: {
|
|
|
+ handler(val) {
|
|
|
+ this.$emit('updateFileList', val, this.index, this.indexs);
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 显示自定义样式文件列表
|
|
|
+ onFileChange(file, fileList) {
|
|
|
+ this.afterSelectFile(file);
|
|
|
+ fileList.forEach((file) => {
|
|
|
+ if (!file.progress || file.progress <= 0) file.progress = 0;
|
|
|
+ });
|
|
|
+ const lists = this.$refs.upload.uploadFiles;
|
|
|
+ if (lists.length === 0) return;
|
|
|
+ const files = lists.filter((item) => {
|
|
|
+ let find = this.content.file_list.findIndex((p) => p.uid === item.uid);
|
|
|
+ return find === -1;
|
|
|
+ });
|
|
|
+ if (this.limit !== null && this.content.file_list.length + files.length > this.limit) {
|
|
|
+ this.$message.warning(
|
|
|
+ `当前限制选择 ${this.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
|
|
|
+ files.length + this.content.file_list.length
|
|
|
+ } 个文件`,
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.content.file_list = [...this.content.file_list, ...files];
|
|
|
+ },
|
|
|
+
|
|
|
+ handleExceed(files, fileList) {
|
|
|
+ this.$message.warning(
|
|
|
+ `当前限制选择 ${this.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
|
|
|
+ files.length + fileList.length
|
|
|
+ } 个文件`,
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除文件
|
|
|
+ removeFile(file, i) {
|
|
|
+ this.$confirm('是否删除当前文件?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ this.$refs.upload.handleRemove(file);
|
|
|
+ this.content.file_list.splice(i, 1);
|
|
|
+ this.content.file_id_list.splice(i, 1);
|
|
|
+ })
|
|
|
+ .catch(() => {});
|
|
|
+ },
|
|
|
+
|
|
|
+ // 文件校验
|
|
|
+ afterSelectFile(file) {
|
|
|
+ const fileName = file.name;
|
|
|
+ let singleSizeTip = `文件[${fileName}]大小超过${conversionSize(this.singleSize * 1024 * 1024)},被移除!`;
|
|
|
+ if (file.size > this.singleSize * 1024 * 1024) {
|
|
|
+ this.$message.error(singleSizeTip);
|
|
|
+ this.$refs.upload.handleRemove(file);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const suffix = fileName.slice(fileName.lastIndexOf('.') + 1, fileName.length).toLowerCase();
|
|
|
+ let fileType = [];
|
|
|
+ let typeTip = '';
|
|
|
+
|
|
|
+ if (this.type === 'audio') {
|
|
|
+ fileType = ['mp3', 'acc', 'wma', 'wav'];
|
|
|
+ typeTip = '音频文件只能是 mp3、acc、wma、wav格式!';
|
|
|
+ } else if (
|
|
|
+ this.type === 'picture' ||
|
|
|
+ this.type === 'image_text' ||
|
|
|
+ this.type === 'drawing' ||
|
|
|
+ this.type === 'character_structure' ||
|
|
|
+ this.type === 'newWord_template' ||
|
|
|
+ this.type === 'character'
|
|
|
+ ) {
|
|
|
+ fileType = ['jpg', 'png', 'jpeg'];
|
|
|
+ typeTip = '图片文件只能是 jpg、png、jpeg 格式!';
|
|
|
+ } else if (this.type === 'video' || this.type === 'video_interaction') {
|
|
|
+ fileType = ['mp4'];
|
|
|
+ typeTip = '视频文件只能是 mp4 格式!';
|
|
|
+ } else if (this.type === 'upload_preview' || this.type === 'video_interaction_file') {
|
|
|
+ fileType = [
|
|
|
+ 'png',
|
|
|
+ 'jpg',
|
|
|
+ 'jpeg',
|
|
|
+ 'txt',
|
|
|
+ 'pdf',
|
|
|
+ 'doc',
|
|
|
+ 'docx',
|
|
|
+ 'xls',
|
|
|
+ 'xlsx',
|
|
|
+ 'ppt',
|
|
|
+ 'pptx',
|
|
|
+ 'mp3',
|
|
|
+ 'wma',
|
|
|
+ 'mp4',
|
|
|
+ 'mov',
|
|
|
+ 'zip',
|
|
|
+ 'rar',
|
|
|
+ ];
|
|
|
+ typeTip = '文件不支持';
|
|
|
+ } else if (this.type === 'h5_games') {
|
|
|
+ fileType = ['zip', 'html'];
|
|
|
+ typeTip = 'H5游戏文件只能是 zip、html 格式!';
|
|
|
+ } else if (this.type === '3DModel') {
|
|
|
+ fileType = ['fbx', 'zip', 'gltf', 'glb'];
|
|
|
+ typeTip = '3D模型文件只能是 fbx、gltf、glb、zip 格式!';
|
|
|
+ }
|
|
|
+ const isNeedType = fileType.includes(suffix);
|
|
|
+ if (!isNeedType) {
|
|
|
+ typeTip += `,[${fileName}]被移除!`;
|
|
|
+ this.$message.error(typeTip);
|
|
|
+ this.$refs.upload.handleRemove(file);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传文件
|
|
|
+ uploadFiles() {
|
|
|
+ const files = (this.content.file_list || []).filter((file) => file.uid && file.status !== 'success');
|
|
|
+ if (files.length <= 0) {
|
|
|
+ this.$message.error('没有需要上传的文件!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const totalSize = files.reduce((sum, cur) => sum + Number(cur.size || 0), 0);
|
|
|
+ if (totalSize > this.totalSize * 1024 * 1024) {
|
|
|
+ this.$message.error(`文件总大小不能超过${conversionSize(this.totalSize * 1024 * 1024)}!`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ files
|
|
|
+ .filter((p) => {
|
|
|
+ let pro = p.progress || -1;
|
|
|
+ return pro <= 0;
|
|
|
+ })
|
|
|
+ .forEach((file) => {
|
|
|
+ // 添加验证
|
|
|
+ if (!file.raw || !(file.raw instanceof Blob)) {
|
|
|
+ this.$message.error(`文件 ${file.name} 无效,请删除后重新选择!`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let form = new FormData();
|
|
|
+ form.append(file.name, file.raw, file.name);
|
|
|
+ fileUpload('Mid', form, {
|
|
|
+ handleUploadProgress: (progressEvent) => {
|
|
|
+ // 进度到99等服务器返回文件信息后才算实际完成
|
|
|
+ let per = Number((progressEvent.progress * 99).toFixed(2) || 0);
|
|
|
+ let en = this.content.file_list.find((p) => p.uid === file.uid);
|
|
|
+ if (en) {
|
|
|
+ en.progress = per;
|
|
|
+ this.$forceUpdate();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }).then(({ file_info_list }) => {
|
|
|
+ let file_index = this.content.file_list.findIndex((p) => p.uid === file.uid);
|
|
|
+ if (file_index > -1) {
|
|
|
+ if (this.type === 'picture') {
|
|
|
+ this.content.file_info_list[file_index] = {
|
|
|
+ file_id: file_info_list[0].file_id,
|
|
|
+ file_name: file_info_list[0].file_name,
|
|
|
+ title: '',
|
|
|
+ intro: '',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ this.content.file_list[file_index] = {
|
|
|
+ file_id: file_info_list[0].file_id,
|
|
|
+ file_name: file_info_list[0].file_name,
|
|
|
+ file_url: file_info_list[0].file_url,
|
|
|
+ };
|
|
|
+ if (this.canEditName) {
|
|
|
+ let obj = {
|
|
|
+ xuhao: '',
|
|
|
+ isEdit: false,
|
|
|
+ file_name: file_info_list[0].file_name,
|
|
|
+ };
|
|
|
+ this.$set(this.content.file_info, file_info_list[0].file_id, obj);
|
|
|
+ }
|
|
|
+ this.content.file_id_list.push(file_info_list[0].file_id);
|
|
|
+ this.$refs.upload.uploadFiles = [];
|
|
|
+ this.$forceUpdate();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示弹窗
|
|
|
+ viewDialog(file_id) {
|
|
|
+ if (file_id) this.visible = true;
|
|
|
+ this.curFile = this.content.file_info_list.find((file) => file.file_id === file_id);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 给文件加介绍
|
|
|
+ fillDescribeToFile(file) {
|
|
|
+ let en = this.content.file_info_list.find((p) => p.file_id === file.file_id);
|
|
|
+ if (en) {
|
|
|
+ Object.assign(en, file);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 使用资源
|
|
|
+ useResource() {
|
|
|
+ this.visibleResource = true;
|
|
|
+ },
|
|
|
+ selectResource({ file_id, file_name, file_url, intro }) {
|
|
|
+ this.content.file_list.push({ file_id, file_name, file_url });
|
|
|
+ this.content.file_id_list.push(file_id);
|
|
|
+ this.content.file_info_list.push({ file_id, file_name, title: '', intro });
|
|
|
+ if (this.canEditName) {
|
|
|
+ let obj = {
|
|
|
+ xuhao: '',
|
|
|
+ isEdit: false,
|
|
|
+ file_name,
|
|
|
+ };
|
|
|
+ this.$set(this.content.file_info, file_id, obj);
|
|
|
+ }
|
|
|
+ this.visibleResource = false;
|
|
|
+ },
|
|
|
+ // 编辑文件名及序号
|
|
|
+ changeIsEdit(file) {
|
|
|
+ file.isEdit = !file.isEdit;
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.module-content {
|
|
|
+ .file-area {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 16px;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .label-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: $font-light-color;
|
|
|
+ }
|
|
|
+
|
|
|
+ div {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-divider {
|
|
|
+ margin: 16px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-tip {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #86909c;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-box {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .el-button + .el-button {
|
|
|
+ margin-left: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-uploader {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ :deep .el-upload {
|
|
|
+ &--text {
|
|
|
+ width: 100%;
|
|
|
+ background-color: $fill-color;
|
|
|
+ border-radius: 2px 0 0 2px;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ width: 100%;
|
|
|
+ color: #86909c;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ border-radius: 0 2px 2px 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .old_file_list {
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 16px;
|
|
|
+
|
|
|
+ li {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 12px;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .file-name {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 14px;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ max-width: 500px; // 360px有点窄
|
|
|
+ padding: 8px 12px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #1d2129;
|
|
|
+ background-color: #f7f8fa;
|
|
|
+
|
|
|
+ span {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 14px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .svg-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|