Commit 0a6e8675 authored by Bill's avatar Bill
Browse files

Add TrendChart

parent 5376efaf
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -4,7 +4,9 @@ import TabView from "./components/TabView.vue";
import RealtimeMap from './components/RealtimeMap.vue'
import WeatherSpan from './components/weather-span.vue'
import BusChartVue from './components/BusChartVue.vue'

import CanteenTraffic from './components/CanteenTraffic.vue'
import TrendChart from './components/TrendChart.vue'

export default defineClientConfig({
  enhance({ app }) {
@@ -13,6 +15,8 @@ export default defineClientConfig({
    app.component("RealtimeMap", RealtimeMap)
    app.component("WeatherSpan", WeatherSpan)
    app.component("BusChartVue", BusChartVue)
    add.component("CanteenTraffic", CanteenTraffic)
    
    app.component("CanteenTraffic", CanteenTraffic)
    app.component("TrendChart", TrendChart)
  },
})
+227 −11
Original line number Diff line number Diff line
<template>
  <div id="weather-container">
    <span id="weather">{{ text }}</span>
  <div>
    <a-config-provider :theme="{
      token: {
        colorPrimary: '#49BF7C',
      },
    }">
      <a-segmented v-model:value="initSelect" :options="tabOptions" @change="switchTab">
        <template #label="{ payload }">
          <div style="padding: 4px 8px">
            <div>{{ payload.title }}</div>
            <div>{{ payload.subTitle }}</div>
          </div>
        </template>
      </a-segmented>

      <div class="tab-container">
        <!-- realtime-queue-length -->
        <div v-if="currentSelect === 'realtime-queue-length'">
          <div class="container mt-3" :class="{ 'dark-mode': isDarkMode }">
            <div v-for="canteen in trafficList" :key="canteen.canteen_id" class="card mb-3">
              <div class="card-header">
                <h5 class="card-title">{{ canteen.canteen_name }}</h5>
                <p class="card-subtitle">平均人数: {{ canteen.avg_number.toFixed(2) }}</p>
                <p class="card-subtitle">更新时间: {{ timeFormat(canteen.time) }}</p>
              </div>
              <ul class="list-group list-group-flush">
                <li class="list-group-item" v-for="booth in canteen.booth_traffic" :key="booth.booth_id">
                  <strong>{{ booth.booth_name }}</strong> - 排队人数约: {{ booth.avg_number }} 人
                </li>
              </ul>
            </div>
          </div>
        </div>

        <!-- 第二个 div 根据 value 控制显示与隐藏 -->
        <div v-if="currentSelect === 'queue-trend-chart'">
          <TrendChart></TrendChart>
        </div>
      </div>


    </a-config-provider>
  </div>
</template>

<script>
import axios from 'axios';
import { ConfigProvider } from 'ant-design-vue';
import { Segmented } from 'ant-design-vue';
import { watch, ref } from 'vue';

export default {
  name: "CanteenTraffic",
  data: () => ({
    text: ""
  }),
  data() {
    return {
      baseUrl: "http://localhost:8102/api/v1/traffic",
      trafficList: [],
      isDarkMode: false,
      formatter: new Intl.DateTimeFormat('zh-CN', {
        hour12: false,
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
      })
    };
  },

  mounted() {
    this.load_weather();
    this.detectDarkMode();
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
      this.isDarkMode = e.matches;
    });
    this.getTrafficList();
  },

  methods: {
    load_weather() {
      axios.get("https://api.sustech.online/weather")
          .then((res) => {
            this.text = res.data.msg;
          })
    detectDarkMode() {
      this.isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    },
    async getTrafficList() {
      try {
        // 获取初始交通列表
        const res = await axios.get(this.baseUrl + "/canteens");
        this.trafficList = res.data.data;

        // 创建一个请求每个食堂详细交通数据的 promise 数组
        const trafficPromises = this.trafficList.map(elem =>
          axios.get(`${this.baseUrl}/canteens/${elem.canteen_id}`)
        );

        // 并行执行所有请求
        const trafficResults = await Promise.all(trafficPromises);

        // 将结果合并回 trafficList
        this.trafficList = this.trafficList.map((elem, index) => {
          return {
            ...elem,
            booth_traffic: trafficResults[index].data.data
          };
        });
      } catch (error) {
        console.error("Error fetching traffic data:", error);
      }
    },
    timeFormat(time) {
      const t = new Date(time)
      const formattedTime = this.formatter.format(t)
      return formattedTime
    }
  },

  components: {
    AConfigProvider: ConfigProvider,
    ASegmented: Segmented
  },
  props: {
    isMapTabEnabled: {
      type: Boolean,
      default: true,
    },
  },
  setup(props) {
    const initSelect = ref('realtime-queue-length');
    const currentSelect = ref('realtime-queue-length');
    const tabOptions = ref([
      {
        value: 'realtime-queue-length',
        payload: {
          title: '实时排队人数',
          subTitle: 'Realtime Queue Length',
        },
      },
      {
        value: 'queue-trend-chart',
        payload: {
          title: '排队趋势图',
          subTitle: 'Queue Trend Chart',
        },
      },
      // {
      //   value: 'weekly-menus',
      //   payload: {
      //     title: '本周菜谱',
      //     subTitle: 'Weekly Menus'
      //   }
      // }
    ]);

    const switchTab = (tabOptionValue) => {
      currentSelect.value = tabOptionValue;
    };

    let showMapChart = props.isMapTabEnabled;

    return {
      initSelect,
      currentSelect,
      tabOptions,
      switchTab,
      showMapChart
    };
  },
};
</script>

