|
|
@@ -1,162 +1,464 @@
|
|
|
<template>
|
|
|
<div class="syax-page" v-loading="loading">
|
|
|
- <!-- 标题 -->
|
|
|
- <div class="page-header">
|
|
|
- <h1>水域岸线功能区统计</h1>
|
|
|
- </div>
|
|
|
-
|
|
|
<!-- 数据总览卡片 -->
|
|
|
<div class="overview-cards">
|
|
|
- <div class="card" v-for="rv in overview" :key="rv.name">
|
|
|
- <div class="card-name">{{ rv.name }}</div>
|
|
|
- <div class="card-value">{{ rv.total }} <span class="card-unit">km</span></div>
|
|
|
- <div class="card-sub">{{ rv.zones }} 个功能区</div>
|
|
|
+ <div class="card" v-for="(rv, idx) in overview" :key="rv.name">
|
|
|
+ <div class="card-icon" :class="'icon-' + idx">
|
|
|
+ <svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor">
|
|
|
+ <path
|
|
|
+ d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="card-info">
|
|
|
+ <div class="card-name">{{ rv.name }}</div>
|
|
|
+ <div class="card-stats">
|
|
|
+ <div class="stat-item">
|
|
|
+ <span class="stat-value">{{ rv.total }}</span>
|
|
|
+ <span class="stat-unit">km</span>
|
|
|
+ </div>
|
|
|
+ <div class="stat-divider"></div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <span class="stat-value">{{ rv.zones }}</span>
|
|
|
+ <span class="stat-unit">个功能区</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 详细表格 + 图表区 -->
|
|
|
- <div class="content-grid">
|
|
|
- <!-- 表格 -->
|
|
|
- <div class="grid-left">
|
|
|
- <el-card shadow="never">
|
|
|
- <template #header>岸线数据明细</template>
|
|
|
- <el-table :data="tableData" border size="small" :span-method="spanMethod">
|
|
|
- <el-table-column label="河(湖)名称" prop="river" width="90" />
|
|
|
- <el-table-column label="总长度(km)" prop="lineLeng" width="100" />
|
|
|
- <el-table-column label="功能区" prop="zone" width="100" />
|
|
|
- <el-table-column label="功能区长度(km)" prop="zoneLeng" />
|
|
|
- <el-table-column label="功能区数量" prop="zoneCount" width="100" />
|
|
|
+ <!-- 内容区域:表格 + 图表 -->
|
|
|
+ <div class="content-area">
|
|
|
+ <!-- 左侧表格 -->
|
|
|
+ <div class="table-section">
|
|
|
+ <div class="section-title">岸线数据明细</div>
|
|
|
+ <div class="table-body">
|
|
|
+ <el-table
|
|
|
+ :data="tableData"
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ :span-method="spanMethod"
|
|
|
+ :header-cell-style="tableHeaderStyle"
|
|
|
+ height="100%"
|
|
|
+ >
|
|
|
+ <el-table-column
|
|
|
+ label="河(湖)名称"
|
|
|
+ prop="river"
|
|
|
+ width="100"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ label="总长度(km)"
|
|
|
+ prop="lineLeng"
|
|
|
+ width="110"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ label="功能区"
|
|
|
+ prop="zone"
|
|
|
+ width="110"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ label="功能区长度(km)"
|
|
|
+ prop="zoneLeng"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
+ <el-table-column
|
|
|
+ label="功能区数量"
|
|
|
+ prop="zoneCount"
|
|
|
+ width="110"
|
|
|
+ align="center"
|
|
|
+ />
|
|
|
</el-table>
|
|
|
- </el-card>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 图表 -->
|
|
|
- <div class="grid-right">
|
|
|
- <el-card shadow="never">
|
|
|
- <template #header>太湖 — 功能区分布</template>
|
|
|
- <div class="chart-row"><div ref="thBar" class="chart-box" /><div ref="thPie" class="chart-box" /></div>
|
|
|
- </el-card>
|
|
|
- <el-card shadow="never">
|
|
|
- <template #header>望虞河 — 功能区分布</template>
|
|
|
- <div class="chart-row"><div ref="wyBar" class="chart-box" /><div ref="wyPie" class="chart-box" /></div>
|
|
|
- </el-card>
|
|
|
- <el-card shadow="never">
|
|
|
- <template #header>太浦河 — 功能区分布</template>
|
|
|
- <div class="chart-row"><div ref="tpBar" class="chart-box" /><div ref="tpPie" class="chart-box" /></div>
|
|
|
- </el-card>
|
|
|
+ <!-- 右侧图表 -->
|
|
|
+ <div class="chart-area">
|
|
|
+ <div class="chart-card" v-for="rv in rivers" :key="rv.key">
|
|
|
+ <div class="chart-title">{{ rv.name }} 功能区分布</div>
|
|
|
+ <div class="chart-row">
|
|
|
+ <div class="chart-panel">
|
|
|
+ <div class="chart-label">柱状图</div>
|
|
|
+ <div
|
|
|
+ :ref="(el) => (refMap[rv.key + 'Bar'] = el)"
|
|
|
+ class="chart-box"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="chart-panel">
|
|
|
+ <div class="chart-label">饼图</div>
|
|
|
+ <div
|
|
|
+ :ref="(el) => (refMap[rv.key + 'Pie'] = el)"
|
|
|
+ class="chart-box"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, onMounted, nextTick } from 'vue'
|
|
|
-import { getShorelineStats } from '@/api/hzz/shoreline'
|
|
|
-import * as echarts from 'echarts'
|
|
|
+import { ref, onMounted, nextTick, reactive } from "vue";
|
|
|
+import { getShorelineStats } from "@/api/hzz/shoreline";
|
|
|
+import * as echarts from "echarts";
|
|
|
|
|
|
-const loading = ref(false)
|
|
|
-const rawData = ref([])
|
|
|
-const thBar = ref(null), thPie = ref(null), wyBar = ref(null), wyPie = ref(null), tpBar = ref(null), tpPie = ref(null)
|
|
|
-const refMap = { thBar, thPie, wyBar, wyPie, tpBar, tpPie }
|
|
|
+const loading = ref(false);
|
|
|
+const rawData = ref([]);
|
|
|
+const refMap = reactive({});
|
|
|
+const tableHeaderStyle = {
|
|
|
+ background: "#f5f7fa",
|
|
|
+ color: "#333",
|
|
|
+ fontWeight: "600",
|
|
|
+ fontSize: "13px",
|
|
|
+ textAlign: "center",
|
|
|
+};
|
|
|
|
|
|
-const zones = ['保护区', '保留区', '控制利用区']
|
|
|
-const tableData = ref([])
|
|
|
-const overview = ref([])
|
|
|
+const zones = ["保护区", "保留区", "控制利用区"];
|
|
|
+const tableData = ref([]);
|
|
|
+const overview = ref([]);
|
|
|
|
|
|
const spanMethod = ({ columnIndex, rowIndex }) => {
|
|
|
if (columnIndex <= 1) {
|
|
|
- const rows = tableData.value
|
|
|
- for (let i = rowIndex - 1; i >= 0; i--) { if (rows[i].river === rows[rowIndex].river) return { rowspan: 0, colspan: 1 } }
|
|
|
- let cnt = 1; for (let i = rowIndex + 1; i < rows.length && rows[i].river === rows[rowIndex].river; i++) cnt++
|
|
|
- return { rowspan: cnt, colspan: 1 }
|
|
|
+ const rows = tableData.value;
|
|
|
+ for (let i = rowIndex - 1; i >= 0; i--) {
|
|
|
+ if (rows[i].river === rows[rowIndex].river)
|
|
|
+ return { rowspan: 0, colspan: 1 };
|
|
|
+ }
|
|
|
+ let cnt = 1;
|
|
|
+ for (
|
|
|
+ let i = rowIndex + 1;
|
|
|
+ i < rows.length && rows[i].river === rows[rowIndex].river;
|
|
|
+ i++
|
|
|
+ )
|
|
|
+ cnt++;
|
|
|
+ return { rowspan: cnt, colspan: 1 };
|
|
|
}
|
|
|
-}
|
|
|
+};
|
|
|
|
|
|
const rivers = [
|
|
|
- { name: '太湖', key: 'th', indices: [0,1,2], chartRefs: { bar: 'thBar', pie: 'thPie' } },
|
|
|
- { name: '望虞河', key: 'wy', indices: [3,4,5], chartRefs: { bar: 'wyBar', pie: 'wyPie' } },
|
|
|
- { name: '太浦河', key: 'tp', indices: [6,8], chartRefs: { bar: 'tpBar', pie: 'tpPie' } },
|
|
|
-]
|
|
|
+ { name: "太湖", key: "th", indices: [0, 1, 2] },
|
|
|
+ { name: "望虞河", key: "wy", indices: [3, 4, 5] },
|
|
|
+ { name: "太浦河", key: "tp", indices: [6, 8] },
|
|
|
+];
|
|
|
|
|
|
const buildTable = () => {
|
|
|
- const d = rawData.value
|
|
|
+ const d = rawData.value;
|
|
|
tableData.value = [
|
|
|
- { river: '太湖', lineLeng: d[0]?.lineLeng || '-', zone: '保护区', zoneLeng: d[0]?.lineGn || '-', zoneCount: d[0]?.count || '-' },
|
|
|
- { river: '太湖', lineLeng: '', zone: '保留区', zoneLeng: d[1]?.lineGn || '-', zoneCount: d[1]?.count || '-' },
|
|
|
- { river: '太湖', lineLeng: '', zone: '控制利用区', zoneLeng: d[2]?.lineGn || '-', zoneCount: d[2]?.count || '-' },
|
|
|
- { river: '望虞河', lineLeng: d[3]?.lineLeng || '-', zone: '保护区', zoneLeng: d[3]?.lineGn || '-', zoneCount: d[3]?.count || '-' },
|
|
|
- { river: '望虞河', lineLeng: '', zone: '保留区', zoneLeng: d[4]?.lineGn || '-', zoneCount: d[4]?.count || '-' },
|
|
|
- { river: '望虞河', lineLeng: '', zone: '控制利用区', zoneLeng: d[5]?.lineGn || '-', zoneCount: d[5]?.count || '-' },
|
|
|
- { river: '太浦河', lineLeng: d[6]?.lineLeng || '-', zone: '保护区', zoneLeng: d[6]?.lineGn || '-', zoneCount: d[6]?.count || '-' },
|
|
|
- { river: '太浦河', lineLeng: '', zone: '控制利用区', zoneLeng: d[8]?.lineGn || '-', zoneCount: d[8]?.count || '-' },
|
|
|
- ]
|
|
|
+ {
|
|
|
+ river: "太湖",
|
|
|
+ lineLeng: d[0]?.lineLeng || "-",
|
|
|
+ zone: "保护区",
|
|
|
+ zoneLeng: d[0]?.lineGn || "-",
|
|
|
+ zoneCount: d[0]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "太湖",
|
|
|
+ lineLeng: "",
|
|
|
+ zone: "保留区",
|
|
|
+ zoneLeng: d[1]?.lineGn || "-",
|
|
|
+ zoneCount: d[1]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "太湖",
|
|
|
+ lineLeng: "",
|
|
|
+ zone: "控制利用区",
|
|
|
+ zoneLeng: d[2]?.lineGn || "-",
|
|
|
+ zoneCount: d[2]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "望虞河",
|
|
|
+ lineLeng: d[3]?.lineLeng || "-",
|
|
|
+ zone: "保护区",
|
|
|
+ zoneLeng: d[3]?.lineGn || "-",
|
|
|
+ zoneCount: d[3]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "望虞河",
|
|
|
+ lineLeng: "",
|
|
|
+ zone: "保留区",
|
|
|
+ zoneLeng: d[4]?.lineGn || "-",
|
|
|
+ zoneCount: d[4]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "望虞河",
|
|
|
+ lineLeng: "",
|
|
|
+ zone: "控制利用区",
|
|
|
+ zoneLeng: d[5]?.lineGn || "-",
|
|
|
+ zoneCount: d[5]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "太浦河",
|
|
|
+ lineLeng: d[6]?.lineLeng || "-",
|
|
|
+ zone: "保护区",
|
|
|
+ zoneLeng: d[6]?.lineGn || "-",
|
|
|
+ zoneCount: d[6]?.count || "-",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ river: "太浦河",
|
|
|
+ lineLeng: "",
|
|
|
+ zone: "控制利用区",
|
|
|
+ zoneLeng: d[8]?.lineGn || "-",
|
|
|
+ zoneCount: d[8]?.count || "-",
|
|
|
+ },
|
|
|
+ ];
|
|
|
overview.value = [
|
|
|
- { name: '太湖', total: d[0]?.lineLeng || '-', zones: 3 },
|
|
|
- { name: '望虞河', total: d[3]?.lineLeng || '-', zones: 3 },
|
|
|
- { name: '太浦河', total: d[6]?.lineLeng || '-', zones: 2 },
|
|
|
- ]
|
|
|
-}
|
|
|
+ { name: "太湖", total: d[0]?.lineLeng || "-", zones: 3 },
|
|
|
+ { name: "望虞河", total: d[3]?.lineLeng || "-", zones: 3 },
|
|
|
+ { name: "太浦河", total: d[6]?.lineLeng || "-", zones: 2 },
|
|
|
+ ];
|
|
|
+};
|
|
|
|
|
|
const renderCharts = () => {
|
|
|
- const d = rawData.value
|
|
|
+ const d = rawData.value;
|
|
|
nextTick(() => {
|
|
|
- rivers.forEach(rv => {
|
|
|
- const vals = rv.indices.map(i => Number(d[i]?.lineGn || 0))
|
|
|
- const names = rv.indices.map(i => zones[i === 8 ? 2 : i === 6 && rv.key === 'tp' ? 0 : rv.indices.indexOf(i)])
|
|
|
- const labels = rv.key === 'tp' ? ['保护区', '控制利用区'] : zones
|
|
|
-
|
|
|
- const makeChart = (r, type) => {
|
|
|
- const el = refMap[rv.key + (type === 'bar' ? 'Bar' : 'Pie')].value
|
|
|
- if (!el) return
|
|
|
- const c = echarts.init(el)
|
|
|
- c.setOption(type === 'bar' ? {
|
|
|
- grid: { top: 20, bottom: 30, left: 40, right: 20 },
|
|
|
- xAxis: { type: 'category', data: labels }, yAxis: { type: 'value', name: 'km' },
|
|
|
- series: [{ type: 'bar', data: vals, itemStyle: { color: '#409EFF' }, barMaxWidth: 50 }]
|
|
|
- } : {
|
|
|
- series: [{ type: 'pie', radius: ['45%','70%'], center: ['50%','55%'], data: labels.map((n,i)=>({name:n,value:vals[i]})), label: { formatter: '{b}\n{d}%', fontSize: 12 } }]
|
|
|
- })
|
|
|
- window.addEventListener('resize', () => c.resize())
|
|
|
+ rivers.forEach((rv) => {
|
|
|
+ const vals = rv.indices.map((i) => Number(d[i]?.lineGn || 0));
|
|
|
+ const labels = rv.key === "tp" ? ["保护区", "控制利用区"] : zones;
|
|
|
+ const colors = ["#409EFF", "#67C23A", "#E6A23C"];
|
|
|
+
|
|
|
+ const barEl = refMap[rv.key + "Bar"];
|
|
|
+ if (barEl) {
|
|
|
+ const barChart = echarts.init(barEl);
|
|
|
+ barChart.setOption({
|
|
|
+ tooltip: { trigger: "axis" },
|
|
|
+ grid: { top: 10, bottom: 20, left: 42, right: 12 },
|
|
|
+ xAxis: {
|
|
|
+ type: "category",
|
|
|
+ data: labels,
|
|
|
+ axisLabel: { fontSize: 10 },
|
|
|
+ },
|
|
|
+ yAxis: { type: "value", name: "km", nameTextStyle: { fontSize: 10 } },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "bar",
|
|
|
+ data: vals.map((v, i) => ({
|
|
|
+ value: v,
|
|
|
+ itemStyle: { color: colors[i], borderRadius: [3, 3, 0, 0] },
|
|
|
+ })),
|
|
|
+ barMaxWidth: 35,
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ position: "top",
|
|
|
+ fontSize: 10,
|
|
|
+ color: "#666",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ window.addEventListener("resize", () => barChart.resize());
|
|
|
}
|
|
|
- makeChart(rv, 'bar')
|
|
|
- makeChart(rv, 'pie')
|
|
|
- })
|
|
|
- })
|
|
|
-}
|
|
|
+
|
|
|
+ const pieEl = refMap[rv.key + "Pie"];
|
|
|
+ if (pieEl) {
|
|
|
+ const pieChart = echarts.init(pieEl);
|
|
|
+ pieChart.setOption({
|
|
|
+ tooltip: { trigger: "item" },
|
|
|
+ legend: {
|
|
|
+ bottom: 2,
|
|
|
+ textStyle: { fontSize: 10 },
|
|
|
+ itemWidth: 8,
|
|
|
+ itemHeight: 8,
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: "pie",
|
|
|
+ radius: ["52%", "72%"],
|
|
|
+ center: ["50%", "43%"],
|
|
|
+ data: labels.map((n, i) => ({
|
|
|
+ name: n,
|
|
|
+ value: vals[i],
|
|
|
+ itemStyle: { color: colors[i] },
|
|
|
+ })),
|
|
|
+ label: { formatter: "{b}\n{d}%", fontSize: 10 },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ window.addEventListener("resize", () => pieChart.resize());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
onMounted(async () => {
|
|
|
- loading.value = true
|
|
|
+ loading.value = true;
|
|
|
try {
|
|
|
- const res = await getShorelineStats()
|
|
|
- rawData.value = res.data || []
|
|
|
- buildTable()
|
|
|
- renderCharts()
|
|
|
- } finally { loading.value = false }
|
|
|
-})
|
|
|
+ const res = await getShorelineStats();
|
|
|
+ rawData.value = res.data || [];
|
|
|
+ buildTable();
|
|
|
+ renderCharts();
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.syax-page { padding: 20px 24px; overflow-y: auto; height: calc(100vh - 84px); background: #f0f2f5; }
|
|
|
-.page-header { margin-bottom: 20px; }
|
|
|
-.page-header h1 { font-size: 20px; color: #1a1a2e; font-weight: 600; margin: 0; }
|
|
|
-.page-header h1::before { content: ''; display: inline-block; width: 4px; height: 20px; background: #409EFF; margin-right: 10px; vertical-align: middle; border-radius: 2px; }
|
|
|
+.syax-page {
|
|
|
+ height: 100%;
|
|
|
+ background: #f0f2f5;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 12px;
|
|
|
+ gap: 10px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
|
|
|
/* 总览卡片 */
|
|
|
-.overview-cards { display: flex; gap: 16px; margin-bottom: 20px; }
|
|
|
-.card { flex: 1; background: #fff; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 1px 4px rgba(0,0,0,.06); }
|
|
|
-.card-name { font-size: 14px; color: #666; margin-bottom: 8px; }
|
|
|
-.card-value { font-size: 28px; font-weight: 700; color: #0d4b80; }
|
|
|
-.card-unit { font-size: 13px; font-weight: 400; color: #999; }
|
|
|
-.card-sub { font-size: 12px; color: #999; margin-top: 4px; }
|
|
|
-
|
|
|
-/* 内容区 */
|
|
|
-.content-grid { display: flex; gap: 16px; align-items: flex-start; }
|
|
|
-.grid-left { width: 520px; flex-shrink: 0; }
|
|
|
-.grid-right { flex: 1; display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
|
-
|
|
|
-.chart-row { display: flex; gap: 12px; }
|
|
|
-.chart-box { flex: 1; height: 240px; }
|
|
|
-
|
|
|
-:deep(.el-card__header) { font-size: 14px; font-weight: 600; color: #333; padding: 12px 16px; }
|
|
|
-:deep(.el-card__body) { padding: 12px 16px; }
|
|
|
+.overview-cards {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ gap: 10px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 14px 18px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+.card-icon {
|
|
|
+ width: 38px;
|
|
|
+ height: 38px;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.icon-0 {
|
|
|
+ background: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+.icon-1 {
|
|
|
+ background: #f0f9eb;
|
|
|
+ color: #67c23a;
|
|
|
+}
|
|
|
+.icon-2 {
|
|
|
+ background: #fdf6ec;
|
|
|
+ color: #e6a23c;
|
|
|
+}
|
|
|
+
|
|
|
+.card-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.card-name {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+.card-stats {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+.stat-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 2px;
|
|
|
+}
|
|
|
+.stat-value {
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #0d4b80;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+.stat-unit {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+.stat-divider {
|
|
|
+ width: 1px;
|
|
|
+ height: 18px;
|
|
|
+ background: #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 内容区域 */
|
|
|
+.content-area {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧表格 */
|
|
|
+.table-section {
|
|
|
+ width: 540px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.section-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ padding: 12px 16px 0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.table-body {
|
|
|
+ flex: 1;
|
|
|
+ padding: 10px 12px 12px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧图表 */
|
|
|
+.chart-area {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.chart-card {
|
|
|
+ flex: 1;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.chart-title {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ padding: 10px 14px 0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.chart-row {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 12px 10px;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+.chart-panel {
|
|
|
+ flex: 1;
|
|
|
+ background: #fafbfc;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 6px 6px 2px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.chart-label {
|
|
|
+ font-size: 10px;
|
|
|
+ color: #bbb;
|
|
|
+ text-align: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.chart-box {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
</style>
|