فهرست منبع

课程中心详情页面开发

canghailong 7 ماه پیش
والد
کامیت
bfd2abdaf1

+ 2 - 1
package.json

@@ -71,7 +71,8 @@
 		"vue3-colorpicker": "2.0.4",
 		"vue3-print-nb": "0.1.4",
 		"vue3-tree-org": "4.2.2",
-		"vuedraggable-es": "4.1.1"
+		"vuedraggable-es": "4.1.1",
+		"xgplayer": "^3.0.22"
 	},
 	"devDependencies": {
 		"@antfu/eslint-config": "0.29.4",

+ 118 - 0
pnpm-lock.yaml

@@ -182,6 +182,9 @@ importers:
       vuedraggable-es:
         specifier: 4.1.1
         version: 4.1.1(vue@3.2.44)
+      xgplayer:
+        specifier: ^3.0.22
+        version: 3.0.22(core-js@3.43.0)
     devDependencies:
       '@antfu/eslint-config':
         specifier: 0.29.4
@@ -1445,6 +1448,13 @@ packages:
   d3-timer@1.0.10:
     resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==}
 
+  d@1.0.2:
+    resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
+    engines: {node: '>=0.12'}
+
+  danmu.js@1.1.13:
+    resolution: {integrity: sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg==}
+
   data-view-buffer@1.0.2:
     resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
     engines: {node: '>= 0.4'}
@@ -1571,6 +1581,9 @@ packages:
     resolution: {integrity: sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==}
     hasBin: true
 
+  downloadjs@1.4.7:
+    resolution: {integrity: sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==}
+
   dunder-proto@1.0.1:
     resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
     engines: {node: '>= 0.4'}
@@ -1632,6 +1645,17 @@ packages:
     resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
     engines: {node: '>= 0.4'}
 
+  es5-ext@0.10.64:
+    resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
+    engines: {node: '>=0.10'}
+
+  es6-iterator@2.0.3:
+    resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
+
+  es6-symbol@3.1.4:
+    resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
+    engines: {node: '>=0.12'}
+
   esbuild@0.17.19:
     resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
     engines: {node: '>=12'}
@@ -1839,6 +1863,10 @@ packages:
     deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
     hasBin: true
 
+  esniff@2.0.1:
+    resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
+    engines: {node: '>=0.10'}
+
   espree@10.4.0:
     resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1873,15 +1901,24 @@ packages:
     resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
     engines: {node: '>=0.10.0'}
 
+  event-emitter@0.3.5:
+    resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
+
   event-source-polyfill@1.0.31:
     resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
 
   eventemitter3@2.0.3:
     resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==}
 
+  eventemitter3@4.0.7:
+    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
   exsolve@1.0.5:
     resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==}
 
+  ext@1.7.0:
+    resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
+
   extend@3.0.2:
     resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
 
@@ -2555,6 +2592,9 @@ packages:
     engines: {node: '>= 4.4.x'}
     hasBin: true
 
+  next-tick@1.1.0:
+    resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
+
   node-releases@2.0.19:
     resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
 
@@ -3241,6 +3281,9 @@ packages:
     resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
     engines: {node: '>=8'}
 
+  type@2.7.3:
+    resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
+
   typed-array-buffer@1.0.3:
     resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
     engines: {node: '>= 0.4'}
@@ -3533,6 +3576,16 @@ packages:
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
 
+  xgplayer-subtitles@3.0.22:
+    resolution: {integrity: sha512-2XjamtZnWS/r4QjesOC34JmuGD3QPbgeqkI4t5Gq19dN1CWNBP7nJ8pbGLuAeHswKjGg8LFRpnsic7xjc/XSyA==}
+    peerDependencies:
+      core-js: '>=3.12.1'
+
+  xgplayer@3.0.22:
+    resolution: {integrity: sha512-uVKffa02NxWnWMVzgnrU0HGwZFH0ymPHsD3zGxtV6oPPplA6EBLyh9N5q3b++J7jRs2usvKR2+WslT+je1RuwA==}
+    peerDependencies:
+      core-js: '>=3.12.1'
+
   xml-name-validator@4.0.0:
     resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
     engines: {node: '>=12'}
@@ -5113,6 +5166,15 @@ snapshots:
 
   d3-timer@1.0.10: {}
 
