Преглед изворни кода

feat(dashboard): 实现学校预约数据看板及图表组件

- 替换旧API,新增学校预约相关接口调用
- 新增BarChart柱状图组件展示学校预约概览数据
- 新增MonthlyLineChart折线图组件展示学校预约月份统计
- 重构数据概览模块,增加注册用户及多维度预约/入场统计
- 实现学校预约列表分页表格,支持多条件筛选及时间范围选择
- 优化页面布局,新增筛选操作区和分页控制
- 使用dayjs动态生成年份列表,支持按年和学校筛选月份数据
- 添加样式调整,优化卡片和表格头部视觉效果
zhangtao пре 18 часа
родитељ
комит
fcc4b8f5c7

+ 19 - 20
src/views/dashboard/Analysis/api.ts

@@ -1,25 +1,24 @@
 import { defHttp } from '/@/utils/http/axios';
 
 enum Api {
-  loginfo = '/sys/loginfo',
-  visitInfo = '/sys/visitInfo',
-  indexData = '/statisticsInfo/findIndexStatistics',
-  chartData='/statisticsInfo/findMonthSaleTrend',
-  storeData = '/statisticsInfo/findByDeptSum',
-  goodsData = '/statisticsInfo/findByShopSum',
+  overview = '/app/dataBoard/overview',
+  schoolOverview = '/app/dataBoard/schoolOverview',
+  schoolMonthly = '/app/dataBoard/schoolMonthly',
+  schoolReservationPage = '/app/dataBoard/schoolReservationPage',
+  schools = '/app/dataBoard/schools',
 }
-/**
- * 日志统计信息
- * @param params
- */
-export const getLoginfo = (params) => defHttp.get({ url: Api.loginfo, params }, { isTransformResponse: false });
-/**
- * 访问量信息
- * @param params
- */
-export const getVisitInfo = (params) => defHttp.get({ url: Api.visitInfo, params }, { isTransformResponse: false });
 
-export const getIndexData = () => defHttp.get({ url: Api.indexData });
-export const getChartData = () => defHttp.get({ url: Api.chartData });
-export const getStoreData = (params) => defHttp.get({ url: Api.storeData, params });
-export const getGoodsData = (params) => defHttp.get({ url: Api.goodsData, params });
+/** 数据概览 */
+export const getOverview = () => defHttp.get({ url: Api.overview });
+
+/** 学校预约概览-柱状图 */
+export const getSchoolOverview = () => defHttp.get({ url: Api.schoolOverview });
+
+/** 学校预约月份统计-折线图 */
+export const getSchoolMonthly = (params?: any) => defHttp.get({ url: Api.schoolMonthly, params });
+
+/** 学校预约列表-分页 */
+export const getSchoolReservationPage = (params?: any) => defHttp.get({ url: Api.schoolReservationPage, params });
+
+/** 获取全部学校下拉列表 */
+export const getSchools = () => defHttp.get({ url: Api.schools });

+ 42 - 0
src/views/dashboard/Analysis/components/BarChart.vue

@@ -0,0 +1,42 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts" name="BarChart" setup>
+  import { ref, type Ref, type PropType, watch } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  const props = defineProps({
+    width: { type: String, default: '100%' },
+    height: { type: String, default: '350px' },
+    chartData: { type: Array as PropType<any[]>, default: () => [] },
+  });
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+  watch(
+    () => props.chartData,
+    (data) => {
+      if (!data || !data.length) return;
+      const names = data.map((item) => item.schoolName || '');
+      const reservations = data.map((item) => item.reservationCount || 0);
+      const entries = data.map((item) => item.entryCount || 0);
+      setOptions({
+        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
+        legend: { data: ['预约人数', '入场人数'], top: 0 },
+        grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
+        xAxis: {
+          type: 'category',
+          data: names,
+          axisLabel: { interval: 0, rotate: names.length > 8 ? 30 : 0, fontSize: 12 },
+        },
+        yAxis: { type: 'value' },
+        series: [
+          { name: '预约人数', type: 'bar', data: reservations, itemStyle: { color: '#4A90D9' }, barGap: '0%' },
+          { name: '入场人数', type: 'bar', data: entries, itemStyle: { color: '#69C0C0' } },
+        ],
+      });
+    },
+    { immediate: true }
+  );
+</script>

