15 changed files with 1489 additions and 688 deletions
-
BINsrc/assets/images/page4_03-b.png
-
BINsrc/assets/images/pageOne-1-b.png
-
BINsrc/assets/images/pageOne-2-b.png
-
4src/assets/json/lib.json
-
13src/assets/styles/font-some.css
-
220src/assets/styles/index.scss
-
473src/components/echart/todayCircle.vue
-
491src/components/echart/yearCircle.vue
-
2src/main.js
-
14src/views/index.vue
-
536src/views/map/index.vue
-
172src/views/pageFive/index.vue
-
133src/views/pageFour/index.vue
-
3src/views/pageOne/index.vue
-
116src/views/pageThree/index.vue
|
After Width: 1835 | Height: 1201 | Size: 69 KiB |
|
After Width: 267 | Height: 128 | Size: 25 KiB |
|
After Width: 267 | Height: 128 | Size: 28 KiB |
@ -1,4 +1,4 @@ |
|||||
{ |
{ |
||||
"大湾城市书房":[114.664064,30.478882], |
|
||||
"张家湾城市书房":[114.635376,30.532215] |
|
||||
|
"葛店城市书房·大湾分馆":[114.664064,30.478882], |
||||
|
"葛店城市书房·张家湾分馆":[114.635376,30.532215] |
||||
} |
} |
||||
@ -1,274 +1,311 @@ |
|||||
<template> |
<template> |
||||
<div style="height: calc(100% - 200px); display: flex; align-items: center; justify-content: flex-start;"> |
|
||||
<div id="todayType" style="width:400px; height: 230px" /> |
|
||||
|
<!-- 1. 样式抽离到class,提升可读性和维护性 --> |
||||
|
<div class="chart-container"> |
||||
|
<div ref="todayTypeRef" class="today-type-chart" /> |
||||
</div> |
</div> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
|
import centerImg from '@/assets/images/circle-bg.png' |
||||
|
|
||||
export default { |
export default { |
||||
|
name: 'TodayTypePieChart', // 3. 增加组件名,便于调试和复用 |
||||
props: { |
props: { |
||||
todayAllNum: { |
todayAllNum: { |
||||
type: Object, |
type: Object, |
||||
require: true, |
|
||||
default: function() { |
|
||||
return {} |
|
||||
} |
|
||||
|
required: true, // 4. 修正拼写错误 require -> required |
||||
|
default: () => ({}) // 5. 简化默认值写法 |
||||
} |
} |
||||
}, |
}, |
||||
data() { |
data() { |
||||
return { |
return { |
||||
|
myChart: null, |
||||
|
initDebounceTimer: null |
||||
} |
} |
||||
}, |
}, |
||||
watch: { |
watch: { |
||||
'todayAllNum': { |
|
||||
|
todayAllNum: { |
||||
handler(val) { |
handler(val) { |
||||
setTimeout(() => { |
|
||||
this.initTodayCircle() |
|
||||
}, 100) |
|
||||
|
if (!val) return |
||||
|
|
||||
|
clearTimeout(this.initDebounceTimer) |
||||
|
this.initDebounceTimer = setTimeout(() => { |
||||
|
this.$nextTick(() => { |
||||
|
this.initTodayCircle() |
||||
|
}) |
||||
|
}, 300) |
||||
}, |
}, |
||||
immediate: true, |
immediate: true, |
||||
deep: true |
deep: true |
||||
} |
} |
||||
}, |
}, |
||||
created() { |
|
||||
}, |
|
||||
beforeDestroy() { |
|
||||
}, |
|
||||
mounted() { |
mounted() { |
||||
this.initTodayCircle() |
this.initTodayCircle() |
||||
}, |
}, |
||||
|
beforeDestroy() { |
||||
|
this.destroyChart() |
||||
|
}, |
||||
methods: { |
methods: { |
||||
|
/** |
||||
|
* 9. 销毁图表实例和定时器(抽离通用逻辑) |
||||
|
*/ |
||||
|
destroyChart() { |
||||
|
clearTimeout(this.initDebounceTimer) |
||||
|
if (this.myChart) { |
||||
|
this.myChart.dispose() |
||||
|
this.myChart = null |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 10. 计算数组value总和(抽离为独立方法,提升可读性) |
||||
|
* @param {Array} arr - 数据数组 |
||||
|
* @returns {Number} 总和 |
||||
|
*/ |
||||
|
calculateTotal(arr) { |
||||
|
if (!Array.isArray(arr) || arr.length === 0) return 0 |
||||
|
return arr.reduce((total, item) => total + (item.value || 0), 0) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 11. 构建图例格式化文本(抽离独立方法) |
||||
|
* @param {String} name - 图例名称 |
||||
|
* @param {Array} data - 图表数据 |
||||
|
* @returns {Array|String} 格式化后的文本 |
||||
|
*/ |
||||
|
formatLegendName(name, data) { |
||||
|
const total = this.calculateTotal(data) |
||||
|
const targetItem = data.find(item => item.name === name) |
||||
|
|
||||
|
if (!targetItem) return name |
||||
|
|
||||
|
const percentage = total > 0 ? `${((targetItem.value / total) * 100).toFixed(0)}%` : '0%' |
||||
|
return [`{name|${name}}`, `{num|${percentage}}`] |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 初始化今日类型饼图 |
||||
|
*/ |
||||
initTodayCircle() { |
initTodayCircle() { |
||||
const optionData = [ |
|
||||
|
// 12. 使用ref替代getElementById,更符合Vue规范 |
||||
|
const chartDom = this.$refs.todayTypeRef |
||||
|
if (!chartDom) { |
||||
|
console.warn('图表容器未找到') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 13. 数据处理抽离,提升代码可读性 |
||||
|
const chartData = [ |
||||
{ value: this.todayAllNum.headerLib || 0, name: '大湾' }, |
{ value: this.todayAllNum.headerLib || 0, name: '大湾' }, |
||||
{ value: this.todayAllNum.branchLib || 0, name: '张家湾' } |
{ value: this.todayAllNum.branchLib || 0, name: '张家湾' } |
||||
] |
] |
||||
const myChart = this.$echarts.init(document.getElementById('todayType')) |
|
||||
|
|
||||
function arrCount(arr) { |
|
||||
let count = 0 |
|
||||
arr.forEach(item => { |
|
||||
count = count + item.value |
|
||||
}) |
|
||||
return count |
|
||||
|
// 14. 销毁旧实例,避免内存泄漏 |
||||
|
if (this.myChart) { |
||||
|
this.myChart.dispose() |
||||
} |
} |
||||
var centerImg = require('@/assets/images/circle-bg.png') |
|
||||
const option = { |
|
||||
baseOption: { |
|
||||
tooltip: { |
|
||||
show: false, |
|
||||
trigger: 'item', |
|
||||
position: 'bottom', |
|
||||
textStyle: { |
|
||||
color: '#EEF6FF', |
|
||||
fontSize: '18' |
|
||||
} |
|
||||
// backgroundColor: 'rgba(74, 144, 226, 0.84)', |
|
||||
// formatter: (params) => { |
|
||||
// return `<div>${params.seriesName} <br> ${params.data.name}:${this.$parent.formatter(params.data.value)} (${params.percent}%)</div>` |
|
||||
// } |
|
||||
}, |
|
||||
legend: { |
|
||||
orient: 'vertical', |
|
||||
right: 40, |
|
||||
top: 55, |
|
||||
// textStyle: { |
|
||||
// color: '#EEF6FF', |
|
||||
// padding: [20, 0, 18, 4], |
|
||||
// fontSize: '14' |
|
||||
// }, |
|
||||
itemWidth: 10, |
|
||||
itemHeight: 10, |
|
||||
icon: 'circle', |
|
||||
selectedMode: false, |
|
||||
data: ['大湾', '张家湾'], |
|
||||
formatter: (name) => { |
|
||||
// `${name} ${((flag.value / count).toFixed(2)) * 100 + '%'}` |
|
||||
const count = arrCount(optionData) |
|
||||
if (optionData) { |
|
||||
const flag = optionData?.find(item => name === item.name) |
|
||||
if (flag) { |
|
||||
const percentage = count > 0 ? ((flag.value / count) * 100).toFixed(0) + '%' : '0%' |
|
||||
return [`{name|${name}}`, `{num|${percentage}}`] |
|
||||
} |
|
||||
} |
|
||||
return name |
|
||||
}, |
|
||||
textStyle: { |
|
||||
rich: { |
|
||||
name: { |
|
||||
fontSize: 18, |
|
||||
color: '#EEF6FF', |
|
||||
padding: [20, 0, 20, 4], |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular' |
|
||||
}, |
|
||||
num: { |
|
||||
fontSize: 20, |
|
||||
fontWeight: 600, |
|
||||
padding: [20, 0, 20, 15], |
|
||||
color: '#4C90FF', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular' |
|
||||
} |
|
||||
|
|
||||
|
// 初始化图表 |
||||
|
this.myChart = this.$echarts.init(chartDom) |
||||
|
|
||||
|
// 15. 抽离option配置,拆分基础配置和媒体查询配置,提升可维护性 |
||||
|
const baseOption = { |
||||
|
tooltip: { |
||||
|
show: false, |
||||
|
trigger: 'item', |
||||
|
position: 'bottom', |
||||
|
textStyle: { |
||||
|
color: '#EEF6FF', |
||||
|
fontSize: 18 |
||||
|
} |
||||
|
}, |
||||
|
legend: { |
||||
|
orient: 'vertical', |
||||
|
right: 40, |
||||
|
top: 55, |
||||
|
itemWidth: 10, |
||||
|
itemHeight: 10, |
||||
|
icon: 'circle', |
||||
|
selectedMode: false, |
||||
|
data: ['大湾', '张家湾'], |
||||
|
formatter: (name) => this.formatLegendName(name, chartData), |
||||
|
textStyle: { |
||||
|
rich: { |
||||
|
name: { |
||||
|
fontSize: 18, |
||||
|
color: '#EEF6FF', |
||||
|
padding: [20, 0, 20, 4], |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular' |
||||
|
}, |
||||
|
num: { |
||||
|
fontSize: 24, |
||||
|
fontWeight: 600, |
||||
|
padding: [20, 0, 20, 15], |
||||
|
color: '#4C90FF', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular' |
||||
} |
} |
||||
} |
} |
||||
}, |
|
||||
// 中心图片配置(关键代码) |
|
||||
graphic: [ |
|
||||
{ |
|
||||
type: 'image', |
|
||||
id: 'logo', |
|
||||
left: '8.5%', // 调整图片位置 |
|
||||
top: '22%', // 调整图片位置 |
|
||||
z: -10, |
|
||||
bounding: 'raw', |
|
||||
rotation: 0, // 旋转 |
|
||||
origin: [64.5, 32.5], // 中心点 |
|
||||
scale: [1.0, 1.0], // 缩放 |
|
||||
// 设置图片样式 |
|
||||
style: { |
|
||||
image: centerImg, |
|
||||
width: 132, |
|
||||
height: 131, |
|
||||
opacity: 1 |
|
||||
} |
|
||||
|
} |
||||
|
}, |
||||
|
graphic: [ |
||||
|
{ |
||||
|
type: 'image', |
||||
|
id: 'logo', |
||||
|
left: '8.5%', |
||||
|
top: '22%', |
||||
|
z: -10, |
||||
|
bounding: 'raw', |
||||
|
rotation: 0, |
||||
|
origin: [64.5, 32.5], |
||||
|
scale: [1.0, 1.0], |
||||
|
style: { |
||||
|
image: centerImg, |
||||
|
width: 132, |
||||
|
height: 131, |
||||
|
opacity: 1 |
||||
} |
} |
||||
], |
|
||||
series: [ |
|
||||
{ |
|
||||
name: '今日累计借阅', |
|
||||
type: 'pie', |
|
||||
left: '-50%', |
|
||||
radius: ['60%', '70%'], |
|
||||
avoidLabelOverlap: false, |
|
||||
|
} |
||||
|
], |
||||
|
series: [ |
||||
|
{ |
||||
|
name: '今日累计借阅', |
||||
|
type: 'pie', |
||||
|
left: '-50%', |
||||
|
radius: ['60%', '70%'], |
||||
|
avoidLabelOverlap: false, |
||||
|
label: { |
||||
|
show: false, |
||||
|
position: 'center' |
||||
|
}, |
||||
|
labelLine: { |
||||
|
show: true |
||||
|
}, |
||||
|
itemStyle: { |
||||
|
borderWidth: 2, |
||||
|
borderColor: 'rgba(16,16,21,0.4)' |
||||
|
}, |
||||
|
emphasis: { |
||||
label: { |
label: { |
||||
show: false, |
|
||||
position: 'center' |
|
||||
}, |
|
||||
labelLine: { |
|
||||
show: true |
|
||||
}, |
|
||||
itemStyle: { |
|
||||
borderWidth: 2, |
|
||||
borderColor: 'rgba(16,16,21,0.4)' |
|
||||
}, |
|
||||
emphasis: { |
|
||||
label: { |
|
||||
show: true, |
|
||||
// 自定义文字显示,函数默认params接受当前指向所有属性 |
|
||||
formatter: function(params) { |
|
||||
const { value, name } = params |
|
||||
return [ |
|
||||
`{c| ${value}}`, |
|
||||
`{b| ${name}}` |
|
||||
].join('\n') // 换行 |
|
||||
|
show: true, |
||||
|
formatter: ({ value, name }) => [ // 16. 解构赋值简化代码 |
||||
|
`{c| ${value}}`, |
||||
|
`{b| ${name}}` |
||||
|
].join('\n'), |
||||
|
rich: { |
||||
|
c: { |
||||
|
fontSize: 24, |
||||
|
fontWeight: 600, |
||||
|
color: '#317FFF', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular', |
||||
|
lineHeight: 34 |
||||
}, |
}, |
||||
rich: { |
|
||||
c: { |
|
||||
fontSize: 24, |
|
||||
fontWeight: 600, |
|
||||
color: '#317FFF', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular', |
|
||||
lineHeight: 34 |
|
||||
}, |
|
||||
b: { |
|
||||
fontSize: 18, |
|
||||
color: '#fff', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular' |
|
||||
} |
|
||||
|
b: { |
||||
|
fontSize: 18, |
||||
|
color: '#fff', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular' |
||||
} |
} |
||||
} |
} |
||||
}, |
|
||||
color: ['#317FFF', '#31DFFF'], |
|
||||
data: optionData |
|
||||
} |
|
||||
] |
|
||||
}, |
|
||||
media: [ |
|
||||
{ |
|
||||
query: { |
|
||||
maxWidth: 317 |
|
||||
|
} |
||||
}, |
}, |
||||
option: { |
|
||||
legend: { |
|
||||
right: 30, |
|
||||
top: 'center', |
|
||||
textStyle: { |
|
||||
rich: { |
|
||||
name: { |
|
||||
fontSize: 14 |
|
||||
}, |
|
||||
num: { |
|
||||
fontSize: 16 |
|
||||
} |
|
||||
} |
|
||||
|
color: ['#FFD14F', '#317FFF'], |
||||
|
data: chartData |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
const mediaOption = [ |
||||
|
{ |
||||
|
query: { maxWidth: 317 }, |
||||
|
option: { |
||||
|
legend: { |
||||
|
right: 30, |
||||
|
top: 'center', |
||||
|
textStyle: { |
||||
|
rich: { |
||||
|
name: { fontSize: 14 }, |
||||
|
num: { fontSize: 16 } |
||||
} |
} |
||||
}, |
|
||||
// 中心图片配置(关键代码) |
|
||||
graphic: [ |
|
||||
{ |
|
||||
left: '8%', // 调整图片位置 |
|
||||
top: '26%', // 调整图片位置 |
|
||||
z: -10, |
|
||||
bounding: 'raw', |
|
||||
rotation: 0, // 旋转 |
|
||||
origin: [64.5, 32.5], // 中心点 |
|
||||
scale: [1.0, 1.0], // 缩放 |
|
||||
// 设置图片样式 |
|
||||
style: { |
|
||||
image: centerImg, |
|
||||
width: 110, |
|
||||
height: 110, |
|
||||
opacity: 1 |
|
||||
} |
|
||||
|
} |
||||
|
}, |
||||
|
graphic: [ |
||||
|
{ |
||||
|
left: '8%', |
||||
|
top: '26%', |
||||
|
z: -10, |
||||
|
bounding: 'raw', |
||||
|
rotation: 0, |
||||
|
origin: [64.5, 32.5], |
||||
|
scale: [1.0, 1.0], |
||||
|
style: { |
||||
|
image: centerImg, |
||||
|
width: 110, |
||||
|
height: 110, |
||||
|
opacity: 1 |
||||
} |
} |
||||
], |
|
||||
series: [ |
|
||||
{ |
|
||||
type: 'pie', |
|
||||
radius: ['50%', '55%'], |
|
||||
emphasis: { |
|
||||
label: { |
|
||||
rich: { |
|
||||
c: { |
|
||||
fontSize: 22, |
|
||||
lineHeight: 28 |
|
||||
}, |
|
||||
b: { |
|
||||
fontSize: 16 |
|
||||
} |
|
||||
} |
|
||||
|
} |
||||
|
], |
||||
|
series: [ |
||||
|
{ |
||||
|
type: 'pie', |
||||
|
radius: ['50%', '55%'], |
||||
|
emphasis: { |
||||
|
label: { |
||||
|
rich: { |
||||
|
c: { fontSize: 22, lineHeight: 28 }, |
||||
|
b: { fontSize: 16 } |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
] |
|
||||
} |
|
||||
|
} |
||||
|
] |
||||
} |
} |
||||
] |
|
||||
} |
|
||||
myChart.setOption(option) |
|
||||
// // 然后可以通过定时器来旋转背景图 |
|
||||
// var step = 0 |
|
||||
// setInterval(function() { |
|
||||
// // 每秒旋转1度 |
|
||||
// myChart.setOption({ |
|
||||
// graphic: { |
|
||||
// id: 'logo', |
|
||||
// style: { |
|
||||
// transform: 'rotate(' + (step++ % 360) + 'deg)' // 持续旋转 |
|
||||
// } |
|
||||
// } |
|
||||
// }) |
|
||||
// }, 1000) |
|
||||
|
} |
||||
|
] |
||||
|
|
||||
|
// 合并配置并设置 |
||||
|
const option = { baseOption, media: mediaOption } |
||||
|
this.myChart.setOption(option) |
||||
|
|
||||
|
// 自动轮播tooltip |
||||
|
this.$LoopShowTooltip(this.myChart, baseOption, { loopSeries: true, interval: 4000 }) |
||||
|
|
||||
this.$LoopShowTooltip(myChart, option.baseOption, { loopSeries: true, interval: 4000 }) |
|
||||
|
// 17. 监听窗口大小变化,自适应图表 |
||||
|
window.addEventListener('resize', this.handleResize) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 18. 窗口大小变化时重绘图表 |
||||
|
*/ |
||||
|
handleResize() { |
||||
|
if (this.myChart) { |
||||
|
this.myChart.resize() |
||||
|
} |
||||
} |
} |
||||
|
}, |
||||
|
|
||||
|
// 19. 移除无用的created钩子 |
||||
|
beforeUnmount() { |
||||
|
// 20. 移除resize监听,避免内存泄漏 |
||||
|
window.removeEventListener('resize', this.handleResize) |
||||
|
this.destroyChart() |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
</script> |
</script> |
||||
|
|
||||
<style lang="scss"> |
|
||||
|
<style lang="scss" scoped> // 21. 添加scoped,避免样式污染 |
||||
@import "~@/assets/styles/index.scss"; |
@import "~@/assets/styles/index.scss"; |
||||
@import "~@/assets/styles/font-some.css"; |
@import "~@/assets/styles/font-some.css"; |
||||
|
|
||||
|
// 22. 样式抽离,便于维护和修改 |
||||
|
.chart-container { |
||||
|
height: calc(100% - 200px); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-start; |
||||
|
|
||||
|
.today-type-chart { |
||||
|
width: 400px; |
||||
|
height: 230px; |
||||
|
} |
||||
|
} |
||||
</style> |
</style> |
||||
@ -1,289 +1,312 @@ |
|||||
<template> |
<template> |
||||
<div style="height: calc(100% - 200px); display: flex; align-items: center; justify-content: flex-start;"> |
|
||||
<div id="modelType" style="width:400px; height: 230px" /> |
|
||||
|
<!-- 1. 内联样式抽离为class,提升可读性和维护性 --> |
||||
|
<div class="year-chart-container"> |
||||
|
<!-- 2. 使用ref替代getElementById,符合Vue规范 --> |
||||
|
<div ref="modelTypeRef" class="model-type-chart" /> |
||||
</div> |
</div> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
|
// 3. 图片引入改为ES6 import规范,替代require |
||||
|
import centerImg from '@/assets/images/circle-bg.png' |
||||
|
|
||||
export default { |
export default { |
||||
|
name: 'YearTypePieChart', // 4. 增加组件名,便于调试和复用 |
||||
props: { |
props: { |
||||
yearAllNum: { |
yearAllNum: { |
||||
type: Object, |
type: Object, |
||||
require: true, |
|
||||
default: function() { |
|
||||
return {} |
|
||||
} |
|
||||
|
required: true, // 5. 修正拼写错误 require -> required |
||||
|
default: () => ({}) // 6. 简化默认值写法 |
||||
} |
} |
||||
}, |
}, |
||||
data() { |
data() { |
||||
return { |
return { |
||||
|
myChart: null, // 7. 存储图表实例,便于销毁和重绘 |
||||
|
resizeTimer: null // 8. 防抖定时器,优化resize性能 |
||||
} |
} |
||||
}, |
}, |
||||
watch: { |
watch: { |
||||
'yearAllNum': { |
|
||||
|
yearAllNum: { |
||||
handler(val) { |
handler(val) { |
||||
setTimeout(() => { |
|
||||
this.initYearCircle() |
|
||||
|
// 9. 空值保护:值为空时直接返回,避免无效执行 |
||||
|
if (!val) return |
||||
|
|
||||
|
// 10. 防抖处理,避免频繁触发图表初始化 |
||||
|
clearTimeout(this.initDebounceTimer) |
||||
|
this.initDebounceTimer = setTimeout(() => { |
||||
|
this.$nextTick(() => { |
||||
|
this.initYearCircle() |
||||
|
}) |
||||
}, 100) |
}, 100) |
||||
}, |
}, |
||||
immediate: true, |
immediate: true, |
||||
deep: true |
deep: true |
||||
} |
} |
||||
}, |
}, |
||||
created() { |
|
||||
}, |
|
||||
beforeDestroy() { |
|
||||
}, |
|
||||
mounted() { |
mounted() { |
||||
|
// 11. 初始化图表 |
||||
this.initYearCircle() |
this.initYearCircle() |
||||
|
// 12. 监听窗口大小变化,实现图表自适应 |
||||
|
window.addEventListener('resize', this.handleResize) |
||||
|
}, |
||||
|
beforeUnmount() { |
||||
|
// 13. 组件销毁时清理资源,避免内存泄漏 |
||||
|
window.removeEventListener('resize', this.handleResize) |
||||
|
this.destroyChart() |
||||
|
clearTimeout(this.initDebounceTimer) |
||||
}, |
}, |
||||
methods: { |
methods: { |
||||
|
/** |
||||
|
* 14. 销毁图表实例(抽离通用逻辑) |
||||
|
*/ |
||||
|
destroyChart() { |
||||
|
if (this.myChart) { |
||||
|
this.myChart.dispose() |
||||
|
this.myChart = null |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 15. 计算数组value总和(抽离独立方法,提升可读性) |
||||
|
* @param {Array} arr - 数据数组 |
||||
|
* @returns {Number} 总和 |
||||
|
*/ |
||||
|
calculateTotal(arr) { |
||||
|
if (!Array.isArray(arr) || arr.length === 0) return 0 |
||||
|
return arr.reduce((total, item) => total + (item.value || 0), 0) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 16. 构建图例格式化文本(抽离独立方法) |
||||
|
* @param {String} name - 图例名称 |
||||
|
* @param {Array} data - 图表数据 |
||||
|
* @returns {Array|String} 格式化后的文本 |
||||
|
*/ |
||||
|
formatLegendName(name, data) { |
||||
|
const total = this.calculateTotal(data) |
||||
|
const targetItem = data.find(item => item.name === name) |
||||
|
|
||||
|
if (!targetItem) return name |
||||
|
|
||||
|
const percentage = total > 0 ? `${((targetItem.value / total) * 100).toFixed(0)}%` : '0%' |
||||
|
return [`{name|${name}}`, `{num|${percentage}}`] |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 初始化本年类型饼图 |
||||
|
*/ |
||||
initYearCircle() { |
initYearCircle() { |
||||
const optionData = [ |
|
||||
|
// 17. 获取图表容器DOM,增加判空保护 |
||||
|
const chartDom = this.$refs.modelTypeRef |
||||
|
if (!chartDom) { |
||||
|
console.warn('图表容器未找到,初始化失败') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 18. 数据处理抽离,提升代码可读性 |
||||
|
const chartData = [ |
||||
{ value: this.yearAllNum.headerLib || 0, name: '大湾' }, |
{ value: this.yearAllNum.headerLib || 0, name: '大湾' }, |
||||
{ value: this.yearAllNum.branchLib || 0, name: '张家湾' } |
{ value: this.yearAllNum.branchLib || 0, name: '张家湾' } |
||||
] |
] |
||||
const myChart = this.$echarts.init(document.getElementById('modelType')) |
|
||||
|
|
||||
function arrCount(arr) { |
|
||||
let count = 0 |
|
||||
arr.forEach(item => { |
|
||||
count = count + item.value |
|
||||
}) |
|
||||
return count |
|
||||
} |
|
||||
var centerImg = require('@/assets/images/circle-bg.png') |
|
||||
const option = { |
|
||||
baseOption: { |
|
||||
tooltip: { |
|
||||
show: false, |
|
||||
trigger: 'item', |
|
||||
position: 'bottom', |
|
||||
textStyle: { |
|
||||
color: '#EEF6FF', |
|
||||
fontSize: '18' |
|
||||
} |
|
||||
// backgroundColor: 'rgba(74, 144, 226, 0.84)', |
|
||||
// formatter: (params) => { |
|
||||
// return `<div>${params.seriesName} <br> ${params.data.name}:${this.$parent.formatter(params.data.value)} (${params.percent}%)</div>` |
|
||||
// } |
|
||||
}, |
|
||||
legend: { |
|
||||
orient: 'vertical', |
|
||||
right: 40, |
|
||||
top: 55, |
|
||||
// textStyle: { |
|
||||
// color: '#EEF6FF', |
|
||||
// padding: [20, 0, 18, 4], |
|
||||
// fontSize: '14' |
|
||||
// }, |
|
||||
itemWidth: 10, |
|
||||
itemHeight: 10, |
|
||||
icon: 'circle', |
|
||||
selectedMode: false, |
|
||||
data: ['大湾', '张家湾'], |
|
||||
formatter: (name) => { |
|
||||
// `${name} ${((flag.value / count).toFixed(2)) * 100 + '%'}` |
|
||||
const count = arrCount(optionData) |
|
||||
if (optionData) { |
|
||||
const flag = optionData?.find(item => name === item.name) |
|
||||
if (flag) { |
|
||||
const percentage = count > 0 ? ((flag.value / count) * 100).toFixed(0) + '%' : '0%' |
|
||||
return [`{name|${name}}`, `{num|${percentage}}`] |
|
||||
} |
|
||||
} |
|
||||
return name |
|
||||
}, |
|
||||
textStyle: { |
|
||||
rich: { |
|
||||
name: { |
|
||||
fontSize: 18, |
|
||||
color: '#EEF6FF', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular', |
|
||||
padding: [20, 0, 20, 4] |
|
||||
}, |
|
||||
num: { |
|
||||
fontSize: 20, |
|
||||
fontWeight: 600, |
|
||||
padding: [20, 0, 20, 15], |
|
||||
color: '#4C90FF', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular' |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
// formatter: function(name) { |
|
||||
// let tarValue |
|
||||
// const count = arrCount(optionData) |
|
||||
// for (let i = 0; i < optionData.length; i++) { |
|
||||
// if (data[i].name === name) { |
|
||||
// tarValue = data[i].value |
|
||||
// } |
|
||||
// } |
|
||||
|
// 19. 先销毁旧实例,避免多实例叠加 |
||||
|
this.destroyChart() |
||||
|
|
||||
|
// 20. 初始化图表实例 |
||||
|
this.myChart = this.$echarts.init(chartDom) |
||||
|
|
||||
// return [`{name|${name}}`, `{num|${((tarValue / count).toFixed(2)) * 100 + '%'}}`].join('\n') |
|
||||
// } |
|
||||
}, |
|
||||
// 中心图片配置(关键代码) |
|
||||
graphic: [ |
|
||||
{ |
|
||||
type: 'image', |
|
||||
id: 'logo', |
|
||||
left: '8.5%', // 调整图片位置 |
|
||||
top: '22%', // 调整图片位置 |
|
||||
z: -10, |
|
||||
bounding: 'raw', |
|
||||
rotation: 0, // 旋转 |
|
||||
origin: [64.5, 32.5], // 中心点 |
|
||||
scale: [1.0, 1.0], // 缩放 |
|
||||
// 设置图片样式 |
|
||||
style: { |
|
||||
image: centerImg, |
|
||||
width: 132, |
|
||||
height: 131, |
|
||||
opacity: 1 |
|
||||
|
// 21. 拆分option配置,降低函数复杂度 |
||||
|
const baseOption = { |
||||
|
tooltip: { |
||||
|
show: false, |
||||
|
trigger: 'item', |
||||
|
position: 'bottom', |
||||
|
textStyle: { |
||||
|
color: '#EEF6FF', |
||||
|
fontSize: 18 |
||||
|
} |
||||
|
}, |
||||
|
legend: { |
||||
|
orient: 'vertical', |
||||
|
right: 40, |
||||
|
top: 55, |
||||
|
itemWidth: 10, |
||||
|
itemHeight: 10, |
||||
|
icon: 'circle', |
||||
|
selectedMode: false, |
||||
|
data: ['大湾', '张家湾'], |
||||
|
formatter: (name) => this.formatLegendName(name, chartData), |
||||
|
textStyle: { |
||||
|
rich: { |
||||
|
name: { |
||||
|
fontSize: 18, |
||||
|
color: '#EEF6FF', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular', |
||||
|
padding: [20, 0, 20, 4] |
||||
|
}, |
||||
|
num: { |
||||
|
fontSize: 24, |
||||
|
fontWeight: 600, |
||||
|
padding: [20, 0, 20, 15], |
||||
|
color: '#4C90FF', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular' |
||||
} |
} |
||||
} |
} |
||||
], |
|
||||
series: [ |
|
||||
{ |
|
||||
name: '本年累计借阅', |
|
||||
type: 'pie', |
|
||||
left: '-50%', |
|
||||
radius: ['60%', '70%'], |
|
||||
avoidLabelOverlap: false, |
|
||||
|
} |
||||
|
}, |
||||
|
graphic: [ |
||||
|
{ |
||||
|
type: 'image', |
||||
|
id: 'logo', |
||||
|
left: '8.5%', |
||||
|
top: '22%', |
||||
|
z: -10, |
||||
|
bounding: 'raw', |
||||
|
rotation: 0, |
||||
|
origin: [64.5, 32.5], |
||||
|
scale: [1.0, 1.0], |
||||
|
style: { |
||||
|
image: centerImg, |
||||
|
width: 132, |
||||
|
height: 131, |
||||
|
opacity: 1 |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
series: [ |
||||
|
{ |
||||
|
name: '本年累计借阅', |
||||
|
type: 'pie', |
||||
|
left: '-50%', |
||||
|
radius: ['60%', '70%'], |
||||
|
avoidLabelOverlap: false, |
||||
|
label: { |
||||
|
show: false, |
||||
|
position: 'center' |
||||
|
}, |
||||
|
labelLine: { |
||||
|
show: true |
||||
|
}, |
||||
|
itemStyle: { |
||||
|
borderWidth: 2, |
||||
|
borderColor: 'rgba(16,16,21,0.4)' |
||||
|
}, |
||||
|
emphasis: { |
||||
label: { |
label: { |
||||
show: false, |
|
||||
position: 'center' |
|
||||
}, |
|
||||
labelLine: { |
|
||||
show: true |
|
||||
}, |
|
||||
itemStyle: { |
|
||||
borderWidth: 2, |
|
||||
borderColor: 'rgba(16,16,21,0.4)' |
|
||||
}, |
|
||||
emphasis: { |
|
||||
label: { |
|
||||
show: true, |
|
||||
// 自定义文字显示,函数默认params接受当前指向所有属性 |
|
||||
formatter: function(params) { |
|
||||
const { value, name } = params |
|
||||
return [ |
|
||||
`{c| ${value}}`, |
|
||||
`{b| ${name}}` |
|
||||
].join('\n') // 换行 |
|
||||
|
show: true, |
||||
|
// 22. 解构赋值简化代码 |
||||
|
formatter: ({ value, name }) => [ |
||||
|
`{c| ${value}}`, |
||||
|
`{b| ${name}}` |
||||
|
].join('\n'), |
||||
|
rich: { |
||||
|
c: { |
||||
|
fontSize: 24, |
||||
|
fontWeight: 600, |
||||
|
color: '#317FFF', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular', |
||||
|
lineHeight: 34 |
||||
}, |
}, |
||||
rich: { |
|
||||
c: { |
|
||||
fontSize: 24, |
|
||||
fontWeight: 600, |
|
||||
color: '#317FFF', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular', |
|
||||
// color: { |
|
||||
// type: 'linear', |
|
||||
// x: 0, |
|
||||
// y: 0, |
|
||||
// x2: 0, |
|
||||
// y2: 1, |
|
||||
// colorStops: [ |
|
||||
// { |
|
||||
// offset: 0, |
|
||||
// color: 'red' // 0% 处的颜色 |
|
||||
// }, |
|
||||
// { |
|
||||
// offset: 1, |
|
||||
// color: 'yellow' // 100% 处的颜色 |
|
||||
// } |
|
||||
// ], |
|
||||
// globalCoord: false // 缺省为 false |
|
||||
// }, |
|
||||
lineHeight: 34 |
|
||||
}, |
|
||||
b: { |
|
||||
fontSize: 18, |
|
||||
color: '#fff', |
|
||||
fontFamily: 'DingTalk_JinBuTi_Regular' |
|
||||
} |
|
||||
|
b: { |
||||
|
fontSize: 18, |
||||
|
color: '#fff', |
||||
|
fontFamily: 'DingTalk_JinBuTi_Regular' |
||||
} |
} |
||||
} |
} |
||||
}, |
|
||||
color: ['#317FFF', '#31DFFF'], |
|
||||
data: optionData |
|
||||
} |
|
||||
] |
|
||||
}, |
|
||||
media: [ |
|
||||
{ |
|
||||
query: { |
|
||||
maxWidth: 317 |
|
||||
|
} |
||||
}, |
}, |
||||
option: { |
|
||||
legend: { |
|
||||
right: 30, |
|
||||
top: 'center', |
|
||||
textStyle: { |
|
||||
rich: { |
|
||||
name: { |
|
||||
fontSize: 14 |
|
||||
}, |
|
||||
num: { |
|
||||
fontSize: 16 |
|
||||
} |
|
||||
} |
|
||||
|
color: ['#FFD14F', '#317FFF'], |
||||
|
data: chartData |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
const mediaOption = [ |
||||
|
{ |
||||
|
query: { maxWidth: 317 }, |
||||
|
option: { |
||||
|
legend: { |
||||
|
right: 30, |
||||
|
top: 'center', |
||||
|
textStyle: { |
||||
|
rich: { |
||||
|
name: { fontSize: 14 }, |
||||
|
num: { fontSize: 16 } |
||||
} |
} |
||||
}, |
|
||||
// 中心图片配置(关键代码) |
|
||||
graphic: [ |
|
||||
{ |
|
||||
left: '8%', // 调整图片位置 |
|
||||
top: '26%', // 调整图片位置 |
|
||||
z: -10, |
|
||||
bounding: 'raw', |
|
||||
rotation: 0, // 旋转 |
|
||||
origin: [64.5, 32.5], // 中心点 |
|
||||
scale: [1.0, 1.0], // 缩放 |
|
||||
// 设置图片样式 |
|
||||
style: { |
|
||||
image: centerImg, |
|
||||
width: 110, |
|
||||
height: 110, |
|
||||
opacity: 1 |
|
||||
} |
|
||||
|
} |
||||
|
}, |
||||
|
graphic: [ |
||||
|
{ |
||||
|
left: '8%', |
||||
|
top: '26%', |
||||
|
z: -10, |
||||
|
bounding: 'raw', |
||||
|
rotation: 0, |
||||
|
origin: [64.5, 32.5], |
||||
|
scale: [1.0, 1.0], |
||||
|
style: { |
||||
|
image: centerImg, |
||||
|
width: 110, |
||||
|
height: 110, |
||||
|
opacity: 1 |
||||
} |
} |
||||
], |
|
||||
series: [ |
|
||||
{ |
|
||||
type: 'pie', |
|
||||
radius: ['50%', '55%'], |
|
||||
emphasis: { |
|
||||
label: { |
|
||||
rich: { |
|
||||
c: { |
|
||||
fontSize: 22, |
|
||||
lineHeight: 28 |
|
||||
}, |
|
||||
b: { |
|
||||
fontSize: 16 |
|
||||
} |
|
||||
} |
|
||||
|
} |
||||
|
], |
||||
|
series: [ |
||||
|
{ |
||||
|
type: 'pie', |
||||
|
radius: ['50%', '55%'], |
||||
|
emphasis: { |
||||
|
label: { |
||||
|
rich: { |
||||
|
c: { fontSize: 22, lineHeight: 28 }, |
||||
|
b: { fontSize: 16 } |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
] |
|
||||
} |
|
||||
|
} |
||||
|
] |
||||
} |
} |
||||
] |
|
||||
} |
|
||||
myChart.setOption(option) |
|
||||
this.$LoopShowTooltip(myChart, option.baseOption, { loopSeries: true, interval: 4000 }) |
|
||||
|
} |
||||
|
] |
||||
|
|
||||
|
// 23. 合并配置并设置 |
||||
|
const option = { baseOption, media: mediaOption } |
||||
|
this.myChart.setOption(option) |
||||
|
|
||||
|
// 自动轮播tooltip |
||||
|
this.$LoopShowTooltip(this.myChart, baseOption, { loopSeries: true, interval: 4000 }) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 24. 窗口大小变化时防抖重绘图表 |
||||
|
*/ |
||||
|
handleResize() { |
||||
|
clearTimeout(this.resizeTimer) |
||||
|
this.resizeTimer = setTimeout(() => { |
||||
|
if (this.myChart) { |
||||
|
this.myChart.resize() |
||||
|
} |
||||
|
}, 100) |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
</script> |
</script> |
||||
|
|
||||
<style lang="scss"> |
|
||||
|
<style lang="scss" scoped> // 25. 添加scoped,避免样式污染全局 |
||||
@import "~@/assets/styles/index.scss"; |
@import "~@/assets/styles/index.scss"; |
||||
@import "~@/assets/styles/font-some.css"; |
@import "~@/assets/styles/font-some.css"; |
||||
|
|
||||
|
// 26. 样式抽离,便于维护 |
||||
|
.year-chart-container { |
||||
|
height: calc(100% - 200px); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-start; |
||||
|
|
||||
|
.model-type-chart { |
||||
|
width: 400px; |
||||
|
height: 230px; |
||||
|
} |
||||
|
} |
||||
</style> |
</style> |
||||
@ -0,0 +1,172 @@ |
|||||
|
<template> |
||||
|
<div class="page-wrapper" style="padding: 0 !important; z-index: 9999;"> |
||||
|
<el-carousel |
||||
|
ref="carousel" |
||||
|
indicator-position="none" |
||||
|
:autoplay="false" |
||||
|
:autoplay-hover-pause="true" |
||||
|
@setActiveItem="setActiveItem" |
||||
|
@change="carouselChange" |
||||
|
> |
||||
|
<div v-if="slideData && slideData.length > 0"> |
||||
|
<el-carousel-item v-for="(item, index) in slideData" :key="index"> |
||||
|
<video |
||||
|
ref="videos" |
||||
|
class="tsgz-video" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
controls |
||||
|
preload="auto" |
||||
|
:src="item.cover" |
||||
|
:poster="poster" |
||||
|
autoplay |
||||
|
type="video/mp4" |
||||
|
muted |
||||
|
@ended="playNextVideo(index)" |
||||
|
@loadedmetadata="playVideo" |
||||
|
> |
||||
|
您的浏览器不支持 video 标签。 |
||||
|
</video> |
||||
|
</el-carousel-item> |
||||
|
</div> |
||||
|
<el-empty |
||||
|
v-else |
||||
|
description="暂无视频" |
||||
|
style="height: 100vh" |
||||
|
:image-size="100" |
||||
|
image="" |
||||
|
/> |
||||
|
</el-carousel> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { FetchTotalResource } from '@/api/library' |
||||
|
export default { |
||||
|
name: 'PageFive', |
||||
|
components: { |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
poster: require('@/assets/images/poster.png'), |
||||
|
videoIndex: 0, |
||||
|
slideData: [] |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
this.destroy() |
||||
|
}, |
||||
|
created() { |
||||
|
}, |
||||
|
activated() { |
||||
|
this.getVideoResource() |
||||
|
this.load() |
||||
|
}, |
||||
|
deactivated() { |
||||
|
this.destroy() |
||||
|
}, |
||||
|
mounted() { |
||||
|
}, |
||||
|
methods: { |
||||
|
load() { |
||||
|
const videos = this.$refs.videos |
||||
|
if (videos) { |
||||
|
videos[this.videoIndex].load() |
||||
|
videos[this.videoIndex].currentTime = localStorage.getItem('videoCurrentTime') ? localStorage.getItem('videoCurrentTime') : 0 |
||||
|
} |
||||
|
}, |
||||
|
destroy() { |
||||
|
localStorage.setItem('videoIndex', this.videoIndex) |
||||
|
localStorage.setItem('videoCurrentTime', this.$refs.videos[this.videoIndex].currentTime) |
||||
|
this.$refs.videos[this.videoIndex].pause() |
||||
|
}, |
||||
|
// 视频资源 |
||||
|
getVideoResource() { |
||||
|
FetchTotalResource().then(res => { |
||||
|
const result = JSON.parse(res.data) |
||||
|
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API |
||||
|
this.slideData = result.map((item, index) => { |
||||
|
if (item.filePath) { |
||||
|
item.cover = linkSrc + '/downloadFile' + item.filePath |
||||
|
} else { |
||||
|
item.cover = null |
||||
|
} |
||||
|
return item |
||||
|
}) |
||||
|
// 下次进入页面时优先缓存的部分 |
||||
|
if (localStorage.getItem('videoIndex')) { |
||||
|
this.videoIndex = parseInt(localStorage.getItem('videoIndex')) |
||||
|
this.$nextTick(() => { |
||||
|
this.$refs.carousel.setActiveItem(this.videoIndex) |
||||
|
const videos = this.$refs.videos |
||||
|
const nextVideo = videos[this.videoIndex] |
||||
|
videos.forEach((video) => { |
||||
|
video.pause() |
||||
|
video.currentTime = 0 |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
nextVideo.currentTime = localStorage.getItem('videoCurrentTime') ? localStorage.getItem('videoCurrentTime') : 0 |
||||
|
nextVideo.play() |
||||
|
}, 2000) |
||||
|
}) |
||||
|
} |
||||
|
}).catch(error => { |
||||
|
console.error('Error', error) |
||||
|
}) |
||||
|
}, |
||||
|
playVideo() { |
||||
|
this.$refs.videos[this.videoIndex].play().catch(error => { |
||||
|
console.error(error) |
||||
|
}) |
||||
|
}, |
||||
|
setActiveItem(index) { |
||||
|
this.$refs.carousel.setActiveItem(index) |
||||
|
}, |
||||
|
carouselChange(index) { |
||||
|
const videos = this.$refs.videos |
||||
|
this.videoIndex = index |
||||
|
videos.forEach((video) => { |
||||
|
video.currentTime = 0 // 将视频回到起始时间 |
||||
|
video.pause() // 暂停视频播放 |
||||
|
}) |
||||
|
videos[index].play() |
||||
|
}, |
||||
|
playNextVideo(index) { |
||||
|
const videos = this.$refs.videos |
||||
|
let nextIndex = index |
||||
|
this.videoIndex = nextIndex |
||||
|
if (index < this.slideData.length - 1) { |
||||
|
nextIndex = nextIndex + 1 |
||||
|
} else { |
||||
|
nextIndex = 0 |
||||
|
} |
||||
|
const carousel = this.$refs.carousel |
||||
|
carousel.setActiveItem(nextIndex) |
||||
|
const nextVideo = videos[nextIndex] |
||||
|
videos.forEach((video) => { |
||||
|
video.pause() |
||||
|
video.currentTime = 0 |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
nextVideo.play() |
||||
|
}, 1000) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
@import "~@/assets/styles/index.scss"; |
||||
|
.el-carousel { |
||||
|
margin-top: 0 !important; |
||||
|
} |
||||
|
::v-deep .el-carousel__container{ |
||||
|
height: 100vh !important; |
||||
|
} |
||||
|
video { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue