Vue开源项目Pure Admin二次开发:实现前后端柱状图

目标:在Vue开源项目Pure Admin的基础上,增加菜单和标签页,实现同期温度对比的柱状图,支持按时段查询。

先贴上效果图:

下载运行Vue开源项目vue-pure-admin

下载运行Vue开源项目vue-pure-admin-CSDN博客

增加菜单

新建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-col
        v-motion
        class="mb-[18px]"
        :value="24"
        :xs="24"
        :initial="{
          opacity: 0,
          y: 100
        }"
        :enter="{
          opacity: 1,
          y: 0,
          transition: {
            delay: 400
          }
        }" 
      >
        <el-form
          ref="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-picker
              v-model="form.loginTime"
              :shortcuts="getPickerShortcuts()"
              type="datetimerange"
              range-separator="至"
              start-placeholder="开始日期时间"
              end-placeholder="结束日期时间"
            />
          </el-form-item>
          <el-form-item>
            <el-button
              type="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-col
        v-motion
        class="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 datetime


def 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,t2

def getTemperatureByTime(t1,t2):
    #读取Excel
    df1 = 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 data


from flask import Flask,url_for,request
from flask_cors import CORS
import json

app = 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格式如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值