Przeglądaj źródła

课时回显修改部分

于添 7 miesięcy temu
rodzic
commit
3339f33eff

+ 6 - 0
src/api/courseCenter/courseProduction.js

@@ -15,8 +15,14 @@ export default {
 	edit(data) {
 		return request('disk/chapter/edit', data, 'post')
 	},
+	delete(data) {
+		return request('disk/chapter/delete', data, 'post')
+	},
 	// 章节详情
 	detail(data) {
 		return request('disk/chapter/detail', data, 'get')
+	},
+	allList(data) {
+		return request('disk/chapter/allList', data, 'get')
 	}
 }

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

@@ -15,3 +15,6 @@ export const list = (p) => request('disk/courseinfo/page', p, 'get')
 export const addViewCount = (p) => request('disk/courseauditrecord/addViewCount', p, 'post')
 //详情
 export const detail = (p) => request('disk/courseauditrecord/detail', p, 'get')
+//收藏增加
+//资源列表 排除没权限得条目
+export const courceDownList = (p) => request('disk/courseauditrecord/courceDownList', p, 'get')

+ 12 - 0
src/api/hour/index.js

@@ -0,0 +1,12 @@
+// 文件模块相关接口
+import { moduleRequest } from '@/utils/reSourceRequest'
+
+const request = moduleRequest(`/api/webapp/`)
+
+/**
+ * 获取文件列表相关接口
+ */
+// 获取文件列表(区分文件路径)
+//收藏增加
+export const add = (p) => request('/disk/hour/add', p, 'post')
+export const detail = (p) => request('/disk/hour/detail', p, 'get')

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

@@ -120,6 +120,13 @@
 	const upLoadTag = ref(false) // 文件的md5值
 	const startTime = ref(0) // 开始时间戳(毫秒
 	const totalSize = ref(0) // 开始时间戳(毫秒
+	const props = defineProps({
+		uploadCount: {
+			type: Number,
+			default: () => 10
+		}
+	})
+
 	const handlerRemoveItem = (index) => {
 		const item = uploadFileList.value[index]
 		if (item && item.md5) {
@@ -631,6 +638,10 @@
 	}
 	const beforeUpload = async (file) => {
 		console.log('选择了文件', file)
+		if(uploadFileList.value.length >= props.uploadCount){
+			message.error('超过上传条目' + props.uploadCount + "条")
+			return false
+		}
 		let upFile = await handleFileChange({ raw: file })
 		console.log('可以上传的文件内容是', upFile)
 		// 检查本地 uploadFileList 是否已存在该 md5 文件

+ 205 - 0
src/components/UpLoadDoc/index.vue

@@ -0,0 +1,205 @@
+<template>
+	<div class="cover-upload-row">
+		<a-upload
+			:key="form.id"
+			:show-upload-list="false"
+			:before-upload="beforeUploadDoc"
+			accept=".ppt,.pptx,.doc,.docx,.pdf"
+			@change="handleChange"
+			:action="action"
+			:headers="headers"
+		>
+				<a-button v-if="form.id == ''">
+					<CloudUploadOutlined/>
+					上传文件
+				</a-button>
+				<span v-if="form.id != ''" style="cursor: pointer;">{{form.name}}</span>
+		</a-upload>
+		<span class="upload-tip">支持ppt、word等格式文件上传,文件大小不超过10MB</span>
+	</div>
+
+</template>
+
+<script setup>
+import {ref, reactive, watch, defineProps, defineEmits} from 'vue'
+import {message} from 'ant-design-vue'
+import {PictureOutlined, CloudUploadOutlined} from '@ant-design/icons-vue'
+import tool from "@/utils/tool";
+const action = ref('/api/webapp/dev/file/uploadMinioReturnId')
+const headers = ref({
+	token: tool.data.get('TOKEN')
+})
+//课程类型
+
+
+const props = defineProps({
+	count: Number,
+	default : () => 1
+})
+const emit = defineEmits(['update:visible', 'ok','handlerUpSelect','handlerNewSelect','handlerUpDoc'])
+
+const modalVisible = ref(props.visible)
+watch(
+	() => props.visible,
+	(v) => {
+		modalVisible.value = v
+	}
+)
+watch(modalVisible, (v) => {
+	emit('update:visible', v)
+})
+
+const formRef = ref()
+const file = ref({
+	id : '',
+	name: '',
+})
+const form = reactive({
+
+	id : '',
+	title: '',
+	video: '',
+	coverUrl: '',
+	docUrl: '',
+	srtUrl: '',
+	name : ''
+})
+
+const rules = {
+	title: [{required: true, message: '请输入课时名称'}],
+	video: [{required: true, message: '请选择或上传视频'}],
+	coverUrl: [{required: true, message: '请上传封面'}]
+}
+
+
+const handleChange = (res) => {
+	console.log('上传完毕',res)
+	if (res.file && res.file.response &&res.file.response.code == 200) {
+		message.success('上传成功')
+		form.id =	res.file.response.data
+		emit('handlerUpDoc',form.id)
+	} else {
+		// message.error('上传失败')
+	}
+}
+
+//回显用显示图片
+const setDoc = (src) => {
+	form.coverUrl = src
+}
+const beforeUploadDoc = (file) =>{
+	const isDoc = /\.(ppt|pptx|doc|docx|pdf)$/i.test(file.name)
+	const isLt10M = file.size / 1024 / 1024 < 10
+	if (!isDoc) {
+		message.error('仅支持ppt、word、pdf格式')
+		return false
+	}
+	if (!isLt10M) {
+		message.error('文件不能超过10MB')
+		return false
+	}
+	form.name = file.name
+	return true
+}
+
+
+
+const dummyRequest = ({onSuccess}) => {
+
+}
+const setFile = (fileData) => {
+	console.log("设置了文件",fileData)
+	file.value.id = fileData.id
+	file.value.name = fileData.fileName
+}
+
+
+const getData = (callBack) => {
+	formRef.value.validate().then(() => {
+		callBack({...form})
+	})
+}
+
+const handleCancel = () => {
+	modalVisible.value = false
+}
+
+defineExpose({
+	getData,setFile,setDoc
+})
+</script>
+
+
+<style lang="less" scoped>
+.add-class-hours-modal {
+	.ant-modal-content {
+		border-radius: 10px;
+	}
+
+	.ant-modal-header {
+		border-radius: 10px 10px 0 0;
+	}
+
+	.ant-form-item {
+		margin-bottom: 24px;
+	}
+
+	.video-select-row {
+		display: flex;
+		align-items: center;
+	}
+
+	.cover-upload-row {
+		display: flex;
+		align-items: center;
+
+		.cover-upload-box {
+			width: 120px;
+			height: 120px;
+			background: #f7f8fa;
+			border-radius: 8px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin-right: 24px;
+			border: 1px dashed #d9d9d9;
+			cursor: pointer;
+
+			.cover-img {
+				width: 100%;
+				height: 100%;
+				object-fit: cover;
+				border-radius: 8px;
+			}
+
+			.cover-placeholder {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 100%;
+				height: 100%;
+				color: #bbb;
+				font-size: 32px;
+			}
+		}
+
+		.cover-tip {
+			color: #888;
+			font-size: 13px;
+		}
+	}
+
+	.upload-tip {
+		color: #888;
+		font-size: 13px;
+		margin-left: 12px;
+	}
+
+	.footer-btns {
+		display: flex;
+		justify-content: flex-end;
+		gap: 16px;
+		margin-top: 24px;
+	}
+}
+</style>

+ 222 - 0
src/components/UpLoadImg/index.vue

@@ -0,0 +1,222 @@
+<template>
+	<div class="cover-upload-row">
+		<a-upload
+			:show-upload-list="false"
+			:before-upload="beforeUploadImg"
+			accept=".jpg,.png"
+			:action="action"
+			:headers="headers"
+			@change ="handleChange"
+		>
+			<div class="cover-upload-box">
+				<img v-if="form.coverUrl" :src="form.coverUrl" class="cover-img"/>
+				<div v-else class="cover-placeholder">
+					<PictureOutlined style="font-size: 32px; color: #bbb"/>
+				</div>
+			</div>
+		</a-upload>
+		<div class="cover-tip">支持jpg、png等格式文件上传,文件大小不超过10MB</div>
+	</div>
+
+</template>
+
+<script setup>
+import {ref, reactive, watch, defineProps, defineEmits} from 'vue'
+import {message} from 'ant-design-vue'
+import {PictureOutlined, CloudUploadOutlined} from '@ant-design/icons-vue'
+import tool from "@/utils/tool";
+const action = ref('/api/webapp/dev/file/uploadMinioReturnId')
+const headers = ref({
+	token: tool.data.get('TOKEN')
+})
+//课程类型
+
+
+const props = defineProps({
+	count: Number,
+	default : () => 1
+})
+const emit = defineEmits(['update:visible', 'ok','handlerUpSelect','handlerNewSelect','handlerUpImage'])
+
+const modalVisible = ref(props.visible)
+watch(
+	() => props.visible,
+	(v) => {
+		modalVisible.value = v
+	}
+)
+watch(modalVisible, (v) => {
+	emit('update:visible', v)
+})
+
+const formRef = ref()
+const file = ref({
+	id : '',
+	name: '',
+})
+const form = reactive({
+
+	id : '',
+	title: '',
+	video: '',
+	coverUrl: '',
+	docUrl: '',
+	srtUrl: ''
+})
+
+const rules = {
+	title: [{required: true, message: '请输入课时名称'}],
+	video: [{required: true, message: '请选择或上传视频'}],
+	coverUrl: [{required: true, message: '请上传封面'}]
+}
+
+// mock视频资源
+const videoList = ref([
+	{id: 'v1', name: '示例视频1.mp4'},
+	{id: 'v2', name: '示例视频2.mp4'}
+])
+
+const beforeUploadImg = (file)=> {
+	const isImg = file.type === 'image/jpeg' || file.type === 'image/png'
+	const isLt10M = file.size / 1024 / 1024 < 10
+	if (!isImg) {
+		message.error('只能上传jpg/png图片')
+		return false
+	}
+	if (!isLt10M) {
+		message.error('图片不能超过10MB')
+		return false
+	}
+	// mock上传
+	const reader = new FileReader()
+	reader.onload = (e) => {
+		form.coverUrl = e.target.result
+	}
+	reader.readAsDataURL(file)
+	return true
+}
+
+//回显用显示图片
+const setImage = (src) => {
+	form.coverUrl = src
+}
+
+
+const dummyRequest = ({ file, onSuccess, onError }) => {
+	console.log('走了吗',onSuccess)
+	setTimeout(() => {
+		onSuccess('ok')
+	}, 500)
+}
+const handleChange = (res) => {
+	console.log('上传图片',res)
+	if (res.file && res.file.response &&res.file.response.code == 200) {
+		message.success('上传成功')
+		form.id =	res.file.response.data
+		emit('handlerUpImage',form.id)
+	} else {
+		// message.error('上传失败')
+	}
+}
+const setFile = (fileData) => {
+	console.log("设置了文件",fileData)
+	file.value.id = fileData.id
+	file.value.name = fileData.fileName
+}
+
+const handleOk = () =>{
+	formRef.value.validate().then(() => {
+		emit('ok', {...form})
+		modalVisible.value = false
+	})
+}
+
+const getData = (callBack) => {
+	formRef.value.validate().then(() => {
+		callBack({...form})
+	})
+}
+
+const handleCancel = () => {
+	modalVisible.value = false
+}
+
+defineExpose({
+	getData,setFile,setImage
+})
+</script>
+
+
+<style lang="less" scoped>
+.add-class-hours-modal {
+	.ant-modal-content {
+		border-radius: 10px;
+	}
+
+	.ant-modal-header {
+		border-radius: 10px 10px 0 0;
+	}
+
+	.ant-form-item {
+		margin-bottom: 24px;
+	}
+
+	.video-select-row {
+		display: flex;
+		align-items: center;
+	}
+
+	.cover-upload-row {
+		display: flex;
+		align-items: center;
+
+		.cover-upload-box {
+			width: 120px;
+			height: 120px;
+			background: #f7f8fa;
+			border-radius: 8px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin-right: 24px;
+			border: 1px dashed #d9d9d9;
+			cursor: pointer;
+
+			.cover-img {
+				width: 100%;
+				height: 100%;
+				object-fit: cover;
+				border-radius: 8px;
+			}
+
+			.cover-placeholder {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 100%;
+				height: 100%;
+				color: #bbb;
+				font-size: 32px;
+			}
+		}
+
+		.cover-tip {
+			color: #888;
+			font-size: 13px;
+		}
+	}
+
+	.upload-tip {
+		color: #888;
+		font-size: 13px;
+		margin-left: 12px;
+	}
+
+	.footer-btns {
+		display: flex;
+		justify-content: flex-end;
+		gap: 16px;
+		margin-top: 24px;
+	}
+}
+</style>

+ 212 - 0
src/components/UpLoadSrt/index.vue

@@ -0,0 +1,212 @@
+<template>
+	<div class="cover-upload-row">
+		<a-upload
+			:show-upload-list="false"
+			:before-upload="beforeUploadSrt"
+			accept=".srt"
+			@change="handleChange"
+			:action="action"
+			:headers="headers"
+		>
+			<a-button v-if="form.id ==''">
+				<CloudUploadOutlined/>
+				上传文件
+			</a-button>
+			<span v-if="form.id !=''" style="cursor: pointer;">{{form.name}}</span>
+		</a-upload>
+		<span class="upload-tip">仅支持srt格式文件上传,文件大小不超过1MB</span>
+	</div>
+
+</template>
+
+<script setup>
+import {ref, reactive, watch, defineProps, defineEmits} from 'vue'
+import {message} from 'ant-design-vue'
+import {PictureOutlined, CloudUploadOutlined} from '@ant-design/icons-vue'
+import tool from "@/utils/tool";
+const action = ref('/api/webapp/dev/file/uploadMinioReturnId')
+const headers = ref({
+	token: tool.data.get('TOKEN')
+})
+//课程类型
+
+
+const props = defineProps({
+	count: Number,
+	default : () => 1
+})
+const emit = defineEmits(['update:visible', 'ok','handlerUpSelect','handlerNewSelect','handlerUpSrt'])
+
+const modalVisible = ref(props.visible)
+watch(
+	() => props.visible,
+	(v) => {
+		modalVisible.value = v
+	}
+)
+watch(modalVisible, (v) => {
+	emit('update:visible', v)
+})
+
+const formRef = ref()
+const file = ref({
+	id : '',
+	name: '',
+})
+const form = reactive({
+
+	id : '',
+	title: '',
+	video: '',
+	coverUrl: '',
+	docUrl: '',
+	srtUrl: '',
+	name : ''
+})
+
+const rules = {
+	title: [{required: true, message: '请输入课时名称'}],
+	video: [{required: true, message: '请选择或上传视频'}],
+	coverUrl: [{required: true, message: '请上传封面'}]
+}
+
+// mock视频资源
+const videoList = ref([
+	{id: 'v1', name: '示例视频1.mp4'},
+	{id: 'v2', name: '示例视频2.mp4'}
+])
+
+const beforeUploadSrt = (file) => {
+	const isSrt = file.name.endsWith('.srt')
+	const isLt1M = file.size / 1024 / 1024 < 1
+	if (!isSrt) {
+		message.error('仅支持srt格式')
+		return false
+	}
+	if (!isLt1M) {
+		message.error('文件不能超过1MB')
+		return false
+	}
+	form.name = file.name
+	return true
+}
+//回显用显示图片
+const setImage = (src) => {
+	form.coverUrl = src
+}
+
+
+const dummyRequest = ({onSuccess}) => {
+}
+const handleChange = (res) => {
+	console.log('上传完毕',res)
+	if (res.file && res.file.response &&res.file.response.code == 200) {
+		message.success('上传成功')
+		form.id =	res.file.response.data
+		emit('handlerUpSrt',form.id)
+	} else {
+		// message.error('上传失败')
+	}
+}
+const setFile = (fileData) => {
+	console.log("设置了文件",fileData)
+	file.value.id = fileData.id
+	file.value.name = fileData.fileName
+}
+
+const handleOk = () =>{
+	formRef.value.validate().then(() => {
+		emit('ok', {...form})
+		modalVisible.value = false
+	})
+}
+
+const getData = (callBack) => {
+	formRef.value.validate().then(() => {
+		callBack({...form})
+	})
+}
+
+const handleCancel = () => {
+	modalVisible.value = false
+}
+
+defineExpose({
+	getData,setFile,setImage
+})
+</script>
+
+
+<style lang="less" scoped>
+.add-class-hours-modal {
+	.ant-modal-content {
+		border-radius: 10px;
+	}
+
+	.ant-modal-header {
+		border-radius: 10px 10px 0 0;
+	}
+
+	.ant-form-item {
+		margin-bottom: 24px;
+	}
+
+	.video-select-row {
+		display: flex;
+		align-items: center;
+	}
+
+	.cover-upload-row {
+		display: flex;
+		align-items: center;
+
+		.cover-upload-box {
+			width: 120px;
+			height: 120px;
+			background: #f7f8fa;
+			border-radius: 8px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin-right: 24px;
+			border: 1px dashed #d9d9d9;
+			cursor: pointer;
+
+			.cover-img {
+				width: 100%;
+				height: 100%;
+				object-fit: cover;
+				border-radius: 8px;
+			}
+
+			.cover-placeholder {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 100%;
+				height: 100%;
+				color: #bbb;
+				font-size: 32px;
+			}
+		}
+
+		.cover-tip {
+			color: #888;
+			font-size: 13px;
+		}
+	}
+
+	.upload-tip {
+		color: #888;
+		font-size: 13px;
+		margin-left: 12px;
+	}
+
+	.footer-btns {
+		display: flex;
+		justify-content: flex-end;
+		gap: 16px;
+		margin-top: 24px;
+	}
+}
+</style>

+ 2 - 2
src/components/XnWorkflow/nodes/addNode.vue

@@ -1,5 +1,5 @@
-<template>
-	<div class="add-node-btn-box">
+	<div class="add-node-btn-<template>
+box">
 		<div class="add-node-btn">
 			<a-popover v-model:visible="visible" placement="rightTop" trigger="click" :width="270">
 				<template #content>

+ 3 - 3
src/views/courseAdd/components/courseInfo.vue

@@ -147,9 +147,8 @@
 			.then((res) => {
 				console.log(res.data, '表单添加')
 				courseInfoId.value = res.data.courseId
-				//表单添加成功保存ID
-				localStorage.setItem('courseInfoId', res.data.courseId)
-				emit('nextStep')
+				// localStorage.setItem('courseInfoId', res.data.courseId)
+				emit('nextStep',res.data.courseId)
 			})
 			.catch((err) => {
 				console.log(err)
@@ -163,6 +162,7 @@
 			.edit({ ...formState, courseId: courseInfoId.value })
 			.then((res) => {
 				console.log(res.data, '表单编辑')
+				emit('nextStep',courseInfoId.value)
 			})
 			.catch((err) => {
 				console.log(err)

+ 186 - 0
src/views/courseAdd/components/courseProduction/ListView.vue

@@ -0,0 +1,186 @@
+<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, record }">
+			<template v-if="column.dataIndex === 'publishTime'">{{ formatTimestamp(text) }}</template>
+			<template v-if="column.dataIndex === 'action'">
+				<a-popover v-model:visible="popoverVisible[record.iiiiiindex]" title="提示" trigger="click">
+					<template #content>
+						<a-button style="margin-right: 10px" type="primary" @click="handleOk(record)">选择
+						</a-button>
+						<a-button @click="()=>{	popoverVisible[record.iiiiiindex] = false}">取消</a-button>
+					</template>
+					<a-button size="small" style="margin-right: 5px">选择</a-button>
+				</a-popover>
+
+			</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 {courceDownList} from '@/api/courseinfo'
+import {useRouter} from 'vue-router'
+import collegeApi from '@/api/college'
+
+const router = useRouter()
+const popoverVisible = ref({})
+const emit = defineEmits(['handleSelectFile'])
+//发布按钮状态
+const releaseVisible = ref(false)
+const loading = ref(false) // 列表loading
+const dataSource = ref([])
+const formState = ref({
+	name: '',
+	loacl: ''
+}) // 列表loading
+const columns = [
+	{
+		title: '资源名称',
+		dataIndex: 'fileName',
+		sorter: true,
+		width: '20%'
+	},
+	{
+		title: '状态',
+		dataIndex: 'verifyStatusName',
+		sorter: true,
+		width: '10%'
+	},
+	{
+		title: '更新时间',
+		dataIndex: 'uploadTime',
+		sorter: true,
+		width: '20%'
+	},
+	{
+		title: '操作',
+		dataIndex: 'action',
+		width: '5%'
+	}
+]
+// 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 handleOk = (item) => {
+	popoverVisible.value[item.iiiiiindex] = false
+	console.log('选择了什么', item)
+	emit('handleSelectFile', item)
+
+}
+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)
+	// 在这里添加编辑记录的逻辑
+
+	emit('handleEdit', record)
+}
+
+// 上架按钮点击事件
+const handleShelf = (record) => {
+	console.log('上架记录', record)
+	// 在这里添加上架记录的逻辑
+}
+
+// 删除按钮点击事件
+const handleDelete = (record) => {
+	console.log('删除记录', record)
+	// 在这里添加删除记录的逻辑
+}
+const getList = () => {
+
+	courceDownList({...pagination}).then((data) => {
+		if (data.code == 200) {
+			let list = []
+			for (let i = 0; i < data.data.records.length; i++) {
+				let item =  data.data.records[i]
+				item.iiiiiindex = i
+				list.push(item)
+			}
+			dataSource.value = list
+			pagination.current = data.data.current
+			pagination.size = data.data.size
+			pagination.total = data.data.total
+		}
+		// data.records
+	})
+}
+const setList = (search) => {
+	formState.value = search
+	pagination.current = 1
+	courceDownList({...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>

+ 124 - 0
src/views/courseAdd/components/courseProduction/QueryView.vue

@@ -0,0 +1,124 @@
+<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.fileName" placeholder="请输入资源名称" 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({
+		fileName: '',
+	}) // 列表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 = {
+			fileName: '',
+			// 其他需要重置的字段
+		}
+		emit('handlerSearch', formState.value)
+	}
+	onMounted(() => {
+		// getListData()
+
+		getList()
+	})
+</script>
+
+<style scoped>
+	.desc p {
+		margin-bottom: 1em;
+	}
+</style>

+ 698 - 0
src/views/courseAdd/components/courseProduction/UploadModal.vue

@@ -0,0 +1,698 @@
+<template>
+	<!-- 上传组件 -->
+	<uploader
+		class="uploader-app"
+		ref="uploaderRef"
+		:options="options"
+		:autoStart="false"
+		:fileStatusText="fileStatusText"
+		@files-added="handleFilesAdded"
+		@file-success="handleFileSuccess"
+		@file-error="handleFileError"
+		@dragleave="hideUploadMask"
+		@file-progress="handleFileProgress"
+	>
+		<uploader-unsupport></uploader-unsupport>
+		<!-- 选择按钮 在这里隐藏 -->
+		<uploader-btn class="select-file-btn" :attrs="attrs" ref="uploadBtn"> 选择文件 </uploader-btn>
+		<uploader-btn class="select-file-btn" :attrs="attrs" :directory="true" ref="uploadDirBtn"> 选择目录 </uploader-btn>
+
+		<!-- 拖拽上传 -->
+		<uploader-drop class="drop-box" id="dropBox" @paste="handlePaste" @click="handleUpload">
+			<span class="text"> 点击上传或将文件拖拽至此区域上传 </span>
+			<p class="text">按住Ctrl可同时多选,支持上传PPT/excel/pdf/mp4/zip/rar,等单个文件不能超过2G</p>
+			<UploadOutlined class="upload-icon" v-show="pasteImg.src" @click="handleUploadPasteImg" />
+		</uploader-drop>
+
+		<!-- 上传列表 -->
+		<uploader-list v-show="true">
+			<template #default="props">
+				<div class="file-panel">
+					<div class="file-title">
+						<span class="title-span">
+							上传列表 <span class="count">({{ props.fileList.length }})</span>
+						</span>
+					</div>
+
+					<transition name="collapse">
+						<ul class="file-list" v-show="!collapse">
+							<li
+								v-for="file in props.fileList"
+								:key="file.id"
+								class="file-item"
+								:class="{ 'custom-status-item': file.statusStr !== '' }"
+							>
+								<uploader-file ref="fileItem" :file="file" :list="true" />
+								<!-- 上传完成显示状态 -->
+								<template v-if="file.status">
+									<span class="file-status">
+										{{ fileStatusText[file.status] }}
+									</span>
+								</template>
+							</li>
+							<div class="no-file" v-if="!props.fileList.length"><FileExclamationOutlined /> 暂无待上传文件</div>
+						</ul>
+					</transition>
+				</div>
+			</template>
+		</uploader-list>
+	</uploader>
+</template>
+
+<script setup>
+	import { ref, reactive, computed, nextTick, getCurrentInstance } from 'vue'
+	import { message } from 'ant-design-vue'
+	import { useMyResourceStore } from '@/store/myResource'
+	import SparkMD5 from 'spark-md5'
+	import tool from '@/utils/tool'
+	const props = defineProps({
+		visible: {
+			type: Boolean,
+			default: true
+		}
+	})
+
+	const emit = defineEmits(['update:visible', 'success'])
+
+	const { proxy } = getCurrentInstance()
+	const store = useMyResourceStore()
+
+	// refs
+	const formRef = ref(null)
+	const uploaderRef = ref(null)
+	const uploadBtn = ref(null)
+	const uploadDirBtn = ref(null)
+	const fileItem = ref(null)
+
+	// 表单数据
+	const formState = reactive({
+		title: '',
+		description: '',
+		category: undefined,
+		fileIds: []
+	})
+
+	const rules = {
+		title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+		category: [{ required: true, message: '请选择分类', trigger: 'change' }]
+	}
+
+	watch(
+		() => props.fileList,
+		(val) => {
+			console.log('参数都有什么啊', val)
+		}
+	)
+
+	// 上传相关数据
+	const options = ref({
+		target: `${proxy.$RESOURCE_CONFIG.baseContext}/resourceFile/uploadfile`,
+		chunkSize: 1024 * 1024,
+		fileParameterName: 'file',
+		maxChunkRetries: 3,
+		simultaneousUploads: 5, // 控制并发上传数
+		testChunks: true,
+		checkChunkUploadedByResponse: (chunk, message) => {
+			let objMessage = JSON.parse(message)
+			if (objMessage.success) {
+				let data = objMessage.data
+				if (data.skipUpload) {
+					return true
+				}
+				return (data.uploaded || []).indexOf(chunk.offset + 1) >= 0
+			} else {
+				return true
+			}
+		},
+		headers: {
+			token: tool.data.get('TOKEN')
+		},
+		query: () => {}
+	})
+
+	const fileStatusText = ref({
+		success: '上传成功',
+		error: '上传失败',
+		uploading: '上传中',
+		paused: '暂停中',
+		waiting: '等待中'
+	})
+
+	const attrs = ref({
+		accept: '.doc,.docx,.ppt,.pptx,.xls,.xlsx,.mkv,.mp4,.wmv,.avi,.flv,.mpeg,.mpg,.rmvb,.mov'
+		// accept: '*'
+	})
+
+	const dropBoxShow = ref(false)
+	const pasteImg = ref({
+		src: '',
+		name: ''
+	})
+	const pasteImgObj = ref(null)
+	const filesLength = ref(0)
+	const uploadStatus = ref({})
+	const submitting = ref(false)
+	const canClose = ref(true)
+
+	// 计算属性
+	const uploaderInstance = computed(() => {
+		return uploaderRef.value?.uploader
+	})
+
+	const remainderStorageValue = computed(() => {
+		return store.remainderStorageValue
+	})
+
+	// 方法
+	const hideUploadMask = (e) => {
+		e.stopPropagation()
+		e.preventDefault()
+		dropBoxShow.value = false
+	}
+
+	const handleUpload = () => {
+		if (uploadBtn.value?.$el) {
+			uploadBtn.value.$el.click()
+		}
+	}
+
+	const handleUploadDir = () => {
+		if (uploadDirBtn.value?.$el) {
+			uploadDirBtn.value.$el.click()
+		}
+	}
+
+	const handlePasteUpload = () => {
+		pasteImg.value.src = ''
+		pasteImg.value.name = ''
+		pasteImgObj.value = null
+		dropBoxShow.value = true
+	}
+	const handlePaste = (event) => {
+		let pasteItems = (event.clipboardData || window.clipboardData).items
+		if (pasteItems && pasteItems.length) {
+			let imgObj = pasteItems[0].getAsFile()
+			pasteImgObj.value =
+				imgObj !== null
+					? new File([imgObj], `qiwenshare_${new Date().valueOf()}.${imgObj.name.split('.')[1]}`, { type: imgObj.type })
+					: null
+		} else {
+			message.error('当前浏览器不支持')
+			return false
+		}
+
+		if (!pasteImgObj.value) {
+			message.error('粘贴内容非图片')
+			return false
+		}
+
+		pasteImg.value.name = pasteImgObj.value.name
+
+		let reader = new FileReader()
+		reader.onload = (event) => {
+			pasteImg.value.src = event.target.result
+		}
+		reader.readAsDataURL(pasteImgObj.value)
+	}
+
+	const handleUploadPasteImg = () => {
+		uploaderInstance.value.addFile(pasteImgObj.value)
+	}
+
+	const handleDeletePasteImg = () => {
+		pasteImg.value.src = ''
+		pasteImg.value.name = ''
+		pasteImgObj.value = null
+	}
+
+	const handleFilesAdded = (filesSource) => {
+		console.log('handleFilesAdded', filesSource)
+		const filesTotalSize = filesSource
+			.map((item) => {
+				console.log(item, 'itemitemitemitem')
+				// 为每个文件添加上传速度跟踪属性
+				item.speed = 0
+				item.lastLoaded = item.loaded || 0
+				item.lastTime = Date.now()
+				item.remainingTime = '计算中...'
+				return item
+			})
+			.reduce((pre, next) => {
+				return pre + next.size
+			}, 0)
+		console.log('handleFilesAdded', filesSource)
+		if (remainderStorageValue.value < filesTotalSize) {
+			// 批量选择的文件超出剩余存储空间
+			message.warning(`剩余存储空间不足,请重新选择${filesSource.length > 1 ? '批量' : ''}文件`)
+			filesSource.ignored = true // 本次选择的文件过滤掉
+		} else {
+			filesLength.value += filesSource.length
+			// 手动控制上传队列,每次只处理5个文件
+			const batchSize = 5
+			for (let i = 0; i < filesSource.length; i += batchSize) {
+				const batch = filesSource.slice(i, i + batchSize)
+				batch.forEach((file) => {
+					computeMD5(file)
+				})
+			}
+		}
+	}
+	// 格式化速度显示
+	const formatSpeed = (bytesPerSecond) => {
+		if (bytesPerSecond < 1024) return `${bytesPerSecond.toFixed(1)} B/s`;
+		if (bytesPerSecond < 1024 * 1024) return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
+		return `${(bytesPerSecond / (1024 * 1024)).toFixed(1)} MB/s`;
+	}
+
+	// 格式化时间显示
+	const formatTime = (seconds) => {
+		if (seconds < 60) return `${Math.ceil(seconds)}秒`;
+		const minutes = Math.floor(seconds / 60);
+		const secs = Math.ceil(seconds % 60);
+		return `${minutes}分${secs}秒`;
+	}
+	const handleFileSuccess = (rootFile, file, response) => {
+		if (response === '') {
+			uploadStatus.value[file.id] = '上传失败'
+			return
+		}
+
+		const result = JSON.parse(response)
+		if (result.success) {
+			file.statusStr = ''
+			// 将上传成功的文件ID添加到表单数据中
+			formState.fileIds.push(result.data.userFileId)
+			emit('success', formState.fileIds)
+		} else {
+			message.error(result.msg)
+			uploadStatus.value[file.id] = '上传失败'
+		}
+		filesLength.value--
+		// 所有文件上传完成后,允许关闭弹窗
+		canClose.value = filesLength.value === 0
+		// 当一个文件完成时,检查是否有待上传文件
+		if (uploaderInstance.value.files.some((f) => !f.isComplete)) {
+			uploaderInstance.value.upload()
+		}
+		console.log(formState.fileIds, response, result, 'formState.fileIdsformState.fileIdsformState.fileIds')
+	}
+
+	const handleFileError = (rootFile, file, response) => {
+		message.error(response)
+		filesLength.value--
+		canClose.value = filesLength.value === 0
+	}
+	// 在 handleFileSuccess 前添加
+	const handleFileProgress = (rootFile, file, chunk) => {
+		// 每500ms更新一次显示
+		if (Date.now() - file.lastTime > 500) {
+			file.calculateRemainingTime?.()
+		}
+	}
+
+	const computeMD5 = (file) => {
+		let fileReader = new FileReader()
+		let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
+		let currentChunk = 0
+		const chunkSize = 1024 * 1024
+		let chunks = Math.ceil(file.size / chunkSize)
+		let spark = new SparkMD5.ArrayBuffer()
+
+		file.statusStr = '计算MD5'
+		file.pause()
+
+		const loadNext = () => {
+			let start = currentChunk * chunkSize
+			let end = start + chunkSize >= file.size ? file.size : start + chunkSize
+			fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
+		}
+
+		fileReader.onload = (e) => {
+			spark.append(e.target.result)
+			if (currentChunk < chunks) {
+				currentChunk++
+				loadNext()
+				file.statusStr = `校验MD5 ${((currentChunk / chunks) * 100).toFixed(0)}%`
+			} else {
+				let md5 = spark.end()
+				calculateFileMD5End(md5, file)
+			}
+		}
+
+		fileReader.onerror = () => {
+			message.error({
+				content: `文件${file.name}读取出错,请检查该文件`,
+				duration: 2
+			})
+			file.cancel()
+			filesLength.value--
+			canClose.value = filesLength.value === 0
+		}
+
+		loadNext()
+	}
+	const uploadFileParams = computed(() => {
+		return {
+			filePath: '/',
+			isDir: 0,
+			funcType: 0
+		}
+	})
+	const calculateFileMD5End = (md5, file) => {
+		Object.assign(uploaderInstance.value.opts, {
+			query: uploadFileParams.value
+		})
+		file.uniqueIdentifier = md5
+		file.resume()
+		file.statusStr = ''
+		// 文件开始上传时,禁止关闭弹窗
+		// canClose.value = false
+	}
+
+	const handleCancel = () => {
+		if (!canClose.value) {
+			message.warning('文件正在上传中,请等待上传完成后再关闭')
+			return
+		}
+		// 重置表单和上传状态
+		formRef.value?.resetFields()
+		formState.fileIds = []
+		if (uploaderInstance.value) {
+			uploaderInstance.value.cancel()
+		}
+		emit('update:visible', false)
+	}
+
+	const handleSubmit = () => {
+		formRef.value
+			.validate()
+			.then(() => {
+				if (formState.fileIds.length === 0) {
+					message.warning('请上传文件')
+					return
+				}
+
+				if (filesLength.value > 0) {
+					message.warning('文件正在上传中,请等待上传完成后再提交')
+					return
+				}
+
+				submitting.value = true
+
+				// 模拟提交数据
+				setTimeout(() => {
+					message.success('提交成功')
+					submitting.value = false
+					emit('success', formState)
+					// 重置表单和上传状态
+					formRef.value?.resetFields()
+					formState.fileIds = []
+					emit('update:visible', false)
+				}, 1000)
+			})
+			.catch(() => {
+				// 表单验证失败
+			})
+	}
+
+	// 暴露方法给父组件
+	defineExpose({
+		handleUpload
+	})
+</script>
+
+<style lang="less" scoped>
+	@import '@/style/myResource/varibles.less';
+
+	.upload-btn-wrapper {
+		display: flex;
+		align-items: center;
+	}
+
+	.uploader-file {
+		width: 560px;
+	}
+
+	.select-file-btn {
+		display: none;
+	}
+
+	.drop-box {
+		position: relative;
+		width: 100%;
+		height: 200px;
+		border: 2px dashed #e9e9e9;
+		border-radius: 6px;
+		background-color: #fafafa;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		margin-top: 10px;
+
+		.text {
+			font-size: 14px;
+			color: #999;
+		}
+
+		.paste-img-wrapper {
+			width: 100%;
+			height: 100%;
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+
+			.paste-name {
+				font-size: 14px;
+				margin-bottom: 10px;
+			}
+
+			.paste-img {
+				max-width: 80%;
+				max-height: 120px;
+			}
+		}
+
+		.upload-icon,
+		.delete-icon {
+			position: absolute;
+			bottom: 10px;
+			font-size: 20px;
+			cursor: pointer;
+		}
+
+		.upload-icon {
+			right: 40px;
+			color: #1890ff;
+		}
+
+		.delete-icon {
+			right: 10px;
+			color: #ff4d4f;
+		}
+
+		.close-icon {
+			position: absolute;
+			top: 10px;
+			right: 10px;
+			font-size: 16px;
+			cursor: pointer;
+			color: #999;
+		}
+	}
+
+	.uploader-app {
+		width: 560px;
+	}
+
+	.file-panel {
+		width: 100%;
+		margin-top: 15px;
+		border: 1px solid #e9e9e9;
+		border-radius: 4px;
+		overflow: hidden;
+
+		.file-title {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 8px 12px;
+			background-color: #f5f5f5;
+			border-bottom: 1px solid #e9e9e9;
+
+			.title-span {
+				font-size: 14px;
+				font-weight: 500;
+
+				.count {
+					color: #999;
+					font-weight: normal;
+				}
+			}
+		}
+
+		.file-list {
+			position: relative;
+			height: 240px;
+			overflow-x: hidden;
+			overflow-y: auto;
+			background-color: #fff;
+			font-size: 12px;
+			list-style: none;
+
+			&::-webkit-scrollbar {
+				width: 6px;
+			}
+
+			&::-webkit-scrollbar-thumb {
+				background: @scrollbar-thumb-color;
+				border-radius: 4px;
+			}
+
+			&::-webkit-scrollbar-track {
+				background: @scrollbar-track-color;
+			}
+
+			.file-item {
+				position: relative;
+				background-color: #fff;
+
+				:deep(.uploader-file) {
+					height: 40px;
+					line-height: 40px;
+					.progress-info,
+					.file-status {
+						position: absolute;
+						right: 30px;
+						top: 50%;
+						transform: translateY(-50%);
+						font-size: 12px;
+						color: #666;
+					}
+
+					:deep(.uploader-file) {
+						.uploader-file-status {
+							display: none; // 隐藏默认状态显示
+						}
+					}
+				}
+
+				:deep(.uploader-file[status='success']) {
+					.uploader-file-progress {
+						border: none;
+					}
+				}
+			}
+
+			.file-item.custom-status-item {
+				:deep(.uploader-file-status) {
+					visibility: hidden;
+				}
+
+				.custom-status {
+					position: absolute;
+					top: 0;
+					right: 10%;
+					width: 24%;
+					height: 40px;
+					line-height: 40px;
+				}
+			}
+			.no-file {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%, -50%);
+				font-size: 14px;
+			}
+
+			:deep(.uploader-file-icon) {
+				display: none;
+			}
+
+			:deep(.uploader-file-actions > span) {
+				margin-right: 6px;
+			}
+		}
+	}
+
+	.collapse-enter-active,
+	.collapse-leave-active {
+		transition: all 0.3s;
+		max-height: 300px;
+		overflow: hidden;
+	}
+
+	.collapse-enter-from,
+	.collapse-leave-to {
+		max-height: 0;
+	}
+
+	.upload-area {
+		border: 2px dashed #3ca9f5;
+		padding: 40px;
+		text-align: center;
+	}
+
+	.upload-area p {
+		margin: 10px 0;
+	}
+
+	.file-item {
+		display: flex;
+		align-items: center;
+		margin: 10px 0;
+	}
+
+	.file-item .ant-progress {
+		flex: 1;
+		margin: 0 10px;
+	}
+
+	/* 新增表单样式 */
+	.ant-form-item {
+		margin-bottom: 16px;
+	}
+
+	.public-status-buttons {
+		display: flex;
+	}
+
+	.status-button {
+		padding: 5px 10px;
+		/* margin-right: 10px; */
+		border: 1px solid #ccc;
+		/* border-radius: 3px; */
+		cursor: pointer;
+		background-color: #fff;
+	}
+
+	.status-button.active {
+		background-color: #40a9ff;
+		color: #fff;
+		border-color: #40a9ff;
+	}
+
+	.upload-area {
+		border: 2px dashed #3ca9f5;
+		padding: 40px;
+		text-align: center;
+		transition: border-color 0.3s;
+		/* 平滑过渡效果 */
+	}
+
+	.upload-area.drag-over {
+		border-color: #1890ff;
+		background-color: rgba(24, 144, 255, 0.05);
+	}
+
+	.remaining-time {
+		position: absolute;
+		right: 120px;
+		top: 50%;
+		transform: translateY(-50%);
+		color: #666;
+		font-size: 12px;
+	}
+</style>

