Răsfoiți Sursa

feat: 站点切换器改为顶栏可搜索下拉并去掉整页刷新,11 个页面站点筛选联动

skyline 3 zile în urmă
părinte
comite
66d4a8426f

+ 53 - 0
admin-web-new/src/hooks/useStationScope.ts

@@ -0,0 +1,53 @@
+import { onMounted, onUnmounted } from "vue";
+import { emitter } from "@/utils/mitt";
+
+//Session 读取(与顶栏站点切换器写入格式一致:sessionStorage + JSON)
+const getCurrentStationId = () => {
+  const value = sessionStorage.getItem("currentStationId");
+  if (!value) return null;
+  try {
+    return JSON.parse(value);
+  } catch {
+    return null;
+  }
+};
+
+/**
+ * 站点范围组合式函数:页面站点筛选与全局"当前站点"联动
+ * 1. 页面初始化时,若站点筛选为空则默认选中全局当前站点(onMounted 中先于页面自身加载执行)
+ * 2. 全局站点切换时(顶栏站点选择器),联动更新页面筛选并刷新列表
+ *
+ * @param formQuery 页面查询条件对象(需包含 stationId 字段)
+ * @param loadData 页面数据加载方法,切换站点时以 refresh=true 重新加载
+ * @param options.follow 是否响应全局站点切换,默认 true
+ */
+export const useStationScope = (
+  formQuery: any,
+  loadData?: (refresh?: boolean) => void,
+  options?: { follow?: boolean }
+) => {
+  const follow = options?.follow !== false;
+
+  const onStationChange = (stationId: any) => {
+    if (!follow || !stationId) return;
+    //站点未变化时不重复加载
+    if (formQuery.stationId === stationId) return;
+    formQuery.stationId = stationId;
+    if (loadData) loadData(true);
+  };
+
+  onMounted(() => {
+    //初始化:站点筛选为空时默认选中全局当前站点
+    if (!formQuery.stationId) {
+      const currentStationId = getCurrentStationId();
+      if (currentStationId) {
+        formQuery.stationId = currentStationId;
+      }
+    }
+    emitter.on("stationChangeRefresh", onStationChange);
+  });
+
+  onUnmounted(() => {
+    emitter.off("stationChangeRefresh", onStationChange);
+  });
+};

+ 257 - 0
admin-web-new/src/layout/components/lay-navbar/StationSwitcher.vue

