目标:在Vue开源项目Pure Admin的基础上,增加菜单和标签页,实现同期温度对比的柱状图,支持按时段查询。
先贴上效果图:
增加菜单
新建src\router\modules\weather.ts,
export default {path: "/weather",meta: {title: "天气"},children: [{path: "/weather/index",name: "Weather",component: () => import("@/views/weather/index.vue"),meta: {title: "天气",showParent: true}}]
};
跨域访问配置:
在vite.config.ts中增加proxy配置:
proxy: {"/api": {// 这里填写后端地址target: "http://127.0.0.1:5000",changeOrigin: true,rewrite: path => path.replace(/^\/api/, "")}},
新建src\api\utils.ts,将所有api/xxx的访问通过代理转发至http://127.0.0.1:5000
export const baseUrlApi = (url: string) => `/api/${url}`;
发送post请求(在下文的hook.tsx代码中):
const getWeather = (data?: object) => {return http.request<any>("post", baseUrlApi("getWeather"), { data });};const { data } = await getWeather(form);
前端柱状图
新建src\views\weather\index.vue,在其中编写网页代码。
<script setup lang="ts">import { weatherRole } from "./hook";
import { ref } from "vue";
import { getPickerShortcuts } from "../monitor/utils";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Refresh from "@iconify-icons/ep/refresh";
import Segmented, { type OptionsType } from "@/components/ReSegmented";
import { ChartBar } from "./components/charts";
import ReCol from "@/components/ReCol";defineOptions({// name 作为一种规范最好必须写上并且和路由的name保持一致name: "Weather",
});const formRef = ref();const {form,resetForm,onSearch,loading,oneData,twoData,headData,getWeather} = weatherRole();onSearch();</script><template><div class="main"><el-row :gutter="24" justify="space-around"><re-colv-motionclass="mb-[18px]":value="24":xs="24":initial="{opacity: 0,y: 100}":enter="{opacity: 1,y: 0,transition: {delay: 400}}" ><el-formref="formRef":inline="true":model="form"class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"><el-form-item label="时间" prop="loginTime"><el-date-pickerv-model="form.loginTime":shortcuts="getPickerShortcuts()"type="datetimerange"range-separator="至"start-placeholder="开始日期时间"end-placeholder="结束日期时间"/></el-form-item><el-form-item><el-buttontype="primary":icon="useRenderIcon('ri:search-line')"@click="onSearch">搜索</el-button><el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">重置</el-button></el-form-item></el-form></re-col></el-row><el-row :gutter="24" justify="space-around"><re-colv-motionclass="mb-[18px]":value="24":xs="24":initial="{opacity: 0,y: 100}":enter="{opacity: 1,y: 0,transition: {delay: 400}}" ><el-card class="bar-card" shadow="never"><div class="flex justify-between"><span class="text-md font-medium">天气概览</span></div><div class="flex justify-between items-start mt-3"><ChartBar:oneData="oneData":twoData="twoData":headData="headData"/></div></el-card></re-col> </el-row></div></template>
新建src\views\weather\components\charts\ChartBar.vue,在其中编写Echarts柱状图代码:
<script setup lang="ts">
import { useDark, useECharts } from "@pureadmin/utils";
import { type PropType, ref, computed, watch, nextTick } from "vue";const props = defineProps({oneData: {type: Array as PropType<Array<number>>,default: () => []},twoData: {type: Array as PropType<Array<number>>,default: () => []},headData: {type: Array as PropType<Array<string>>,default: () => []}
});const { isDark } = useDark();const theme = computed(() => (isDark.value ? "dark" : "light"));const chartRef = ref();
const { setOptions } = useECharts(chartRef, {theme
});watch(() => props,async () => {await nextTick(); // 确保DOM更新完成后再执行setOptions({container: ".bar-card",color: ["#41b6ff", "#e85f33"],tooltip: {trigger: "axis",axisPointer: {type: "none"}},grid: {top: "20px",left: "50px",right: 0},legend: {data: ["去年", "今年"],textStyle: {color: "#606266",fontSize: "0.875rem"},bottom: 0},xAxis: [{type: "category",data: props.headData,axisLabel: {fontSize: "0.875rem"},axisPointer: {type: "shadow"}}],yAxis: [{type: "value",axisLabel: {fontSize: "0.875rem"},splitLine: {show: false // 去网格线}// name: "单位: 个"}],series: [{name: "去年",type: "bar",barWidth: 10,itemStyle: {color: "#41b6ff",borderRadius: [10, 10, 0, 0]},data: props.oneData},{name: "今年",type: "bar",barWidth: 10,itemStyle: {color: "#e86033ce",borderRadius: [10, 10, 0, 0]},data: props.twoData}]});},{deep: true,immediate: true}
);
</script><template><div ref="chartRef" style="width: 100%; height: 365px" />
</template>
新建src\views\weather\components\charts\index.ts,导出ChartBar:
export { default as ChartBar } from "./ChartBar.vue";
前后端数据交互:
新建src\views\weather\hook.tsx,编写数据交互和逻辑:
import { type Ref, ref, reactive } from "vue";
import { http } from "@/utils/http";
import { baseUrlApi } from "../../api/utils";export function weatherRole() {const form = reactive({username: "",status: "",loginTime: ""});const loading = ref(true);const resetForm = formEl => {if (!formEl) return;formEl.resetFields();onSearch();};async function onSearch() {const { data } = await getWeather(form);oneData.value = data.one;twoData.value = data.two;headData.value = data.head;console.log("banner====>", data);};async function onSearch_bak() {getWeather(form).then(res => {console.log("banner11111====>", res);oneData.value = res.data;twoData.value = res.data.two;headData.value = res.data.head;});};const oneData = ref([]);const twoData = ref([]);const headData = ref([]);const getWeather = (data?: object) => {return http.request<any>("post", baseUrlApi("getWeather"), { data });};return {form,loading,resetForm,onSearch,oneData,twoData,headData,getWeather};
}
后端Python 代码:
import numpy as np
import pandas as pd
from datetime import datetimedef cvtTime(time_array):#time_array = ['2024-5-05T16:00:00.000Z', '2024-06-05T16:00:00.000Z']date_format = '%Y-%m-%dT%H:%M:%S.%fZ't1 = datetime.strptime(time_array[0], date_format)t2 = datetime.strptime(time_array[1], date_format) return t1,t2def getTemperatureByTime(t1,t2):#读取Exceldf1 = pd.read_excel('23.xls',header=0,encoding='gbk')df2 = pd.read_excel('24.xls',header=0,encoding='gbk')df = pd.concat([df1,df2])df['Time'] = pd.to_datetime(df['Time'],format='%d.%m.%Y %H:%M')df.set_index('Time', inplace=True)#以时间为索引的时间序列值df = df[["Temperature"]]#选取温度列df = df.resample('24H').agg(func=['max','min','mean'])#上采样data_this_year = df[(df.index >= t1) & (df.index <= t2)]#选取指定时段data_last_year = df[(df.index >= (t1 + pd.DateOffset(years=-1))) & (df.index <= (t2 + pd.DateOffset(years=-1)))]#去年同期ts = [str(x).replace(' 00:00:00','') for x in list(data_this_year.index)]#日期数组(年月日)oneT = list(data_last_year['Temperature']['max'].values)twoT = list(data_this_year['Temperature']['max'].values)data = {"data": {"one":oneT,"two":twoT,"head":ts}} return datafrom flask import Flask,url_for,request
from flask_cors import CORS
import jsonapp = Flask(__name__)
CORS(app)CORS(app, origins='http://localhost:8000/')@app.route("/getWeather", methods=['POST'])
def getWeather():loginTime = request.json.get("loginTime") print(loginTime)t1,t2 = cvtTime(loginTime)data = getTemperatureByTime(t1,t2)return json.dumps(data).encode('utf-8')app.run()
天气数据的xls格式如下: