|
|
@@ -80,59 +80,81 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<div v-if="demandAnalysisExpanded" class="card-body">
|
|
|
- <div class="demand-list">
|
|
|
- <div class="demand-item" v-for="(demand, index) in demands" :key="index">
|
|
|
- <div class="demand-name">{{ demand.name }}</div>
|
|
|
- <div class="demand-value">{{ demand.value }} 万m³</div>
|
|
|
- <div class="demand-percent">{{ demand.percent }}%</div>
|
|
|
+ <div class="date-range-filter">
|
|
|
+ <div class="date-display" @click="showDatePicker = !showDatePicker">
|
|
|
+ <span class="date-text">{{ formatDateDisplay(startDate) }} 至 {{ formatDateDisplay(endDate) }}</span>
|
|
|
+ <span class="calendar-icon">📅</span>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="data-card mt-20">
|
|
|
- <div class="card-header" @click="toggleOptimization">
|
|
|
- <h3 class="card-title">优化建议</h3>
|
|
|
- <div class="header-actions">
|
|
|
- <span class="toggle-btn">{{ optimizationExpanded ? '▼' : '▶' }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div v-if="optimizationExpanded" class="card-body">
|
|
|
- <div class="suggestion-list">
|
|
|
- <div class="suggestion-item" v-for="(sug, index) in suggestions" :key="index">
|
|
|
- <div class="suggestion-icon">{{ sug.icon }}</div>
|
|
|
- <div class="suggestion-text">{{ sug.text }}</div>
|
|
|
+ <div v-if="showDatePicker" class="date-picker-popup">
|
|
|
+ <div class="date-picker-header">
|
|
|
+ <button class="nav-btn" @click="prevMonth"><</button>
|
|
|
+ <span class="current-month">{{ currentYear }}年{{ currentMonth + 1 }}月</span>
|
|
|
+ <button class="nav-btn" @click="nextMonth">></button>
|
|
|
+ <button class="close-btn" @click="showDatePicker = false">×</button>
|
|
|
+ </div>
|
|
|
+ <div class="calendar-container">
|
|
|
+ <div class="weekdays">
|
|
|
+ <div v-for="day in weekdays" :key="day" class="weekday">{{ day }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="days">
|
|
|
+ <div
|
|
|
+ v-for="(date, index) in calendarDays"
|
|
|
+ :key="index"
|
|
|
+ class="day"
|
|
|
+ :class="{
|
|
|
+ 'empty': !date,
|
|
|
+ 'selected': isSelected(date),
|
|
|
+ 'in-range': isInRange(date),
|
|
|
+ 'start': isStartDate(date),
|
|
|
+ 'end': isEndDate(date),
|
|
|
+ 'today': isToday(date)
|
|
|
+ }"
|
|
|
+ @click="selectDate(date)"
|
|
|
+ >
|
|
|
+ {{ date ? new Date(date).getDate() : '' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="date-picker-footer">
|
|
|
+ <button class="confirm-btn" @click="confirmDateRange">确定</button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="data-card mt-20">
|
|
|
- <div class="card-header" @click="toggleEffectPreview">
|
|
|
- <h3 class="card-title">效果预演</h3>
|
|
|
- <div class="header-actions">
|
|
|
- <span class="toggle-btn">{{ effectPreviewExpanded ? '▼' : '▶' }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div v-if="effectPreviewExpanded" class="card-body">
|
|
|
- <div class="preview-content">
|
|
|
- <div class="preview-stats">
|
|
|
- <div class="stat-row">
|
|
|
- <div class="stat-label">预计节水</div>
|
|
|
- <div class="stat-value">+12.5%</div>
|
|
|
+ <div class="demand-cards">
|
|
|
+ <div class="demand-cards-row">
|
|
|
+ <div class="demand-card">
|
|
|
+ <div class="card-label">总需水量</div>
|
|
|
+ <div class="card-value">{{ demandData.totalDemand }} 万m³</div>
|
|
|
+ <div class="card-change" :class="demandData.changePercent >= 0 ? 'up' : 'down'">
|
|
|
+ {{ demandData.changePercent >= 0 ? '+' : '' }}{{ demandData.changePercent }}%
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="stat-row">
|
|
|
- <div class="stat-label">供水保证率</div>
|
|
|
- <div class="stat-value">98.2%</div>
|
|
|
+ <div class="demand-card">
|
|
|
+ <div class="card-label">供需状态</div>
|
|
|
+ <div class="card-value" :class="demandData.supplyStatus >= 0 ? 'surplus' : 'deficit'">
|
|
|
+ {{ demandData.supplyStatus >= 0 ? '+' : '' }}{{ demandData.supplyStatus }} 万m³
|
|
|
+ </div>
|
|
|
+ <div class="card-status" :class="demandData.supplyStatus >= 0 ? 'surplus' : 'deficit'">
|
|
|
+ {{ demandData.supplyStatus >= 0 ? '盈余' : '缺口' }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="stat-row">
|
|
|
- <div class="stat-label">生态流量</div>
|
|
|
- <div class="stat-value">达标</div>
|
|
|
+ </div>
|
|
|
+ <div class="demand-trend-card">
|
|
|
+ <div class="chart-label">需水趋势</div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <canvas id="demandTrendChart" width="320" height="240"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="demand-comparison-card">
|
|
|
+ <div class="chart-label">同期对比</div>
|
|
|
+ <div class="chart-container">
|
|
|
+ <canvas id="demandComparisonChart" width="320" height="260"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -148,14 +170,28 @@ export default {
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
+ const today = new Date()
|
|
|
+ const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
|
|
|
+ const formatDate = (date) => {
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ return `${year}-${month}-${day}`
|
|
|
+ }
|
|
|
return {
|
|
|
basicDataExpanded: true,
|
|
|
reservoirStatusExpanded: true,
|
|
|
allocationPlanExpanded: true,
|
|
|
demandAnalysisExpanded: true,
|
|
|
- optimizationExpanded: true,
|
|
|
- effectPreviewExpanded: true,
|
|
|
allocationChart: null,
|
|
|
+ demandStructureChart: null,
|
|
|
+ showDatePicker: false,
|
|
|
+ startDate: formatDate(weekAgo),
|
|
|
+ endDate: formatDate(today),
|
|
|
+ selectingStart: true,
|
|
|
+ currentMonth: today.getMonth(),
|
|
|
+ currentYear: today.getFullYear(),
|
|
|
+ weekdays: ['日', '一', '二', '三', '四', '五', '六'],
|
|
|
reservoirs: [
|
|
|
{ name: '小塔山水库', level: 18.6, capacity: 22.5 },
|
|
|
{ name: '吴山水库', level: 12.3, capacity: 15.0 },
|
|
|
@@ -166,21 +202,123 @@ export default {
|
|
|
{ name: '居民生活', value: 12.3, percent: 18 },
|
|
|
{ name: '生态环境', value: 9.1, percent: 14 }
|
|
|
],
|
|
|
- suggestions: [
|
|
|
- { icon: '💧', text: '建议灌区采用滴灌技术' },
|
|
|
- { icon: '🌱', text: '加强生态补水调度' }
|
|
|
- ]
|
|
|
+ demandData: {
|
|
|
+ totalDemand: 67.2,
|
|
|
+ changePercent: 5.3,
|
|
|
+ supplyStatus: 15.4
|
|
|
+ },
|
|
|
+ demandTrendChart: null,
|
|
|
+ demandTrendData: {
|
|
|
+ dates: [],
|
|
|
+ totalDemand: [],
|
|
|
+ agriculturalDemand: [],
|
|
|
+ residentialDemand: [],
|
|
|
+ heilinInflow: [],
|
|
|
+ xiaotaishanSupply: []
|
|
|
+ },
|
|
|
+ demandComparisonChart: null,
|
|
|
+ demandComparisonData: {
|
|
|
+ categories: ['农业灌溉', '居民用水', '生态环境'],
|
|
|
+ current: [],
|
|
|
+ lastYear: [],
|
|
|
+ lastMonth: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ calendarDays() {
|
|
|
+ const days = []
|
|
|
+ const firstDay = new Date(this.currentYear, this.currentMonth, 1)
|
|
|
+ const lastDay = new Date(this.currentYear, this.currentMonth + 1, 0)
|
|
|
+
|
|
|
+ const firstDayOfWeek = firstDay.getDay()
|
|
|
+ const lastDateOfMonth = lastDay.getDate()
|
|
|
+
|
|
|
+ for (let i = 0; i < firstDayOfWeek; i++) {
|
|
|
+ days.push(null)
|
|
|
+ }
|
|
|
+
|
|
|
+ for (let day = 1; day <= lastDateOfMonth; day++) {
|
|
|
+ const date = new Date(this.currentYear, this.currentMonth, day)
|
|
|
+ days.push(this.formatDate(date))
|
|
|
+ }
|
|
|
+
|
|
|
+ return days
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
+ const start = new Date(this.startDate)
|
|
|
+ const end = new Date(this.endDate)
|
|
|
+ const diffTime = Math.abs(end - start)
|
|
|
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
|
+ this.generateDemandTrendData(start, end, diffDays)
|
|
|
+ this.generateDemandComparisonData()
|
|
|
setTimeout(() => {
|
|
|
this.initCharts()
|
|
|
+ this.initDemandTrendChart()
|
|
|
+ this.initDemandComparisonChart()
|
|
|
}, 100)
|
|
|
},
|
|
|
beforeUnmount() {
|
|
|
this.destroyCharts()
|
|
|
},
|
|
|
methods: {
|
|
|
+ formatDate(date) {
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ return `${year}-${month}-${day}`
|
|
|
+ },
|
|
|
+ prevMonth() {
|
|
|
+ if (this.currentMonth === 0) {
|
|
|
+ this.currentMonth = 11
|
|
|
+ this.currentYear--
|
|
|
+ } else {
|
|
|
+ this.currentMonth--
|
|
|
+ }
|
|
|
+ },
|
|
|
+ nextMonth() {
|
|
|
+ if (this.currentMonth === 11) {
|
|
|
+ this.currentMonth = 0
|
|
|
+ this.currentYear++
|
|
|
+ } else {
|
|
|
+ this.currentMonth++
|
|
|
+ }
|
|
|
+ },
|
|
|
+ selectDate(date) {
|
|
|
+ if (!date) return
|
|
|
+
|
|
|
+ if (this.selectingStart) {
|
|
|
+ this.startDate = date
|
|
|
+ this.endDate = date
|
|
|
+ this.selectingStart = false
|
|
|
+ } else {
|
|
|
+ if (date < this.startDate) {
|
|
|
+ this.endDate = this.startDate
|
|
|
+ this.startDate = date
|
|
|
+ } else {
|
|
|
+ this.endDate = date
|
|
|
+ }
|
|
|
+ this.selectingStart = true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isSelected(date) {
|
|
|
+ return date === this.startDate || date === this.endDate
|
|
|
+ },
|
|
|
+ isInRange(date) {
|
|
|
+ if (!date || !this.startDate || !this.endDate) return false
|
|
|
+ return date > this.startDate && date < this.endDate
|
|
|
+ },
|
|
|
+ isStartDate(date) {
|
|
|
+ return date === this.startDate
|
|
|
+ },
|
|
|
+ isEndDate(date) {
|
|
|
+ return date === this.endDate
|
|
|
+ },
|
|
|
+ isToday(date) {
|
|
|
+ const today = new Date()
|
|
|
+ return date === this.formatDate(today)
|
|
|
+ },
|
|
|
toggleBasicData() {
|
|
|
this.basicDataExpanded = !this.basicDataExpanded
|
|
|
},
|
|
|
@@ -197,12 +335,118 @@ export default {
|
|
|
},
|
|
|
toggleDemandAnalysis() {
|
|
|
this.demandAnalysisExpanded = !this.demandAnalysisExpanded
|
|
|
+ if (this.demandAnalysisExpanded) {
|
|
|
+ const start = new Date(this.startDate)
|
|
|
+ const end = new Date(this.endDate)
|
|
|
+ const diffTime = Math.abs(end - start)
|
|
|
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
|
+ this.generateDemandTrendData(start, end, diffDays)
|
|
|
+ this.generateDemandComparisonData()
|
|
|
+ setTimeout(() => {
|
|
|
+ this.initDemandTrendChart()
|
|
|
+ this.initDemandComparisonChart()
|
|
|
+ }, 100)
|
|
|
+ }
|
|
|
},
|
|
|
- toggleOptimization() {
|
|
|
- this.optimizationExpanded = !this.optimizationExpanded
|
|
|
+ formatDateDisplay(dateStr) {
|
|
|
+ if (!dateStr) return ''
|
|
|
+ const date = new Date(dateStr)
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ return `${year}-${month}-${day}`
|
|
|
},
|
|
|
- toggleEffectPreview() {
|
|
|
- this.effectPreviewExpanded = !this.effectPreviewExpanded
|
|
|
+ confirmDateRange() {
|
|
|
+ this.showDatePicker = false
|
|
|
+ this.onDateRangeChange()
|
|
|
+ },
|
|
|
+ onDateRangeChange() {
|
|
|
+ if (!this.startDate || !this.endDate) return
|
|
|
+
|
|
|
+ const start = new Date(this.startDate)
|
|
|
+ const end = new Date(this.endDate)
|
|
|
+ const diffTime = Math.abs(end - start)
|
|
|
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
|
+
|
|
|
+ let baseMultiplier = 1
|
|
|
+ if (diffDays <= 1) {
|
|
|
+ baseMultiplier = 0.14
|
|
|
+ } else if (diffDays <= 7) {
|
|
|
+ baseMultiplier = 1
|
|
|
+ } else if (diffDays <= 30) {
|
|
|
+ baseMultiplier = 4
|
|
|
+ } else {
|
|
|
+ baseMultiplier = diffDays / 7
|
|
|
+ }
|
|
|
+
|
|
|
+ const randomFactor = 0.8 + Math.random() * 0.4
|
|
|
+ const multiplier = baseMultiplier * randomFactor
|
|
|
+
|
|
|
+ this.demandData = {
|
|
|
+ totalDemand: parseFloat((67.2 * multiplier).toFixed(1)),
|
|
|
+ changePercent: parseFloat((3 + Math.random() * 5).toFixed(1)) * (Math.random() > 0.5 ? 1 : -1),
|
|
|
+ supplyStatus: parseFloat((15.4 * multiplier * (Math.random() > 0.5 ? 1 : -1)).toFixed(1))
|
|
|
+ }
|
|
|
+
|
|
|
+ this.generateDemandTrendData(start, end, diffDays)
|
|
|
+ this.generateDemandComparisonData()
|
|
|
+
|
|
|
+ this.updateDemandTrendChart()
|
|
|
+ this.updateDemandComparisonChart()
|
|
|
+ },
|
|
|
+ generateDemandTrendData(startDate, endDate, days) {
|
|
|
+ const dates = []
|
|
|
+ const totalDemand = []
|
|
|
+ const agriculturalDemand = []
|
|
|
+ const residentialDemand = []
|
|
|
+ const heilinInflow = []
|
|
|
+ const xiaotaishanSupply = []
|
|
|
+
|
|
|
+ let current = new Date(startDate)
|
|
|
+ for (let i = 0; i <= days; i++) {
|
|
|
+ const month = current.getMonth() + 1
|
|
|
+ const day = current.getDate()
|
|
|
+ dates.push(`${month}月${day}日`)
|
|
|
+
|
|
|
+ const baseAg = 6.5
|
|
|
+ const baseRes = 1.8
|
|
|
+ const factor = 0.8 + Math.random() * 0.4
|
|
|
+
|
|
|
+ const ag = parseFloat((baseAg * factor).toFixed(1))
|
|
|
+ const res = parseFloat((baseRes * factor).toFixed(1))
|
|
|
+ const total = parseFloat((ag + res).toFixed(1))
|
|
|
+ const inflow = parseFloat((total * (0.7 + Math.random() * 0.6)).toFixed(1))
|
|
|
+ const supply = parseFloat((total * (0.8 + Math.random() * 0.5)).toFixed(1))
|
|
|
+
|
|
|
+ agriculturalDemand.push(ag)
|
|
|
+ residentialDemand.push(res)
|
|
|
+ totalDemand.push(total)
|
|
|
+ heilinInflow.push(inflow)
|
|
|
+ xiaotaishanSupply.push(supply)
|
|
|
+
|
|
|
+ current.setDate(current.getDate() + 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.demandTrendData = {
|
|
|
+ dates,
|
|
|
+ totalDemand,
|
|
|
+ agriculturalDemand,
|
|
|
+ residentialDemand,
|
|
|
+ heilinInflow,
|
|
|
+ xiaotaishanSupply
|
|
|
+ }
|
|
|
+ },
|
|
|
+ generateDemandComparisonData() {
|
|
|
+ const currentData = [45.8, 12.3]
|
|
|
+ const lastYearData = [42.5, 11.8]
|
|
|
+ const lastMonthData = [44.2, 12.0]
|
|
|
+
|
|
|
+ this.demandComparisonData = {
|
|
|
+ categories: ['农业灌溉', '居民用水'],
|
|
|
+ current: currentData,
|
|
|
+ lastYear: lastYearData,
|
|
|
+ lastMonth: lastMonthData
|
|
|
+ }
|
|
|
},
|
|
|
initCharts() {
|
|
|
this.initAllocationChart()
|
|
|
@@ -211,6 +455,12 @@ export default {
|
|
|
if (this.allocationChart) {
|
|
|
this.allocationChart.dispose()
|
|
|
}
|
|
|
+ if (this.demandTrendChart) {
|
|
|
+ this.demandTrendChart.dispose()
|
|
|
+ }
|
|
|
+ if (this.demandComparisonChart) {
|
|
|
+ this.demandComparisonChart.dispose()
|
|
|
+ }
|
|
|
},
|
|
|
initAllocationChart() {
|
|
|
if (document.getElementById('allocationChart')) {
|
|
|
@@ -228,45 +478,336 @@ export default {
|
|
|
orient: 'vertical',
|
|
|
left: 'left',
|
|
|
textStyle: {
|
|
|
- color: '#7bbef6'
|
|
|
- }
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 10
|
|
|
+ },
|
|
|
+ itemWidth: 10,
|
|
|
+ itemHeight: 10,
|
|
|
+ itemGap: 6
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: '水资源分配',
|
|
|
type: 'pie',
|
|
|
- radius: ['40%', '70%'],
|
|
|
+ radius: '60%',
|
|
|
avoidLabelOverlap: false,
|
|
|
- itemStyle: {
|
|
|
- borderRadius: 10,
|
|
|
- borderColor: 'rgba(0, 30, 60, 0.6)',
|
|
|
- borderWidth: 2
|
|
|
- },
|
|
|
label: {
|
|
|
- show: false,
|
|
|
- position: 'center'
|
|
|
+ show: true,
|
|
|
+ position: 'outside',
|
|
|
+ formatter: '{b}: {c}万m³ ({d}%)',
|
|
|
+ color: '#e0fcff',
|
|
|
+ fontSize: 11
|
|
|
},
|
|
|
emphasis: {
|
|
|
label: {
|
|
|
show: true,
|
|
|
- fontSize: 14,
|
|
|
+ fontSize: 12,
|
|
|
fontWeight: 'bold',
|
|
|
color: '#e0fcff'
|
|
|
}
|
|
|
},
|
|
|
labelLine: {
|
|
|
- show: false
|
|
|
+ show: true,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#7bbef6'
|
|
|
+ },
|
|
|
+ length: 10,
|
|
|
+ length2: 15
|
|
|
},
|
|
|
data: [
|
|
|
{ value: 45.8, name: '农业灌溉', itemStyle: { color: '#62f6fb' } },
|
|
|
- { value: 12.3, name: '居民生活', itemStyle: { color: '#38bdf8' } },
|
|
|
- { value: 9.1, name: '生态环境', itemStyle: { color: 'rgba(123, 190, 246, 0.5)' } }
|
|
|
+ { value: 12.3, name: '居民用水', itemStyle: { color: '#38bdf8' } }
|
|
|
]
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
this.allocationChart.setOption(option)
|
|
|
}
|
|
|
+ },
|
|
|
+ initDemandTrendChart() {
|
|
|
+ if (document.getElementById('demandTrendChart')) {
|
|
|
+ if (this.demandTrendChart) {
|
|
|
+ this.demandTrendChart.dispose()
|
|
|
+ }
|
|
|
+ this.demandTrendChart = echarts.init(document.getElementById('demandTrendChart'))
|
|
|
+ this.updateDemandTrendChart()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ updateDemandTrendChart() {
|
|
|
+ if (!this.demandTrendChart) return
|
|
|
+ const option = {
|
|
|
+ animation: false,
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ formatter: '{b}<br/>{a0}: {c0}万m³<br/>{a1}: {c1}万m³<br/>{a2}: {c2}万m³<br/>{a3}: {c3}万m³'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['农业灌溉', '居民用水', '黑林水文站来水', '小塔山水库供水'],
|
|
|
+ textStyle: {
|
|
|
+ color: '#e0fcff',
|
|
|
+ fontSize: 11
|
|
|
+ },
|
|
|
+ itemWidth: 12,
|
|
|
+ itemHeight: 12,
|
|
|
+ itemGap: 8,
|
|
|
+ bottom: '0%'
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: 20,
|
|
|
+ right: 15,
|
|
|
+ bottom: 50,
|
|
|
+ top: 20,
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: true,
|
|
|
+ data: this.demandTrendData.dates,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#7bbef6'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 10
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '水量(万m³)',
|
|
|
+ nameLocation: 'end',
|
|
|
+ nameGap: 5,
|
|
|
+ nameTextStyle: {
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 11,
|
|
|
+ align: 'left',
|
|
|
+ verticalAlign: 'bottom',
|
|
|
+ padding: [0, 0, 0, -15]
|
|
|
+ },
|
|
|
+ min: 0,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#7bbef6'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 10
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(123, 190, 246, 0.2)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '农业灌溉',
|
|
|
+ type: 'bar',
|
|
|
+ stack: 'total',
|
|
|
+ data: this.demandTrendData.agriculturalDemand,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#38bdf8'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '居民用水',
|
|
|
+ type: 'bar',
|
|
|
+ stack: 'total',
|
|
|
+ data: this.demandTrendData.residentialDemand,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#a78bfa'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '黑林水文站来水',
|
|
|
+ type: 'line',
|
|
|
+ data: this.demandTrendData.heilinInflow,
|
|
|
+ smooth: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#f97316'
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '小塔山水库供水',
|
|
|
+ type: 'line',
|
|
|
+ data: this.demandTrendData.xiaotaishanSupply,
|
|
|
+ smooth: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#22c55e'
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ this.demandTrendChart.setOption(option)
|
|
|
+ },
|
|
|
+ initDemandComparisonChart() {
|
|
|
+ if (document.getElementById('demandComparisonChart')) {
|
|
|
+ if (this.demandComparisonChart) {
|
|
|
+ this.demandComparisonChart.dispose()
|
|
|
+ }
|
|
|
+ this.demandComparisonChart = echarts.init(document.getElementById('demandComparisonChart'))
|
|
|
+ this.updateDemandComparisonChart()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ updateDemandComparisonChart() {
|
|
|
+ if (!this.demandComparisonChart) return
|
|
|
+ const { categories, current, lastYear, lastMonth } = this.demandComparisonData
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ animation: false,
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ },
|
|
|
+ formatter: (params) => {
|
|
|
+ let result = params[0].name + '<br/>'
|
|
|
+ params.forEach((param) => {
|
|
|
+ const dataValue = typeof param.data === 'object' ? param.data.value : param.data
|
|
|
+ let diff = 0
|
|
|
+ let diffPercent = 0
|
|
|
+ if (param.seriesName === '去年同期') {
|
|
|
+ diff = current[param.dataIndex] - lastYear[param.dataIndex]
|
|
|
+ diffPercent = ((diff / lastYear[param.dataIndex]) * 100).toFixed(1)
|
|
|
+ } else if (param.seriesName === '上月同期') {
|
|
|
+ diff = current[param.dataIndex] - lastMonth[param.dataIndex]
|
|
|
+ diffPercent = ((diff / lastMonth[param.dataIndex]) * 100).toFixed(1)
|
|
|
+ }
|
|
|
+ result += `${param.seriesName}: ${dataValue}万m³`
|
|
|
+ if (param.seriesName !== '当期') {
|
|
|
+ const sign = diff >= 0 ? '+' : ''
|
|
|
+ result += ` (${sign}${diff.toFixed(1)}万m³, ${sign}${diffPercent}%)`
|
|
|
+ }
|
|
|
+ result += '<br/>'
|
|
|
+ })
|
|
|
+ return result
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['当期', '去年同期', '上月同期'],
|
|
|
+ textStyle: {
|
|
|
+ color: '#e0fcff',
|
|
|
+ fontSize: 10
|
|
|
+ },
|
|
|
+ itemWidth: 10,
|
|
|
+ itemHeight: 10,
|
|
|
+ itemGap: 6,
|
|
|
+ bottom: '5%'
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: 20,
|
|
|
+ right: 15,
|
|
|
+ bottom: 45,
|
|
|
+ top: 20,
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: categories,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#7bbef6'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 10
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '水量(万m³)',
|
|
|
+ nameLocation: 'end',
|
|
|
+ nameGap: 5,
|
|
|
+ nameTextStyle: {
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 11,
|
|
|
+ align: 'left',
|
|
|
+ verticalAlign: 'bottom',
|
|
|
+ padding: [0, 0, 0, -15]
|
|
|
+ },
|
|
|
+ min: 0,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#7bbef6'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#7bbef6',
|
|
|
+ fontSize: 9
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(123, 190, 246, 0.2)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '当期',
|
|
|
+ type: 'bar',
|
|
|
+ data: current,
|
|
|
+ itemStyle: { color: '#38bdf8' },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'top',
|
|
|
+ color: '#e0fcff',
|
|
|
+ fontSize: 9,
|
|
|
+ formatter: (params) => {
|
|
|
+ return params.value.toFixed(1)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ barWidth: '20%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '去年同期',
|
|
|
+ type: 'bar',
|
|
|
+ data: lastYear,
|
|
|
+ itemStyle: { color: '#f97316' },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'top',
|
|
|
+ color: '#e0fcff',
|
|
|
+ fontSize: 8,
|
|
|
+ formatter: (params) => {
|
|
|
+ const diff = current[params.dataIndex] - lastYear[params.dataIndex]
|
|
|
+ const diffPercent = ((diff / lastYear[params.dataIndex]) * 100).toFixed(1)
|
|
|
+ const sign = diff >= 0 ? '+' : ''
|
|
|
+ return `${sign}${diff.toFixed(1)}\n${sign}${diffPercent}%`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ barWidth: '20%'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '上月同期',
|
|
|
+ type: 'bar',
|
|
|
+ data: lastMonth,
|
|
|
+ itemStyle: { color: '#22c55e' },
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: 'top',
|
|
|
+ color: '#e0fcff',
|
|
|
+ fontSize: 8,
|
|
|
+ formatter: (params) => {
|
|
|
+ const diff = current[params.dataIndex] - lastMonth[params.dataIndex]
|
|
|
+ const diffPercent = ((diff / lastMonth[params.dataIndex]) * 100).toFixed(1)
|
|
|
+ const sign = diff >= 0 ? '+' : ''
|
|
|
+ return `${sign}${diff.toFixed(1)}\n${sign}${diffPercent}%`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ barWidth: '20%'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ this.demandComparisonChart.setOption(option)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -520,4 +1061,305 @@ export default {
|
|
|
font-size: 14px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
+
|
|
|
+.date-range-filter {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.date-display {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 4px 8px;
|
|
|
+ background: rgba(0, 20, 40, 0.5);
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.2);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #e0fcff;
|
|
|
+ font-size: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.date-display:hover {
|
|
|
+ border-color: rgba(0, 212, 255, 0.5);
|
|
|
+ background: rgba(0, 20, 40, 0.7);
|
|
|
+}
|
|
|
+
|
|
|
+.date-text {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.calendar-icon {
|
|
|
+ font-size: 14px;
|
|
|
+ margin-left: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.date-picker-popup {
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ left: 0;
|
|
|
+ margin-top: 8px;
|
|
|
+ background: rgba(0, 30, 60, 0.95);
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.3);
|
|
|
+ border-radius: 6px;
|
|
|
+ z-index: 100;
|
|
|
+ width: 100%;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 212, 255, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.date-picker-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-bottom: 1px solid rgba(0, 212, 255, 0.2);
|
|
|
+ color: #e0fcff;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-btn {
|
|
|
+ background: none;
|
|
|
+ border: none;
|
|
|
+ color: #7bbef6;
|
|
|
+ font-size: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-btn:hover {
|
|
|
+ background: rgba(0, 212, 255, 0.2);
|
|
|
+ color: #e0fcff;
|
|
|
+}
|
|
|
+
|
|
|
+.current-month {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn {
|
|
|
+ background: none;
|
|
|
+ border: none;
|
|
|
+ color: #7bbef6;
|
|
|
+ font-size: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.close-btn:hover {
|
|
|
+ color: #e0fcff;
|
|
|
+}
|
|
|
+
|
|
|
+.calendar-container {
|
|
|
+ padding: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.weekdays {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(7, 1fr);
|
|
|
+ gap: 4px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.weekday {
|
|
|
+ text-align: center;
|
|
|
+ color: #7bbef6;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ padding: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.days {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(7, 1fr);
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.day {
|
|
|
+ text-align: center;
|
|
|
+ padding: 8px 4px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #e0fcff;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.day:hover:not(.empty) {
|
|
|
+ background: rgba(0, 212, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.day.empty {
|
|
|
+ cursor: default;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+.day.today {
|
|
|
+ color: #ffd93d;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.day.in-range {
|
|
|
+ background: rgba(0, 212, 255, 0.15);
|
|
|
+ border-radius: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.day.start {
|
|
|
+ background: rgba(0, 212, 255, 0.3);
|
|
|
+ border-radius: 4px 0 0 4px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.day.end {
|
|
|
+ background: rgba(0, 212, 255, 0.3);
|
|
|
+ border-radius: 0 4px 4px 0;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.day.start.end {
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.date-picker-footer {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-top: 1px solid rgba(0, 212, 255, 0.2);
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.confirm-btn {
|
|
|
+ padding: 8px 20px;
|
|
|
+ background: rgba(0, 212, 255, 0.2);
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.5);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #62f6fb;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.confirm-btn:hover {
|
|
|
+ background: rgba(0, 212, 255, 0.3);
|
|
|
+ color: #e0fcff;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-cards {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-cards-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card {
|
|
|
+ flex: 1;
|
|
|
+ padding: 8px;
|
|
|
+ background: rgba(0, 20, 40, 0.5);
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.2);
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-label {
|
|
|
+ color: #7bbef6;
|
|
|
+ font-size: 11px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-value {
|
|
|
+ color: #e0fcff;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-change {
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-change.up {
|
|
|
+ color: #ff6b6b;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-change.down {
|
|
|
+ color: #4ade80;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-value.surplus {
|
|
|
+ color: #4ade80;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-value.deficit {
|
|
|
+ color: #ff6b6b;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-status {
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-status.surplus {
|
|
|
+ color: #4ade80;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-card .card-status.deficit {
|
|
|
+ color: #ff6b6b;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-trend-card {
|
|
|
+ padding: 8px;
|
|
|
+ background: rgba(0, 20, 40, 0.5);
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.demand-trend-card .chart-label {
|
|
|
+ color: #e0fcff;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ text-align: left;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-trend-card .chart-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-comparison-card {
|
|
|
+ padding: 8px;
|
|
|
+ background: rgba(0, 20, 40, 0.5);
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid rgba(0, 212, 255, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.demand-comparison-card .chart-label {
|
|
|
+ color: #e0fcff;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ text-align: left;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.demand-comparison-card .chart-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
</style>
|