Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-resource
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-resource
Commits
fd7bc1bd
提交
fd7bc1bd
authored
6月 11, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 对接数据可视化接口
上级
840c9345
隐藏空白字符变更
内嵌
并排
正在显示
5 个修改的文件
包含
175 行增加
和
841 行删除
+175
-841
api.ts
src/modules/teach/chart/api.ts
+17
-0
Learning.vue
src/modules/teach/chart/views/Learning.vue
+17
-351
Origin.vue
src/modules/teach/chart/views/Origin.vue
+83
-363
Resource.vue
src/modules/teach/chart/views/Resource.vue
+11
-64
Visualization.vue
src/modules/teach/chart/views/Visualization.vue
+47
-63
没有找到文件。
src/modules/teach/chart/api.ts
0 → 100644
浏览文件 @
fd7bc1bd
import
httpRequest
from
'@/utils/axios'
export
function
getResourceChart
(
params
?:
Record
<
string
,
any
>
)
{
return
httpRequest
.
get
(
'/api/resource/v1/teaching/chart/resource'
,
{
params
})
}
export
function
getLearningChart
(
params
?:
Record
<
string
,
any
>
)
{
return
httpRequest
.
get
(
'/api/resource/v1/teaching/chart/learning'
,
{
params
})
}
export
function
getTeachingChart
(
params
?:
Record
<
string
,
any
>
)
{
return
httpRequest
.
get
(
'/api/resource/v1/teaching/chart/teaching'
,
{
params
})
}
export
function
getOriginChart
(
params
?:
Record
<
string
,
any
>
)
{
return
httpRequest
.
get
(
'/api/resource/v1/teaching/chart/origin'
,
{
params
})
}
src/modules/teach/chart/views/Learning.vue
浏览文件 @
fd7bc1bd
...
@@ -2,13 +2,13 @@
...
@@ -2,13 +2,13 @@
import
{
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
{
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
*
as
echarts
from
'echarts'
import
*
as
echarts
from
'echarts'
import
'echarts-wordcloud'
import
'echarts-wordcloud'
import
{
User
,
UserFilled
,
Clock
,
Monitor
,
Files
}
from
'@element-plus/icons-vue'
import
{
User
,
UserFilled
,
Monitor
,
Files
}
from
'@element-plus/icons-vue'
import
{
getLearningChart
}
from
'../api'
// 近30天学习数据
// 近30天学习数据
const
learningStats
=
ref
({
const
learningStats
=
ref
({
studySessions
:
0
,
// 学习人次
studySessions
:
0
,
// 学习人次
studyUsers
:
0
,
// 学习人数
studyUsers
:
0
,
// 学习人数
studyDuration
:
0
,
// 学习时长(小时)
studyCourses
:
0
,
// 学习课程数量
studyCourses
:
0
,
// 学习课程数量
studyResources
:
0
,
// 学习资源数量
studyResources
:
0
,
// 学习资源数量
})
})
...
@@ -21,9 +21,6 @@ const studentWordCloud = ref([])
...
@@ -21,9 +21,6 @@ const studentWordCloud = ref([])
const
courseWordCloud
=
ref
([])
const
courseWordCloud
=
ref
([])
const
resourceWordCloud
=
ref
([])
const
resourceWordCloud
=
ref
([])
// 最近学习列表
const
recentStudyList
=
ref
([])
// 走势图数据
// 走势图数据
const
studyTrendData
=
ref
({
const
studyTrendData
=
ref
({
dates
:
[],
dates
:
[],
...
@@ -41,139 +38,25 @@ const courseStudyData = ref({
...
@@ -41,139 +38,25 @@ const courseStudyData = ref({
values
:
[],
values
:
[],
})
})
const
durationData
=
ref
({
dates
:
[],
values
:
[],
})
// 全国分布数据
const
mapData
=
ref
([])
// 图表实例
// 图表实例
let
behaviorChart
=
null
let
behaviorChart
=
null
let
studentWordCloudChart
=
null
let
studentWordCloudChart
=
null
let
courseWordCloudChart
=
null
let
courseWordCloudChart
=
null
let
resourceWordCloudChart
=
null
let
resourceWordCloudChart
=
null
let
mapChart
=
null
let
studyTrendChart
=
null
let
studyTrendChart
=
null
let
resourceStudyChart
=
null
let
resourceStudyChart
=
null
let
courseStudyChart
=
null
let
courseStudyChart
=
null
let
durationChart
=
null
// 模拟数据加载
const
loadData
=
()
=>
{
// 近30天学习数据
learningStats
.
value
=
{
studySessions
:
15680
,
studyUsers
:
3240
,
studyDuration
:
28450
,
studyCourses
:
180
,
studyResources
:
1250
,
}
// 学习行为分布
behaviorData
.
value
=
[
{
name
:
'观看视频'
,
value
:
8560
},
{
name
:
'下载课件'
,
value
:
3240
},
{
name
:
'在线测试'
,
value
:
1890
},
{
name
:
'讨论互动'
,
value
:
1230
},
{
name
:
'作业提交'
,
value
:
760
},
]
// 学生词云数据
studentWordCloud
.
value
=
[
{
name
:
'计算机科学'
,
value
:
100
},
{
name
:
'软件工程'
,
value
:
85
},
{
name
:
'数据科学'
,
value
:
75
},
{
name
:
'人工智能'
,
value
:
70
},
{
name
:
'网络安全'
,
value
:
65
},
{
name
:
'机器学习'
,
value
:
60
},
{
name
:
'前端开发'
,
value
:
55
},
{
name
:
'后端开发'
,
value
:
50
},
{
name
:
'移动开发'
,
value
:
45
},
{
name
:
'云计算'
,
value
:
40
},
]
// 课程词云数据
courseWordCloud
.
value
=
[
{
name
:
'Vue.js开发'
,
value
:
95
},
{
name
:
'React框架'
,
value
:
88
},
{
name
:
'Node.js后端'
,
value
:
82
},
{
name
:
'Python编程'
,
value
:
78
},
{
name
:
'Java基础'
,
value
:
75
},
{
name
:
'数据库设计'
,
value
:
70
},
{
name
:
'算法与数据结构'
,
value
:
68
},
{
name
:
'系统设计'
,
value
:
65
},
{
name
:
'微服务架构'
,
value
:
60
},
{
name
:
'DevOps实践'
,
value
:
55
},
]
// 资源词云数据
resourceWordCloud
.
value
=
[
{
name
:
'教学视频'
,
value
:
90
},
{
name
:
'PPT课件'
,
value
:
85
},
{
name
:
'实验指导'
,
value
:
80
},
{
name
:
'习题集'
,
value
:
75
},
{
name
:
'案例分析'
,
value
:
70
},
{
name
:
'参考书籍'
,
value
:
65
},
{
name
:
'代码示例'
,
value
:
60
},
{
name
:
'项目模板'
,
value
:
55
},
{
name
:
'在线工具'
,
value
:
50
},
{
name
:
'学习笔记'
,
value
:
45
},
]
// 最近学习列表
recentStudyList
.
value
=
[
{
course
:
'Vue.js前端开发实战'
,
student
:
'张三'
,
duration
:
'2小时30分'
,
time
:
'2024-01-15 14:30'
},
{
course
:
'React组件化开发'
,
student
:
'李四'
,
duration
:
'1小时45分'
,
time
:
'2024-01-15 13:20'
},
{
course
:
'Node.js后端开发'
,
student
:
'王五'
,
duration
:
'3小时15分'
,
time
:
'2024-01-15 11:45'
},
{
course
:
'Python数据分析'
,
student
:
'赵六'
,
duration
:
'2小时10分'
,
time
:
'2024-01-15 10:30'
},
{
course
:
'Java Spring Boot'
,
student
:
'钱七'
,
duration
:
'1小时55分'
,
time
:
'2024-01-15 09:15'
},
{
course
:
'数据库设计与优化'
,
student
:
'孙八'
,
duration
:
'2小时40分'
,
time
:
'2024-01-14 16:20'
},
{
course
:
'算法与数据结构'
,
student
:
'周九'
,
duration
:
'3小时05分'
,
time
:
'2024-01-14 14:10'
},
{
course
:
'微服务架构设计'
,
student
:
'吴十'
,
duration
:
'2小时20分'
,
time
:
'2024-01-14 11:30'
},
{
course
:
'Docker容器化'
,
student
:
'郑十一'
,
duration
:
'1小时50分'
,
time
:
'2024-01-14 09:45'
},
{
course
:
'Kubernetes集群管理'
,
student
:
'王十二'
,
duration
:
'2小时35分'
,
time
:
'2024-01-13 15:20'
},
]
// 生成最近30天的走势数据
const
dates
=
[]
const
sessions
=
[]
const
users
=
[]
const
resourceValues
=
[]
const
courseValues
=
[]
const
durationValues
=
[]
const
today
=
new
Date
()
for
(
let
i
=
29
;
i
>=
0
;
i
--
)
{
const
date
=
new
Date
(
today
)
date
.
setDate
(
date
.
getDate
()
-
i
)
dates
.
push
(
date
.
toISOString
().
split
(
'T'
)[
0
])
sessions
.
push
(
Math
.
floor
(
Math
.
random
()
*
200
)
+
300
)
users
.
push
(
Math
.
floor
(
Math
.
random
()
*
50
)
+
80
)
resourceValues
.
push
(
Math
.
floor
(
Math
.
random
()
*
30
)
+
20
)
courseValues
.
push
(
Math
.
floor
(
Math
.
random
()
*
15
)
+
10
)
durationValues
.
push
(
Math
.
floor
(
Math
.
random
()
*
100
)
+
200
)
}
studyTrendData
.
value
=
{
dates
,
sessions
,
users
}
resourceStudyData
.
value
=
{
dates
,
values
:
resourceValues
}
courseStudyData
.
value
=
{
dates
,
values
:
courseValues
}
durationData
.
value
=
{
dates
,
values
:
durationValues
}
// 全国分布数据(模拟)
const
loadData
=
async
()
=>
{
mapData
.
value
=
[
const
{
data
}
=
await
getLearningChart
()
{
name
:
'北京'
,
value
:
1200
},
learningStats
.
value
=
{
...
learningStats
.
value
,
...(
data
.
stats
||
{})
}
{
name
:
'上海'
,
value
:
980
},
behaviorData
.
value
=
data
.
behaviorData
||
[]
{
name
:
'广东'
,
value
:
850
},
studentWordCloud
.
value
=
data
.
studentRanking
||
[]
{
name
:
'江苏'
,
value
:
720
},
courseWordCloud
.
value
=
data
.
courseRanking
||
[]
{
name
:
'浙江'
,
value
:
680
},
resourceWordCloud
.
value
=
data
.
resourceRanking
||
[]
{
name
:
'山东'
,
value
:
560
},
studyTrendData
.
value
=
data
.
studyTrendData
||
{
dates
:
[],
sessions
:
[],
users
:
[]
}
{
name
:
'四川'
,
value
:
480
},
resourceStudyData
.
value
=
data
.
resourceStudyData
||
{
dates
:
[],
values
:
[]
}
{
name
:
'湖北'
,
value
:
420
},
courseStudyData
.
value
=
data
.
courseStudyData
||
{
dates
:
[],
values
:
[]
}
{
name
:
'河南'
,
value
:
380
},
{
name
:
'湖南'
,
value
:
350
},
]
}
}
// 初始化图表
// 初始化图表
...
@@ -230,7 +113,7 @@ const initCharts = () => {
...
@@ -230,7 +113,7 @@ const initCharts = () => {
studentWordCloudChart
=
echarts
.
init
(
studentWordEl
)
studentWordCloudChart
=
echarts
.
init
(
studentWordEl
)
const
option
=
{
const
option
=
{
title
:
{
title
:
{
text
:
'活跃学生
词云
'
,
text
:
'活跃学生
Top10
'
,
left
:
'center'
,
left
:
'center'
,
textStyle
:
{
fontSize
:
16
},
textStyle
:
{
fontSize
:
16
},
},
},
...
@@ -274,7 +157,7 @@ const initCharts = () => {
...
@@ -274,7 +157,7 @@ const initCharts = () => {
courseWordCloudChart
=
echarts
.
init
(
courseWordEl
)
courseWordCloudChart
=
echarts
.
init
(
courseWordEl
)
const
option
=
{
const
option
=
{
title
:
{
title
:
{
text
:
'热门课程
词云
'
,
text
:
'热门课程
Top10
'
,
left
:
'center'
,
left
:
'center'
,
textStyle
:
{
fontSize
:
16
},
textStyle
:
{
fontSize
:
16
},
},
},
...
@@ -318,7 +201,7 @@ const initCharts = () => {
...
@@ -318,7 +201,7 @@ const initCharts = () => {
resourceWordCloudChart
=
echarts
.
init
(
resourceWordEl
)
resourceWordCloudChart
=
echarts
.
init
(
resourceWordEl
)
const
option
=
{
const
option
=
{
title
:
{
title
:
{
text
:
'热门资源
词云
'
,
text
:
'热门资源
Top10
'
,
left
:
'center'
,
left
:
'center'
,
textStyle
:
{
fontSize
:
16
},
textStyle
:
{
fontSize
:
16
},
},
},
...
@@ -353,55 +236,6 @@ const initCharts = () => {
...
@@ -353,55 +236,6 @@ const initCharts = () => {
resourceWordCloudChart
.
setOption
(
option
)
resourceWordCloudChart
.
setOption
(
option
)
}
}
// 全国分布图 - 使用柱状图代替地图
const
mapEl
=
document
.
getElementById
(
'mapChart'
)
console
.
log
(
'mapChart元素:'
,
mapEl
)
if
(
mapEl
)
{
if
(
mapChart
)
{
mapChart
.
dispose
()
}
mapChart
=
echarts
.
init
(
mapEl
)
const
option
=
{
title
:
{
text
:
'在线学生全国分布'
,
left
:
'center'
,
textStyle
:
{
fontSize
:
16
},
},
tooltip
:
{
trigger
:
'axis'
,
axisPointer
:
{
type
:
'shadow'
,
},
},
xAxis
:
{
type
:
'category'
,
data
:
mapData
.
value
.
map
((
item
)
=>
item
.
name
),
axisLabel
:
{
rotate
:
45
,
},
},
yAxis
:
{
type
:
'value'
,
name
:
'学生人数'
,
},
series
:
[
{
name
:
'学生分布'
,
type
:
'bar'
,
data
:
mapData
.
value
.
map
((
item
)
=>
item
.
value
),
itemStyle
:
{
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[
{
offset
:
0
,
color
:
'#83bff6'
},
{
offset
:
0.5
,
color
:
'#188df0'
},
{
offset
:
1
,
color
:
'#188df0'
},
]),
},
},
],
}
mapChart
.
setOption
(
option
)
}
// 学习人次人数走势图
// 学习人次人数走势图
const
studyTrendEl
=
document
.
getElementById
(
'studyTrendChart'
)
const
studyTrendEl
=
document
.
getElementById
(
'studyTrendChart'
)
console
.
log
(
'studyTrendChart元素:'
,
studyTrendEl
)
console
.
log
(
'studyTrendChart元素:'
,
studyTrendEl
)
...
@@ -585,58 +419,6 @@ const initCharts = () => {
...
@@ -585,58 +419,6 @@ const initCharts = () => {
courseStudyChart
.
setOption
(
option
)
courseStudyChart
.
setOption
(
option
)
}
}
// 学习时长走势图
const
durationEl
=
document
.
getElementById
(
'durationChart'
)
if
(
durationEl
)
{
if
(
durationChart
)
{
durationChart
.
dispose
()
}
durationChart
=
echarts
.
init
(
durationEl
)
const
option
=
{
title
:
{
text
:
'学习时长走势'
,
left
:
'center'
,
top
:
'5%'
,
textStyle
:
{
fontSize
:
16
},
},
tooltip
:
{
trigger
:
'axis'
,
},
grid
:
{
top
:
'20%'
,
left
:
'3%'
,
right
:
'4%'
,
bottom
:
'3%'
,
containLabel
:
true
,
},
xAxis
:
{
type
:
'category'
,
data
:
durationData
.
value
.
dates
,
axisLabel
:
{
rotate
:
45
,
},
},
yAxis
:
{
type
:
'value'
,
name
:
'时长(小时)'
,
},
series
:
[
{
name
:
'学习时长'
,
type
:
'line'
,
data
:
durationData
.
value
.
values
,
smooth
:
true
,
areaStyle
:
{
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[
{
offset
:
0
,
color
:
'rgba(255, 99, 132, 0.3)'
},
{
offset
:
1
,
color
:
'rgba(255, 99, 132, 0.1)'
},
]),
},
},
],
}
durationChart
.
setOption
(
option
)
}
}
}
// 窗口大小改变时重新调整图表
// 窗口大小改变时重新调整图表
...
@@ -645,11 +427,9 @@ const handleResize = () => {
...
@@ -645,11 +427,9 @@ const handleResize = () => {
studentWordCloudChart
?.
resize
()
studentWordCloudChart
?.
resize
()
courseWordCloudChart
?.
resize
()
courseWordCloudChart
?.
resize
()
resourceWordCloudChart
?.
resize
()
resourceWordCloudChart
?.
resize
()
mapChart
?.
resize
()
studyTrendChart
?.
resize
()
studyTrendChart
?.
resize
()
resourceStudyChart
?.
resize
()
resourceStudyChart
?.
resize
()
courseStudyChart
?.
resize
()
courseStudyChart
?.
resize
()
durationChart
?.
resize
()
}
}
// 清理所有图表实例
// 清理所有图表实例
...
@@ -658,26 +438,22 @@ const disposeAllCharts = () => {
...
@@ -658,26 +438,22 @@ const disposeAllCharts = () => {
studentWordCloudChart
?.
dispose
()
studentWordCloudChart
?.
dispose
()
courseWordCloudChart
?.
dispose
()
courseWordCloudChart
?.
dispose
()
resourceWordCloudChart
?.
dispose
()
resourceWordCloudChart
?.
dispose
()
mapChart
?.
dispose
()
studyTrendChart
?.
dispose
()
studyTrendChart
?.
dispose
()
resourceStudyChart
?.
dispose
()
resourceStudyChart
?.
dispose
()
courseStudyChart
?.
dispose
()
courseStudyChart
?.
dispose
()
durationChart
?.
dispose
()
// 重置为null
// 重置为null
behaviorChart
=
null
behaviorChart
=
null
studentWordCloudChart
=
null
studentWordCloudChart
=
null
courseWordCloudChart
=
null
courseWordCloudChart
=
null
resourceWordCloudChart
=
null
resourceWordCloudChart
=
null
mapChart
=
null
studyTrendChart
=
null
studyTrendChart
=
null
resourceStudyChart
=
null
resourceStudyChart
=
null
courseStudyChart
=
null
courseStudyChart
=
null
durationChart
=
null
}
}
onMounted
(()
=>
{
onMounted
(
async
()
=>
{
loadData
()
await
loadData
()
// 确保DOM完全渲染后再初始化图表
// 确保DOM完全渲染后再初始化图表
setTimeout
(()
=>
{
setTimeout
(()
=>
{
...
@@ -726,16 +502,6 @@ onUnmounted(() => {
...
@@ -726,16 +502,6 @@ onUnmounted(() => {
</div>
</div>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-icon duration"
>
<Clock
/>
</div>
<div
class=
"stat-content"
>
<div
class=
"stat-number"
>
{{
learningStats
.
studyDuration
.
toLocaleString
()
}}
</div>
<div
class=
"stat-label"
>
学习时长(小时)
</div>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-card"
>
<div
class=
"stat-icon courses"
>
<div
class=
"stat-icon courses"
>
<Monitor
/>
<Monitor
/>
...
@@ -777,34 +543,6 @@ onUnmounted(() => {
...
@@ -777,34 +543,6 @@ onUnmounted(() => {
</div>
</div>
</div>
</div>
<!-- 全国分布 -->
<div
class=
"distribution-section"
>
<h2>
在线学生全国分布
</h2>
<div
class=
"charts-row"
>
<div
class=
"chart-container large"
>
<div
id=
"mapChart"
class=
"chart"
></div>
</div>
</div>
</div>
<!-- 最近学习列表 -->
<div
class=
"recent-section"
>
<h2>
最近学习列表 Top10
</h2>
<div
class=
"recent-study-list"
>
<div
v-for=
"(item, index) in recentStudyList"
:key=
"index"
class=
"list-item"
>
<div
class=
"rank"
>
{{
index
+
1
}}
</div>
<div
class=
"content"
>
<div
class=
"course-name"
>
{{
item
.
course
}}
</div>
<div
class=
"student-info"
>
<span
class=
"student"
>
{{
item
.
student
}}
</span>
<span
class=
"duration"
>
{{
item
.
duration
}}
</span>
</div>
<div
class=
"time"
>
{{
item
.
time
}}
</div>
</div>
</div>
</div>
</div>
<!-- 走势图分析 -->
<!-- 走势图分析 -->
<div
class=
"trend-section"
>
<div
class=
"trend-section"
>
<h2>
学习趋势分析
</h2>
<h2>
学习趋势分析
</h2>
...
@@ -822,9 +560,6 @@ onUnmounted(() => {
...
@@ -822,9 +560,6 @@ onUnmounted(() => {
<div
class=
"chart-container large"
>
<div
class=
"chart-container large"
>
<div
id=
"courseStudyChart"
class=
"chart"
></div>
<div
id=
"courseStudyChart"
class=
"chart"
></div>
</div>
</div>
<div
class=
"chart-container large"
>
<div
id=
"durationChart"
class=
"chart"
></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -854,8 +589,6 @@ onUnmounted(() => {
...
@@ -854,8 +589,6 @@ onUnmounted(() => {
.stats-section
,
.stats-section
,
.analysis-section
,
.analysis-section
,
.distribution-section
,
.recent-section
,
.trend-section
{
.trend-section
{
margin-bottom
:
40px
;
margin-bottom
:
40px
;
...
@@ -904,7 +637,6 @@ onUnmounted(() => {
...
@@ -904,7 +637,6 @@ onUnmounted(() => {
&
.sessions
,
&
.sessions
,
&
.users
,
&
.users
,
&
.duration
,
&
.courses
,
&
.courses
,
&
.resources
{
&
.resources
{
background
:
#f5ebda
;
background
:
#f5ebda
;
...
@@ -956,72 +688,6 @@ onUnmounted(() => {
...
@@ -956,72 +688,6 @@ onUnmounted(() => {
}
}
}
}
.recent-study-list
{
background
:
white
;
border-radius
:
8px
;
padding
:
20px
;
box-shadow
:
0
2px
12px
0
rgba
(
0
,
0
,
0
,
0
.1
);
max-height
:
500px
;
overflow-y
:
auto
;
.list-item
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
0
;
border-bottom
:
1px
solid
#f0f0f0
;
&
:last-child
{
border-bottom
:
none
;
}
.rank
{
width
:
30px
;
height
:
30px
;
border-radius
:
50%
;
background
:
linear-gradient
(
135deg
,
#409eff
,
#67c23a
);
color
:
white
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-weight
:
bold
;
margin-right
:
15px
;
font-size
:
14px
;
}
.content
{
flex
:
1
;
.course-name
{
font-size
:
14px
;
color
:
#303133
;
font-weight
:
500
;
margin-bottom
:
4px
;
}
.student-info
{
display
:
flex
;
justify-content
:
space-between
;
margin-bottom
:
2px
;
.student
{
font-size
:
12px
;
color
:
#606266
;
}
.duration
{
font-size
:
12px
;
color
:
#67c23a
;
font-weight
:
500
;
}
}
.time
{
font-size
:
11px
;
color
:
#909399
;
}
}
}
}
}
}
@media
(
max-width
:
768px
)
{
@media
(
max-width
:
768px
)
{
...
...
src/modules/teach/chart/views/Origin.vue
浏览文件 @
fd7bc1bd
<
template
>
<
template
>
<div
class=
"origin-container"
>
<div
class=
"origin-container"
>
<!-- 页面标题 -->
<!-- 页面标题 -->
<div
class=
"header"
>
<div
class=
"page-header"
>
<h1>
<h1>
生源地分布可视化
</h1>
<el-icon
class=
"header-icon"
><Location
/></el-icon>
生源地分布可视化
</h1>
<p>
全面了解学生来源地域分布情况与统计分析
</p>
<p>
全面了解学生来源地域分布情况与统计分析
</p>
</div>
</div>
<!-- 筛选器 -->
<div
class=
"filter-bar"
>
<div
class=
"filter-group"
>
<el-icon
class=
"filter-icon"
style=
"color: var(--primary-color)"
><Calendar
/></el-icon>
<label>
学期:
</label>
<select
v-model=
"filters.year"
@
change=
"onFilterChange"
>
<option
value=
"2024"
>
2024学年
</option>
<option
value=
"2023"
>
2023学年
</option>
<option
value=
"2022"
>
2022学年
</option>
</select>
</div>
<div
class=
"filter-group"
>
<el-icon
class=
"filter-icon"
style=
"color: var(--secondary-color)"
><School
/></el-icon>
<label>
年级:
</label>
<select
v-model=
"filters.grade"
@
change=
"onFilterChange"
>
<option
value=
"all"
>
全部年级
</option>
<option
value=
"1"
>
一年级
</option>
<option
value=
"2"
>
二年级
</option>
<option
value=
"3"
>
三年级
</option>
<option
value=
"4"
>
四年级
</option>
</select>
</div>
<div
class=
"filter-group"
>
<el-icon
class=
"filter-icon"
style=
"color: var(--success-color)"
><Grid
/></el-icon>
<label>
专业:
</label>
<select
v-model=
"filters.major"
@
change=
"onFilterChange"
>
<option
value=
"all"
>
全部专业
</option>
<option
value=
"cs"
>
计算机科学
</option>
<option
value=
"ee"
>
电子工程
</option>
<option
value=
"ba"
>
工商管理
</option>
<option
value=
"med"
>
医学
</option>
</select>
</div>
</div>
<!-- 统计卡片 -->
<!-- 统计卡片 -->
<div
class=
"stats-grid"
>
<div
class=
"stats-grid"
>
<div
class=
"stat-card"
>
<div
class=
"stat-card"
>
...
@@ -73,15 +35,6 @@
...
@@ -73,15 +35,6 @@
<div
class=
"stat-label"
>
覆盖城市
</div>
<div
class=
"stat-label"
>
覆盖城市
</div>
</div>
</div>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-icon"
style=
"background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)"
>
<el-icon
class=
"stat-icon-inner"
><Location
/></el-icon>
</div>
<div
class=
"stat-content"
>
<div
class=
"stat-value"
>
{{
stats
.
internationalCountries
}}
</div>
<div
class=
"stat-label"
>
国际学生国家
</div>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-card"
>
<div
class=
"stat-icon"
style=
"background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)"
>
<div
class=
"stat-icon"
style=
"background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)"
>
<el-icon
class=
"stat-icon-inner"
><TrendCharts
/></el-icon>
<el-icon
class=
"stat-icon-inner"
><TrendCharts
/></el-icon>
...
@@ -112,10 +65,10 @@
...
@@ -112,10 +65,10 @@
</div>
</div>
</div>
</div>
<!--
地图
可视化 -->
<!--
省份排行
可视化 -->
<div
class=
"map-section"
>
<div
class=
"map-section"
>
<h2
class=
"section-title"
>
全国生源
地图分布
</h2>
<h2
class=
"section-title"
>
全国生源
省份排行
</h2>
<div
ref=
"
chinaMap
Chart"
class=
"map-container"
></div>
<div
ref=
"
provinceRank
Chart"
class=
"map-container"
></div>
</div>
</div>
<!-- 详细数据表格 -->
<!-- 详细数据表格 -->
...
@@ -128,8 +81,6 @@
...
@@ -128,8 +81,6 @@
<th>
省份
</th>
<th>
省份
</th>
<th>
学生人数
</th>
<th>
学生人数
</th>
<th>
占比
</th>
<th>
占比
</th>
<th>
趋势
</th>
<th>
分布可视化
</th>
</tr>
</tr>
</thead>
</thead>
<tbody>
<tbody>
...
@@ -151,19 +102,6 @@
...
@@ -151,19 +102,6 @@
<td>
<td>
<strong>
{{
item
.
percentage
}}
%
</strong>
<strong>
{{
item
.
percentage
}}
%
</strong>
</td>
</td>
<td
:style=
"
{ color: getTrendColor(item.trend) }">
<el-icon
class=
"trend-icon"
>
<ArrowUp
v-if=
"item.trend.startsWith('+')"
/>
<ArrowDown
v-else-if=
"item.trend.startsWith('-')"
/>
<Minus
v-else
/>
</el-icon>
{{
item
.
trend
}}
</td>
<td>
<div
class=
"progress-bar"
>
<div
class=
"progress-fill"
:style=
"
{ width: `${item.percentage}%` }">
</div>
</div>
</td>
</tr>
</tr>
</tbody>
</tbody>
</table>
</table>
...
@@ -175,27 +113,20 @@
...
@@ -175,27 +113,20 @@
import
{
ref
,
reactive
,
onMounted
,
onUnmounted
,
nextTick
}
from
'vue'
import
{
ref
,
reactive
,
onMounted
,
onUnmounted
,
nextTick
}
from
'vue'
import
*
as
echarts
from
'echarts'
import
*
as
echarts
from
'echarts'
import
{
import
{
Location
,
Calendar
,
School
,
Grid
,
User
,
User
,
LocationInformation
,
LocationInformation
,
OfficeBuilding
,
OfficeBuilding
,
TrendCharts
,
TrendCharts
,
PieChart
,
PieChart
,
Histogram
,
Histogram
,
ArrowUp
,
ArrowDown
,
Minus
,
}
from
'@element-plus/icons-vue'
}
from
'@element-plus/icons-vue'
import
{
getOriginChart
}
from
'../api'
// 类型定义
// 类型定义
interface
ProvinceData
{
interface
ProvinceData
{
name
:
string
name
:
string
value
:
number
value
:
number
percentage
:
number
percentage
:
number
trend
:
string
rank
:
number
rank
:
number
}
}
...
@@ -203,63 +134,41 @@ interface Stats {
...
@@ -203,63 +134,41 @@ interface Stats {
totalStudents
:
number
totalStudents
:
number
provinces
:
number
provinces
:
number
cities
:
number
cities
:
number
internationalCountries
:
number
localPercentage
:
number
localPercentage
:
number
}
}
interface
Filters
{
year
:
string
grade
:
string
major
:
string
}
// 响应式数据
// 响应式数据
const
regionPieChart
=
ref
<
HTMLElement
>
()
const
regionPieChart
=
ref
<
HTMLElement
>
()
const
cityBarChart
=
ref
<
HTMLElement
>
()
const
cityBarChart
=
ref
<
HTMLElement
>
()
const
chinaMapChart
=
ref
<
HTMLElement
>
()
const
provinceRankChart
=
ref
<
HTMLElement
>
()
const
filters
=
reactive
<
Filters
>
({
year
:
'2024'
,
grade
:
'all'
,
major
:
'all'
,
})
const
stats
=
reactive
<
Stats
>
({
const
stats
=
reactive
<
Stats
>
({
totalStudents
:
8650
,
totalStudents
:
0
,
provinces
:
31
,
provinces
:
0
,
cities
:
256
,
cities
:
0
,
internationalCountries
:
12
,
localPercentage
:
0
,
localPercentage
:
68
,
})
})
const
provinceData
=
ref
<
ProvinceData
[]
>
([
const
provinceData
=
ref
<
ProvinceData
[]
>
([])
{
name
:
'广东'
,
value
:
5882
,
percentage
:
68
,
trend
:
'+5%'
,
rank
:
1
},
const
regionData
=
ref
<
{
name
:
string
;
value
:
number
}[]
>
([])
{
name
:
'湖南'
,
value
:
432
,
percentage
:
5
,
trend
:
'+2%'
,
rank
:
2
},
const
cityData
=
ref
<
{
name
:
string
;
value
:
number
}[]
>
([])
{
name
:
'湖北'
,
value
:
389
,
percentage
:
4.5
,
trend
:
'+3%'
,
rank
:
3
},
{
name
:
'江西'
,
value
:
346
,
percentage
:
4
,
trend
:
'+1%'
,
rank
:
4
},
{
name
:
'河南'
,
value
:
302
,
percentage
:
3.5
,
trend
:
'-1%'
,
rank
:
5
},
{
name
:
'四川'
,
value
:
259
,
percentage
:
3
,
trend
:
'+4%'
,
rank
:
6
},
{
name
:
'广西'
,
value
:
216
,
percentage
:
2.5
,
trend
:
'+2%'
,
rank
:
7
},
{
name
:
'福建'
,
value
:
173
,
percentage
:
2
,
trend
:
'0%'
,
rank
:
8
},
{
name
:
'浙江'
,
value
:
156
,
percentage
:
1.8
,
trend
:
'+1%'
,
rank
:
9
},
{
name
:
'江苏'
,
value
:
138
,
percentage
:
1.6
,
trend
:
'+2%'
,
rank
:
10
},
{
name
:
'其他'
,
value
:
357
,
percentage
:
4.1
,
trend
:
'+1%'
,
rank
:
11
},
])
// 图表实例
// 图表实例
let
regionPieChartInstance
:
echarts
.
ECharts
|
null
=
null
let
regionPieChartInstance
:
echarts
.
ECharts
|
null
=
null
let
cityBarChartInstance
:
echarts
.
ECharts
|
null
=
null
let
cityBarChartInstance
:
echarts
.
ECharts
|
null
=
null
let
chinaMap
ChartInstance
:
echarts
.
ECharts
|
null
=
null
let
provinceRank
ChartInstance
:
echarts
.
ECharts
|
null
=
null
// 工具函数
// 工具函数
const
getRankClass
=
(
rank
:
number
):
string
=>
{
const
getRankClass
=
(
rank
:
number
):
string
=>
{
return
rank
<=
3
?
`rank-
${
rank
}
`
:
'rank-other'
return
rank
<=
3
?
`rank-
${
rank
}
`
:
'rank-other'
}
}
const
getTrendColor
=
(
trend
:
string
):
string
=>
{
const
loadData
=
async
()
=>
{
if
(
trend
.
startsWith
(
'+'
))
return
'#10b981'
const
{
data
}
=
await
getOriginChart
()
if
(
trend
.
startsWith
(
'-'
))
return
'#ef4444'
Object
.
assign
(
stats
,
data
.
stats
||
{})
return
'#6b7280'
provinceData
.
value
=
data
.
provinceData
||
[]
regionData
.
value
=
data
.
regionData
||
[]
cityData
.
value
=
data
.
cityData
||
[]
}
}
// 初始化区域分布饼图
// 初始化区域分布饼图
...
@@ -301,14 +210,7 @@ const initRegionPieChart = () => {
...
@@ -301,14 +210,7 @@ const initRegionPieChart = () => {
fontWeight
:
'bold'
,
fontWeight
:
'bold'
,
},
},
},
},
data
:
[
data
:
regionData
.
value
,
{
value
:
5882
,
name
:
'省内'
},
{
value
:
1214
,
name
:
'华中地区'
},
{
value
:
518
,
name
:
'西南地区'
},
{
value
:
432
,
name
:
'华东地区'
},
{
value
:
389
,
name
:
'华北地区'
},
{
value
:
215
,
name
:
'其他地区'
},
],
},
},
],
],
}
}
...
@@ -337,14 +239,14 @@ const initCityBarChart = () => {
...
@@ -337,14 +239,14 @@ const initCityBarChart = () => {
},
},
yAxis
:
{
yAxis
:
{
type
:
'category'
,
type
:
'category'
,
data
:
[
'惠州'
,
'东莞'
,
'佛山'
,
'中山'
,
'珠海'
,
'江门'
,
'肇庆'
,
'汕头'
,
'湛江'
,
'深圳'
]
,
data
:
cityData
.
value
.
map
((
item
)
=>
item
.
name
)
,
axisLabel
:
{
fontSize
:
11
},
axisLabel
:
{
fontSize
:
11
},
},
},
series
:
[
series
:
[
{
{
name
:
'学生人数'
,
name
:
'学生人数'
,
type
:
'bar'
,
type
:
'bar'
,
data
:
[
856
,
923
,
1245
,
678
,
534
,
489
,
412
,
367
,
325
,
1053
]
,
data
:
cityData
.
value
.
map
((
item
)
=>
item
.
value
)
,
itemStyle
:
{
itemStyle
:
{
borderRadius
:
[
0
,
8
,
8
,
0
],
borderRadius
:
[
0
,
8
,
8
,
0
],
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
1
,
0
,
[
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
1
,
0
,
[
...
@@ -363,205 +265,89 @@ const initCityBarChart = () => {
...
@@ -363,205 +265,89 @@ const initCityBarChart = () => {
cityBarChartInstance
.
setOption
(
option
)
cityBarChartInstance
.
setOption
(
option
)
}
}
// 初始化中国地图
// 初始化省份排行图
const
initChinaMap
=
async
()
=>
{
const
initProvinceRankChart
=
()
=>
{
if
(
!
chinaMapChart
.
value
)
return
if
(
!
provinceRankChart
.
value
)
return
chinaMapChartInstance
=
echarts
.
init
(
chinaMapChart
.
value
)
const
mapData
=
[
{
name
:
'广东'
,
value
:
5882
},
{
name
:
'湖南'
,
value
:
432
},
{
name
:
'湖北'
,
value
:
389
},
{
name
:
'江西'
,
value
:
346
},
{
name
:
'河南'
,
value
:
302
},
{
name
:
'四川'
,
value
:
259
},
{
name
:
'广西'
,
value
:
216
},
{
name
:
'福建'
,
value
:
173
},
{
name
:
'浙江'
,
value
:
156
},
{
name
:
'江苏'
,
value
:
138
},
{
name
:
'安徽'
,
value
:
125
},
{
name
:
'山东'
,
value
:
112
},
{
name
:
'重庆'
,
value
:
98
},
{
name
:
'贵州'
,
value
:
87
},
{
name
:
'云南'
,
value
:
76
},
]
provinceRankChartInstance
=
echarts
.
init
(
provinceRankChart
.
value
)
const
mapData
=
provinceData
.
value
const
option
=
{
const
option
=
{
title
:
{
title
:
{
text
:
'
全国生源分布热力图
'
,
text
:
'
省份生源排行
'
,
left
:
'center'
,
left
:
'center'
,
top
:
20
,
top
:
20
,
textStyle
:
{
fontSize
:
16
,
color
:
'#2C3E50'
,
},
},
},
tooltip
:
{
tooltip
:
{
trigger
:
'item'
,
trigger
:
'axis'
,
formatter
:
function
(
params
:
any
)
{
axisPointer
:
{
type
:
'shadow'
},
if
(
params
.
value
)
{
},
return
params
.
name
+
'<br/>学生人数: '
+
params
.
value
+
'人'
grid
:
{
}
left
:
'10%'
,
return
params
.
name
+
'<br/>暂无数据'
right
:
'10%'
,
},
top
:
'15%'
,
bottom
:
'10%'
,
},
},
visualMap
:
{
xAxis
:
{
min
:
0
,
type
:
'category'
,
max
:
6000
,
data
:
mapData
.
map
((
item
)
=>
item
.
name
),
text
:
[
'高'
,
'低'
],
axisLabel
:
{
realtime
:
false
,
interval
:
0
,
calculable
:
true
,
rotate
:
45
,
inRange
:
{
fontSize
:
11
,
color
:
[
'#fef2f2'
,
'#fecaca'
,
'#f87171'
,
'#dc2626'
,
'#C21F30'
],
},
},
bottom
:
50
,
},
left
:
20
,
yAxis
:
{
type
:
'value'
,
name
:
'学生人数'
,
},
},
series
:
[
series
:
[
{
{
name
:
'生源分布'
,
type
:
'bar'
,
type
:
'scatter'
,
data
:
mapData
.
map
((
item
)
=>
item
.
value
),
coordinateSystem
:
'geo'
,
data
:
[
{
name
:
'广东'
,
value
:
[
113.23
,
23.16
,
5882
]
},
{
name
:
'湖南'
,
value
:
[
112.59
,
28.21
,
432
]
},
{
name
:
'湖北'
,
value
:
[
114.31
,
30.52
,
389
]
},
{
name
:
'江西'
,
value
:
[
115.89
,
28.68
,
346
]
},
{
name
:
'河南'
,
value
:
[
113.65
,
34.76
,
302
]
},
{
name
:
'四川'
,
value
:
[
104.06
,
30.67
,
259
]
},
{
name
:
'广西'
,
value
:
[
108.33
,
22.84
,
216
]
},
{
name
:
'福建'
,
value
:
[
119.3
,
26.08
,
173
]
},
{
name
:
'浙江'
,
value
:
[
120.19
,
30.26
,
156
]
},
{
name
:
'江苏'
,
value
:
[
118.78
,
32.04
,
138
]
},
],
symbolSize
:
function
(
val
:
number
[])
{
return
Math
.
sqrt
(
val
[
2
])
/
2
},
itemStyle
:
{
itemStyle
:
{
color
:
'#C21F30'
,
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[
shadowBlur
:
10
,
{
offset
:
0
,
color
:
'#C21F30'
},
shadowColor
:
'rgba(194, 31, 48, 0.5)'
,
{
offset
:
1
,
color
:
'#E94057'
},
]),
borderRadius
:
[
8
,
8
,
0
,
0
],
},
},
label
:
{
label
:
{
formatter
:
'{b}'
,
show
:
true
,
position
:
'top'
,
position
:
'top'
,
show
:
false
,
fontSize
:
10
,
},
emphasis
:
{
label
:
{
show
:
true
,
},
},
},
},
},
],
],
geo
:
{
map
:
'china'
,
roam
:
true
,
scaleLimit
:
{
min
:
1
,
max
:
3
,
},
label
:
{
show
:
false
,
},
itemStyle
:
{
areaColor
:
'#f3f3f3'
,
borderColor
:
'#ccc'
,
},
emphasis
:
{
itemStyle
:
{
areaColor
:
'#e6f2ff'
,
},
label
:
{
show
:
true
,
color
:
'#333'
,
},
},
},
}
try
{
// 尝试加载中国地图数据
const
response
=
await
fetch
(
'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json'
)
const
chinaJson
=
await
response
.
json
()
echarts
.
registerMap
(
'china'
,
chinaJson
)
chinaMapChartInstance
.
setOption
(
option
)
}
catch
(
error
)
{
// 如果加载失败,显示备用图表
console
.
log
(
'地图加载失败,使用备用可视化'
)
const
backupOption
=
{
title
:
{
text
:
'省份生源排行'
,
left
:
'center'
,
top
:
20
,
},
tooltip
:
{
trigger
:
'axis'
,
axisPointer
:
{
type
:
'shadow'
},
},
grid
:
{
left
:
'10%'
,
right
:
'10%'
,
top
:
'15%'
,
bottom
:
'10%'
,
},
xAxis
:
{
type
:
'category'
,
data
:
mapData
.
map
((
item
)
=>
item
.
name
),
axisLabel
:
{
interval
:
0
,
rotate
:
45
,
fontSize
:
11
,
},
},
yAxis
:
{
type
:
'value'
,
name
:
'学生人数'
,
},
series
:
[
{
type
:
'bar'
,
data
:
mapData
.
map
((
item
)
=>
item
.
value
),
itemStyle
:
{
color
:
new
echarts
.
graphic
.
LinearGradient
(
0
,
0
,
0
,
1
,
[
{
offset
:
0
,
color
:
'#C21F30'
},
{
offset
:
1
,
color
:
'#E94057'
},
]),
borderRadius
:
[
8
,
8
,
0
,
0
],
},
label
:
{
show
:
true
,
position
:
'top'
,
fontSize
:
10
,
},
},
],
}
chinaMapChartInstance
.
setOption
(
backupOption
)
}
}
provinceRankChartInstance
.
setOption
(
option
)
}
}
// 筛选器变化处理
const
renderCharts
=
async
()
=>
{
const
onFilterChange
=
()
=>
{
regionPieChartInstance
?.
dispose
()
console
.
log
(
'筛选器变化:'
,
filters
)
cityBarChartInstance
?.
dispose
()
// 这里可以添加数据刷新逻辑
provinceRankChartInstance
?.
dispose
()
// 例如:重新获取数据、更新图表等
regionPieChartInstance
=
null
cityBarChartInstance
=
null
provinceRankChartInstance
=
null
await
nextTick
()
initRegionPieChart
()
initCityBarChart
()
initProvinceRankChart
()
}
}
// 窗口大小变化处理
// 窗口大小变化处理
const
handleResize
=
()
=>
{
const
handleResize
=
()
=>
{
regionPieChartInstance
?.
resize
()
regionPieChartInstance
?.
resize
()
cityBarChartInstance
?.
resize
()
cityBarChartInstance
?.
resize
()
chinaMap
ChartInstance
?.
resize
()
provinceRank
ChartInstance
?.
resize
()
}
}
// 生命周期
// 生命周期
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
await
loadData
()
await
nextTick
()
await
nextTick
()
initRegionPieChart
()
await
renderCharts
()
initCityBarChart
()
await
initChinaMap
()
window
.
addEventListener
(
'resize'
,
handleResize
)
window
.
addEventListener
(
'resize'
,
handleResize
)
})
})
...
@@ -569,7 +355,7 @@ onMounted(async () => {
...
@@ -569,7 +355,7 @@ onMounted(async () => {
onUnmounted
(()
=>
{
onUnmounted
(()
=>
{
regionPieChartInstance
?.
dispose
()
regionPieChartInstance
?.
dispose
()
cityBarChartInstance
?.
dispose
()
cityBarChartInstance
?.
dispose
()
chinaMap
ChartInstance
?.
dispose
()
provinceRank
ChartInstance
?.
dispose
()
window
.
removeEventListener
(
'resize'
,
handleResize
)
window
.
removeEventListener
(
'resize'
,
handleResize
)
})
})
</
script
>
</
script
>
...
@@ -593,34 +379,20 @@ onUnmounted(() => {
...
@@ -593,34 +379,20 @@ onUnmounted(() => {
--shadow-hover
:
0
10px
25px
rgba
(
0
,
0
,
0
,
0.1
);
--shadow-hover
:
0
10px
25px
rgba
(
0
,
0
,
0
,
0.1
);
}
}
.header
{
.
page-
header
{
text-align
:
center
;
text-align
:
center
;
margin-bottom
:
2rem
;
margin-bottom
:
30px
;
padding
:
2rem
;
background
:
white
;
border-radius
:
16px
;
box-shadow
:
var
(
--shadow
);
}
}
.header
h1
{
.
page-
header
h1
{
font-size
:
2
rem
;
font-size
:
2
8px
;
color
:
var
(
--dark-color
);
color
:
var
(
--dark-color
);
margin-bottom
:
0.5rem
;
margin-bottom
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
1rem
;
}
.header-icon
{
color
:
var
(
--primary-color
);
font-size
:
2.5rem
;
margin-right
:
0.5rem
;
}
}
.header
p
{
.
page-
header
p
{
color
:
#7f8c8d
;
color
:
#7f8c8d
;
font-size
:
1
rem
;
font-size
:
1
6px
;
}
}
.stats-grid
{
.stats-grid
{
...
@@ -853,64 +625,12 @@ onUnmounted(() => {
...
@@ -853,64 +625,12 @@ onUnmounted(() => {
box-shadow
:
0
1px
3px
rgba
(
194
,
31
,
48
,
0.3
);
box-shadow
:
0
1px
3px
rgba
(
194
,
31
,
48
,
0.3
);
}
}
.filter-bar
{
background
:
white
;
padding
:
1.5rem
;
border-radius
:
12px
;
box-shadow
:
var
(
--shadow
);
margin-bottom
:
2rem
;
display
:
flex
;
gap
:
1rem
;
flex-wrap
:
wrap
;
align-items
:
center
;
}
.filter-group
{
display
:
flex
;
align-items
:
center
;
gap
:
0.5rem
;
}
.filter-icon
{
font-size
:
1.1rem
;
}
.filter-group
label
{
color
:
var
(
--dark-color
);
font-weight
:
500
;
font-size
:
0.95rem
;
}
.filter-group
select
{
padding
:
0.5rem
1rem
;
border
:
2px
solid
var
(
--border-color
);
border-radius
:
8px
;
font-size
:
0.95rem
;
color
:
var
(
--dark-color
);
background
:
white
;
cursor
:
pointer
;
transition
:
all
0.3s
;
min-width
:
120px
;
box-shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.1
);
}
.filter-group
select
:hover
{
border-color
:
var
(
--primary-color
);
box-shadow
:
0
2px
6px
rgba
(
194
,
31
,
48
,
0.15
);
}
.filter-group
select
:focus
{
outline
:
none
;
border-color
:
var
(
--primary-color
);
box-shadow
:
0
0
0
3px
rgba
(
194
,
31
,
48
,
0.1
),
0
2px
6px
rgba
(
194
,
31
,
48
,
0.15
);
}
@media
(
max-width
:
768px
)
{
@media
(
max-width
:
768px
)
{
.origin-container
{
.origin-container
{
padding
:
1rem
;
padding
:
1rem
;
}
}
.header
h1
{
.
page-
header
h1
{
font-size
:
1.5rem
;
font-size
:
1.5rem
;
}
}
...
...
src/modules/teach/chart/views/Resource.vue
浏览文件 @
fd7bc1bd
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
{
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
{
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
*
as
echarts
from
'echarts'
import
*
as
echarts
from
'echarts'
import
{
VideoCamera
,
Suitcase
,
FolderOpened
,
Files
,
Monitor
}
from
'@element-plus/icons-vue'
import
{
VideoCamera
,
Suitcase
,
FolderOpened
,
Files
,
Monitor
}
from
'@element-plus/icons-vue'
import
{
getResourceChart
}
from
'../api'
// 统计数据
// 统计数据
const
statsData
=
ref
({
const
statsData
=
ref
({
...
@@ -43,68 +44,14 @@ let electiveTypeChart = null
...
@@ -43,68 +44,14 @@ let electiveTypeChart = null
let
courseTrendChart
=
null
let
courseTrendChart
=
null
let
videoUploadTrendChart
=
null
let
videoUploadTrendChart
=
null
// 模拟数据加载
const
loadData
=
async
()
=>
{
const
loadData
=
()
=>
{
const
{
data
}
=
await
getResourceChart
()
// 基础统计数据
statsData
.
value
=
{
...
statsData
.
value
,
...(
data
.
stats
||
{})
}
statsData
.
value
=
{
resourceTypeData
.
value
=
data
.
resourceTypeData
||
[]
videoCount
:
1250
,
courseTypeData
.
value
=
data
.
courseTypeData
||
[]
coursewareCount
:
890
,
electiveTypeData
.
value
=
data
.
electiveTypeData
||
[]
lessonPlanCount
:
650
,
courseTrendData
.
value
=
data
.
courseTrendData
||
{
dates
:
[],
values
:
[]
}
otherResourceCount
:
320
,
videoUploadTrendData
.
value
=
data
.
videoUploadTrendData
||
{
dates
:
[],
values
:
[]
}
courseCount
:
180
,
todayNewVideo
:
15
,
todayNewCourseware
:
8
,
todayNewLessonPlan
:
12
,
todayNewOtherResource
:
5
,
todayNewCourse
:
3
,
}
// 资源类型占比数据
resourceTypeData
.
value
=
[
{
name
:
'视频资源'
,
value
:
1250
},
{
name
:
'课件资源'
,
value
:
890
},
{
name
:
'教案资源'
,
value
:
650
},
{
name
:
'其他资源'
,
value
:
320
},
]
// 课程类型占比数据
courseTypeData
.
value
=
[
{
name
:
'必修课'
,
value
:
120
},
{
name
:
'选修课'
,
value
:
45
},
{
name
:
'实践课'
,
value
:
10
},
{
name
:
'理论课'
,
value
:
5
},
]
// 选课类型占比数据
electiveTypeData
.
value
=
[
{
name
:
'公共选修'
,
value
:
25
},
{
name
:
'专业选修'
,
value
:
15
},
{
name
:
'通识选修'
,
value
:
5
},
]
// 生成最近30天的走势数据
const
dates
=
[]
const
courseValues
=
[]
const
videoValues
=
[]
const
today
=
new
Date
()
for
(
let
i
=
29
;
i
>=
0
;
i
--
)
{
const
date
=
new
Date
(
today
)
date
.
setDate
(
date
.
getDate
()
-
i
)
dates
.
push
(
date
.
toISOString
().
split
(
'T'
)[
0
])
courseValues
.
push
(
Math
.
floor
(
Math
.
random
()
*
10
)
+
1
)
videoValues
.
push
(
Math
.
floor
(
Math
.
random
()
*
20
)
+
5
)
}
courseTrendData
.
value
=
{
dates
,
values
:
courseValues
,
}
videoUploadTrendData
.
value
=
{
dates
,
values
:
videoValues
,
}
}
}
// 初始化图表
// 初始化图表
...
@@ -359,8 +306,8 @@ const disposeAllCharts = () => {
...
@@ -359,8 +306,8 @@ const disposeAllCharts = () => {
videoUploadTrendChart
=
null
videoUploadTrendChart
=
null
}
}
onMounted
(()
=>
{
onMounted
(
async
()
=>
{
loadData
()
await
loadData
()
setTimeout
(()
=>
{
setTimeout
(()
=>
{
initCharts
()
initCharts
()
},
100
)
},
100
)
...
...
src/modules/teach/chart/views/Visualization.vue
浏览文件 @
fd7bc1bd
<
script
setup
>
<
script
setup
>
import
{
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
{
nextTick
,
ref
,
onMounted
,
onUnmounted
}
from
'vue'
import
*
as
echarts
from
'echarts'
import
*
as
echarts
from
'echarts'
import
{
User
,
School
,
Trophy
,
TrendCharts
,
Monitor
,
Document
}
from
'@element-plus/icons-vue'
import
{
User
,
School
,
Trophy
,
TrendCharts
,
Monitor
,
Document
}
from
'@element-plus/icons-vue'
import
{
getTeachingChart
}
from
'../api'
// 教学数据统计
// 教学数据统计
const
teachingStats
=
ref
({
const
teachingStats
=
ref
({
...
@@ -33,48 +34,46 @@ let scoreDistributionChart = null
...
@@ -33,48 +34,46 @@ let scoreDistributionChart = null
let
subjectScoreChart
=
null
let
subjectScoreChart
=
null
let
scoreTrendChart
=
null
let
scoreTrendChart
=
null
// 加载模拟数据
const
loadData
=
async
()
=>
{
const
loadData
=
()
=>
{
const
{
data
}
=
await
getTeachingChart
()
// 教学数据统计
teachingStats
.
value
=
{
...
teachingStats
.
value
,
...(
data
.
stats
||
{})
}
teachingStats
.
value
=
{
learningProgressData
.
value
=
data
.
learningProgressData
||
{
students
:
[],
progress
:
[]
}
totalTeachers
:
128
,
scoreAnalysisData
.
value
=
data
.
scoreAnalysisData
||
{
distribution
:
[],
subjects
:
[],
trends
:
{
months
:
[],
scores
:
[]
}
}
totalStudents
:
2850
,
}
totalCourses
:
76
,
totalClasses
:
38
,
avgScore
:
87.2
,
completionRate
:
94.5
,
}
// 学习进度数据
const
toChartNumber
=
(
value
)
=>
Number
(
value
)
||
0
learningProgressData
.
value
=
{
students
:
[
'计算机1班'
,
'数学1班'
,
'物理1班'
,
'化学1班'
,
'英语1班'
,
'语文1班'
],
progress
:
[
92
,
88
,
85
,
90
,
87
,
83
],
}
// 成绩分析数据
const
roundedMax
=
(
value
,
minValue
)
=>
{
scoreAnalysisData
.
value
=
{
const
current
=
Math
.
max
(
toChartNumber
(
value
),
minValue
)
distribution
:
[
const
magnitude
=
10
**
Math
.
max
(
0
,
Math
.
floor
(
Math
.
log10
(
current
))
-
1
)
{
name
:
'优秀(90-100)'
,
value
:
35
,
count
:
998
},
return
Math
.
ceil
((
current
*
1.2
)
/
magnitude
)
*
magnitude
{
name
:
'良好(80-89)'
,
value
:
40
,
count
:
1140
},
{
name
:
'中等(70-79)'
,
value
:
20
,
count
:
570
},
{
name
:
'及格(60-69)'
,
value
:
4
,
count
:
114
},
{
name
:
'不及格(<60)'
,
value
:
1
,
count
:
28
},
],
subjects
:
[
{
name
:
'数学'
,
score
:
89.5
},
{
name
:
'英语'
,
score
:
85.2
},
{
name
:
'计算机'
,
score
:
92.8
},
{
name
:
'物理'
,
score
:
87.6
},
{
name
:
'化学'
,
score
:
88.9
},
{
name
:
'语文'
,
score
:
84.3
},
],
trends
:
{
months
:
[
'1月'
,
'2月'
,
'3月'
,
'4月'
,
'5月'
,
'6月'
],
scores
:
[
85.2
,
86.8
,
87.5
,
88.1
,
87.9
,
87.2
],
},
}
}
}
const
overviewMetrics
=
()
=>
[
{
name
:
'教师数量'
,
value
:
toChartNumber
(
teachingStats
.
value
.
totalTeachers
),
max
:
roundedMax
(
teachingStats
.
value
.
totalTeachers
,
10
),
},
{
name
:
'学生数量'
,
value
:
toChartNumber
(
teachingStats
.
value
.
totalStudents
),
max
:
roundedMax
(
teachingStats
.
value
.
totalStudents
,
100
),
},
{
name
:
'课程数量'
,
value
:
toChartNumber
(
teachingStats
.
value
.
totalCourses
),
max
:
roundedMax
(
teachingStats
.
value
.
totalCourses
,
10
),
},
{
name
:
'班级数量'
,
value
:
toChartNumber
(
teachingStats
.
value
.
totalClasses
),
max
:
roundedMax
(
teachingStats
.
value
.
totalClasses
,
10
),
},
{
name
:
'平均成绩'
,
value
:
toChartNumber
(
teachingStats
.
value
.
avgScore
),
max
:
100
},
{
name
:
'完成率'
,
value
:
toChartNumber
(
teachingStats
.
value
.
completionRate
),
max
:
100
},
]
// 初始化图表
// 初始化图表
const
initCharts
=
()
=>
{
const
initCharts
=
()
=>
{
// 教学数据总览
// 教学数据总览
...
@@ -93,16 +92,9 @@ const initCharts = () => {
...
@@ -93,16 +92,9 @@ const initCharts = () => {
},
},
tooltip
:
{},
tooltip
:
{},
radar
:
{
radar
:
{
indicator
:
[
indicator
:
overviewMetrics
().
map
(({
name
,
max
})
=>
({
name
,
max
})),
{
name
:
'教师数量'
,
max
:
150
},
{
name
:
'学生数量'
,
max
:
3000
},
{
name
:
'课程数量'
,
max
:
100
},
{
name
:
'班级数量'
,
max
:
50
},
{
name
:
'平均成绩'
,
max
:
100
},
{
name
:
'完成率'
,
max
:
100
},
],
center
:
[
'50%'
,
'60%'
],
center
:
[
'50%'
,
'60%'
],
radius
:
'
70
%'
,
radius
:
'
62
%'
,
},
},
series
:
[
series
:
[
{
{
...
@@ -110,14 +102,7 @@ const initCharts = () => {
...
@@ -110,14 +102,7 @@ const initCharts = () => {
type
:
'radar'
,
type
:
'radar'
,
data
:
[
data
:
[
{
{
value
:
[
value
:
overviewMetrics
().
map
(({
value
})
=>
value
),
teachingStats
.
value
.
totalTeachers
,
teachingStats
.
value
.
totalStudents
,
teachingStats
.
value
.
totalCourses
,
teachingStats
.
value
.
totalClasses
,
teachingStats
.
value
.
avgScore
,
teachingStats
.
value
.
completionRate
,
],
name
:
'当前数据'
,
name
:
'当前数据'
,
areaStyle
:
{
color
:
'rgba(64, 158, 255, 0.3)'
},
areaStyle
:
{
color
:
'rgba(64, 158, 255, 0.3)'
},
},
},
...
@@ -232,7 +217,7 @@ const initCharts = () => {
...
@@ -232,7 +217,7 @@ const initCharts = () => {
scoreDistributionChart
.
setOption
(
option
)
scoreDistributionChart
.
setOption
(
option
)
}
}
//
学科
成绩柱状图
//
课程
成绩柱状图
const
subjectEl
=
document
.
getElementById
(
'subjectScoreChart'
)
const
subjectEl
=
document
.
getElementById
(
'subjectScoreChart'
)
if
(
subjectEl
)
{
if
(
subjectEl
)
{
if
(
subjectScoreChart
)
{
if
(
subjectScoreChart
)
{
...
@@ -241,7 +226,7 @@ const initCharts = () => {
...
@@ -241,7 +226,7 @@ const initCharts = () => {
subjectScoreChart
=
echarts
.
init
(
subjectEl
)
subjectScoreChart
=
echarts
.
init
(
subjectEl
)
const
option
=
{
const
option
=
{
title
:
{
title
:
{
text
:
'
各学科平均成绩
'
,
text
:
'
课程平均成绩 Top10
'
,
left
:
'center'
,
left
:
'center'
,
top
:
'5%'
,
top
:
'5%'
,
textStyle
:
{
fontSize
:
16
},
textStyle
:
{
fontSize
:
16
},
...
@@ -359,11 +344,10 @@ const disposeAllCharts = () => {
...
@@ -359,11 +344,10 @@ const disposeAllCharts = () => {
scoreTrendChart
=
null
scoreTrendChart
=
null
}
}
onMounted
(()
=>
{
onMounted
(
async
()
=>
{
loadData
()
await
loadData
()
setTimeout
(()
=>
{
await
nextTick
()
initCharts
()
initCharts
()
},
500
)
window
.
addEventListener
(
'resize'
,
handleResize
)
window
.
addEventListener
(
'resize'
,
handleResize
)
})
})
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论