+ 61 - 0
src/views/dashboard/Analysis/components/MonthlyLineChart.vue

@@ -0,0 +1,61 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts" name="MonthlyLineChart" setup>
+  import { ref, type Ref, type PropType, watch } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  const props = defineProps({
+    width: { type: String, default: '100%' },
+    height: { type: String, default: '350px' },
+    chartData: { type: Array as PropType<any[]>, default: () => [] },
+  });
+
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+  watch(
+    () => props.chartData,
+    (data) => {
+      const months = Array.from({ length: 12 }, (_, i) => `${i + 1}月`);
+      const reservations = new Array(12).fill(0);
+      const entries = new Array(12).fill(0);
+      if (data && data.length) {
+        data.forEach((item) => {
+          if (item.month >= 1 && item.month <= 12) {
+            reservations[item.month - 1] = item.reservationCount || 0;
+            entries[item.month - 1] = item.entryCount || 0;
+          }
+        });
+      }
+      setOptions({
+        tooltip: { trigger: 'axis' },
+        legend: { data: ['预约人数', '入场人数'], top: 0, icon: 'circle' },
+        grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
+        xAxis: { type: 'category', data: months, boundaryGap: false },
+        yAxis: { type: 'value' },
+        series: [
+          {
+            name: '预约人数',
+            type: 'line',
+            data: reservations,
+            symbol: 'circle',
+            symbolSize: 8,
+            itemStyle: { color: '#3B9E9E' },
+            lineStyle: { color: '#3B9E9E' },
+          },
+          {
+            name: '入场人数',
+            type: 'line',
+            data: entries,
+            symbol: 'circle',
+            symbolSize: 8,
+            itemStyle: { color: '#7ECFCF' },
+            lineStyle: { color: '#7ECFCF' },
+          },
+        ],
+      });
+    },
+    { immediate: true }
+  );
+</script>

+ 233 - 234
src/views/dashboard/Analysis/index.vue

@@ -1,271 +1,270 @@
+<!-- eslint-disable vue/multi-word-component-names -->
 <template>
-  <div class="p16px">
-    <DefineGradientBg v-slot="{ $slots, gradientColor }">
-      <div class="rd-8px px-16px pb-4px pt-8px text-white" :style="{ backgroundImage: gradientColor }">
-        <component :is="$slots.default" />
+  <div class="p-16px">
+    <!-- 数据概览 -->
+    <div class="mb-16px">
+      <div class="flex items-center mb-12px">
+        <div class="w-4px h-18px rounded-sm mr-8px" style="background-color: #1890ff"></div>
+        <span class="text-16px font-bold">数据概览</span>
       </div>
