From 8151c3253dc287a79914bc08414af17dfb380c96 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 14 十月 2026 14:34:33 +0800
Subject: [PATCH] refactor(首页): 展示模块改为跟随菜单权限自动隐藏

---
 src/views/mes/home/modules/work-order-chart.vue            |   13 
 src/views/dashboard/analytics/modules/quick-actions.vue    |   17 
 src/utils/use-home-access.ts                               |   56 +++++
 src/views/mes/home/index.vue                               |   67 +++++
 src/views/dashboard/analytics/modules/biz-cards.vue        |   14 
 src/views/mes/home/modules/kpi-cards.vue                   |   55 ++++
 /dev/null                                                  |   30 --
 src/views/mes/home/modules/production-trend.vue            |   12 
 src/views/dashboard/analytics/modules/alert-panel.vue      |   13 
 src/views/dashboard/analytics/modules/kpi-cards.vue        |   28 +-
 src/views/dashboard/analytics/modules/production-trend.vue |    7 
 src/views/dashboard/analytics/index.vue                    |   53 ++++
 src/views/mes/home/modules/shortcuts.vue                   |   73 +-----
 src/views/mes/home/data.ts                                 |  101 +++++++++
 src/views/mes/home/modules/alert-panel.vue                 |   64 +++--
 15 files changed, 420 insertions(+), 183 deletions(-)

diff --git a/src/utils/use-home-access.ts b/src/utils/use-home-access.ts
new file mode 100644
index 0000000..0fe0ba0
--- /dev/null
+++ b/src/utils/use-home-access.ts
@@ -0,0 +1,56 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { computed } from 'vue';
+
+import { useAccess } from '@vben/access';
+import { useAccessStore } from '@vben/stores';
+
+/** 棣栭〉甯搁┗锛屼笟鍔℃暟鎹拰鍏ュ彛浠嶉渶閬靛惊褰撳墠鐢ㄦ埛鐨勮彍鍗曟潈闄愩�� */
+export function useHomeAccess() {
+  const accessStore = useAccessStore();
+  const { hasAccessByCodes } = useAccess();
+
+  function canQuery(permission: string) {
+    return hasAccessByCodes([permission, '*:*:*']);
+  }
+
+  function canNavigate(name: string) {
+    function contains(routes: RouteRecordRaw[]): boolean {
+      return routes.some((route) => {
+        if (route.meta?.menuVisibleWithForbidden) return false;
+        return route.name === name || contains(route.children ?? []);
+      });
+    }
+    return contains(accessStore.accessRoutes);
+  }
+
+  /**
+   * 璇ヨ彍鍗曞褰撳墠鐢ㄦ埛鏄惁銆屽紑鐫�銆嶁�斺�� 鐢ㄤ簬棣栭〉妯″潡寮�鍏炽��
+   *
+   * 鍚庣鑿滃崟 `visible = 0` 浼氳鍓嶇杞垚璺敱鐨� `meta.hideInMenu`锛岃繖绫昏矾鐢变粛鐒舵敞鍐屽湪
+   * router 閲岋紙`canNavigate` 涓� true锛岄〉闈㈠彲浠ョ洿鎺ヨ闂級锛屽彧鏄暣妫靛瓙鏍戜笉浼氬嚭鐜板湪渚ц竟鏍忋��
+   * 鎵�浠ュ垽鏂�岃繖涓ā鍧楁湁娌℃湁寮�銆嶄笉鑳藉彧鐪嬭矾鐢辨槸鍚﹀瓨鍦紝杩樿鐪嬪畠鍜屽畠鐨勬瘡涓�绾х鍏堟槸鍚﹀彲瑙併��
+   */
+  function canSeeMenu(name: string) {
+    function find(
+      routes: RouteRecordRaw[],
+      ancestorHidden: boolean,
+    ): boolean | undefined {
+      for (const route of routes) {
+        const hidden =
+          ancestorHidden ||
+          route.meta?.hideInMenu === true ||
+          route.meta?.menuVisibleWithForbidden === true;
+        if (route.name === name) return !hidden;
+        const found = find(route.children ?? [], hidden);
+        if (found !== undefined) return found;
+      }
+      return undefined;
+    }
+    return find(accessStore.accessRoutes, false) === true;
+  }
+
+  const canViewHome = computed(() => canQuery('mes:home:query'));
+
+  return { canNavigate, canQuery, canSeeMenu, canViewHome };
+}
diff --git a/src/views/dashboard/analytics/index.vue b/src/views/dashboard/analytics/index.vue
index 907fac8..cdf7447 100644
--- a/src/views/dashboard/analytics/index.vue
+++ b/src/views/dashboard/analytics/index.vue
@@ -8,6 +8,8 @@
 
 import { getHomeSummary } from '#/api/mes/home';
 
+import { useHomeAccess } from '#/utils/use-home-access';
+
 import { defaultSummary } from './data';
 import AiInsights from './modules/ai-insights.vue';
 import AlertPanel from './modules/alert-panel.vue';
@@ -15,13 +17,34 @@
 import KpiCards from './modules/kpi-cards.vue';
 import ProductionTrend from './modules/production-trend.vue';
 import QuickActions from './modules/quick-actions.vue';
-import { useHomeAccess } from './use-home-access';
 
 defineOptions({ name: 'DashboardAnalytics' });
 
 const router = useRouter();
 const summary = ref(defaultSummary);
