zhangsq 7 tháng trước cách đây
mục cha
commit
d34cf71c68

+ 8 - 0
src/api/resourceAudit.js

@@ -70,5 +70,13 @@ export default {
 	//资源格式下拉
 	fileformat(data = {}) {
 		return request('disk/fileformat/page', data, 'get')
+	},
+	//公开人员查询分级查询
+	orgUserTreeRespectively(data = {}) {
+		return request('disk/college/orgUserTreeRespectively', data, 'get')
+	},
+	//公开人员查询所有以及下级
+	orgUserTreeSelector(data = {}) {
+		return request('disk/college/orgUserTreeSelector', data, 'get')
 	}
 }

+ 10 - 0
src/router/portal.js

@@ -52,6 +52,16 @@ const portal = [
 				name: 'portal.resourceManagement',
 				path: '/portal/resourceManagement',
 				component: () => import('@/views/myResources/resourceManagement/index.vue')
+			},
+			{
+				name: 'portal.courseCenter',
+				path: '/portal/courseCenter',
+				component: () => import('@/views/courseCenter/index.vue')
+			},
+			{
+				name: 'portal.courseDetails',
+				path: '/portal/courseDetails',
+				component: () => import('@/views/courseDetails/index.vue')
 			}
 		]
 	}

+ 8 - 0
src/router/whiteList.js

@@ -35,6 +35,14 @@ const constRouters = [
 		path: '/portal/resourceManagement',
 		component: () => import('@/views/myResources/resourceManagement/index.vue')
 	},
