Bläddra i källkod

阶段性提交

于添 3 månader sedan
förälder
incheckning
ca7d0823fb

+ 1 - 1
src/App.vue

@@ -25,7 +25,7 @@
 	<!--		1-->
 	<!--		<router-view />-->
 	<!--	</a-config-provider>-->
-	<MiniYun></MiniYun>
+
 </template>
 
 <script setup name="App">

+ 6 - 0
src/api/exam/question/tQuestionApi.js

@@ -26,5 +26,11 @@ export default {
 				'Content-Type': 'multipart/form-data'
 			}
 		})
+	},
+	 downTemplate : () => {
+		return request('/api/admin/question/downloadImportQuesTemplate',null,'get',{
+			responseType: 'blob',
+		 }
+		)
 	}
 }

+ 63 - 3
src/components/MiniYun/index.vue

@@ -1,7 +1,13 @@
 <template>
-	<div style="width: 100px;  height: 50px; background-color: red; position: fixed;   left: 100; top: 100; "  @click="onOpenMiniForm"></div>
-	<ResourceUpload ref="resourceUploadRef"></ResourceUpload>
-	<MiniForm ref="miniFormRef"></MiniForm>
+	<div style="width: 100px;  height: 100%; display: flex; justify-content: center;  align-items: center;  cursor: pointer ; " >
+		<div class="shake-animation"  v-if="fileForms && fileForms.length > 0" style="padding: 5px" @click="onOpenMiniForm">
+			<span  style="display: block"  >上传任务管理</span>
+		</div>
+
+		<ResourceUpload ref="resourceUploadRef"></ResourceUpload>
+		<MiniForm ref="miniFormRef"></MiniForm>
+	</div>
+
 </template>
 <script setup>
 import ResourceUpload from './resourceUpload.vue'
@@ -10,7 +16,19 @@ import EventBus from "@/utils/EventBus";
 
 const resourceUploadRef = ref(null)
 const miniFormRef = ref(null)
+import { miniyunStore } from '@/store/miniyun'
+import {storeToRefs} from "pinia";
+const myMiniyunStore = miniyunStore()
+// 使用 storeToRefs 保持响应性
+const { getFileForms ,getPauseFlags} = storeToRefs(myMiniyunStore)
 
