| | |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'App', |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | }, |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | #app { |
| | | * { |
| | | margin: 0; |
| | | padding: 0; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | html, |
| | | body { |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | background: #000033; |
| | | } |
| | | |
| | | #app { |
| | | -webkit-font-smoothing: antialiased; |
| | | -moz-osx-font-smoothing: grayscale; |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="bottom-charts"> |
| | | <div class="chart-group"> |
| | | <div class="chart-title">收入趋势</div> |
| | | <div class="income-chart" ref="incomeChart"></div> |
| | | </div> |
| | | <div class="chart-group"> |
| | | <div class="chart-title">租金收入趋势图</div> |
| | | <div class="trend-chart" ref="rentTrendChart"></div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from 'echarts' |
| | | |
| | | export default { |
| | | name: 'BottomCharts', |
| | | data() { |
| | | return { |
| | | charts: { |
| | | income: null, |
| | | rentTrend: null |
| | | } |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.$nextTick(() => { |
| | | this.initCharts() |
| | | }) |
| | | }, |
| | | methods: { |
| | | initCharts() { |
| | | // 收入趋势图表 |
| | | this.charts.income = echarts.init(this.$refs.incomeChart) |
| | | this.charts.income.setOption({ |
| | | grid: { |
| | | top: '15%', |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['23-4月', '23-12月', '24-3月', '24-4月', '24-5月', '24-6月', '25-3月'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | data: [100, 400, 200, 300, 200, 400, 300], |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { |
| | | color: '#ffd700', |
| | | width: 2 |
| | | }, |
| | | itemStyle: { |
| | | color: '#ffd700' |
| | | }, |
| | | areaStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: 'rgba(255, 215, 0, 0.3)' |
| | | }, { |
| | | offset: 1, |
| | | color: 'rgba(255, 215, 0, 0.1)' |
| | | }] |
| | | } |
| | | } |
| | | }] |
| | | }) |
| | | |
| | | // 租金趋势图表 |
| | | this.charts.rentTrend = echarts.init(this.$refs.rentTrendChart) |
| | | this.charts.rentTrend.setOption({ |
| | | grid: { |
| | | top: '15%', |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['23-4月', '23-12月', '24-3月', '24-4月', '24-5月', '24-6月', '25-3月'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | data: [300, 200, 400, 300, 500, 400, 300], |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { |
| | | color: '#4facfe', |
| | | width: 2 |
| | | }, |
| | | itemStyle: { |
| | | color: '#4facfe' |
| | | }, |
| | | areaStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: 'rgba(79,172,254,0.3)' |
| | | }, { |
| | | offset: 1, |
| | | color: 'rgba(79,172,254,0.1)' |
| | | }] |
| | | } |
| | | } |
| | | }] |
| | | }) |
| | | |
| | | window.addEventListener('resize', () => { |
| | | Object.values(this.charts).forEach(chart => { |
| | | chart && chart.resize() |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .bottom-charts { |
| | | height: 200px; |
| | | width: 700px; |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | z-index: 1003; |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .chart-group { |
| | | flex: 1; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border-radius: 8px; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .chart-title { |
| | | font-size: 16px; |
| | | margin-bottom: 15px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .income-chart, |
| | | .trend-chart { |
| | | height: calc(100% - 30px); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="header"> |
| | | <div class="time-weather"> |
| | | <span class="time">{{ currentTime }}</span> |
| | | <span class="date">{{ currentDate }}</span> |
| | | <span class="weather"> |
| | | <i class="weather-icon"></i> |
| | | {{ temperature }}°C |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import dayjs from 'dayjs' |
| | | |
| | | export default { |
| | | name: 'Header', |
| | | data() { |
| | | return { |
| | | currentTime: '', |
| | | currentDate: '', |
| | | temperature: '--' |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.startTimeUpdate() |
| | | this.startWeatherUpdate() |
| | | }, |
| | | methods: { |
| | | startTimeUpdate() { |
| | | const updateTime = () => { |
| | | this.currentTime = dayjs().format('HH:mm:ss') |
| | | this.currentDate = dayjs().format('YYYY/MM/DD') |
| | | } |
| | | updateTime() |
| | | setInterval(updateTime, 1000) |
| | | }, |
| | | startWeatherUpdate() { |
| | | const updateWeather = async () => { |
| | | try { |
| | | const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=29.6487&longitude=91.1172¤t=temperature_2m&timezone=Asia%2FShanghai') |
| | | const data = await response.json() |
| | | if (data.current) { |
| | | this.temperature = Math.round(data.current.temperature_2m) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取天气数据失败:', error) |
| | | } |
| | | } |
| | | |
| | | updateWeather() |
| | | setInterval(updateWeather, 30 * 60 * 1000) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .header { |
| | | height: 120px; |
| | | display: flex; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100vw; |
| | | background: linear-gradient(to bottom, rgba(1, 85, 121, 0.8) 0%, rgba(1, 85, 121, 0.4) 40%, rgba(151, 190, 192, 0) 100%); |
| | | z-index: 1000; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | padding: 10px 20px; |
| | | backdrop-filter: blur(10px); |
| | | } |
| | | |
| | | .time-weather { |
| | | display: flex; |
| | | gap: 20px; |
| | | align-items: center; |
| | | } |
| | | |
| | | .time { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .date { |
| | | color: #fff; |
| | | } |
| | | |
| | | .weather { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <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"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"> <i class="icon-area"></i> 房屋总面积</div> |
| | | <div class="stat-value">{{ totalArea }}<span class="unit">m²</span></div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"> <i class="icon-area"></i>已出租面积</div> |
| | | <div class="stat-value">{{ rentedArea }}<span class="unit">m²</span></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="header-right"> |
| | | <div class="stat-group"> |
| | | <div class="stat-item mt-10"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"> <i class="icon-money"></i>今日已收租金</div> |
| | | <div class="stat-value">{{ todayRent }}<span class="unit">万元</span></div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-item mt-10"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"><i class="icon-money"></i>今日已收租金</div> |
| | | <div class="stat-value">{{ todayIncome }}<span class="unit">万元</span></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="chart-card" style="width: 350px;"> |
| | | <div class="chart-title">区域租金分析</div> |
| | | <div class="area-chart" ref="areaRentChart"></div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import * as echarts from 'echarts' |
| | | |
| | | export default { |
| | | name: 'LeftPanel', |
| | | data() { |
| | | return { |
| | | totalArea: '386.5', |
| | | rentedArea: '316.5', |
| | | todayRent: '316.5', |
| | | todayIncome: '124.5', |
| | | charts: { |
| | | areaRent: null |
| | | } |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.$nextTick(() => { |
| | | this.initCharts() |
| | | }) |
| | | }, |
| | | methods: { |
| | | initCharts() { |
| | | this.charts.areaRent = echarts.init(this.$refs.areaRentChart) |
| | | this.charts.areaRent.setOption({ |
| | | grid: { |
| | | left: '20%', |
| | | right: '5%', |
| | | top: '10%', |
| | | bottom: '10%' |
| | | }, |
| | | xAxis: { |
| | | type: 'value', |
| | | axisLine: { |
| | | show: false |
| | | }, |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'category', |
| | | data: ['新城大道', '拉萨路', '新城大道', '拉萨路', '新城大道', '新城大道', '拉萨路', '新城大道', '拉萨路', '新城大道'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | type: 'bar', |
| | | data: [1900, 1850, 1800, 1750, 1700, 1600, 1500, 1400, 1300, 1200], |
| | | barWidth: '30%', |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 1, |
| | | y2: 0, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: '#0ff' |
| | | }, { |
| | | offset: 1, |
| | | color: '#0ff' |
| | | }] |
| | | } |
| | | } |
| | | }] |
| | | }) |
| | | |
| | | window.addEventListener('resize', () => { |
| | | this.charts.areaRent && this.charts.areaRent.resize() |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .left-panel { |
| | | width: 400px; |
| | | display: flex; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | height: calc(100vh); |
| | | 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; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .chart-title { |
| | | font-size: 16px; |
| | | margin-bottom: 15px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .area-chart { |
| | | height: calc(100% - 30px); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="center-panel"> |
| | | <div class="map-container" ref="mapContainer"></div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import AMapLoader from '@amap/amap-jsapi-loader' |
| | | |
| | | export default { |
| | | name: 'MapPanel', |
| | | data() { |
| | | return { |
| | | map: null, |
| | | markers: [], |
| | | infoWindow: null |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.$nextTick(() => { |
| | | this.initMap() |
| | | }) |
| | | }, |
| | | methods: { |
| | | async initMap() { |
| | | try { |
| | | const AMap = await AMapLoader.load({ |
| | | key: '526e04b30ceba8f217c5def5a92392f9', |
| | | version: '2.0', |
| | | plugins: ['AMap.ToolBar', 'AMap.Scale'] |
| | | }) |
| | | |
| | | this.map = new AMap.Map(this.$refs.mapContainer, { |
| | | zoom: 16, |
| | | center: [91.1172, 29.6487], |
| | | viewMode: '3D', |
| | | pitch: 35, |
| | | mapStyle: 'amap://styles/normal', |
| | | features: ['bg', 'road', 'building', 'point'], |
| | | buildingAnimation: true |
| | | }) |
| | | |
| | | this.map.addControl(new AMap.ToolBar({ |
| | | position: 'RB' |
| | | })) |
| | | this.map.addControl(new AMap.Scale({ |
| | | position: 'RB' |
| | | })) |
| | | |
| | | this.infoWindow = new AMap.InfoWindow({ |
| | | offset: new AMap.Pixel(0, -30), |
| | | closeWhenClickMap: true, |
| | | autoMove: true, |
| | | anchor: 'bottom-center' |
| | | }) |
| | | |
| | | this.map.on('click', () => { |
| | | if (this.infoWindow) { |
| | | this.infoWindow.close() |
| | | } |
| | | }) |
| | | |
| | | this.addMarkers() |
| | | } catch (error) { |
| | | console.error('地图加载失败:', error) |
| | | } |
| | | }, |
| | | addMarkers() { |
| | | const houses = [ |
| | | { |
| | | position: [91.1172, 29.6487], |
| | | title: '新城大道房源', |
| | | status: 'waiting', |
| | | content: ` |
| | | <div class="info-window" data-status="waiting"> |
| | | <div class="info-title">房屋状态:待租出</div> |
| | | <div class="info-content"> |
| | | <p>房屋编号:新城大道</p> |
| | | <p>房屋状态:待租出</p> |
| | | <p>租赁面积:1000/2000㎡</p> |
| | | <p>本季租金:500/2000元/月</p> |
| | | </div> |
| | | </div> |
| | | ` |
| | | }, |
| | | { |
| | | position: [91.1272, 29.6587], |
| | | title: '拉萨路房源', |
| | | status: 'rented', |
| | | content: ` |
| | | <div class="info-window" data-status="rented"> |
| | | <div class="info-title">房屋状态:已租出</div> |
| | | <div class="info-content"> |
| | | <p>房屋编号:拉萨路</p> |
| | | <p>房屋状态:已租出</p> |
| | | <p>租赁面积:800/1500㎡</p> |
| | | <p>本季租金:400/1800元/月</p> |
| | | </div> |
| | | </div> |
| | | ` |
| | | } |
| | | ] |
| | | |
| | | houses.forEach(house => { |
| | | const circles = [] |
| | | const baseRadius = 15 |
| | | const numCircles = 3 |
| | | |
| | | const centerCircle = new AMap.Circle({ |
| | | center: house.position, |
| | | radius: baseRadius / 2, |
| | | fillColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50', |
| | | fillOpacity: 0.8, |
| | | strokeWeight: 0, |
| | | zIndex: numCircles + 2, |
| | | bubble: true, |
| | | cursor: 'pointer' |
| | | }) |
| | | |
| | | for (let i = 0; i < numCircles; i++) { |
| | | circles.push(new AMap.Circle({ |
| | | center: house.position, |
| | | radius: baseRadius * (i + 1), |
| | | fillColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50', |
| | | fillOpacity: 0.3 - (i * 0.05), |
| | | strokeColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50', |
| | | strokeWeight: 2, |
| | | strokeOpacity: 0.5 - (i * 0.08), |
| | | zIndex: numCircles - i, |
| | | bubble: true, |
| | | cursor: 'pointer' |
| | | })) |
| | | } |
| | | |
| | | const clickHandler = () => { |
| | | if (this.infoWindow) { |
| | | this.infoWindow.setContent(house.content) |
| | | this.infoWindow.open(this.map, house.position) |
| | | } |
| | | } |
| | | |
| | | centerCircle.on('click', clickHandler) |
| | | circles.forEach(circle => circle.on('click', clickHandler)) |
| | | |
| | | this.map.add([centerCircle, ...circles]) |
| | | this.markers.push({ center: centerCircle, rings: circles }) |
| | | |
| | | let phase = 0 |
| | | const animate = () => { |
| | | phase += 0.15 |
| | | const scale = 1 + 0.6 * Math.sin(phase) |
| | | |
| | | circles.forEach((circle, index) => { |
| | | const currentRadius = baseRadius * (index + 1) |
| | | circle.setRadius(currentRadius * scale) |
| | | |
| | | const baseOpacity = 0.3 - (index * 0.05) |
| | | const opacityScale = 1 + 0.5 * Math.sin(phase) |
| | | circle.setOptions({ |
| | | fillOpacity: baseOpacity * opacityScale, |
| | | strokeOpacity: (0.5 - (index * 0.08)) * opacityScale |
| | | }) |
| | | }) |
| | | |
| | | requestAnimationFrame(animate) |
| | | } |
| | | animate() |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .center-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .map-container { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | width: 100%; |
| | | height: 100vh; |
| | | } |
| | | |
| | | .info-window { |
| | | padding: 10px; |
| | | background: rgba(146, 146, 146, 0.5); |
| | | border-radius: 8px; |
| | | z-index: 10003; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #fff; |
| | | margin-bottom: 12px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 1px solid #eee; |
| | | } |
| | | |
| | | .info-content { |
| | | font-size: 12px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .info-content p { |
| | | margin: 8px 0; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .info-window[data-status="waiting"] .info-title { |
| | | color: #fff; |
| | | } |
| | | |
| | | .info-window[data-status="rented"] .info-title { |
| | | color: #fff; |
| | | } |
| | | |
| | | .amap-info { |
| | | z-index: 10003 !important; |
| | | display: block !important; |
| | | } |
| | | |
| | | .amap-info-content { |
| | | background: rgba(146, 146, 146, 0.5); |
| | | padding: 0 !important; |
| | | } |
| | | |
| | | .amap-info-sharp { |
| | | display: none !important; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <div class="right-panel"> |
| | | <div class="right-panel-top"> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本月新增用户</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ newUsers }}<span class="unit">户</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本季度交租率</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ quarterlyRate }}<span class="unit">元</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="right-panel-top1"> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本月新增用户</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ newUsers }}<span class="unit">万元</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本季度交租率</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ quarterlyRate }}<span class="unit">万元</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="status-list"> |
| | | <div class="status-title">实时租赁数据</div> |
| | | <div class="status-items-container"> |
| | | <div class="status-items"> |
| | | <div v-for="(item, index) in rentList" :key="index" class="status-item"> |
| | | <span class="area-name">{{ item.area }}</span> |
| | | <div class="status-actions"> |
| | | <span class="action-btn">启用中</span> |
| | | <span class="action-btn">合同签署</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'RightPanel', |
| | | data() { |
| | | return { |
| | | newUsers: '877', |
| | | quarterlyRate: '4302', |
| | | rentList: [ |
| | | { area: '新城大道', status: 'online' }, |
| | | { area: '拉萨路', status: 'offline' }, |
| | | { area: '新城大道', status: 'online' }, |
| | | { area: '拉萨路', status: 'online' }, |
| | | { area: '新城大道', status: 'offline' }, |
| | | { area: '拉萨路', status: 'online' }, |
| | | { area: '新城大道', status: 'offline' }, |
| | | { area: '新城大道', status: 'online' } |
| | | ] |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .flex { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .right-panel { |
| | | width: 300px; |
| | | display: flex; |
| | | position: fixed; |
| | | top: 0; |
| | | right: 0; |
| | | height: calc(100vh - 100px); |
| | | margin-top: 100px; |
| | | z-index: 1000; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .data-card { |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | height: 80px; |
| | | } |
| | | |
| | | .data-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .data-title { |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .data-tabs { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .tab-value { |
| | | font-size: 30px; |
| | | font-family: 'DIN ', 'DIN', sans-serif; |
| | | font-weight: bold; |
| | | color: #fff; |
| | | } |
| | | |
| | | .right-panel-top { |
| | | display: flex; |
| | | background: rgba(146, 146, 146, 0.5); |
| | | margin-top: 50px; |
| | | padding-top: 10px; |
| | | padding-bottom: 30px; |
| | | border-radius: 2px; |
| | | margin-right: 20px; |
| | | border-left: 4px solid #87F7C7; |
| | | } |
| | | |
| | | .right-panel-top1 { |
| | | display: flex; |
| | | background: rgba(146, 146, 146, 0.5); |
| | | margin-top: 10px; |
| | | padding-top: 10px; |
| | | padding-bottom: 30px; |
| | | border-radius: 2px; |
| | | margin-right: 20px; |
| | | border-left: 4px solid #FFB822; |
| | | } |
| | | |
| | | .status-list { |
| | | background: rgba(146, 146, 146, 0.5); |
| | | border-radius: 2px; |
| | | margin-right: 20px; |
| | | margin-top: 10px; |
| | | padding: 15px; |
| | | height: 500px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .status-title { |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .status-items { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | height: calc(100% - 45px); |
| | | animation: scrollUp 20s linear infinite; |
| | | } |
| | | |
| | | .status-items:hover { |
| | | animation-play-state: paused; |
| | | } |
| | | |
| | | .status-items-container { |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | @keyframes scrollUp { |
| | | 0% { |
| | | transform: translateY(0); |
| | | } |
| | | |
| | | 100% { |
| | | transform: translateY(-50%); |
| | | } |
| | | } |
| | | |
| | | .status-items::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | .status-items { |
| | | -ms-overflow-style: none; |
| | | scrollbar-width: none; |
| | | } |
| | | |
| | | .status-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .area-name { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .status-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .action-btn { |
| | | padding: 2px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | background: rgba(0, 255, 255, 0.2); |
| | | } |
| | | |
| | | .unit { |
| | | font-size: 14px; |
| | | margin-left: 4px; |
| | | color: #fff; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div id="app"> |
| | | <div class="header"> |
| | | <div class="time-weather"> |
| | | <span class="time">{{ currentTime }}</span> |
| | | <span class="date">{{ currentDate }}</span> |
| | | <span class="weather"> |
| | | <i class="weather-icon"></i> |
| | | {{ temperature }}°C |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <div > |
| | | <Header /> |
| | | <div class="main-content"> |
| | | <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"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"> <i class="icon-area"></i> 房屋总面积</div> |
| | | <div class="stat-value">{{ totalArea }}<span class="unit">m²</span></div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-item"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"> <i class="icon-area"></i>已出租面积</div> |
| | | <div class="stat-value">{{ rentedArea }}<span class="unit">m²</span></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="header-right"> |
| | | <div class="stat-group"> |
| | | <div class="stat-item mt-10"> |
| | | <div class="stat-info"> |
| | | <div class="stat-label"> <i class="icon-money"></i>今日已收租金</div> |
| | | <div class="stat-value">{{ todayRent }}<span class="unit">万元</span></div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-item mt-10"> |
| | | |
| | | <div class="stat-info"> |
| | | <div class="stat-label"><i class="icon-money"></i>今日已收租金</div> |
| | | <div class="stat-value">{{ todayIncome }}<span class="unit">万元</span></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="chart-card" style="width: 350px;"> |
| | | <div class="chart-title">区域租金分析</div> |
| | | <div class="area-chart" ref="areaRentChart"></div> |
| | | </div> |
| | | <div class="bottom-charts"> |
| | | <div class="bottom-charts"> |
| | | <div class="chart-group"> |
| | | <div class="chart-title">收入趋势</div> |
| | | <div class="income-chart" ref="incomeChart"></div> |
| | | </div> |
| | | <div class="chart-group"> |
| | | <div class="chart-title">租金收入趋势图</div> |
| | | <div class="trend-chart" ref="rentTrendChart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="center-panel"> |
| | | <div class="map-container" ref="mapContainer"></div> |
| | | </div> |
| | | <div class="right-panel"> |
| | | <div class="right-panel-top"> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本月新增用户</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ newUsers }}<span class="unit">户</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本季度交租率</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ quarterlyRate }}<span class="unit">元</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="right-panel-top1"> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本月新增用户</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ newUsers }}<span class="unit">万元</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="data-card"> |
| | | <div class="data-header flex"> |
| | | <div class="data-title">本季度交租率</div> |
| | | <div class="data-tabs"> |
| | | <div class="tab-value">{{ quarterlyRate }}<span class="unit">万元</span></div> |
| | | <div class="tab-chart"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="status-list"> |
| | | <div class="status-title">实时租赁数据</div> |
| | | |
| | | <div class="status-items-container"> |
| | | <div class="status-items"> |
| | | <div v-for="(item, index) in rentList" :key="index" class="status-item"> |
| | | <span class="area-name">{{ item.area }}</span> |
| | | <div class="status-actions"> |
| | | <span class="action-btn">启用中</span> |
| | | <span class="action-btn">合同签署</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <LeftPanel /> |
| | | <MapPanel /> |
| | | <RightPanel /> |
| | | </div> |
| | | |
| | | <BottomCharts /> |
| | | <div class="footer"></div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import AMapLoader from '@amap/amap-jsapi-loader' |
| | | import * as echarts from 'echarts' |
| | | import dayjs from 'dayjs' |
| | | import Header from '@/components/Header.vue' |
| | | import LeftPanel from '@/components/LeftPanel.vue' |
| | | import MapPanel from '@/components/MapPanel.vue' |
| | | import RightPanel from '@/components/RightPanel.vue' |
| | | import BottomCharts from '@/components/BottomCharts.vue' |
| | | import { getData } from './service' |
| | | |
| | | export default { |
| | | name: 'DataScreen', |
| | | data() { |
| | | return { |
| | | map: null, |
| | | markers: [], |
| | | infoWindow: null, |
| | | currentTime: '', |
| | | currentDate: '', |
| | | temperature: '--', |
| | | totalArea: '386.5', |
| | | rentedArea: '316.5', |
| | | todayRent: '316.5', |
| | | todayIncome: '124.5', |
| | | newUsers: '877', |
| | | quarterlyRate: '4302', |
| | | weather: null, |
| | | rentList: [ |
| | | { area: '新城大道', status: 'online' }, |
| | | { area: '拉萨路', status: 'offline' }, |
| | | { area: '新城大道', status: 'online' }, |
| | | { area: '拉萨路', status: 'online' }, |
| | | { area: '新城大道', status: 'offline' }, |
| | | { area: '拉萨路', status: 'online' }, |
| | | { area: '新城大道', status: 'offline' }, |
| | | { area: '新城大道', status: 'online' } |
| | | ], |
| | | charts: { |
| | | areaRent: null, |
| | | income: null, |
| | | rentTrend: null |
| | | } |
| | | } |
| | | components: { |
| | | Header, |
| | | LeftPanel, |
| | | MapPanel, |
| | | RightPanel, |
| | | BottomCharts |
| | | }, |
| | | created() { |
| | | console.log(this.$route); |
| | |
| | | this.fetchData() |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.$nextTick(() => { |
| | | this.initMap() |
| | | this.initCharts() |
| | | }) |
| | | this.startTimeUpdate() |
| | | this.startWeatherUpdate() |
| | | }, |
| | | methods: { |
| | | fetchData() { |
| | | getData().then(res => { |
| | | console.log(res) |
| | | }) |
| | | }, |
| | | startTimeUpdate() { |
| | | const updateTime = () => { |
| | | this.currentTime = dayjs().format('HH:mm:ss') |
| | | this.currentDate = dayjs().format('YYYY/MM/DD') |
| | | } |
| | | updateTime() |
| | | setInterval(updateTime, 1000) |
| | | }, |
| | | startWeatherUpdate() { |
| | | const updateWeather = async () => { |
| | | try { |
| | | // 使用 OpenMeteo API |
| | | const response = await fetch('https://api.open-meteo.com/v1/forecast?latitude=29.6487&longitude=91.1172¤t=temperature_2m&timezone=Asia%2FShanghai') |
| | | const data = await response.json() |
| | | if (data.current) { |
| | | this.temperature = Math.round(data.current.temperature_2m) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取天气数据失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 立即更新一次 |
| | | updateWeather() |
| | | |
| | | // 每30分钟更新一次 |
| | | setInterval(updateWeather, 30 * 60 * 1000) |
| | | }, |
| | | async initMap() { |
| | | try { |
| | | const AMap = await AMapLoader.load({ |
| | | key: '526e04b30ceba8f217c5def5a92392f9', |
| | | version: '2.0', |
| | | plugins: ['AMap.ToolBar', 'AMap.Scale'] |
| | | }) |
| | | |
| | | this.map = new AMap.Map(this.$refs.mapContainer, { |
| | | zoom: 16, |
| | | center: [91.1172, 29.6487], |
| | | viewMode: '3D', |
| | | pitch: 35, |
| | | mapStyle: 'amap://styles/normal', |
| | | features: ['bg', 'road', 'building', 'point'], |
| | | buildingAnimation: true |
| | | }) |
| | | |
| | | this.map.addControl(new AMap.ToolBar({ |
| | | position: 'RB' |
| | | })) |
| | | this.map.addControl(new AMap.Scale({ |
| | | position: 'RB' |
| | | })) |
| | | |
| | | // 创建信息窗口 |
| | | this.infoWindow = new AMap.InfoWindow({ |
| | | offset: new AMap.Pixel(0, -30), |
| | | closeWhenClickMap: true, |
| | | autoMove: true, |
| | | anchor: 'bottom-center' |
| | | }) |
| | | |
| | | // 添加地图点击事件 |
| | | this.map.on('click', () => { |
| | | if (this.infoWindow) { |
| | | this.infoWindow.close() |
| | | } |
| | | }) |
| | | |
| | | this.addMarkers() |
| | | } catch (error) { |
| | | console.error('地图加载失败:', error) |
| | | } |
| | | }, |
| | | addMarkers() { |
| | | const houses = [ |
| | | { |
| | | position: [91.1172, 29.6487], |
| | | title: '新城大道房源', |
| | | status: 'waiting', |
| | | content: ` |
| | | <div class="info-window" data-status="waiting"> |
| | | <div class="info-title">房屋状态:待租出</div> |
| | | <div class="info-content"> |
| | | <p>房屋编号:新城大道</p> |
| | | <p>房屋状态:待租出</p> |
| | | <p>租赁面积:1000/2000㎡</p> |
| | | <p>本季租金:500/2000元/月</p> |
| | | </div> |
| | | </div> |
| | | ` |
| | | }, |
| | | { |
| | | position: [91.1272, 29.6587], |
| | | title: '拉萨路房源', |
| | | status: 'rented', |
| | | content: ` |
| | | <div class="info-window" data-status="rented"> |
| | | <div class="info-title">房屋状态:已租出</div> |
| | | <div class="info-content"> |
| | | <p>房屋编号:拉萨路</p> |
| | | <p>房屋状态:已租出</p> |
| | | <p>租赁面积:800/1500㎡</p> |
| | | <p>本季租金:400/1800元/月</p> |
| | | </div> |
| | | </div> |
| | | ` |
| | | } |
| | | ] |
| | | |
| | | houses.forEach(house => { |
| | | // 创建多层圆环 |
| | | const circles = [] |
| | | const baseRadius = 15 |
| | | const numCircles = 3 |
| | | |
| | | // 创建中心点 |
| | | const centerCircle = new AMap.Circle({ |
| | | center: house.position, |
| | | radius: baseRadius / 2, |
| | | fillColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50', |
| | | fillOpacity: 0.8, |
| | | strokeWeight: 0, |
| | | zIndex: numCircles + 2, |
| | | bubble: true, |
| | | cursor: 'pointer' |
| | | }) |
| | | |
| | | // 创建多层圆环 |
| | | for (let i = 0; i < numCircles; i++) { |
| | | circles.push(new AMap.Circle({ |
| | | center: house.position, |
| | | radius: baseRadius * (i + 1), |
| | | fillColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50', |
| | | fillOpacity: 0.3 - (i * 0.05), |
| | | strokeColor: house.status === 'waiting' ? '#ff9800' : '#4CAF50', |
| | | strokeWeight: 2, |
| | | strokeOpacity: 0.5 - (i * 0.08), |
| | | zIndex: numCircles - i, |
| | | bubble: true, |
| | | cursor: 'pointer' |
| | | })) |
| | | } |
| | | |
| | | // 点击事件处理 |
| | | const clickHandler = () => { |
| | | if (this.infoWindow) { |
| | | this.infoWindow.setContent(house.content) |
| | | this.infoWindow.open(this.map, house.position) |
| | | } |
| | | } |
| | | |
| | | // 为所有圆环添加点击事件 |
| | | centerCircle.on('click', clickHandler) |
| | | circles.forEach(circle => circle.on('click', clickHandler)) |
| | | |
| | | // 添加到地图 |
| | | this.map.add([centerCircle, ...circles]) |
| | | this.markers.push({ center: centerCircle, rings: circles }) |
| | | |
| | | // 动画效果 |
| | | let phase = 0 |
| | | const animate = () => { |
| | | phase += 0.15 |
| | | const scale = 1 + 0.6 * Math.sin(phase) |
| | | |
| | | circles.forEach((circle, index) => { |
| | | const currentRadius = baseRadius * (index + 1) |
| | | circle.setRadius(currentRadius * scale) |
| | | |
| | | const baseOpacity = 0.3 - (index * 0.05) |
| | | const opacityScale = 1 + 0.5 * Math.sin(phase) |
| | | circle.setOptions({ |
| | | fillOpacity: baseOpacity * opacityScale, |
| | | strokeOpacity: (0.5 - (index * 0.08)) * opacityScale |
| | | }) |
| | | }) |
| | | |
| | | requestAnimationFrame(animate) |
| | | } |
| | | animate() |
| | | }) |
| | | }, |
| | | initCharts() { |
| | | // 区域租金分析图表 |
| | | this.charts.areaRent = echarts.init(this.$refs.areaRentChart) |
| | | this.charts.areaRent.setOption({ |
| | | grid: { |
| | | left: '20%', |
| | | right: '5%', |
| | | top: '10%', |
| | | bottom: '10%' |
| | | }, |
| | | xAxis: { |
| | | type: 'value', |
| | | axisLine: { |
| | | show: false |
| | | }, |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'category', |
| | | data: ['新城大道', '拉萨路', '新城大道', '拉萨路', '新城大道', '新城大道', '拉萨路', '新城大道', '拉萨路', '新城大道'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | type: 'bar', |
| | | data: [1900, 1850, 1800, 1750, 1700, 1600, 1500, 1400, 1300, 1200, 1100, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 0], |
| | | barWidth: '30%', |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 1, |
| | | y2: 0, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: '#0ff' |
| | | }, { |
| | | offset: 1, |
| | | color: '#0ff' |
| | | }] |
| | | } |
| | | } |
| | | }] |
| | | }) |
| | | |
| | | // 收入趋势图表 |
| | | this.charts.income = echarts.init(this.$refs.incomeChart) |
| | | this.charts.income.setOption({ |
| | | grid: { |
| | | top: '15%', |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['23-4月', '23-12月', '24-3月', '24-4月', '24-5月', '24-6月', '25-3月'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | data: [100, 400, 200, 300, 200, 400, 300], |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { |
| | | color: '#ffd700', |
| | | width: 2 |
| | | }, |
| | | itemStyle: { |
| | | color: '#ffd700' |
| | | }, |
| | | areaStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: 'rgba(255, 215, 0, 0.3)' |
| | | }, { |
| | | offset: 1, |
| | | color: 'rgba(255, 215, 0, 0.1)' |
| | | }] |
| | | } |
| | | } |
| | | }] |
| | | }) |
| | | |
| | | // 租金趋势图表 |
| | | this.charts.rentTrend = echarts.init(this.$refs.rentTrendChart) |
| | | this.charts.rentTrend.setOption({ |
| | | grid: { |
| | | top: '15%', |
| | | left: '3%', |
| | | right: '3%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['23-4月', '23-12月', '24-3月', '24-4月', '24-5月', '24-6月', '25-3月'], |
| | | axisLine: { |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | splitLine: { |
| | | show: false |
| | | }, |
| | | axisLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | axisLabel: { |
| | | color: '#fff' |
| | | } |
| | | }, |
| | | series: [{ |
| | | data: [300, 200, 400, 300, 500, 400, 300], |
| | | type: 'line', |
| | | smooth: true, |
| | | symbol: 'circle', |
| | | symbolSize: 8, |
| | | lineStyle: { |
| | | color: '#4facfe', |
| | | width: 2 |
| | | }, |
| | | itemStyle: { |
| | | color: '#4facfe' |
| | | }, |
| | | areaStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [{ |
| | | offset: 0, |
| | | color: 'rgba(79,172,254,0.3)' |
| | | }, { |
| | | offset: 1, |
| | | color: 'rgba(79,172,254,0.1)' |
| | | }] |
| | | } |
| | | } |
| | | }] |
| | | }) |
| | | |
| | | // 监听窗口大小变化 |
| | | window.addEventListener('resize', () => { |
| | | Object.values(this.charts).forEach(chart => { |
| | | chart && chart.resize() |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | |
| | | </script> |
| | | |
| | | <style> |
| | | .flex { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | #app { |
| | | width: 100vw; |
| | | height: 100vh; |
| | |
| | | font-display: swap; |
| | | } |
| | | |
| | | .header { |
| | | height: 120px; |
| | | .main-content { |
| | | flex: 1; |
| | | display: flex; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100vw; |
| | | background: linear-gradient(to bottom, rgba(1, 85, 121, 0.8) 0%, rgba(1, 85, 121, 0.4) 40%, rgba(151, 190, 192, 0) 100%); |
| | | z-index: 1000; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | padding: 10px 20px; |
| | | /* background: rgba(255, 255, 255, 0.05); */ |
| | | backdrop-filter: blur(10px); |
| | | gap: 10px; |
| | | } |
| | | |
| | | .footer { |
| | |
| | | width: 100vw; |
| | | z-index: 1000; |
| | | background: linear-gradient(to top, rgba(1, 85, 121, 0.8) 0%, rgba(1, 85, 121, 0.4) 20%, rgba(151, 190, 192, 0) 100%); |
| | | } |
| | | |
| | | .header-left, |
| | | .header-right { |
| | | /* width: 30%; */ |
| | | } |
| | | |
| | | .header-center { |
| | | /* text-align: 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; |
| | | /* margin-bottom: 10px; */ |
| | | } |
| | | |
| | | .sub-title { |
| | | font-size: 12px; |
| | | color: fff; |
| | | } |
| | | |
| | | .time-weather { |
| | | display: flex; |
| | | gap: 20px; |
| | | align-items: center; |
| | | /* justify-content: center; */ |
| | | } |
| | | |
| | | .time { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .date { |
| | | color: #fff; |
| | | } |
| | | |
| | | .weather { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .main-content { |
| | | flex: 1; |
| | | display: flex; |
| | | /* padding: 10px; */ |
| | | gap: 10px; |
| | | } |
| | | |
| | | .left-panel { |
| | | width: 400px; |
| | | display: flex; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | height: calc(100vh); |
| | | background: linear-gradient(to right, rgba(1, 85, 121, 0.8) 0%, rgba(1, 85, 121, 0.8) 20%, rgba(151, 190, 192, 0)); |
| | | /* margin-top: 100px; */ |
| | | /* padding-top: 10px; */ |
| | | z-index: 1002; |
| | | flex-direction: column; |
| | | padding-bottom: 200px; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .bottom-charts { |
| | | height: 200px; |
| | | width: 700px; |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | z-index: 1000; |
| | | /* background: linear-gradient(to right, rgba(1, 85, 121, 0.1) 0%, rgba(1, 85, 121, 0.1) 50%, rgba(151, 190, 192, 0) 100%); */ |
| | | } |
| | | |
| | | .right-panel { |
| | | width: 300px; |
| | | display: flex; |
| | | position: fixed; |
| | | top: 0; |
| | | right: 0; |
| | | height: calc(100vh - 100px); |
| | | margin-top: 100px; |
| | | z-index: 1000; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .center-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .map-container { |
| | | flex: 1; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | width: 100%; |
| | | height: 100%; |
| | | min-height: 500px; |
| | | } |
| | | |
| | | .bottom-charts { |
| | | /* height: 200px; */ |
| | | display: flex; |
| | | gap: 10px; |
| | | /* margin-bottom: 40px; */ |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .chart-title { |
| | | font-size: 16px; |
| | | margin-bottom: 15px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .area-chart { |
| | | height: calc(100% - 30px); |
| | | } |
| | | |
| | | .data-card { |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | height: 80px; |
| | | } |
| | | |
| | | .data-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .data-title { |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .data-tabs { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .tab-value { |
| | | font-size: 30px; |
| | | font-family: 'DIN ', 'DIN', sans-serif; |
| | | font-weight: bold; |
| | | color: #fff; |
| | | /* font-family: "DINPro-Bold"; */ |
| | | } |
| | | |
| | | .status-list { |
| | | background: rgba(146, 146, 146, 0.5); |
| | | border-radius: 2px; |
| | | margin-right: 20px; |
| | | margin-top: 10px; |
| | | padding: 15px; |
| | | height: 500px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .status-title { |
| | | font-size: 16px; |
| | | /* margin-bottom: 15px; */ |
| | | } |
| | | |
| | | .status-items { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | height: calc(100% - 45px); |
| | | animation: scrollUp 20s linear infinite; |
| | | } |
| | | |
| | | .status-items:hover { |
| | | animation-play-state: paused; |
| | | } |
| | | |
| | | .status-items-container { |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | @keyframes scrollUp { |
| | | 0% { |
| | | transform: translateY(0); |
| | | } |
| | | |
| | | 100% { |
| | | transform: translateY(-50%); |
| | | } |
| | | } |
| | | |
| | | /* 隐藏滚动条 */ |
| | | .status-items::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | .status-items { |
| | | -ms-overflow-style: none; |
| | | scrollbar-width: none; |
| | | } |
| | | |
| | | .status-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | /* padding: 10px; */ |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .area-name { |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .status-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .action-btn { |
| | | padding: 2px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | background: rgba(0, 255, 255, 0.2); |
| | | } |
| | | |
| | | .chart-group { |
| | | flex: 1; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border-radius: 8px; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .income-chart, |
| | | .trend-chart { |
| | | height: calc(100% - 30px); |
| | | } |
| | | |
| | | .info-window { |
| | | padding: 10px; |
| | | background: rgba(146, 146, 146, 0.5); |
| | | border-radius: 8px; |
| | | z-index: 10003; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | color: #fff; |
| | | margin-bottom: 12px; |
| | | padding-bottom: 8px; |
| | | border-bottom: 1px solid #eee; |
| | | } |
| | | |
| | | .info-content { |
| | | font-size: 12px; |
| | | color: #fff; |
| | | } |
| | | |
| | | .info-content p { |
| | | margin: 8px 0; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .info-window[data-status="waiting"] .info-title { |
| | | color: #fff; |
| | | } |
| | | |
| | | .info-window[data-status="rented"] .info-title { |
| | | color: #fff; |
| | | } |
| | | |
| | | .amap-info { |
| | | z-index: 10003 !important; |
| | | display: block !important; |
| | | } |
| | | |
| | | .amap-info-content { |
| | | background: rgba(146, 146, 146, 0.5); |
| | | padding: 0 !important; |
| | | } |
| | | |
| | | .amap-info-sharp { |
| | | display: none !important; |
| | | } |
| | | |
| | | .right-panel-top { |
| | | display: flex; |
| | | background: rgba(146, 146, 146, 0.5); |
| | | margin-top: 50px; |
| | | padding-top: 10px; |
| | | padding-bottom: 30px; |
| | | border-radius: 2px; |
| | | margin-right: 20px; |
| | | border-left: 4px solid #87F7C7; |
| | | /* height: 100px; */ |
| | | } |
| | | |
| | | .right-panel-top1 { |
| | | display: flex; |
| | | background: rgba(146, 146, 146, 0.5); |
| | | margin-top: 10px; |
| | | padding-top: 10px; |
| | | padding-bottom: 30px; |
| | | border-radius: 2px; |
| | | margin-right: 20px; |
| | | border-left: 4px solid #FFB822; |
| | | /* height: 100px; */ |
| | | } |
| | | </style> |