更新学习路径推荐

develoop
JayChou 3 months ago
parent d8550d2564
commit 68af92d3a8
  1. 10
      src/api/course.ts
  2. 3
      src/api/types/courseType.ts
  3. 11
      src/router/module/constRouter/index.ts
  4. 1
      src/views/course/components/courseTree.vue
  5. 257
      src/views/course/index.vue
  6. 16
      src/views/home/components/Graph.vue
  7. 1
      src/views/knowledge/index.vue
  8. 358
      src/views/roadbedRecommendation/components/mountNode.vue
  9. 149
      src/views/roadbedRecommendation/index.vue

@ -1,5 +1,5 @@
import request from '@/utils/requset' import request from '@/utils/requset'
import { GetCourseObjectivesList} from './types/courseType'
// 获取课程列表 // 获取课程列表
export const getCourseList = (params: object) => { export const getCourseList = (params: object) => {
@ -56,3 +56,11 @@ export const getCourseAtlasTow = (params: object) => {
params, params,
}) })
} }
// 获取课程目标
export const getCourseObjectivesList = (params:GetCourseObjectivesList) => {
return request({
url:'/api/course_objectives/list/' + params.id,
})
}

@ -0,0 +1,3 @@
export interface GetCourseObjectivesList {
id:string | number
}

@ -79,7 +79,16 @@ export const constRouter: any = [
title: '知识点首页', title: '知识点首页',
hidden: true, hidden: true,
}, },
} },
{
path:'/roadbedRecommendation',
component:() => import('@/views/roadbedRecommendation/index.vue'),
name:'RoadbedRecommendation',
meta: {
title: '学习推荐',
hidden: false,
},
},
// { // {
// path: '/talentDevelopment', // 人才培养 // path: '/talentDevelopment', // 人才培养
// component: () => import('@/views/talentDevelopment/index.vue'), // component: () => import('@/views/talentDevelopment/index.vue'),

@ -29,5 +29,6 @@ const defaultProps = {
} }
:deep(.el-tree-node__content) { :deep(.el-tree-node__content) {
font-size: 16px; font-size: 16px;
height: 32px;
} }
</style> </style>

