123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- <template>
- <div class="container mx-auto px-4 py-8">
- <div class="bg-white rounded-xl shadow-lg overflow-hidden">
- <div class="p-6 border-b border-gray-200">
- <h1 class="text-[clamp(1rem,3vw,2.5rem)] font-bold text-gray-800 text-center">大气污染公司列表</h1>
- </div>
-
- <!-- 加载状态 -->
- <div v-if="loading" class="py-20 flex justify-center items-center">
- <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
- </div>
-
- <!-- 错误状态 -->
- <div v-else-if="error" class="p-8 bg-red-50 border-l-4 border-red-400 text-red-700">
- <div class="flex">
- <div class="flex-shrink-0">
- <i class="fa fa-exclamation-triangle text-red-500 text-xl"></i>
- </div>
- <div class="ml-3">
- <h3 class="text-sm font-medium text-red-800">加载失败</h3>
- <div class="mt-2 text-sm text-red-700">
- <p>{{ error }}</p>
- </div>
- <div class="mt-4">
- <button @click="fetchData" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150 ease-in-out">
- <i class="fa fa-refresh mr-2"></i>重试
- </button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 数据表格 -->
- <div v-else-if="filteredData.length > 0" class="overflow-x-auto">
- <table class="min-w-full divide-y divide-gray-200">
- <thead class="bg-gray-50">
- <tr>
- <th
- v-for="(col, index) in displayColumns"
- :key="index"
- class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors"
- @click="sortData(col.key)"
- >
- <div class="flex items-center justify-between">
- {{ col.label }}
- <span v-if="sortKey === col.key" class="ml-1 text-gray-400">
- {{ sortOrder === 'asc' ? '↑' : '↓' }}
- </span>
- </div>
- </th>
- </tr>
- </thead>
- <tbody class="bg-white divide-y divide-gray-200">
- <tr v-for="(item, rowIndex) in sortedData" :key="rowIndex"
- class="hover:bg-gray-50 transition-colors duration-150">
- <td
- v-for="(col, colIndex) in displayColumns"
- :key="colIndex"
- class="px-6 py-4 whitespace-nowrap text-sm"
- >
- <div class="flex items-center">
- <div class="text-gray-900 font-medium">
- {{ item[col.key] !== null ? item[col.key] : '-' }}
- </div>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
-
- <!-- 空数据状态 -->
- <div v-else class="p-8 text-center">
- <div class="flex flex-col items-center justify-center">
- <div class="text-gray-400 mb-4">
- <i class="fa fa-database text-5xl"></i>
- </div>
- <h3 class="text-lg font-medium text-gray-900 mb-1">暂无有效数据</h3>
- <p class="text-gray-500">已过滤全空行</p>
- </div>
- </div>
-
- <!-- 数据表格 + 统计 -->
- <div class="p-4 bg-gray-50 border-t border-gray-200">
- <div class="flex flex-col md:flex-row justify-between items-center">
- <div class="text-sm text-gray-500 mb-2 md:mb-0">
- 共 <span class="font-medium text-gray-900">{{ filteredData.length }}</span> 条数据
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted } from 'vue';
- import axios from 'axios';
- // 定义固定列配置
- const displayColumns = ref([
- { key: '污染源序号', label: '污染源序号' },
- { key: '公司', label: '公司' },
- { key: '类型', label: '类型' },
- { key: '所属区县', label: '所属区县' },
- { key: '大气颗粒物排放(t/a)', label: '大气颗粒物排放(t/a)' },
- { key: '经度', label: '经度' },
- { key: '纬度', label: '纬度' },
- ]);
- // 状态管理
- const waterData = ref([]);
- const loading = ref(true);
- const error = ref(null);
- const sortKey = ref('');
- const sortOrder = ref('asc');
- // 获取数据
- const fetchData = async () => {
- try {
- loading.value = true;
- error.value = null;
- const response = await axios.get('http://localhost:3000/table/Atmosphere_company_data');
- waterData.value = response.data.data || response.data;
- } catch (err) {
- error.value = err.message || '无法连接到服务器,请检查接口是否可用';
- } finally {
- loading.value = false;
- }
- };
- // 过滤全空行
- const filteredData = computed(() => {
- return waterData.value.filter(item => {
- return displayColumns.value.some(col => item[col.key] !== null && item[col.key] !== '-');
- });
- });
- // 排序功能
- const sortedData = computed(() => {
- if (!sortKey.value) return filteredData.value;
-
- return [...filteredData.value].sort((a, b) => {
- const valA = a[sortKey.value];
- const valB = b[sortKey.value];
- if (valA < valB) return sortOrder.value === 'asc' ? -1 : 1;
- if (valA > valB) return sortOrder.value === 'asc' ? 1 : -1;
- return 0;
- });
- });
- // 切换排序
- const sortData = (key) => {
- if (sortKey.value === key) {
- sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
- } else {
- sortKey.value = key;
- sortOrder.value = 'asc';
- }
- };
- // 组件挂载
- onMounted(() => {
- fetchData();
- });
- </script>
- <style scoped>
- /* 布局 */
- .container {
- max-width: 1280px;
- margin: 0 auto;
- padding: 32px 16px;
- }
- .overflow-x-auto { overflow-x: auto; }
- /* 卡片 */
- .bg-white { background-color: #fff; }
- .rounded-xl { border-radius: 1rem; }
- .shadow-lg {
- box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1),
- 0 4px 6px -4px rgba(0,0,0,0.1);
- }
- /* 文字 */
- .text-center { text-align: center; }
- .text-lg { font-size: 1.125rem; }
- .font-bold { font-weight: 700; }
- .text-gray-800 { color: #111827; }
- /* 动画 */
- .animate-spin {
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
- }
- /* 表格 */
- table { width: 100%; }
- .px-6 { padding: 0 1.5rem; }
- .py-4 { padding: 1rem 0; }
- .hover\:bg-gray-50:hover { background-color: #f9fafb; }
- /* 响应式 */
- @media (max-width: 640px) {
- .container { padding: 32px 8px; }
- .px-6 { padding: 0 0.75rem; }
- }
- table {
- border-collapse: collapse; /* 合并边框线 */
- }
- th, td {
- border: 1px solid #d1d5db; /* 灰色边框 */
- text-align: center; /* 内容居中 */
- padding: 12px 8px; /* 内边距优化 */
- }
- </style>
|