| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- <template>
- <div class="metal-tables-container">
- <!-- 灌溉水表格 -->
- <div class="table-section">
- <el-row :gutter="10" align="middle">
- <el-col :span="20">
- <div class="title">灌溉水采样点各重金属平均值</div>
- </el-col>
- <el-col :span="4" style="text-align: right;">
- <div class="sample-count" v-if="waterValidSamples > 0">
- 有效样本:{{ waterValidSamples }}个
- </div>
- </el-col>
- </el-row>
-
- <el-table
- v-if="waterTableData.length && !waterLoading && !waterError"
- :data="waterTableData"
- border
- style="width: 100%"
- >
- <el-table-column label="指标" prop="indicator" width="80" />
- <el-table-column
- v-for="(metal, field) in waterMetals"
- :key="field"
- :label="metal.label"
-
- >
- <template #default="scope">
- {{ scope.row[field] }}
- </template>
- </el-table-column>
- </el-table>
-
- <div v-else class="empty-state" v-if="!waterLoading && !waterError">
- <el-empty description="灌溉水数据为空,请刷新" />
- </div>
- <el-alert
- v-if="waterError"
- type="error"
- :description="waterError"
- show-icon
- class="error-alert"
- />
- </div>
- <!-- 断面数据表格 -->
- <div class="table-section">
- <el-row :gutter="10" align="middle">
- <el-col :span="20">
- <div class="title">断面采样点镉含量平均值</div>
- </el-col>
- <el-col :span="4" style="text-align: right;">
- <div class="sample-count" v-if="sectionValidSamples > 0">
- 有效样本:{{ sectionValidSamples }}个
- </div>
- </el-col>
- </el-row>
-
- <el-table
- v-if="sectionTableData.length && !sectionLoading && !sectionError"
- :data="sectionTableData"
- border
- style="width: 100%"
- >
- <el-table-column label="指标" prop="indicator" width="80" />
- <el-table-column
- v-for="(metal, field) in sectionMetals"
- :key="field"
- :label="metal.label"
-
- >
- <template #default="scope">
- {{ scope.row[field] }}
- </template>
- </el-table-column>
- </el-table>
-
- <div v-else class="empty-state" v-if="!sectionLoading && !sectionError">
- <el-empty description="断面数据为空,请刷新" />
- </div>
- <el-alert
- v-if="sectionError"
- type="error"
- :description="sectionError"
- show-icon
- class="error-alert"
- />
- </div>
- <!-- 大气企业表格 -->
- <div class="table-section">
- <el-row :gutter="10" align="middle">
- <el-col :span="20">
- <div class="title">大气企业颗粒物排放平均值</div>
- </el-col>
- <el-col :span="4" style="text-align: right;">
- <div class="sample-count" v-if="atmoCompanyValidSamples > 0">
- 有效样本:{{ atmoCompanyValidSamples }}个
- </div>
- </el-col>
- </el-row>
-
- <el-table
- v-if="atmoCompanyTableData.length && !atmoCompanyLoading && !atmoCompanyError"
- :data="atmoCompanyTableData"
- border
- style="width: 100%"
- >
- <el-table-column label="指标" prop="indicator" width="80" />
- <el-table-column
- v-for="(metric, field) in atmoCompanyMetrics"
- :key="field"
- :label="metric.label"
-
- >
- <template #default="scope">
- {{ scope.row[field]}}
- </template>
- </el-table-column>
- </el-table>
-
- <div v-else class="empty-state" v-if="!atmoCompanyLoading && !atmoCompanyError">
- <el-empty description="大气企业数据为空,请刷新" />
- </div>
- <el-alert
- v-if="atmoCompanyError"
- type="error"
- :description="atmoCompanyError"
- show-icon
- class="error-alert"
- />
- </div>
- <!-- 大气样本表格 -->
- <div class="table-section">
- <el-row :gutter="10" align="middle">
- <el-col :span="20">
- <div class="title">大气样本重金属平均值</div>
- </el-col>
- <el-col :span="4" style="text-align: right;">
- <div class="sample-count" v-if="atmoSampleValidSamples > 0">
- 有效样本:{{ atmoSampleValidSamples }}个
- </div>
- </el-col>
- </el-row>
-
- <el-table
- v-if="atmoSampleTableData.length && !atmoSampleLoading && !atmoSampleError"
- :data="atmoSampleTableData"
- border
- style="width: 100%"
- >
- <el-table-column label="指标" prop="indicator" width="80" />
- <el-table-column
- v-for="(metric, field) in atmoSampleMetrics"
- :key="field"
- :label="metric.label"
- :width="getColWidth(field)"
- >
- <template #default="scope">
- {{ scope.row[field] }}
- </template>
- </el-table-column>
- </el-table>
-
- <div v-else class="empty-state" v-if="!atmoSampleLoading && !atmoSampleError">
- <el-empty description="大气样本数据为空,请刷新" />
- </div>
- <el-alert
- v-if="atmoSampleError"
- type="error"
- :description="atmoSampleError"
- show-icon
- class="error-alert"
- />
- </div>
- <!-- 统一刷新按钮 -->
- <div class="refresh-btn">
- <el-button
- type="primary"
- @click="refreshAll"
- :loading="waterLoading || sectionLoading || atmoCompanyLoading || atmoSampleLoading"
- icon="el-icon-refresh"
- >
- 刷新所有数据
- </el-button>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted } from 'vue';
- import axios from 'axios';
- // ====================== 通用工具类型 ======================
- type MetricsMap<T extends string> = { [K in T]: { label: string } };
- type TableRow<T extends string> = { indicator: string } & { [K in T]: number };
- // ====================== 灌溉水模块(严格类型) ======================
- const waterMetals = {
- cr_concentration: { label: '铬含量(ug/L)' },
- as_concentration: { label: '砷含量(ug/L)' },
- cd_concentration: { label: '镉含量(ug/L)' },
- hg_concentration: { label: '汞含量(ug/L)' },
- pb_concentration: { label: '铅含量(ug/L)' },
- };
- type WaterMetalKey = keyof typeof waterMetals;
- type WaterRow = TableRow<WaterMetalKey>;
- const waterLoading = ref(false);
- const waterError = ref('');
- const waterData = ref<{ [K in WaterMetalKey]?: number | string }[]>([]);
- const waterTableData = ref<WaterRow[]>([]);
- const waterValidSamples = ref(0); // 有效样本数
- const calculateWaterAverage = () => {
- const stats: Record<WaterMetalKey, { sum: number; count: number }> = {} as any;
- (Object.keys(waterMetals) as WaterMetalKey[]).forEach(key => {
- stats[key] = { sum: 0, count: 0 };
- });
- let validSamples = 0;
- waterData.value.forEach(item => {
- let hasValidMetric = false;
- (Object.keys(waterMetals) as WaterMetalKey[]).forEach(key => {
- const value = Number(item[key]);
- if (!isNaN(value)) {
- stats[key].sum += value;
- stats[key].count += 1;
- hasValidMetric = true;
- }
- });
- if (hasValidMetric) validSamples++;
- });
- waterTableData.value = [(Object.keys(waterMetals) as WaterMetalKey[]).reduce((row, key) => {
- row.indicator = '平均值';
- row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
- return row;
- }, {} as WaterRow)];
- waterValidSamples.value = validSamples;
- };
- const fetchWaterData = async () => {
- try {
- waterLoading.value = true;
- waterError.value = '';
- const res = await axios.get(
- 'http://localhost:8000/api/vector/export/all?table_name=water_sampling_data'
- );
- waterData.value = res.data.features.map((f: { properties: any }) => f.properties);
- calculateWaterAverage();
- } catch (err: any) {
- waterError.value = err.message;
- waterTableData.value = [];
- } finally {
- waterLoading.value = false;
- }
- };
- // ====================== 断面模块(严格类型) ======================
- const sectionMetals = { cd_concentration: { label: '镉含量(ug/L)' } };
- type SectionMetalKey = keyof typeof sectionMetals;
- type SectionRow = TableRow<SectionMetalKey>;
- const sectionLoading = ref(false);
- const sectionError = ref('');
- const sectionData = ref<{ [K in SectionMetalKey]?: number | string }[]>([]);
- const sectionTableData = ref<SectionRow[]>([]);
- const sectionValidSamples = ref(0); // 有效样本数
- const calculateSectionAverage = () => {
- const stats: Record<SectionMetalKey, { sum: number; count: number }> = {} as any;
- (Object.keys(sectionMetals) as SectionMetalKey[]).forEach(key => {
- stats[key] = { sum: 0, count: 0 };
- });
- let validSamples = 0;
- sectionData.value.forEach(item => {
- let hasValidMetric = false;
- (Object.keys(sectionMetals) as SectionMetalKey[]).forEach(key => {
- const value = Number(item[key]);
- if (!isNaN(value)) {
- stats[key].sum += value;
- stats[key].count += 1;
- hasValidMetric = true;
- }
- });
- if (hasValidMetric) validSamples++;
- });
- sectionTableData.value = [(Object.keys(sectionMetals) as SectionMetalKey[]).reduce((row, key) => {
- row.indicator = '平均值';
- row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
- return row;
- }, {} as SectionRow)];
- sectionValidSamples.value = validSamples;
- };
- const fetchSectionData = async () => {
- try {
- sectionLoading.value = true;
- sectionError.value = '';
- const res = await fetch('http://localhost:8000/api/vector/export/all?table_name=cross_section');
- if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
- let rawText = await res.text();
- rawText = rawText.replace(/:\s*NaN/g, ': null'); // 修复 NaN
- const geoJSON = JSON.parse(rawText);
- sectionData.value = geoJSON.features.map((f: { properties: any }) => f.properties);
- calculateSectionAverage();
- } catch (err: any) {
- sectionError.value = err.message;
- sectionTableData.value = [];
- } finally {
- sectionLoading.value = false;
- }
- };
- // ====================== 大气企业模块(严格类型) ======================
- const atmoCompanyMetrics = {
- particulate_emission: { label: '颗粒物排放量' },
- };
- type AtmoCompanyKey = keyof typeof atmoCompanyMetrics;
- type AtmoCompanyRow = TableRow<AtmoCompanyKey>;
- const atmoCompanyLoading = ref(false);
- const atmoCompanyError = ref('');
- const atmoCompanyData = ref<{ [K in AtmoCompanyKey]?: number | string }[]>([]);
- const atmoCompanyTableData = ref<AtmoCompanyRow[]>([]);
- const atmoCompanyValidSamples = ref(0); // 有效样本数
- const calculateAtmoCompanyAverage = () => {
- const stats: Record<AtmoCompanyKey, { sum: number; count: number }> = {} as any;
- (Object.keys(atmoCompanyMetrics) as AtmoCompanyKey[]).forEach(key => {
- stats[key] = { sum: 0, count: 0 };
- });
- let validSamples = 0;
- atmoCompanyData.value.forEach(item => {
- let hasValidMetric = false;
- (Object.keys(atmoCompanyMetrics) as AtmoCompanyKey[]).forEach(key => {
- const value = Number(item[key]);
- if (!isNaN(value)) {
- stats[key].sum += value;
- stats[key].count += 1;
- hasValidMetric = true;
- }
- });
- if (hasValidMetric) validSamples++;
- });
- atmoCompanyTableData.value = [(Object.keys(atmoCompanyMetrics) as AtmoCompanyKey[]).reduce((row, key) => {
- row.indicator = '平均值';
- row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
- return row;
- }, {} as AtmoCompanyRow)];
- atmoCompanyValidSamples.value = validSamples;
- };
- const fetchAtmoCompanyData = async () => {
- try {
- atmoCompanyLoading.value = true;
- atmoCompanyError.value = '';
- const res = await fetch('http://localhost:8000/api/vector/export/all?table_name=atmo_company');
- if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
- let rawText = await res.text();
- rawText = rawText.replace(/:\s*NaN/g, ': null'); // 修复 NaN
- const geoJSON = JSON.parse(rawText);
- atmoCompanyData.value = geoJSON.features.map((f: { properties: any }) => f.properties);
- calculateAtmoCompanyAverage();
- } catch (err: any) {
- atmoCompanyError.value = err.message;
- atmoCompanyTableData.value = [];
- } finally {
- atmoCompanyLoading.value = false;
- }
- };
- // ====================== 大气样本模块(严格类型) ======================
- const weightColumns = [
- { key: 'Cr_particulate', label: 'Cr mg/kg' },
- { key: 'As_particulate', label: 'As mg/kg' },
- { key: 'Cd_particulate', label: 'Cd mg/kg' },
- { key: 'Hg_particulate', label: 'Hg mg/kg' },
- { key: 'Pb_particulate', label: 'Pb mg/kg' },
- { key: 'particle_weight', label: '颗粒物重量 mg' },
- ];
- const atmoSampleMetrics = weightColumns.reduce((obj, col) => {
- obj[col.key] = { label: col.label };
- return obj;
- }, {} as Record<string, { label: string }>);
- type AtmoSampleKey = keyof typeof atmoSampleMetrics;
- type AtmoSampleRow = TableRow<AtmoSampleKey>;
- const atmoSampleLoading = ref(false);
- const atmoSampleError = ref('');
- const atmoSampleData = ref<{ [K in AtmoSampleKey]?: number | string }[]>([]);
- const atmoSampleTableData = ref<AtmoSampleRow[]>([]);
- const atmoSampleValidSamples = ref(0); // 有效样本数
- const getColWidth = computed(() => (field: AtmoSampleKey) => {
- const col = weightColumns.find(c => c.key === field);
- return col ;
- });
- const calculateAtmoSampleAverage = () => {
- const stats: Record<AtmoSampleKey, { sum: number; count: number }> = {} as any;
- (Object.keys(atmoSampleMetrics) as AtmoSampleKey[]).forEach(key => {
- stats[key] = { sum: 0, count: 0 };
- });
- let validSamples = 0;
- atmoSampleData.value.forEach(item => {
- let hasValidMetric = false;
- (Object.keys(atmoSampleMetrics) as AtmoSampleKey[]).forEach(key => {
- const value = Number(item[key]);
- if (!isNaN(value)) {
- stats[key].sum += value;
- stats[key].count += 1;
- hasValidMetric = true;
- }
- });
- if (hasValidMetric) validSamples++;
- });
- atmoSampleTableData.value = [(Object.keys(atmoSampleMetrics) as AtmoSampleKey[]).reduce((row, key) => {
- row.indicator = '平均值';
- row[key] = stats[key].count > 0 ? stats[key].sum / stats[key].count : 0;
- return row;
- }, {} as AtmoSampleRow)];
- atmoSampleValidSamples.value = validSamples;
- };
- const fetchAtmoSampleData = async () => {
- try {
- atmoSampleLoading.value = true;
- atmoSampleError.value = '';
- const res = await fetch('http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data');
- if (!res.ok) throw new Error(`HTTP 错误:${res.status}`);
- let rawText = await res.text();
- rawText = rawText.replace(/:\s*NaN/g, ': null'); // 修复 NaN
- const geoJSON = JSON.parse(rawText);
- atmoSampleData.value = geoJSON.features.map((f: { properties: any }) => f.properties);
- calculateAtmoSampleAverage();
- } catch (err: any) {
- atmoSampleError.value = err.message;
- atmoSampleTableData.value = [];
- } finally {
- atmoSampleLoading.value = false;
- }
- };
- // ====================== 统一刷新 ======================
- const refreshAll = () => {
- fetchWaterData();
- fetchSectionData();
- fetchAtmoCompanyData();
- fetchAtmoSampleData();
- };
- onMounted(() => {
- refreshAll();
- });
- </script>
- <style scoped>
- .metal-tables-container {
- padding: 20px;
- background: #fff;
- border-radius: 8px;
- box-shadow: 0 2px 12px rgba(0,0,0,0.1);
- }
- .table-section {
- margin-bottom: 30px;
- padding-bottom: 20px;
- border-bottom: 1px solid #eee;
- }
- .table-section:last-child {
- border-bottom: none;
- margin-bottom: 0;
- padding-bottom: 0;
- }
- .title {
- font-size: 20px;
- font-weight: 500;
- margin-bottom: 8px;
- color: #333;
- }
- .sample-count {
- font-size: 14px;
- color: #666;
- font-style: italic;
- }
- .error-alert {
- margin: 16px 0;
- }
- .empty-state {
- padding: 40px 0;
- text-align: center;
- }
- .refresh-btn {
- text-align: center;
- margin-top: 10px;
- }
- /* 关键样式:自适应列宽 + 内容不换行 */
- .el-table {
- table-layout: auto; /* 列宽自适应内容 */
- min-width: 100%; /* 确保容器宽度不足时触发滚动 */
- }
- /* 覆盖Element UI的单元格样式(提高优先级) */
- .el-table td,
- .el-table th {
- white-space: nowrap !important; /* 强制不换行 */
- overflow: hidden !important; /* 溢出隐藏 */
- text-overflow: ellipsis !important; /* 溢出省略号 */
- word-break: normal !important; /* 禁止自动断词 */
- }
- .el-table__cell {
- white-space: nowrap !important; /* 强制不换行 */
- overflow: hidden; /* 溢出隐藏 */
- text-overflow: ellipsis; /* 溢出显示省略号 */
- }
- .table-section {
- overflow-x: auto; /* 横向滚动 */
- }
- </style>
|