atmcompanyline.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <div class="region-average-chart">
  3. <!-- 错误提示 -->
  4. <div v-if="error" class="status error">
  5. <i class="fa fa-exclamation-circle"></i> {{ error }}
  6. <div class="raw-response" v-if="rawResponse">
  7. <button @click="showRaw = !showRaw">
  8. {{ showRaw ? '收起原始响应' : '查看原始响应(前1000字符)' }}
  9. </button>
  10. <pre v-if="showRaw">{{ truncatedRawResponse }}</pre>
  11. </div>
  12. </div>
  13. <!-- 加载状态 -->
  14. <div v-if="loading" class="loading-state">
  15. <div class="spinner"></div>
  16. <p>数据加载中...</p>
  17. </div>
  18. <!-- 数据表格 -->
  19. <div v-else class="table-container">
  20. <table class="data-table">
  21. <thead>
  22. <tr>
  23. <th>污染源序号</th>
  24. <th>公司名称</th>
  25. <th>公司类型</th>
  26. <th>所属区县</th>
  27. <th>颗粒物排放(t/a)</th>
  28. <th>经度</th>
  29. <th>纬度</th>
  30. </tr>
  31. </thead>
  32. <tbody>
  33. <tr v-for="item in tableData" :key="item.id">
  34. <td>{{ item.id }}</td>
  35. <td>{{ item.company_name }}</td>
  36. <td>{{ item.company_type }}</td>
  37. <td>{{ item.county }}</td>
  38. <td>{{ item.particulate_emission }}</td>
  39. <td>{{ item.longitude.toFixed(6) }}</td>
  40. <td>{{ item.latitude.toFixed(6) }}</td>
  41. </tr>
  42. <tr v-if="tableData.length === 0">
  43. <td colspan="7" class="empty-state">暂无有效数据</td>
  44. </tr>
  45. </tbody>
  46. </table>
  47. </div>
  48. </div>
  49. </template>
  50. <script setup>
  51. import { ref, onMounted, computed } from 'vue';
  52. import { wgs84togcj02 } from 'coordtransform'; // 经纬度转换(按需保留)
  53. // ========== 接口配置 ==========
  54. const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=atmo_company';
  55. // ========== 响应式数据 ==========
  56. const error = ref(''); // 错误信息
  57. const loading = ref(true); // 加载状态
  58. const tableData = ref([]); // 表格数据
  59. const rawResponse = ref(''); // 原始响应文本(调试用)
  60. const showRaw = ref(false); // 是否展开原始响应
  61. // 截断原始响应(前1000字符)
  62. const truncatedRawResponse = computed(() => {
  63. return rawResponse.value.length > 1000
  64. ? rawResponse.value.slice(0, 1000) + '...'
  65. : rawResponse.value;
  66. });
  67. // ========== 数据请求 & 修复逻辑 ==========
  68. const fetchData = async () => {
  69. try {
  70. loading.value = true;
  71. error.value = '';
  72. rawResponse.value = '';
  73. // 1. 发起请求(获取原始文本)
  74. const response = await fetch(apiUrl);
  75. if (!response.ok) {
  76. throw new Error(`HTTP 错误:${response.status}`);
  77. }
  78. // 2. 获取原始响应文本(关键:避免自动解析错误)
  79. let rawText = await response.text();
  80. rawResponse.value = rawText; // 保存原始响应
  81. // 3. 暴力替换 NaN 为 null(核心修复!)
  82. rawText = rawText.replace(/:\s*NaN/g, ': null');
  83. // 4. 解析 JSON(若失败,报错含修复后内容)
  84. const geoJSONData = JSON.parse(rawText);
  85. // 5. 校验 GeoJSON 结构(兜底)
  86. if (!geoJSONData.features || !Array.isArray(geoJSONData.features)) {
  87. throw new Error('修复后仍无有效 features 数组');
  88. }
  89. // 6. 处理数据(转换经纬度 + 字段兜底)
  90. tableData.value = geoJSONData.features
  91. .map(feature => feature.properties || {}) // 兜底空 properties
  92. .map(props => {
  93. // 经纬度转换(按需调整,若不需要可移除)
  94. const lng = Number(props.longitude);
  95. const lat = Number(props.latitude);
  96. if (isNaN(lng) || isNaN(lat)) {
  97. console.warn('无效经纬度,跳过该数据:', props);
  98. return null;
  99. }
  100. const [gcjLng, gcjLat] = wgs84togcj02(lng, lat); // 或直接用 lng/lat
  101. return {
  102. id: props.id || '未知',
  103. company_name: props.company_name || '未知',
  104. company_type: props.company_type || '未知',
  105. county: props.county || '未知',
  106. particulate_emission: props.particulate_emission !== undefined
  107. ? props.particulate_emission
  108. : '未知',
  109. longitude: gcjLng,
  110. latitude: gcjLat
  111. };
  112. })
  113. .filter(item => item !== null); // 过滤无效项
  114. } catch (err) {
  115. error.value = `数据加载失败:${err.message}`;
  116. console.error('详细错误:', err);
  117. } finally {
  118. loading.value = false;
  119. }
  120. };
  121. // ========== 生命周期 ==========
  122. onMounted(() => {
  123. fetchData();
  124. });
  125. </script>
  126. <style scoped>
  127. .region-average-chart {
  128. width: 100%;
  129. margin: 20px 0;
  130. padding: 10px;
  131. }
  132. /* 错误提示 */
  133. .status.error {
  134. color: #dc2626;
  135. border: 1px solid #dc2626;
  136. padding: 10px;
  137. margin-bottom: 10px;
  138. }
  139. .status.error button {
  140. padding: 2px 6px;
  141. cursor: pointer;
  142. margin-top: 5px;
  143. }
  144. .raw-response pre {
  145. white-space: pre-wrap;
  146. word-break: break-all;
  147. font-size: 12px;
  148. margin: 5px 0;
  149. padding: 5px;
  150. border: 1px solid #ccc;
  151. }
  152. /* 加载状态 */
  153. .loading-state {
  154. padding: 20px 0;
  155. text-align: center;
  156. }
  157. .spinner {
  158. width: 30px;
  159. height: 30px;
  160. margin: 0 auto 10px;
  161. border: 3px solid #e5e7eb;
  162. border-top: 3px solid #333;
  163. border-radius: 50%;
  164. animation: spin 1s linear infinite;
  165. }
  166. @keyframes spin {
  167. 0% { transform: rotate(0deg); }
  168. 100% { transform: rotate(360deg); }
  169. }
  170. /* 最基础的表格样式 */
  171. .table-container {
  172. overflow-x: auto;
  173. background-color: white;
  174. }
  175. .data-table {
  176. width: 100%;
  177. border-collapse: collapse;
  178. border: 1px solid #ccc;
  179. }
  180. .data-table th, .data-table td {
  181. padding: 8px 10px;
  182. text-align: center;
  183. border: 1px solid #ccc;
  184. font-size: 14px;
  185. }
  186. .data-table th {
  187. font-weight: bold;
  188. }
  189. /* 完全移除所有行的背景色和悬停效果 */
  190. .data-table tr {
  191. background-color: transparent;
  192. }
  193. .data-table tr:hover {
  194. background-color: transparent;
  195. }
  196. .empty-state {
  197. padding: 20px 0;
  198. color: #666;
  199. }
  200. </style>