Unverified Commit 2ecd4ddc authored by liziwl's avatar liziwl Committed by GitHub
Browse files

Merge pull request #156 from ITBillZ/master

添加了食堂服务功能
parents a93fdafe 2673fba6
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>
+137 −0
Original line number Diff line number Diff line
<template>
  <transition name="fade" mode="out-in" appear>
    <div class="container mt-3">
      <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 }} {{ canteen.canteen_en_name }}</h5>
          <p class="card-subtitle">平均人数 Avg Number: {{ canteen.avg_number.toFixed(2) }}</p>
          <p class="card-subtitle">更新时间 Last Updated: {{ timeFormat(canteen.time) }}</p>
        </div>
        <ul class="list-group">
          <li class="list-group-item" v-for="booth in canteen.booth_traffic" :key="booth.booth_id">
            <strong>{{ booth.booth_name }} {{ booth.booth_en_name }}</strong> - 排队人数约: {{ booth.avg_number }}
          </li>
        </ul>
      </div>
    </div>
  </transition>
</template>

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

export default {
  name: "RealtimeTraffic",
  data() {
    return {
      baseUrl: "https://susteen.itbill.cn/api/v1/traffic",
      trafficList: [],
      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.getTrafficList();
  },

  methods: {
    async getTrafficList() {
      try {
        const res = await axios.get(this.baseUrl + "/canteens");
        this.trafficList = res.data.data;

        const trafficPromises = this.trafficList.map(elem =>
          axios.get(`${this.baseUrl}/canteens/${elem.canteen_id}`)
        );

        const trafficResults = await Promise.all(trafficPromises);

        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);
      return this.formatter.format(t);
    }
  },
};
</script>

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

.card {
  margin: 10px 0px;
  border: none;
  border-radius: 16px;
  overflow: hidden;
  background-color: #fff;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}

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

.card-header {
  color: #333;
  padding: 8px 24px;
  background-color: #fff;
}

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

.list-group {
  list-style-type: none;
  padding-left: 0;
  border-radius: 16px;
}

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

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

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

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
+123 −0
Original line number Diff line number Diff line
<template>
  <div id="bustable">
    <object-selector :objs="{
      '中心餐厅 | Center Cafeteria': true,
      '十栋餐厅 | Building 10 No.2 Cafeteria': false
    }" v-slot="canteenProps">
      <br />
      <br />
      <object-selector :objs="canteenProps.selected ? {
        '麻辣烫 | Spicy Hot Pot': 11,
        '大众菜左 | Popular Dishes': 12,
        '大众菜右 | Popular Dishes': 13,
        '风味面食 | Noodles': 14,
        '潮汕卤味套餐 | Chiu Chow-style Brino Meat': 15,
        '铁锅拌饭 | Rice with Mixed Vegetables': 16
      } : { '大众菜左 | Popular Dishes': 21, '大众菜右 | Popular Dishes': 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