-    </DefineGradientBg>
-    <a-card :bordered="false" title="数据概览">
-      <div class="grid xl:grid-cols-5 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-3">
-        <div v-for="item in cardData" :key="item.key" :xs="24" :sm="12" :md="12" :lg="5">
-          <GradientBg v-if="item.ifsShow !== false" :gradient-color="getGradientColor(item.color)" class="flex-1">
-            <a-tooltip>
-              <template #title>{{ item.cardDsc }}</template>
-              <h3 class="text-16px">
-                {{ item.title }}
-                <Icon icon="ant-design:info-circle-outlined"></Icon>
-              </h3>
-            </a-tooltip>
-            <div class="flex">
-              <CountTo :endVal="item.value" decimals="2" :style="{ fontSize: '34px' }"></CountTo>
-              <div class="ml12px mt25px"> {{ item.unit }}</div>
+      <div class="grid xl:grid-cols-5 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-12px">
+        <!-- 注册用户 -->
+        <div class="overview-card">
+          <div class="card-header" style="color: #fa8c16">注册用户</div>
+          <div class="card-body flex items-center justify-center" style="min-height: 70px">
+            <span class="text-32px font-bold">{{ overviewData.registeredUsers ?? 0 }}</span>
+          </div>
+        </div>
+        <!-- 累计/今年/本月/今日 -->
+        <div v-for="item in statsCards" :key="item.key" class="overview-card" :class="{ 'active-card': item.key === 'today' }">
+          <div class="card-header" :style="{ color: item.color }">{{ item.title }}</div>
+          <div class="card-body grid grid-cols-2 text-center" style="min-height: 70px">
+            <div class="flex flex-col justify-center">
+              <div class="text-22px font-bold">{{ item.data?.reservationCount ?? 0 }}</div>
+              <div class="text-12px text-gray-400 mt-4px">预约人数</div>
             </div>
-            <div class="flex justify-between pt-12px">
-              <Icon :icon="item.icon" :size="28"></Icon>
-              <div class="text-18px" v-if="item.Proportion != null">
-                环比:<span> {{ item.Proportion > 0 ? '+' : '' }} {{ item.Proportion }} %</span>
-              </div>
-              <div v-else> 无环比数据</div>
+            <div class="flex flex-col justify-center">
+              <div class="text-22px font-bold">{{ item.data?.entryCount ?? 0 }}</div>
+              <div class="text-12px text-gray-400 mt-4px">入场人数</div>
             </div>
-          </GradientBg>
+          </div>
         </div>
       </div>
-    </a-card>
-    <div class="mt24px">
-      <a-card :bordered="false" title="销售额趋势">
-        <Line height="300px" :chartData="chartData" v-if="chartData.length"></Line>
-      </a-card>
     </div>
 
-    <div class="mt24px grid gap-3" :class="[!getIsMerchant ? 'lg:grid-cols-2 sm:grid-cols-1' : '']">
-      <a-card :bordered="false" title="门店销售额排行" v-if="!getIsMerchant">
-        <template #extra>
-          <a-radio-group v-model:value="storeBtn" button-style="solid">
-            <a-radio-button :value="1">今日</a-radio-button>
-            <a-radio-button :value="2">本周</a-radio-button>
-            <a-radio-button :value="3">本月</a-radio-button>
-          </a-radio-group>
-        </template>
-        <!--        <div class="overflow-y-scroll pl-20px">-->
-        <a-list item-layout="horizontal" :data-source="storeDataList">
-          <template #renderItem="{ item, index }">
-            <a-badge-ribbon placement="start" :text="index + 1" :color="index == 0 ? 'red' : index == 1 ? 'green' : index == 2 ? 'volcano' : ''">
-              <a-list-item>
-                <template #actions>
-                  <span> {{ item.total_price }} </span>
-                </template>
-                <a-list-item-meta>
-                  <template #title>
-                    <span class="ml40px">{{ item.name }} </span>
-                  </template>
-                </a-list-item-meta>
-              </a-list-item>
-            </a-badge-ribbon>
-          </template>
-        </a-list>
-        <!--        </div>-->
+    <!-- 图表区域 -->
+    <div class="grid lg:grid-cols-2 grid-cols-1 gap-16px mb-16px">
+      <!-- 学校预约概览 - 柱状图 -->
+      <a-card :bordered="false" title="学校预约概览">
+        <BarChart :chartData="schoolOverviewData" height="350px" />
       </a-card>
-      <a-card :bordered="false" title="商品销量排行">
+      <!-- 学校预约(月份)- 折线图 -->
+      <a-card :bordered="false">
+        <template #title>学校预约(月份)</template>
         <template #extra>
-          <a-radio-group v-model:value="goodsBtn" button-style="solid">
-            <a-radio-button :value="1">今日</a-radio-button>
-            <a-radio-button :value="2">本周</a-radio-button>
-            <a-radio-button :value="3">本月</a-radio-button>
-          </a-radio-group>
+          <div class="flex items-center gap-8px">
+            <a-select v-model:value="monthlyYear" style="width: 100px" @change="fetchMonthlyData">
+              <a-select-option v-for="y in yearOptions" :key="y" :value="y">{{ y }}年</a-select-option>
+            </a-select>
+            <a-select v-model:value="monthlySchoolId" placeholder="请选择学校" style="width: 150px" allowClear @change="fetchMonthlyData">
+              <a-select-option v-for="s in schoolOptions" :key="s.id" :value="s.id">{{ s.name }}</a-select-option>
+            </a-select>
+          </div>
         </template>
-        <div class="grid grid-cols-4 text-center font-bold mb12px">
-          <div class="text-left">排名</div>
-          <div class="text-left">商品名称</div>
-          <div>销量</div>
-          <div>销售额(元)</div>
-        </div>
-        <a-list item-layout="horizontal" :data-source="goodsDataList">
-          <template #renderItem="{ item, index }">
-            <a-badge-ribbon placement="start" :text="index + 1" :color="index == 0 ? 'red' : index == 1 ? 'green' : index == 2 ? 'volcano' : ''">
-              <a-list-item>
-                <template #actions>
-                  <span class="w-56 block"> {{ item.total_price }} </span>
-                </template>
-                <a-list-item-meta>
-                  <template #title>
-                    <span class="ml180px">{{ item.product_name }} </span>
-                  </template>
-                </a-list-item-meta>
-                <div> {{ item.total_quantity }}</div>
-              </a-list-item>
-            </a-badge-ribbon>
-          </template>
-        </a-list>
+        <MonthlyLineChart :chartData="monthlyData" height="350px" />
       </a-card>
     </div>
+
+    <!-- 学校预约列表 -->
+    <a-card :bordered="false" class="table-section">
+      <!-- 筛选区域 -->
+      <div class="flex items-center gap-12px mb-16px flex-wrap">
+        <span>学校:</span>
+        <a-select v-model:value="tableSchoolId" mode="multiple" placeholder="全部" style="width: 150px" allowClear>
+          <a-select-option v-for="s in schoolOptions" :key="s.id" :value="s.id">{{ s.name }}</a-select-option>
+        </a-select>
+        <a-radio-group v-model:value="tableTimeType" button-style="solid" @change="handleTimeTypeChange">
+          <a-radio-button value="0">不限</a-radio-button>
+          <a-radio-button value="1">今年</a-radio-button>
+          <a-radio-button value="2">本月</a-radio-button>
+          <a-radio-button value="3">自定义</a-radio-button>
+        </a-radio-group>
+        <a-range-picker v-if="tableTimeType === '3'" v-model:value="tableDateRange" />
+        <a-button type="primary" @click="handleSearch">查询</a-button>
+        <a-button @click="handleReset">重置</a-button>
+      </div>
+
+      <!-- 表格 -->
+      <a-table :dataSource="tableData" :columns="tableColumns" :loading="tableLoading" bordered size="middle" :pagination="false" rowKey="siteId">
+        <template #bodyCell="{ column, index }">
+          <template v-if="column.dataIndex === 'index'">
+            {{ (tablePagination.current - 1) * tablePagination.pageSize + index + 1 }}
+          </template>
+        </template>
+      </a-table>
+
+      <!-- 合计 + 分页 -->
+      <div class="flex justify-between items-center mt-12px">
+        <div class="text-14px text-gray-600">合计:预约人数 {{ totalReservationCount }} / 入场人数{{ totalEntryCount }}</div>
+        <TablePagination
+          v-model:current="tablePagination.current"
+          :total="tablePagination.total"
+          :pageSize="tablePagination.pageSize"
+          simple
+          @change="handlePageChange"
+        />
+      </div>
+    </a-card>
   </div>
 </template>
