添加屏幕扩展动画
This commit is contained in:
@ -5,7 +5,7 @@ VITE_APP_TITLE = 煤科建管平台
|
||||
VITE_APP_ENV = 'development'
|
||||
|
||||
# 开发环境
|
||||
VITE_APP_BASE_API = 'http://192.168.110.180:8899'
|
||||
VITE_APP_BASE_API = 'http://192.168.110.209:8899'
|
||||
|
||||
# 无人机接口地址
|
||||
|
||||
|
34
src/api/projectScreen/index.ts
Normal file
34
src/api/projectScreen/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
// 查询生项目天气
|
||||
export const getScreenWeather = (projectId: number | string) => {
|
||||
return request({
|
||||
url: '/project/big/screen/weather/' + projectId,
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
// 查询项目安全天数
|
||||
export const getScreenSafetyDay = (projectId: number | string) => {
|
||||
return request({
|
||||
url: '/project/big/screen/safetyDay/' + projectId,
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
// 查询项目公告
|
||||
export const getScreenNews = (projectId: number | string) => {
|
||||
return request({
|
||||
url: '/project/big/screen/news/' + projectId,
|
||||
method: 'get',
|
||||
});
|
||||
};
|
||||
|
||||
// 查询项目土地统计
|
||||
export const getScreenLand = (projectId: number | string) => {
|
||||
return request({
|
||||
url: '/project/big/screen/' + projectId,
|
||||
method: 'get',
|
||||
});
|
||||
};
|
5
src/api/projectScreen/types.ts
Normal file
5
src/api/projectScreen/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface TableQuery extends PageQuery {
|
||||
tableName: string;
|
||||
tableComment: string;
|
||||
dataName: string;
|
||||
}
|
BIN
src/assets/projectLarge/center.png
Normal file
BIN
src/assets/projectLarge/center.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 248 KiB |
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="centerPage">
|
||||
<div class="topPage">
|
||||
<!-- 暂无 -->
|
||||
<div class="topPage" :class="{ 'full-height': hideFooter }">
|
||||
<!-- <img src="@/assets/projectLarge/center.png" alt="" style="width: 100%;object-fit: scale-down"> -->
|
||||
</div>
|
||||
<div class="endPage">
|
||||
<div class="endPage" :class="{ 'slide-out-down': hideFooter }" v-if="!isHide">
|
||||
<Title title="AI安全巡检" :prefix="true" />
|
||||
|
||||
<div class="swiper">
|
||||
@ -13,10 +13,10 @@
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="swiper_content" ref="swiperContent">
|
||||
<div class="swiper_item" v-for="(item, index) in swiperList" :key="index">
|
||||
<img src="@/assets/projectLarge/swiper.png" alt="" class="swiper_img">
|
||||
<div class="swiper_date">{{ item.date }}</div>
|
||||
<div class="swiper_tip">{{ item.tip }}</div>
|
||||
<div class="swiper_item" v-for="(item, i) in 10" :key="i">
|
||||
<img src="@/assets/projectLarge/swiper.png" alt="安全巡检图片" class="swiper_img">
|
||||
<div class="swiper_date">2025-03-18</div>
|
||||
<div class="swiper_tip">未佩戴安全帽{{ i }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow" :class="{ 'canUse': canRight }" @click="swiperClick('right')">
|
||||
@ -30,30 +30,29 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { ref, onMounted } from "vue"
|
||||
import Title from './title.vue'
|
||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
||||
|
||||
const swiperList = ref([
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽1' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽2' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽3' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽4' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽5' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽6' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽7' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽8' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽9' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽10' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽11' },
|
||||
{ date: '03-18 15:00', tip: '未佩戴安全帽12' },
|
||||
])
|
||||
const props = defineProps({
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isHide: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const swiperList = ref([])
|
||||
const swiperContent = ref<HTMLDivElement>()
|
||||
const swiperItemWidth = ref(100)
|
||||
const canLeft = ref(false)
|
||||
const canRight = ref(true)
|
||||
|
||||
const swiperClick = (direction: 'left' | 'right') => {
|
||||
if (!swiperContent.value) return
|
||||
|
||||
if (direction === 'right') {
|
||||
if (swiperContent.value.scrollLeft >= swiperContent.value.scrollWidth - swiperContent.value.clientWidth) {
|
||||
@ -70,36 +69,65 @@ const swiperClick = (direction: 'left' | 'right') => {
|
||||
}
|
||||
swiperContent.value.scrollLeft -= swiperItemWidth.value
|
||||
}
|
||||
|
||||
// 更新箭头状态
|
||||
canLeft.value = swiperContent.value.scrollLeft > 0
|
||||
canRight.value = swiperContent.value.scrollLeft < swiperContent.value.scrollWidth - swiperContent.value.clientWidth
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
|
||||
if (swiperContent.value && swiperContent.value.children.length > 0) {
|
||||
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.centerPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 50vw;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.topPage,
|
||||
.endPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 15px 0;
|
||||
border: 1px solid rgba(29, 214, 255, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.topPage,
|
||||
.endPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 15px 0;
|
||||
border: 1px solid rgba(29, 214, 255, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.topPage {
|
||||
flex: 1;
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
.topPage {
|
||||
flex: 1;
|
||||
margin-bottom: 23px;
|
||||
transition: flex 1s ease;
|
||||
/* 添加flex过渡效果 */
|
||||
}
|
||||
|
||||
/* 当endPage隐藏时,topPage占满高度 */
|
||||
.full-height {
|
||||
flex: 1 1 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.endPage {
|
||||
max-height: 300px;
|
||||
/* 根据实际内容调整最大高度 */
|
||||
opacity: 1;
|
||||
transition: all 1s ease;
|
||||
}
|
||||
|
||||
/* 向下滑出动画 */
|
||||
.slide-out-down {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
@ -166,9 +194,16 @@ onMounted(() => {
|
||||
border-radius: 50%;
|
||||
border: 1px solid skyblue;
|
||||
color: skyblue;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:canUse {
|
||||
color: #000 !important;
|
||||
&.canUse {
|
||||
background-color: skyblue;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
&:hover:not(.canUse) {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -6,7 +6,7 @@
|
||||
</div>
|
||||
<div style="font-size: 12px; padding-left: 10px">安全生产天数:</div>
|
||||
<div class="header_left_text">
|
||||
1,235
|
||||
{{ safetyDay }}
|
||||
<span style="font-size: 12px">天</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -14,16 +14,18 @@
|
||||
<div>XXX智慧工地管理平台</div>
|
||||
<div>XXX Smart Construction Stic Management Dashboard</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="header_right">
|
||||
<div class="top-bar">
|
||||
<!-- 左侧:天气图标 + 日期文字 -->
|
||||
<div class="left-section">
|
||||
<img src="@/assets/large/weather.png" alt="天气图标" />
|
||||
|
||||
<span>
|
||||
<span>多云 9°/18°</span>
|
||||
<span style="padding-left: 20px"> {{ week[date.week] }} ({{ date.ymd }})</span>
|
||||
</span>
|
||||
<div class="weather-list" @mouseenter="requestPause" @mouseleave="resumeScroll">
|
||||
<div v-for="(item, i) in weatherList" :key="i" class="weather-item"
|
||||
:style="{ transform: `translateY(-${offsetY}px)`, transition: transition }">
|
||||
<img :src="`../../../src/assets/images/${item.icon}.png`" alt="" />
|
||||
<div>{{ item.weather }}{{ item.tempMin }}°/{{ item.tempMax }}°</div>
|
||||
<div>{{ item.week }}({{ item.date }})</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分割线 -->
|
||||
<div class="divider">
|
||||
@ -31,7 +33,7 @@
|
||||
<div class="bottom-block"></div>
|
||||
</div>
|
||||
<!-- 右侧:管理系统图标 + 文字 -->
|
||||
<div class="right-section">
|
||||
<div class="right-section" @click="emit('changePage')">
|
||||
<img src="@/assets/large/setting.png" alt="设置图标" />
|
||||
<span>管理系统</span>
|
||||
</div>
|
||||
@ -41,42 +43,131 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||||
const date = ref({
|
||||
ymd: '',
|
||||
hms: '',
|
||||
week: 0
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { getScreenSafetyDay, getScreenWeather } from '@/api/projectScreen';
|
||||
|
||||
interface Weather {
|
||||
week: string;
|
||||
date: string;
|
||||
icon: string;
|
||||
weather: string;
|
||||
tempMax: string;
|
||||
tempMin: string;
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
projectId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['changePage'])
|
||||
|
||||
const safetyDay = ref<number>(0);
|
||||
const weatherList = ref<Weather[]>([])
|
||||
const timer = ref<number | null>(0)
|
||||
const offsetY = ref<number>(0)
|
||||
const curIndex = ref(0)
|
||||
const transition = ref('transform 0.5s ease');
|
||||
const pendingPause = ref(false);
|
||||
|
||||
/**
|
||||
* 判断当前时间是白天/夜晚
|
||||
*/
|
||||
function judgeDayOrNight(sunRise: string, sunSet: string) {
|
||||
// 将 "HH:MM" 格式转为分钟数(便于计算)
|
||||
const timeToMinutes = (timeStr: any) => {
|
||||
const [hours, minutes] = timeStr.split(':').map(Number);
|
||||
return isNaN(hours) || isNaN(minutes) ? 0 : hours * 60 + minutes;
|
||||
};
|
||||
// 转换日出、日落时间为分钟数
|
||||
const sunRiseMinutes = timeToMinutes(sunRise);
|
||||
const sunSetMinutes = timeToMinutes(sunSet);
|
||||
// 获取当前时间并转为分钟数
|
||||
const now = new Date();
|
||||
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
||||
// true 白天 false 夜晚
|
||||
return currentMinutes >= sunRiseMinutes && currentMinutes <= sunSetMinutes
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置天气周期滑动
|
||||
*/
|
||||
const setWeatherScroll = () => {
|
||||
curIndex.value += 1
|
||||
transition.value = 'transform 0.3s ease';
|
||||
offsetY.value = curIndex.value * 60
|
||||
|
||||
if (curIndex.value === weatherList.value.length - 1) {
|
||||
setTimeout(() => {
|
||||
transition.value = 'none';
|
||||
curIndex.value = 0;
|
||||
offsetY.value = 0;
|
||||
}, 350);
|
||||
}
|
||||
}
|
||||
|
||||
function startScroll() {
|
||||
if (timer.value) clearInterval(timer.value);
|
||||
timer.value = window.setInterval(setWeatherScroll, 5000);
|
||||
}
|
||||
|
||||
function requestPause() {
|
||||
if(timer.value) {
|
||||
clearInterval(timer.value)
|
||||
timer.value = null
|
||||
}
|
||||
pendingPause.value = true;
|
||||
}
|
||||
|
||||
function resumeScroll() {
|
||||
console.log('resumeScroll')
|
||||
pendingPause.value = false;
|
||||
startScroll();
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
/**
|
||||
* 获取安全生产天数
|
||||
*/
|
||||
getScreenSafetyDay(props.projectId).then(res => {
|
||||
const { data, code } = res
|
||||
if (code === 200) {
|
||||
safetyDay.value = data.safetyDay;
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 获取近三天天气
|
||||
*/
|
||||
getScreenWeather(props.projectId).then(res => {
|
||||
const { data, code } = res
|
||||
if (code === 200) {
|
||||
data.forEach(item => {
|
||||
if (judgeDayOrNight(item.sunRise, item.sunSet)) {
|
||||
item.weather = item.dayStatus
|
||||
item.icon = item.dayIcon
|
||||
} else {
|
||||
item.weather = item.nightStatus
|
||||
item.icon = item.nightIcon
|
||||
}
|
||||
})
|
||||
weatherList.value = data
|
||||
// 多添加第一项 实现无缝衔接
|
||||
weatherList.value = [...weatherList.value, weatherList.value[0]]
|
||||
startScroll()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const setTime = () => {
|
||||
let date1 = new Date();
|
||||
let year: any = date1.getFullYear();
|
||||
let month: any = date1.getMonth() + 1;
|
||||
let day: any = date1.getDate();
|
||||
let hours: any = date1.getHours();
|
||||
if (hours < 10) {
|
||||
hours = '0' + hours;
|
||||
}
|
||||
let minutes: any = date1.getMinutes();
|
||||
if (minutes < 10) {
|
||||
minutes = '0' + minutes;
|
||||
}
|
||||
let seconds: any = date1.getSeconds();
|
||||
if (seconds < 10) {
|
||||
seconds = '0' + seconds;
|
||||
}
|
||||
date.value.ymd = year + '-' + month + '-' + day;
|
||||
date.value.hms = hours + ':' + minutes + ':' + seconds;
|
||||
date.value.week = date1.getDay();
|
||||
};
|
||||
|
||||
// 添加定时器,每秒更新一次时间
|
||||
const timer = setInterval(setTime, 1000);
|
||||
|
||||
// 组件卸载时清除定时器
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer);
|
||||
});
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -107,6 +198,12 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.header_right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-family: 'AlimamaShuHeiTi', sans-serif;
|
||||
@ -124,12 +221,6 @@ onUnmounted(() => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 顶部栏容器:Flex 水平布局 + 垂直居中 */
|
||||
.top-bar {
|
||||
width: 100%;
|
||||
@ -137,7 +228,6 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
// background-color: #1e2128;
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
@ -145,24 +235,37 @@ onUnmounted(() => {
|
||||
|
||||
/* 左侧区域(天气 + 日期):自身也用 Flex 水平排列,确保元素在一行 */
|
||||
.left-section {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// margin-right: auto; /* 让右侧元素(管理系统)居右 */
|
||||
}
|
||||
|
||||
.left-section img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 8px;
|
||||
/* 图标与文字间距 */
|
||||
.weather-list {
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
|
||||
.weather-item {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&>div:last-child {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 分割线(视觉分隔,可根据需求调整样式) */
|
||||
.divider {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
height: 100%;
|
||||
/* 根据需要调整高度 */
|
||||
gap: 2px;
|
||||
padding: 14px 10px;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,11 @@
|
||||
<Title title="项目公告" />
|
||||
|
||||
<div class="content">
|
||||
<div class="content_item" v-for="item in 6" :key="item">
|
||||
<div class="content_item" v-for="item in news" :key="item.id">
|
||||
<div class="round">
|
||||
<div class="sub_round"></div>
|
||||
</div>
|
||||
<div class="ellipsis">2025年6月23日 重庆市两江新区广场前期准备与审批完毕区广场前期准备与审批完毕前期准备与审批完毕区广场前期准备与审批完毕</div>
|
||||
<div class="ellipsis">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
<div class="map">
|
||||
<img src="@/assets/projectLarge/map.svg" alt="">
|
||||
<!-- <div ref="mapChartRef"></div> -->
|
||||
</div>
|
||||
|
||||
<div class="attendance_tag">
|
||||
@ -51,7 +52,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import Title from './title.vue'
|
||||
import { getScreenNews } from '@/api/projectScreen';
|
||||
import { mapOption } from './optionList'
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const props = defineProps({
|
||||
projectId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
let mapChart = null
|
||||
const mapChartRef = ref<HTMLDivElement | null>(null);
|
||||
const news = ref([])
|
||||
const list = ref([
|
||||
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
|
||||
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
|
||||
@ -60,20 +74,49 @@ const list = ref([
|
||||
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
|
||||
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
|
||||
])
|
||||
|
||||
const tagList = ref([
|
||||
{ title: '出勤人数', number: 259 },
|
||||
{ title: '在岗人数', number: 100 },
|
||||
{ title: '出勤率', number: 100 },
|
||||
])
|
||||
|
||||
/**
|
||||
* 初始化地图
|
||||
*/
|
||||
const initMapChart = () => {
|
||||
if (!mapChartRef.value) {
|
||||
return;
|
||||
}
|
||||
mapChart = echarts.init(mapChartRef.value);
|
||||
mapChart.setOption(mapOption);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// nextTick(() => {
|
||||
// initMapChart();
|
||||
// });
|
||||
getScreenNews(props.projectId).then(res => {
|
||||
const { data, code } = res
|
||||
if (code === 200) {
|
||||
news.value = data
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// if (mapChart) {
|
||||
// mapChart.dispose();
|
||||
// mapChart = null;
|
||||
// }
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.leftPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(25vw - 30px);
|
||||
margin: 0 15px;
|
||||
width: 25vw;
|
||||
height: 100%;
|
||||
|
||||
.topPage,
|
||||
|
@ -0,0 +1,146 @@
|
||||
export let pieOption = {
|
||||
// 定义中心文字
|
||||
graphic: [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: '40%',
|
||||
style: {
|
||||
text: '70%',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
fill: '#fff'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: '50%',
|
||||
style: {
|
||||
text: '总进度',
|
||||
fontSize: 14,
|
||||
fill: '#fff'
|
||||
}
|
||||
},
|
||||
],
|
||||
legend: {
|
||||
show: true,
|
||||
type: 'plain',
|
||||
bottom: 20,
|
||||
itemWidth: 12,
|
||||
itemHeight: 12,
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
series: {
|
||||
type: 'pie',
|
||||
data: [],
|
||||
radius: [50, 80],
|
||||
center: ['50%', '45%'],
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
label: {
|
||||
alignTo: 'edge',
|
||||
formatter: '{name|{b}}\n{percent|{c} %}',
|
||||
minMargin: 10,
|
||||
edgeDistance: 20,
|
||||
lineHeight: 15,
|
||||
rich: {
|
||||
name: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
},
|
||||
percent: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export let barOption = {
|
||||
legend: {
|
||||
icon: 'rect',
|
||||
itemWidth: 12,
|
||||
itemHeight: 12,
|
||||
// 调整文字与图标间距
|
||||
data: ['计划流转面积', '已流转面积'],
|
||||
top: 0,
|
||||
right: 20,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['地块1', '地块2', '地块3', '地块4', '地块5', '地块6'],
|
||||
axisLabel: {
|
||||
color: '#fff'
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
name: '单位:m²',
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
bottom: 0, // 距离容器底部的距离
|
||||
containLabel: true // 确保坐标轴标签不被裁剪
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '计划流转面积',
|
||||
type: 'bar',
|
||||
data: [],
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
color: 'rgb(29, 253, 253)'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '已流转面积',
|
||||
type: 'bar',
|
||||
data: [],
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
color: 'rgb(25, 181, 251)'
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export let mapOption = {
|
||||
geo: {
|
||||
map: 'ch',
|
||||
roam: true,
|
||||
aspectScale: Math.cos((47 * Math.PI) / 180),
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
coordinateSystem: 'geo',
|
||||
data: [
|
||||
{ name: 'a', value: [7.667821250000001, 46.791734269956265] },
|
||||
{ name: 'b', value: [7.404848750000001, 46.516308805996054] },
|
||||
{ name: 'c', value: [7.376673125000001, 46.24728858538375] },
|
||||
{ name: 'd', value: [8.015320625000001, 46.39460918238572] },
|
||||
{ name: 'e', value: [8.616400625, 46.7020608630855] },
|
||||
{ name: 'f', value: [8.869981250000002, 46.37539345234199] },
|
||||
{ name: 'g', value: [9.546196250000001, 46.58676648282309] },
|
||||
{ name: 'h', value: [9.311399375, 47.182454114178896] },
|
||||
{ name: 'i', value: [9.085994375000002, 47.55395822835779] },
|
||||
{ name: 'j', value: [8.653968125000002, 47.47709530818285] },
|
||||
{ name: 'k', value: [8.203158125000002, 47.44506909144329] }
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -8,15 +8,18 @@
|
||||
<div class="content_item">项目位置:贵州省贵阳市乌当区(具体地块编号:01-123-11)</div>
|
||||
<div class="content_item">占地面积:约10000亩</div>
|
||||
<div class="content_item"> 土地性质:城镇住宅用地(兼容商业用地,容积率≤2.5)</div>
|
||||
<div class="content_item"> 土地性质:城镇住宅用地(兼容商业用地,容积率≤2.5)</div>
|
||||
<div class="content_item"> 土地性质:城镇住宅用地(兼容商业用地,容积率≤2.5)</div>
|
||||
<div class="content_item"> 土地性质:城镇住宅用地(兼容商业用地,容积率≤2.5)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="endPage">
|
||||
<!-- 饼图容器 -->
|
||||
<Title title="形象进度" />
|
||||
|
||||
<div ref="pieChartRef" class="echart" />
|
||||
<!-- 折线图容器 -->
|
||||
<div ref="lineChartRef" class="echart" />
|
||||
<div class="chart_container">
|
||||
<div ref="pieChartRef" class="echart" />
|
||||
<div ref="lineChartRef" class="echart" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -25,15 +28,25 @@
|
||||
import { ref, onMounted, onUnmounted, nextTick } from "vue"
|
||||
import Title from './title.vue'
|
||||
import * as echarts from 'echarts';
|
||||
import { pieOption, barOption } from './optionList';
|
||||
import { getScreenLand } from '@/api/projectScreen';
|
||||
|
||||
const props = defineProps({
|
||||
projectId: {
|
||||
type: String,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
// 饼图相关
|
||||
const pieChartRef = ref<HTMLDivElement | null>(null);
|
||||
let pieChart: any = null;
|
||||
|
||||
// 折线图相关
|
||||
const lineChartRef = ref<HTMLDivElement | null>(null);
|
||||
let lineChart: any = null;
|
||||
|
||||
// 土地数据 折线图
|
||||
const designAreaData = ref([])
|
||||
const transferAreaData = ref([])
|
||||
// 饼图数据
|
||||
const pieData = [
|
||||
{ name: '桩点浇筑', value: 13 },
|
||||
@ -43,131 +56,13 @@ const pieData = [
|
||||
{ name: '组件安装', value: 20 },
|
||||
]
|
||||
|
||||
// 折线图数据
|
||||
const barData = {
|
||||
xAxis: ['地块1', '地块2', '地块3', '地块4', '地块5', '地块6'],
|
||||
series: [
|
||||
{
|
||||
name: '计划流转面积',
|
||||
data: [70, 25, 45, 115, 70, 85]
|
||||
},
|
||||
{
|
||||
name: '已流转面积',
|
||||
data: [105, 30, 150, 65, 80, 200]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 饼图配置
|
||||
const pieOption = {
|
||||
series: {
|
||||
type: 'pie',
|
||||
data: pieData,
|
||||
radius: [50, 80],
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
label: {
|
||||
alignTo: 'edge',
|
||||
formatter: '{name|{b}}\n{percent|{c} %}',
|
||||
minMargin: 10,
|
||||
edgeDistance: 20,
|
||||
lineHeight: 15,
|
||||
rich: {
|
||||
name: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
},
|
||||
percent: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// 柱状图配置
|
||||
const barOption = {
|
||||
legend: {
|
||||
data: ['计划流转面积', '已流转面积'],
|
||||
top: 0
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: barData.xAxis
|
||||
},
|
||||
yAxis: {
|
||||
name: '单位:m²',
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: [], // 空数据,仅用于承载markArea
|
||||
markArea: {
|
||||
silent: true, // 背景不响应交互
|
||||
data: (() => {
|
||||
const groupCount = 3; // 共3组(6个月 ÷ 2)
|
||||
const groupWidth = 1.8; // 每组背景宽度(覆盖2根柱子)
|
||||
const bgData = [];
|
||||
|
||||
for (let i = 0; i < groupCount; i++) {
|
||||
const startX = i * 2 - 0.9; // 每组起始位置
|
||||
const endX = startX + groupWidth; // 每组结束位置
|
||||
bgData.push([
|
||||
{ xAxis: startX, yAxis: 0 },
|
||||
{ xAxis: endX, yAxis: 100 }
|
||||
]);
|
||||
}
|
||||
return bgData;
|
||||
})(),
|
||||
itemStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.05)',
|
||||
borderRadius: 4
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '计划流转面积',
|
||||
type: 'bar',
|
||||
data: barData.series[0].data,
|
||||
barWidth: 15, // 柱形宽度
|
||||
itemStyle: {
|
||||
color: 'rgb(29, 253, 253)'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '已流转面积',
|
||||
type: 'bar',
|
||||
data: barData.series[1].data,
|
||||
barWidth: 15,
|
||||
itemStyle: {
|
||||
color: '#rgb(25, 181, 251)'
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 初始化饼图
|
||||
const initPieChart = () => {
|
||||
if (!pieChartRef.value) {
|
||||
console.error('未找到饼图容器元素');
|
||||
return;
|
||||
}
|
||||
pieOption.series.data = pieData
|
||||
pieChart = echarts.init(pieChartRef.value, null, {
|
||||
renderer: 'canvas',
|
||||
useDirtyRect: false
|
||||
@ -181,6 +76,8 @@ const initLineChart = () => {
|
||||
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
|
||||
@ -194,11 +91,24 @@ const handleResize = () => {
|
||||
if (lineChart) lineChart.resize();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取项目土地统计数据
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化图表
|
||||
onMounted(() => {
|
||||
getScreenLandData()
|
||||
nextTick(() => {
|
||||
initPieChart();
|
||||
initLineChart();
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
});
|
||||
@ -221,8 +131,7 @@ onUnmounted(() => {
|
||||
.leftPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(25vw - 30px);
|
||||
margin: 0 15px;
|
||||
width: 25vw;
|
||||
height: 100%;
|
||||
|
||||
.topPage,
|
||||
@ -240,15 +149,37 @@ onUnmounted(() => {
|
||||
flex: 1;
|
||||
margin-top: 23px;
|
||||
|
||||
.echart {
|
||||
.chart_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.echart {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 10px 35px;
|
||||
max-height: 100px;
|
||||
margin: 0 15px;
|
||||
padding: 0 10px;
|
||||
margin-top: 15px;
|
||||
box-sizing: border-box;
|
||||
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;
|
||||
@ -262,12 +193,6 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.ellipse {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.subfont {
|
||||
color: rgba(138, 149, 165, 1);
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="large-screen">
|
||||
<Header />
|
||||
<div class="large_screen">
|
||||
<Header :projectId="projectId" @changePage="handleChangePage" />
|
||||
<div class="nav">
|
||||
<leftPage />
|
||||
<centerPage />
|
||||
<rightPage />
|
||||
<div class="nav_left" v-if="!isHide" :class="{ 'slide-out-left': isHiding }">
|
||||
<leftPage :projectId="projectId" />
|
||||
</div>
|
||||
<div class="nav_center" :class="{ 'full-width': isHiding }">
|
||||
<centerPage :projectId="projectId" :hideFooter="isHiding" :isHide="isHide" />
|
||||
</div>
|
||||
<div class="nav_right" v-if="!isHide" :class="{ 'slide-out-right': isHiding }">
|
||||
<rightPage :projectId="projectId" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -14,33 +20,72 @@ import Header from './components/header.vue';
|
||||
import leftPage from './components/leftPage.vue';
|
||||
import centerPage from './components/centerPage.vue';
|
||||
import rightPage from './components/rightPage.vue';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStoreHook();
|
||||
const projectId = computed(() => userStore.selectedProject.id);
|
||||
const isHide = ref(false)
|
||||
const isHiding = ref(false)
|
||||
|
||||
const handleChangePage = () => {
|
||||
isHiding.value = true;
|
||||
|
||||
setTimeout(() => {
|
||||
isHide.value = !isHide.value;
|
||||
isHiding.value = false;
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.large-screen {
|
||||
.large_screen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: url('@/assets/large/bg.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-attachment: fixed;
|
||||
background-color: rgba(4, 7, 17, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 15rpx;
|
||||
width: 100%;
|
||||
height: calc(100vh - 100px);
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
width: calc(100vw - 30px);
|
||||
height: calc(100vh - 90px);
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav_left,
|
||||
.nav_right {
|
||||
margin: 0 15px 15px 15px;
|
||||
width: 25vw;
|
||||
transition: transform 1s ease, opacity 1s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav_center {
|
||||
margin-bottom: 15px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
transition: all 1s ease;
|
||||
}
|
||||
|
||||
/* 中间面板全屏动画 */
|
||||
.full-width {
|
||||
/* 取消flex增长,使用固定宽度 */
|
||||
width: calc(100vw - 30px);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.slide-out-left {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-out-right {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user