-const { canNavigate, canViewHome } = useHomeAccess();
+const { canNavigate, canSeeMenu, canViewHome } = useHomeAccess();
+
+// 棣栭〉妯″潡璺熼殢渚ц竟鏍忚彍鍗曪細鑿滃崟娌″紑鐨勬ā鍧楁暣鍧椾笉灞曠ず
+/** MES 鐢熶骇 KPI锛氫换鎰忎竴寮犲崱鐨勮彍鍗曞紑鐫�灏辨覆鏌撴暣琛� */
+const showKpi = computed(
+  () =>
+    canViewHome.value &&
+    ['MesProWorkOrder', 'MesProFeedback', 'MesQcIqc', 'MesDvMachinery'].some(
+      (name) => canSeeMenu(name),
+    ),
+);
+/** AI 鏅鸿兘鍒嗘瀽锛欰I 妯″潡鐨勮彍鍗曞紑鐫�鎵嶆覆鏌� */
+const showAiInsights = computed(
+  () => canViewHome.value && canSeeMenu('AiChat'),
+);
+/** 鐢熶骇瓒嬪娍锛氭暟鎹潵鑷敓浜ф姤宸� */
+const showTrend = computed(
+  () => canViewHome.value && canSeeMenu('MesProFeedback'),
+);
+/** 宸ュ崟鐘舵�佸垎甯冿細鏁版嵁鏉ヨ嚜鐢熶骇璁㈠崟 */
+const showWorkOrderStatus = computed(
+  () => canViewHome.value && canSeeMenu('MesProWorkOrder'),
+);
 
 const greeting = computed(() => {
   const hour = new Date().getHours();
@@ -76,21 +99,29 @@
     </div>
 
     <!-- MES 鐢熶骇 KPI -->
-    <KpiCards v-if="canViewHome" :summary="summary" @navigate="handleNavigate" />
+    <KpiCards v-if="showKpi" :summary="summary" @navigate="handleNavigate" />
 
     <!-- ERP/WMS/BPM 缁忚惀 KPI -->
     <BizCards @navigate="handleNavigate" />
 
     <!-- AI 鏅鸿兘鍒嗘瀽 -->
     <AiInsights
-      v-if="canViewHome && canNavigate('MesProWorkOrder')"
+      v-if="showAiInsights"
       :active-order-count="summary.workOrderActiveCount"
       @navigate="handleNavigate"
     />
 
     <!-- 鐢熶骇瓒嬪娍 + 棰勮闈㈡澘 -->
-    <Row v-if="canViewHome" :gutter="16" class="mb-4">
-      <Col :lg="14" :md="24" :sm="24" :xl="14" :xs="24" class="mb-4">
+    <Row v-if="showTrend || showWorkOrderStatus" :gutter="16" class="mb-4">
+      <Col
+        v-if="showTrend"
+        :lg="showWorkOrderStatus ? 14 : 24"
+        :md="24"
+        :sm="24"
+        :xl="showWorkOrderStatus ? 14 : 24"
+        :xs="24"
+        class="mb-4"
+      >
         <div class="glass-card rounded-2xl p-5">
           <h3 class="mb-4 text-base font-semibold text-gray-800 dark:text-gray-100">
             鐢熶骇瓒嬪娍锛堣繎7澶╋級
@@ -98,7 +129,15 @@
           <ProductionTrend />
         </div>
       </Col>
-      <Col :lg="10" :md="24" :sm="24" :xl="10" :xs="24" class="mb-4">
+      <Col
+        v-if="showWorkOrderStatus"
+        :lg="showTrend ? 10 : 24"
+        :md="24"
+        :sm="24"
+        :xl="showTrend ? 10 : 24"
+        :xs="24"
+        class="mb-4"
+      >
         <AlertPanel
           :repair-active-count="summary.repairActiveCount"
           @navigate="handleNavigate"
diff --git a/src/views/dashboard/analytics/modules/alert-panel.vue b/src/views/dashboard/analytics/modules/alert-panel.vue
index 4911ffa..59d562c 100644
--- a/src/views/dashboard/analytics/modules/alert-panel.vue
+++ b/src/views/dashboard/analytics/modules/alert-panel.vue
@@ -12,7 +12,7 @@
 
 import { getWorkOrderStatusDistribution } from '#/api/mes/home';
 
-import { useHomeAccess } from '../use-home-access';
+import { useHomeAccess } from '#/utils/use-home-access';
 
 defineOptions({ name: 'DashboardAlertPanel' });
 
@@ -27,10 +27,11 @@
 const chartRef = ref<EchartsUIType>();
 const { renderEcharts } = useEcharts(chartRef);
 const loading = ref(false);
-const { canNavigate, canViewHome } = useHomeAccess();
+const { canSeeMenu } = useHomeAccess();
 
 async function loadStatus() {
-  if (!canViewHome.value) return;
+  // 宸ュ崟鐘舵�佸垎甯冨彇鑷敓浜ц鍗曪紝璁㈠崟鑿滃崟娌″紑灏变笉璇锋眰
+  if (!canSeeMenu('MesProWorkOrder')) return;
   loading.value = true;
   try {
     const data = await getWorkOrderStatusDistribution();
@@ -72,9 +73,9 @@
     <!-- 棰勮鍗$墖琛� -->
     <div class="grid grid-cols-1 gap-3">
       <div
-        v-if="canNavigate('MesProRepair')"
+        v-if="canSeeMenu('MesDvRepair')"
         class="glass-tilt group cursor-pointer overflow-hidden rounded-2xl border-0 p-4"
-        @click="emit('navigate', 'MesProRepair')"
+        @click="emit('navigate', 'MesDvRepair')"
       >
         <div class="relative z-10 flex items-center gap-3">
           <div class="flex size-10 items-center justify-center rounded-lg bg-amber-50 text-amber-600 shadow-sm dark:bg-amber-500/15 dark:text-amber-400">
@@ -92,7 +93,7 @@
     </div>
 
     <!-- 宸ュ崟鐘舵�佸浘 -->
-    <div class="glass-tilt rounded-2xl p-5">
+    <div v-if="canSeeMenu('MesProWorkOrder')" class="glass-tilt rounded-2xl p-5">
       <h4 class="mb-3 text-sm font-semibold text-gray-800 dark:text-gray-100">
         宸ュ崟鐘舵�佸垎甯�
       </h4>
diff --git a/src/views/dashboard/analytics/modules/biz-cards.vue b/src/views/dashboard/analytics/modules/biz-cards.vue
index 5fc8ff1..4efb8d5 100644
--- a/src/views/dashboard/analytics/modules/biz-cards.vue
+++ b/src/views/dashboard/analytics/modules/biz-cards.vue
@@ -11,7 +11,7 @@
 import { getMaterialStockPage } from '#/api/mes/wm/materialstock';
 import { getTaskTodoPage } from '#/api/bpm/task';
 
-import { useHomeAccess } from '../use-home-access';
+import { useHomeAccess } from '#/utils/use-home-access';
 
 defineOptions({ name: 'DashboardBizCards' });
 
@@ -23,11 +23,11 @@
 const saleCount = ref(0);
 const stockCount = ref(0);
 const bpmTodoCount = ref(0);
-const { canNavigate, canQuery } = useHomeAccess();
-const canViewPurchase = computed(() => canQuery('erp:purchase-order:query') && canNavigate('ErpPurchaseOrder'));
-const canViewSale = computed(() => canQuery('erp:sale-order:query') && canNavigate('ErpSaleOrder'));
-const canViewStock = computed(() => canQuery('mes:wm-material-stock:query') && canNavigate('MesWmMaterialStock'));
-const canViewBpmTodo = computed(() => canQuery('bpm:task:query') && canNavigate('BpmTaskTodo'));
+const { canSeeMenu, canQuery } = useHomeAccess();
+const canViewPurchase = computed(() => canQuery('erp:purchase-order:query') && canSeeMenu('ErpPurchaseOrder'));
+const canViewSale = computed(() => canQuery('erp:sale-order:query') && canSeeMenu('ErpSaleOrder'));
+const canViewStock = computed(() => canQuery('mes:wm-material-stock:query') && canSeeMenu('MesWmMaterialStock'));
+const canViewBpmTodo = computed(() => canQuery('bpm:task:query') && canSeeMenu('BpmTodoTask'));
 
 async function loadPurchase() {
   if (!canViewPurchase.value) return;
@@ -183,7 +183,7 @@
         :style="tiltStyle('bpm')"
         @mousemove="(e: MouseEvent) => onMouseMove(e, 'bpm')"
         @mouseleave="onMouseLeave('bpm')"
-        @click="emit('navigate', 'BpmTaskTodo')"
+        @click="emit('navigate', 'BpmTodoTask')"
       >
         <div class="relative z-10 flex items-center gap-4 p-5">
           <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-red-500 to-rose-400 shadow-lg shadow-red-500/25 transition-transform duration-300 group-hover:scale-110">
diff --git a/src/views/dashboard/analytics/modules/kpi-cards.vue b/src/views/dashboard/analytics/modules/kpi-cards.vue
index 88853ef..733d378 100644
--- a/src/views/dashboard/analytics/modules/kpi-cards.vue
+++ b/src/views/dashboard/analytics/modules/kpi-cards.vue
@@ -8,11 +8,11 @@
 
 import { Col, Row } from 'ant-design-vue';
 
-import { useHomeAccess } from '../use-home-access';
+import { useHomeAccess } from '#/utils/use-home-access';
 
 defineOptions({ name: 'DashboardKpiCards' });
 
-const { canNavigate } = useHomeAccess();
+const { canSeeMenu } = useHomeAccess();
 
 const props = defineProps<{
   summary: MesHomeApi.Summary;
@@ -63,14 +63,14 @@
 <template>
   <Row :gutter="16">
     <!-- 鐢熶骇璁㈠崟 -->
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col v-if="canSeeMenu('MesProWorkOrder')" :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
       <div
         class="glass-tilt group overflow-hidden rounded-2xl border-0"
-        :class="{ 'cursor-pointer': canNavigate('MesProWorkOrder') }"
+        :class="{ 'cursor-pointer': canSeeMenu('MesProWorkOrder') }"
         :style="tiltStyle('order')"
         @mousemove="(e: MouseEvent) => onMouseMove(e, 'order')"
         @mouseleave="onMouseLeave('order')"
-        @click="canNavigate('MesProWorkOrder') && emit('navigate', 'MesProWorkOrder')"
+        @click="canSeeMenu('MesProWorkOrder') && emit('navigate', 'MesProWorkOrder')"
       >
         <div class="relative z-10 flex items-center gap-4 p-5">
           <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-cyan-400 shadow-lg shadow-blue-500/25 transition-transform duration-300 group-hover:scale-110">
@@ -95,14 +95,14 @@
     </Col>
 
     <!-- 浠婃棩浜ч噺 -->
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col v-if="canSeeMenu('MesProFeedback')" :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
       <div
         class="glass-tilt group overflow-hidden rounded-2xl border-0"
-        :class="{ 'cursor-pointer': canNavigate('MesProFeedback') }"
+        :class="{ 'cursor-pointer': canSeeMenu('MesProFeedback') }"
         :style="tiltStyle('output')"
         @mousemove="(e: MouseEvent) => onMouseMove(e, 'output')"
         @mouseleave="onMouseLeave('output')"
-        @click="canNavigate('MesProFeedback') && emit('navigate', 'MesProFeedback')"
+        @click="canSeeMenu('MesProFeedback') && emit('navigate', 'MesProFeedback')"
       >
         <div class="relative z-10 flex items-center gap-4 p-5">
           <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-green-400 shadow-lg shadow-emerald-500/25 transition-transform duration-300 group-hover:scale-110">
@@ -127,14 +127,14 @@
     </Col>
 
     <!-- 璐ㄩ噺鍚堟牸鐜� -->
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col v-if="canSeeMenu('MesProFeedback')" :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
       <div
         class="glass-tilt group overflow-hidden rounded-2xl border-0"
-        :class="{ 'cursor-pointer': canNavigate('MesQcRecord') }"
+        :class="{ 'cursor-pointer': canSeeMenu('MesProFeedback') }"
         :style="tiltStyle('quality')"
         @mousemove="(e: MouseEvent) => onMouseMove(e, 'quality')"
         @mouseleave="onMouseLeave('quality')"
-        @click="canNavigate('MesQcRecord') && emit('navigate', 'MesQcRecord')"
+        @click="canSeeMenu('MesProFeedback') && emit('navigate', 'MesProFeedback')"
       >
         <div class="relative z-10 flex items-center gap-4 p-5">
           <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-orange-500 to-amber-400 shadow-lg shadow-orange-500/25 transition-transform duration-300 group-hover:scale-110">
@@ -161,14 +161,14 @@
     </Col>
 
     <!-- 璁惧杩愯鐜� -->
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col v-if="canSeeMenu('MesDvMachinery')" :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
       <div
         class="glass-tilt group overflow-hidden rounded-2xl border-0"
-        :class="{ 'cursor-pointer': canNavigate('MesDvMachinery') }"
+        :class="{ 'cursor-pointer': canSeeMenu('MesDvMachinery') }"
         :style="tiltStyle('equip')"
         @mousemove="(e: MouseEvent) => onMouseMove(e, 'equip')"
         @mouseleave="onMouseLeave('equip')"
-        @click="canNavigate('MesDvMachinery') && emit('navigate', 'MesDvMachinery')"
+        @click="canSeeMenu('MesDvMachinery') && emit('navigate', 'MesDvMachinery')"
       >
         <div class="relative z-10 flex items-center gap-4 p-5">
           <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-violet-500 to-purple-400 shadow-lg shadow-violet-500/25 transition-transform duration-300 group-hover:scale-110">
diff --git a/src/views/dashboard/analytics/modules/production-trend.vue b/src/views/dashboard/analytics/modules/production-trend.vue
index 45bc2bb..cff6e5c 100644
--- a/src/views/dashboard/analytics/modules/production-trend.vue
+++ b/src/views/dashboard/analytics/modules/production-trend.vue
@@ -8,16 +8,17 @@
 
 import { getProductionTrend } from '#/api/mes/home';
 
-import { useHomeAccess } from '../use-home-access';
+import { useHomeAccess } from '#/utils/use-home-access';
 
 defineOptions({ name: 'DashboardProductionTrend' });
 
 const chartRef = ref<EchartsUIType>();
 const { renderEcharts } = useEcharts(chartRef);
-const { canViewHome } = useHomeAccess();
+const { canSeeMenu } = useHomeAccess();
 
 async function loadTrend() {
-  if (!canViewHome.value) return;
+  // 鐢熶骇瓒嬪娍鍙栬嚜鐢熶骇鎶ュ伐锛屾姤宸ヨ彍鍗曟病寮�灏变笉璇锋眰
+  if (!canSeeMenu('MesProFeedback')) return;
   const data = await getProductionTrend(7);
   const dates = data.map((d: MesHomeApi.ProductionTrend) => d.date.slice(5));
   const quantities = data.map((d: MesHomeApi.ProductionTrend) => d.quantity);
diff --git a/src/views/dashboard/analytics/modules/quick-actions.vue b/src/views/dashboard/analytics/modules/quick-actions.vue
index d7cc976..58115d0 100644
--- a/src/views/dashboard/analytics/modules/quick-actions.vue
+++ b/src/views/dashboard/analytics/modules/quick-actions.vue
@@ -3,7 +3,7 @@
 
 import { IconifyIcon } from '@vben/icons';
 
-import { useHomeAccess } from '../use-home-access';
+import { useHomeAccess } from '#/utils/use-home-access';
 
 defineOptions({ name: 'DashboardQuickActions' });
 
@@ -22,16 +22,17 @@
 const actions: QuickAction[] = [
   { title: '鐢熶骇宸ュ崟', icon: 'lucide:file-text', route: 'MesProWorkOrder', gradient: 'from-blue-500 to-cyan-500', shadow: 'shadow-blue-500/15' },
   { title: '鐢熶骇鎶ュ伐', icon: 'lucide:check-square', route: 'MesProFeedback', gradient: 'from-emerald-500 to-green-500', shadow: 'shadow-emerald-500/15' },
-  { title: '璐ㄦ绠$悊', icon: 'lucide:clipboard-check', route: 'MesQcRecord', gradient: 'from-orange-500 to-amber-500', shadow: 'shadow-orange-500/15' },
+  { title: '璐ㄦ绠$悊', icon: 'lucide:clipboard-check', route: 'MesQcIqc', gradient: 'from-orange-500 to-amber-500', shadow: 'shadow-orange-500/15' },
   { title: '璁惧鐐规', icon: 'lucide:settings', route: 'MesDvMachinery', gradient: 'from-violet-500 to-purple-500', shadow: 'shadow-violet-500/15' },
-  { title: '鐢熶骇鎺掔▼', icon: 'lucide:calendar-range', route: 'MesProScheduling', gradient: 'from-cyan-500 to-teal-500', shadow: 'shadow-cyan-500/15' },
-  { title: '鐗╂枡搴撳瓨', icon: 'lucide:package-search', route: 'WlsMaterialStock', gradient: 'from-indigo-500 to-blue-500', shadow: 'shadow-indigo-500/15' },
-  { title: '宸ヨ壓璺嚎', icon: 'lucide:route', route: 'MesPdRouting', gradient: 'from-teal-500 to-emerald-500', shadow: 'shadow-teal-500/15' },
-  { title: '鏁版嵁鎶ヨ〃', icon: 'lucide:bar-chart-big', route: 'MesHomeIndex', gradient: 'from-pink-500 to-rose-500', shadow: 'shadow-pink-500/15' },
+  { title: '鐢熶骇鎺掔▼', icon: 'lucide:calendar-range', route: 'MesProTask', gradient: 'from-cyan-500 to-teal-500', shadow: 'shadow-cyan-500/15' },
+  { title: '鐗╂枡搴撳瓨', icon: 'lucide:package-search', route: 'MesWmMaterialStock', gradient: 'from-indigo-500 to-blue-500', shadow: 'shadow-indigo-500/15' },
+  { title: '宸ヨ壓璺嚎', icon: 'lucide:route', route: 'MesProcessDesignRoute', gradient: 'from-teal-500 to-emerald-500', shadow: 'shadow-teal-500/15' },
 ];
 
-const { canNavigate } = useHomeAccess();
-const accessibleActions = computed(() => actions.filter((action) => canNavigate(action.route)));
+const { canSeeMenu } = useHomeAccess();
+const accessibleActions = computed(() =>
+  actions.filter((action) => canSeeMenu(action.route)),
+);
 </script>
 
 <template>
diff --git a/src/views/dashboard/analytics/use-home-access.ts b/src/views/dashboard/analytics/use-home-access.ts
deleted file mode 100644
index 65df114..0000000
--- a/src/views/dashboard/analytics/use-home-access.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-import { computed } from 'vue';
-
-import { useAccess } from '@vben/access';
-import { useAccessStore } from '@vben/stores';
-
-/** 棣栭〉甯搁┗锛屼笟鍔℃暟鎹拰鍏ュ彛浠嶉渶閬靛惊褰撳墠鐢ㄦ埛鐨勮彍鍗曟潈闄愩�� */
-export function useHomeAccess() {
-  const accessStore = useAccessStore();
-  const { hasAccessByCodes } = useAccess();
-
-  function canQuery(permission: string) {
-    return hasAccessByCodes([permission, '*:*:*']);
-  }
-
-  function canNavigate(name: string) {
-    function contains(routes: RouteRecordRaw[]): boolean {
-      return routes.some((route) => {
-        if (route.meta?.menuVisibleWithForbidden) return false;
-        return route.name === name || contains(route.children ?? []);
-      });
-    }
-    return contains(accessStore.accessRoutes);
-  }
-
-  const canViewHome = computed(() => canQuery('mes:home:query'));
-
-  return { canNavigate, canQuery, canViewHome };
-}
diff --git a/src/views/mes/home/data.ts b/src/views/mes/home/data.ts
index 3189c82..e737f33 100644
--- a/src/views/mes/home/data.ts
+++ b/src/views/mes/home/data.ts
@@ -2,6 +2,107 @@
 
 import { MesProWorkOrderStatusEnum } from '@vben/constants';
 
+/** 棣栭〉鍚勬ā鍧椾緷璧栫殑鑿滃崟锛坮oute.name = 鍚庣鑿滃崟鐨� component_name锛夛細鑿滃崟娌″紑鐨勬ā鍧楁暣鍧椾笉灞曠ず */
+export const HOME_BLOCK_MENUS = {
+  /** 瀹夌伅鍛煎彨 */
+  andon: 'MesProAndonRecord',
+  /** 鍒拌揣閫氱煡 */
+  arrivalNotice: 'MesWmArrivalNotice',
+  /** 鐢熶骇鎶ュ伐 */
+  feedback: 'MesProFeedback',
+  /** 璁惧鍙拌处 */
+  machinery: 'MesDvMachinery',
+  /** 搴撳瓨鐜版湁閲� */
+  materialStock: 'MesWmMaterialStock',
+  /** 宸ュ簭娴佽浆锛堟祦杞崱锛� */
+  processCard: 'MesProCard',
+  /** 鐢熶骇鎺掍骇锛堢敓浜т换鍔★級 */
+  proTask: 'MesProTask',
+  /** 鏉ユ枡妫�楠� */
+  qcIqc: 'MesQcIqc',
+  /** 缁翠慨鍗� */
+  repair: 'MesDvRepair',
+  /** 鐢熶骇璁㈠崟 */
+  workOrder: 'MesProWorkOrder',
+} as const;
+
+/** 鐢熶骇 KPI 鍗$墖渚濊禆鐨勮彍鍗� */
+export const KPI_MENUS: string[] = [
+  HOME_BLOCK_MENUS.workOrder,
+  HOME_BLOCK_MENUS.feedback,
+  HOME_BLOCK_MENUS.machinery,
+];
+
+/** 寰呭姙涓庡紓甯告潯鐩緷璧栫殑鑿滃崟 */
+export const ALERT_MENUS: string[] = [
+  HOME_BLOCK_MENUS.andon,
+  HOME_BLOCK_MENUS.repair,
+  HOME_BLOCK_MENUS.workOrder,
+];
+
+/** 蹇嵎鍏ュ彛锛氬悕绉般�佸浘鏍囥�佸簳鑹蹭笌渚濊禆鐨勮彍鍗� */
+export const HOME_SHORTCUTS: {
+  bgColor: string;
+  icon: string;
+  menu: string;
+  name: string;
+}[] = [
+  {
+    bgColor: '#409EFF',
+    icon: 'lucide:file-text',
+    menu: HOME_BLOCK_MENUS.workOrder,
+    name: '鐢熶骇璁㈠崟',
+  },
+  {
+    bgColor: '#67C23A',
+    icon: 'lucide:edit',
+    menu: HOME_BLOCK_MENUS.feedback,
+    name: '鐢熶骇鎶ュ伐',
+  },
+  {
+    bgColor: '#E6A23C',
+    icon: 'lucide:search',
+    menu: HOME_BLOCK_MENUS.qcIqc,
+    name: '璐ㄩ噺妫�楠�',
+  },
+  {
+    bgColor: '#F56C6C',
+    icon: 'lucide:box',
+    menu: HOME_BLOCK_MENUS.materialStock,
+    name: '搴撳瓨鏌ヨ',
+  },
+  {
+    bgColor: '#7c3aed',
+    icon: 'lucide:cpu',
+    menu: HOME_BLOCK_MENUS.machinery,
+    name: '璁惧绠$悊',
+  },
+  {
+    bgColor: '#0ea5e9',
+    icon: 'lucide:list',
+    menu: HOME_BLOCK_MENUS.proTask,
+    name: '鐢熶骇浠诲姟',
+  },
+  {
+    bgColor: '#14b8a6',
+    icon: 'lucide:truck',
+    menu: HOME_BLOCK_MENUS.arrivalNotice,
+    name: '鍒拌揣閫氱煡',
+  },
+  {
+    bgColor: '#f59e0b',
+    icon: 'lucide:settings-2',
+    menu: HOME_BLOCK_MENUS.repair,
+    name: '璁惧缁翠慨',
+  },
+  {
+    bgColor: '#ec4899',
+    icon: 'lucide:tickets',
+    menu: HOME_BLOCK_MENUS.processCard,
+    name: '娴佽浆鍗�',
+  },
+];
+
 /** 棣栭〉姹囨�荤粺璁¢粯璁ゅ�� */
 export const defaultSummary: MesHomeApi.Summary = {
   andonActiveCount: 0,
diff --git a/src/views/mes/home/index.vue b/src/views/mes/home/index.vue
index 258fbca..da8595f 100644
--- a/src/views/mes/home/index.vue
+++ b/src/views/mes/home/index.vue
@@ -1,7 +1,7 @@
 锘�<script lang="ts" setup>
 import type { MesHomeApi } from '#/api/mes/home';
 
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import { useRouter } from 'vue-router';
 
 import { Page } from '@vben/common-ui';
@@ -9,8 +9,14 @@
 import { Col, Row } from 'ant-design-vue';
 
 import { getHomeSummary } from '#/api/mes/home';
+import { useHomeAccess } from '#/utils/use-home-access';
 
-import { defaultSummary } from './data';
+import {
+  ALERT_MENUS,
+  defaultSummary,
+  HOME_BLOCK_MENUS,
+  HOME_SHORTCUTS,
+} from './data';
 import AlertPanel from './modules/alert-panel.vue';
 import KpiCards from './modules/kpi-cards.vue';
 import ProductionTrend from './modules/production-trend.vue';
@@ -19,6 +25,19 @@
 
 const router = useRouter();
 const summary = ref<MesHomeApi.Summary>(defaultSummary); // 棣栭〉姹囨�荤粺璁�
+
+// 棣栭〉妯″潡璺熼殢渚ц竟鏍忚彍鍗曪細鑿滃崟娌″紑鐨勬ā鍧楁暣鍧椾笉灞曠ず
+const { canSeeMenu } = useHomeAccess();
+/** 鐢熶骇瓒嬪娍锛堝彇鑷敓浜ф姤宸ワ級 */
+const showTrend = computed(() => canSeeMenu(HOME_BLOCK_MENUS.feedback));
+/** 寰呭姙涓庡紓甯革細浠绘剰涓�鏉$殑鑿滃崟寮�鐫�灏辨覆鏌撹繖涓�鍒� */
+const showAlert = computed(() => ALERT_MENUS.some((menu) => canSeeMenu(menu)));
+/** 璁㈠崟鐘舵�佸垎甯冿紙鍙栬嚜鐢熶骇璁㈠崟锛� */
+const showChart = computed(() => canSeeMenu(HOME_BLOCK_MENUS.workOrder));
+/** 蹇嵎鍏ュ彛锛氫换鎰忎竴涓叆鍙g殑鑿滃崟寮�鐫�灏辨覆鏌撹繖涓�鍒� */
+const showShortcuts = computed(() =>
+  HOME_SHORTCUTS.some((item) => canSeeMenu(item.menu)),
+);
 
 /** 璺宠浆鍒扮洰鏍囬〉闈紙鎸夎矾鐢� name锛� */
 function handleNavigate(name: string) {
@@ -40,21 +59,53 @@
     <KpiCards :summary="summary" class="mb-4" @navigate="handleNavigate" />
 
     <!-- 绗簩琛岋細鐢熶骇瓒嬪娍 + 寰呭姙寮傚父 -->
-    <Row :gutter="16" class="mb-4">
-      <Col :lg="16" :md="24" :sm="24" :xl="16" :xs="24" class="mb-4">
+    <Row v-if="showTrend || showAlert" :gutter="16" class="mb-4">
+      <Col
+        v-if="showTrend"
+        :lg="showAlert ? 16 : 24"
+        :md="24"
+        :sm="24"
+        :xl="showAlert ? 16 : 24"
+        :xs="24"
+        class="mb-4"
+      >
         <ProductionTrend />
       </Col>
-      <Col :lg="8" :md="24" :sm="24" :xl="8" :xs="24" class="mb-4">
+      <Col
+        v-if="showAlert"
+        :lg="showTrend ? 8 : 24"
+        :md="24"
+        :sm="24"
+        :xl="showTrend ? 8 : 24"
+        :xs="24"
+        class="mb-4"
+      >
         <AlertPanel :summary="summary" @navigate="handleNavigate" />
       </Col>
     </Row>
 
     <!-- 绗笁琛岋細璁㈠崟鍒嗗竷 + 蹇嵎鍏ュ彛 -->
-    <Row :gutter="16">
-      <Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24" class="mb-4">
+    <Row v-if="showChart || showShortcuts" :gutter="16">
+      <Col
+        v-if="showChart"
+        :lg="showShortcuts ? 12 : 24"
+        :md="24"
+        :sm="24"
+        :xl="showShortcuts ? 12 : 24"
+        :xs="24"
+        class="mb-4"
+      >
         <WorkOrderChart />
       </Col>
-      <Col :lg="12" :md="24" :sm="24" :xl="12" :xs="24" class="mb-4">
+      <Col
+        v-if="showShortcuts"
+        :lg="showChart ? 12 : 24"
+        :md="24"
+        :sm="24"
+        :xl="showChart ? 12 : 24"
+        :xs="24"
+        class="mb-4"
+      >
         <Shortcuts @navigate="handleNavigate" />
       </Col>
     </Row>
diff --git a/src/views/mes/home/modules/alert-panel.vue b/src/views/mes/home/modules/alert-panel.vue
index 8584cce..9128c9c 100644
--- a/src/views/mes/home/modules/alert-panel.vue
+++ b/src/views/mes/home/modules/alert-panel.vue
@@ -7,6 +7,10 @@
 
 import { Badge, Card } from 'ant-design-vue';
 
+import { useHomeAccess } from '#/utils/use-home-access';
+
+import { HOME_BLOCK_MENUS } from '../data';
+
 defineOptions({ name: 'MesHomeAlertPanel' });
 
 const props = defineProps<{
@@ -17,37 +21,41 @@
   navigate: [name: string];
 }>();
 
-/** 寰呭姙鎻愰啋鍒楄〃锛氭爣绛俱�佹弿杩般�佸浘鏍囥�佺洰鏍囪矾鐢卞悕绉般�佹暟閲� */
-const alertItems = computed(() => [
-  {
-    count: props.summary.andonActiveCount,
-    desc: '鏈缃殑瀹夌伅鍛煎彨',
-    icon: 'lucide:bell-ring',
-    iconClass: 'bg-red-50 text-red-500',
-    label: '瀹夌伅鎶ヨ',
-    routeName: 'MesProAndonRecord',
-  },
-  {
-    count: props.summary.repairActiveCount,
-    desc: '寰呭鐞嗙殑缁翠慨璁㈠崟',
-    icon: 'lucide:wrench',
-    iconClass: 'bg-orange-50 text-orange-500',
-    label: '璁惧缁翠慨',
-    routeName: 'MesDvRepair',
-  },
-  {
-    count: props.summary.workOrderPrepareCount,
-    desc: '鑽夌鐘舵�佺殑鐢熶骇璁㈠崟',
-    icon: 'lucide:clipboard-list',
-    iconClass: 'bg-blue-50 text-blue-500',
-    label: '寰呮帓浜ц鍗�',
-    routeName: 'MesProWorkOrder',
-  },
-]);
+const { canSeeMenu } = useHomeAccess();
+
+/** 寰呭姙鎻愰啋鍒楄〃锛氭爣绛俱�佹弿杩般�佸浘鏍囥�佺洰鏍囪矾鐢卞悕绉般�佹暟閲忥紱鑿滃崟娌″紑鐨勬潯鐩笉灞曠ず */
+const alertItems = computed(() =>
+  [
+    {
+      count: props.summary.andonActiveCount,
+      desc: '鏈缃殑瀹夌伅鍛煎彨',
+      icon: 'lucide:bell-ring',
+      iconClass: 'bg-red-50 text-red-500',
+      label: '瀹夌伅鎶ヨ',
+      routeName: HOME_BLOCK_MENUS.andon,
+    },
+    {
+      count: props.summary.repairActiveCount,
+      desc: '寰呭鐞嗙殑缁翠慨璁㈠崟',
+      icon: 'lucide:wrench',
+      iconClass: 'bg-orange-50 text-orange-500',
+      label: '璁惧缁翠慨',
+      routeName: HOME_BLOCK_MENUS.repair,
+    },
+    {
+      count: props.summary.workOrderPrepareCount,
+      desc: '鑽夌鐘舵�佺殑鐢熶骇璁㈠崟',
+      icon: 'lucide:clipboard-list',
+      iconClass: 'bg-blue-50 text-blue-500',
+      label: '寰呮帓浜ц鍗�',
+      routeName: HOME_BLOCK_MENUS.workOrder,
+    },
+  ].filter((item) => canSeeMenu(item.routeName)),
+);
 </script>
 
 <template>
-  <Card title="寰呭姙涓庡紓甯�" class="h-full">
+  <Card v-if="alertItems.length" title="寰呭姙涓庡紓甯�" class="h-full">
     <div class="flex flex-col">
       <div
         v-for="item in alertItems"
diff --git a/src/views/mes/home/modules/kpi-cards.vue b/src/views/mes/home/modules/kpi-cards.vue
index f104479..e6b7c5f 100644
--- a/src/views/mes/home/modules/kpi-cards.vue
+++ b/src/views/mes/home/modules/kpi-cards.vue
@@ -8,6 +8,10 @@
 
 import { Card, Col, Divider, Row } from 'ant-design-vue';
 
+import { useHomeAccess } from '#/utils/use-home-access';
+
+import { HOME_BLOCK_MENUS } from '../data';
+
 defineOptions({ name: 'MesHomeKpiCards' });
 
 const props = defineProps<{
@@ -17,6 +21,9 @@
 const emit = defineEmits<{
   navigate: [name: string];
 }>();
+
+/** 鑿滃崟娌″紑鐨勬ā鍧楋紝瀵瑰簲 KPI 鍗′笉灞曠ず */
+const { canSeeMenu } = useHomeAccess();
 
 /** 鏄惁鏈夎川閲忔暟鎹紙鍚堟牸鍝� + 涓嶈壇鍝� > 0锛� */
 const hasQualityData = computed(
@@ -40,10 +47,18 @@
 
 <template>
   <Row :gutter="16">
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col
+      v-if="canSeeMenu(HOME_BLOCK_MENUS.workOrder)"
+      :lg="6"
+      :md="12"
+      :sm="12"
+      :xl="6"
+      :xs="24"
+      class="mb-4"
+    >
       <Card
         class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
-        @click="emit('navigate', 'MesProWorkOrder')"
+        @click="emit('navigate', HOME_BLOCK_MENUS.workOrder)"
       >
         <div class="flex items-center gap-4">
           <div
@@ -71,10 +86,18 @@
         </div>
       </Card>
     </Col>
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col
+      v-if="canSeeMenu(HOME_BLOCK_MENUS.feedback)"
+      :lg="6"
+      :md="12"
+      :sm="12"
+      :xl="6"
+      :xs="24"
+      class="mb-4"
+    >
       <Card
         class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
-        @click="emit('navigate', 'MesProFeedback')"
+        @click="emit('navigate', HOME_BLOCK_MENUS.feedback)"
       >
         <div class="flex items-center gap-4">
           <div
@@ -100,10 +123,18 @@
         </div>
       </Card>
     </Col>
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col
+      v-if="canSeeMenu(HOME_BLOCK_MENUS.feedback)"
+      :lg="6"
+      :md="12"
+      :sm="12"
+      :xl="6"
+      :xs="24"
+      class="mb-4"
+    >
       <Card
         class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
-        @click="emit('navigate', 'MesProFeedback')"
+        @click="emit('navigate', HOME_BLOCK_MENUS.feedback)"
       >
         <div class="flex items-center gap-4">
           <div
@@ -135,10 +166,18 @@
         </div>
       </Card>
     </Col>
-    <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4">
+    <Col
+      v-if="canSeeMenu(HOME_BLOCK_MENUS.machinery)"
+      :lg="6"
+      :md="12"
+      :sm="12"
+      :xl="6"
+      :xs="24"
+      class="mb-4"
+    >
       <Card
         class="kpi-card cursor-pointer transition-all hover:-translate-y-1 hover:shadow-lg"
-        @click="emit('navigate', 'MesDvMachinery')"
+        @click="emit('navigate', HOME_BLOCK_MENUS.machinery)"
       >
         <div class="flex items-center gap-4">
           <div
diff --git a/src/views/mes/home/modules/production-trend.vue b/src/views/mes/home/modules/production-trend.vue
index 4441514..9a6c21d 100644
--- a/src/views/mes/home/modules/production-trend.vue
+++ b/src/views/mes/home/modules/production-trend.vue
@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 import type { EchartsUIType } from '@vben/plugins/echarts';
 
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 
 import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
 
@@ -9,16 +9,24 @@
 
 import { getProductionTrend } from '#/api/mes/home';
 
+import { useHomeAccess } from '#/utils/use-home-access';
+
 import { getProductionTrendChartOptions } from '../chart-options';
+import { HOME_BLOCK_MENUS } from '../data';
 
 defineOptions({ name: 'MesHomeProductionTrend' });
 
 const trendDays = ref(7); // 褰撳墠閫変腑鐨勫ぉ鏁拌寖鍥�
 const chartRef = ref<EchartsUIType>();
 const { renderEcharts } = useEcharts(chartRef);
+const { canSeeMenu } = useHomeAccess();
+
+/** 鐢熶骇瓒嬪娍鍙栬嚜鐢熶骇鎶ュ伐锛屾姤宸ヨ彍鍗曟病寮�灏变笉灞曠ず銆佷笉璇锋眰 */
+const showTrend = computed(() => canSeeMenu(HOME_BLOCK_MENUS.feedback));
 
 /** 鍔犺浇鐢熶骇瓒嬪娍鏁版嵁骞舵覆鏌撳浘琛� */
 async function loadData() {
+  if (!showTrend.value) return;
   const data = await getProductionTrend(trendDays.value);
   const dates = data.map((d) => d.date.slice(5));
   const quantities = data.map((d) => d.quantity);
@@ -40,7 +48,7 @@
 </script>
 
 <template>
-  <Card title="鐢熶骇瓒嬪娍" class="h-full">
+  <Card v-if="showTrend" title="鐢熶骇瓒嬪娍" class="h-full">
     <template #extra>
       <RadioGroup
         v-model:value="trendDays"
diff --git a/src/views/mes/home/modules/shortcuts.vue b/src/views/mes/home/modules/shortcuts.vue
index 09a6c38..f1e81a9 100644
--- a/src/views/mes/home/modules/shortcuts.vue
+++ b/src/views/mes/home/modules/shortcuts.vue
@@ -1,7 +1,13 @@
 锘�<script lang="ts" setup>
+import { computed } from 'vue';
+
 import { IconifyIcon } from '@vben/icons';
 
 import { Card, Col, Row } from 'ant-design-vue';
+
+import { useHomeAccess } from '#/utils/use-home-access';
+
+import { HOME_SHORTCUTS } from '../data';
 
 defineOptions({ name: 'MesHomeShortcuts' });
 
@@ -9,72 +15,21 @@
   navigate: [name: string];
 }>();
 
-/** 蹇嵎鍏ュ彛鍒楄〃锛�3脳3 缃戞牸甯冨眬涓庤鍗曠姸鎬佸垎甯冮潰鏉跨瓑楂� */
-const shortcuts = [
-  {
-    bgColor: '#409EFF',
-    icon: 'lucide:file-text',
-    name: '鐢熶骇璁㈠崟',
-    routeName: 'MesProWorkOrder',
-  },
-  {
-    bgColor: '#67C23A',
-    icon: 'lucide:edit',
-    name: '鐢熶骇鎶ュ伐',
-    routeName: 'MesProFeedback',
-  },
-  {
-    bgColor: '#E6A23C',
-    icon: 'lucide:search',
-    name: '璐ㄩ噺妫�楠�',
-    routeName: 'MesQcIqc',
-  },
-  {
-    bgColor: '#F56C6C',
-    icon: 'lucide:box',
-    name: '搴撳瓨鏌ヨ',
-    routeName: 'MesWmMaterialStock',
-  },
-  {
-    bgColor: '#7c3aed',
-    icon: 'lucide:cpu',
-    name: '璁惧绠$悊',
-    routeName: 'MesDvMachinery',
-  },
-  {
-    bgColor: '#0ea5e9',
-    icon: 'lucide:list',
-    name: '鐢熶骇浠诲姟',
-    routeName: 'MesProTask',
-  },
-  {
-    bgColor: '#14b8a6',
-    icon: 'lucide:truck',
-    name: '鍒拌揣閫氱煡',
-    routeName: 'MesWmArrivalNotice',
-  },
-  {
-    bgColor: '#f59e0b',
-    icon: 'lucide:settings-2',
-    name: '璁惧缁翠慨',
-    routeName: 'MesDvRepair',
-  },
-  {
-    bgColor: '#ec4899',
-    icon: 'lucide:tickets',
-    name: '娴佽浆鍗�',
-    routeName: 'MesProCard',
-  },
-];
+const { canSeeMenu } = useHomeAccess();
+
+/** 蹇嵎鍏ュ彛鍒楄〃锛�3脳3 缃戞牸甯冨眬涓庤鍗曠姸鎬佸垎甯冮潰鏉跨瓑楂橈紱鑿滃崟娌″紑鐨勪笉灞曠ず */
+const shortcuts = computed(() =>
+  HOME_SHORTCUTS.filter((item) => canSeeMenu(item.menu)),
+);
 </script>
 
 <template>
-  <Card title="蹇嵎鍏ュ彛" class="h-full">
+  <Card v-if="shortcuts.length" title="蹇嵎鍏ュ彛" class="h-full">
     <Row :gutter="16">
       <Col v-for="item in shortcuts" :key="item.name" :span="8" class="mb-4">
         <div
           class="hover:bg-accent flex cursor-pointer flex-col items-center gap-2 rounded-lg py-3 transition-all hover:-translate-y-0.5"
-          @click="emit('navigate', item.routeName)"
+          @click="emit('navigate', item.menu)"
         >
           <div
             class="flex size-12 items-center justify-center rounded-xl text-white"
diff --git a/src/views/mes/home/modules/work-order-chart.vue b/src/views/mes/home/modules/work-order-chart.vue
index f96be87..8988db8 100644
--- a/src/views/mes/home/modules/work-order-chart.vue
+++ b/src/views/mes/home/modules/work-order-chart.vue
@@ -1,7 +1,7 @@
 锘�<script lang="ts" setup>
 import type { EchartsUIType } from '@vben/plugins/echarts';
 
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 
 import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
 
@@ -9,16 +9,23 @@
 
 import { getWorkOrderStatusDistribution } from '#/api/mes/home';
 
+import { useHomeAccess } from '#/utils/use-home-access';
+
 import { getWorkOrderStatusChartOptions } from '../chart-options';
-import { WORK_ORDER_STATUS_COLOR_MAP } from '../data';
+import { HOME_BLOCK_MENUS, WORK_ORDER_STATUS_COLOR_MAP } from '../data';
 
 defineOptions({ name: 'MesHomeWorkOrderChart' });
 
 const chartRef = ref<EchartsUIType>();
 const { renderEcharts } = useEcharts(chartRef);
+const { canSeeMenu } = useHomeAccess();
+
+/** 鑿滃崟娌″紑灏变笉灞曠ず銆佷笉璇锋眰 */
+const showChart = computed(() => canSeeMenu(HOME_BLOCK_MENUS.workOrder));
 
 /** 鍔犺浇璁㈠崟鐘舵�佸垎甯冩暟鎹苟娓叉煋楗煎浘 */
 async function loadData() {
+  if (!showChart.value) return;
   const data = await getWorkOrderStatusDistribution();
   const chartData = data.map((d) => ({
     itemStyle: { color: WORK_ORDER_STATUS_COLOR_MAP[d.status] || '#409EFF' },
@@ -34,7 +41,7 @@
 </script>
 
 <template>
-  <Card title="璁㈠崟鐘舵�佸垎甯�" class="h-full">
+  <Card v-if="showChart" title="璁㈠崟鐘舵�佸垎甯�" class="h-full">
     <EchartsUI ref="chartRef" class="h-[280px] w-full" />
   </Card>
 </template>

--
Gitblit v1.9.3