<style scoped>
.container {
  font-family: 'Nunito', sans-serif;
  color: #333;
}

.dark-mode {
  color: #ccc;
  background-color: #2c2c2c;
}

.card {
  border: none;
  border-radius: 16px;
  overflow: hidden;
  background-color: #fff;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}

.card-header {
  background-color: #007bff;
  color: white;
  padding: 16px 24px;
}

.dark-mode .card-header {
  background-color: #333;
}

.card-subtitle {
  font-size: 0.875rem;
  margin-top: 8px;
}

.list-group-item {
  font-size: 0.875rem;
  background-color: #FFFFFF;
  padding: 12px 24px;
  border: none;
  transition: background-color 0.3s;
}

.list-group-item:hover {
  background-color: #f8f9fa;
}

.dark-mode .list-group-item {
  background-color: #2a2a2a;
  color: #ddd;
  border-top: 1px solid #3a3a3a;
}

.dark-mode .list-group-item:hover {
  background-color: #3a3a3a;
}

@media (prefers-color-scheme: dark) {
  .dark-mode .card {
    background-color: #1a1a1a;
    color: white;
  }
}

.realtime-queue-length-hint {
  padding-bottom: 4px;
}

.tab-container {
  margin-top: 6px;
}
</style>
+639 −0
Original line number Diff line number Diff line
<template>
  <div>
    <v-chart class="echarts" :option="chartData" />
  </div>

  <div id="bustable">
    <!-- <object-selector :objs="{
      '工作日 Workday': true,
      '节假日 Holiday': false
    }" v-slot="weekdayProps">
      <br />
      <object-selector :objs="weekdayProps.selected ? {
        'Line 1 号线 │ 工学院方向 To COE': '/bus_times/one_down.json',
        'Line 1 号线 │ 欣园方向 To Joy Highland': '/bus_times/one_up.json',
        'Line 2 号线 │ 科研楼方向 To Research Bldg.': '/bus_times/two_down.json',
        'Line 2 号线 │ 欣园方向 To Joy Highland': '/bus_times/two_up.json',
      } : {
        'Line 1 号线 │ 工学院方向  To COE': '/bus_times/one_down_holiday.json',
        'Line 1 号线 │ 欣园方向 To Joy Highland': '/bus_times/one_up_holiday.json',
        'Line 1 号线 │ 欣园方向 To Joy Highland': '/bus_times/one_up_holiday.json',
        'Line 1 号线 │ 欣园方向 To Joy Highland': '/bus_times/one_up_holiday.json',
      }" v-slot="routeProps">
        <data-request :path="routeProps.selected" v-slot="{ data }">
          <bus-timer v-if="data" v-bind="data"></bus-timer>
          <grid-list v-if="data" :data="data.times">
          </grid-list>
        </data-request>
      </object-selector>
    </object-selector> -->
  </div>
</template>
  
<script>
import axios from "axios";