+  d@1.0.2:
+    dependencies:
+      es5-ext: 0.10.64
+      type: 2.7.3
+
+  danmu.js@1.1.13:
+    dependencies:
+      event-emitter: 0.3.5
+
   data-view-buffer@1.0.2:
     dependencies:
       call-bound: 1.0.4
@@ -5232,6 +5294,8 @@ snapshots:
     dependencies:
       minimatch: 3.1.2
 
+  downloadjs@1.4.7: {}
+
   dunder-proto@1.0.1:
     dependencies:
       call-bind-apply-helpers: 1.0.2
@@ -5346,6 +5410,24 @@ snapshots:
       is-date-object: 1.1.0
       is-symbol: 1.1.1
 
+  es5-ext@0.10.64:
+    dependencies:
+      es6-iterator: 2.0.3
+      es6-symbol: 3.1.4
+      esniff: 2.0.1
+      next-tick: 1.1.0
+
+  es6-iterator@2.0.3:
+    dependencies:
+      d: 1.0.2
+      es5-ext: 0.10.64
+      es6-symbol: 3.1.4
+
+  es6-symbol@3.1.4:
+    dependencies:
+      d: 1.0.2
+      ext: 1.7.0
+
   esbuild@0.17.19:
     optionalDependencies:
       '@esbuild/android-arm': 0.17.19
@@ -5638,6 +5720,13 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  esniff@2.0.1:
+    dependencies:
+      d: 1.0.2
+      es5-ext: 0.10.64
+      event-emitter: 0.3.5
+      type: 2.7.3
+
   espree@10.4.0:
     dependencies:
       acorn: 8.15.0
@@ -5670,12 +5759,23 @@ snapshots:
 
   esutils@2.0.3: {}
 
+  event-emitter@0.3.5:
+    dependencies:
+      d: 1.0.2
+      es5-ext: 0.10.64
+
   event-source-polyfill@1.0.31: {}
 
   eventemitter3@2.0.3: {}
 
+  eventemitter3@4.0.7: {}
+
   exsolve@1.0.5: {}
 
+  ext@1.7.0:
+    dependencies:
+      type: 2.7.3
+
   extend@3.0.2: {}
 
   fast-deep-equal@3.1.3: {}
@@ -6346,6 +6446,8 @@ snapshots:
       sax: 1.4.1
     optional: true
 
+  next-tick@1.1.0: {}
+
   node-releases@2.0.19: {}
 
   normalize-package-data@2.5.0:
@@ -7084,6 +7186,8 @@ snapshots:
 
   type-fest@0.8.1: {}
 
+  type@2.7.3: {}
+
   typed-array-buffer@1.0.3:
     dependencies:
       call-bound: 1.0.4
@@ -7448,6 +7552,20 @@ snapshots:
 
   wrappy@1.0.2: {}
 
+  xgplayer-subtitles@3.0.22(core-js@3.43.0):
+    dependencies:
+      core-js: 3.43.0
+      eventemitter3: 4.0.7
+
+  xgplayer@3.0.22(core-js@3.43.0):
+    dependencies:
+      core-js: 3.43.0
+      danmu.js: 1.1.13
+      delegate: 3.2.0
+      downloadjs: 1.4.7
+      eventemitter3: 4.0.7
+      xgplayer-subtitles: 3.0.22(core-js@3.43.0)
+
   xml-name-validator@4.0.0: {}
 
   xss@1.0.15:

+ 0 - 6
src/api/forum/forumApi.js

@@ -1,12 +1,6 @@
 import { baseRequest } from '@/utils/request'
 
 const request = (url, ...arg) => baseRequest(`/api/webapp/${url}`, ...arg)