+ 271 - 225
src/views/courseAdd/components/courseProduction/addClassHours.vue

@@ -1,256 +1,302 @@
 <template>
-	<a-modal
-		v-model:visible="modalVisible"
-		title="添加课时"
-		:footer="null"
-		width="700px"
-		@cancel="handleCancel"
-		class="add-class-hours-modal"
-	>
-		<a-form :model="form" :rules="rules" ref="formRef" layout="vertical">
-			<a-form-item label="课时名称:" name="title" required>
-				<a-input v-model:value="form.title" placeholder="输入内容" />
-			</a-form-item>
-			<a-form-item label="选择视频:" required>
-				<div class="video-select-row">
-					<a-select v-model:value="form.video" style="width: 220px; margin-right: 12px" placeholder="选择已有资源">
-						<a-select-option v-for="item in videoList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
-					</a-select>
-					<a-upload :show-upload-list="false" :before-upload="beforeUploadVideo" :custom-request="dummyRequest">
-						<a-button type="primary">上传新资源</a-button>
-					</a-upload>
-				</div>
-			</a-form-item>
-			<a-form-item label="上传封面:" required>
-				<div class="cover-upload-row">
-					<a-upload
-						:show-upload-list="false"
-						:before-upload="beforeUploadImg"
-						:custom-request="dummyRequest"
-						accept=".jpg,.png"
-					>
-						<div class="cover-upload-box">
-							<img v-if="form.coverUrl" :src="form.coverUrl" class="cover-img" />
-							<div v-else class="cover-placeholder">
-								<PictureOutlined style="font-size: 32px; color: #bbb" />
-							</div>
-						</div>
-					</a-upload>
-					<div class="cover-tip">支持jpg、png等格式文件上传,文件大小不超过10MB</div>
-				</div>
-			</a-form-item>
-			<a-form-item label="上传讲义:">
-				<a-upload
-					:show-upload-list="false"
-					:before-upload="beforeUploadDoc"
-					:custom-request="dummyRequest"
-					accept=".ppt,.pptx,.doc,.docx,.pdf"
-				>
-					<a-button><CloudUploadOutlined /> 上传文件</a-button>
-				</a-upload>
-				<span class="upload-tip">支持ppt、word等格式文件上传,文件大小不超过10MB</span>
-			</a-form-item>
-			<a-form-item label="上传字幕:">
-				<a-upload
-					:show-upload-list="false"
-					:before-upload="beforeUploadSrt"
-					:custom-request="dummyRequest"
-					accept=".srt"
-				>
-					<a-button><CloudUploadOutlined /> 上传文件</a-button>
-				</a-upload>
-				<span class="upload-tip">仅支持srt格式文件上传,文件大小不超过1MB</span>
-			</a-form-item>
-			<div class="footer-btns">
-				<a-button @click="handleCancel">取消</a-button>
-				<a-button type="primary" @click="handleOk">确定</a-button>
+	<a-form :model="form" :rules="rules" ref="formRef" layout="vertical">
+		<a-form-item label="课时名称:" name="title" required>
+			<a-input v-model:value="form.title" placeholder="输入内容"/>
+		</a-form-item>
+		<a-form-item label="选择视频:" required>
+			<div class="video-select-row">
+				<a-button type="primary" @click="()=>{emit('handlerSelect')}">选择资源</a-button>
+				<a-button type="primary" @click="()=>{emit('handlerUpSelect')}">新上传资源</a-button>
 			</div>