@@ -0,0 +1,257 @@
+<script setup lang="ts">
+import { reactive, computed, onMounted } from "vue";
+import { emitter } from "@/utils/mitt";
+import { getStationList } from "@/api/station";
+import Location from "~icons/ri/map-pin-line";
+
+defineOptions({ name: "StationSwitcher" });
+
+const Session = {
+  get: (key: string) => {
+    const value = sessionStorage.getItem(key);
+    return value ? JSON.parse(value) : null;
+  },
+  set: (key: string, value: any) => {
+    sessionStorage.setItem(key, JSON.stringify(value));
+  }
+};
+
+const state = reactive({
+  stationList: [] as Array<any>,
+  currentStationId: null as any,
+  options: [] as Array<any>,
+  loading: true,
+  open: false
+});
+
+const placeholder = computed(() => {
+  if (state.loading) return "加载站点中";
+  if (state.stationList.length === 0) return "暂无站点";
+  return "选择站点";
+});
+
+//站点切换:持久化并广播,各页面通过 useStationScope 联动刷新(不整页刷新)
+const handleStationChange = (stationId: string) => {
+  const station = state.stationList.find((k: any) => k.stationId === stationId);
+  if (station) {
+    Session.set("currentStationId", stationId);
+    emitter.emit("stationChangeRefresh", stationId);
+  }
+};
+
+const loadStationList = () => {
+  getStationList({ pageNum: 1, pageSize: 1024 })
+    .then((res: any) => {
+      //必须先结束 loading 再赋值选项:element-plus 在 loading 态下过滤结果为空,
+      //会导致选中项 label 解析失败、回退显示原始站点ID
+      state.loading = false;
+      const { list } = res || {};
+      state.stationList = list || [];
+      state.options = state.stationList.map((k: any) => ({
+        value: k.stationId,
+        label: k.stationName
+      }));
+      if (state.stationList.length > 0) {
+        //优先恢复上次选择的站点,其次默认第一个
+        const savedId = Session.get("currentStationId");
+        const current =
+          state.stationList.find((k: any) => k.stationId === savedId) || state.stationList[0];
+        state.currentStationId = current.stationId;
+        setTimeout(() => {
+          Session.set("currentStationId", current.stationId);
+          emitter.emit("stationChangeRefresh", current.stationId);
+        }, 200);
+      }
+    })
+    .catch(() => {
+      //列表加载失败由请求拦截器提示
+    })
+    .finally(() => {
+      state.loading = false;
+    });
+};
+
+onMounted(() => {
+  loadStationList();
+});
+</script>
+
+<template>
+  <div
+    id="header-station"
+    class="station-switcher"
+    :class="{ 'is-open': state.open, 'is-empty': !state.loading && state.stationList.length === 0 }"
+    title="切换当前查看的站点,页面数据范围会跟随变化"
+  >
+    <el-select-v2
+      v-model="state.currentStationId"
+      :options="state.options"
+      :loading="state.loading"
+      :loading-text="'正在加载站点…'"
+      :no-data-text="'没有匹配的站点'"
+      :disabled="!state.loading && state.stationList.length === 0"
+      :placeholder="placeholder"
+      filterable
+      popper-class="station-select-popper"
+      class="station-select"
+      @change="handleStationChange"
+      @visible-change="state.open = $event"
+    >
+      <template #prefix>
+        <IconifyIconOffline :icon="Location" :size="15" />
+      </template>
+      <template #header>
+        <span>站点范围</span>
+        <span>共 {{ state.stationList.length }} 个站点</span>
+      </template>
+    </el-select-v2>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.station-switcher {
+  display: flex;
+  align-items: center;
+  height: 48px;
+  padding: 0 4px;
+
+  .station-select {
+    width: 180px;
+
+    :deep(.el-select__wrapper) {
+      height: 34px;
+      min-height: 34px;
+      padding: 0 8px;
+      border-radius: 8px;
+      background: transparent;
+      box-shadow: none;
+      transition: background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+
+      &.is-hovering,
+      &.is-focused {
+        background: var(--el-fill-color-light);
+      }
+
+      &.is-disabled {
+        background: transparent;
+      }
+    }
+
+    :deep(.el-select__prefix) {
+      display: flex;
+      align-items: center;
+      margin-right: 6px;
+      color: inherit;
+      transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+    }
+
+    :deep(.el-select__placeholder),
+    :deep(.el-select__input) {
+      color: var(--el-text-color-primary);
+      font-size: 14px;
+      font-weight: 500;
+      transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+    }
+
+    //未选中任何站点时的占位:退一档
+    :deep(.el-select__placeholder.is-transparent) {
+      color: var(--el-text-color-placeholder);
+      font-weight: 400;
+    }
+
+    :deep(.el-select__caret) {
+      color: var(--el-text-color-regular);
+      opacity: 0.6;
+      transform-origin: center;
+      transition:
+        transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
+        opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
+        color 0.2s;
+    }
+  }
+
+  //悬停时文字与图标转为品牌色(禁用态除外)
+  &:not(.is-empty) .station-select:hover {
+    :deep(.el-select__wrapper .el-select__placeholder),
+    :deep(.el-select__wrapper .el-select__input),
+    :deep(.el-select__wrapper .el-select__prefix),
+    :deep(.el-select__wrapper .el-select__caret) {
+      color: var(--el-color-primary);
+      opacity: 1;
+    }
+  }
+
+  //展开时箭头翻转
+  &.is-open .station-select {
+    :deep(.el-select__caret) {
+      transform: rotate(180deg);
+      opacity: 1;
+      color: var(--el-color-primary);
+    }
+  }
+
+  //禁用态(无站点可用)
+  &:deep(.el-select__wrapper.is-disabled .el-select__placeholder),
+  &:deep(.el-select__wrapper.is-disabled .el-select__prefix),
+  &:deep(.el-select__wrapper.is-disabled .el-select__caret) {
+    color: var(--el-text-color-disabled);
+    opacity: 1;
+  }
+}
+</style>
+
+<style lang="scss">
+// 下拉面板(teleported,需全局样式)
+.station-select-popper {
+  .el-select-dropdown {
+    padding: 6px;
+    border: none;
+    border-radius: 12px;
+    box-shadow:
+      0 10px 15px -3px rgba(0, 0, 0, 0.08),
+      0 4px 6px -4px rgba(0, 0, 0, 0.06);
+  }
+
+  .el-select-dropdown__header {
+    display: flex;
+    justify-content: space-between;
+    padding: 4px 10px 6px;
+    font-size: 12px;
+    font-weight: 500;
+    color: var(--el-text-color-secondary);
+
+    span:last-child {
+      font-weight: 400;
+      color: var(--el-text-color-placeholder);
+    }
+  }
+
+  .el-select-dropdown__list {
+    padding: 2px;
+  }
+
+  .el-select-dropdown__item {
+    height: 34px;
+    margin: 2px 0;
+    padding: 0 10px;
+    border-radius: 8px;
+    font-size: 14px;
+  }
+
+  .el-select-dropdown__item.is-hovering {
+    background: var(--el-fill-color-light);
+  }
+
+  //当前站点:品牌色 + 加粗标记「你在这里」
+  .el-select-dropdown__item.is-selected {
+    color: var(--el-color-primary);
+    font-weight: 600;
+  }
+
+  .el-select-dropdown__empty {
+    padding: 14px 10px;
+    font-size: 13px;
+    color: var(--el-text-color-placeholder);
+  }
+}
+</style>