-/**
- * 菜单
- *
- * @author yubaoshan
- * @date 2022-09-22 22:33:20
- */
 export default {
 	// 查询帖子列表接口
 	forumList(data) {

+ 33 - 0
src/api/student/classCentre.js

@@ -0,0 +1,33 @@
+import { baseRequest } from '@/utils/request'
+
+const request = (url, ...arg) => baseRequest(`/api/webapp/disk/${url}`, ...arg)
+export default {
+	//课程中心-课程-详情(学生端)
+	courseDetail(data) {
+		return request('coursecentry/detail', data, 'get')
+	},
+	//课程中心-课程-详情(学生端)
+	coursechapterList(data) {
+		return request('coursecentry/coursechapterList', data, 'get')
+	},
+	//课程中心-课时-详情
+	courseTimeDetail(data) {
+		return request('coursecentry/hourDetail', data, 'get')
+	},
+	//课程中心-笔记/笔记本-添加
+	notesAdd(data) {
+		return request('courseNotes/add', data)
+	},
+	//课程中心-笔记-分页列表
+	notesList(data) {
+		return request('courseNotes/page', data, 'get')
+	},
+	//课程中心-笔记-修改
+	notesEdit(data) {
+		return request('courseNotes/edit', data)
+	},
+	//课程中心-笔记-删除
+	notesEdit(data) {
+		return request('courseNotes/delete', data)
+	},
+}

+ 2 - 1
src/router/index.js

@@ -8,6 +8,7 @@ import { afterEach, beforeEach } from './scrollBehavior'
 import whiteListRouters from './whiteList'
 import portal from './portal'
 import forum from './forum'
+import student from './student'
 import userRoutes from '@/config/route'
 import tool from '@/utils/tool'
 import fullPageTool from './fullPageTool'
@@ -27,7 +28,7 @@ const routes_404 = [
 	}
 ]
 // 系统路由
-const routes = [...systemRouter, ...whiteListRouters, ...routes_404,...forum]
+const routes = [...systemRouter, ...whiteListRouters, ...routes_404,...forum,...student]
 
 const router = createRouter({
 	history: createWebHistory(),

+ 11 - 0
src/router/student.js

@@ -0,0 +1,11 @@
+const forum = [
+	{
+		path: '/student/classCentre',
+		hide: true,
+		component: () => import('@/views/student/classCentre/index.vue'),
+		meta: {
+			title: '课程中心详情'
+		}
+	}
+]
+export default forum

+ 111 - 0
src/views/student/classCentre/form.vue

@@ -0,0 +1,111 @@
+<template>
+	<xn-form-container
+		:width="500"
+		:get-container="false"
+		:visible="visible"
+		:destroy-on-close="true"
+		@close="onClose"
+		:mask="false"
+	>
+		<div v-if="itemObj.type == 1" style="height: 100%;">
+			<a-image width="100%" :src="sysConfig.FILE_URL + itemObj.url" v-if="fileType(itemObj.url)" />
+			<iframe
+				v-else
+				:src="`https://view.officeapps.live.com/op/view.aspx?src=${sysConfig.FILE_URL + itemObj.url}`"
+				frameborder="0"
+				width="100%"
+				height="100%"
+			></iframe>
+		</div>
+		<div v-if="itemObj.type == 2">
+			<a-card :bordered="false">
+				<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
+					<a-row :gutter="16">
+						<a-col :span="24">
+							<a-form-item name="postTitle">
+								<a-textarea v-model:value="formData.postTitle" placeholder="请输入" :rows="4" />
+							</a-form-item>
+						</a-col>
+					</a-row>
+				</a-form>
+				<div class="frc">
+					<a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
+				</div>
+				<note></note>
+			</a-card>
+		</div>
+	</xn-form-container>
+</template>
+
+<script setup>
+	import classCentre from '@/api/student/classCentre'
+	import { required } from '@/utils/formRules'
+	import sysConfig from '@/config/index'
+	import note from './note.vue'
+	const props = defineProps({
+		rightItem: {
+			type: [Array, Object],
+			required: () => {}
+		}
+	})
+	const itemObj = computed(() => props.rightItem)
+	const submitLoading = ref(false)
+	// 表单数据,也就是默认给一些数据
+	const formData = ref({})
+	const formRef = ref()
+	//tabs
+	const activeKey = ref('1')
+	// 默认是关闭状态
+	const visible = ref(false)
+	const emit = defineEmits({ successful: null })
+	// 模块ID
+	const moduleId = ref('')
+	// 打开抽屉
+	const onOpen = (record, module) => {
+		moduleId.value = module
+		visible.value = true
+	}
+	// 关闭抽屉
+	const onClose = () => {
+		visible.value = false
+	}
+	// 默认要校验的
+	const formRules = {
+		postTitle: [required('请输入标题')]
+	}
+	// 提交数据
+	const onSubmit = () => {
+		formRef.value
+			.validate()
+			.then(() => {
+				submitLoading.value = true
+				forumApi.submitForm(formData.value).then(() => {
+					onClose()
+					emit('successful')
+				})
+			})
+			.finally(() => {
+				submitLoading.value = false
+			})
+	}
+	const fileType = (str) => {
+		let index = str.lastIndexOf('.')
+		let ext = str.substr(index)
+		if(ext == '.xls' || ext == '.doc'){
+			return false
+		}else{
+			return true
+		}
+	}
+	// 调用这个函数将子组件的一些数据和方法暴露出去
+	defineExpose({
+		onOpen
+	})
+</script>
+<style scoped lang="less">
+	.frc {
+		display: flex;
+		justify-content: flex-end;
+		align-items: center;
+	}
+</style>

+ 136 - 0
src/views/student/classCentre/index.vue

@@ -0,0 +1,136 @@
+<template>
+	<a-layout>
+		<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible collapsedWidth="0">
+			<div class="classTitle">
+				<div>{{ classDetail.courseName }}</div>
+			</div>
+			<a-menu v-model:openKeys="openKeys" v-model:selectedKeys="selectedKeys" theme="dark" mode="inline">
+				<template v-for="(item, idx) in classTimeList" :key="item.id">
+					<template v-if="!item.classHours">
+						<a-menu-item :key="item.id" v-for="(item, idx) in classTimeList">
+							<span class="mr-2">
+								<CheckSquareOutlined v-if="selectedKeys.includes(item.id)" />
+								<BorderOutlined v-else />
+							</span>
+							<span>{{ item.name }}</span>
+						</a-menu-item>
+					</template>
+					<template v-else>
+						<a-sub-menu :key="String(idx + 1)" v-for="(item, idx) in classTimeList">
+							<template #title>{{ item.name }}</template>
+							<a-menu-item :key="itemc.id" v-for="(itemc, idxc) in item.classHours">
+								<span class="mr-2">
+									<CheckSquareOutlined v-if="selectedKeys.includes(itemc.id)" />
+									<BorderOutlined v-else />
+								</span>
+								<span>{{ itemc.name }}</span>
+							</a-menu-item>
+						</a-sub-menu>
+					</template>
+				</template>
+			</a-menu>
+		</a-layout-sider>
+		<a-layout>
+			<a-layout-header style="padding: 0 20px">
+				<div class="flex-between">
+					<div>
+						<menu-unfold-outlined
+							v-if="collapsed"
+							class="trigger"
+							@click="() => (collapsed = !collapsed)"
+							style="font-size: 30px; color: #fff"
+						/>
+						<menu-fold-outlined
+							v-else
+							class="trigger"
+							@click="() => (collapsed = !collapsed)"
+							style="font-size: 30px; color: #fff"
+						/>
+					</div>
+				</div>
+			</a-layout-header>
+			<a-layout-content>
+				<a-card :bordered="false">
+					<div id="player" style="height: 900px; width: 100%"></div>
+					<rightMenu :dataList="classTimeData" @callback="editVideo"></rightMenu>
+				</a-card>
+				<a-card :bordered="false" class="mt-2"></a-card>
+			</a-layout-content>
+		</a-layout>
+	</a-layout>
+</template>
+
+<script setup name="classCentre">
+	import classCentre from '@/api/student/classCentre'
+	import rightMenu from './rightMenu.vue'
+	import { useRoute, useRouter } from 'vue-router'
+	import sysConfig from '@/config/index'
+	import XGPlayer from 'xgplayer'
+	import 'xgplayer/dist/index.min.css'
+	import TextTrack from 'xgplayer/es/plugins/track'
+
+	const route = useRoute()
+	const router = useRouter()
+	const classDetail = ref({})
+	const classTimeList = ref([])
+	const classTimeData = ref({})
+	const openKeys = ref(['1'])
+	const selectedKeys = ref([])
+	const collapsed = ref(false)
+
+	const getClassData = () => {
+		classCentre.courseDetail({ courseId: route.query.id }).then((data) => {
+			classDetail.value = data
+			classCentre.coursechapterList({ courseId: data.courseId }).then((data) => {
+				classTimeList.value = data
+				selectedKeys.value = [data[0].classHours[0].id]
+				classCentre.courseTimeDetail({ id: selectedKeys.value[0] }).then((data) => {
+					classTimeData.value = data.courseRelates
+					videoStart();
+				})
+			})
+		})
+	}
+	getClassData()
+
+	const player = ref()
+
+	const videoStart = () => {
+		player.value = new XGPlayer({
+			lang: 'zh',
+			id: 'player',
+			url: sysConfig.FILE_URL + classTimeData.value.filter((r) => r.funcType == 1)[0].url, // 视频文件路径
+			width: '100%',
+			height: 900,
+			plugins: [TextTrack],
+			texttrack: {
+				list: [
+					{
+						id: 'vtt1',
+						url: sysConfig.FILE_URL + classTimeData.value.filter((r) => r.funcType == 3)[0].url,
+						language: 'zh',
+						text: '中文字幕'
+					}
+				]
+			}
+		})
+	}
+	const editVideo = (e) => {
+		// player.value.src = e.url
+	}
+</script>
+<style scoped lang="less">
+	.classTitle {
+		height: 64px;
+		width: 200px;
+		font-size: 18px;
+		color: #ccc;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+	.flex-between {
+		display: flex;
+		justify-content: space-between;
+	}
+</style>

+ 102 - 0
src/views/student/classCentre/note.vue

@@ -0,0 +1,102 @@
+<template>
+	<a-list class="demo-loadmore-list" :loading="initLoading" item-layout="horizontal" :data-source="listData">
+		<template #loadMore>
+			<div
+				v-if="!initLoading && !loading"
+				:style="{ textAlign: 'center', marginTop: '12px', height: '32px', lineHeight: '32px' }"
+			>
+				<a-button @click="onLoadMore">loading more</a-button>
+			</div>
+		</template>
+		<template #renderItem="{ item }">
+			<a-list-item>
+				<a-skeleton avatar :title="false" :loading="!!item.loading" active>
+					<div>
+						<a-list-item-meta>
+							<template #title>
+								<a href="https://www.antdv.com/">{{ item.title }}</a>
+							</template>
+							<template #avatar>
+								<a-avatar :src="item.avatar" />
+							</template>
+						</a-list-item-meta>
+						{{ item.content }}
+						<div class="flc">
+							<div v-for="{ icon, text } in actions" :key="icon">
+								<component :is="icon" style="margin-right: 8px" />
+								{{ text }}
+							</div>
+						</div>
+					</div>
+				</a-skeleton>
+			</a-list-item>
+		</template>
+	</a-list>
+</template>
+
+<script setup>
+	import classCentre from '@/api/student/classCentre'
+	import { StarOutlined, LikeOutlined, MessageOutlined } from '@ant-design/icons-vue'
+	const props = defineProps({
+		noteData: {
+			type: [Array, Object],
+			default: () => {}
+		}
+	})
+	const count = 3
+
+	const initLoading = ref(true)
+	const loading = ref(false)
+	const data = ref([])
+	const listData = ref([])
+	const actions = [
+		{ icon: StarOutlined, text: '156' },
+		{ icon: LikeOutlined, text: '156' },
+		{ icon: MessageOutlined, text: '2' }
+	]
+	onMounted(() => {
+		for (let i = 0; i < 23; i++) {
+			listData.value.push({
+				href: 'https://www.antdv.com/',
+				title: `ant design vue part ${i}`,
+				avatar: 'https://joeschmoe.io/api/v1/random',
+				loading: false,
+				description: 'Ant Design, a design language for background applications, is refined by Ant UED Team.',
+				content:
+					'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.'
+			})
+		}
+		loading.value = false
+		initLoading.value = false
+	})
+
+	const onLoadMore = () => {
+		loading.value = true
+		for (let i = 0; i < 23; i++) {
+			listData.value.push({
+				href: 'https://www.antdv.com/',
+				title: `ant design vue part ${i}`,
+				avatar: 'https://joeschmoe.io/api/v1/random',
+				loading: false,
+				description: 'Ant Design, a design language for background applications, is refined by Ant UED Team.',
+				content:
+					'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.'
+			})
+		}
+		loading.value = false
+		initLoading.value = false
+	}
+	// 调用这个函数将子组件的一些数据和方法暴露出去
+	defineExpose({})
+</script>
+<style scoped lang="less">
+	.flc {
+		display: flex;
+		align-items: center;
+	}
+	.fcbc {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+</style>

+ 161 - 0
src/views/student/classCentre/rightMenu.vue

@@ -0,0 +1,161 @@
+<template>
+	<div class="rightBtn">
+		<a-menu v-model:selectedKeys="selectedKeys" style="width: 60px" mode="inline" theme="dark">
+			<a-menu-item :key="item.key" class="btnItem" v-for="(item, idx) in listBtn" @click="selectBtn(item)">
+				<div class="fcc">
+					<component :is="item.icon"></component>
+				</div>
+				<div class="fcc">{{ item.title }}</div>
+			</a-menu-item>
+		</a-menu>
+		<rightContent ref="formRef" :rightItem="rightItem"></rightContent>
+	</div>
+</template>
+
+<script setup name="rightMenu">
+	import classCentre from '@/api/student/classCentre'
+	import rightContent from './form.vue'
+	import sysConfig from '@/config/index'
+	import axios from 'axios'
+	const emit = defineEmits({ callback: null })
+	import {
+		PlaySquareOutlined,
+		FileTextOutlined,
+		AlignCenterOutlined,
+		SnippetsOutlined,
+		CopyOutlined,
+		ReadOutlined,
+		QuestionCircleOutlined
+	} from '@ant-design/icons-vue'
+	const formRef = ref()
+	const rightItem = ref({})
+	const selectedKeys = ref([''])
+	const btnList = ref([
+		{
+			title: '视频',
+			key: '1',
+			icon: PlaySquareOutlined,
+			type: 0
+		},
+		{
+			title: '讲义',
+			key: '2',
+			icon: FileTextOutlined,
+			type: 1
+		},
+		{
+			title: '字幕',
+			key: '3',
+			icon: AlignCenterOutlined,
+			type: 1
+		},
+		{
+			title: '作业',
+			key: '4',
+			icon: SnippetsOutlined,
+			type: 3
+		},
+		{
+			title: '测验',
+			key: '5',
+			icon: CopyOutlined,
+			type: 3
+		},
+		{
+			title: '笔记',
+			key: '6',
+			icon: ReadOutlined,
+			type: 2
+		},
+		{
+			title: '问答',
+			key: '7',
+			icon: QuestionCircleOutlined,
+			type: 3
+		}
+	])
+	const selectBtn = (event) => {
+		event.url = sysConfig.FILE_URL + event.url
+		if (event.key == 3) {
+			// GetSrtInfo(event.url)
+		}
+		if (event.type == 3) {
+			
+		} else {
+			if (event.key == 1) {
+				emit('callback', event)
+			} else {
+				rightItem.value = event
+				formRef.value.onOpen()
+			}
+		}
+	}
+	const props = defineProps({
+		dataList: {
+			type: [Array, Object],
+			default: () => []
+		}
+	})
+	const listBtn = computed(() => {
+		return btnList.value.map((r) => {
+			const match = props.dataList.length && props.dataList.find((e) => r.key == e.funcType)
+			return match ? { ...r, ...match } : r
+		})
+	})
+	//获取字幕内容,发送一个get请求
+	function GetSrtInfo(srtUrl) {
+		axios
+			.get(`${srtUrl}`)
+			.then(function (response) {
+				console.log('🚀 ~ GetSrtInfo ~ response:', response.data)
+				let textList = response.data
+					.split(/\n\s\n/)
+					.filter((item) => item != '')
+					.map((item, index) => {
+						let textItem = item.split(/\n/)
+						return {
+							index: index,
+							sort: textItem[0],
+							text: textItem[2],
+							startTime: ToSeconds(textItem[1].split(' --> ')[0]),
+							endTime: ToSeconds(textItem[1].split(' --> ')[1]),
+							timeLine: textItem[1]
+						}
+					})
+				btnList.value[2].srtInfoList = textList
+				console.log('解析之后的字幕内容', textList)
+			})
+			.catch(function (error) {
+				console.log(error)
+			})
+	}
+	//将时间转化为秒
+	function ToSeconds(t) {
+		var s = 0.0
+		if (t) {
+			var p = t.split(':')
+			for (let i = 0; i < p.length; i++) {
+				s = s * 60 + parseFloat(p[i].replace(',', '.'))
+			}
+		}
+		return s
+	}
+</script>
+<style scoped lang="less">
+	.rightBtn {
+		position: fixed;
+		right: 0;
+		top: 100px;
+		z-index: 99999;
+	}
+	.fcc {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+	:deep(.btnItem) {
+		height: auto;
+		line-height: normal;
+		padding: 10px 0 !important;
+	}
+</style>