-<script lang="ts" setup>
-  import { Icon } from '/@/components/Icon';
-  import Line from './components/Line.vue';
-  import CountTo from '/@/components/CountTo/src/CountTo.vue';
-  import { createReusableTemplate } from '@vueuse/core';
-  import { ref, unref, watch } from 'vue';
 
-  const { getIsMerchant } = storeToRefs(useUserStore());
-  import { getGoodsData, getIndexData, getStoreData, getChartData } from './api';
-  import { storeToRefs } from 'pinia';
-  import { useUserStore } from '/@/store/modules/user';
+<script lang="ts" name="DataBoard" setup>
+  import { ref, reactive, computed } from 'vue';
+  import { Pagination as TablePagination } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import BarChart from './components/BarChart.vue';
+  import MonthlyLineChart from './components/MonthlyLineChart.vue';
+  import { getOverview, getSchoolOverview, getSchoolMonthly, getSchoolReservationPage, getSchools } from './api';
 
-  const userStore = useUserStore();
-  const storeBtn = ref(1);
-  const goodsBtn = ref(1);
-  const storeDataList = ref([]);
-  const goodsDataList = ref([]);
+  // ========== 数据概览 ==========
+  const overviewData = ref<any>({});
+  const statsCards = computed(() => [
+    { key: 'cumulative', title: '累计', color: '#597EF7', data: overviewData.value.cumulative },
+    { key: 'thisYear', title: '今年', color: '#52C41A', data: overviewData.value.thisYear },
+    { key: 'thisMonth', title: '本月', color: '#FA8C16', data: overviewData.value.thisMonth },
+    { key: 'today', title: '今日', color: '#1890FF', data: overviewData.value.today },
+  ]);
 
