Unverified Commit 2c2f75e3 authored by Zhanwei Zhang's avatar Zhanwei Zhang Committed by GitHub
Browse files

Merge pull request #1 from ITBillZ/dev

Dev
parents 1019f00b 7a63eb7b
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -4,7 +4,8 @@ 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 AdsenseUnit from './components/adsense-inline-article.vue'

import Canteen from './components/Canteen.vue'

export default defineClientConfig({
  enhance({ app }) {
@@ -13,6 +14,7 @@ export default defineClientConfig({
    app.component("RealtimeMap", RealtimeMap)
    app.component("WeatherSpan", WeatherSpan)
    app.component("BusChartVue", BusChartVue)
    // app.component("AdsenseUnit", AdsenseUnit)
    
    app.component("Canteen", Canteen)
  },
})
+105 −0
Original line number Diff line number Diff line
<template>
  <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 2px">
            <div>{{ payload.title }}</div>
            <div>{{ payload.subTitle }}</div>
          </div>
        </template>
      </a-segmented>
    </a-config-provider>

    <div class="tab-container">
      <div v-if="currentSelect === 'realtime-queue-length'">
        <RealtimeTraffic></RealtimeTraffic>
      </div>

      <div v-if="currentSelect === 'queue-trend-chart'">
        <TrendChart></TrendChart>
      </div>

      <div v-if="currentSelect === 'daily-menus'">
        <Menu></Menu>
      </div>

    </div>

  </div>
</template>

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

import RealtimeTraffic from './canteen/RealtimeTraffic.vue'
import TrendChart from './canteen/TrendChart.vue';
import Menu from './canteen/Menu.vue'

export default {
  name: "Canteen",
  components: {
    AConfigProvider: ConfigProvider,
    ASegmented: Segmented,
    RealtimeTraffic,
    TrendChart,
    Menu

  },
  data() {
    return {

    }
  },
  setup() {
    const initSelect = ref('realtime-queue-length');
    const currentSelect = ref('realtime-queue-length');
    const tabOptions = ref([
      {
        value: 'realtime-queue-length',
        payload: {
          title: '实时排队人数',
          subTitle: 'Queue Length',
        },
      },
      {
        value: 'queue-trend-chart',
        payload: {
          title: '排队趋势图',
          subTitle: 'Queue Trend',
        },
      },
      {
        value: 'daily-menus',
        payload: {
          title: '今日菜谱',
          subTitle: 'Daily Menus'
        }
      }
    ]);

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

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

<style>
.tab-container {
  margin-top: 6px;
}
</style>
 No newline at end of file
+46 −0
Original line number Diff line number Diff line
<template>
  <div v-for="menu in dailyMenus" :key="menu.canteen_id">
    <img class="menu-img" :src="menu.url" :alt="menu.canteen_id" sizes="width=200px">
  </div>
</template>

<script>
import { onMounted, ref } from 'vue';
import axios from 'axios';

export default {
  name: 'Menu',
  setup() {
    const dailyMenus = ref([]);

    const getDailyMenus = () => {
      const currentDate = new Date();
      const year = currentDate.getFullYear();
      const month = String(currentDate.getMonth() + 1).padStart(2, '0');
      const day = String(currentDate.getDate()).padStart(2, '0');

      axios.get(`https://susteen.itbill.cn/api/v1/menu/${year}/${month}/${day}`)
        .then((res) => {
          dailyMenus.value = res.data.data;
        })
        .catch(error => console.log("Error in Menu: " + error));
    };

    onMounted(() => {
      getDailyMenus();
    });

    return {
      dailyMenus,
    };
  },
};
</script>

<style scoped>

.menu-img {
  margin-bottom: 2rem;
}

</style>
+183 −0
Original line number Diff line number Diff line
<template>
  <Transition mode="out-in" name="fade" appear>
  <div class="container mt-3" :class="{ 'dark-mode': isDarkMode }">
    <div class="card mb-3" v-for="canteen in trafficList" :key="canteen.canteen_id">
        <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>
  </Transition>
</template>

<script>
import axios from 'axios';
import { watch, ref } from 'vue';


export default {
  name: "RealtimeTraffic",
  data() {
    return {
      baseUrl: "https://susteen.itbill.cn/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.detectDarkMode();
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
      this.isDarkMode = e.matches;
    });
    this.getTrafficList();
  },

  methods: {
    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
    }
  },
};
</script>

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

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

.card {
  padding-top: 10px;
  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: 0px 24px;
}

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

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

.list-group-item {
  list-style-type: none; /* 移除列表项前的点 */
  /* ... 其他已有的样式 ... */
}

/* 如果您想要对整个列表应用样式,而不仅仅是单个列表项 */
.list-group-flush {
  list-style-type: none;
  padding-left: 0; /* 也可能需要移除默认的内边距 */
}

.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;
}

/* 淡入淡出效果 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}</style>
+122 −0
Original line number Diff line number Diff line
<template>
  <div id="bustable">
    <object-selector :objs="{
      '中心餐厅': true,
      '十栋餐厅': false
    }" v-slot="canteenProps">
      <br />
      <object-selector :objs="canteenProps.selected ? {
        '麻辣烫 ': 11,
        '大众菜左': 12,
        '大众菜右': 13,
        '特色菜左': 14,
        '特色菜中': 15,
        '特色菜右': 16
      } : { '大众菜左': 21, '大众菜右': 22 }
        " v-slot="boothProps">
        <div v-for="meal in [0, 1, 2]" :key="meal">
          <data-request :path="getUrl(boothProps.selected, meal)" v-slot="{ data }">
            <v-chart class="echarts" :option="getChartData(data, meal)" />
          </data-request>
        </div>
      </object-selector>

    </object-selector>
  </div>
</template>

<script>
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 ObjectSelector from "../bus/ObjectSelector.vue";
import DataRequest from "../bus/DataRequest.vue";

use([
  CanvasRenderer,
  LineChart,
  GridComponent,
  TooltipComponent,
  TitleComponent,
  LegendComponent,
  DataZoomComponent
]);

export default {
  name: "TrendChart",
  components: {
    'data-request': DataRequest,
    'object-selector': ObjectSelector,
    'v-chart': ECharts
  },

  data() {
    return {
    }
  },

  setup() {

  },

  methods: {
    getUrl(boothId, meal) {
      const currentDate = new Date();
      const year = currentDate.getFullYear();
      const month = String(currentDate.getMonth() + 1).padStart(2, '0');
      const day = String(currentDate.getDate()).padStart(2, '0');
      const date = `${year}${month}${day}`;

      return `https://susteen.itbill.cn/api/v1/traffic/booths/${boothId}?date=${date}&meal=${meal}`;
    },

    getChartData(trafficList, meal) {
      if (trafficList) {
        trafficList = trafficList.data;
        const chartData = {
          title: {
            text: ["早餐 Breakfast", "午餐 Lunch", "晚餐 Dinner"][meal],
          },
          xAxis: {
            type: 'category',
            data: trafficList.map(item => item.time.slice(11, 16)), // 提取小时和分钟
          },
          yAxis: {
            type: 'value',
            name: '人数 Number',
          },
          series: [{
            data: trafficList.map(item => item.number),
            type: 'line',
            lineStyle: {
              color: "rgb(90,120,200)"
            }
          }],
          dataZoom: [{
            type: 'slider',
            xAxisIndex: 0,
            start: 50,
            end: 100
          }]
        };
        return chartData;
      }
    }
  }


}

</script>


<style>
.echarts {
  width: 100%;
  height: 250px;
}
</style>
Loading