<template>
|
<div class="left-panel">
|
<div class="header-center">
|
<div class="title">西藏国投租金系统数据看板</div>
|
<div class="sub-title">Ecological simulation display in business district</div>
|
</div>
|
<div class="header-left" style="width: 350px;">
|
<div class="stat-group">
|
<div class="stat-item">
|
<img :src="require('@/assets/area.png')" width="40" height="40" />
|
<div class="stat-info">
|
<div class="stat-label"> <i class="icon-area"></i> 房屋总面积</div>
|
<div class="stat-value">{{ staticsData.houseTotalArea }}<span class="unit">m²</span></div>
|
</div>
|
</div>
|
<div class="stat-item">
|
<img :src="require('@/assets/rent.png')" width="40" height="40" />
|
<div class="stat-info">
|
<div class="stat-label"> <i class="icon-area"></i>已出租面积</div>
|
<div class="stat-value">{{ staticsData.houseRentedArea }}<span class="unit">m²</span></div>
|
</div>
|
</div>
|
</div>
|
<div class="header-right">
|
<div class="stat-group">
|
<div class="stat-item mt-10">
|
<img :src="require('@/assets/money.png')" width="40" height="40" />
|
<div class="stat-info">
|
<div class="stat-label"> <i class="icon-money"></i>总计应收租金</div>
|
<div class="stat-value">{{ staticsData.totalReceivableRent }}<span class="unit">万元</span></div>
|
</div>
|
</div>
|
<div class="stat-item mt-10">
|
<img :src="require('@/assets/successPay.png')" width="40" height="40" />
|
<div class="stat-info">
|
<div class="stat-label"><i class="icon-money"></i>总计已收租金</div>
|
<div class="stat-value">{{ staticsData.totalReceivedRent }}<span class="unit">万元</span></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="stat-group mt-10">
|
<div class="stat-item">
|
<img :src="require('@/assets/arrears.png')" width="40" height="40" />
|
<div class="stat-info">
|
<div class="stat-label"> <i class="icon-area"></i> 本季度欠费</div>
|
<div class="stat-value">{{ staticsData.totalRentOwe }}<span class="unit">万元</span></div>
|
</div>
|
</div>
|
<div class="stat-item">
|
<img :src="require('@/assets/totalArrears.png')" width="40" height="40" />
|
<div class="stat-info">
|
<div class="stat-label"> <i class="icon-area"></i>总计欠费</div>
|
<div class="stat-value">{{ staticsData.totalRentOweAll }}<span class="unit">万元</span></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="chart-card" style="width: 350px;">
|
<div class="chart-title">区域租金排名</div>
|
<div class="chart-unit">单位(万元)</div>
|
<div class="rent-rank-list" ref="scrollContainer" @mouseenter="stopScroll" @mouseleave="startScroll">
|
<div class="rank-list-content" ref="scrollContent">
|
<div class="rank-list-group">
|
<div v-for="(item, index) in displayRank" :key="index" class="rent-rank-item">
|
<div class="rank-name">{{ item.streetName }}</div>
|
<div class="rank-progress">
|
<div class="rank-bar">
|
<div class="rank-bar-inner" :style="{
|
width: (item.rentAmount / Math.max(...rentRank.map(i => i.rentAmount)) * 100) + '%',
|
background: getBarColor(item.rentAmount)
|
}"></div>
|
</div>
|
<div class="rank-value">{{ item.rentAmount }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
name: 'LeftPanel',
|
props: {
|
staticsData: {
|
type: Object,
|
default: () => { }
|
},
|
rentRank: {
|
type: Array,
|
default: () => []
|
}
|
},
|
data() {
|
return {
|
scrollTimer: null,
|
isScrolling: true,
|
currentTranslate: 0,
|
firstItemHeight: 0,
|
animationFrameId: null,
|
lastTimestamp: 0,
|
displayRank: []
|
}
|
},
|
watch: {
|
rentRank: {
|
immediate: true,
|
handler(newVal) {
|
if (newVal && newVal.length) {
|
// 复制两组数据用于无缝滚动
|
this.displayRank = [...newVal, ...newVal];
|
}
|
}
|
}
|
},
|
mounted() {
|
this.$nextTick(() => {
|
const firstItem = this.$el.querySelector('.rent-rank-item');
|
if (firstItem) {
|
this.firstItemHeight = firstItem.offsetHeight + 8;
|
}
|
this.startScroll();
|
});
|
},
|
beforeDestroy() {
|
this.clearScrollTimer();
|
if (this.animationFrameId) {
|
cancelAnimationFrame(this.animationFrameId);
|
}
|
},
|
methods: {
|
startScroll() {
|
this.isScrolling = true;
|
if (!this.animationFrameId) {
|
this.lastTimestamp = performance.now();
|
this.startAutoScroll();
|
}
|
},
|
stopScroll() {
|
this.isScrolling = false;
|
},
|
clearScrollTimer() {
|
if (this.animationFrameId) {
|
cancelAnimationFrame(this.animationFrameId);
|
this.animationFrameId = null;
|
}
|
},
|
startAutoScroll() {
|
const animate = (timestamp) => {
|
if (!this.isScrolling) {
|
this.lastTimestamp = timestamp;
|
this.animationFrameId = requestAnimationFrame(animate);
|
return;
|
}
|
|
const deltaTime = timestamp - this.lastTimestamp;
|
const speed = 0.03;
|
const step = speed * deltaTime;
|
|
const content = this.$refs.scrollContent;
|
if (!content || !this.firstItemHeight) {
|
this.animationFrameId = requestAnimationFrame(animate);
|
return;
|
}
|
|
this.currentTranslate -= step;
|
|
// 当滚动到第一组数据的末尾时,重置位置到第二组数据的开始
|
const halfHeight = this.firstItemHeight * this.rentRank.length;
|
if (Math.abs(this.currentTranslate) >= halfHeight) {
|
this.currentTranslate = 0;
|
}
|
|
content.style.transform = `translateY(${this.currentTranslate}px)`;
|
this.lastTimestamp = timestamp;
|
this.animationFrameId = requestAnimationFrame(animate);
|
};
|
|
this.animationFrameId = requestAnimationFrame(animate);
|
},
|
getBarColor(value) {
|
if (value == 0) return 'transparent';
|
return '#87F7C7';
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.left-panel {
|
width: 400px;
|
display: flex;
|
position: fixed;
|
top: 0;
|
left: 0;
|
height: 100%;
|
background: linear-gradient(to right, rgba(1, 85, 121, 0.8) 0%, rgba(1, 85, 121, 0.8) 20%, rgba(151, 190, 192, 0));
|
z-index: 1002;
|
flex-direction: column;
|
padding-bottom: 200px;
|
gap: 10px;
|
}
|
|
.header-center {
|
margin-left: 20px;
|
margin-top: 20px;
|
padding-bottom: 20px;
|
border-bottom: 1px solid #8CB4C6;
|
}
|
|
.title {
|
font-size: 30px;
|
font-weight: bold;
|
background: linear-gradient(90deg, #fff, #0ff);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
}
|
|
.sub-title {
|
font-size: 12px;
|
color: fff;
|
}
|
|
.stat-group {
|
display: flex;
|
gap: 10px;
|
}
|
|
.stat-item {
|
flex: 1;
|
background: rgba(255, 255, 255, 0.05);
|
border-radius: 8px;
|
padding: 15px;
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
min-width: 0;
|
}
|
|
.icon-area,
|
.icon-money {
|
width: 40px;
|
height: 40px;
|
background: rgba(255, 255, 255, 0.1);
|
border-radius: 50%;
|
}
|
|
.stat-info {
|
flex: 1;
|
}
|
|
.mt-10 {
|
margin-top: 10px;
|
}
|
|
.stat-label {
|
font-size: 14px;
|
color: #fff;
|
margin-bottom: 5px;
|
}
|
|
.stat-value {
|
font-size: 24px;
|
font-weight: bold;
|
color: #0ff;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
width: 100%;
|
}
|
|
.unit {
|
font-size: 14px;
|
margin-left: 4px;
|
color: #fff;
|
}
|
|
.chart-card {
|
flex: 1;
|
background: rgba(255, 255, 255, 0.05);
|
border-radius: 8px;
|
padding: 15px;
|
min-height: 300px;
|
/* max-height: calc(100vh - 600px); */
|
overflow: hidden;
|
margin-bottom: 20px;
|
}
|
|
.chart-title {
|
font-size: 16px;
|
color: #fff;
|
}
|
|
.chart-unit {
|
font-size: 12px;
|
margin-bottom: 15px;
|
}
|
|
.rent-rank-list {
|
height: calc(100% - 60px);
|
overflow: hidden;
|
padding: 10px 0;
|
position: relative;
|
max-height: calc(100% - 60px);
|
}
|
|
.rank-list-content {
|
position: relative;
|
transition: none;
|
transform: translateY(0);
|
will-change: transform;
|
}
|
|
.rank-list-group {
|
position: relative;
|
}
|
|
.rent-rank-item {
|
display: flex;
|
flex-direction: column;
|
padding: 8px 0;
|
}
|
|
.rank-name {
|
color: #fff;
|
font-size: 14px;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.rank-progress {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.rank-bar {
|
flex: 1;
|
height: 8px;
|
background: rgba(255, 255, 255, 0.1);
|
overflow: hidden;
|
}
|
|
.rank-bar-inner {
|
height: 100%;
|
transition: all 0.3s ease;
|
}
|
|
.rank-value {
|
color: #fff;
|
font-size: 14px;
|
min-width: 45px;
|
text-align: right;
|
}
|
</style>
|