-  interface GradientBgProps {
-    gradientColor: string;
+  async function fetchOverview() {
+    try {
+      overviewData.value = (await getOverview()) || {};
+    } catch (e) {
+      console.error('获取数据概览失败', e);
+    }
   }
 
-  interface CardData {
-    key: string;
-    title: string;
-    value: number;
-    unit: string;
-    color: {
-      start: string;
-      end: string;
-    };
-    icon: string;
-    Proportion: number;
-    cardDsc: string;
+  // ========== 学校预约概览-柱状图 ==========
+  const schoolOverviewData = ref<any[]>([]);
+
+  async function fetchSchoolOverview() {
+    try {
+      schoolOverviewData.value = (await getSchoolOverview()) || [];
+    } catch (e) {
+      console.error('获取学校预约概览失败', e);
+    }
   }
 
-  const [DefineGradientBg, GradientBg] = createReusableTemplate<GradientBgProps>();
-  const cardData = ref<CardData[]>([
-    {
-      key: 'visitCount',
-      title: `今日${userStore.userInfo?.orgCode == 'A01' ? '平台' : '门店'}销售额`,
-      value: 9725,
-      unit: '元',
-      color: {
-        start: '#ec4786',
-        end: '#b955a4',
-      },
-      icon: 'ant-design:bar-chart-outlined',
-      Proportion: 8,
-      cardDsc: '',
-    },
-    {
-      key: 'turnover',
-      title: `今日${userStore.userInfo?.orgCode == 'A01' ? '平台' : '门店'}有效订单量`,
-      value: 1026,
-      unit: '笔',
-      color: {
-        start: '#865ec0',
-        end: '#5144b4',
-      },
-      icon: 'ant-design:file-text-outlined',
-      Proportion: 8,
-      cardDsc: '',
-    },
-    {
-      key: 'downloadCount',
-      title: `今日${userStore.userInfo?.orgCode == 'A01' ? '平台' : '门店'}预计收入`,
-      value: 970925,
-      unit: '元',
-      color: {
-        start: '#56cdf3',
-        end: '#719de3',
-      },
-      icon: 'ant-design:dollar-circle-outlined',
-      Proportion: 8,
-      cardDsc: '',
-    },
-    {
-      key: 'dealCount',
-      title: `今日${userStore.userInfo?.orgCode == 'A01' ? '平台' : '门店'}消费人次`,
-      value: 9527,
-      unit: '人次',
-      color: {
-        start: '#fcbc25',
-        end: '#f68057',
-      },
-      icon: 'ant-design:user-outlined',
-      Proportion: 8,
-      cardDsc: '',
-    },
-    {
-      key: 'add',
-      title: '今日平台新增用户数',
-      value: 9527,
-      unit: '个',
-      color: {
-        start: '#4ade80',
-        end: '#134e4a',
-      },
-      icon: 'ant-design:user-add-outlined',
-      Proportion: 8,
-      cardDsc: '',
-      ifsShow: userStore.userInfo?.orgCode == 'A01',
-    },
-  ]);
-  const chartData = ref([]);
-  watch(
-    () => [storeBtn.value, goodsBtn.value],
-    (newValue, old) => {
-      if (newValue[0] != old[0]) {
-        getStoreDataList();
-      }
-      if (newValue[1] != old[1]) {
-        getGoodsList();
-      }
+  // ========== 学校预约月份统计-折线图 ==========
+  const currentYear = dayjs().year();
+  const yearOptions = Array.from({ length: 5 }, (_, i) => currentYear - i);
+  const monthlyYear = ref(currentYear);
+  const monthlySchoolId = ref<string | undefined>(undefined);
+  const monthlyData = ref<any[]>([]);
+
+  async function fetchMonthlyData() {
+    try {
+      const params: Record<string, any> = { year: monthlyYear.value };
+      if (monthlySchoolId.value) params.siteId = monthlySchoolId.value;
+      monthlyData.value = (await getSchoolMonthly(params)) || [];
+    } catch (e) {
+      console.error('获取月份统计失败', e);
     }
-  );
+  }
+
+  // ========== 学校下拉列表 ==========
+  const schoolOptions = ref<any[]>([]);
 
-  function getGradientColor(color: CardData['color']) {
-    return `linear-gradient(to bottom right, ${color.start}, ${color.end})`;
+  async function fetchSchools() {
+    try {
+      schoolOptions.value = (await getSchools()) || [];
+    } catch (e) {
+      console.error('获取学校列表失败', e);
+    }
   }
 
-  async function getDataIndex() {
-    const res = await getIndexData();
-    unref(cardData)[0].value = res.salesAmount;
-    unref(cardData)[0].Proportion = res.salesGrowthRate;
-    unref(cardData)[0].cardDsc = '订单实际支付金额(扣除退款)';
-    unref(cardData)[1].value = res.validOrderCount;
-    unref(cardData)[1].Proportion = res.validOrderGrowthRate;
-    unref(cardData)[1].cardDsc = '成功支付的订单数量(剔除未付款/已退款)';
-    unref(cardData)[2].value = res.expectedIncome;
-    unref(cardData)[2].Proportion = res.expectedIncomeGrowthRate;
-    unref(cardData)[2].cardDsc = `销售额中${userStore.userInfo?.orgCode == 'A01' ? '平台' : '门店'}预计能分的钱`;
-    unref(cardData)[3].value = res.platformConsumptionUsers;
-    unref(cardData)[3].Proportion = res.platformConsumptionGrowthRate;
-    unref(cardData)[3].cardDsc = `今日${userStore.userInfo?.orgCode == 'A01' ? '平台' : '门店'}下订单(不去除退款)的人次`;
-    unref(cardData)[4].value = res.newUsersCount;
-    unref(cardData)[4].Proportion = res.newUsersGrowthRate;
-    unref(cardData)[4].cardDsc = `今日平台新增的小程序用户数`;
-    // chartData.value = res.findByStatisticsChartVOList;
+  // ========== 学校预约列表-表格 ==========
+  const tableSchoolId = ref<string[]>([]);
+  const tableTimeType = ref('0');
+  const tableDateRange = ref<any>(null);
+  const tableData = ref<any[]>([]);
+  const tableLoading = ref(false);
+  const totalReservationCount = ref(0);
+  const totalEntryCount = ref(0);
+  const tablePagination = reactive({ current: 1, pageSize: 10, total: 0 });
+
+  const tableColumns = [
+    { title: '序号', dataIndex: 'index', width: 80, align: 'center' as const },
+    { title: '学校名称', dataIndex: 'schoolName', align: 'center' as const },
+    { title: '预约人数', dataIndex: 'reservationCount', align: 'center' as const },
+    { title: '入场人数', dataIndex: 'entryCount', align: 'center' as const },
+    { title: '履约率', dataIndex: 'fulfillmentRate', align: 'center' as const },
+  ];
+
+  async function fetchTableData() {
+    tableLoading.value = true;
+    try {
+      const params: Record<string, any> = {
+        pageNo: tablePagination.current,
+        pageSize: tablePagination.pageSize,
+      };
+      if (tableSchoolId.value?.length) params.siteIds = tableSchoolId.value.join(',');
+      if (tableTimeType.value !== '0') params.timeType = tableTimeType.value;
+      if (tableTimeType.value === '3' && tableDateRange.value?.length === 2) {
+        params.startDate = dayjs(tableDateRange.value[0]).format('YYYY-MM-DD');
+        params.endDate = dayjs(tableDateRange.value[1]).format('YYYY-MM-DD');
+      }
+      const res = await getSchoolReservationPage(params);
+      tableData.value = res?.records || [];
+      tablePagination.total = res?.total || 0;
+      totalReservationCount.value = res?.totalReservationCount || 0;
+      totalEntryCount.value = res?.totalEntryCount || 0;
+    } catch (e) {
+      console.error('获取学校预约列表失败', e);
+    } finally {
+      tableLoading.value = false;
+    }
   }
 
-  async function getStoreDataList() {
-    const res = await getStoreData({ type: storeBtn.value });
-    console.log(res, 'getStoreDataList');
-    storeDataList.value = res;
+  function handlePageChange(page: number) {
+    tablePagination.current = page;
+    fetchTableData();
   }
 
-  // 图表
-  async function getChartDataList() {
-    const res = await getChartData();
-    console.log(res, '--图表数据');
-    chartData.value = res;
+  function handleSearch() {
+    tablePagination.current = 1;
+    fetchTableData();
   }
 
-  getStoreDataList();
-  getDataIndex();
-  getChartDataList();
+  function handleReset() {
+    tableSchoolId.value = [];
+    tableTimeType.value = '0';
+    tableDateRange.value = null;
+    tablePagination.current = 1;
+    fetchTableData();
+  }
 
-  async function getGoodsList() {
-    const res = await getGoodsData({ type: goodsBtn.value });
-    console.log(res, 'getGoodsList');
-    goodsDataList.value = res;
+  function handleTimeTypeChange() {
+    if (tableTimeType.value !== '3') {
+      tableDateRange.value = null;
+    }
   }
 
-  getGoodsList();
+  // ========== 初始化加载 ==========
+  fetchOverview();
+  fetchSchoolOverview();
+  fetchSchools();
+  fetchMonthlyData();
+  fetchTableData();
 </script>
 
-<style scoped></style>
+<style scoped>
+  .overview-card {
+    border: 1px solid #e8e8e8;
+    border-radius: 4px;
+    overflow: hidden;
+    background: #fff;
+  }
+
+  .overview-card.active-card {
+    border: 2px solid #1890ff;
+  }
+
+  .card-header {
+    text-align: center;
+    padding: 6px 0;
+    font-weight: bold;
+    font-size: 15px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  .card-body {
+    padding: 12px;
+  }
+
+  .table-section :deep(.ant-table-thead > tr > th) {
+    background-color: #c8956c;
+    color: #fff;
+    text-align: center;
+    font-weight: bold;
+  }
+</style>