+	{
+		path: '/portal/courseCenter',
+		component: () => import('@/views/courseCenter/index.vue')
+	},
+	{
+		path: '/portal/courseDetails',
+		component: () => import('@/views/courseDetails/index.vue')
+	},
 	{
 		path: '/other',
 		name: 'other',

+ 224 - 0
src/views/courseCenter/components/ResourceList.vue

@@ -0,0 +1,224 @@
+<template>
+	<div class="resource-list">
+		<div class="list-header">
+			<div style="display: flex">
+				<div style="display: flex; justify-content: center; align-items: center">
+					<div class="line"></div>
+					<span style="font-weight: bold">共计 {{ total }} 个课程</span>
+				</div>
+				<div style="width: 20px"></div>
+				<TabSwitcher @selectTab="selectTab" />
+			</div>
+
+			<a-input-search
+				v-model:value="currentPage.queryInfo"
+				placeholder="请输入课程标题/专业名称/关键词"
+				style="width: 200px"
+				@search="onSearch"
+			/>
+		</div>
+		<a-row :gutter="[16, 16]">
+			<a-col :span="8" v-for="(item, index) in resources" :key="index">
+				<div style="border-radius: 10px 10px 5px 5px; border: 1px solid #dcdcdc; position: relative">
+					<div style="display: flex; position: relative">
+						<div
+							class="resource"
+							@click="handleItem(item)"
+							:style="{
+								backgroundSize: 'cover',
+								backgroundPosition: 'center',
+								backgroundImage:
+									'url(' +
+									(item.coverImagePath != '' && sysConfig.FILE_URL + item.coverImagePath
+										? sysConfig.FILE_URL + item.coverImagePath
+										: '') +
+									')'
+							}"
+						>
+							<PlayCircleOutlined
+								:style="{ fontSize: '40px', color: 'white' }"
+								style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%)"
+							/>
+							<div
+								style="
+									position: absolute;
+									bottom: 0;
+									right: 0;
+									background-color: #40a0ff;
+									color: white;
+									padding: 5px 20px;
+									border-radius: 4px;
+								"
+							>
+								共{{ item.lessonCount }}节课
+							</div>
+						</div>
+					</div>
+					<div style="display: flex; flex-direction: column; padding: 5px 10px">
+						<span style="font-size: 16px; font-weight: bold">{{ item.fileName }}</span>
+						<span style="font-size: 12px">{{ item.collegeIdName }}</span>
+						<span style="font-size: 12px">{{ item.majorIdName }}</span>
+						<div style="display: flex; justify-content: space-between">
+							<div style="display: flex; justify-content: center; align-items: center">
+								<FieldTimeOutlined />
+								<div style="width: 5px"></div>
+								<span style="font-size: 12px">{{ item.uploadTime }}</span>
+							</div>
+
+							<div style="display: flex; justify-content: center; align-items: center">
+								<EyeOutlined />
+								<div style="width: 5px"></div>
+								<span style="font-size: 12px">{{ item.viewCount }}</span>
+							</div>
+						</div>
+					</div>
+				</div>
+			</a-col>
+		</a-row>
+
+		<div style="height: 20px"></div>
+		<div style="display: flex; width: 100%; align-items: center; justify-content: center">
+			<a-pagination
+				v-model:current="currentPage.current"
+				v-model:pageSize="currentPage.size"
+				:total="total"
+				@change="onChange"
+			/>
+		</div>
+		<div style="height: 20px"></div>
+	</div>
+</template>
+
+<script setup>
+	import { ref, onMounted } from 'vue'
+	import TabSwitcher from './TabSwitcher.vue'
+	import { list } from '@/api/portal'
+	import tool from '@/utils/tool'
+	import EventBus from '@/utils/EventBus'
+	import sysConfig from '@/config/index'
+
+	const queryData = ref({})
+	const total = ref(0)
+	const tabKey = ref(0)
+	const currentPage = reactive({
+		current: 1,
+		size: 12,
+		queryInfo: '',
+		sortflag: tabKey
+	})
+	const searchKeyword = ref('')
+	const resources = ref([])
+
+	const selectTab = (key) => {
+		if (key == 'latest') {
+			tabKey.value = 0
+		} else {
+			tabKey.value = 1
+		}
+		currentPage.sortflag = tabKey.value
+		currentPage.current = 1
+		currentPage.size = 12
+		getList()
+	}
+
+	const handleItem = (item) => {
+		EventBus.emit('openCourseDetails', { id: item.id })
+	}
+
+	const onSearch = (value) => {
+		currentPage.current = 1
+		currentPage.size = 12
+		getList()
+	}
+
+	const onChange = (page, pageSize) => {
+		getList()
+	}
+
+	const getList = () => {
+		list({ ...currentPage, ...queryData.value })
+			.then((res) => {
+				if (res.code == 200) {
+					resources.value = res.data.records
+					total.value = res.data.total
+					currentPage.current = res.data.current
+				}
+			})
+			.catch((err) => {
+				console.log(err)
+			})
+	}
+
+	const upLoadList = (data) => {
+		currentPage.current = 1
+		currentPage.size = 12
+		queryData.value = data
+		list({ ...currentPage, ...queryData.value })
+			.then((res) => {
+				if (res.code == 200) {
+					resources.value = res.data.records
+					total.value = res.data.total
+					currentPage.current = res.data.current
+				}
+			})
+			.catch((err) => {
+				console.log(err)
+			})
+	}
+
+	onMounted(() => {
+		getList()
+	})
+
+	EventBus.off('upLoadList', upLoadList)
+	EventBus.on('upLoadList', upLoadList)
+</script>
+
+<style scoped>
+	.list-header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		margin-bottom: 20px;
+	}
+	.line {
+		width: 6px;
+		height: 15px;
+		background-color: rgb(0, 140, 255);
+		margin-right: 5px;
+	}
+
+	.resource {
+		width: 100%;
+		height: 150px;
+		position: relative;
+		background: #00000011;
+		border-radius: 10px 10px 0 0;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		cursor: pointer;
+		position: relative;
+		overflow: hidden;
+	}
+	.resource::before {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		background-color: transparent;
+		transition: background-color 0.6s ease;
+		z-index: 1;
+	}
+
+	.resource:hover::before {
+		background-color: rgba(0, 0, 0, 0.4); /* 悬停变暗 */
+	}
+
+	.resource > * {
+		position: relative;
+		z-index: 2;
+	}
+</style>

+ 43 - 0
src/views/courseCenter/components/TabSwitcher.vue