-		</a-form>
-	</a-modal>
+			<div style="margin-top: 12px;  margin-bottom: 12px">{{ file.name }}</div>
+		</a-form-item>
+		<a-form-item label="上传封面:" required>
+			<div class="cover-upload-row">
+				<UpLoadImg ref="upLoadImgRef" @handlerUpImage="handlerUpImage"></UpLoadImg>
+			</div>
+		</a-form-item>
+		<a-form-item label="上传讲义:">
+			<UpLoadDoc ref="upLoadDocRef" @handlerUpDoc="handlerUpDoc"></UpLoadDoc>
+		</a-form-item>
+		<a-form-item label="上传字幕:">
+			<UpLoadSrt ref="UpLoadSrtRef" @handlerUpSrt="handlerUpSrt"></UpLoadSrt>
+		</a-form-item>
+
+	</a-form>
 </template>
 
 <script setup>
-	import { ref, reactive, watch, defineProps } from 'vue'
-	import { message } from 'ant-design-vue'
-	import { PictureOutlined, CloudUploadOutlined } from '@ant-design/icons-vue'
+import {ref, reactive, watch, defineProps, defineEmits} from 'vue'
+import {message} from 'ant-design-vue'
+import {PictureOutlined, CloudUploadOutlined} from '@ant-design/icons-vue'
+import UpLoadImg from '@/components/UpLoadImg/index.vue'
+import UpLoadDoc from '@/components/UpLoadDoc/index.vue'
+import UpLoadSrt from '@/components/UpLoadSrt/index.vue'
+import {add, detail} from '@/api/hour/index'
 
-	const props = defineProps({
-		visible: Boolean
-	})
-	const emit = defineEmits(['update:visible', 'ok'])
+const props = defineProps({
+	visible: Boolean
+})
+const emit = defineEmits(['update:visible', 'ok', 'handlerUpSelect', 'handlerNewSelect'])
 
-	const modalVisible = ref(props.visible)
-	watch(
-		() => props.visible,
-		(v) => {
-			modalVisible.value = v
-		}
-	)
-	watch(modalVisible, (v) => {
-		emit('update:visible', v)
+const modalVisible = ref(props.visible)
+watch(
+	() => props.visible,
+	(v) => {
+		modalVisible.value = v
+	}
+)
+watch(modalVisible, (v) => {
+	emit('update:visible', v)
+})
+
+const upLoadImgRef = ref(null)
+const modeTag = ref('add')
+const formRef = ref()
+const file = ref({
+	id: '',
+	name: '',
+})
+const form = reactive({
+	title: '',
+	video: '',
+	coverUrl: '',
+	docUrl: '',
+	srtUrl: ''
+})
+
+const rules = {
+	title: [{required: true, message: '请输入课时名称'}],
+	video: [{required: true, message: '请选择或上传视频'}],
+	coverUrl: [{required: true, message: '请上传封面'}]
+}
+
+// mock视频资源
+const videoList = ref([
+	{id: 'v1', name: '示例视频1.mp4'},
+	{id: 'v2', name: '示例视频2.mp4'}
+])
+
+
+const handlerUpImage = (id) => {
+	form.coverUrl = id
+}
+const handlerUpDoc = (id) => {
+	form.docUrl = id
+}
+const handlerUpSrt = (id) => {
+	form.srtUrl = id
+}
+
+const setFile = (fileData) => {
+	console.log("设置了文件", fileData)
+	file.value.id = fileData.id
+	file.value.name = fileData.fileName
+
+	form.video = fileData.id
+}
+
+const handleOk = () => {
+	formRef.value.validate().then(() => {
+		emit('ok', {...form})
+		modalVisible.value = false
 	})
+}
+
+const getData = (callBack) => {
+	formRef.value.validate().then(() => {
+		let data = {}
+
+		data.name = form.title
+		data.courseRelates = []
+		//todo :1视频资源,2讲义,3字幕,4作业,5测验
+		data.courseRelates.push({funcType: 1, relateId: form.video})
+		data.courseRelates.push({funcType: 2, relateId: form.docUrl})
+		data.courseRelates.push({funcType: 3, relateId: form.srtUrl})
+
+		// {
+		// 	"chapterId": "voluptate nostrud",
+		// 	"name": "蒋霞",
+		// 	"remark": "amet ut",
+		// 	"courseRelates": [
+		// 	{
+		// 		"funcType": "anim dolore",
+		// 		"relateId": "veniam enim"
+		// 	},
+		// 	{
+		// 		"funcType": "cillum consequat",
+		// 		"relateId": "et pariatur laborum nostrud"
+		// 	}
+		// ]
+		// }
+
 
-	const formRef = ref()
-	const form = reactive({
-		title: '',
-		video: '',
-		coverUrl: '',
-		docUrl: '',
-		srtUrl: ''
+		callBack(data)
 	})
+}
 
-	const rules = {
-		title: [{ required: true, message: '请输入课时名称' }],
-		video: [{ required: true, message: '请选择或上传视频' }],
-		coverUrl: [{ required: true, message: '请上传封面' }]
-	}
+const handleCancel = () => {
+	modalVisible.value = false
+}
+const edit = (item) => {
+	// form.value.id = data.id
+	modeTag.value = 'edit'
 
-	// mock视频资源
-	const videoList = ref([
-		{ id: 'v1', name: '示例视频1.mp4' },
-		{ id: 'v2', name: '示例视频2.mp4' }
-	])
-
-	function beforeUploadImg(file) {
-		const isImg = file.type === 'image/jpeg' || file.type === 'image/png'
-		const isLt10M = file.size / 1024 / 1024 < 10
-		if (!isImg) {
-			message.error('只能上传jpg/png图片')
-			return false
-		}
-		if (!isLt10M) {
-			message.error('图片不能超过10MB')
-			return false
-		}
-		// mock上传
-		const reader = new FileReader()
-		reader.onload = (e) => {
-			form.coverUrl = e.target.result
-		}
-		reader.readAsDataURL(file)
-		return false
-	}
-	function beforeUploadVideo(file) {
-		// 这里只做类型和大小校验,mock上传
-		const isVideo = file.type.startsWith('video/')
-		const isLt500M = file.size / 1024 / 1024 < 500
-		if (!isVideo) {
-			message.error('只能上传视频文件')
-			return false
-		}
-		if (!isLt500M) {
-			message.error('视频不能超过500MB')
-			return false
-		}
-		// mock添加到下拉
-		videoList.value.push({ id: 'mock_' + Date.now(), name: file.name })
-		form.video = videoList.value[videoList.value.length - 1].id
-		return false
-	}
-	function beforeUploadDoc(file) {
-		const isDoc = /\.(ppt|pptx|doc|docx|pdf)$/i.test(file.name)
-		const isLt10M = file.size / 1024 / 1024 < 10
-		if (!isDoc) {
-			message.error('仅支持ppt、word、pdf格式')
-			return false
-		}
-		if (!isLt10M) {
-			message.error('文件不能超过10MB')
-			return false
-		}
-		form.docUrl = file.name
-		return false
-	}
-	function beforeUploadSrt(file) {
-		const isSrt = file.name.endsWith('.srt')
-		const isLt1M = file.size / 1024 / 1024 < 1
-		if (!isSrt) {
-			message.error('仅支持srt格式')
-			return false
-		}
-		if (!isLt1M) {
-			message.error('文件不能超过1MB')
-			return false
+	detail({id : item.id}).then((res)=>{
+		console.log('需要展示修改了123123', res)
+		if(res.code == 200){
+		     let data =	res.data
+			form.title = 	data.name
+			for (let i = 0; i <data.courseRelates.length ; i++) {
+			  let itemi =data.courseRelates[i]
+				switch (itemi.funcType) {
+					case '1':
+						form.video = itemi.relateId
+						break;
+					case '2':
+						form.docUrl = itemi.relateId
+						break;
+					case '3':
+						form.srtUrl = itemi.relateId
+						break;
+				}
+			}
 		}
-		form.srtUrl = file.name
-		return false
+
+		// {
+		// 	"code": 200,
+		// 	"msg": "操作成功",
+		// 	"data": {
+		// 	"chapterId": "1945821914740051969",
+		// 		"courseRelates": [
+		// 		{
+		// 			"relateId": "1941793498696044545",
+		// 			"chapterHourType": "1",
+		// 			"name": "1940384168492494848",
+		// 			"mainId": "1946118995975536642",
+		// 			"url": "upload/20250705/3705fd26117d7e6653b13d60e8e0399d.mp4",
+		// 			"funcType": "1"
+		// 		},
+		// 		{
+		// 			"relateId": "1946118974324539394",
+		// 			"chapterHourType": "1",
+		// 			"name": "",
+		// 			"mainId": "1946118995975536642",
+		// 			"url": "",
+		// 			"funcType": "2"
+		// 		},
+		// 		{
+		// 			"relateId": "1946118987192664066",
+		// 			"chapterHourType": "1",
+		// 			"name": "",
+		// 			"mainId": "1946118995975536642",
+		// 			"url": "",
+		// 			"funcType": "3"
+		// 		}
+		// 	],
+		// 		"name": "110",
+		// 		"remark": "",
+		// 		"id": "1946118995975536642"
+		// }
+		// }
+	}).catch((err) => {
+
+	})
+
+	// chapterId:"1945821914740051969"
+	// createTime:"2025-07-18 16:05:00.444"
+	// createUserName:"超管"
+	// id:"1946118995975536642"
+	// name:"110"
+	// remark:""
+	// let data = {}
+	//
+	// data.name = form.title
+	// data.courseRelates = []
+	// //todo :1视频资源,2讲义,3字幕,4作业,5测验
+	// data.courseRelates.push({funcType : 1,relateId:form.video})
+	// data.courseRelates.push({funcType : 2,relateId:form.docUrl})
+	// data.courseRelates.push({funcType : 3,relateId:form.srtUrl})
+
+
+
+}
+defineExpose({
+	getData, setFile, edit
+})
+
+</script>
+
+<style lang="less" scoped>
+.add-class-hours-modal {
+	.ant-modal-content {
+		border-radius: 10px;
 	}
-	function dummyRequest({ onSuccess }) {
-		setTimeout(() => {
-			onSuccess && onSuccess()
-		}, 500)
+
+	.ant-modal-header {
+		border-radius: 10px 10px 0 0;
 	}
-	function handleOk() {
-		formRef.value.validate().then(() => {
-			emit('ok', { ...form })
-			modalVisible.value = false
-		})
+
+	.ant-form-item {
+		margin-bottom: 24px;
 	}
-	function handleCancel() {
-		modalVisible.value = false
+
+	.video-select-row {
+		display: flex;
+		align-items: center;
 	}
-</script>
 
-<style lang="less" scoped>
-	.add-class-hours-modal {
-		.ant-modal-content {
-			border-radius: 10px;
-		}
-		.ant-modal-header {
-			border-radius: 10px 10px 0 0;
-		}
-		.ant-form-item {
-			margin-bottom: 24px;
-		}
-		.video-select-row {
-			display: flex;
-			align-items: center;
-		}
-		.cover-upload-row {
+	.cover-upload-row {
+		display: flex;
+		align-items: center;
+
+		.cover-upload-box {
+			width: 120px;
+			height: 120px;
+			background: #f7f8fa;
+			border-radius: 8px;
 			display: flex;
 			align-items: center;
-			.cover-upload-box {
-				width: 120px;
-				height: 120px;
-				background: #f7f8fa;
+			justify-content: center;
+			margin-right: 24px;
+			border: 1px dashed #d9d9d9;
+			cursor: pointer;
+
+			.cover-img {
+				width: 100%;
+				height: 100%;
+				object-fit: cover;
 				border-radius: 8px;
+			}
+
+			.cover-placeholder {
 				display: flex;
 				align-items: center;
 				justify-content: center;
-				margin-right: 24px;
-				border: 1px dashed #d9d9d9;
-				cursor: pointer;
-				.cover-img {
-					width: 100%;
-					height: 100%;
-					object-fit: cover;
-					border-radius: 8px;
-				}
-				.cover-placeholder {
-					display: flex;
-					align-items: center;
-					justify-content: center;
-					width: 100%;
-					height: 100%;
-					color: #bbb;
-					font-size: 32px;
-				}
-			}
-			.cover-tip {
-				color: #888;
-				font-size: 13px;
+				width: 100%;
+				height: 100%;
+				color: #bbb;
+				font-size: 32px;
 			}
 		}
-		.upload-tip {
+
+		.cover-tip {
 			color: #888;
 			font-size: 13px;
-			margin-left: 12px;
-		}
-		.footer-btns {
-			display: flex;
-			justify-content: flex-end;
-			gap: 16px;
-			margin-top: 24px;
 		}
 	}
+
+	.upload-tip {
+		color: #888;
+		font-size: 13px;
+		margin-left: 12px;
+	}
+
+	.footer-btns {
+		display: flex;
+		justify-content: flex-end;
+		gap: 16px;
+		margin-top: 24px;
+	}
+}
 </style>

+ 201 - 0
src/views/courseAdd/components/courseProduction/addDialog.vue

@@ -0,0 +1,201 @@
+<template>
+	<div>
+		<a-modal
+			v-model:visible="modalVisible"
+			:title="modeTag.value == 'add'?'添加课时':'修改课时'"
+			:footer="null"
+			width="700px"
+			class="add-class-hours-modal"
+		>
+			<a-tabs v-model:activeKey="activeKey" type="card" @change="handleChange">
+				<a-tab-pane key="1" tab="课时">
+					<addClassHours ref="addClassHoursRef" @handlerSelect="handlerSelect" @handlerUpSelect="handlerUpSelect"></addClassHours>
+				</a-tab-pane>
+				<a-tab-pane key="2" tab="作业" >
+					<div>这里是作业的内容</div>
+				</a-tab-pane>
+				<a-tab-pane key="3" tab="考试" >
+					<div>这里是考试的内容</div>
+				</a-tab-pane>
+			</a-tabs>
+			<div class="footer-btns">
+				<a-button @click="handleCancel">取消</a-button>
+				<a-button type="primary" @click="handleOk">确定</a-button>
+			</div>
+		</a-modal>
+
+		<resListDialog ref="resListDialogRef" @handleSelectFile="handleSelectFile"></resListDialog>
+		<resourceUpload ref="resourceUploadRef"></resourceUpload>
+	</div>
+
+</template>
+
+<script setup>
+	import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
+	import { message } from 'ant-design-vue'
+	import addClassHours from './addClassHours.vue'
+	import resListDialog from './resListDialog.vue'
+	import resourceUpload from './resourceUpload.vue'
+	import { add } from '@/api/hour/index'
+
+	const addClassHoursRef = ref(null)
+	const resListDialogRef = ref(null)
+	const resourceUploadRef = ref(null)
+	const activeKey = ref('1')
+	const modeTag = ref('add')
+
+	const emit = defineEmits(['update:visible', 'ok','onAddChapter'])
+
+	const props = defineProps({
+		//课程id
+		courseInfoId: {
+			type: Number,
+			required: true,
+			default: null
+		},
+		// visible: Boolean
+	})
+
+	const form = ref({
+		id : ''
+	})
+
+
+	const modalVisible = ref(false)
+	// watch(
+	// 	() => props.visible,
+	// 	(v) => {
+	// 		modalVisible.value = v
+	// 	}
+	// )
+	// watch(modalVisible, (v) => {
+	// 	emit('update:visible', v)
+	// })
+
+	const open = () =>{
+		activeKey.value = '1'
+		modalVisible.value = true
+		modeTag.value = 'add'
+	}
+	const handleChange = (activeKey) =>{
+
+	}
+	const setData = (data) => {
+		console.log('进来的章节信息',data)
+		form.value.id = data.id
+	}
+	const edit = (item) => {
+		activeKey.value = '1'
+		console.log('进来的章节信息',item)
+		// form.value.id = data.id
+		modalVisible.value = true
+		modeTag.value = 'edit'
+		console.log('有没有',addClassHoursRef.value)
+		nextTick(()=>{
+			addClassHoursRef.value.edit(item)
+		})
+
+	}
+
+	const handlerSelect = () =>{
+		resListDialogRef.value.open()
+	}
+	const handlerUpSelect = () =>{
+		resourceUploadRef.value.open()
+	}
+	const handleSelectFile = (item) =>{
+		resListDialogRef.value.close()
+		addClassHoursRef.value.setFile(item)
+	}
+	const  handleOk = () =>{
+
+		console.log('有没有',addClassHoursRef)
+		addClassHoursRef.value.getData((data)=>{
+			//设置章节id
+			data.chapterId = form.value.id
+			// props.courseInfoId
+			console.log('提交的参数',data)
+
+			add(data).then((res)=>{
+				modalVisible.value = false
+				emit('onAddChapter')
+			}).catch((err) => {
+
+			})
+
+		})
+		// formRef.value.validate().then(() => {
+		// 	emit('ok', { ...form })
+		// 	modalVisible.value = false
+		// })
+	}
+	function handleCancel() {
+		modalVisible.value = false
+	}
+
+	defineExpose({ open ,setData,edit})
+</script>
+
+<style lang="less" scoped>
+	.add-class-hours-modal {
+		.ant-modal-content {
+			border-radius: 10px;
+		}
+		.ant-modal-header {
+			border-radius: 10px 10px 0 0;
+		}
+		.ant-form-item {
+			margin-bottom: 24px;
+		}
+		.video-select-row {
+			display: flex;
+			align-items: center;
+		}
+		.cover-upload-row {
+			display: flex;
+			align-items: center;
+			.cover-upload-box {
+				width: 120px;
+				height: 120px;
+				background: #f7f8fa;
+				border-radius: 8px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				margin-right: 24px;
+				border: 1px dashed #d9d9d9;
+				cursor: pointer;
+				.cover-img {
+					width: 100%;
+					height: 100%;
+					object-fit: cover;
+					border-radius: 8px;
+				}
+				.cover-placeholder {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					width: 100%;
+					height: 100%;
+					color: #bbb;
+					font-size: 32px;
+				}
+			}
+			.cover-tip {
+				color: #888;
+				font-size: 13px;
+			}
+		}
+		.upload-tip {
+			color: #888;
+			font-size: 13px;
+			margin-left: 12px;
+		}
+		.footer-btns {
+			display: flex;
+			justify-content: flex-end;
+			gap: 16px;
+			margin-top: 24px;
+		}
+	}
+</style>

+ 153 - 0
src/views/courseAdd/components/courseProduction/coverUpload/index.vue

@@ -0,0 +1,153 @@
+<template>
+	<div>
+		<div style="margin-bottom: 10px">
+			<!-- <a-button type="primary" @click="chooseFile" :disabled="coverFileList.length > 0 && isedit">选择图片</a-button> -->
+			<a-button
+				type="primary"
+				@click="chooseFile"
+				:disabled="isedit ? coverFileList.length > 0 && isedit : coverFileList.length > 0"
+				>选择图片</a-button
+			>
+			<span style="margin-left: 10px">仅支持上传jpg/png格式文件,单个文件不能超过500kb</span>
+		</div>
+		<a-upload
+			ref="upload"
+			:before-upload="beforeUploadCover"
+			accept=".jpg,.png,.bmp,.jpeg"
+			:remove="handleRemoveCover"
+			:headers="headers"
+			:action="action"
+			:on-change="handleChangeCover"
+			@preview="handlePreview"
+			:file-list="coverFileList"
+		>
+		</a-upload>
+		<div class="preview-area" v-if="previewImageUrl">
+			<a-image
+				:width="200"
+				:src="previewImageUrl"
+				:preview="{
+					src: previewImageUrl
+				}"
+			/>
+		</div>
+	</div>
+</template>
+
+<script setup>
+	import { ref, reactive, watch } from 'vue'
+	import { Modal, Upload, message } from 'ant-design-vue'
+	import tool from '@/utils/tool'
+	const emit = defineEmits(['handleRemoveCover', 'handleChangeCover'])
+	const headers = ref({
+		token: tool.data.get('TOKEN')
+	})
+	import sysConfig from '@/config/index'
+	const props = defineProps({
+		coverImageId: {
+			type: Number,
+			required: true,
+			default: null
+		},
+		imageUrl: {
+			type: String,
+			required: true,
+			default: null
+		},
+		isedit: {
+			type: Boolean,
+			required: true,
+			default: false
+		}
+	})
+
+	const action = ref('/api/webapp/dev/file/uploadMinioReturnId')
+	// 封面文件列表
+	const coverFileList = ref([])
+	const previewImageUrl = ref('')
+	const coverImageId = ref(null)
+	watch(
+		() => props.imageUrl,
+		(newVal) => {
+			if (newVal) {
+				previewImageUrl.value = sysConfig.FILE_URL + newVal
+				coverImageId.value = props.coverImageId
+			} else {
+				previewImageUrl.value = ''
+				coverImageId.value = null
+			}
+		},
+		{ immediate: true } // 立即执行一次
+	)
+	// 上传封面前的钩子函数
+	const beforeUploadCover = (file) => {
+		// 允许的文件扩展名
+		const allowedExtensions = ['.jpg', '.png', '.bmp', '.jpeg']
+		// 获取文件扩展名
+		const fileExtension = file.name.slice(file.name.lastIndexOf('.')).toLowerCase()
+		console.log(file, fileExtension, 'filefilefilefile')
+
+		// 验证文件类型
+		const isFileTypeAllowed = allowedExtensions.includes(fileExtension)
+
+		if (!isFileTypeAllowed) {
+			message.error(`不支持的文件格式: ${fileExtension},请上传以下格式: ${allowedExtensions.join(', ')}`)
+			return false
+		}
+
+		// 验证文件大小(500KB限制)
+		const isLt500K = file.size / 1024 < 500
+		if (!isLt500K) {
+			message.error('文件大小不能超过500KB!')
+			return false
+		}
+
+		return isFileTypeAllowed && isLt500K
+	}
+	// 移除封面文件
+	const handleRemoveCover = (file) => {
+		const index = coverFileList.value.indexOf(file)
+		const newFileList = coverFileList.value.slice()
+		newFileList.splice(index, 1)
+		coverFileList.value = newFileList
+		previewImageUrl.value = ''
+		// 如果移除的是当前封面文件,则清空coverImageId
+		if (coverImageId.value === file.id) {
+			coverImageId.value = null
+			emit('handleRemoveCover')
+		}
+	}
+	// 封面文件状态改变时的处理函数
+	const handleChangeCover = ({ file, fileList: newFileList }) => {
+		if (file.status === 'done') {
+			// 上传成功,获取文件ID
+			const fileId = file.response?.data || file.id
+			console.log('上传成功,获取文件ID', fileId)
+			if (fileId) {
+				coverImageId.value = fileId
+				emit('handleChangeCover', fileId)
+				// 生成预览URL
+				if (file.originFileObj) {
+					previewImageUrl.value = URL.createObjectURL(file.originFileObj)
+				} else if (file.url) {
+					previewImageUrl.value = file.url
+				}
+			}
+		}
+		if (file.status === 'error') {
+			// 延迟移除,确保用户能看到错误提示
+			setTimeout(() => {
+				coverFileList.value = coverFileList.value.filter((f) => f.uid !== file.uid)
+			}, 1500)
+		}
+		coverFileList.value = newFileList
+	}
+
+	// 选择文件按钮点击事件
+	const chooseFile = () => {
+		document.querySelector('.ant-upload input').click()
+	}
+</script>
+
+<style lang="less">
+</style>

+ 278 - 231
src/views/courseAdd/components/courseProduction/index.vue

@@ -8,283 +8,330 @@
 			<!-- 章节标题 -->
 			<div class="chapter-title">
 				<span>{{ chapter.name }}</span>
-				<a-icon type="edit" @click="editChapter(chapterIndex)" />
+				<!--				<a-icon type="edit" @click="editChapter(chapterIndex)" />-->
+				<div>
+					<EditOutlined style="margin-right: 20px" @click="editChapter(chapterIndex)"/>
+					<a-popover v-model:visible="popoverVisible[chapterIndex]" title="提示" trigger="click">
+						<template #content>
+							<a-button style="margin-right: 10px" type="primary" @click="delChapter(chapterIndex)">删除
+							</a-button>
+							<a-button @click="()=>{	popoverVisible[chapterIndex] = false}">取消</a-button>
+						</template>
+						<DeleteOutlined/>
+					</a-popover>
+
+				</div>
+
 			</div>
 
 			<!-- 添加课时按钮 -->
 			<a-button class="add-chapter-btn1" type="primary" size="small" @click="showAddLessonModal(chapterIndex)">
-				<PlusOutlined /> 添加课时</a-button
-			>
+				<PlusOutlined/>
+				添加课时
+			</a-button>
 			<!-- 课时列表 -->
 			<div v-for="(lesson, lessonIndex) in chapter.classHours" :key="lessonIndex" class="lesson">
-				<a-row :gutter="48">
-					<a-col :span="4">
-						<!-- 视频封面 -->
-						<img src="@/assets/images/fileImg/gif.png" alt="Video Cover" class="video-cover" />
-					</a-col>
-					<a-col :span="16">
-						<!-- 课时信息 -->
-						<div class="lesson-info">
-							<div class="lesson-title">{{ lesson.name }}</div>
-							<div class="lesson-details">
-								<span>视频大小:{{ lesson.size }}MB</span>
-								<span>发布时间:{{ lesson.createTime }}</span>
-								<span>发布人:{{ lesson.createUserName }}</span>
-							</div>
-						</div>
-					</a-col>
-					<a-col :span="4">
-						<!-- 编辑和删除按钮 -->
-						<div class="lesson-actions">
-							<EditOutlined class="action-icon" @click="$emit('edit-lesson', lesson)" />
-							<DeleteOutlined class="action-icon" @click="$emit('delete-lesson', lesson)" />
-						</div>
-					</a-col>
-				</a-row>
+				<!-- 视频封面 -->
+				<img  src="@/assets/images/fileImg/gif.png" alt="Video Cover" class="video-cover" style="width: 140px; height: 90px"/>
+				<!-- 课时信息 -->
+				<div style="display: flex; flex-direction: column; justify-content: space-between; height: 100%  ">
+					<div>{{ lesson.name }}</div>
+					<div>
+						<span>视频大小:{{ lesson.size }}MB</span>
+						<span>发布时间:{{ lesson.createTime }}</span>
+						<span>发布人:{{ lesson.createUserName }}</span>
+					</div>
+				</div>
+				<div style="display: flex;  height: 100%;  position: absolute;  right: 15px;  top: 15px">
+					<div>
+						<EditOutlined class="action-icon" @click="handleEdit(lesson)"/>
+						<DeleteOutlined class="action-icon" @click="handleDel(lesson)"/>
+					</div>
+				</div>
 			</div>
 		</div>
 		<!-- 添加章节模态框 -->
-		<a-modal v-model:visible="modalVisible" title="添加章节" @ok="handleOk" @cancel="handleCancel">
+		<a-modal v-model:visible="modalVisible" :title="dialogTitle" @ok="handleOk" @cancel="handleCancel">
 			<a-form :model="formState">
 				<a-form-item label="章节名称">
-					<a-input v-model:value="formState.chapterName" placeholder="请输入章节名称" />
+					<a-input v-model:value="formState.chapterName" placeholder="请输入章节名称"/>
 				</a-form-item>
 			</a-form>
 		</a-modal>
 		<!-- 添加课时模态框 -->
-		<addClassHours v-model:visible="addLessonModalVisible" @ok="onAddClassHoursOk" />
+		<addDialog ref="addDialogRef" @ok="onAddClassHoursOk" @onAddChapter="onAddChapter"/>
 	</div>
 </template>
 
 <script setup>
-	import { ref, reactive, onMounted } from 'vue'
-	import addClassHours from './addClassHours.vue'
-	import courseProductionApi from '@/api/courseCenter/courseProduction.js'
-	const addLessonModalVisible = ref(false)
-	// 章节数据
-	const chapters = ref([
-		{
-			title: '第一章 课程导学',
-			lessons: [
-				{
-					title: '1-1 课程简介',
-					size: 300,
-					publishTime: '2025-07-01 10:23:59',
-					publisher: '张三'
-				},
-				{
-					title: '1-2 课程前瞻',
-					size: 300,
-					publishTime: '2025-07-01 10:23:59',
-					publisher: '张三'
-				}
-			]
-		},
-		{
-			title: '第二章 课程XX',
-			lessons: [
-				{
-					title: '2-1 课时标题',
-					size: 300,
-					publishTime: '2025-07-01 10:23:59',
-					publisher: '张三'
-				},
-				{
-					title: '2-2 课时标题',
-					size: 300,
-					publishTime: '2025-07-01 10:23:59',
-					publisher: '张三'
-				},
-				{
-					title: '2-3 课时标题',
-					size: 300,
-					publishTime: '2025-07-01 10:23:59',
-					publisher: '张三'
-				}
-			]
-		}
-	])
-	// 模态框显示状态
-	const modalVisible = ref(false)
-	const currentChapterIndex = ref(null)
-	// 表单状态
-	const formState = reactive({
-		chapterName: ''
-	})
-	const pagination = reactive({
-		pageSize: 10,
-		pageNum: 1,
-		total: 50,
-		showTotal: (total) => `共${total}条`,
-	})
-	// 显示模态框
-	const showModal = () => {
-		modalVisible.value = true
-	}
+import {ref, reactive, onMounted} from 'vue'
+import addDialog from './addDialog.vue'
+import courseProductionApi from '@/api/courseCenter/courseProduction.js'
+import {useRoute, useRouter} from 'vue-router'
+
+const router = useRouter()
+const route = useRoute()
+const popoverVisible = ref({})
+const modeTag = ref('add')
+const dialogTitle = ref('添加章节')
+
 
-	// 确认按钮点击事件
-	const handleOk = () => {
-		let courseInfoId = localStorage.getItem('courseInfoId')
-		if (formState.chapterName) {
-			courseProductionApi
-				.add({
-					courseId: courseInfoId,
-					name: formState.chapterName
-				})
-				.then((res) => {
-					console.log(res, '章节添加')
-					getList()
-				})
-				.catch((err) => {
-					console.log(err)
-				})
-			formState.chapterName = '' // 清空表单
-			modalVisible.value = false // 关闭模态框
-		}
+const props = defineProps({
+	//课程id
+	courseInfoId: {
+		type: Number,
+		required: true,
+		default: null
 	}
+})
+// 章节数据
+const chapters = ref([])
+const closePopover = () => {
+	popoverVisible.value = false
+}
+const handleEdit = (item) => {
+	addDialogRef.value.edit(item)
+}
 
-	// 取消按钮点击事件
-	const handleCancel = () => {
+// 模态框显示状态
+const modalVisible = ref(false)
+const currentChapterIndex = ref(null)
+const addDialogRef = ref(null)
+// 表单状态
+const formState = reactive({
+	id: '',
+	chapterName: ''
+})
+const pagination = reactive({
+	pageSize: 10,
+	pageNum: 1,
+	total: 50,
+	showTotal: (total) => `共${total}条`
+})
+// 显示模态框
+const showModal = () => {
+	dialogTitle.value = '添加章节'
+	modeTag.value = 'add'
+	modalVisible.value = true
+	formState.chapterName = ''
+}
+
+// 确认按钮点击事件
+const handleOk = () => {
+	let courseInfoId = props.courseInfoId
+	if (formState.chapterName && modeTag.value == 'add') {
+
+		courseProductionApi
+			.add({
+				courseId: courseInfoId,
+				name: formState.chapterName
+			})
+			.then((res) => {
+				console.log(res, '章节添加')
+				getList()
+			})
+			.catch((err) => {
+				console.log(err)
+			})
 		formState.chapterName = '' // 清空表单
 		modalVisible.value = false // 关闭模态框
 	}
-	// 编辑章节
-	const editChapter = (chapterIndex) => {
-		// 实现编辑逻辑
-	}
+	if (formState.chapterName && modeTag.value == 'edit') {
 
-	// 编辑课时
-	const editLesson = (chapterIndex, lessonIndex) => {
-		// 实现编辑逻辑
-	}
-	// 获取章节列表
-	const getList = () => {
-		let params = {
-			current: pagination.pageNum,
-			size: pagination.pageSize
-		}
 		courseProductionApi
-			.page(params)
+			.edit({
+				id: formState.id,
+				courseId: courseInfoId,
+				name: formState.chapterName
+			})
 			.then((res) => {
-				console.log(res, '章节列表')
-				chapters.value = res.data.records
+				console.log(res, '章节添加')
+				getList()
 			})
 			.catch((err) => {
 				console.log(err)
 			})
+		formState.chapterName = '' // 清空表单
+		modalVisible.value = false // 关闭模态框
 	}
+}
 
-	// 删除课时
-	const deleteLesson = (chapterIndex, lessonIndex) => {
-		chapters.value[chapterIndex].lessons.splice(lessonIndex, 1)
-	}
-	// 显示课时模态框
-	const showAddLessonModal = (chapterIndex) => {
-		currentChapterIndex.value = chapterIndex
-		addLessonModalVisible.value = true
-	}
-	// 显示课时模态框
-	const onAddClassHoursOk = (data) => {
-		console.log(data, 'onAddClassHoursOk')
-		// addLessonModalVisible.value = true
-	}
-	onMounted(() => {
+// 取消按钮点击事件
+const handleCancel = () => {
+	formState.chapterName = '' // 清空表单
+	modalVisible.value = false // 关闭模态框
+}
+// 编辑章节
+const editChapter = (chapterIndex) => {
+	// 实现编辑逻辑
+	let item = chapters.value[chapterIndex]
+	modalVisible.value = true
+	dialogTitle.value = '修改章节'
+	modeTag.value = 'edit'
+	formState.id = item.id
+	formState.chapterName = item.name
+
+}
+
+// 编辑课时
+const editLesson = (chapterIndex, lessonIndex) => {
+	// 实现编辑逻辑
+}
+const delChapter = (chapterIndex) => {
+	console.log('删除', chapterIndex)
+	// 实现编辑逻辑
+	let item = chapters.value[chapterIndex]
+	popoverVisible.value[chapterIndex] = false
+
+	courseProductionApi.delete([{id: item.id}]).then((res) => {
+		console.log('章节列表', res)
 		getList()
 	})
+		.catch((err) => {
+			console.log(err)
+		})
+
+}
+// 获取章节列表
+const getList = () => {
+	courseProductionApi
+		.allList({courseId: props.courseInfoId})
+		.then((res) => {
+			console.log('章节列表', res)
+			chapters.value = res.data
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+}
+
+// 删除课时
+const deleteLesson = (chapterIndex, lessonIndex) => {
+	chapters.value[chapterIndex].lessons.splice(lessonIndex, 1)
+}
+// 显示课时模态框
+const showAddLessonModal = (chapterIndex) => {
+	currentChapterIndex.value = chapterIndex
+	addDialogRef.value.open()
+	addDialogRef.value.setData(chapters.value[chapterIndex])
+	modeTag.value = 'add'
+
+
+}
+// 显示课时模态框
+const onAddClassHoursOk = (data) => {
+	console.log(data, 'onAddClassHoursOk')
+	// addLessonModalVisible.value = true
+}
+const onAddChapter = () => {
+	getList()
+}
+onMounted(() => {
+	console.log('有没有id呢', props.courseInfoId)
+	getList()
+})
 </script>
 
 <style scoped>
-	.chapter {
-		width: 80%;
-	}
-	.course-chapter {
-		padding: 20px;
-	}
+.chapter {
+	width: 80%;
+}
 
-	.chapter-title {
-		background: #f0f0f0;
-		padding: 10px;
-		margin-bottom: 10px;
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-	}
+.course-chapter {
+	padding: 20px;
+}
 
-	.lesson {
-		width: 100%;
-		height: 120px;
-		display: flex;
-		align-items: center;
-		background: #f7f8fa;
-		border-radius: 8px;
-		margin-bottom: 16px;
-		padding: 16px 4px;
-		box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.03);
-		transition: box-shadow 0.2s;
-	}
+.chapter-title {
+	background: #f0f0f0;
+	padding: 10px;
+	margin-bottom: 10px;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
 
-	.video-cover {
-		width: 100%;
-		height: 100%;
-		object-fit: cover;
-		border-radius: 4px;
-	}
+.lesson {
+	width: 100%;
+	height: 120px;
+	display: flex;
+	align-items: center;
+	background: #f7f8fa;
+	border-radius: 8px;
+	margin-bottom: 16px;
+	padding: 16px 4px;
+	box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.03);
+	transition: box-shadow 0.2s;
+	position: relative;
+}
 
-	.lesson-info {
-		margin-left: 10px;
-	}
+.video-cover {
+	width: 100%;
+	height: 100%;
+	object-fit: cover;
+	border-radius: 4px;
+}
 
-	.lesson-title {
-		font-weight: bold;
-		margin-bottom: 30px;
-	}
+.lesson-info {
+	margin-left: 10px;
+}
 
-	.lesson-details {
-		display: flex;
-		flex-wrap: wrap;
-	}
+.lesson-title {
+	font-weight: bold;
+	margin-bottom: 30px;
+}
 
-	.lesson-details span {
-		margin-right: 10px;
-	}
-	/* 模态框内样式 */
-	.ant-modal-content {
-		width: 400px;
-	}
+.lesson-details {
+	display: flex;
+	flex-wrap: wrap;
+}
 
-	.ant-modal-body {
-		padding: 20px;
-	}
+.lesson-details span {
+	margin-right: 10px;
+}
 
-	.ant-form-item-label > label {
-		font-weight: normal;
-	}
-	.lesson-actions {
-		display: flex;
-		align-items: center;
-	}
-	.action-icon {
-		font-size: 18px;
-		color: #347aff;
-		margin-left: 18px;
-		cursor: pointer;
-		transition: color 0.2s;
-	}
-	.action-icon:hover {
-		color: #1d5fd6;
-	}
-	.add-chapter-btn {
-		background: #ffff;
-		border: 1px solid #347aff;
-		color: #347aff;
-		border-radius: 3px;
-		margin-bottom: 10px;
-	}
-	.add-chapter-btn1 {
-		color: #ffff;
-		border: 1px solid #347aff;
-		background: #347aff;
-		border-radius: 3px;
-		margin-bottom: 10px;
-	}
+/* 模态框内样式 */
+.ant-modal-content {
+	width: 400px;
+}
+
+.ant-modal-body {
+	padding: 20px;
+}
+
+.ant-form-item-label > label {
+	font-weight: normal;
+}
+
+.lesson-actions {
+	display: flex;
+	align-items: center;
+}
+
+.action-icon {
+	font-size: 18px;
+	color: #347aff;
+	margin-left: 18px;
+	cursor: pointer;
+	transition: color 0.2s;
+}
+
+.action-icon:hover {
+	color: #1d5fd6;
+}
+
+.add-chapter-btn {
+	background: #ffff;
+	border: 1px solid #347aff;
+	color: #347aff;
+	border-radius: 3px;
+	margin-bottom: 10px;
+}
+
+.add-chapter-btn1 {
+	color: #ffff;
+	border: 1px solid #347aff;
+	background: #347aff;
+	border-radius: 3px;
+	margin-bottom: 10px;
+}
 </style>

+ 122 - 0
src/views/courseAdd/components/courseProduction/resListDialog.vue

@@ -0,0 +1,122 @@
+<template>
+		<a-modal
+			v-model:visible="visible"
+			title="选择资源"
+			:footer="null"
+			width="700px"
+			class="add-class-hours-modal"
+		>
+			<QueryView style="margin-top: 10px" @handlerSearch="handlerSearch"></QueryView>
+			<ListView ref="listViewRef" style="margin-top: 10px" @handleSelectFile="handleSelectFile"></ListView>
+		</a-modal>
+
+</template>
+
+<script setup>
+	import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
+	import { message } from 'ant-design-vue'
+	import addClassHours from './addClassHours.vue'
+	import { PictureOutlined, CloudUploadOutlined } from '@ant-design/icons-vue'
+	import QueryView from "./QueryView.vue";
+	import ListView from "./ListView.vue";
+
+	const addClassHoursRef = ref(null)
+	const listViewRef = ref(null)
+
+	const emit = defineEmits(['update:visible', 'ok','handleSelectFile'])
+
+	const visible = ref(false)
+	// watch(
+	// 	() => props.visible,
+	// 	(v) => {
+	// 		modalVisible.value = v
+	// 	}
+	// )
+	// watch(modalVisible, (v) => {
+	// 	emit('update:visible', v)
+	// })
+	const  handlerSearch = (data) => {
+		listViewRef.value.setList(data)
+	}
+	const  handleSelectFile = (item) => {
+		emit('handleSelectFile', item)
+	}
+
+
+	const open = () =>{
+		visible.value = true
+		getList()
+	}
+	const close = () =>{
+		visible.value = false
+	}
+	const getList = () => {
+
+	}
+
+	defineExpose({open,close})
+</script>
+
+<style lang="less" scoped>
+	.add-class-hours-modal {
+		.ant-modal-content {
+			border-radius: 10px;
+		}
+		.ant-modal-header {
+			border-radius: 10px 10px 0 0;
+		}
+		.ant-form-item {
+			margin-bottom: 24px;
+		}
+		.video-select-row {
+			display: flex;
+			align-items: center;
+		}
+		.cover-upload-row {
+			display: flex;
+			align-items: center;
+			.cover-upload-box {
+				width: 120px;
+				height: 120px;
+				background: #f7f8fa;
+				border-radius: 8px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				margin-right: 24px;
+				border: 1px dashed #d9d9d9;
+				cursor: pointer;
+				.cover-img {
+					width: 100%;
+					height: 100%;
+					object-fit: cover;
+					border-radius: 8px;
+				}
+				.cover-placeholder {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					width: 100%;
+					height: 100%;
+					color: #bbb;
+					font-size: 32px;
+				}
+			}
+			.cover-tip {
+				color: #888;
+				font-size: 13px;
+			}
+		}
+		.upload-tip {
+			color: #888;
+			font-size: 13px;
+			margin-left: 12px;
+		}
+		.footer-btns {
+			display: flex;
+			justify-content: flex-end;
+			gap: 16px;
+			margin-top: 24px;
+		}
+	}
+</style>

+ 671 - 0
src/views/courseAdd/components/courseProduction/resourceUpload.vue

@@ -0,0 +1,671 @@
+<template>
+	<!-- 上传资源模态框 -->
+	<a-modal
+		v-model:visible="uploadModalVisible"
+		:title="isState == 0 ? '上传资源' : '编辑'"
+		@ok="handleUploadOk"
+		@cancel="handleUploadCancel"
+		width="600px"
+	>
+		<a-form :model="formState" :rules="rules" ref="formRef">
+			<a-form-item label="院系" name="collegeId" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-cascader
+					v-model:value="majorIdName"
+					:options="collegeMajorOptions"
+					:fieldNames="{ label: 'name', value: 'id', children: 'children' }"
+					placeholder="请选择院系"
+					style="width: 300px"
+					changeOnSelect
+					@change="changeCollegeMajor"
+				/>
+			</a-form-item>
+			<!-- <a-form-item label="专业" name="majorId" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-select
+					v-model:value="formState.majorId"
+					style="width: 200px"
+					:fieldNames="{ label: 'majorName', value: 'majorCode' }"
+					:options="majorOptions"
+					placeholder="请选择专业"
+				/>
+			</a-form-item> -->
+			<!-- <a-form-item label="课程" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-select
+					v-model:value="formState.courseId"
+					style="width: 200px"
+					:fieldNames="{ label: 'courseName', value: 'courseId' }"
+					:options="courseOptions"
+					placeholder="请选择课程"
+				/>
+			</a-form-item> -->
+			<a-form-item label="资源类型" name="resourceType" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-cascader
+					style="width: 240px; margin-left: 8px"
+					v-model:value="resourceName"
+					:options="resourceTypeOptions"
+					:fieldNames="{ label: 'name', value: 'id', children: 'children' }"
+					placeholder="请选择资源类型"
+					changeOnSelect
+					@change="changeCollegeResource"
+				/>
+			</a-form-item>
+			<a-form-item
+				label="关键词(需添加2-5个关键词)"
+				name="keywordValue"
+				:label-col="{ span: 10 }"
+				:wrapper-col="{ span: 18 }"
+			>
+				<div v-if="!formState.keywordValue.length" style="color: #ccc">请选择下方热门关键词</div>
+				<div v-if="formState.keywordValue.length">
+					<a-tag
+						v-for="(keyword, index) in formState.keywordValue"
+						:key="index"
+						closable
+						@close="handleRemoveKeyword(keyword, index)"
+					>
+						{{ keyword }}
+					</a-tag>
+				</div>
+			</a-form-item>
+			<a-form-item label="热门关键词" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-checkbox-group
+					v-model:value="formState.keyword"
+					:options="HotKeywordsOptions"
+					@change="handleChangeKeyword"
+				/>
+			</a-form-item>
+			<a-row>
+				<a-col :span="5"></a-col>
+				<a-col :span="8">
+					<a-input v-model:value="newKeyword" placeholder="请输入关键词" @pressEnter="handleAddKeyword"/>
+				</a-col>
+				<a-col :span="9" style="line-height: 30px"> 按回车Enter创建新关键词</a-col>
+			</a-row>
+			<a-form-item label="资源是否公开" style="margin-top: 10px" :label-col="{ span: 6 }"
+						 :wrapper-col="{ span: 18 }">
+				<div class="public-status-buttons">
+					<button :class="['status-button', { active: formState.authType === '0' }]"
+							@click="setPublicStatus('0')">
+						公开
+					</button>
+					<button :class="['status-button', { active: formState.authType === '1' }]"
+							@click="setPublicStatus('1')">
+						部分人可见
+					</button>
+				</div>
+			</a-form-item>
+			<a-form-item label="是否热门" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-radio-group v-model:value="formState.isHot">
+					<a-radio :value="0">否</a-radio>
+					<a-radio :value="1">是</a-radio>
+				</a-radio-group>
+			</a-form-item>
+			<a-form-item label="是否推荐" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
+				<a-radio-group v-model:value="formState.isRecommend">
+					<a-radio :value="0">否</a-radio>
+					<a-radio :value="1">是</a-radio>
+				</a-radio-group>
+			</a-form-item>
+			<a-form-item v-if="isState == 1 && isVerifyStatus != '0,3'" label="资源描述" name="resourceDesc">
+				<a-textarea v-model:value="formState.resourceDesc" placeholder="请输入资源描述" :rows="4"/>
+			</a-form-item>
+			<a-form-item v-if="isState == 1 && isVerifyStatus != '0,3'" label="上传封面" name="coverImage">
+				<coverUpload
+					:isedit="true"
+					:coverImageId="formState.coverImage"
+					:imageUrl="coverImagePath"
+					@handleChangeCover="handleChangeCover"
+					@handleRemoveCover="handleRemoveCover"
+				></coverUpload>
+			</a-form-item>
+			<!-- 选择成员 -->
+			<userSelection
+				:visible="userReleaseVisible"
+				:userRelateIds="userRelateIdss"
+				@close="userReleaseVisible = false"
+				@confirm="confirmUser"
+			></userSelection>
+		</a-form>
+		<template v-if="isState == 0">
+			<!-- 资源上传 -->
+			<!-- <UploadModal @success="uploadSuccess"></UploadModal> -->
+			<UpLoadBreakPoint uploadCount="1" @onSuccess="onSuccess"></UpLoadBreakPoint>
+		</template>
+	</a-modal>
+</template>
+
+<script setup>
+import {ref, reactive, onMounted} from 'vue'
+import {Modal, Upload, Form} from 'ant-design-vue'
+import resourceAuditApi from '@/api/resourceAudit.js'
+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()
+const props = defineProps({
+	isState: {
+		type: Number,
+		required: true,
+		default: 0
+	},
+	isVerifyStatus: {
+		type: Number,
+		required: true,
+		default: 0
+	},
+	resourcesId: {
+		type: Number,
+		required: true,
+		default: null
+	}
+})
+const emit = defineEmits(['close', 'getList'])
+import tool from '@/utils/tool'
+
+const headers = ref({
+	token: tool.data.get('TOKEN')
+})
+//课程类型
+const courseTypeOptions = tool.dictList('COURSE_TYPE')
+const action = ref('/api/webapp/dev/file/uploadMinioReturnId')
+const formState = reactive({
+	userfileIds: null, //资源文件id
+	coverImage: null, //封面id
+	// courseId: null, //课程
+	// majorType: null, //专业
+	resourceType: null, //资源类型一级
+	resourceTwoType: null, //资源类型二级
+	collegeId: null, //院校一级id
+	collegeTwoId: null, //院校二级id
+	collegeThreeId: null, //院校三级id
+	resourceDesc: null, //资源介绍
+	// majorId: null, //专业id
+	// courseTypeName: [], // 资源类型
+	keywordValue: [], // 添加关键词
+	keyword: [], // 热门关键词
+	authType: '0', // 资源是否公开
+	userRelateIds: null, //资源公开人员id
+	isRecommend: 0, // 资源是否推荐
+	isHot: 0 // 资源是否热门
+})
+const coverImagePath = ref() // 预览回显
+const formRef = ref() // 添加表单引用
+const collegeMajorOptions = ref([]) //院系
+const resourceTypeOptions = ref([]) //资源类型下拉数据
+const HotKeywordsOptions = ref([]) //热门关键词列表
+const majorIdName = ref([]) //院系回显
+const resourceName = ref([]) //资源回显
+const majorOptions = ref([]) //专业
+const courseOptions = ref([]) //课程
+const userRelateIdss = ref([])
+// 上传资源模态框
+const uploadModalVisible = ref(false)
+// 用户选择模态框
+const userReleaseVisible = ref(false)
+
+// 文件列表
+const fileList = ref([])
+
+// 关闭模态框
+const handleUploadCancel = () => {
+	emit('close')
+	fileList.value = []
+}
+
+const open = () => {
+	uploadModalVisible.value = true
+}
+const close = () => {
+	uploadModalVisible.value = false
+}
+// 新增Set记录已处理ID
+// const processedIds = ref(new Set())
+//资源文件上传成功返回
+const uploadSuccess = async (idsArr) => {
+	formState.userfileIds = idsArr.join(',')
+	// const newIds = idsArr.filter((id) => !processedIds.value.has(id))
+	// if (newIds.length === 0) return
+	// //文件转换
+	// try {
+	// 	const requests = newIds.map((id) => resourceAuditApi.fileFormatConversion({ userFileId: id }))
+	// 	await Promise.all(requests)
+	// 	newIds.forEach((id) => processedIds.value.add(id))
+	// 	newKeyword.value = ''
+	// } catch (err) {
+	// 	console.error('部分请求失败:', err)
+	// }
+}
+const onSuccess = (uploadFileList) => {
+	let list = []
+	console.log('formState.userfileIds是数组:', uploadFileList)
+	for (let i = 0; i < uploadFileList.length; i++) {
+		if (uploadFileList[i].userFileId) {
+			list.push(uploadFileList[i].userFileId)
+		}
+	}
+	console.log('formState.userfileIds是:', list)
+	formState.userfileIds = list.join(',')
+}
+
+// 自定义校验函数示例
+const validateKeywords = (rule, value, callback) => {
+	if (value.length < 2) {
+		callback(new Error('至少需要添加2个关键词'))
+	} else if (value.length > 5) {
+		callback(new Error('最多只能添加5个关键词'))
+	} else {
+		callback()
+	}
+}
+// 封面文件id
+const handleChangeCover = (fileId) => {
+	formState.coverImage = fileId
+}
+
+// 移除封面文件
+const handleRemoveCover = () => {
+	formState.coverImage = null
+}
+const rules = {
+	// courseTypeName: [{ required: true, message: '请选择资源类型', trigger: 'change' }],
+	collegeId: [{required: true, message: '请选择院系', trigger: 'blur'}],
+	keywordValue: [
+		{required: true, message: '请选择关键词', trigger: 'blur'},
+		{validator: validateKeywords, trigger: 'blur'}
+	],
+	// coverImage: [{ required: true, message: '请上传封面', trigger: 'blur' }],
+	resourceType: [{required: true, message: '请选择资源类型', trigger: 'blur'}],
+	resourceDesc: [
+		{required: true, message: '请输入资源描述', trigger: 'blur'},
+		{pattern: /^[\s\S]{10,500}$/, message: '描述长度应在10-500字符之间', trigger: 'blur'}
+	]
+}
+const newKeyword = ref('') //关键词
+const handleAddKeyword = (e) => {
+	const newKeywords = newKeyword.value.trim()
+	resourceAuditApi
+		.addHotKeywords({
+			wordName: newKeywords,
+			popular: 0
+		})
+		.then((res) => {
+			console.log(res.data, '添加热门关键词')
+			getHotKeywords()
+			newKeyword.value = ''
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+	// if (newKeywords && !formState.keywordValue.includes(newKeywords)) {
+	// 	formState.keywordValue.push(newKeywords)
+	// }
+}
+
+const handleRemoveKeyword = (keywordName, index) => {
+	console.log(keywordName, index)
+	// 1. 从显示列表中删除
+	formState.keywordValue = formState.keywordValue.filter((name) => name !== keywordName)
+	// 2. 更新checkbox的绑定值
+	formState.keyword = HotKeywordsOptions.value
+		.filter((option) => formState.keywordValue.includes(option.label))
+		.map((item) => item.value)
+}
+const getHotKeywords = () => {
+	resourceAuditApi
+		.HotKeywords()
+		.then((res) => {
+			console.log(res.data, '获取热门关键词')
+			HotKeywordsOptions.value = res.data.map((it) => {
+				return {
+					value: it.id,
+					label: it.wordName
+				}
+			})
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+}
+//资源类型下拉查询
+const getResourceTypeTree = () => {
+	resourceAuditApi
+		.resourceTypeTree()
+		.then((res) => {
+			console.log(res.data, '资源类型下拉')
+			resourceTypeOptions.value = res.data
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+}
+const handleChangeKeyword = (checkedValues) => {
+	formState.keyword = checkedValues
+	formState.keywordValue = HotKeywordsOptions.value
+		.filter((option) => checkedValues.includes(option.value))
+		.map((item) => item.label)
+}
+const setPublicStatus = (status) => {
+	formState.authType = status
+	if (status === '1') {
+		userReleaseVisible.value = true
+	}
+}
+//院系组织查询
+const getOrgTreeSelector = () => {
+	resourceAuditApi
+		.orgTreeSelector()
+		.then((res) => {
+			console.log(res.data, '获取组织树选择器')
+			collegeMajorOptions.value = res.data
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+}
+//院系组织查询
+const getCourseAllList = () => {
+	resourceAuditApi
+		.courseAllList()
+		.then((res) => {
+			console.log(res.data, '获取全部课程')
+			courseOptions.value = res.data
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+}
+const changeCollegeMajor = (value, selectedOptions) => {
+	console.log('Selected:', value, selectedOptions)
+	if (!value) {
+		majorIdName.value = ''
+		return false
+	}
+	majorIdName.value = selectedOptions.map((it) => it.name).join('/')
+	formState.collegeId = value[0] || null
+	formState.collegeTwoId = value[1] || null
+	formState.collegeThreeId = value[2] || null
+	if (selectedOptions.length) {
+		// 获取选中的最后一级
+		const lastSelected = selectedOptions[selectedOptions.length - 1]
+		// formState.selectedCollegeMajor = {
+		// 	id: lastSelected.id,
+		// 	name: lastSelected.name,
+		// 	fullPath: selectedOptions.map((opt) => opt.name).join(' / ')
+		// }
+		console.log(lastSelected, '最后一级id')
+		getCollegeMajor(lastSelected.id)
+	}
+}
+const changeCollegeResource = (value, selectedOptions) => {
+	console.log('Selected:', value, selectedOptions)
+	if (!value) {
+		resourceName.value = ''
+		return false
+	}
+	resourceName.value = selectedOptions.map((it) => it.name).join('/')
+	formState.resourceType = value[0] || null
+	formState.resourceTwoType = value[1] || null
+}
+const getCollegeMajor = (id) => {
+	resourceAuditApi
+		.zyselect({collegeId: id})
+		.then((res) => {
+			console.log(res.data, '专业下拉数据')
+			majorOptions.value = res.data
+		})
+		.catch((err) => {
+			console.log(err)
+		})
+}
+const confirmUser = (userIds) => {
+	console.log(userIds, '用户id')
+	userReleaseVisible.value = false
+	formState.userRelateIds = userIds.join(',')
+}
+// 确认上传
+const handleUploadOk = async () => {
+	try {
+		await formRef.value.validate()
+		console.log('formState.userfileIds是:提交了', formState.userfileIds)
+		if (!formState.userfileIds) {
+			Modal.error({content: '请先上传文件!'})
+			return
+		}
+		if (props.isState == 1) {
+			const formData = {
+				id: props.resourcesId,
+				userfileIds: formState.userfileIds,
+				resourceType: formState.resourceType,
+				resourceTwoType: formState.resourceTwoType,
+				collegeId: formState.collegeId,
+				collegeTwoId: formState.collegeTwoId,
+				collegeThreeId: formState.collegeThreeId,
+				keywordValue: formState.keywordValue.join(','),
+				keyword: formState.keyword.join(','),
+				resourceDesc: formState.resourceDesc,
+				isHot: formState.isHot,
+				isRecommend: formState.isRecommend,
+				coverImage: formState.coverImage,
+				publicStatus: formState.publicStatus,
+				authType: formState.authType,
+				userRelateIds: formState.authType == 1 ? formState.userRelateIds : null
+			}
+			resourceAuditApi
+				.edit(formData)
+				.then((res) => {
+					emit('getList')
+					Modal.success({content: '资源编辑成功'})
+				})
+				.catch((err) => {
+					Modal.success({content: '资源编辑失败'})
+					console.log(err)
+				})
+		} else {
+			const formData = {
+				userfileIds: formState.userfileIds,
+				resourceType: formState.resourceType,
+				resourceTwoType: formState.resourceTwoType,
+				collegeId: formState.collegeId,
+				collegeTwoId: formState.collegeTwoId,
+				collegeThreeId: formState.collegeThreeId,
+				keywordValue: formState.keywordValue.join(','),
+				keyword: formState.keyword.join(','),
+				resourceDesc: formState.resourceDesc,
+				isHot: formState.isHot,
+				isRecommend: formState.isRecommend,
+				coverImage: formState.coverImage,
+				publicStatus: formState.publicStatus,
+				authType: formState.authType,
+				userRelateIds: formState.authType == 1 ? formState.userRelateIds : null
+			}
+			console.log(formData, '上传数据')
+			resourceAuditApi
+				.add(formData)
+				.then((res) => {
+					emit('getList')
+					Modal.success({content: '资源上传成功'})
+				})
+				.catch((err) => {
+					Modal.success({content: '资源上传失败'})
+					console.log(err)
+				})
+		}
+	} catch (error) {
+		if (error.errorFields) {
+			// 表单验证错误
+			Modal.error({content: '请检查表单填写是否正确'})
+		} else {
+			// API错误
+			Modal.error({content: '资源上传失败'})
+			console.error(error)
+		}
+	}
+}
+// 获取资源详情
+const getDetail = () => {
+	resourceAuditApi.detail({id: props.resourcesId}).then((res) => {
+		console.log(res.data, '资源详情')
+		formState.userfileIds = res.data.fileId
+		formState.userRelateIds = res.data.userRelateIdList?.join(',')
+		userRelateIdss.value = res.data.userRelateIdList
+		formState.resourceType = res.data.resourceType
+		formState.resourceTwoType = res.data.resourceTwoType
+		formState.collegeId = res.data.collegeId
+		formState.coverImage = res.data.coverImage
+		coverImagePath.value = res.data.coverImagePath
+		formState.collegeTwoId = res.data.collegeTwoId
+		formState.collegeThreeId = res.data.collegeThreeId
+		majorIdName.value = res.data.collegeAllId?.split(',')
+		resourceName.value = res.data.resourceALLTypeName?.split(',')
+		getCollegeMajor(majorIdName.value[majorIdName.value.length - 1])
+		formState.resourceDesc = res.data.resourceDesc
+		// formState.majorId = res.data.majorId
+		formState.keywordValue = res.data.keywordValue?.split(',')
+		formState.keyword = res.data.keyword.split(',').filter(Boolean).map(Number)
+	})
+}
+// 获取历史添加表单
+const getformState = () => {
+	resourceAuditApi.recentlyRecord().then((res) => {
+		console.log(res.data, '历史表单数据')
+		formState.resourceType = res.data.resourceType
+		formState.userRelateIds = res.data.userRelateIdList?.join(',')
+		userRelateIdss.value = res.data.userRelateIdList
+		formState.resourceTwoType = res.data.resourceTwoType
+		formState.collegeId = res.data.collegeId
+		formState.collegeTwoId = res.data.collegeTwoId
+		formState.collegeThreeId = res.data.collegeThreeId
+		majorIdName.value = res.data.collegeAllId?.split(',')
+		resourceName.value = res.data.resourceAllType?.split(',')
+		getCollegeMajor(majorIdName.value[majorIdName.value.length - 1])
+		formState.resourceDesc = res.data.resourceDesc
+		// formState.majorId = res.data.majorId
+		formState.keywordValue = res.data.keywordValue?.split(',')
+		formState.keyword = res.data.keyword?.split(',').filter(Boolean).map(Number)
+	})
+}
+// 上传前的钩子函数
+const beforeUpload = (file) => {
+	const isLt2G = file.size / 1024 / 1024 / 1024 < 2
+	if (!isLt2G) {
+		Modal.error({content: '文件大小不能超过 2GB!'})
+	}
+	return isLt2G
+}
+
+// 移除文件
+const handleRemove = (file) => {
+	const index = fileList.value.indexOf(file)
+	const newFileList = fileList.value.slice()
+	newFileList.splice(index, 1)
+	fileList.value = newFileList
+	// 如果移除的是当前封面文件,则清空coverImageId
+	if (formState.userfileIds === file.id) {
+		formState.userfileIds = null
+	}
+}
+
+// 文件状态改变时的处理函数
+const handleChange = ({file, fileList: newFileList}) => {
+	if (newFileList.length > 1) {
+		fileList.value = [newFileList[0]] // 只保留最新上传的文件
+		Modal.error({content: '只能上传一个文件!'})
+		return
+	}
+
+	if (file.response?.code == 200) {
+		// 上传成功,获取文件ID
+		const fileId = file.response?.data || file.id
+		console.log('上传成功,获取文件ID', fileId)
+		if (fileId) {
+			formState.userfileIds = fileId
+		}
+	}
+	fileList.value = newFileList
+	if (file.status === 'uploading') {
+		file.percent = Math.floor(file.percent)
+	}
+	if (file.response?.code == 200) {
+		file.percent = 100
+		Modal.success({content: '文件上传成功'})
+	} else if (file.response?.code == 500) {
+		Modal.error({content: '文件上传失败'})
+		file.percent = 0
+	}
+}
+onMounted(() => {
+	getOrgTreeSelector()
+	getCourseAllList()
+	getHotKeywords()
+	getResourceTypeTree()
+	if (props.isState == 1) {
+		getDetail()
+	} else {
+		getformState()
+	}
+
+
+})
+defineExpose({open, close})
+</script>
+<style scoped>
+.upload-area {
+	border: 2px dashed #3ca9f5;
+	padding: 40px;
+	text-align: center;
+}
+
+.upload-area p {
+	margin: 10px 0;
+}
+
+.file-item {
+	display: flex;
+	align-items: center;
+	margin: 10px 0;
+}
+
+.file-item .ant-progress {
+	flex: 1;
+	margin: 0 10px;
+}
+
+/* 新增表单样式 */
+.ant-form-item {
+	margin-bottom: 16px;
+}
+
+.public-status-buttons {
+	display: flex;
+}
+
+.status-button {
+	padding: 5px 10px;
+	/* margin-right: 10px; */
+	border: 1px solid #ccc;
+	/* border-radius: 3px; */
+	cursor: pointer;
+	background-color: #fff;
+}
+
+.status-button.active {
+	background-color: #40a9ff;
+	color: #fff;
+	border-color: #40a9ff;
+}
+
+.upload-area {
+	border: 2px dashed #3ca9f5;
+	padding: 40px;
+	text-align: center;
+	transition: border-color 0.3s; /* 平滑过渡效果 */
+}
+
+.upload-area.drag-over {
+	border-color: #1890ff;
+	background-color: rgba(24, 144, 255, 0.05);
+}
+</style>

+ 300 - 0
src/views/courseAdd/components/courseProduction/userSelection.vue

@@ -0,0 +1,300 @@
+<template>
+	<a-drawer
+		v-model:visible="visible"
+		title="选择可见成员"
+		placement="right"
+		width="50%"
+		:footer="null"
+		@close="handleCancel"
+	>
+		<!-- 左侧:树状结构成员列表 -->
+		<div class="left-panel">
+			<a-input-search
+				v-model:value="searchValue"
+				placeholder="输入部门或成员名称"
+				style="margin-bottom: 16px"
+				@search="onSearch"
+			/>
+			<a-tree
+				v-if="treeData.length > 0"
+				:tree-data="filteredTreeData"
+				:field-names="{ key: 'id', title: 'name', children: 'children' }"
+				:checked-keys="checkedKeys"
+				:expanded-keys="expandedKeys"
+				:auto-expand-parent="autoExpandParent"
+				checkable
+				show-icon
+				@check="onCheck"
+				@expand="onExpand"
+			>
+				<template #title="{ name, avatar }">
+					<span style="display: inline-flex; align-items: center">
+						<a-avatar :src="avatar" size="small" style="margin-right: 8px" />
+						{{ name }}
+					</span>
+				</template>
+				<template #switcherIcon="{ expanded }">
+					<caret-down-outlined v-if="expanded" />
+					<caret-right-outlined v-else />
+				</template>
+			</a-tree>
+		</div>
+
+		<!-- 右侧:已选择成员列表 -->
+		<div class="right-panel">
+			<div class="header">
+				<span>已选 {{ selectedUsers.length }} / 30</span>
+				<a-button type="link" @click="clearSelection">清空</a-button>
+			</div>
+			<a-list item-layout="horizontal" :data-source="selectedUsers">
+				<template #renderItem="{ item }">
+					<a-list-item>
+						<a-list-item-meta>
+							<template #avatar>
+								<a-avatar :src="item.avatar" />
+							</template>
+							<template #title>
+								<a>{{ item.name }}</a>
+							</template>
+							<template #description>
+								<span>{{ item.department }}</span>
+							</template>
+						</a-list-item-meta>
+						<template #actions>
+							<a @click="removeUser(item)">删除</a>
+						</template>
+					</a-list-item>
+				</template>
+			</a-list>
+		</div>
+
+		<!-- 底部按钮 -->
+		<template #footer>
+			<a-space>
+				<a-button @click="handleCancel">取消</a-button>
+				<a-button type="primary" @click="handleOk">确定</a-button>
+			</a-space>
+		</template>
+	</a-drawer>
+</template>
+
+<script setup>
+	import { ref, reactive, computed, onMounted } from 'vue'
+	import resourceAuditApi from '@/api/resourceAudit.js'
+	import { Modal, Input, Tree, List, Avatar, Button } from 'ant-design-vue'
+	const emit = defineEmits(['close', 'confirm'])
+	// const visible = ref(true)
+	const props = defineProps({
+		visible: {
+			type: Boolean,
+			default: false
+		},
+		userRelateIds: {
+			type: Array,
+			default: () => {}
+		}
+	})
+
+	const searchValue = ref('')
+	const treeData = ref([
+		{
+			id: '1',
+			name: '组织1',
+			children: [
+				{
+					id: '1-1',
+					name: '张小刚',
+					avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
+					isLeaf: true
+				},
+				{
+					id: '1-2',
+					name: '李小红',
+					avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
+					isLeaf: true
+				}
+			]
+		},
+		{
+			id: '2',
+			name: '组织2',
+			children: [
+				{
+					id: '2-1',
+					name: '研发部',
+					children: [
+						{
+							id: '2-1-1',
+							name: '王小明',
+							avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
+							isLeaf: true
+						}
+					]
+				}
+			]
+		}
+	])
+	watch(
+		() => props.userRelateIds,
+		(newVal) => {
+			if (newVal) {
+				console.log(newVal, 'props.userRelateIds')
+				checkedKeys.value = newVal
+				// selectedUsers.value = flatTree(treeData.value)
+				// 	.filter((node) => newVal.includes(node.id))
+				// 	.map((node) => ({ id: node.id, name: node.name }))
+			}
+		},
+		{ deep: true }
+	)
+	const selectedKeys = ref([])
+	const selectedUsers = ref([])
+	const checkedKeys = ref([])
+	const expandedKeys = ref([]) // 默认展开第一层
+	const autoExpandParent = ref(true)
+	const filteredTreeData = computed(() => {
+		const filterFn = (node) => {
+			// 保留匹配节点及其所有祖先节点
+			if (node.name.includes(searchValue.value)) return true
+			if (node.children) {
+				const hasMatchingChild = node.children.some(filterFn)
+				if (hasMatchingChild) return true
+			}
+			return false
+		}
+
+		return treeData.value.filter(filterFn)
+	})
+
+	// 替换原来的onSelect方法
+	const onCheck = (checkedKeysValue, { checked, node, checkedNodes }) => {
+		// 过滤掉非叶子节点
+		const leafNodes = checkedNodes.filter((node) => node.isLeaf)
+		checkedKeys.value = leafNodes.map((node) => node.id)
+		selectedUsers.value = leafNodes.map((node) => ({
+			id: node.id,
+			name: node.name
+			// 可选: 保留部门信息
+			// department: findDepartmentName(node.id, treeData.value)
+		}))
+	}
+	// 查找部门名称的辅助函数
+	const findDepartmentName = (id, nodes) => {
+		for (const node of nodes) {
+			if (node.children) {
+				const found = node.children.find((child) => child.id === id)
+				if (found) return node.name
+				const result = findDepartmentName(id, node.children)
+				if (result) return result
+			}
+		}
+		return ''
+	}
+	const onExpand = (keys) => {
+		// console.log(keys, 'onExpand')
+		expandedKeys.value = keys
+		autoExpandParent.value = false
+	}
+
+	const onSearch = (value) => {
+		searchValue.value = value
+	}
+	const augmentNode = (node) => {
+		if (node.children) {
+			node.isLeaf = false // 有children的节点标记为非叶子节点
+			node.children.forEach((child) => augmentNode(child))
+		} else {
+			node.isLeaf = true // 无children的节点标记为叶子节点
+		}
+	}
+	const getOrgUserTreeRespectively = () => {
+		resourceAuditApi
+			.orgUserTreeSelector()
+			.then((res) => {
+				if (res?.data) {
+					console.log(res.data, 'getOrgUserTreeRespectively')
+					res.data.forEach((root) => augmentNode(root))
+					// if (treeData.value.length > 0 && treeData.value[0]?.id) {
+					// 	expandedKeys.value = [treeData.value[0].id]
+					// }
+					treeData.value = res.data
+				}
+			})
+			.catch((err) => {
+				console.log(err)
+			})
+	}
+
+	const clearSelection = () => {
+		selectedKeys.value = []
+		selectedUsers.value = []
+		checkedKeys.value = []
+	}
+
+	const removeUser = (user) => {
+		const index = selectedUsers.value.findIndex((u) => u.id === user.id)
+		if (index !== -1) {
+			selectedUsers.value.splice(index, 1)
+			selectedKeys.value = selectedUsers.value.map((u) => u.id)
+			checkedKeys.value = selectedUsers.value.map((u) => u.id)
+		}
+		// selectedUsers.value = selectedUsers.value.filter((u) => u.id !== user.id)
+		// checkedKeys.value = selectedUsers.value.map((u) => u.id)
+	}
+
+	const showModal = () => {
+		visible.value = true
+	}
+
+	const handleOk = () => {
+		console.log('Selected Users:', selectedUsers.value, checkedKeys.value)
+		emit('confirm', checkedKeys.value)
+	}
+
+	const handleCancel = () => {
+		emit('close')
+	}
+	onMounted(() => {
+		getOrgUserTreeRespectively()
+		// if (props.userRelateIds) {
+		// 	checkedKeys.value = props.userRelateIds // 直接赋值给树组件的checkedKeys
+		// }
+	})
+</script>
+
+<style scoped>
+	.left-panel,
+	.right-panel {
+		display: inline-block;
+		vertical-align: top;
+		width: 48%;
+		height: calc(100vh - 200px);
+		overflow-y: auto;
+		padding: 0 10px;
+	}
+
+	.right-panel {
+		border-left: 1px solid #f0f0f0;
+	}
+
+	.header {
+		display: flex;
+		justify-content: space-between;
+		margin-bottom: 16px;
+	}
+
+	.ant-tree-switcher {
+		width: 24px;
+		height: 24px;
+		line-height: 24px;
+	}
+
+	.ant-tree-switcher-icon {
+		font-size: 12px;
+		transition: transform 0.3s;
+	}
+
+	.ant-tree-switcher_close .ant-tree-switcher-icon {
+		transform: rotate(-90deg);
+	}
+</style>

+ 8 - 6
src/views/courseAdd/index.vue

@@ -8,16 +8,16 @@
 					<a-tab-pane key="1" tab="教室信息">
 						<courseInfo :courseInfoId="courseInfoId" @nextStep="nextStep" />
 					</a-tab-pane>
-					<a-tab-pane key="2" tab="课程制作">
-						<courseProduction />
+					<a-tab-pane key="2" tab="课程制作" :disabled="courseInfoId==null">
+						<courseProduction :courseInfoId="courseInfoId" />
 					</a-tab-pane>
-					<a-tab-pane key="3" tab="学员管理">
+					<a-tab-pane key="3" tab="学员管理" :disabled="courseInfoId==null">
 						<StudentDetails></StudentDetails>
 					</a-tab-pane>
-					<a-tab-pane key="4" tab="作业布置">
+					<a-tab-pane key="4" tab="作业布置" :disabled="courseInfoId==null">
 						<div>这里是作业布置的内容</div>
 					</a-tab-pane>
-					<a-tab-pane key="5" tab="测试布置">
+					<a-tab-pane key="5" tab="测试布置" :disabled="courseInfoId==null">
 						<div>这里是测试布置的内容</div>
 					</a-tab-pane>
 				</a-tabs>
@@ -39,7 +39,9 @@
 	const route = useRoute()
 	const activeKey = ref('1') // 默认选中的标签页
 	const courseInfoId = ref(route.query.id || null) // 编辑课程信息id
-	const nextStep = () => {
+	const nextStep = (id) => {
+
+		courseInfoId.value = id
 		activeKey.value = '2'
 	}
 	const onChangeCurrent = (current) => {

+ 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.14:9003',
+					target: 'http://192.168.31.80:19003',
 					ws: false,
 					changeOrigin: true
 					// rewrite: (path) => path.replace(/^\/api/, '')