大屏提交

This commit is contained in:
ljx
2025-09-09 22:07:43 +08:00
parent d01a56d9dc
commit 0afefe44ca
8 changed files with 751 additions and 421 deletions

View File

@ -0,0 +1,43 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 查询项目数据
* @param query
* @returns {*}
*/
export const projectProgress = (query?: any): any => {
return request({
url: '/enterprise/big/screen/projectProgress',
method: 'get',
params: query
});
};
/**
* 产值
* @param query
* @returns {*}
*/
export const outpuProgress = (query?: any): any => {
return request({
url: '/enterprise/big/screen/projectOutputValueComparison',
method: 'get',
params: query
});
};
/**
* 预警
* @param query
* @returns {*}
*/
export const earlyWarning = (query?: any): any => {
return request({
url: '/enterprise/big/screen/riskEarlyWarning',
method: 'get',
params: query
});
};

BIN
src/assets/large/actual.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/large/delay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/assets/large/plan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,275 +1,328 @@
<template> <template>
<div class="centerPage"> <div class="centerPage">
<div class="topPage"> <div class="centerPage_map">
<div id="earth" style="width: 100%;height: 100%;"></div> <div ref="mapRef" class="map-container" style="width: 100%; height: 100%" />
</div> </div>
<div class="endPage" :class="{ 'slide-out-down': isHide }"> <div class="centerPage_bottom">
<Title title="AI安全巡检"> <Title title="风险预警">
<img src="@/assets/projectLarge/robot.svg" alt="" height="20px" width="20px"> <img src="@/assets/projectLarge/robot.svg" alt="" height="20px" width="20px" />
</Title> </Title>
<div class="swiper" v-if="inspectionList.length"> <div class="centerPage_bottom_table">
<div class="arrow" :class="{ 'canUse': canLeft }" @click="swiperClick('left')"> <el-table v-loading="loading" :data="tableData" show-overflow-tooltip>
<el-icon size="16" :color="canLeft ? 'rgba(29, 214, 255, 1)' : 'rgba(29, 214, 255, 0.3)'"> <el-table-column label="时间" align="center" prop="date" />
<ArrowLeft /> <el-table-column label="类型" align="center" prop="riskType">
</el-icon> <template #default="scope">
</div> {{ safety_inspection_type[scope.row.riskType] }}
<div class="swiper_content" ref="swiperContent"> </template>
<div class="swiper_item" v-for="(item, i) in inspectionList" :key="i"> </el-table-column>
<img :src="item.picture" alt="安全巡检" class="swiper_img"> <el-table-column label="报警内容" align="center" prop="alarmContent" />
<div class="swiper_date">{{ item.createTime.slice(5) }}</div> <el-table-column label="危险等级" align="center" prop="dangerLevel" />
<div class="swiper_tip">{{ item.label }}</div> <el-table-column label="来源" align="center" prop="source" />
</div> <el-table-column label="警告等级" align="center" prop="alarmLevel">
</div> <template #default="scope">
<div class="arrow" :class="{ 'canUse': canRight }" @click="swiperClick('right')"> {{ risk_level_type[scope.row.alarmLevel] }}
<el-icon size="16" :color="canRight ? 'rgba(29, 214, 255, 1)' : 'rgba(29, 214, 255, 0.3)'"> </template>
<ArrowRight /> </el-table-column>
</el-icon> </el-table>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref, onMounted, toRefs, getCurrentInstance } from "vue" import * as echarts from 'echarts';
import Title from './title.vue' import china from '@/assets/china.json';
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue' import RevenueContractCard from './RevenueContractCard.vue';
import { getScreenSafetyInspection } from '@/api/projectScreen' import bottomboxconpoent from './bottomboxconpoent.vue';
import { totalAmount, projectGis } from '@/api/largeScreen/index'; // 导入projectGis接口
const { proxy } = getCurrentInstance(); import { ref, onMounted, onUnmounted, nextTick } from 'vue';
const { violation_level_type } = toRefs(proxy?.useDict('violation_level_type')); import Title from './title.vue';
import { earlyWarning } from '@/api/outputApi';
const props = defineProps({ import { useDict } from '@/utils/dict';
isHide: { import { getDicts } from '@/api/system/dict/data';
type: Boolean, // 地图相关变量
default: false const mapRef = ref<HTMLDivElement | null>(null);
}, let myChart: any = null;
projectId: { const projectData = ref<any[]>([]); // 存储项目地理信息数据
type: String, const loading = ref(false);
default: "" const tableData = ref([]);
const risk_level_type = ref();
const safety_inspection_type = ref();
// 新增:获取项目地理信息数据
const getProjectGisData = async () => {
try {
const response = await projectGis();
if (response.code === 200) {
// 过滤掉没有经纬度的项目
projectData.value = response.data.filter((item: any) => item.lng !== null && item.lat !== null && item.lng !== '' && item.lat !== '');
console.log('项目地理数据:', projectData.value);
} else {
console.error('项目地理数据请求失败:', response.msg);
}
} catch (error) {
console.error('项目地理数据调用异常:', error);
} }
}) };
//获取字典
const getDictsType = async () => {
const res = await getDicts('risk_level_type');
if (res.code === 200) {
risk_level_type.value = res.data.reduce((acc, item) => {
acc[item.dictValue] = item.dictLabel;
return acc;
}, {});
}
};
const getDictsType2 = async () => {
const res = await getDicts('safety_inspection_type');
if (res.code === 200) {
safety_inspection_type.value = res.data.reduce((acc, item) => {
acc[item.dictValue] = item.dictLabel;
return acc;
}, {});
}
};
//获取表格数据
const getTableList = async () => {
const res = await earlyWarning();
if (res.code === 200) {
tableData.value = res.data;
}
};
const inspectionList = ref([{ // 地图resize处理
id: "", const handleResize = () => {
label: "", if (myChart) myChart.resize();
picture: "", };
createTime: ""
}])
const swiperContent = ref()
const swiperItemWidth = ref(100)
const canLeft = ref(false)
const canRight = ref(true)
const swiperClick = (direction) => { // 初始化地图(修改为使用接口数据)
if (!swiperContent.value) return const initEcharts = () => {
if (!mapRef.value || projectData.value.length === 0) {
if (direction === 'right') { console.error('未找到地图容器或项目数据');
if (swiperContent.value.scrollLeft >= swiperContent.value.scrollWidth - swiperContent.value.clientWidth) { return;
canRight.value = false
canLeft.value = true
return
}
swiperContent.value.scrollLeft += swiperItemWidth.value
} else {
if (swiperContent.value.scrollLeft <= 0) {
canLeft.value = false
canRight.value = true
return
}
swiperContent.value.scrollLeft -= swiperItemWidth.value
} }
// 更新箭头状态 echarts.registerMap('china', china as any);
canLeft.value = swiperContent.value.scrollLeft > 0
canRight.value = swiperContent.value.scrollLeft < swiperContent.value.scrollWidth - swiperContent.value.clientWidth
}
const getInspectionList = async () => { // 从接口数据生成散点数据
const res = await getScreenSafetyInspection(props.projectId) const scatterData = projectData.value.map((item) => ({
const { code, data } = res name: item.projectName,
if (code === 200) { value: [parseFloat(item.lng), parseFloat(item.lat)], // 转换为数值类型
data.map(item => { shortName: item.shortName,
item.label = violation_level_type.value.find((i) => i.value === item.violationType)?.label projectSite: item.projectSite,
}) symbol: 'diamond',
inspectionList.value = data itemStyle: { color: '#0166d6' },
} symbolSize: [20, 20],
} label: {
// 创建地球 show: true,
const createEarth = () => { formatter: '{b}', // 显示项目名称
window.YJ.on({ position: 'top',
ws: true, color: '#fff',
// host: getIP(), //资源所在服务器地址 fontSize: 12,
// username: this.loginForm.username, //用户名 可以不登录(不填写用户名),不登录时无法加载服务端的数据 backgroundColor: 'rgba(3, 26, 52, 0.7)',
// password: md5pass, //密码 生成方式md5(用户名_密码) padding: [3, 6],
}).then((res) => { borderRadius: 3
let earth = new YJ.YJEarth("earth");
window.Earth1 = earth;
YJ.Global.openRightClick(window.Earth1);
YJ.Global.openLeftClick(window.Earth1);
let view = {
"position": {
"lng": 102.03643298211526,
"lat": 34.393586474501,
"alt": 11298179.51993155
},
"orientation": {
"heading": 360,
"pitch": -89.94481747201486,
"roll": 0
}
} }
loadBaseMap(earth.viewer) }));
YJ.Global.CesiumContainer(window.Earth1, {
compass: false, //罗盘 myChart = echarts.init(mapRef.value, null, {
}); renderer: 'canvas',
// YJ.Global.flyTo(earth, view); useDirtyRect: false
// YJ.Global.setDefaultView(earth.viewer, view)
})
}
// 加载底图
const loadBaseMap = (viewer) => {
// 创建瓦片提供器
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
// 可选:设置瓦片的格式
fileExtension: 'png',
// 可选:设置瓦片的范围和级别
minimumLevel: 0,
maximumLevel: 18,
// 可选设置瓦片的投影默认为Web Mercator
projection: Cesium.WebMercatorProjection,
// 可选:如果瓦片服务需要跨域请求,设置请求头部
credit: new Cesium.Credit('卫星图数据来源')
}); });
// 添加图层到视图 const option: any = {
const layer = viewer.imageryLayers.addImageryProvider(imageryProvider); roam: true, // 允许缩放和平移
} geo: {
onMounted(() => { type: 'map',
getInspectionList() map: 'china',
createEarth() zoom: 2, // 调整初始缩放级别
if (swiperContent.value && swiperContent.value.children.length > 0) { center: [108.95, 34.27], // 中国中心位置经纬度
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20 label: { show: false, color: '#fff' },
} itemStyle: {
}) areaColor: '#031a34',
borderColor: '#1e3a6e',
borderWidth: 1
}
},
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(3, 26, 52, 0.8)',
borderColor: '#1e3a6e',
textStyle: {
color: '#fff'
},
formatter: function (params: any) {
if (params.data) {
// 处理散点数据
const data = params.data;
return `
<div style="font-weight: bold;">${data.name}</div>
<div>地点:${data.projectSite}</div>
<div>经纬度:${data.value[0].toFixed(6)}, ${data.value[1].toFixed(6)}</div>
`;
}
if (params.seriesType === 'map') {
// console.log(params, 111111);
return `
<div style="font-weight: bold;">${params.name}</div>
`;
}
}
},
series: [
{
type: 'map',
map: 'china',
geoIndex: 0,
emphasis: {
areaColor: '#fff',
label: { show: true, color: '#fff' },
itemStyle: { areaColor: '#02417e' }
},
select: { itemStyle: { areaColor: '#02417e' } },
data: [] // 可以留空,或根据需要添加区域数据
},
{
type: 'scatter',
coordinateSystem: 'geo',
data: scatterData,
emphasis: {
scale: true, // 鼠标悬停时放大
symbolSize: [25, 25]
}
}
]
};
myChart.setOption(option);
};
const risk_level_type1 = ref([]);
const safety_inspection_type1 = ref([]);
// 组件生命周期
onMounted(() => {
nextTick(async () => {
// 先获取合同数据和项目地理数据,再初始化地图
getDictsType();
getDictsType2();
await Promise.all([getProjectGisData()]);
initEcharts();
getTableList();
window.addEventListener('resize', handleResize);
});
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
if (myChart) {
myChart.dispose();
myChart = null;
}
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.centerPage { .centerPage {
display: flex; width: 100%;
flex-direction: column;
height: 100%; height: 100%;
}
.topPage,
.endPage {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; // justify-content: space-between;
justify-content: center; padding: 0 10px 10px 10px;
width: 100%;
padding: 15px 0;
border: 1px solid rgba(230, 247, 255, 0.1);
box-sizing: border-box; box-sizing: border-box;
}
.topPage { .centerPage_map {
flex: 1;
margin-bottom: 23px;
transition: flex 0.5s ease;
}
.endPage {
max-height: 300px;
opacity: 1;
transition: all 0.5s ease;
}
/* 向下滑出动画 */
.slide-out-down {
transform: translateY(100%);
opacity: 0;
max-height: 0;
padding: 0;
margin: 0;
border: none;
}
.swiper {
width: 100%;
display: flex;
align-items: center;
gap: 20px;
padding: 20px 20px 10px 20px;
.swiper_content {
width: 100%; width: 100%;
display: flex; height: 65vh;
gap: 20px;
transition: all 0.3s ease-in-out;
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
} }
.centerPage_bottom {
.swiper_item { width: 100%;
position: relative; height: 25vh;
border: 1px solid rgba(29, 214, 255, 0.1);
padding: 10px 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; .centerPage_bottom_table {
width: 133px;
height: 84px;
.swiper_img {
width: 133px;
height: 84px;
object-fit: cover;
}
.swiper_date {
position: absolute;
top: 4px;
right: 4px;
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
}
.swiper_tip {
position: absolute;
bottom: 0;
width: 100%; width: 100%;
padding: 5px 0; height: 100%;
text-align: center; overflow: auto;
font-size: 12px; :deep(.el-table, .el-table__expanded-cell) {
font-weight: 400; background: rgba(0, 0, 0, 0);
color: rgba(230, 247, 255, 1); color: #fff;
background-color: rgba(0, 0, 0, 0.5); }
:deep(.el-table tr) {
background: rgba(0, 0, 0, 0);
// border: 1px solid rgba(0, 255, 255, 0.5) !important;
}
:deep(.el-table .el-table__header-wrapper th, .el-table .el-table__fixed-header-wrapper th) {
background: rgba(0, 0, 0, 0) !important;
}
:deep(.el-table th.el-table__cell) {
background: rgba(0, 0, 0, 0);
color: #fff;
border-bottom: 1px solid transparent !important;
border-right: 1px solid transparent !important;
}
:deep(.el-table--enable-row-transition .el-table__body td.el-table__cell) {
border-right: 1px solid transparent !important;
border-bottom: 1px solid transparent !important;
}
:deep(.el-table__body tr:hover > td) {
background-color: rgba(40, 75, 91, 0.9) !important;
}
/* 表格边框颜色 */
:deep(.el-table, .el-table__header-wrapper, .el-table__body-wrapper, .el-table__footer-wrapper, .el-table th, .el-table td) {
border: 1px solid transparent !important;
background: rgba(0, 0, 0, 0);
}
:deep(.el-table__inner-wrapper:before) {
background-color: transparent !important;
}
/* 固定列的边框 */
:deep(.el-table__fixed, .el-table__fixed-right) {
border: 1px solid transparent !important;
}
:deep(.el-table__body-wrapper::-webkit-scrollbar) {
width: 4px;
height: 10px;
}
:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
opacity: 0.2;
background: #00c0ff;
}
:deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 0;
background: rgba(220, 228, 245, 0.8);
}
:deep(.el-icon-arrow-right:before) {
color: #0ff;
font-weight: bold;
font-size: 16px;
}
} }
}
}
.arrow { // 滚动条优化
display: grid; .centerPage_bottom_table::-webkit-scrollbar {
place-items: center; width: 5px;
width: 24px; height: 5px;
height: 24px; }
border-radius: 50%;
border: 1px solid rgba(29, 214, 255, 0.3);
color: skyblue;
cursor: pointer;
transition: all 0.3s ease;
&.canUse { .centerPage_bottom_table::-webkit-scrollbar-thumb {
border: 1px solid rgba(29, 214, 255, 1); background-color: #0ff;
} border-radius: 5px;
}
&:hover:not(.canUse) { .centerPage_bottom_table::-webkit-scrollbar-track {
opacity: 0.7; background-color: rgba(0, 255, 255, 0.2);
}
} }
} }
</style> </style>

View File

@ -1,51 +1,54 @@
export let pieOption = { export let pieOption = {
// 定义中心文字 // 定义中心文字
graphic: [ graphic: [
{ // {
type: 'text', // type: 'text',
left: 'center', // left: 'center',
top: '40%', // top: '40%',
style: { // style: {
// 需要从接口替换 // // 需要从接口替换
text: '70%', // text: '70%',
fontSize: 24, // fontSize: 24,
fontWeight: 'bold', // fontWeight: 'bold',
fill: '#fff' // fill: '#fff'
} // }
}, // }
{ // {
type: 'text', // type: 'text',
left: 'center', // left: 'center',
top: '50%', // top: '50%',
style: { // style: {
text: '总进度', // text: '111',
fontSize: 14, // fontSize: 14,
fill: '#fff' // fill: '#fff'
} // }
}, // }
], ],
legend: { // legend: {
show: true, // show: true,
type: 'plain', // type: 'plain',
bottom: 20, // bottom: 20,
itemWidth: 12, // itemWidth: 12,
itemHeight: 12, // itemHeight: 12,
textStyle: { // textStyle: {
color: '#fff' // color: '#fff'
} // }
}, // },
series: { series: {
type: 'pie', type: 'pie',
data: [], data: [],
radius: [50, 80], radius: [50, 80],
center: ['50%', '45%'], center: ['50%', '45%'],
itemStyle: { // itemStyle: {
borderColor: '#fff', // borderColor: '#fff',
borderWidth: 1 // borderWidth: 1
}, // },
label: { label: {
alignTo: 'edge', alignTo: 'edge',
formatter: '{name|{b}}\n{percent|{c} %}', formatter: function (params) {
// 只显示前三个数据项
return `{name|${params.data.name}}\n{percent|${params.data.completionRate}MW}`;
},
minMargin: 10, minMargin: 10,
edgeDistance: 20, edgeDistance: 20,
lineHeight: 15, lineHeight: 15,
@ -62,7 +65,7 @@ export let pieOption = {
}, },
legend: { legend: {
top: 'bottom' top: 'bottom'
}, }
} }
}; };
@ -72,16 +75,39 @@ export let barOption = {
itemWidth: 12, itemWidth: 12,
itemHeight: 12, itemHeight: 12,
// 调整文字与图标间距 // 调整文字与图标间距
data: ['计划流转面积', '已流转面积'], data: ['计划产值', '实际产值'],
top: 0, top: 0,
right: 20, right: 10,
bottom: 10,
textStyle: { textStyle: {
color: '#fff', color: '#fff'
} }
}, },
tooltip: {
show: true,
backgroundColor: '',
trigger: 'axis',
// formatter: '{b0}{c0}万元',
formatter: (params: any) => {
// params 是数组,对应每条柱子
return params
.map((p: any) => `${p.seriesName}${Number(p.value).toFixed(2)} 亿元`)
.join('<br/>');
},
textStyle: {
color: '#fff'
},
axisPointer: {
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow',
}
// borderColor: 'rgba(252, 217, 18, 1)'
},
xAxis: { xAxis: {
type: 'category', type: 'category',
data: ['地块1', '地块2', '地块3', '地块4', '地块5', '地块6'], data: [],
axisLabel: { axisLabel: {
color: '#fff' color: '#fff'
}, },
@ -93,34 +119,71 @@ export let barOption = {
} }
}, },
yAxis: { yAxis: {
name: '单位:', name: '单位:亿元',
type: 'value', type: 'value',
axisLabel: { axisLabel: {
formatter: '{value}' formatter: '{value}'
},
splitLine: {
show: false // 不显示分割线
} }
}, },
grid: { grid: {
bottom: 0, // 距离容器底部的距离 left: '12%',
containLabel: true // 确保坐标轴标签不被裁剪 top: '15%', // 顶部留一点空间给 legend
bottom: '8%',
right: '2%'
}, },
series: [ series: [
{ {
name: '计划流转面积', name: '计划产值',
type: 'bar', type: 'bar',
data: [], data: [],
barWidth: '20%', barWidth: '10',
itemStyle: { itemStyle: {
color: 'rgb(29, 253, 253)' color: {
}, type: 'linear',
x: 0,
y: 1, // 修改y为1表示从底部开始
x2: 0,
y2: 0, // 修改y2为0表示渐变到顶部
colorStops: [
{
offset: 0,
color: 'rgba(255, 209, 92, 0.1)' // 底部透明度0.1
},
{
offset: 1,
color: 'rgba(255, 209, 92, 1)' // 顶部透明度1
}
]
}
}
}, },
{ {
name: '已流转面积', name: '实际产值',
type: 'bar', type: 'bar',
data: [], data: [],
barWidth: '20%', barWidth: '10',
itemStyle: { itemStyle: {
color: 'rgb(25, 181, 251)' color: {
}, type: 'linear',
x: 0,
y: 1, // 从底部开始
x2: 0,
y2: 0, // 到顶部结束
colorStops: [
{
offset: 0,
color: 'rgba(7, 209, 250, 0.1)' // 底部透明度0.1
},
{
offset: 1,
color: 'rgba(7, 209, 250, 1)' // 顶部透明度1
}
]
}
}
} }
] ]
}; };
@ -129,7 +192,7 @@ export let mapOption = {
geo: { geo: {
map: 'ch', map: 'ch',
roam: true, roam: true,
aspectScale: Math.cos((47 * Math.PI) / 180), aspectScale: Math.cos((47 * Math.PI) / 180)
}, },
series: [ series: [
{ {
@ -147,7 +210,7 @@ export let mapOption = {
{ name: 'i', value: [9.085994375000002, 47.55395822835779] }, { name: 'i', value: [9.085994375000002, 47.55395822835779] },
{ name: 'j', value: [8.653968125000002, 47.47709530818285] }, { name: 'j', value: [8.653968125000002, 47.47709530818285] },
{ name: 'k', value: [8.203158125000002, 47.44506909144329] } { name: 'k', value: [8.203158125000002, 47.44506909144329] }
], ]
} }
] ]
}; };