@@ -0,0 +1,43 @@
+<template>
+	<div class="tab-switcher">
+		<div :class="{ active: selectedTab === 'latest' }" @click="selectTab('latest')">最新</div>
+		<div :class="{ active: selectedTab === 'hot' }" @click="selectTab('hot')">热门</div>
+	</div>
+</template>
+
+<script setup>
+	import { ref } from 'vue'
+	const emit = defineEmits(['selectTab'])
+	const selectedTab = ref('latest')
+
+	const selectTab = (tab) => {
+		if (selectedTab.value != tab) {
+			selectedTab.value = tab
+			emit('selectTab', tab)
+		}
+	}
+</script>
+
+<style scoped>
+	.tab-switcher {
+		display: flex;
+		border-radius: 20px;
+		border: 1px solid #1e90ff;
+		overflow: hidden;
+	}
+
+	.tab-switcher div {
+		padding: 2px 20px;
+		background-color: #f5f5f5;
+
+		cursor: pointer;
+	}
+
+	.tab-switcher div.active {
+		background-color: #1e90ff;
+		color: white;
+	}
+
+	.tab-switcher div:not(:last-child) {
+	}
+</style>

+ 58 - 0
src/views/courseCenter/index.vue

@@ -0,0 +1,58 @@
+<template>
+	<div style="overflow-y: auto">
+		<a-layout>
+			<Header @onChangeCurrent="onChangeCurrent" />
+			<div style="width: 71%; margin-left: 10%">
+				<div style="height: 20px"></div>
+				<ResourceList />
+			</div>
+		</a-layout>
+		<Footer />
+	</div>
+</template>
+
+<script setup>
+	import Header from '@/views/portal/components/Header.vue'
+	import Footer from '@/views/portal/components/Footer.vue'
+	import ResourceList from './components/ResourceList.vue'
+	import { useRouter, useRoute } from 'vue-router'
+
+	import EventBus from '@/utils/EventBus'
+
+	const router = useRouter()
+	const indexType = ref('resourceCenter')
+	const onChangeCurrent = (current) => {
+		indexType.value = current
+
+		router.push({
+			path: '/' + current
+		})
+	}
+
+	const handlerItemCorrelation = (item) => {
+		// emit('handlerItemSidebar', item)
+	}
+	const handleOpenCourseDetails = (item) => {
+		console.log('看看呢', item)
+		router.push({
+			path: '/portal/courseDetails',
+			query: {
+				id: item.id
+			}
+		})
+	}
+	onMounted(() => {
+		// setTimeout(() => {
+		// 	images.value = 'http://192.168.1.245:10005/education/2025/7/2/1940361083973906434.jpg'
+		// }, 10000)
+	})
+	EventBus.off('openCourseDetails', handleOpenCourseDetails)
+	EventBus.on('openCourseDetails', handleOpenCourseDetails)
+</script>
+
+<style scoped>
+	.content {
+		padding-left: 10%;
+		padding-right: 30%;
+	}
+</style>

+ 74 - 0
src/views/courseDetails/components/LessonDetails.vue

@@ -0,0 +1,74 @@
+<template>
+	<div v-for="(chapter, index) in chapters" :key="index">
+		<a-card :title="chapter.title" style="margin-bottom: 20px">
+			<a-list item-layout="horizontal" :data-source="chapter.lessons">
+				<template #renderItem="{ item }">
+					<a-list-item>
+						<a-list-item-meta>
+							<template #avatar>
+								<a-image
+									width="100"
+									src="https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png"
+								/>
+							</template>
+							<template #title>
+								<a>{{ item.title }}</a>
+							</template>
+							<template #description>
+								<div>视频大小:{{ item.size }}MB</div>
+								<div>发布时间:{{ item.publishTime }}</div>
+								<div>发布人:{{ item.publisher }}</div>
+							</template>
+						</a-list-item-meta>
+						<template #actions>
+							<a-tooltip title="编辑">
+								<EditOutlined style="margin-right: 10px" />
+							</a-tooltip>
+							<a-tooltip title="删除">
+								<DeleteOutlined />
+							</a-tooltip>
+						</template>
+					</a-list-item>
+				</template>
+			</a-list>
+		</a-card>
+	</div>
+</template>
+
+<script setup>
+	import { ref } from 'vue'
+	import { EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
+
+	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: '张三'
+				}
+			]
+		}
+	])
+</script>
+
+<style scoped></style>

+ 120 - 0
src/views/courseDetails/index.vue

@@ -0,0 +1,120 @@
+<template>
+	<div style="overflow-y: auto">
+		<a-layout>
+			<div style="width: 71%; margin-left: 10%">
+				<!-- 顶部信息栏 -->
+				<a-row :gutter="16" style="margin-bottom: 20px; align-items: center">
+					<a-col :span="4">
+						<a-image width="100" src="https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png" />
+					</a-col>
+					<a-col :span="16">
+						<div style="margin-bottom: 10px">
+							<span style="font-weight: bold">课程名称</span>
+							<span style="margin-left: 20px">当前状态</span>
+							<span style="margin-left: 20px">授课教师</span>
+							<span style="margin-left: 20px">课程分类</span>
+							<span style="margin-left: 20px">课时数量</span>
+						</div>
+						<div style="margin-bottom: 10px">
+							<span>👁️ 10000</span>
+							<span style="margin-left: 20px"><a-tag color="green">正常</a-tag></span>
+							<span style="margin-left: 20px">赵小刚</span>
+							<span style="margin-left: 20px">航空理论系-初级飞行训练</span>
+							<span style="margin-left: 20px">16</span>
+						</div>
+						<div>
+							<span>📅 05-22 10:49</span>
+						</div>
+					</a-col>
+					<a-col :span="4" style="text-align: right">
+						<a-button type="primary">编辑课程</a-button>
+						<a-button style="margin-left: 10px">下架课程</a-button>
+						<a-button style="margin-left: 10px">删除课程</a-button>
+					</a-col>
+				</a-row>
+
+				<!-- 标签页导航 -->
+				<a-tabs default-active-key="1">
+					<a-tab-pane key="1" tab="课时详情">
+						<!-- 章节和课时列表 -->
+						<div v-for="(chapter, index) in chapters" :key="index">
+							<a-card :title="chapter.title" style="margin-bottom: 20px">
+								<a-list item-layout="horizontal" :data-source="chapter.lessons">
+									<template #renderItem="{ item }">
+										<a-list-item>
+											<a-list-item-meta>
+												<template #avatar>
+													<a-image
+														width="100"
+														src="https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png"
+													/>
+												</template>
+												<template #title>
+													<a>{{ item.title }}</a>
+												</template>
+												<template #description>
+													<div>视频大小:{{ item.size }}MB</div>
+													<div>发布时间:{{ item.publishTime }}</div>
+													<div>发布人:{{ item.publisher }}</div>
+												</template>
+											</a-list-item-meta>
+											<template #actions>
+												<a-tooltip title="编辑">
+													<EditOutlined style="margin-right: 10px" />
+												</a-tooltip>
+												<a-tooltip title="删除">
+													<DeleteOutlined />
+												</a-tooltip>
+											</template>
+										</a-list-item>
+									</template>
+								</a-list>
+							</a-card>
+						</div>
+					</a-tab-pane>
+					<!-- 其他标签页可以类似添加 -->
+				</a-tabs>
+			</div>
+		</a-layout>
+		<Footer />
+	</div>
+</template>
+
+<script setup>
+	import { ref } from 'vue'
+	import { EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
+
+	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: '张三'
+				}
+			]
+		}
+	])
+</script>
+
+<style scoped>
+</style>

+ 8 - 4
src/views/myResources/resourceUpload.vue

@@ -82,10 +82,10 @@
 			</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.publicStatus === '0' }]" @click="setPublicStatus('0')">
+					<button :class="['status-button', { active: formState.authType === '0' }]" @click="setPublicStatus('0')">
 						公开
 					</button>
-					<button :class="['status-button', { active: formState.publicStatus === '1' }]" @click="setPublicStatus('1')">
+					<button :class="['status-button', { active: formState.authType === '1' }]" @click="setPublicStatus('1')">
 						部分人可见
 					</button>
 				</div>