+// getFileModel 保持了与 store 的响应式连接
+const fileForms = computed({
+	get: () => getFileForms.value
+})
+const pauseFlags = computed({
+	get: () => getPauseFlags.value
+})
 
 const onOpenList = () => {
 	// console.log('打开上传资源')
@@ -32,5 +50,47 @@ EventBus.on('openMiniYun',openMiniYun)
 
 
 <style scoped>
+.shake-animation {
+	animation: scaleAndShake 3s infinite;
+}
 
+.shake-animation span {
+	animation: textColorChange 3s infinite;
+}
+
+@keyframes scaleAndShake {
+	0% {
+		transform: scale(1);
+	}
+	30% {
+		transform: scale(0.8);
+	}
+	40%, 50%, 60%, 70%, 80% {
+		transform: scale(0.8) translateX(3px);
+	}
+	45%, 55%, 65%, 75% {
+		transform: scale(0.8) translateX(-3px);
+	}
+	85% {
+		transform: scale(0.8);
+	}
+	100% {
+		transform: scale(1);
+	}
+}
+
+@keyframes textColorChange {
+	0% {
+		color: #333;
+	}
+	30% {
+		color: #409eff;
+	}
+	85% {
+		color: #409eff;
+	}
+	100% {
+		color: #333;
+	}
+}
 </style>

+ 90 - 2
src/components/MiniYun/miniForm.vue

@@ -2,8 +2,9 @@
 	<a-modal
 		v-model:visible="uploadModalVisible"
 		width="600px"
+		:footer="null"
 	>
-		<div v-for="itemi in fileForms" :key="itemi.key" >
+		<div v-for="(itemi, indexi) in fileForms" :key="itemi.key" >
 			<a-form :model="itemi" :rules="rules" ref="formRef">
 				<div>
 					<span style="color: blue">关键词 :</span>
@@ -52,6 +53,53 @@
 					<span style="color: red" v-if="itemi.isRecommend == '0'">否</span>
 					<span style="color: red" v-if="itemi.isRecommend == '1'">是</span>
 				</div>
+
+
+				<div v-for="(itemii, indexii) in itemi.uploadFileList" :key="indexii">
+					<div style="padding: 2px">
+						<div style="display: flex; width: 100%; align-items: center; justify-content: space-between">
+							<div>
+								<span>{{ itemii.name.length > 20 ? itemii.name.slice(0, 20) + '...' + itemii.fileSuffix : itemii.name }}</span>
+							</div>
+							<div>
+								<span v-if="itemii.time != ''" style="display: block; color: blue">{{ itemii.time }}</span>
+							</div>
+							<div>
+								<div>
+									<!--							<span-->
+									<!--								v-if="item.percents == 0"-->
+									<!--								style="color: red; cursor: pointer; margin-left: 10px"-->
+									<!--							>读取中</span-->
+									<!--							>-->
+									<span
+										v-if="itemii.percents == 0"
+										style="color: red; cursor: pointer; margin-left: 10px"
+										@click="handlerRemoveItem(indexi,indexii)"
+									>删除</span
+									>
+																<span
+																	v-if="
+																		itemii.percents >= 0 &&
+																		itemii.percents < 100 &&
+																		(pauseFlags[itemii.md5] == false || pauseFlags[itemii.md5] == undefined)
+																	"
+																	style="color: blue; cursor: pointer; margin-left: 10px"
+																	@click="pauseUpload(itemii.md5)"
+																	>暂停</span>
+																<span
+																	v-if="itemii.percents >= 0 && itemii.percents < 100 && pauseFlags[itemii.md5] == true"
+																	style="color: green; cursor: pointer; margin-left: 10px"
+																	@click="resumeUpload(itemii.md5)"
+																	>恢复</span
+																>
+								</div>
+							</div>
+						</div>
+						<a-progress :percent="itemii.percents" />
+					</div>
+				</div>
+
+
 <!--			<a-form-item label="是否热门" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">-->
 <!--				<a-radio-group v-model:value="formState.isHot">-->
 <!--					<a-radio  v-if='itemi.isHot==0' :value="0">否</a-radio>-->
@@ -138,12 +186,15 @@
 	import {storeToRefs} from "pinia";
 	const myMiniyunStore = miniyunStore()
 	// 使用 storeToRefs 保持响应性
-	const { getFileForms } = storeToRefs(myMiniyunStore)
+	const { getFileForms ,getPauseFlags} = storeToRefs(myMiniyunStore)
 
 	// getFileModel 保持了与 store 的响应式连接
 	const fileForms = computed({
 		get: () => getFileForms.value
 	})
+	const pauseFlags = computed({
+		get: () => getPauseFlags.value
+	})
 	const upLoadBreakPointRef = ref(null) // 预览回显
 	const coverImagePath = ref() // 预览回显
 	const formRef = ref() // 添加表单引用
@@ -163,6 +214,43 @@
 
 
 
+	const pauseUpload = (md5) => {
+		myMiniyunStore.pauseUpload(md5)
+		// const item = uploadFileList.value[index]
+		// if (item && item.md5) {
+		// 	pauseFlags.value[item.md5] = true
+		// 	uploadingTasks.value[item.md5] = false
+		// }
+	}
+
+	const resumeUpload = (md5) => {
+		myMiniyunStore.resumeUpload(md5)
+		// const item = uploadFileList.value[index]
+		// if (!item || !item.md5) return
+		//
+		// pauseFlags.value[item.md5] = false
+		//
+		// // 如果当前上传任务小于 2,则开始上传
+		// const activeTasks = Object.keys(uploadingTasks.value).filter((key) => uploadingTasks.value[key])
+		// if (activeTasks.length < 2) {
+		// 	uploadingTasks.value[item.md5] = true
+		// 	// uploadSingleFile(item)
+		// } else {
+		// 	pauseFlags.value[item.md5] = true
+		// }
+	}
+
+	const handlerRemoveItem = (indexi,indexii) => {
+		// const item = uploadFileList.value[index]
+		// if (item && item.md5) {
+		// 	delete pauseFlags.value[item.md5] // 清理暂停标志
+		// }
+
+		myMiniyunStore.handlerRemoveItem(indexi,indexii)
+
+
+		// emit('onSuccess', uploadFileList.value)
+	}
 	// 关闭模态框
 	const handleUploadCancel = () => {
 		emit('close')

+ 10 - 6
src/components/MiniYun/upLoadBreakPoint.vue

@@ -2,6 +2,7 @@
 	<div class="mydiv">
 		<a-spin :spinning="spinning" tip="读取中...">
 			<a-upload-dragger
+				ref="fileUpload"
 				:file-list="fileList"
 				:before-upload="beforeUpload"
 				@change="handleChange"
@@ -11,7 +12,7 @@
 				:drag="true"
 				:progress="progress"
 			>
-				<div>
+				<div >
 									<div><p class="ant-upload-text" style="display: inline-block;">点击上传或将文件拖拽至此区域上传</p></div>
 					<div>	<p class="ant-upload-hint"  style="display: inline-block;">
 										按住Ctrl可同时多选,支持上传
@@ -104,6 +105,7 @@ import axios from 'axios'
 import sysConfig from '@/config/index'
 	import { miniyunStore } from '@/store/miniyun'
 	const myMiniyunStore = miniyunStore()
+const fileUpload = ref(null)
 	import { message } from 'ant-design-vue'
 	import SparkMD5 from 'spark-md5'
 	import tool from '@/utils/tool'
@@ -816,11 +818,13 @@ const chunkSize = ref(5 * 1024 * 1024)
 		clearFileList()
 	}
 
-	onMounted(() => {
-		// getList()
-		// pauseFlags.value = {}
-
-	})
+onMounted(() => {
+	nextTick(() => {
+		// if ( fileUpload.value && fileUpload.value.$el.querySelector('input')) {
+		// 	fileUpload.value.$el.querySelector('input').webkitdirectory = true;
+		// }
+	});
+});
 onUpdated(() => {
 	console.log('组件已更新')
 })

+ 10 - 5
src/components/UpLoadBreakPoint/index.vue

@@ -282,7 +282,8 @@
 			chunks: chunkList, // 分块列表
 			fileSuffix: fileSuffix, // 后缀
 			percents: 0,
-			time: ''
+			time: '',
+			affiliationFuncType : 1
 		}
 
 		// console.log('md5:', fileMd5.value)
@@ -363,7 +364,8 @@
 			size: uploadFile.size,
 			chunkSize: uploadFile.chunks.length,
 			fileName: uploadFile.name,
-			fileSuffix: uploadFile.fileSuffix
+			fileSuffix: uploadFile.fileSuffix,
+			affiliationFuncType : uploadFile.affiliationFuncType
 		}
 		md5List.push(element)
 
@@ -557,7 +559,7 @@
 			if (uploadFileList.value[i].chunks.length === chunksUploadedList[i]) {
 				const mergeResult = await axios.post(
 					// `/api/webapp/disk/minio/merge?md5=${uploadFileList.value[i].md5}&fileSuffix=${uploadFileList.value[i].fileSuffix}&chunkTotal=${chunksUploadedList[i]}`
-					sysConfig.API_URL+`/api/webapp/minio/merge?md5=${uploadFileList.value[i].md5}&fileSuffix=${uploadFileList.value[i].fileSuffix}&chunkTotal=${chunksUploadedList[i]}&fileName=${uploadFileList.value[i].name}&fileSize=${uploadFileList.value[i].size}`,
+					sysConfig.API_URL+`/api/webapp/minio/merge?md5=${uploadFileList.value[i].md5}&fileSuffix=${uploadFileList.value[i].fileSuffix}&chunkTotal=${chunksUploadedList[i]}&fileName=${uploadFileList.value[i].name}&fileSize=${uploadFileList.value[i].size}&affiliationFuncType=${uploadFileList.value[i].affiliationFuncType}`,
 					null,
 					{ headers: { Token: tool.data.get('TOKEN') } }
 				)
@@ -604,6 +606,7 @@
 		formData.append('chunkSize', data.chunkSize)
 		formData.append('fileSuffix', data.fileSuffix)
 		formData.append('fileName', data.fileName)
+		formData.append('affiliationFuncType', data.affiliationFuncType)
 		return axios
 			.post(sysConfig.API_URL+'/api/webapp/minio/upload', formData, {
 				headers: { 'Content-Type': 'multipart/form-data', Token: tool.data.get('TOKEN') }
@@ -630,6 +633,7 @@
 		formData.append('chunkSize', data.chunkSize)
 		formData.append('fileSuffix', data.fileSuffix)
 		formData.append('fileName', data.fileName)
+		formData.append('affiliationFuncType', data.affiliationFuncType)
 		return axios
 			.post(sysConfig.API_URL+'/api/webapp/minio/upload', formData, {
 				headers: { 'Content-Type': 'multipart/form-data', Token: tool.data.get('TOKEN') }
@@ -761,7 +765,8 @@
 						chunkIndex: i + 1,
 						fileSuffix: item.fileSuffix,
 						chunkSize: item.chunks.length,
-						fileName: item.name
+						fileName: item.name,
+						affiliationFuncType : item.affiliationFuncType
 					},
 					() => {
 						// chunksUploaded.value++
@@ -787,7 +792,7 @@
 
 		// 合并分片
 		const mergeResult = await axios.post(
-			sysConfig.API_URL+`/api/webapp/minio/merge?md5=${md5}&fileSuffix=${item.fileSuffix}&chunkTotal=${item.chunks.length}&fileName=${item.name}&fileSize=${item.size}`,
+			sysConfig.API_URL+`/api/webapp/minio/merge?md5=${md5}&fileSuffix=${item.fileSuffix}&chunkTotal=${item.chunks.length}&fileName=${item.name}&fileSize=${item.size}&affiliationFuncType=${item.affiliationFuncType}`,
 			null,
 			{ headers: { Token: tool.data.get('TOKEN') } }
 		)

+ 345 - 30
src/store/miniyun.js

@@ -1,30 +1,32 @@
+import {defineStore} from 'pinia'
 
-import { defineStore } from 'pinia'
-
-import { ref, onMounted } from 'vue'
+import {ref, onMounted} from 'vue'
 import axios from 'axios'
 import SparkMD5 from 'spark-md5'
 import tool from '@/utils/tool'
-import { message } from 'ant-design-vue'
+import {message} from 'ant-design-vue'
 import sysConfig from '@/config/index'
-
+import resourceAuditApi from '@/api/resourceAudit.js'
+import EventBus from "@/utils/EventBus";
 export const miniyunStore = defineStore({
 	id: 'miniyun',
 	state: () => ({
-		 pauseFlags : {}, // 控制每个文件是否暂停 { md5: true/false }
-		 uploadingTasks : {}, // 正在上传的任务 { md5: true }
+		pauseFlags: {}, // 控制每个文件是否暂停 { md5: true/false }
+		uploadingTasks: {}, // 正在上传的任务 { md5: true }
+		checkingFiles: new Set(), // 正在检查的文件MD5
+		checkedFiles: new Map(), // 已检查的文件结果缓存
 		//当前选中的文件
-		 currentFile : null,
-		 spinning : false,
-		 chunkSize : 5 * 1024 * 1024,
+		currentFile: null,
+		spinning: false,
+		chunkSize: 5 * 1024 * 1024,
 
-		 uploadedSize : 0, // 已上传文件大小(字节)
+		uploadedSize: 0, // 已上传文件大小(字节)
 
-		 chunkCount : 0,
-		 chunksUploaded : 0,
-		 fileMd5 : '', //
+		chunkCount: 0,
+		chunksUploaded: 0,
+		fileMd5: '', //
 		// const emit = defineEmits(['onUpLoading', 'onSuccess'])
-		progress : {
+		progress: {
 			strokeColor: {
 				'0%': '#108ee9',
 				'100%': '#87d068'
@@ -33,27 +35,340 @@ export const miniyunStore = defineStore({
 			format: (percent) => `${parseFloat(percent.toFixed(2))}%`,
 			class: 'test'
 		},
-		 allChunks : 0, // 文件的md5值
-		successfulChunkPercents : 0, // 上传成功的分片百分比
-		 fileSuffix : '', // 文件后缀
-		 chunkList : [], // 文件后缀
-		 uploadList : [], // 文件后缀
-		 fileList : [] ,// 文件后缀
-		 uploadFileList : [], // 文件后缀
-		 uploadChunks : [],// 文件后缀
-
-		 upLoadTag : false, // 文件的md5值
-		 startTime : 0, // 开始时间戳(毫秒
-		 totalSize :0, // 开始时间戳(毫秒
+		allChunks: 0, // 文件的md5值
+		successfulChunkPercents: 0, // 上传成功的分片百分比
+		fileSuffix: '', // 文件后缀
+		chunkList: [], // 文件后缀
+		uploadList: [], // 文件后缀
+		fileList: [],// 文件后缀
+		uploadFileList: [], // 文件后缀
+		uploadChunks: [],// 文件后缀
+		uploadFileListTemp: [],
+		upLoadTag: false, // 文件的md5值
+		startTime: 0, // 开始时间戳(毫秒
+		totalSize: 0, // 开始时间戳(毫秒
+		tempIndex: 0,
 		//文件数据放一起
-		fileForms : [],
+		fileForms: [],
 	}),
 	getters: {
 		getFileForms: (state) => state.fileForms,
+		getPauseFlags: (state) => state.pauseFlags,
 	},
 	actions: {
-		addFileForms(fileForm){
+		async addFileForms(fileForm) {
 			this.fileForms.push(fileForm)
+
+			for (let i = 0; i < fileForm.uploadFileList.length; i++) {
+
+				await this.checkMd5List(fileForm.uploadFileList[i])
+				await this.uploadSingleFile(fileForm.uploadFileList[i])
+			}
+			//准备开启去下载
+			// for (let i = 0; i < fileForm.uploadFileList.length; i++) {
+			// 	this.tempIndex+=1
+			// 	fileForm.uploadFileList[i].tempIndex = this.tempIndex
+			// 	this.uploadFileListTemp.push(fileForm.uploadFileList[i])
+			// }
+		},
+		handlerRemoveItem(mmyIndex, mmmyIndex) {
+			// let myIndex = -1
+			// for (let i = 0; i < this.uploadFileListTemp.length; i++) {
+			// 	if(this.uploadFileListTemp[i].tempIndex == item.tempIndex){
+			// 		myIndex = i
+			// 	}
+			// }
+			// if(myIndex != -1){
+			// 	this.uploadFileListTemp.splice(myIndex, 1)
+			// }
+			// let mmyIndex = -1
+			// let mmmyIndex = -1
+			// for (let i = 0; i < this.fileForms.length; i++) {
+			// 	for (let ii = 0; ii < this.fileForms[i].uploadFileList.length; ii++) {
+			// 		if(this.fileForms[i].uploadFileList[ii].tempIndex == item.tempIndex){
+			// 			mmyIndex = i
+			// 			mmmyIndex = ii
+			// 		}
+			// 	}
+			// }
+			// if(mmyIndex != -1 && mmmyIndex != -1){
+			// 	this.fileForms[mmyIndex].uploadFileList.splice(mmmyIndex, 1)
+			// }
+
+			this.fileForms[mmyIndex].uploadFileList.splice(mmmyIndex, 1)
+
+		},
+		pauseUpload(md5) {
+			this.pauseFlags[md5] = true
+		},
+		resumeUpload(md5) {
+			this.pauseFlags[md5] = false
+		},
+		async checkMd5List(uploadFile) {
+			// 标记文件正在检查
+			this.checkingFiles.add(uploadFile.md5)
+
+			const md5List = [{
+				md5: uploadFile.md5,
+				size: uploadFile.size,
+				chunkSize: uploadFile.chunks.length,
+				fileName: uploadFile.name,
+				fileSuffix: uploadFile.fileSuffix,
+				affiliationFuncType : 0
+			}]
+			await axios
+				.post(sysConfig.API_URL + '/api/webapp/minio/checkMd5List', md5List, {headers: {Token: tool.data.get('TOKEN')}})
+				.then((res) => {
+					console.log('文件上传返回结果:', res.data)
+					// return
+					var list = res.data
+					if (list.length !== 0) {
+						let upList = []
+						for (let item of list) {
+							console.log('item回来的', JSON.stringify(item))
+							if (uploadFile.md5 === item.md5) {
+								uploadFile.userFileId = item.userFileId
+								//重要的步骤
+								if (item.userFileId) {
+									uploadFile.percents = 100
+									this.checkedFiles.set(uploadFile.md5, {
+										userFileId: item.userFileId,
+										status: 'uploaded'
+									})
+									// emit('onSuccess', uploadFile)
+								}
+								// upList.push(item)
+							}
+						}
+						console.log('upList是:', upList)
+						// uploadFileList.value.push(uploadFile)
+						// emit('onSuccess', uploadFileList.value)
+					}
+					// 从正在检查列表中移除
+					this.checkingFiles.delete(uploadFile.md5)
+					return uploadFile
+					// 文件均存在minio中了,无需上传
+					// if (uploadFileList.value.length === 0) {
+					// 	successfulChunkPercents.value = 100
+					// 	alert('文件上传成功')
+					// }
+				})
+				.catch((error) => {
+					console.log('检查返回错误', error)
+					// 从正在检查列表中移除
+					this.checkingFiles.delete(uploadFile.md5)
+					throw error
+				})
+		},
+
+
+		async uploadFilesChunk(data, onSuccess) {
+			console.log('进入了uploadFileChunk方法...')
+			let retryTime = 5 //重试次数
+			const formData = new FormData()
+			formData.append('md5', data.md5)
+			// formData.append('md5', md5)
+			formData.append('chunkIndex', data.chunkIndex)
+			formData.append('chunk', data.chunk)
+			formData.append('chunkSize', data.chunkSize)
+			formData.append('fileSuffix', data.fileSuffix)
+			formData.append('fileName', data.fileName)
+			formData.append('affiliationFuncType', data.affiliationFuncType)
+			return axios
+				.post(sysConfig.API_URL + '/api/webapp/minio/upload', formData, {
+					headers: {'Content-Type': 'multipart/form-data', Token: tool.data.get('TOKEN')}
+				})
+				.then((res) => onSuccess())
+				.catch((error) => {
+					console.log('上传分片失败了...', error)
+					if (retryTime > 0) {
+						retryTime--
+						return this.uploadChunk(data, onSuccess)
+					}
+				})
+		},
+
+		// 上传分片 旧
+		async uploadChunk(data, onSuccess) {
+			let retryTime = 5 //重试次数
+			const formData = new FormData()
+			// formData.append('identifier', fileMd5.value)
+			formData.append('md5', data.md5)
+			// formData.append('md5', md5)
+			formData.append('chunkIndex', data.chunkIndex)
+			formData.append('chunk', data.chunk)
+			formData.append('chunkSize', data.chunkSize)
+			formData.append('fileSuffix', data.fileSuffix)
+			formData.append('fileName', data.fileName)
+			formData.append('affiliationFuncType', data.affiliationFuncType)
+			return axios
+				.post(sysConfig.API_URL + '/api/webapp/minio/upload', formData, {
+					headers: {'Content-Type': 'multipart/form-data', Token: tool.data.get('TOKEN')}
+				})
+				.then((res) => onSuccess())
+				.catch((error) => {
+					if (retryTime > 0) {
+						retryTime--
+						return this.uploadChunk(data, onSuccess)
+					}
+				})
+		},
+		calculateSpeed(startTime, uploadedSize) {
+			const currentTime = new Date().getTime()
+			const timeElapsed = (currentTime - startTime) / 1000 // 单位:秒
+			if (timeElapsed > 0) {
+				const speed = uploadedSize / timeElapsed // 单位:字节/秒
+				return speed
+			}
+			return 0
+		},
+		estimateRemainingTime(startTime, uploadedSize, totalSize) {
+			console.log('疑问', ' 总的 ', totalSize, ' 变化的 ', uploadedSize)
+			const remainingSize = totalSize - uploadedSize // 剩余文件大小
+			const speed = this.calculateSpeed(startTime, uploadedSize) // 平均上传速度(字节/秒)
+
+			if (speed > 0) {
+				const remainingTimeSeconds = remainingSize / speed // 剩余时间(秒)
+				return remainingTimeSeconds
+			}
+			return Infinity // 如果上传速度为 0,则无法估算
+		},
+		formatTime(seconds) {
+			const minutes = Math.floor(seconds / 60)
+			const secs = Math.floor(seconds % 60)
+			if (minutes == 0 && secs == 0) {
+				return ''
+			}
+			return `${minutes} 分 ${secs} 秒`
+		},
+		async uploadSingleFile(fileObj) {
+			const file = fileObj
+			const md5 = file.md5
+			// const index = uploadFileList.value.findIndex((item) => item.md5 === md5)
+			//
+			// if (index === -1) return
+
+			const item = fileObj
+
+			if (item && item.userFileId) {
+				this.getUp(fileObj)
+				return
+			}
+
+			// // 如果是暂停状态则不执行上传
+			// while (pauseFlags.value[md5]) {
+			// 	await new Promise((resolve) => setTimeout(resolve, 500))
+			// }
+			// 添加到正在上传任务中
+			// uploadingTasks.value[md5] = true
+			file.startTime = new Date().getTime()
+			file.uploadedSize = 0
+
+			const chunkPromises = []
+
+			for (let i = 0; i < item.chunks.length; i++) {
+				let chunk = item.chunks[i]
+
+				while (this.pauseFlags[md5]) {
+					await new Promise((resolve) => setTimeout(resolve, 500))
+				}
+
+				chunkPromises.push(
+					await this.uploadFilesChunk(
+						{
+							affiliationFuncType : 0,
+							md5,
+							chunk,
+							chunkIndex: i + 1,
+							fileSuffix: item.fileSuffix,
+							chunkSize: item.chunks.length,
+							fileName: item.name
+						},
+						() => {
+							// chunksUploaded.value++
+							// uploadChunks.value++
+
+							// successfulChunkPercents.value = 100 * (uploadChunks.value / allChunks.value).toFixed(2)
+							item.uploadedSize += chunk.size // 更新已上传大小
+							const remainingTime = this.estimateRemainingTime(item.startTime, item.uploadedSize, item.size)
+							console.log(`预计剩余时间: ${this.formatTime(remainingTime)}`)
+							// item.percents = (100 * (uploadChunks.value / item.chunks.length)).toFixed(2)
+							item.time = this.formatTime(remainingTime)
+
+							const percent = ((i + 1) / item.chunks.length) * 100
+							item.percents = percent.toFixed(2)
+							console.log(`我得名字: `, item.name, ' i ', i, ' item.chunks.length ', item.chunks.length)
+							// item.time = formatTime(estimateRemainingTime())
+						}
+					)
+				)
+			}
+
+			await Promise.all(chunkPromises)
+			item.affiliationFuncType = 0
+			// 合并分片
+			const mergeResult = await axios.post(
+				sysConfig.API_URL + `/api/webapp/minio/merge?md5=${md5}&fileSuffix=${item.fileSuffix}&chunkTotal=${item.chunks.length}&fileName=${item.name}&fileSize=${item.size}&affiliationFuncType=${item.affiliationFuncType}`,
+				null,
+				{headers: {Token: tool.data.get('TOKEN')}}
+			)
+			console.log('怎么说呢', ' 啊网络请求 ', mergeResult)
+			fileObj.userFileId = mergeResult.data.userFileId
+			fileObj.percents = 100
+
+			// uploadingTasks.value[item.md5] = false
+			// item.time = '上传完成'
+			// 尝试恢复一个被暂停的任务
+			// autoResumePausedUpload()
+			// upLoadTag.value = false
+			// emit('onSuccess', uploadFileList.value)
+
+
+			this.getUp(fileObj)
 		},
-	}
+		async getUp(fileObj) {
+
+			for (let i = 0; i < this.fileForms.length; i++) {
+				console.log('怎么说呢', this.fileForms[i].upTag != undefined,' 里面的 ', this.fileForms[i], ' 外面的1 ', fileObj)
+
+				if (this.fileForms[i].upTag == undefined) {
+					let count = 0
+					let list = []
+					for (let ii = 0; ii < this.fileForms[i].uploadFileList.length; ii++) {
+						console.log('怎么说呢', ' 里面的 ', this.fileForms[i], ' 外面的2 ', fileObj)
+						let item = this.fileForms[i].uploadFileList[ii]
+						if (item.userFileId != undefined && item.percents == 100){
+							count ++
+							list.push(item.userFileId)
+						}
+					}
+					if(this.fileForms[i].uploadFileList.length == count){
+					let formData = this.fileForms[i]
+						formData.userfileIds = list.join(',')
+
+						//去上传
+					let res = await resourceAuditApi.add(formData)
+							// .then((res) => {
+							// 	Modal.success({ content: '资源上传成功' })
+							// })
+							// .catch((err) => {
+							// 	Modal.success({ content: '资源上传失败' })
+							// 	console.log(err)
+							// })
+						console.log('上传至hi偶',res)
+						this.fileForms[i].upTag = true
+						EventBus.emit('onUpTag')
+					}
+				}
+			}
+
+		}
+
+	},
+
+
+
+
+
+
 })

+ 7 - 1
src/views/exm/question/index.vue

@@ -166,6 +166,7 @@
 	import { StarFilled, StarOutlined } from '@ant-design/icons-vue'
 	import { Modal, message } from 'ant-design-vue'
 	import { parseTime } from '@/utils/exam'
+	import downloadUtil from '@/utils/downloadUtil'
 	import config from '@/config'
 	const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
 	const examStore = useExamStore()
@@ -342,7 +343,12 @@
 		// })
 	}
 	const downloadTemplate = () => {
-		window.open(`${config.FILE_URL}题目导入模版.xlsx`)
+		// window.open(`${config.FILE_URL}题目导入模版.xlsx`)
+		// window.open(`${config.API_URL}/api/webapp/admin/question/downloadImportQuesTemplate`)
+
+		tQuestionApi.downTemplate().then((res) => {
+			downloadUtil.resultDownload(res)
+		})
 	}
 </script>
 

+ 9 - 2
src/views/myResources/myResources.vue

@@ -174,7 +174,7 @@
 								</span>
 								<template #overlay>
 									<a-menu>
-										<a-menu-item>
+										<a-menu-item >
 											<a href="javascript:;" @click="handleView(record)">预览</a>
 										</a-menu-item>
 										<a-menu-item>
@@ -992,7 +992,10 @@
 		pagination.pageSize = size
 		getListData()
 	}
-
+	const onUpTag = () => {
+		pagination.pageNum = 1
+		getListData()
+	}
 	onMounted(() => {
 		if (pageType == 'economize') {
 			formState.verifyStatus = '1'
@@ -1008,6 +1011,10 @@
 			storageMax.value = res.data.totalStorageSize
 		})
 	})
+
+
+	EventBus.on('onUpTag',onUpTag)
+
 </script>
 
 <style scoped>

+ 4 - 3
src/views/portal/components/Header.vue

@@ -58,6 +58,7 @@
 
 				</div>
 				<div style="width: auto; margin-right: 10px; display: flex; align-items: center; " >
+					<MiniYun></MiniYun>
 					<UserHeader></UserHeader>
 				</div>
 			</div>
@@ -84,10 +85,9 @@
 <script setup>
 	import { createVNode } from 'vue'
 	import {ExclamationCircleOutlined, FolderOutlined} from '@ant-design/icons-vue'
-	import { Modal } from 'ant-design-vue'
+	import { Modal,message } from 'ant-design-vue'
 	import UserHeader from '@/views/portal/components/UserHeader.vue'
 	import loginApi from '@/api/auth/loginApi'
-	import { message } from 'ant-design-vue'
 	import { ref } from 'vue'
 	import { useRouter, useRoute } from 'vue-router'
 	const router = useRouter()
@@ -95,6 +95,7 @@
 	const current = ref([route.path.slice(1)]) // 默认选中“资源中心”
 	import tool from '@/utils/tool'
 	import EventBus from "@/utils/EventBus";
+	import MiniYun from "@/components/MiniYun/index.vue";
 	const emit = defineEmits(['onChangeCurrent'])
 	const userInfo = tool.data.get('USER_INFO')
 	watch(
@@ -106,7 +107,7 @@
 			}
 		}
 	)
-	watch(() => route.path,(newPath, oldPath) => { 
+	watch(() => route.path,(newPath, oldPath) => {
 		current.value = [newPath.slice(1)]
 	 },{ immediate: true });
 

+ 5 - 3
src/views/resourceCenter/components/ResourceList.vue

@@ -7,7 +7,7 @@
 					<span style="font-weight: bold; font-size: 20px; margin-left: 10px">共计 {{ total }} 个资源</span>
 				</div>
 				<div style="width: 20px"></div>
-				<TabSwitcher @selectTab="selectTab"/>
+				<TabSwitcherNew @selectTab="selectTab"/>
 			</div>
 			<div>
 				<a-range-picker
@@ -114,7 +114,7 @@
 <script setup>
 import {ref} from 'vue'
 import Frame350 from '@/assets/images/Frame350.png'
-import TabSwitcher from './TabSwitcher.vue'
+import TabSwitcherNew from './TabSwitcherNew.vue'
 import {list} from '@/api/portal'
 import tool from '@/utils/tool'
 import EventBus from '@/utils/EventBus'
@@ -166,8 +166,10 @@ const selectTab = (key) => {
 	tabKeyName.value = key
 	if (key == 'latest') {
 		tabKey.value = 0
-	} else {
+	} else if(key == 'hot' ){
 		tabKey.value = 1
+	} else if(key == 'new' ){
+		tabKey.value = 2
 	}
 	console.log('有没有', ' currentPage.date ', currentPage.date,)
 	if (dateName.value[0] == '' && dateName.value[1] == '') {

+ 50 - 0
src/views/resourceCenter/components/TabSwitcherNew.vue

@@ -0,0 +1,50 @@
+<template>
+	<div class="tab-switcher">
+		<div :class="{ active: selectedTab === 'latest' }" @click="selectTab('latest')"><span style="font-size: 12px">最新</span></div>
+		<div :class="{ active: selectedTab === 'hot' }" @click="selectTab('hot')"><span style="font-size: 12px">热门</span></div>
+		<div :class="{ active: selectedTab === 'new' }" @click="selectTab('new')"><span style="font-size: 12px">素材库</span></div>
+	</div>
+</template>
+
+<script setup>
+	import { ref } from 'vue'
+	const emit = defineEmits(['selectTab'])
+	const selectedTab = ref('latest')
+
+	const selectTab = (tab) => {
+		if (selectedTab.value != tab) {
+			selectedTab.value = tab
+			emit('selectTab', tab)
+		}
+	}
+</script>
+
+<style scoped>
+	.tab-switcher {
+		display: flex;
+		border-radius: 5px;
+		border: 1px solid #1e90ff;
+		overflow: hidden;
+	}
+
+	.tab-switcher div {
+		padding: 1px 25px;
+		background-color: #ffffff;
+
+		cursor: pointer;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.tab-switcher div.active {
+		background-color: #1e90ff;
+		color: white;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.tab-switcher div:not(:last-child) {
+	}
+</style>

+ 10 - 1
src/views/resourceDetails/components/VideoDetails.vue

@@ -94,12 +94,15 @@
 										<div style="width: 10px"></div>
 										<button class="share-btn">分享资源</button>
 									</div>
-									<div style="display: flex; align-items: center; cursor: pointer" @click="handlerCollection">
+									<div style="display: flex; align-items: center; cursor: pointer;margin-right: 10px" @click="handlerCollection">
 										<StarOutlined v-if="starTag == false" />
 										<StarFilled v-if="starTag == true" />
 										<div style="width: 10px"></div>
 										<button class="favorite-btn">收藏资源</button>
 									</div>
+									<div style="display: flex; align-items: center; cursor: pointer" @click="handlerDown">
+										<a-button type="primary">下载</a-button>
+									</div>
 								</div>
 							</div>
 						</div>
@@ -237,6 +240,12 @@
 			})
 			.catch((err) => {})
 	}
+
+	const handlerDown = () => {
+		console.log('去下载', itemData.value)
+		window.open(sysConfig.FILE_URL + itemData.value.fileUrl)
+	}
+
 	const handlerCollection = async () => {
 		console.log('打开收藏', itemData.value)
 		const id = route.query.id

+ 10 - 5
src/views/statisticalAnalysis/analysisLearningBehaviors/index.vue

@@ -2,8 +2,8 @@
 	<div class="learning-behavior-analysis">
 		<!-- 页面头部 -->
 		<div class="header">
-			<h1>📊 学习行为分析</h1>
-			<p>全面分析学院教学情况和学员学习行为</p>
+			<h1>📊 在线学习统计分析报告</h1>
+			<p>系统不定期生成在线学习统计分析报告</p>
 		</div>
 
 		<!-- 导航标签 -->
@@ -137,7 +137,7 @@
 						</div>
 						<div class="student-stats">
 							<div class="stat-item">
-								<div class="stat-value">{{ (student.finishRate)* 100 }}%</div>
+								<div class="stat-value">{{tool.formatValue(student.finishRate)}}</div>
 								<div class="stat-label">总体进度</div>
 							</div>
 						</div>
@@ -148,9 +148,13 @@
 							<div class="course-name">{{ course.courseName }}</div>
 							<div class="course-stats">
 								<div class="stat-item">
-									<div class="stat-value">{{ (course.finishRate)* 100 }}%</div>
+									<div class="stat-value">{{tool.formatValue(course.finishRate)}}</div>
 									<div class="stat-label">学习进度</div>
 								</div>
+								<div class="stat-item">
+									<div class="stat-value">{{tool.formatValue(course.errorRate)}}</div>
+									<div class="stat-label">错误率</div>
+								</div>
 								<div class="stat-item">
 									<div class="stat-value">{{ course.workRate }}</div>
 									<div class="stat-label">作业完成</div>
@@ -184,7 +188,7 @@
 		searchStudents
 	} from '@/api/statisticalAnalysis/analysisLearningBehaviors'
 	import dayjs from "dayjs";
-
+	import tool from '@/utils/tool'
 	// 当前活动标签
 	const activeTab = ref('college')
 	const times = ref([])
@@ -424,6 +428,7 @@
 		if (tabName === 'college') {
 			nextTick(async () => {
 				await initCollegeCharts()
+
 			})
 		}
 		if (tabName === 'student') {

+ 1 - 1
src/views/statistics/index.vue

@@ -1,7 +1,7 @@
 <template>
 
 	<a-menu v-model:selectedKeys="current" mode="horizontal" theme="light" style="line-height: 55px">
-		<a-menu-item style="margin-left: 10px; margin-right: 10px" key="">学习行为分析</a-menu-item>
+		<a-menu-item style="margin-left: 10px; margin-right: 10px" key="">在线学习统计分析报告</a-menu-item>
 		<a-menu-item style="margin-left: 10px; margin-right: 10px" key="AnalysisTeachingActivities">教学活动分析</a-menu-item>
 		<a-menu-item style="margin-left: 10px; margin-right: 10px" key="OverviewLearningProgress">学习明细数据</a-menu-item>
 		<a-menu-item style="margin-left: 10px; margin-right: 10px" key="VideoAnalysis">视频分析</a-menu-item>