Procházet zdrojové kódy

上传组件调整完毕

于添 před 7 měsíci
rodič
revize
cea6ee3656

+ 18 - 0
src/api/courseinfo/index.js

@@ -0,0 +1,18 @@
+// 文件模块相关接口
+import { moduleRequest } from '@/utils/reSourceRequest'
+
+const request = moduleRequest(`/api/webapp/`)
+
+/**
+ * 获取文件列表相关接口
+ */
+// 获取文件列表(区分文件路径)
+export const list = (p) => request('disk/courseinfo/page', p, 'get')
+// 获取文件列表(区分文件类型)
+// export const getFileListByType = (p) => request('file/selectfilebyfiletype', p, 'get')
+
+// 进入详情之后增加观看次数
+export const addViewCount = (p) => request('disk/courseauditrecord/addViewCount', p, 'post')
+//详情
+export const detail = (p) => request('disk/courseauditrecord/detail', p, 'get')
+//收藏增加

+ 557 - 0
src/components/UpLoadBreakPoint/index.vue

@@ -0,0 +1,557 @@
+<template>
+	<div>
+		<a-upload-dragger
+			:file-list="fileList"
+			:before-upload="beforeUpload"
+			@change="handleChange"
+			:show-upload-list="false"
+			:customRequest="customRequest"
+			:multiple="false"
+			:drag="true"
+			:progress="progress"
+		>
+			<div style="padding: 40px; text-align: center">
+				<span class="text"> 点击上传或将文件拖拽至此区域上传 </span>
+				<p class="text">按住Ctrl可同时多选,支持上传PPT/excel/pdf/mp4/zip/rar,等单个文件不能超过2G</p>
+			</div>
+		</a-upload-dragger>
+
+		<div style="margin-bottom: 20px">
+			<a-button v-if="uploadFileList.length > 0" type="primary" @click="uploadFilesList">上传</a-button>
+		</div>
+
+		<div v-for="(item, index) in uploadFileList" :key="index">
+			<div style="padding: 10px">
+				<div style="display: flex; width: 100%; align-items: center; justify-content: space-between">
+					<div>
+						<span>{{ item.name }}</span>
+					</div>
+					<div>
+						<span v-if="item.time != ''" style="display: block; color: blue">{{ item.time }}</span>
+						<span
+							v-if="item.percents == 0 || item.percents == 100"
+							style="color: red; cursor: pointer; margin-left: 10px"
+							@click="handlerRemoveItem(index)"
+							>删除</span
+						>
+					</div>
+				</div>
+
+				<a-progress :percent="item.percents" />
+			</div>
+		</div>
+
+		<!-- <div> -->
+		<!-- <el-progress :text-inside="true" :stroke-width="20" :percentage="successfulChunkPercents" status="success" /> -->
+		<!-- <a-progress :percent="successfulChunkPercents" /> -->
+		<!-- </div> -->
+
+		<!--    已上传列表-->
+		<!-- <el-table :data="uploadList" border style="width: 100%">
+			<el-table-column fixed prop="id.date" label="日期" width="150"> </el-table-column>
+			<el-table-column prop="url" label="下载地址"> </el-table-column>
+
+			<el-table-column label="操作">
+				<template #default="scope">
+					<el-button link type="primary" size="small" @click.prevent="deleteFile(scope.row.url)"> 删除 </el-button>
+					<el-button link type="primary" size="small" @click.prevent="downloadFile(scope.row.url)"> 下载 </el-button>
+				</template>
+			</el-table-column>
+		</el-table> -->
+	</div>
+</template>
+<script setup>
+	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'
+	//当前选中的文件
+	const currentFile = ref(null)
+	const chunkSize = ref(5 * 1024 * 1024)
+
+	const uploadedSize = ref(0) // 已上传文件大小(字节)
+
+	const chunkCount = ref(0)
+	const chunksUploaded = ref(0)
+	const fileMd5 = ref('') //
+	const emit = defineEmits(['onUpLoading', 'onSuccess'])
+	const progress = {
+		strokeColor: {
+			'0%': '#108ee9',
+			'100%': '#87d068'
+		},
+		strokeWidth: 3,
+		format: (percent) => `${parseFloat(percent.toFixed(2))}%`,
+		class: 'test'
+	}
+	const allChunks = ref(0) // 文件的md5值
+	const successfulChunkPercents = ref(0) // 上传成功的分片百分比
+	const fileSuffix = ref('') // 文件后缀
+	const chunkList = ref([]) // 文件后缀
+	const uploadList = ref([]) // 文件后缀
+	const fileList = ref([]) // 文件后缀
+	const uploadFileList = ref([]) // 文件后缀
+	const uploadChunks = ref([]) // 文件后缀
+
+	const upLoadTag = ref(false) // 文件的md5值
+	const startTime = ref(0) // 开始时间戳(毫秒
+	const totalSize = ref(0) // 开始时间戳(毫秒
+	const handlerRemoveItem = (index) => {
+		uploadFileList.value.splice(index, 1)
+		emit('onSuccess', uploadFileList.value)
+	}
+
+	const calculateFileMD5 = (file) => {
+		return new Promise((resolve, reject) => {
+			const reader = new FileReader()
+			const spark = new SparkMD5.ArrayBuffer()
+
+			reader.onload = (e) => {
+				spark.append(e.target.result)
+				const md5 = spark.end()
+				resolve(md5)
+			}
+
+			reader.onerror = () => reject(new Error('文件读取失败'))
+
+			reader.readAsArrayBuffer(file)
+		})
+	}
+
+	const updateProgress = (chunkSize) => {
+		uploadedSize.value += chunkSize
+		const percent = Math.round((uploadedSize.value / totalSize.value) * 100)
+		console.log(`上传进度: ${percent}%`)
+	}
+	const calculateSpeed = () => {
+		const currentTime = new Date().getTime()
+		const timeElapsed = (currentTime - startTime.value) / 1000 // 单位:秒
+		if (timeElapsed > 0) {
+			const speed = uploadedSize.value / timeElapsed // 单位:字节/秒
+			return speed
+		}
+		return 0
+	}
+	const estimateRemainingTime = () => {
+		console.log('疑问', ' 总的 ', totalSize.value, ' 变化的 ', uploadedSize.value)
+		const remainingSize = totalSize.value - uploadedSize.value // 剩余文件大小
+		const speed = calculateSpeed() // 平均上传速度(字节/秒)
+
+		if (speed > 0) {
+			const remainingTimeSeconds = remainingSize / speed // 剩余时间(秒)
+			return remainingTimeSeconds
+		}
+		return Infinity // 如果上传速度为 0,则无法估算
+	}
+	const formatTime = (seconds) => {
+		const minutes = Math.floor(seconds / 60)
+		const secs = Math.floor(seconds % 60)
+		if (minutes == 0 && secs == 0) {
+			return ''
+		}
+		return `${minutes} 分 ${secs} 秒`
+	}
+	// 异步方法:选择文件时触发 ============
+	const handleFileChange = async (file) => {
+		fileMd5.value = ''
+		successfulChunkPercents.value = 0
+		// const reader = new FileReader()
+		//   const spark = new SparkMD5.ArrayBuffer()
+		// console.log("读取文件",file.raw)
+		// reader.readAsArrayBuffer(file.raw) // 异步读取文件内容
+
+		// const fileContent = await new Promise((resolve, reject) => {
+		//     reader.onload = (event) => {
+		//         console.log('开始读取文件了...', event, reader.result)
+		//          spark.append(event.target.result)
+		//         resolve(spark.end())
+		//     }
+		//     reader.onerror = (error) => {
+		//         console.log('读取文件时发生错误...', error)
+		//         reject(error)
+		//     }
+		//     reader.readAsArrayBuffer(file)
+		// })
+		// console.log('111文件的md5哈希值是 fileContent:', fileContent)
+		// 计算MD5哈希值
+		fileMd5.value = await calculateFileMD5(file.raw)
+		// fileMd5.value = md5(fileContent)
+		console.log('111文件的md5哈希值是:', fileMd5.value)
+
+		console.log('file对象的大小:', file.size)
+		console.log('file对象:', file)
+		chunkCount.value = Math.ceil(file.size / chunkSize.value) // 还是计算分块数量,向上取整
+		chunksUploaded.value = 0 // 已上传的分片数量
+		fileSuffix.value = '.' + file.raw.name.split('.').pop() // 得到.文件类型
+		successfulChunkPercents.value = 0 // 上传成功的分片百分比
+		currentFile.value = await new Promise((resolve, reject) => {
+			console.log('====开始获取File对象了...')
+			resolve(file.raw)
+			reject('获取File对象失败')
+		})
+
+		// for (let i = 0; i < chunkCount.value; i++) {
+		//     // 文件开始遍历切片存入数组
+		//     const start = i * chunkSize.value
+		//     console.log('循环中的currentFile:', currentFile.value)
+		//     const end = Math.min(
+		//         start + chunkSize.value - 1,
+		//         currentFile.value.size - 1
+		//     )
+		//     chunkList.value[i] = currentFile.value.slice(start, end)
+		// }
+		splitFileByChunkSize(currentFile.value, chunkSize.value)
+
+		console.log('md5:', fileMd5.value)
+		console.log('chunkList:', chunkList.value)
+		console.log('fileSuffix:', fileSuffix.value)
+		console.log('currentFile:', currentFile.value)
+		uploadFileList.value.push({
+			name: file.raw.name,
+			size: currentFile.value.size,
+			md5: fileMd5.value, // md5哈希值
+			chunks: chunkList.value, // 分块列表
+			fileSuffix: fileSuffix.value, // 后缀
+			percents: 0,
+			time: ''
+		}) // 使用对象数组进行处理 ============
+		chunkList.value = []
+		console.log('uploadFileList是:', uploadFileList.value)
+		console.log('结束handleFileChange了')
+	}
+
+	const splitFileByChunkSize = (file, chunkSizeValue) => {
+		const fileSize = file.size
+		const chunkCount = Math.ceil(fileSize / chunkSizeValue) // 计算总分块数
+		// console.log(
+		//     '统计:',
+		//     '块数',
+		//     chunkCount,
+		//     '文件大小',
+		//     fileSize,
+		//     ' 单块 ',
+		//     chunkSizeValue
+		// )
+		for (let i = 0; i < chunkCount; i++) {
+			const start = i * chunkSizeValue
+			const min = start + chunkSizeValue
+
+			console.log('奇怪:', ' start ', start, ' chunkSizeValue ', chunkSizeValue, ' min ', min)
+			const end = Math.min(min, fileSize)
+			// console.log(
+			//     '统计:',
+			//     ' start ',
+			//     start,
+			//     ' end ',
+			//     end,
+			//     ' 单块 ',
+			//     chunkSizeValue,
+			//     ' 比较谁大 ',
+			//     start + chunkSizeValue,
+			//     ' ssss ',
+			//     fileSize
+			// )
+			chunkList.value[i] = file.slice(start, end) // 注意:slice 是 [start, end) 前闭后开区间
+
+			console.log(
+				'准备开始:',
+				' 循环次数 ',
+				i,
+				' 块数 ',
+				chunkCount,
+				'开始的大小',
+				start,
+				'结束的大小',
+				end,
+				' file ',
+				file.slice(start, end)
+			)
+		}
+
+		console.log('分片完成:', chunkList.value)
+	}
+
+	// 点击上传按钮触发多文件上传 ===============
+	const uploadFilesList = async () => {
+		if (upLoadTag.value == true) {
+			message.loading('正在上传')
+			return
+		}
+		upLoadTag.value = true
+
+		emit('onUpLoading', upLoadTag.value)
+		if (currentFile.value == null) {
+			// alert('请选择文件后再上传!')
+			message.error('请选择文件后再上传!')
+			successfulChunkPercents.value = 0 // 重置百分比
+			fileList.value = [] // 文件列表
+			return
+		}
+		// 检查所有文件中是否存在未上传的,未上传则需要上传对应的文件 =========
+		let md5List = []
+		console.log('准备上传', uploadFileList.value)
+		for (let i = 0; i < uploadFileList.value.length; i++) {
+			let element = {
+				// md5: uploadFileList.value[i].md5,
+				md5: uploadFileList.value[i].md5,
+				chunkSize: uploadFileList.value[i].chunks.length,
+				fileName: uploadFileList.value[i].name,
+				fileSuffix: uploadFileList.value[i].fileSuffix
+			}
+			md5List.push(element)
+		}
+		console.log('上传的md5_suffix_List是:', md5List)
+
+		await axios
+			.post('/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 item1 of uploadFileList.value) {
+						for (let item2 of list) {
+							console.log('item回来的', JSON.stringify(item2))
+							if (item1.md5 === item2.md5) {
+								//重要的步骤
+								item1.userFileId = item2.userFileId
+								if (item1.userFileId) {
+									item1.percents = 100
+								}
+								upList.push(item1)
+							}
+						}
+					}
+					console.log('upList是:', upList)
+					uploadFileList.value = upList
+				} else {
+					clearFileList()
+				}
+				console.log('最后必须上传的文件:', uploadFileList.value)
+				// 文件均存在minio中了,无需上传
+				// if (uploadFileList.value.length === 0) {
+				// 	successfulChunkPercents.value = 100
+				// 	alert('文件上传成功')
+				// }
+			})
+			.catch((error) => {
+				console.log('检查返回错误', error)
+			})
+		// return
+		console.log('开始上传', uploadFileList.value)
+		// 检查上传的多个文件是否均存在,如果部分存在,进行剔除,剩余部分仍旧进行上传。
+		// 分块的Promises化
+		const chunkPromises = []
+		// 上传分块的数组,校验是否完成上传
+		const chunksUploadedList = []
+		// 直接计算一共多少个分块
+		uploadFileList.value.forEach((item) => {
+			allChunks.value += item.chunks.length
+		})
+
+		console.log('所有文件加起来一共有多少个分块?', allChunks.value, uploadFileList.value)
+		for (const item of uploadFileList.value) {
+			const md5 = item.md5
+			uploadChunks.value = 0
+
+			if (item.userFileId == undefined || item.userFileId == null) {
+				startTime.value = new Date().getTime() // 开始时间戳(毫秒
+				totalSize.value = item.size // 文件总大小
+				uploadedSize.value = 0
+				for (let i = 0; i < item.chunks.length; i++) {
+					console.log('上传第', i + 1, '个分片')
+					let chunk = item.chunks[i]
+					console.log('去上传...', i + 1, chunk)
+					chunkPromises.push(
+						await uploadFilesChunk(
+							{
+								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)
+								uploadedSize.value += chunk.size // 更新已上传大小
+								const remainingTime = estimateRemainingTime()
+								console.log(`预计剩余时间: ${formatTime(remainingTime)}`)
+								item.percents = (100 * (uploadChunks.value / item.chunks.length)).toFixed(2)
+								item.time = formatTime(remainingTime)
+								console.log(
+									'执行了自增...',
+									'分片长度',
+									item.chunks.length,
+									'执行到多少了',
+									uploadChunks.value,
+									'百分比',
+									100 * (uploadChunks.value / item.chunks.length).toFixed(2)
+								)
+								console.log('this.uploadChunks:', uploadChunks.value)
+								console.log('this.allChunks:', allChunks.value)
+								console.log('进度:', (uploadChunks.value / allChunks.value).toFixed(2))
+							}
+						)
+					)
+				}
+			}
+
+			console.log('this.chunkUploaded是:', chunksUploaded.value)
+			chunksUploadedList.push(chunksUploaded.value) // 存储不同文件的上传分块数量
+			chunksUploaded.value = 0
+		}
+		await Promise.all(chunkPromises)
+		console.log('上传完成!')
+		console.log('chunksUploadList是:', chunksUploadedList)
+		let mergeResults = []
+		for (let i = 0; i < chunksUploadedList.length; i++) {
+			console.log('this.uploadFileList' + i + '是:' + uploadFileList.value[i].chunks.length)
+			console.log('chunksUploadedList' + i + '是:' + chunksUploadedList[i])
+			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]}`
+					`/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}`,
+					null,
+					{ headers: { Token: tool.data.get('TOKEN') } }
+				)
+				// if (mergeResult.data.startsWith('[miss_chunk]')) {
+				//     alert('文件缺失,请重新上传')
+				//     return
+				// }
+				console.log('合并结果1:', mergeResult)
+				uploadFileList.value[i].userFileId = mergeResult.data.userFileId
+				// mergeResults.push(mergeResult)
+			}
+		}
+		console.log('合并结果2:', uploadFileList.value)
+		upLoadTag.value = false
+		emit('onUpLoading', upLoadTag.value)
+		let finalRes = true
+		emit('onSuccess', uploadFileList.value)
+		// for (const result of mergeResults) {
+		// 	if (result.data === '失败') {
+		// 		finalRes = false
+		// 		alert('上传失败!请重新上传')
+		// 		return
+		// 	} else {
+		// 		alert('上传成功!')
+		// 		successfulChunkPercents.value = 100
+		// 		clearFileList()
+		// 		// getList()
+		// 		return
+		// 	}
+		// }
+	}
+
+	// 多文件上传分片 ============
+	const uploadFilesChunk = async (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)
+		return axios
+			.post('/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 uploadChunk(data, onSuccess)
+				}
+			})
+	}
+
+	// 上传分片 旧
+	const 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)
+		return axios
+			.post('/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 uploadChunk(data, onSuccess)
+				}
+			})
+	}
+
+	// 删除文件
+	const deleteFile = (url) => {
+		axios
+			.get(`/api/webapp/disk/delete?url=` + url)
+			.then((res) => {
+				console.log('删除文件:', res.data)
+				alert(res.data ? '删除成功!' : '删除失败!')
+				// getList()
+			})
+			.catch((error) => {
+				console.log('删除失败:', error)
+			})
+		console.log('url是:', url)
+	}
+
+	const downloadFile = (url) => {
+		window.location.href = url
+	}
+	const beforeUpload = (file) => {
+		handleFileChange({ raw: file })
+		return false // 阻止默认上传
+	}
+	const handleChange = (info) => {
+		const { file } = info
+		if (file.status === 'removed') {
+			fileList.value = []
+			clearFileList()
+		}
+	}
+
+	const customRequest = () => {}
+	const getList = () => {
+		axios
+			.get('http://127.0.0.1:9000/api/webapp/disk/minio/list')
+			.then((res) => {
+				this.uploadList = res.data
+				console.log('获取列表结果成功:', res.data)
+			})
+			.catch((error) => {
+				console.error('获取列表失败:', error)
+			})
+	}
+
+	const clearFileList = () => {
+		successfulChunkPercents.value = 0
+		uploadFileList.value = []
+		fileList.value = []
+	}
+
+	onMounted(() => {
+		// getList()
+	})
+</script>
+
+<style scoped></style>

+ 5 - 0
src/router/portal.js

@@ -67,6 +67,11 @@ const portal = [
 				name: 'portal.courseAdd',
 				path: '/portal/courseAdd',
 				component: () => import('@/views/courseAdd/index.vue')
+			},
+			{
+				name: 'portal.courseManagement',
+				path: '/portal/courseManagement',
+				component: () => import('@/views/courseManagement/index.vue')
 			}
 		]
 	},

+ 4 - 0
src/router/whiteList.js

@@ -47,6 +47,10 @@ const constRouters = [
 		path: '/portal/courseAdd',
 		component: () => import('@/views/courseAdd/index.vue')
 	},
+	{
+		path: '/portal/courseManagement',
+		component: () => import('@/views/courseManagement/index.vue')
+	},
 	{
 		path: '/other',
 		name: 'other',

+ 22 - 0
src/views/courseManagement/components/DialogView.vue

@@ -0,0 +1,22 @@
+<template>
+	<div>
+		<a-modal v-model:visible="visible" title="上传" @ok="handleOk">
+			<UpLoadBreakPoint></UpLoadBreakPoint>
+		</a-modal>
+	</div>
+</template>
+<script setup>
+	import { ref } from 'vue'
+	import UpLoadBreakPoint from '@/components/UpLoadBreakPoint/index.vue'
+
+	const visible = ref(false)
+	const open = () => {
+		visible.value = true
+		console.log('对话框打开', visible.value)
+	}
+	const handleOk = (e) => {
+		// console.logckPoint.value = false
+	}
+
+	defineExpose({ open })
+</script>

+ 180 - 0
src/views/courseManagement/components/ListView.vue

@@ -0,0 +1,180 @@
+<template>
+	<a-table
+		ref="table"
+		:columns="columns"
+		:data-source="dataSource"
+		:row-key="(record) => record.collegeId"
+		bordered
+		:expand-row-by-click="true"
+		:pagination="false"
+		size="small"
+	>
+		<template #bodyCell="{ column, text }">
+			<template v-if="column.dataIndex === 'publishTime'">{{ formatTimestamp(text) }}</template>
+			<template v-if="column.dataIndex === 'action'">
+				<a-button size="small" @click="handleDetail(record)" style="margin-right: 5px">详情</a-button>
+				<a-button size="small" @click="handleEdit(record)" style="margin-right: 5px">编辑</a-button>
+				<a-button size="small" @click="handleShelf(record)" style="margin-right: 5px">上架</a-button>
+				<a-button size="small" @click="handleDelete(record)" style="margin-right: 5px">删除</a-button>
+			</template>
+		</template>
+	</a-table>
+	<div style="display: flex; width: 100%; justify-content: flex-end; margin-top: 10px">
+		<a-pagination
+			v-model:current="pagination.current"
+			v-model:pageSize="pagination.size"
+			:total="pagination.total"
+			show-less-items
+			@change="handlerChange"
+		/>
+	</div>
+</template>
+
+<script setup>
+	import tool from '@/utils/tool'
+	import { ref, onMounted } from 'vue'
+	import { EyeOutlined, EditOutlined, SnippetsOutlined, DeleteOutlined } from '@ant-design/icons-vue'
+	import { list } from '@/api/courseinfo'
+	import { useRouter } from 'vue-router'
+	import collegeApi from '@/api/college'
+
+	const router = useRouter()
+	//发布按钮状态
+	const releaseVisible = ref(false)
+	const loading = ref(false) // 列表loading
+	const dataSource = ref([])
+	const formState = ref({
+		name: '',
+		loacl: ''
+	}) // 列表loading
+	const columns = [
+		{
+			title: '课程名称',
+			dataIndex: 'courseName',
+			sorter: true,
+			width: '20%'
+		},
+		{
+			title: '状态',
+			dataIndex: 'courseType',
+			sorter: true,
+			width: '10%'
+		},
+		{
+			title: '课程类型',
+			dataIndex: 'isCreaterName',
+			sorter: true,
+			width: '20%'
+		},
+		{
+			title: '课时数量',
+			dataIndex: 'majorId',
+			sorter: true,
+			width: '10%'
+		},
+		{
+			title: '更新时间',
+			dataIndex: 'publishTime',
+			sorter: true,
+			width: '20%'
+		},
+		{
+			title: '操作',
+			dataIndex: 'action',
+			sorter: true,
+			width: '20%'
+		}
+	]
+	// tool.formatTimestamp()
+
+	const formatTimestamp = (time) => {
+		return tool.formatTimestamp(time)
+	}
+	const pagination = reactive({
+		size: 10,
+		current: 1,
+		total: 0
+	})
+	const onChangeCurrent = (current) => {
+		router.push({
+			path: '/' + current
+		})
+	}
+	const handlerChange = (page, pageSize) => {
+		pagination.size = pageSize
+		pagination.current = page
+
+		getList()
+	}
+	const publishedData = ref()
+	//发布确定
+
+	// 上传资源模态框
+	const uploadModalVisible = ref(false)
+	// 详情按钮点击事件
+	const handleDetail = (record) => {
+		console.log('查看详情', record)
+		// 在这里添加查看详情的逻辑
+	}
+
+	// 编辑按钮点击事件
+	const handleEdit = (record) => {
+		console.log('编辑记录', record)
+		// 在这里添加编辑记录的逻辑
+	}
+
+	// 上架按钮点击事件
+	const handleShelf = (record) => {
+		console.log('上架记录', record)
+		// 在这里添加上架记录的逻辑
+	}
+
+	// 删除按钮点击事件
+	const handleDelete = (record) => {
+		console.log('删除记录', record)
+		// 在这里添加删除记录的逻辑
+	}
+	const getList = () => {
+		console.log('获取列表', list)
+
+		list({ ...pagination }).then((data) => {
+			if (data.code == 200) {
+				dataSource.value = data.data.records
+				pagination.current = data.data.current
+				pagination.size = data.data.size
+				pagination.total = data.data.total
+			}
+			// data.records
+		})
+	}
+	const setList = (search) => {
+		console.log('获取列表', list)
+		formState.value = search
+		pagination.current = 1
+		list({ ...pagination, ...search }).then((data) => {
+			if (data.code == 200) {
+				dataSource.value = data.data.records
+				pagination.current = data.data.current
+				pagination.size = data.data.size
+				pagination.total = data.data.total
+			}
+			// data.records
+		})
+	}
+
+	// 重置按钮点击事件
+	onMounted(() => {
+		// getListData()
+		getList()
+	})
+
+	defineExpose({
+		setList
+	})
+</script>
+
+<style scoped>
+	.desc p {
+		margin-bottom: 1em;
+	}
+</style>

+ 147 - 0
src/views/courseManagement/components/QueryView.vue

@@ -0,0 +1,147 @@
+<template>
+	<div style="display: flex; justify-content: space-between; align-items: center">
+		<div>
+			<a-form layout="inline" :model="formState">
+				<a-form-item label="" style="width: 200px">
+					<a-input v-model:value="formState.courseName" placeholder="请输入课程名称" allowClear />
+				</a-form-item>
+				<a-form-item label="" style="width: 200px">
+					<a-cascader
+						v-model:value="formState.loacl"
+						:options="options"
+						placeholder="选择院校/专业"
+						change-on-select
+						allowClear
+						:field-names="{ label: 'name', value: 'id', children: 'children' }"
+					/>
+				</a-form-item>
+				<a-form-item label="" style="width: 200px">
+					<a-input v-model:value="formState.type" placeholder="选择课程类型" allowClear />
+				</a-form-item>
+				<a-form-item label="" style="width: 300px">
+					<a-range-picker allowClear />
+				</a-form-item>
+			</a-form>
+		</div>
+		<div>
+			<a-button type="primary" @click="handleSearch">
+				<template #icon><SearchOutlined /></template>
+				查询
+			</a-button>
+			<a-button style="margin-left: 10px" @click="handleReset">
+				<template #icon><ReloadOutlined /></template>
+				重置
+			</a-button>
+		</div>
+	</div>
+</template>
+
+<script setup>
+	import { ref, onMounted } from 'vue'
+	import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue'
+	import tool from '@/utils/tool'
+	import { useRouter } from 'vue-router'
+	import collegeApi from '@/api/college'
+
+	const emit = defineEmits([])
+	const router = useRouter()
+	//发布按钮状态
+	const releaseVisible = ref(false)
+	const loading = ref(false) // 列表loading
+
+	const formState = ref({
+		courseName: '',
+		collegeId: '',
+		majorId: '',
+		courseType: '',
+		loacl: []
+	}) // 列表loading
+
+	const options = ref([
+		// {
+		// 	value: 'zhejiang',
+		// 	label: 'Zhejiang',
+		// 	isLeaf: false
+		// },
+		// {
+		// 	value: 'jiangsu',
+		// 	label: 'Jiangsu',
+		// 	isLeaf: false
+		// }
+	])
+
+	// 搜索值
+	const searchValue = ref('')
+
+	const pagination = reactive({
+		pageSize: 10,
+		pageNum: 1,
+		total: 0
+	})
+	const onChangeCurrent = (current) => {
+		router.push({
+			path: '/' + current
+		})
+	}
+	const publishedData = ref()
+	//发布确定
+
+	// 上传资源模态框
+	const uploadModalVisible = ref(false)
+
+	const loadData = (selectedOptions) => {
+		const targetOption = selectedOptions[selectedOptions.length - 1]
+		targetOption.loading = true
+
+		// load options lazily
+		setTimeout(() => {
+			targetOption.loading = false
+			targetOption.children = [
+				{
+					label: `${targetOption.label} Dynamic 1`,
+					value: 'dynamic1'
+				},
+				{
+					label: `${targetOption.label} Dynamic 2`,
+					value: 'dynamic2'
+				}
+			]
+			options.value = [...options.value]
+		}, 1000)
+	}
+	const getList = () => {
+		collegeApi.treeAll().then((data) => {
+			options.value = data
+		})
+	}
+	const handleSearch = () => {
+		console.log('执行查询操作', formState.value)
+		// 在这里添加查询逻辑
+
+		emit('handlerSearch', formState.value)
+	}
+
+	// 重置按钮点击事件
+	const handleReset = () => {
+		formState.value = {
+			courseName: '',
+			collegeId: '',
+			majorId: '',
+			courseType: '',
+			loacl: []
+			// 其他需要重置的字段
+		}
+		emit('handlerSearch', formState.value)
+	}
+	onMounted(() => {
+		// getListData()
+
+		getList()
+	})
+</script>
+
+<style scoped>
+	.desc p {
+		margin-bottom: 1em;
+	}
+</style>

+ 82 - 0
src/views/courseManagement/index.vue

@@ -0,0 +1,82 @@
+<template>
+	<div style="overflow-y: auto">
+		<a-layout>
+			<Header @onChangeCurrent="onChangeCurrent" />
+			<div style="width: 71%; margin-left: 10%">
+				<QueryView style="margin-top: 10px" @handlerSearch="handlerSearch"></QueryView>
+				<!-- 新建课程按钮 -->
+				<a-button style="margin-top: 10px" type="primary" @click="handleNewCourse">
+					<template #icon>
+						<PlusOutlined />
+					</template>
+					新建课程
+				</a-button>
+				<div style="height: 10px"></div>
+				<ListView ref="listViewRef" style="margin-top: 10px"></ListView>
+			</div>
+		</a-layout>
+		<Footer />
+		<DialogView ref="dialogViewRef"></DialogView>
+	</div>
+</template>
+
+<script setup>
+	import { ref, onMounted } from 'vue'
+	import { PlusOutlined } from '@ant-design/icons-vue'
+	import tool from '@/utils/tool'
+	import Header from '@/views/portal/components/Header.vue'
+	import Footer from '@/views/portal/components/Footer.vue'
+	import QueryView from './components/QueryView.vue'
+	import ListView from './components/ListView.vue'
+	import DialogView from './components/DialogView.vue'
+	import { useRouter } from 'vue-router'
+	const router = useRouter()
+	//发布按钮状态
+	const releaseVisible = ref(false)
+	const loading = ref(false) // 列表loading
+
+	const isState = ref(0) // 列表loading
+	const listViewRef = ref(null)
+	const dialogViewRef = ref(null)
+
+	// 搜索值
+	const searchValue = ref('')
+	const open = ref(false)
+
+	const handleNewCourse = () => {
+		console.log('新建课程111')
+		// 在这里添加新建课程的逻辑
+		dialogViewRef.value.open()
+	}
+	const handlerSearch = (data) => {
+		console.log('新建课程')
+		// 在这里添加新建课程的逻辑
+		listViewRef.value.setList(data)
+	}
+
+	const pagination = reactive({
+		pageSize: 10,
+		pageNum: 1,
+		total: 0
+	})
+	const onChangeCurrent = (current) => {
+		router.push({
+			path: '/' + current
+		})
+	}
+	const publishedData = ref()
+	//发布确定
+
+	// 上传资源模态框
+	const uploadModalVisible = ref(false)
+
+	onMounted(() => {
+		// getListData()
+	})
+</script>
+
+<style scoped>
+	.desc p {
+		margin-bottom: 1em;
+	}
+</style>

+ 11 - 1
src/views/myResources/resourceUpload.vue

@@ -124,7 +124,8 @@
 		</a-form>
 		<template v-if="isState == 0">
 			<!-- 资源上传 -->
-			<UploadModal @success="uploadSuccess"></UploadModal>
+			<!-- <UploadModal @success="uploadSuccess"></UploadModal> -->
+			<UpLoadBreakPoint @onSuccess="onSuccess"></UpLoadBreakPoint>
 		</template>
 	</a-modal>
 </template>
@@ -136,6 +137,7 @@
 	import userSelection from './userSelection.vue'
 	import UploadModal from './UploadModal.vue'
 	import coverUpload from './coverUpload/index.vue'
+	import UpLoadBreakPoint from '@/components/UpLoadBreakPoint/index.vue'
 	import { useMyResourceStore } from '@/store/myResource'
 	const myResourceStore = useMyResourceStore()
 	const { proxy } = getCurrentInstance()
@@ -224,6 +226,14 @@
 		// 	console.error('部分请求失败:', err)
 		// }
 	}
+	const onSuccess = (uploadFileList) => {
+		let list = []
+		for (let i = 0; i < uploadFileList.length; i++) {
+			list.push(uploadFileList[i].userFileId)
+		}
+		formState.userfileIds = list.join(',')
+	}
+
 	// 自定义校验函数示例
 	const validateKeywords = (rule, value, callback) => {
 		if (value.length < 2) {

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

@@ -13,6 +13,9 @@
 						>个人资源</a-menu-item
 					>
 					<a-menu-item style="margin-left: 10px; margin-right: 10px" key="portal/courseCenter">课程中心</a-menu-item>
+					<a-menu-item style="margin-left: 10px; margin-right: 10px" key="portal/courseManagement"
+						>课程管理</a-menu-item
+					>
 				</a-menu>
 			</div>
 

+ 1 - 1
vite.config.js

@@ -52,7 +52,7 @@ export default defineConfig(({ command, mode }) => {
 			port: envConfig.VITE_PORT,
 			proxy: {
 				'/api': {
-					target: 'http://192.168.31.81:19003',
+					target: 'http://192.168.31.14:9003',
 					ws: false,
 					changeOrigin: true
 					// rewrite: (path) => path.replace(/^\/api/, '')