|
|
@@ -0,0 +1,552 @@
|
|
|
+<script setup>
|
|
|
+import {computed, onMounted, ref} from 'vue';
|
|
|
+import request from "@/utils/request.js";
|
|
|
+import {
|
|
|
+ ElCard,
|
|
|
+ ElEmpty,
|
|
|
+ ElMessage,
|
|
|
+ ElPagination,
|
|
|
+ ElSkeleton,
|
|
|
+ ElSkeletonItem,
|
|
|
+ ElTable,
|
|
|
+ ElTableColumn,
|
|
|
+ ElTag
|
|
|
+} from "element-plus";
|
|
|
+import {
|
|
|
+ AlertCircle,
|
|
|
+ ArrowDownRight,
|
|
|
+ ArrowUpRight,
|
|
|
+ Calendar,
|
|
|
+ Clock,
|
|
|
+ CloudRain,
|
|
|
+ CloudSun,
|
|
|
+ Compass,
|
|
|
+ Droplets,
|
|
|
+ RefreshCw,
|
|
|
+ Thermometer,
|
|
|
+ Volume2,
|
|
|
+ Wind
|
|
|
+} from 'lucide-vue-next';
|
|
|
+
|
|
|
+// Data refs
|
|
|
+const currentWeather = ref(null);
|
|
|
+const historyData = ref([]);
|
|
|
+const loading = ref(true);
|
|
|
+const historyLoading = ref(true);
|
|
|
+const currentPage = ref(1);
|
|
|
+const pageSize = ref(10);
|
|
|
+const total = ref(0);
|
|
|
+const lastRefreshTime = ref(new Date());
|
|
|
+
|
|
|
+// Format functions
|
|
|
+const formatValue = (value, multiplier, unit) => {
|
|
|
+ if (!value) return '-';
|
|
|
+ // For temperature, noise, rainfall, wind speed, and humidity, display directly
|
|
|
+ return `${value} ${unit}`;
|
|
|
+};
|
|
|
+
|
|
|
+const formatWindDirection = (degree) => {
|
|
|
+ if (!degree) return '-';
|
|
|
+
|
|
|
+ const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
|
|
|
+ const val = Math.floor((parseFloat(degree) + 22.5) / 45);
|
|
|
+ return directions[val % 8];
|
|
|
+};
|
|
|
+
|
|
|
+const formatTime = (dateString) => {
|
|
|
+ if (!dateString) return '-';
|
|
|
+ const date = new Date(dateString);
|
|
|
+ return date.toLocaleString();
|
|
|
+};
|
|
|
+
|
|
|
+const getAirQualityLevel = (pm25) => {
|
|
|
+ if (!pm25) return { level: '-', color: 'gray' };
|
|
|
+
|
|
|
+ const value = parseInt(pm25);
|
|
|
+ if (value <= 35) return { level: '优', color: 'green' };
|
|
|
+ if (value <= 75) return { level: '良', color: 'blue' };
|
|
|
+ if (value <= 115) return { level: '轻度污染', color: 'orange' };
|
|
|
+ if (value <= 150) return { level: '中度污染', color: 'red' };
|
|
|
+ return { level: '重度污染', color: 'purple' };
|
|
|
+};
|
|
|
+
|
|
|
+const airQuality = computed(() => {
|
|
|
+ if (!currentWeather.value) return { level: '-', color: 'gray' };
|
|
|
+ return getAirQualityLevel(currentWeather.value.pm25);
|
|
|
+});
|
|
|
+
|
|
|
+// Get current weather data
|
|
|
+const getCurrentData = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const arr = [{
|
|
|
+ column: "create_time",
|
|
|
+ type: "orderByDesc"
|
|
|
+ }];
|
|
|
+
|
|
|
+ const res = await request.get("/weatherData/findByPage", {
|
|
|
+ params: {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 1,
|
|
|
+ conditionJson: encodeURIComponent(JSON.stringify(arr))
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.data.records && res.data.records.length > 0) {
|
|
|
+ currentWeather.value = res.data.records[0];
|
|
|
+ }
|
|
|
+ lastRefreshTime.value = new Date();
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取当前天气数据失败');
|
|
|
+ console.error(error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Get historical weather data
|
|
|
+const getHistoryData = async () => {
|
|
|
+ historyLoading.value = true;
|
|
|
+ try {
|
|
|
+ const arr = [{
|
|
|
+ column: "create_time",
|
|
|
+ type: "orderByDesc"
|
|
|
+ }];
|
|
|
+
|
|
|
+ const res = await request.get("/weatherData/findByPage", {
|
|
|
+ params: {
|
|
|
+ pageNum: currentPage.value,
|
|
|
+ pageSize: pageSize.value,
|
|
|
+ conditionJson: encodeURIComponent(JSON.stringify(arr))
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ historyData.value = res.data.records || [];
|
|
|
+ total.value = res.data.total || 0;
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取历史天气数据失败');
|
|
|
+ console.error(error);
|
|
|
+ } finally {
|
|
|
+ historyLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Handle page change
|
|
|
+const handlePageChange = (page) => {
|
|
|
+ currentPage.value = page;
|
|
|
+ getHistoryData();
|
|
|
+};
|
|
|
+
|
|
|
+// Handle page size change
|
|
|
+const handleSizeChange = (size) => {
|
|
|
+ pageSize.value = size;
|
|
|
+ currentPage.value = 1;
|
|
|
+ getHistoryData();
|
|
|
+};
|
|
|
+
|
|
|
+// Refresh data
|
|
|
+const refreshData = () => {
|
|
|
+ getCurrentData();
|
|
|
+ getHistoryData();
|
|
|
+ ElMessage.success('数据已刷新');
|
|
|
+};
|
|
|
+
|
|
|
+// Get weather icon and background based on conditions
|
|
|
+const getWeatherInfo = computed(() => {
|
|
|
+ if (!currentWeather.value) return { icon: CloudSun, bgClass: 'bg-blue-50' };
|
|
|
+
|
|
|
+ const temp = currentWeather.value.temperature ? parseFloat(currentWeather.value.temperature) : 20;
|
|
|
+ const rainfall = currentWeather.value.rainfall ? parseFloat(currentWeather.value.rainfall) : 0;
|
|
|
+ const humidity = currentWeather.value.humidity ? parseFloat(currentWeather.value.humidity) : 50;
|
|
|
+
|
|
|
+ if (rainfall > 5) {
|
|
|
+ return {
|
|
|
+ icon: CloudRain,
|
|
|
+ bgClass: 'bg-gradient-to-r from-blue-100 to-blue-200',
|
|
|
+ text: '雨天'
|
|
|
+ };
|
|
|
+ } else if (temp > 30) {
|
|
|
+ return {
|
|
|
+ icon: Thermometer,
|
|
|
+ bgClass: 'bg-gradient-to-r from-orange-50 to-red-100',
|
|
|
+ text: '炎热'
|
|
|
+ };
|
|
|
+ } else if (temp < 5) {
|
|
|
+ return {
|
|
|
+ icon: Thermometer,
|
|
|
+ bgClass: 'bg-gradient-to-r from-blue-50 to-indigo-100',
|
|
|
+ text: '寒冷'
|
|
|
+ };
|
|
|
+ } else if (humidity > 80) {
|
|
|
+ return {
|
|
|
+ icon: Droplets,
|
|
|
+ bgClass: 'bg-gradient-to-r from-blue-50 to-cyan-100',
|
|
|
+ text: '潮湿'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ icon: CloudSun,
|
|
|
+ bgClass: 'bg-gradient-to-r from-blue-50 to-cyan-50',
|
|
|
+ text: '晴朗'
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+// Get trend indicators
|
|
|
+const getTrend = (current, previous, isHigherBetter = false) => {
|
|
|
+ if (!current || !previous) return { icon: null, color: 'gray' };
|
|
|
+
|
|
|
+ const curr = parseFloat(current);
|
|
|
+ const prev = parseFloat(previous);
|
|
|
+ const isHigher = curr > prev;
|
|
|
+
|
|
|
+ if (curr === prev) return { icon: null, color: 'gray' };
|
|
|
+
|
|
|
+ const isPositive = isHigherBetter ? isHigher : !isHigher;
|
|
|
+
|
|
|
+ return {
|
|
|
+ icon: isHigher ? ArrowUpRight : ArrowDownRight,
|
|
|
+ color: isPositive ? 'green' : 'red'
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getCurrentData();
|
|
|
+ getHistoryData();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="weather-dashboard p-4 max-w-7xl mx-auto">
|
|
|
+
|
|
|
+ <!-- Current Weather Overview -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <el-card
|
|
|
+ :class="[getWeatherInfo.bgClass, 'border-0 shadow-md overflow-hidden transition-all duration-300']"
|
|
|
+ :body-style="{ padding: '0' }"
|
|
|
+ >
|
|
|
+ <div class="p-6 relative overflow-hidden">
|
|
|
+ <div class="flex flex-col md:flex-row items-center justify-between gap-6">
|
|
|
+ <!-- Weather Icon and Main Info -->
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <div class="bg-white/80 p-4 rounded-full shadow-md">
|
|
|
+ <component :is="getWeatherInfo.icon" class="w-12 h-12 text-blue-500" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <h2 class="text-2xl font-bold text-gray-800">当前天气状况</h2>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <el-tag size="small" :type="airQuality.color === 'gray' ? 'info' : airQuality.color === 'green' ? 'success' : airQuality.color === 'blue' ? 'info' : airQuality.color === 'orange' ? 'warning' : 'danger'">
|
|
|
+ {{ airQuality.level }}
|
|
|
+ </el-tag>
|
|
|
+ <span class="text-gray-600">{{ getWeatherInfo.text }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Temperature and Refresh -->
|
|
|
+ <div class="flex flex-col items-end">
|
|
|
+ <div v-if="currentWeather && !loading" class="text-4xl font-bold text-gray-800">
|
|
|
+ {{ currentWeather.temperature }} °C
|
|
|
+ </div>
|
|
|
+ <div v-else class="h-10 w-24 bg-gray-200 animate-pulse rounded"></div>
|
|
|
+
|
|
|
+ <div class="flex items-center gap-2 mt-2">
|
|
|
+ <Clock class="w-4 h-4 text-gray-500" />
|
|
|
+ <span class="text-sm text-gray-600">更新于: {{ formatTime(lastRefreshTime) }}</span>
|
|
|
+ <button
|
|
|
+ @click="refreshData"
|
|
|
+ class="ml-2 flex items-center gap-1 text-blue-500 hover:text-blue-700 transition-colors"
|
|
|
+ >
|
|
|
+ <RefreshCw class="w-4 h-4" />
|
|
|
+ <span>刷新</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Current Weather Details -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-4 flex items-center gap-2 text-gray-800">
|
|
|
+ <AlertCircle class="w-5 h-5" />
|
|
|
+ 详细气象数据
|
|
|
+ </h2>
|
|
|
+
|
|
|
+ <el-card shadow="hover" class="w-full border-0 shadow-md">
|
|
|
+ <el-skeleton :loading="loading" animated>
|
|
|
+ <template #template>
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
+ <div v-for="i in 6" :key="i" class="flex items-center gap-2">
|
|
|
+ <el-skeleton-item variant="circle" style="width: 40px; height: 40px" />
|
|
|
+ <div>
|
|
|
+ <el-skeleton-item variant="text" style="width: 100px" />
|
|
|
+ <el-skeleton-item variant="text" style="width: 150px" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #default>
|
|
|
+ <div v-if="currentWeather" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
|
|
|
+ <!-- Temperature -->
|
|
|
+ <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
|
+ <div class="bg-orange-50 p-3 rounded-full shadow-sm">
|
|
|
+ <Thermometer class="w-6 h-6 text-orange-500" />
|
|
|
+ </div>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-gray-500 text-sm">温度</div>
|
|
|
+ <div class="text-xl font-bold flex items-center gap-1">
|
|
|
+ {{ currentWeather.temperature }} °C
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Humidity -->
|
|
|
+ <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
|
+ <div class="bg-blue-50 p-3 rounded-full shadow-sm">
|
|
|
+ <Droplets class="w-6 h-6 text-blue-500" />
|
|
|
+ </div>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-gray-500 text-sm">湿度</div>
|
|
|
+ <div class="text-xl font-bold flex items-center gap-1">
|
|
|
+ {{ currentWeather.humidity }} %
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Wind -->
|
|
|
+ <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
|
+ <div class="bg-teal-50 p-3 rounded-full shadow-sm">
|
|
|
+ <Wind class="w-6 h-6 text-teal-500" />
|
|
|
+ </div>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-gray-500 text-sm">风速/风向</div>
|
|
|
+ <div class="text-xl font-bold">
|
|
|
+ {{ currentWeather.windSpeed }} m/s
|
|
|
+ {{ currentWeather.windLevel ? `(${currentWeather.windLevel}级)` : '' }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500 flex items-center gap-1">
|
|
|
+ <Compass class="w-4 h-4" />
|
|
|
+ {{ formatWindDirection(currentWeather.windDirectionDegree) }}
|
|
|
+ {{ currentWeather.windDirectionDegree ? `(${currentWeather.windDirectionDegree}°)` : '' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Air Quality -->
|
|
|
+ <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
|
+ <div :class="`bg-${airQuality.color === 'gray' ? 'gray' : airQuality.color === 'green' ? 'green' : airQuality.color === 'blue' ? 'blue' : airQuality.color === 'orange' ? 'orange' : 'red'}-50 p-3 rounded-full shadow-sm`">
|
|
|
+ <AlertCircle :class="`w-6 h-6 text-${airQuality.color === 'gray' ? 'gray' : airQuality.color === 'green' ? 'green' : airQuality.color === 'blue' ? 'blue' : airQuality.color === 'orange' ? 'orange' : 'red'}-500`" />
|
|
|
+ </div>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-gray-500 text-sm">空气质量</div>
|
|
|
+ <div class="text-xl font-bold">
|
|
|
+ {{ airQuality.level }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500">
|
|
|
+ PM2.5: {{ currentWeather.pm25 || '-' }} μg/m³,
|
|
|
+ PM10: {{ currentWeather.pm10 || '-' }} μg/m³
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Noise -->
|
|
|
+ <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
|
+ <div class="bg-purple-50 p-3 rounded-full shadow-sm">
|
|
|
+ <Volume2 class="w-6 h-6 text-purple-500" />
|
|
|
+ </div>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-gray-500 text-sm">噪声</div>
|
|
|
+ <div class="text-xl font-bold">
|
|
|
+ {{ currentWeather.noise }} dB
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Rainfall -->
|
|
|
+ <div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
|
+ <div class="bg-indigo-50 p-3 rounded-full shadow-sm">
|
|
|
+ <CloudRain class="w-6 h-6 text-indigo-500" />
|
|
|
+ </div>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-gray-500 text-sm">降雨量</div>
|
|
|
+ <div class="text-xl font-bold">
|
|
|
+ {{ currentWeather.rainfall }} mm
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else class="text-center py-8">
|
|
|
+ <el-empty description="暂无天气数据" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Historical Data Table -->
|
|
|
+ <div>
|
|
|
+ <h2 class="text-xl font-bold mb-4 flex items-center gap-2 text-gray-800">
|
|
|
+ <Calendar class="w-5 h-5" />
|
|
|
+ 历史天气数据
|
|
|
+ </h2>
|
|
|
+
|
|
|
+ <el-card shadow="hover" class="w-full border-0 shadow-md">
|
|
|
+ <div v-if="historyLoading" class="py-4">
|
|
|
+ <el-skeleton :rows="5" animated />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <el-table
|
|
|
+ :data="historyData"
|
|
|
+ stripe
|
|
|
+ border
|
|
|
+ style="width: 100%"
|
|
|
+ max-height="500"
|
|
|
+ class="weather-history-table"
|
|
|
+ v-loading="historyLoading"
|
|
|
+ element-loading-text="加载中..."
|
|
|
+ >
|
|
|
+ <el-table-column prop="createTime" label="时间" min-width="180" fixed="left">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatTime(scope.row.createTime) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="温度" min-width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.temperature }} °C
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="湿度" min-width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.humidity }} %
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="风速" min-width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.windSpeed }} m/s
|
|
|
+ <span v-if="scope.row.windLevel">({{ scope.row.windLevel }}级)</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="风向" min-width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatWindDirection(scope.row.windDirectionDegree) }}
|
|
|
+ <span v-if="scope.row.windDirectionDegree">({{ scope.row.windDirectionDegree }}°)</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="PM2.5" min-width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.pm25 || '-' }} μg/m³
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="PM10" min-width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.pm10 || '-' }} μg/m³
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="噪声" min-width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.noise }} dB
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="降雨量" min-width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.rainfall }} mm
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="空气质量" min-width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag
|
|
|
+ :type="getAirQualityLevel(scope.row.pm25).color === 'gray' ? 'info' :
|
|
|
+ getAirQualityLevel(scope.row.pm25).color === 'green' ? 'success' :
|
|
|
+ getAirQualityLevel(scope.row.pm25).color === 'blue' ? 'info' :
|
|
|
+ getAirQualityLevel(scope.row.pm25).color === 'orange' ? 'warning' : 'danger'"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ {{ getAirQualityLevel(scope.row.pm25).level }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- Pagination -->
|
|
|
+ <div class="flex justify-center mt-4">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ :total="total"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ background
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style>
|
|
|
+/* Enhanced styles */
|
|
|
+.weather-dashboard {
|
|
|
+ --card-transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.weather-dashboard .el-card {
|
|
|
+ transition: var(--card-transition);
|
|
|
+}
|
|
|
+
|
|
|
+.weather-dashboard .el-card:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.weather-history-table .el-table__header-wrapper th {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ color: #606266;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* Responsive adjustments */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .weather-history-table {
|
|
|
+ font-size: 0.85rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Animation for refresh */
|
|
|
+@keyframes spin {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+}
|
|
|
+
|
|
|
+.weather-dashboard button:active .lucide-refresh-cw {
|
|
|
+ animation: spin 0.5s linear;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|