View File

@ -1,13 +1,60 @@
<template> <template>
<div class="leftPage"> <div class="leftPage">
<div class="topPage"> <div class="topPage">
<Title title="项目概况" /> <Title title="项目进度分析" />
<div class="content" v-html="generalize"></div> <div class="progress">
<div class="progress_item">
<div class="progress_imgBox">
<div class="progress_img">
<img src="@/assets/large/capacity.png" style="width: 100%; height: 100%" />
</div>
</div>
<div class="progress_text">
<div class="progress_text_title">
<div>{{ capacityData.gridConnectedCapacity ?? '0' }}</div>
<div>MW</div>
</div>
<div class="content_text">井网总容量</div>
</div>
</div>
<div class="progress_item">
<div class="progress_imgBox">
<div class="progress_img">
<img src="@/assets/large/plan.png" style="width: 100%; height: 100%" />
</div>
</div>
<div class="progress_text">
<div class="progress_text_title">
<div>{{ capacityData.plannedCapacity ?? '0' }}</div>
<div>MW</div>
</div>
<div class="content_text">计划总容量</div>
</div>
</div>
<div class="progress_item">
<div class="progress_imgBox">
<div class="progress_img">
<img src="@/assets/large/delay.png" style="width: 100%; height: 100%" />
</div>
</div>
<div class="progress_text">
<div class="progress_text_title">
<div>{{ capacityData.delayedProjectCount ?? '0' }}</div>
<div></div>
</div>
<div class="content_text">延期项目</div>
</div>
</div>
</div>
</div> </div>
<div class="endPage"> <div class="projectItem">
<Title title="形象进度" />
<div class="chart_container"> <div class="chart_container">
<div ref="pieChartRef" class="echart" /> <div ref="pieChartRef" class="echart" />
</div>
</div>
<div class="output">
<Title title="实际产值与预期产值对比" />
<div class="chart_container">
<div ref="lineChartRef" class="echart" /> <div ref="lineChartRef" class="echart" />
</div> </div>
</div> </div>
@ -15,69 +62,40 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from "vue" import { ref, onMounted, onUnmounted, nextTick } from 'vue';
import Title from './title.vue' import Title from './title.vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { pieOption, barOption } from './optionList'; import { pieOption, barOption } from './optionList';
import { getScreenLand, getScreenImgProcess, getScreenGeneralize } from '@/api/projectScreen'; import { projectProgress, outpuProgress } from '@/api/outputApi';
const capacityData: any = ref({});
const props = defineProps({ const props = defineProps({
projectId: { projectId: {
type: String, type: String,
default: 0 default: 0
} }
}) });
const generalize = ref() const generalize = ref();
// 饼图相关 // 饼图相关
const pieChartRef = ref<HTMLDivElement | null>(null); const pieChartRef = ref<HTMLDivElement | null>(null);
let pieChart: any = null; let pieChart: any = null;
const totalPercent = ref(0) const totalPercent = ref(0);
// 折线图相关 // 折线图相关
const lineChartRef = ref<HTMLDivElement | null>(null); const lineChartRef = ref<HTMLDivElement | null>(null);
let lineChart: any = null; let lineChart: any = null;
// 土地数据 折线图 // 土地数据 折线图
const designAreaData = ref([]) const designAreaData = ref([]);
const transferAreaData = ref([]) const transferAreaData = ref([]);
// 饼图数据 const barNames = ref([]);
const pieData = [ // // 饼图数据
{ label: 'areaPercentage', name: '厂区', value: 0 }, // const pieData = [
{ label: 'roadPercentage', name: '道路', value: 0 }, // { label: 'areaPercentage', name: '厂区', value: 0 },
{ label: 'collectorLinePercentage', name: '集电线路', value: 0 }, // { label: 'roadPercentage', name: '道路', value: 0 },
{ label: 'exportLinePercentage', name: '送出线路', value: 0 }, // { label: 'collectorLinePercentage', name: '集电线路', value: 0 },
{ label: 'substationPercentage', name: '升压站', value: 0 }, // { label: 'exportLinePercentage', name: '送出线路', value: 0 },
{ label: 'boxTransformerPercentage', name: '箱变', value: 0 }, // { label: 'substationPercentage', name: '升压站', value: 0 },
] // { label: 'boxTransformerPercentage', name: '箱变', value: 0 }
// ];
// 初始化饼图
const initPieChart = () => {
if (!pieChartRef.value) {
console.error('未找到饼图容器元素');
return;
}
pieOption.series.data = pieData
pieOption.graphic[0].style.text = totalPercent.value + '%'
pieChart = echarts.init(pieChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
});
pieChart.setOption(pieOption);
}
// 初始化折线图
const initLineChart = () => {
if (!lineChartRef.value) {
console.error('未找到折线图容器元素');
return;
}
barOption.series[0].data = designAreaData.value
barOption.series[1].data = transferAreaData.value
lineChart = echarts.init(lineChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
});
lineChart.setOption(barOption);
}
// 响应窗口大小变化 // 响应窗口大小变化
const handleResize = () => { const handleResize = () => {
@ -85,50 +103,126 @@ const handleResize = () => {
if (lineChart) lineChart.resize(); if (lineChart) lineChart.resize();
}; };
/** const processedDataList = ref([]);
* 获取项目土地统计数据 //获取数据
*/ const getData = async () => {
const getScreenLandData = async () => { const res = await projectProgress();
const res = await getScreenLand(props.projectId); if (res.code == 200) {
const { data, code } = res capacityData.value = res.data;
if (code === 200) { // processedDataList.value = res.data.projectProgressDetailList;
designAreaData.value = data.map((item: any) => Number(item.designArea)) let totalCapacity = 0;
transferAreaData.value = data.map((item: any) => Number(item.transferArea)) const processedData = res.data.projectProgressDetailList.map((item) => {
const capacity = parseInt(item.projectCapacity) || 0;
totalCapacity += capacity;
return {
name: item.projectName,
value: capacity,
completionRate: item.completionRate
};
});
// 计算每个项目的百分比
processedData.forEach((item) => {
item.percentage = totalCapacity > 0 ? ((item.value / totalCapacity) * 100).toFixed(2) : '0%';
});
processedDataList.value = processedData;
initPieChart();
}
};
// 初始化饼图
const initPieChart = () => {
if (!pieChartRef.value) {
console.error('未找到饼图容器元素');
return;
}
const data = processedDataList.value.map((item: any) => {
return {
name: item.name,
value: item.percentage,
completionRate: item.value
};
});
pieOption.series.data = data;
// pieOption.graphic[0].style.text = totalPercent.value + '%';
pieChart = echarts.init(pieChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
});
pieChart.setOption(pieOption);
};
//获取产值数据
const getOutputData = async () => {
const res = await outpuProgress();
if (res.code == 200) {
designAreaData.value = res.data.map((item: any) => Number(item.planValue));
transferAreaData.value = res.data.map((item: any) => Number(item.actualValue));
barNames.value = res.data.map((item: any) => item.projectName);
initLineChart(); initLineChart();
} }
} };
// 初始化柱状图图
/** const initLineChart = () => {
* 获取项目形象进度数据 if (!lineChartRef.value) {
*/ console.error('未找到柱状图容器元素');
const getScreenImgProcessData = async () => { return;
const res = await getScreenImgProcess(props.projectId);
const { data, code } = res
if (code === 200) {
totalPercent.value = data.totalPercentage
pieData.forEach((item: any) => {
item.value = data[item.label]
})
initPieChart()
} }
} console.log(barOption);
barOption.xAxis.data = barNames.value;
barOption.series[0].data = designAreaData.value;
barOption.series[1].data = transferAreaData.value;
lineChart = echarts.init(lineChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
});
lineChart.setOption(barOption);
};
// /**
// * 获取项目土地统计数据
// */
// const getScreenLandData = async () => {
// const res = await getScreenLand(props.projectId);
// const { data, code } = res;
// if (code === 200) {
// designAreaData.value = data.map((item: any) => Number(item.designArea));
// transferAreaData.value = data.map((item: any) => Number(item.transferArea));
// // initLineChart();
// }
// };
/** // /**
* 获取项目概况数据 // * 获取项目形象进度数据
*/ // */
const getScreenGeneralizeData = async () => { // const getScreenImgProcessData = async () => {
const res = await getScreenGeneralize(props.projectId); // const res = await getScreenImgProcess(props.projectId);
const { data, code } = res // const { data, code } = res;
if (code === 200) { // if (code === 200) {
generalize.value = data // totalPercent.value = data.totalPercentage;
} // pieData.forEach((item: any) => {
} // item.value = data[item.label];
// });
// initPieChart();
// }
// };
// /**
// * 获取项目概况数据
// */
// const getScreenGeneralizeData = async () => {
// const res = await getScreenGeneralize(props.projectId);
// const { data, code } = res;
// if (code === 200) {
// generalize.value = data;
// }
// };
// 组件挂载时初始化图表 // 组件挂载时初始化图表
onMounted(() => { onMounted(() => {
getScreenLandData() // getScreenLandData();
getScreenImgProcessData() // getScreenImgProcessData();
getScreenGeneralizeData() // getScreenGeneralizeData();
getData();
getOutputData();
nextTick(() => { nextTick(() => {
initPieChart(); initPieChart();
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
@ -155,67 +249,144 @@ onUnmounted(() => {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
.topPage, .topPage {
.endPage {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 100%; width: 100%;
padding: 15px 0; padding: 15px 0 0 0;
border: 1px solid rgba(29, 214, 255, 0.1); border: 1px solid rgba(29, 214, 255, 0.1);
box-sizing: border-box; box-sizing: border-box;
height: 20%;
} }
}
.endPage { // .content {
flex: 1; // height: 100px;
margin-top: 23px; // margin: 0 15px;
// padding: 0 10px;
// margin-top: 15px;
// box-sizing: border-box;
// overflow-y: auto;
.chart_container { // &::-webkit-scrollbar-track {
// background: rgba(204, 204, 204, 0.1);
// border-radius: 10px;
// }
// &::-webkit-scrollbar-thumb {
// background: rgba(29, 214, 255, 0.78);
// border-radius: 10px;
// }
// .content_item {
// font-size: 14px;
// font-weight: 400;
// color: rgba(230, 247, 255, 1);
// margin-bottom: 10px;
// &:last-child {
// margin-bottom: 0;
// }
// }
// }
.progress {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 10px;
.progress_item {
width: 100%;
height: 100%;
// background: rgba(29, 214, 255, 1);
display: flex;
flex-direction: column;
position: relative;
.progress_imgBox {
width: 100%;
height: 35%;
position: relative;
.progress_img {
width: 84px;
height: 48px;
position: absolute;
left: 50%;
top: 80%;
transform: translate(-50%, -50%);
}
}
.progress_text {
width: 100%;
height: 65%;
background: rgba(29, 214, 255, 0.1);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; align-items: flex-end;
width: 100%; padding: 15px 5px 5px 5px;
height: 100%; .progress_text_title {
} width: 100%;
// height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
& > div:first-child {
// 第一个子元素的样式
width: 50%;
font-size: 24px;
// font-weight: bold;
font-family: 'AlimamaShuHeiTi', sans-serif;
text-align: center;
}
& > div:last-child {
// 最后一个子元素的样式
width: 50%;
font-size: 12px;
color: #999;
text-align: center;
}
}
.content_text {
width: 100%;
font-size: 14px;
color: #c6d1db;
text-align: center;
}
}
}
}
.projectItem {
width: 100%;
height: 40%;
// background: rgba(29, 214, 255, 1);
padding: 10px 10px 10px 0;
// border: 1px solid rgba(29, 214, 255, 0.1);
.chart_container {
width: 100%;
height: 100%;
.echart { .echart {
height: 48%; height: 100%;
width: 100%; width: 100%;
} }
} }
} }
.output {
width: 100%;
height: 40%;
// background: rgba(29, 214, 255, 0.5);
padding: 10px 10px 10px 0;
border: 1px solid rgba(29, 214, 255, 0.1);
.content { .chart_container {
height: 100px; width: 100%;
margin: 0 15px; height: 100%;
padding: 0 10px; .echart {
margin-top: 15px; height: 90%;
box-sizing: border-box; width: 100%;
overflow-y: auto;
&::-webkit-scrollbar-track {
background: rgba(204, 204, 204, 0.1);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: rgba(29, 214, 255, 0.78);
border-radius: 10px;
}
.content_item {
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
} }
} }
} }
.subfont {
color: rgba(138, 149, 165, 1);
}
</style> </style>