import { defineComponent } from 'vue';
import ECharts from 'vue-echarts';
import { use } from 'echarts/core';

import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import { GridComponent, TooltipComponent, TitleComponent, LegendComponent, DataZoomComponent } from 'echarts/components';

import BusTimer from "./bus/BusTimer.vue";
import ObjectSelector from "./bus/ObjectSelector.vue";
import DataRequest from "./bus/DataRequest.vue";
import GridList from "./bus/GridList.vue";

// 注册必须的组件
use([
  CanvasRenderer,
  LineChart,
  GridComponent,
  TooltipComponent,
  TitleComponent,
  LegendComponent,
  DataZoomComponent
]);

export default {
  name: "TrendChart",
  components: {
    BusTimer,
    ObjectSelector,
    DataRequest,
    GridList,
    'v-chart': ECharts,
  },
  data() {
    return {
      trafficList: [
        {
            "time": "2023-11-13T11:00:00.000+08:00",
            "number": 2
        },
        {
            "time": "2023-11-13T11:01:00.000+08:00",
            "number": 0
        },
        {
            "time": "2023-11-13T11:02:00.000+08:00",
            "number": 0
        },
        {
            "time": "2023-11-13T11:03:00.000+08:00",
            "number": 0
        },
        {
            "time": "2023-11-13T11:04:00.000+08:00",
            "number": 1
        },
        {
            "time": "2023-11-13T11:05:00.000+08:00",
            "number": 0
        },
        {
            "time": "2023-11-13T11:06:00.000+08:00",
            "number": 0
        },
        {
            "time": "2023-11-13T11:07:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T11:08:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T11:09:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:10:00.000+08:00",
            "number": 10
        },
        {
            "time": "2023-11-13T11:11:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:12:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T11:13:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T11:14:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T11:15:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T11:16:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T11:17:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T11:18:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T11:19:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T11:20:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T11:21:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T11:22:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:23:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:24:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:25:00.000+08:00",
            "number": 16
        },
        {
            "time": "2023-11-13T11:26:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:27:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T11:28:00.000+08:00",
            "number": 10
        },
        {
            "time": "2023-11-13T11:29:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T11:30:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T11:31:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:32:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:33:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T11:34:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:35:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T11:36:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:37:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T11:38:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T11:39:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T11:40:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T11:41:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T11:42:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T11:43:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T11:44:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T11:45:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T11:46:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T11:47:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T11:48:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T11:49:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:50:00.000+08:00",
            "number": 15
        },
        {
            "time": "2023-11-13T11:51:00.000+08:00",
            "number": 18
        },
        {
            "time": "2023-11-13T11:52:00.000+08:00",
            "number": 14
        },
        {
            "time": "2023-11-13T11:53:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T11:54:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T11:55:00.000+08:00",
            "number": 16
        },
        {
            "time": "2023-11-13T11:56:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T11:57:00.000+08:00",
            "number": 16
        },
        {
            "time": "2023-11-13T11:58:00.000+08:00",
            "number": 10
        },
        {
            "time": "2023-11-13T11:59:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T12:00:00.000+08:00",
            "number": 16
        },
        {
            "time": "2023-11-13T12:01:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T12:02:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:03:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T12:04:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T12:05:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:06:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T12:07:00.000+08:00",
            "number": 14
        },
        {
            "time": "2023-11-13T12:08:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T12:09:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T12:10:00.000+08:00",
            "number": 16
        },
        {
            "time": "2023-11-13T12:11:00.000+08:00",
            "number": 17
        },
        {
            "time": "2023-11-13T12:12:00.000+08:00",
            "number": 15
        },
        {
            "time": "2023-11-13T12:13:00.000+08:00",
            "number": 17
        },
        {
            "time": "2023-11-13T12:14:00.000+08:00",
            "number": 10
        },
        {
            "time": "2023-11-13T12:15:00.000+08:00",
            "number": 12
        },
        {
            "time": "2023-11-13T12:16:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:17:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T12:18:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:19:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:20:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:21:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T12:22:00.000+08:00",
            "number": 2
        },
        {
            "time": "2023-11-13T12:23:00.000+08:00",
            "number": 11
        },
        {
            "time": "2023-11-13T12:24:00.000+08:00",
            "number": 10
        },
        {
            "time": "2023-11-13T12:25:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:26:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:27:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T12:28:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T12:29:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:30:00.000+08:00",
            "number": 2
        },
        {
            "time": "2023-11-13T12:31:00.000+08:00",
            "number": 2
        },
        {
            "time": "2023-11-13T12:32:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T12:33:00.000+08:00",
            "number": 2
        },
        {
            "time": "2023-11-13T12:34:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:35:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:36:00.000+08:00",
            "number": 10
        },
        {
            "time": "2023-11-13T12:37:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T12:38:00.000+08:00",
            "number": 9
        },
        {
            "time": "2023-11-13T12:39:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:40:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:41:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T12:42:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T12:43:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T12:44:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T12:45:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T12:46:00.000+08:00",
            "number": 8
        },
        {
            "time": "2023-11-13T12:47:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:48:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:49:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:50:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:51:00.000+08:00",
            "number": 4
        },
        {
            "time": "2023-11-13T12:52:00.000+08:00",
            "number": 5
        },
        {
            "time": "2023-11-13T12:53:00.000+08:00",
            "number": 6
        },
        {
            "time": "2023-11-13T12:54:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T12:55:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:56:00.000+08:00",
            "number": 7
        },
        {
            "time": "2023-11-13T12:57:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T12:58:00.000+08:00",
            "number": 3
        },
        {
            "time": "2023-11-13T12:59:00.000+08:00",
            "number": 3
        }
    ],
      chartData: null
    };
  },
  mounted() {
    // function toggleButtonBasedOnDate(holidata) {
    //   // JSON is from https://github.com/NateScarlet/holiday-cn
    //   // need to update by year.
    //   // Download the JSON to path "docs/.vuepress/public/YYYY.json"
    //   var dayMap = {};
    //   for (let i = 0; i < holidata.days.length; i++) {
    //     dayMap[holidata.days[i].date] = holidata.days[i].isOffDay;
    //   }
    //   var nowDate = new Date();
    //   var ye = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(nowDate);
    //   var mo = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(nowDate);
    //   var da = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(nowDate);
    //   var dayKey = `${ye}-${mo}-${da}`;
    //   var isHoliday;
    //   if (dayMap[dayKey] == null) {
    //     // 不在国家假日调整表里
    //     console.log("Not in GOV declaration");
    //     var dayInWeek = nowDate.getDay();
    //     var isWeekend = (dayInWeek == 6) || (dayInWeek == 0);
    //     // 6 = Saturday, 0 = Sunday
    //     isHoliday = isWeekend;
    //   } else {
    //     console.log("In GOV declaration");
    //     isHoliday = dayMap[dayKey];
    //   }
    //   if (isHoliday) {
    //     console.log("节假日");
    //     const busDiv = document.getElementById("bustable");
    //     const thisDayBtn = busDiv.getElementsByTagName("button")[1];
    //     thisDayBtn.click();
    //   } else {
    //     console.log("工作日");
    //     const busDiv = document.getElementById("bustable");
    //     const thisDayBtn = busDiv.getElementsByTagName("button")[0];
    //     thisDayBtn.click();
    //   }
    // }

    // axios.get("/2023.json").then(response => {
    //   toggleButtonBasedOnDate(response.data);
    // });

    this.getTrafficList();
    this.getChartData();

  },
  methods: {
    getTrafficList() {

    },

    getChartData() {
      this.chartData = {
        xAxis: {
          type: 'category',
          data: this.trafficList.map(item => item.time.slice(11, 16)), // 提取小时和分钟
        },
        yAxis: {
          type: 'value'
        },
        series: [{
          data: this.trafficList.map(item => item.number),
          type: 'line'
        }],
        dataZoom: [{
          type: 'slider',
          xAxisIndex: 0,
          start: 50,
          end: 100
        }]
      };
    }
  }
}
</script>
  
<style scoped>
.echarts {
  width: 100%;
  height: 400px;
}
</style>
  
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -272,6 +272,7 @@ export default defineUserConfig({
                ],
            },
            '/surroundings/',
            '/susteen/'
        ]
    }),
})
+1 −2
Original line number Diff line number Diff line
# ☕️饭堂服务
# 🍜饭堂服务

## 食堂实时排队人数
<ClientOnly>
  <CanteenTraffic></CanteenTraffic>
</ClientOnly>
 No newline at end of file