@@ -178,7 +178,8 @@
 		// courseTypeName: [], // 资源类型
 		keywordValue: [], // 添加关键词
 		keyword: [], // 热门关键词
-		publicStatus: '0', // 资源是否公开
+		authType: '0', // 资源是否公开
+		userRelateIds: [], //资源公开人员id
 		isRecommend: 0, // 资源是否推荐
 		isHot: 0 // 资源是否热门
 	})
@@ -319,7 +320,7 @@
 			.map((item) => item.label)
 	}
 	const setPublicStatus = (status) => {
-		formState.publicStatus = status
+		formState.authType = status
 		if (status === '1') {
 			userReleaseVisible.value = true
 		}
@@ -391,6 +392,9 @@
 				console.log(err)
 			})
 	}
+	const confirmUser = (userIds) => {
+		formState.userRelateIds = userIds.join(',')
+	}
 	// 确认上传
 	const handleUploadOk = async () => {
 		try {

+ 56 - 17
src/views/myResources/userSelection.vue

@@ -16,6 +16,7 @@
 				@search="onSearch"
 			/>
 			<a-tree
+				v-if="treeData.length > 0"
 				:tree-data="filteredTreeData"
 				:field-names="{ key: 'id', title: 'name', children: 'children' }"
 				:checked-keys="checkedKeys"
@@ -23,7 +24,6 @@
 				:auto-expand-parent="autoExpandParent"
 				checkable
 				show-icon
-				:selectable="false"
 				@check="onCheck"
 				@expand="onExpand"
 			>
@@ -79,7 +79,8 @@
 </template>
 
 <script setup>
-	import { ref, reactive, computed, watch } from 'vue'
+	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)
@@ -125,32 +126,40 @@
 	const selectedKeys = ref([])
 	const selectedUsers = ref([])
 	const checkedKeys = ref([])
-	const expandedKeys = ref(['1']) // 默认展开第一层
+	const expandedKeys = ref([]) // 默认展开第一层
 	const autoExpandParent = ref(true)
 	const filteredTreeData = computed(() => {
 		const filterFn = (node) => {
-			if (node.name.includes(searchValue.value)) {
-				return true
-			}
-			if (node.children && node.children.some(filterFn)) {
-				return true
+			// 保留匹配节点及其所有祖先节点
+			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 }) => {
+		// console.log(checkedKeysValue, checked, node, checkedNodes, 'checkedKeysValue')
 		checkedKeys.value = checkedKeysValue
-		selectedUsers.value = checkedNodes
-			.filter((node) => node.isLeaf) // 只保留叶子节点
-			.map((node) => ({
-				id: node.id,
-				name: node.name,
-				avatar: node.avatar,
-				department: findDepartmentName(node.id, treeData.value)
-			}))
+		// selectedUsers.value = checkedNodes
+		// 	.filter((node) => node.isLeaf) // 只保留叶子节点
+		// 	.map((node) => ({
+		// 		id: node.id,
+		// 		name: node.name,
+		// 		// avatar: node.avatar,
+		// 		department: findDepartmentName(node.id, treeData.value)
+		// 	}))
+		selectedUsers.value = checkedNodes.map((node) => ({
+			id: node.id,
+			name: node.name
+			// avatar: node.avatar,
+			// department: findDepartmentName(node.id, treeData.value)
+		}))
 	}
 	// 查找部门名称的辅助函数
 	const findDepartmentName = (id, nodes) => {
@@ -165,6 +174,7 @@
 		return ''
 	}
 	const onExpand = (keys) => {
+		// console.log(keys, 'onExpand')
 		expandedKeys.value = keys
 		autoExpandParent.value = false
 	}
@@ -172,6 +182,31 @@
 	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 = []
@@ -195,12 +230,16 @@
 	}
 
 	const handleOk = () => {
-		console.log('Selected Users:', selectedUsers.value)
+		console.log('Selected Users:', selectedUsers.value, checkedKeys.value)
+		emit('confirm', checkedKeys.value)
 	}
 
 	const handleCancel = () => {
 		emit('close')
 	}
+	onMounted(() => {
+		getOrgUserTreeRespectively()
+	})
 </script>
 
 <style scoped>

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

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