+ 3 - 108
admin-web-new/src/layout/components/lay-navbar/index.vue

@@ -11,10 +11,7 @@ import AccountSettingsIcon from "~icons/ri/user-settings-line";
 import LogoutCircleRLine from "~icons/ri/logout-circle-r-line";
 import Setting from "~icons/ri/settings-3-line";
 import Check from "~icons/ep/check";
-import Location from "~icons/ri/map-pin-line";
-import { getStationList } from "@/api/station";
-import { emitter } from "@/utils/mitt";
-import { reactive, computed, onMounted } from "vue";
+import StationSwitcher from "./StationSwitcher.vue";
 
 const {
   layout,
@@ -30,73 +27,6 @@ const {
 } = useNav();
 
 const { t, locale, translationCh, translationEn } = useTranslationLang();
-
-// Session 工具
-const Session = {
-  get: (key: string) => {
-    const value = sessionStorage.getItem(key);
-    return value ? JSON.parse(value) : null;
-  },
-  set: (key: string, value: any) => {
-    sessionStorage.setItem(key, JSON.stringify(value));
-  }
-};
-
-const state = reactive({
-  stationList: [] as any[],
-  currentStationId: null as string | null,
-  currentStationName: '' as string
-});
-
-// 加载站点列表
-const loadStationList = () => {
-  getStationList({ pageNum: 1, pageSize: 1024 }).then((res: any) => {
-    const list = res?.data?.list || res?.list || res?.data || res || [];
-    state.stationList = list;
-    
-    // 获取当前选中的站点
-    const savedStationId = Session.get("currentStationId");
-    if (savedStationId && list.length > 0) {
-      const currentStation = list.find((s: any) => s.stationId === savedStationId);
-      if (currentStation) {
-        state.currentStationId = currentStation.stationId;
-        state.currentStationName = currentStation.stationName;
-        emitter.emit("stationChangeRefresh", currentStation.stationId);
-      } else {
-        // 如果保存的站点不在列表中,选择第一个并持久化
-        state.currentStationId = list[0]?.stationId;
-        state.currentStationName = list[0]?.stationName;
-        Session.set("currentStationId", list[0]?.stationId);
-        emitter.emit("stationChangeRefresh", list[0]?.stationId);
-      }
-    } else if (list.length > 0) {
-      // 默认选择第一个站点并持久化
-      state.currentStationId = list[0]?.stationId;
-      state.currentStationName = list[0]?.stationName;
-      Session.set("currentStationId", list[0]?.stationId);
-      emitter.emit("stationChangeRefresh", list[0]?.stationId);
-    }
-  }).catch(() => {
-    console.warn('站点列表加载失败');
-  });
-};
-
-// 切换站点
-const handleStationChange = (station: any) => {
-  state.currentStationId = station.stationId;
-  state.currentStationName = station.stationName;
-  Session.set("currentStationId", station.stationId);
-  emitter.emit("stationChangeRefresh", station.stationId);
-
-  // 刷新页面以加载新站点数据
-  setTimeout(() => {
-    window.location.reload();
-  }, 300);
-};
-
-onMounted(() => {
-  loadStationList();
-});
 </script>
 
 <template>
@@ -110,26 +40,8 @@ onMounted(() => {
 
     <div v-if="layout === 'vertical'" class="vertical-header-right">
       <!-- 站点切换 -->
-      <el-dropdown id="header-station" trigger="click" @command="handleStationChange">
-        <div class="station-switcher navbar-bg-hover cursor-pointer outline-hidden flex items-center">
-          <IconifyIconOffline :icon="Location" class="station-icon" />
-          <span class="dark:text-white truncate">{{ state.currentStationName || '选择站点' }}</span>
-          <i class="ri-arrow-down-s-line ml-1" />
-        </div>
-        <template #dropdown>
-          <el-dropdown-menu>
-            <el-dropdown-item 
-              v-for="station in state.stationList" 
-              :key="station.stationId" 
-              :command="station"
-              :disabled="state.currentStationId === station.stationId"
-            >
-              {{ station.stationName }}
-            </el-dropdown-item>
-          </el-dropdown-menu>
-        </template>
-      </el-dropdown>
-      
+      <StationSwitcher />
+
       <!-- 菜单搜索 -->
       <LaySearch id="header-search" />
       <!-- 国际化 -->
@@ -205,23 +117,6 @@ onMounted(() => {
 </template>
 
 <style lang="scss" scoped>
-.station-switcher {
-  height: 48px;
-  padding: 0 12px;
-  min-width: 120px;
-  max-width: 200px;
-
-  .station-icon {
-    margin-right: 6px;
-    font-size: 16px;
-    flex-shrink: 0;
-  }
-
-  span {
-    font-size: 14px;
-  }
-}
-
 .navbar {
   width: 100%;
   height: 48px;

+ 6 - 3
admin-web-new/src/views/admin/dashboard/index.vue

@@ -309,9 +309,12 @@ const loadAll = () => {
 
 onMounted(() => {
   emitter.on("stationChangeRefresh", (stationId) => {
-    state.currentStationId = stationId;
-    state.myCharts = [];
-    nextTick(() => loadAll());
+    //站点未变化时不重复加载
+    if (stationId && stationId !== state.currentStationId) {
+      state.currentStationId = stationId;
+      state.myCharts = [];
+      nextTick(() => loadAll());
+    }
   });
 
   const currentStationId = Session.get("currentStationId");

+ 4 - 0
admin-web-new/src/views/admin/finance/settlement.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick } from "vue";
 import { getSettlementList, triggerSettlement, confirmSettlement } from "@/api/finance";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { getDictOptions, formatDict, getDictColor } from "@/utils/dict";
 
@@ -51,6 +52,9 @@ const state = reactive({
   get statusOptions() { return getDictOptions("Settlement.status"); }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadStationOptions();
   loadData();

+ 4 - 0
admin-web-new/src/views/admin/finance/split-record.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick } from "vue";
 import { getSplitRecordList } from "@/api/finance";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage } from "element-plus";
 import { ExtDLabel, ExtDSelect } from "@/components/ExtForm";
 
@@ -41,6 +42,9 @@ const state = reactive({
   }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadStationOptions();
   loadData();

+ 4 - 0
admin-web-new/src/views/admin/finance/withdraw.vue

@@ -2,6 +2,7 @@
 import { reactive, onMounted, ref, nextTick } from "vue";
 import { getWithdrawList } from "@/api/finance";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { http } from "@/utils/http";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { getDictOptions, formatDict, getDictColor } from "@/utils/dict";
@@ -46,6 +47,9 @@ const state = reactive({
   get paymentStatusOptions() { return getDictOptions("WithdrawnRecord.paymentStatus"); }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadData();
   nextTick(() => {

+ 6 - 0
admin-web-new/src/views/admin/investor/index.vue

@@ -2,6 +2,7 @@
 import { reactive, onMounted, ref, nextTick } from "vue";
 import { getInvestorList, removeInvestor } from "@/api/investor";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage, ElMessageBox } from "element-plus";
 import InvestorDialog from "./dialog.vue";
 
@@ -18,6 +19,7 @@ interface ColumnItem {
 
 const state = reactive({
   formQuery: {
+    stationId: "",
     stationName: "",
     contactName: ""
   },
@@ -44,6 +46,9 @@ const state = reactive({
   }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadData();
   nextTick(() => {
@@ -88,6 +93,7 @@ const handleSearch = () => {
 
 const handleReset = () => {
   state.formQuery = {
+    stationId: "",
     stationName: "",
     contactName: ""
   };

+ 4 - 0
admin-web-new/src/views/admin/parking-coupon-record/index.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick } from "vue";
 import { getParkingCouponRecordPage } from "@/api/parking-coupon";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 
 defineOptions({ name: "AdminParkingCouponRecord" });
 
@@ -36,6 +37,9 @@ const state = reactive({
   }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadStations();
   loadData();

+ 4 - 0
admin-web-new/src/views/admin/station/account.vue

@@ -2,6 +2,7 @@
 import { reactive, onMounted, ref, nextTick } from "vue";
 import { getStationAccount, applyWithdrawn, getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage, ElMessageBox } from "element-plus";
 
 defineOptions({
@@ -36,6 +37,9 @@ const state = reactive({
   stationOptions: [] as Array<{ stationId: string; stationName: string }>
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadStationOptions();
   loadData();

+ 4 - 0
admin-web-new/src/views/admin/station/device.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick, watch } from "vue";
 import { getStationDeviceList, removeDevice, getStationList } from "@/api/station";
 import { stopDevice } from "@/api/device";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { useRoute, useRouter } from "vue-router";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { ExtDLabel, ExtDSelect } from "@/components/ExtForm";
@@ -77,6 +78,9 @@ watch(
   }
 );
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   loadStationOptions();
   if (state.stationId) {

+ 16 - 0
admin-web-new/src/views/admin/station/fault/index.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick, onBeforeUnmount } from "vue";
 import { getSubscriberList, generateQrcode, unsubscribe, getFaultRecords } from "@/api/fault-subscriber";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage, ElMessageBox } from "element-plus";
 
 defineOptions({ name: "AdminStationFault" });
@@ -45,8 +46,23 @@ const faultTypeLabel = (type: string) => {
   }
 };
 
+//站点筛选与全局当前站点联动:切换站点时同步订阅与故障记录两个页签的筛选
+const reloadFaultPage = (refresh?: boolean) => {
+  state.faultQuery.stationId = state.formQuery.stationId;
+  if (state.activeTab === "faultRecord") {
+    loadFaultRecords(!!refresh);
+  }
+  loadSubscribers();
+};
+useStationScope(state.formQuery, (refresh?: boolean) => reloadFaultPage(refresh));
+
 onMounted(() => {
   loadStations();
+  //默认选中全局当前站点时,自动加载订阅列表
+  if (state.formQuery.stationId) {
+    state.faultQuery.stationId = state.formQuery.stationId;
+    loadSubscribers();
+  }
   nextTick(() => {
     const bodyHeight = document.body.clientHeight;
     state.tableHeight = bodyHeight - 280;

+ 4 - 0
admin-web-new/src/views/admin/statistics/daily-stat.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick } from "vue";
 import { getDailyStatList, syncDailyStatBatch } from "@/api/stat";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage, ElMessageBox } from "element-plus";
 
 defineOptions({ name: "AdminDailyStat" });
@@ -45,6 +46,9 @@ const state = reactive({
   }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   const yesterday = new Date();
   yesterday.setDate(yesterday.getDate() - 1);

+ 4 - 0
admin-web-new/src/views/admin/statistics/month-stat.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick } from "vue";
 import { getMonthStatList, syncMonthStat } from "@/api/stat";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { useStationScope } from "@/hooks/useStationScope";
 import { ElMessage, ElMessageBox } from "element-plus";
 
 defineOptions({ name: "AdminMonthStat" });
@@ -44,6 +45,9 @@ const state = reactive({
   }
 });
 
+//站点筛选与全局当前站点联动(回调懒引用,规避 loadData 声明顺序的 TDZ 错误)
+useStationScope(state.formQuery, (refresh?: boolean) => loadData(refresh));
+
 onMounted(() => {
   const now = new Date();
   const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);