@ -7,8 +7,11 @@
:height="800" :height="800"
:index="courseId" :index="courseId"
:id="courseId" :id="courseId"
:radio1="radio1"
:checkList="checkList"
v-show="flag === 1" v-show="flag === 1"
@clickGraph="clickGraphChange" @clickGraph="clickGraphChange"
v-if="isSetting"
/> />
<div class="video" v-if="flag === 2"> <div class="video" v-if="flag === 2">
<video id="video" width="100%" height="100%" controls> <video id="video" width="100%" height="100%" controls>
@ -31,14 +34,34 @@
<div v-show="flag != 1" class="back" @click="flag = 1"> <div v-show="flag != 1" class="back" @click="flag = 1">
<el-icon><Back /></el-icon> <el-icon><Back /></el-icon>
</div> </div>
<div class="show-graph-sett" @click="showSetting" v-if="!showFlag">
<el-icon><ArrowLeftBold /></el-icon>
</div>
<div
:class="showFlag ? 'graph-setting' : 'graph-setting-hidden'"
@mouseleave="heddinSetting"
@mouseenter="clearHeddin"
>
<el-radio-group v-model="radio1" @change="radio1Change">
<el-radio value="2" size="mini" style="margin: 0">二层</el-radio>
<el-radio value="3" size="mini" style="margin: 0">三层</el-radio>
<el-radio value="4" size="mini">四层</el-radio>
</el-radio-group>
<el-checkbox-group v-model="checkList" @change="checkListChange">
<el-checkbox label="包含" value="Value A" style="margin: 0" />
<el-checkbox label="依赖" value="Value B" style="margin: 0" />
<el-checkbox label="顺序" value="Value C" />
</el-checkbox-group>
<div class="reset" @click="reset">重置</div>
</div>
</div> </div>
<div class="cours-target"> <div class="cours-target">
<div class="title">课程目标</div> <div class="title">课程目标</div>
<el-table :data="tableData" border style="width: 100%"> <el-table :data="courseObjectivesTrees" border style="width: 100%">
<el-table-column prop="date" label="课程分项目标"/> <el-table-column prop="name" label="课程分项目标" />
<el-table-column prop="name" label="支撑知识点" /> <el-table-column prop="name" label="支撑知识点" />
<el-table-column prop="address" width="180" label="占比" /> <el-table-column prop="name" width="180" label="占比" />
</el-table> </el-table>
</div> </div>
<div class="resource"> <div class="resource">
@ -124,23 +147,49 @@
Vues 框架的大门 Vues 框架的大门
</div> </div>
</div> </div>
<div class="hours">
<div class="item">
<div style="display: flex">
<div class="label">学时</div>
<div class="content">9H</div>
</div>
<div style="display: flex; margin-left: 20px">
<div class="label">学分</div>
<div class="content">9</div>
</div>
</div>
<div class="item">
<div class="label">先修</div>
<div class="content">Vue.js程序设计与实现</div>
</div>
<div class="item">
<div class="label">后继</div>
<div class="content">前端工程化模块化</div>
</div>
<div class="item">
<div class="label">知识点</div>
<div class="content">webpackvite</div>
</div>
</div>
</div> </div>
<div class="chapter"> <div class="chapter">
<courseTree :chapterList="chapterList" /> <courseTree :chapterList="chapterList" />
</div> </div>
<div class="student-list"> <div class="student-list"></div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref, nextTick } from 'vue'
import Graph from '../home/components/Graph.vue' import Graph from '../home/components/Graph.vue'
import courseTree from './components/courseTree.vue' import courseTree from './components/courseTree.vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { getCourseChapter, getCourseFiles } from '@/api/course' import {
getCourseChapter,
getCourseFiles,
getCourseObjectivesList,
} from '@/api/course'
import { ElLoading } from 'element-plus' import { ElLoading } from 'element-plus'
import { filterFilesType } from '@/utils/filters' import { filterFilesType } from '@/utils/filters'
// @ts-ignore // @ts-ignore
@ -160,7 +209,8 @@ const getCourseChapterEvent = async () => {
background: 'rgba(255, 255, 255, 0.7)', background: 'rgba(255, 255, 255, 0.7)',
}) })
const res = await getCourseChapter({ const res = await getCourseChapter({
courseId: Route.query.id, // courseId: Route.query.id,
courseId: '2cd6b5b62c344fd0becff6010cba566e',
}) })
chapterList.value = res.data chapterList.value = res.data
console.log(res) console.log(res)
@ -169,28 +219,7 @@ const getCourseChapterEvent = async () => {
} }
getCourseChapterEvent() getCourseChapterEvent()
const activeName = ref<string>('1') const activeName = ref<string>('1')
const tableData = [ const courseObjectivesTrees = ref([])
{
date: 'Vue.js设计与实现',
name: 'HTML&CSS&JavaScript',
address: '10%',
},
{
date: 'Vue.js设计与实现',
name: 'HTML&CSS&JavaScript',
address: '20%',
},
{
date: 'Vue.js设计与实现',
name: 'HTML&CSS&JavaScript',
address: '20%',
},
{
date: 'Vue.js设计与实现',
name: 'HTML&CSS&JavaScript',
address: '20%',
},
]
const handleClick = (e: any) => { const handleClick = (e: any) => {
console.log(e) console.log(e)
} }
@ -287,6 +316,59 @@ const clickGraphChange = async (id: number) => {
// courseFilesList.value = filterFilesType(res.data) // courseFilesList.value = filterFilesType(res.data)
// isTabsLoading.value = false // isTabsLoading.value = false
} }
//
//
const radio1 = ref(null)
const radio1Change = (e: any) => {
console.log(e)
}
//
const checkList = ref([])
const checkListChange = (e: any) => {
console.log(e)
}
//
const isSetting = ref(true)
const reset = () => {
radio1.value = null
checkList.value = []
tabLoading.value = true
isSetting.value = false
nextTick(() => {
isSetting.value = true
tabLoading.value = false
})
}
//
const showFlag = ref(false)
const showSetting = () => {
showFlag.value = !showFlag.value
// if (showFlag.value) {
// ;(document.querySelector('.graph-setting-hidden') as any).style.display = 'flex'
// } else {
// ;(document.querySelector('.graph-setting') as any).style.display = 'none'
// }
}
//
let timer: any = null
const heddinSetting = () => {
timer = setTimeout(() => {
// (document.querySelector('.graph-setting') as any).style.display = 'none'
showFlag.value = false
}, 500)
}
const clearHeddin = () => {
if (timer) {
clearTimeout(timer)
}
}
//
const getCourseObjectivesListEvent = async () => {
const res = await getCourseObjectivesList({ id: Route.query.id as string })
courseObjectivesTrees.value = res.data[0].courseObjectivesTrees
console.log(courseObjectivesTrees.value, '111212')
}
getCourseObjectivesListEvent()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -344,6 +426,87 @@ const clickGraphChange = async (id: number) => {
justify-content: center; justify-content: center;
} }
} }
.graph-setting {
position: absolute;
bottom: 30px;
right: 10px;
width: 80px;
height: 270px;
animation: fadeIn 0.3s ease-in-out; /* 持续时间2秒,缓动函数为ease-in-out */
display: flex;
flex-direction: column;
align-items: center;
.reset {
width: 50px;
height: 30px;
text-align: center;
line-height: 30px;
border-radius: 5px;
background-color: #6da0ff;
color: #fff;
margin-top: 20px;
cursor: pointer;
}
}
.graph-setting-hidden {
position: absolute;
bottom: 30px;
right: -90px;
width: 80px;
height: 270px;
animation: fadeOut 0.3s ease-in-out; /* 持续时间2秒,缓动函数为ease-in-out */
flex-direction: column;
align-items: center;
.reset {
width: 50px;
height: 30px;
text-align: center;
line-height: 30px;
border-radius: 5px;
background-color: #6da0ff;
color: #fff;
margin-top: 20px;
cursor: pointer;
}
}
@keyframes fadeIn {
from {
right: -60px;
}
to {
right: 10px;
}
}
@keyframes fadeOut {
from {
right: 10px;
}
to {
right: -60px;
display: none;
}
}
.show-graph-sett {
position: absolute;
bottom: 30px;
right: -20px;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #ebebeb;
display: flex;
align-items: center;
padding-left: 5px;
cursor: pointer;
transition: all 0.3s;
i {
font-size: 14px;
}
}
}
.show-graph-sett:hover {
transform: scale(1.3);
} }
.resource { .resource {
width: 100%; width: 100%;
@ -390,7 +553,7 @@ const clickGraphChange = async (id: number) => {
flex-direction: column; flex-direction: column;
.course-info { .course-info {
width: 100%; width: 100%;
height: 600px; height: 640px;
background-color: #fff; background-color: #fff;
.image { .image {
width: 100%; width: 100%;
@ -401,7 +564,7 @@ const clickGraphChange = async (id: number) => {
} }
.description { .description {
overflow-y: auto; overflow-y: auto;
height: 380px; height: 260px;
padding: 10px 20px; padding: 10px 20px;
text-indent: 2em; /* 2em 约等于两个汉字的宽度 */ text-indent: 2em; /* 2em 约等于两个汉字的宽度 */
font-size: 18px; font-size: 18px;
@ -410,10 +573,24 @@ const clickGraphChange = async (id: number) => {
.description-box { .description-box {
} }
} }
.hours {
padding: 10px 20px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
.item {
display: flex;
height: 30px;
align-items: center;
font-size: 14px;
.label {
color: #999;
}
}
}
} }
.chapter { .chapter {
overflow-y: auto; overflow-y: auto;
height: 500px; height: 460px;
width: 100%; width: 100%;
margin-top: 20px; margin-top: 20px;
background-color: #fff; background-color: #fff;
@ -437,4 +614,16 @@ const clickGraphChange = async (id: number) => {
:deep(.docx-wrapper) { :deep(.docx-wrapper) {
background-color: #fff; background-color: #fff;
} }
:deep(.el-radio-group, .el-checkbox-group) {
display: flex !important;
flex-direction: column;
align-items: center;
}
:deep(.el-checkbox-group) {
display: flex !important;
flex-direction: column;
align-items: center;
margin-top: 20px;
}
</style> </style>

@ -4,7 +4,7 @@
<script lang="ts" setup> <script lang="ts" setup>
// import { useRouter } from 'vue-router' // import { useRouter } from 'vue-router'
import { onMounted, ref, reactive } from 'vue' import { onMounted, ref, reactive ,watch} from 'vue'
import ForceGraph3D from '3d-force-graph' import ForceGraph3D from '3d-force-graph'
//@ts-ignore //@ts-ignore
import { import {
@ -40,6 +40,18 @@ const props = defineProps({
type: String || Number, type: String || Number,
required: true, required: true,
}, },
checkList:{
},
radio1:{}
})
watch(() => props.radio1,(newVal:any) => {
console.log(newVal);
} )
watch(() => props.checkList,(newVal:any) => {
console.log(newVal);
} ) } )
const emits = defineEmits(['clickGraph']) const emits = defineEmits(['clickGraph'])
// const nodeData = ref({}) // const nodeData = ref({})
@ -178,7 +190,7 @@ onMounted(async () => {
highlightNodes.clear() highlightNodes.clear()
highlightLinks.clear() highlightLinks.clear()
if (node) { if (node && node.neighbors ) {
highlightNodes.add(node) highlightNodes.add(node)
node.neighbors.forEach((neighbor:any) => highlightNodes.add(neighbor)) node.neighbors.forEach((neighbor:any) => highlightNodes.add(neighbor))
node.links.forEach((link :any) => highlightLinks.add(link)) node.links.forEach((link :any) => highlightLinks.add(link))

@ -156,6 +156,7 @@ const getCourseFilesEvent = async () => {
isTabsLoading.value = false isTabsLoading.value = false
// console.log(courseFilesList.value) // console.log(courseFilesList.value)
let arr = [] let arr = []
if(res.data) return loadingInstance.close()
for (const key in courseFilesList.value) { for (const key in courseFilesList.value) {
if(courseFilesList.value[key]){ if(courseFilesList.value[key]){
arr.push({[key]:courseFilesList.value[key]}) arr.push({[key]:courseFilesList.value[key]})

@ -0,0 +1,358 @@
<template>
<div id="mountNode"></div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue'
onMounted(() => {
/**
* 该示例演示自定义边和节点实现资金流转图效果
* by 十吾
*/
var colorMap = {
凭证开立: '#72CC4A',
凭证转让: '#1A91FF',
凭证融资: '#FFAA15',
第一阶段: '#72CC4A',
第二阶段: '#1A91FF',
第三阶段: '#FFAA15',
}
var data = {
nodes: [
{
id: '1',
label: '前端',
},
{
id: '2',
label: 'HTML&CSS&Javascript',
},
{
id: '3',
label: 'HTML&CSS&Javascript',
},
{
id: '4',
label: 'React.js',
},
{
id: '5',
label: 'Vue.js',
},
{
id: '6',
label: '工程化脚手架',
},
{
id: '7',
label: '工程化脚手架',
},
{
id: '8',
label: 'HTML&CSS&Javascript',
},
{
id: '9',
label: 'HTML&CSS&Javascript',
},
],
edges: [
{
source: '1',
target: '2',
data: {
type: '第一阶段',
amount: '10学时',
date: '学习基础知识',
},
},
{
source: '1',
target: '3',
data: {
type: '第一阶段',
amount: '10学时',
date: '学习基础知识',
},
},
{
source: '2',
target: '5',
data: {
type: '第二阶段',
amount: '15学时',
date: '学习Vue框架',
},
},
{
source: '5',
target: '6',
data: {
type: '第三阶段',
amount: '8学时',
date: '学习前端开发工程化',
},
},
{
source: '3',
target: '4',
data: {
type: '第二阶段',
amount: '18学时',
date: '学习React.js',
},
},
{
source: '4',
target: '7',
data: {
type: '第三阶段',
amount: '8学时',
date: '学习前端开发工程化',
},
},
{
source: '1',
target: '8',
data: {
type: '第一阶段',
amount: '10学时',
date: '学习基础知识',
},
},
{
source: '1',
target: '9',
data: {
type: '第一阶段',
amount: '10学时',
date: '学习基础知识',
},
},
],
}
G6.registerNode(
'round-rect',
{
drawShape: function drawShape(cfg, group) {
var width = cfg.style.width
var stroke = cfg.style.stroke
var rect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -15,
width: width,
height: 30,
radius: 15,
stroke: stroke,
lineWidth: 1.2,
fillOpacity: 1,
},
})
var circleLeft = group.addShape('circle', {
attrs: {
x: -width / 2,
y: 0,
r: 3,
fill: stroke,
},
})
var circleRight = group.addShape('circle', {
attrs: {
x: width / 2,
y: 0,
r: 3,
fill: stroke,
},
})
return rect
},
getAnchorPoints: function getAnchorPoints() {
return [
[0, 0.5],
[1, 0.5],
]
},
update: function update(cfg, item) {
var group = item.getContainer()
var children = group.get('children')
var node = children[0]
var circleLeft = children[1]
var circleRight = children[2]
var stroke = cfg.style.stroke,
labelStyle = cfg.labelStyle
if (stroke) {
node.attr('stroke', stroke)
circleLeft.attr('fill', stroke)
circleRight.attr('fill', stroke)
}
},
},
'single-shape',
)
G6.registerEdge('polyline', {
itemType: 'edge',
draw: function draw(cfg, group) {
var startPoint = cfg.startPoint
var endPoint = cfg.endPoint
var centerPoint = {
x: (startPoint.x + endPoint.x) / 2,
y: (startPoint.y + endPoint.y) / 2,
}
var Ydiff = endPoint.y - startPoint.y
var slope = Ydiff !== 0 ? 500 / Math.abs(Ydiff) : 0
var cpOffset = 16
var offset = Ydiff < 0 ? cpOffset : -cpOffset
var line1EndPoint = {
x: startPoint.x + slope,
y: endPoint.y + offset,
}
var line2StartPoint = {
x: line1EndPoint.x + cpOffset,
y: endPoint.y,
}
//
var controlPoint = {
x:
((line1EndPoint.x - startPoint.x) * (endPoint.y - startPoint.y)) /
(line1EndPoint.y - startPoint.y) +
startPoint.x,
y: endPoint.y,
}
var path = [
['M', startPoint.x, startPoint.y],
['L', line1EndPoint.x, line1EndPoint.y],
[
'Q',
controlPoint.x,
controlPoint.y,
line2StartPoint.x,
line2StartPoint.y,
],
['L', endPoint.x, endPoint.y],
]
if (Ydiff === 0) {
path = [
['M', startPoint.x, startPoint.y],
['L', endPoint.x, endPoint.y],
]
}
var line = group.addShape('path', {
attrs: {
path: path,
stroke: colorMap[cfg.data.type],
lineWidth: 1.2,
endArrow: false,
},
})
var labelLeftOffset = 8
var labelTopOffset = 8
// amount
var amount = group.addShape('text', {
attrs: {
text: cfg.data.amount,
x: line2StartPoint.x + labelLeftOffset,
y: endPoint.y - labelTopOffset - 2,
fontSize: 14,
textAlign: 'left',
textBaseline: 'middle',
fill: '#000000D9',
},
})
// type
var type = group.addShape('text', {
attrs: {
text: cfg.data.type,
x: line2StartPoint.x + labelLeftOffset,
y: endPoint.y - labelTopOffset - amount.getBBox().height - 2,
fontSize: 10,
textAlign: 'left',
textBaseline: 'middle',
fill: '#000000D9',
},
})
// date
var date = group.addShape('text', {
attrs: {
text: cfg.data.date,
x: line2StartPoint.x + labelLeftOffset,
y: endPoint.y + labelTopOffset + 4,
fontSize: 12,
fontWeight: 300,
textAlign: 'left',
textBaseline: 'middle',
fill: '#000000D9',
},
})
return line
},
})
var graph = new G6.Graph({
container: 'mountNode',
width: window.innerWidth - 19,
height: 600,
layout: {
type: 'dagre',
rankdir: 'LR',
nodesep: 30,
ranksep: 100,
},
modes: {
default: ['drag-canvas'],
},
defaultNode: {
shape: 'round-rect',
labelCfg: {
style: {
fill: '#000000A6',
fontSize: 10,
},
},
style: {
stroke: '#72CC4A',
width: 150,
},
},
defaultEdge: {
shape: 'polyline',
},
})
graph.data(data)
graph.render()
var edges = graph.getEdges()
edges.forEach(function (edge:any) {
var line = edge.getKeyShape()
var stroke = line.attr('stroke')
var targetNode = edge.getTarget()
targetNode.update({
style: {
stroke: stroke,
},
})
})
graph.paint()
})
</script>
<style lang="scss" scoped>
#mountNode {
width: 100%;
height: 100%;
}
</style>

@ -0,0 +1,149 @@
<template>
<div class="path-title">
<div class="title">前端课程学习路径推荐</div>
<div class="setting">
<el-select
v-model="courseName"
placeholder="请选择课程"
size="large"
style="width: 200px"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select>
<el-button type="primary" style="margin-left: 20px">切换</el-button>
<el-button
type="primary"
style="margin-left: 20px"
@click="Router.push('/')"
>
返回课程首页
</el-button>
</div>
</div>
<div class="path-container">
<mountNode />
</div>
<div class="statistics">
<div class="title">学习路径</div>
<el-card style="margin-top: 50px">
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="100px" />
<el-table-column prop="content" label="学习路径">
<template v-slot="{ row }">
<div>
<el-tag
style="margin-right: 10px"
type="primary"
v-for="(item, index) in row.content"
:key="index"
>
{{ item }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="num" label="数量" width="100px" />
<el-table-column prop="hours" label="学时" width="100px" />
</el-table>
<div class="path-description">
前端课程主要分为四个分支每个分支包含很多知识点其中分为三个阶段在每个阶段有不同的知识点在学习后继知识点需要学习前置知识点
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import mountNode from './components/mountNode.vue'
import { useRouter } from 'vue-router'
const Router = useRouter()
const courseName = ref('')
const options = [
{
id: 1,
label: 'Vue.js',
},
]
const tableData = [
{
content: ['HTML', ' CSS', 'JavaScript', 'Node.js', 'Vue.js', 'React.js'],
num: 10,
hours: 7.5,
},
{
content: ['HTML', ' CSS', 'JavaScript', 'Node.js', 'Vue.js', 'React.js'],
num: 10,
hours: 7.5,
},
{
content: ['HTML', ' CSS', 'JavaScript', 'Node.js', 'Vue.js', 'React.js'],
num: 10,
hours: 7.5,
},
{
content: ['HTML', ' CSS', 'JavaScript', 'Node.js', 'Vue.js', 'React.js'],
num: 10,
hours: 7.5,
},
{
content: ['HTML', ' CSS', 'JavaScript', 'Node.js', 'Vue.js', 'React.js'],
num: 10,
hours: 7.5,
},
]
</script>
<style lang="scss" scoped>
.path-container {
width: 100%;
height: 600px;
background-color: #f5f6fd;
}
.path-title {
position: relative;
width: 100%;
height: 50px;
// background-color: skyblue;
display: flex;
justify-content: center;
.title {
font-size: 18px;
height: 100%;
line-height: 50px;
font-weight: 600;
}
.setting {
position: absolute;
left: 50%;
top: 50%;
transform: translate(50%, -50%);
display: flex;
align-items: center;
}
}
.statistics {
width: 100%;
margin-top: 50px;
padding: 0 30px 30px 30px;
.title {
font-size: 32px;
font-weight: 600;
text-align: center;
// margin: 30px 0;
}
}
.path-description{
// padding: 20px 30px;
margin: 30px 0;
font-size: 14px;
}
</style>
Loading…
Cancel
Save