| | |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="top-row"> |
| | | <section v-if="dashboardCards.length > 0" class="top-row"> |
| | | <div class="stats-grid"> |
| | | <article |
| | | v-for="card in dashboardCards" |
| | |
| | | </div> |
| | | </section> |
| | | |
| | | <section class="main-grid"> |
| | | <div class="left-column"> |
| | | <div class="cockpit-panel process-panel"> |
| | | <section v-if="hasVisiblePanels" class="main-grid"> |
| | | <div v-if="hasLeftPanels" class="left-column"> |
| | | <div v-if="visiblePanels.process" class="cockpit-panel process-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">工序数据生产统计明细</div> |
| | | <div class="panel-actions"> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel order-panel"> |
| | | <div v-if="visiblePanels.order" class="cockpit-panel order-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">生产订单进度</div> |
| | | <el-radio-group v-model="orderFilter" size="small"> |
| | |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel contract-panel"> |
| | | <div v-if="visiblePanels.contract" class="cockpit-panel contract-panel"> |
| | | <div class="panel-title">客户合同金额分析</div> |
| | | <div class="contract-summary"> |
| | | <div class="contract-card"> |
| | |
| | | </ul> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel quality-panel"> |
| | | <div v-if="visiblePanels.quality" class="cockpit-panel quality-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">质量统计</div> |
| | | <el-radio-group v-model="qualityRange" size="small" @change="qualityStatisticsInfo"> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="right-column"> |
| | | <div class="cockpit-panel todo-panel"> |
| | | <div v-if="hasRightPanels" class="right-column"> |
| | | <div v-if="visiblePanels.todo" class="cockpit-panel todo-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">待办事项</div> |
| | | <span class="panel-more">更多</span> |
| | |
| | | <div v-else class="panel-empty">暂无数据</div> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel realtime-panel"> |
| | | <div v-if="visiblePanels.realtime" class="cockpit-panel realtime-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">生产实时看板</div> |
| | | <span class="panel-more">更多</span> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel quick-panel"> |
| | | <div v-if="visiblePanels.quick" class="cockpit-panel quick-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">快捷功能</div> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel plan-panel"> |
| | | <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">今日生产计划</div> |
| | | <span class="panel-more">{{ todayPlanList.length }}项</span> |
| | |
| | | </ul> |
| | | </div> |
| | | |
| | | <div class="cockpit-panel receipt-panel"> |
| | | <div v-if="visiblePanels.receipt" class="cockpit-panel receipt-panel"> |
| | | <div class="panel-title">回款与开票分析</div> |
| | | <Echarts |
| | | :options="chartBaseOptions" |
| | |
| | | </div> |
| | | |
| | | </div> |
| | | </section> |
| | | |
| | | <section v-else class="cockpit-panel empty-home-panel"> |
| | | 当前账号没有可展示的首页模块 |
| | | </section> |
| | | |
| | | <el-dialog v-model="processDialogVisible" title="选择工序" width="500px" append-to-body> |
| | |
| | | subValue: formatNumber(businessInfo.value.monthSaleHaveMoney), |
| | | trend: `占比 ${ratioText(businessInfo.value.monthSaleHaveMoney, businessInfo.value.monthSaleMoney)}`, |
| | | icon: DataLine, |
| | | visible: visibleModules.value.sales, |
| | | }, |
| | | { |
| | | key: "purchase", |
| | |
| | | businessInfo.value.monthPurchaseMoney |
| | | )}`, |
| | | icon: ShoppingCartFull, |
| | | visible: visibleModules.value.procurement, |
| | | }, |
| | | { |
| | | key: "inventory", |
| | |
| | | subValue: formatNumber(businessInfo.value.todayInventoryNum), |
| | | trend: "库存结构持续优化", |
| | | icon: Box, |
| | | visible: visibleModules.value.inventory, |
| | | }, |
| | | { |
| | | key: "production", |
| | |
| | | subValue: formatNumber(processTotals.value.scrap), |
| | | trend: `良率 ${ratioText(processTotals.value.output, processTotals.value.input)}`, |
| | | icon: Operation, |
| | | visible: visibleModules.value.production, |
| | | }, |
| | | ]); |
| | | ].filter((item) => item.visible)); |
| | | |
| | | const productionOrders = ref([ |
| | | { |
| | |
| | | ]; |
| | | |
| | | const normalizeMenuTitle = (title) => String(title || "").replace(/\s+/g, "").trim(); |
| | | const normalizeRoutePath = (path) => |
| | | String(path || "") |
| | | .trim() |
| | | .replace(/\/+/g, "/") |
| | | .replace(/\/$/, "") |
| | | .toLowerCase(); |
| | | |
| | | const resolveRoutePath = (route, parentPath = "") => { |
| | | const currentPath = String(route?.path || "").trim(); |
| | |
| | | if (title && fullPath && !String(route.redirect || "").includes("noRedirect")) { |
| | | items.push({ |
| | | title: normalizeMenuTitle(title), |
| | | path: fullPath, |
| | | path: normalizeRoutePath(fullPath), |
| | | }); |
| | | } |
| | | if (Array.isArray(route.children) && route.children.length > 0) { |
| | |
| | | : permissionStore.routes; |
| | | return collectAccessibleRoutes(routePool || []); |
| | | }); |
| | | |
| | | const moduleAccessConfig = { |
| | | sales: { |
| | | titles: ["销售管理", "销售台账"], |
| | | pathPrefixes: ["/salesmanagement"], |
| | | }, |
| | | procurement: { |
| | | titles: ["采购管理", "采购台账"], |
| | | pathPrefixes: ["/procurementmanagement"], |
| | | }, |
| | | inventory: { |
| | | titles: ["库存管理"], |
| | | pathPrefixes: ["/inventorymanagement"], |
| | | }, |
| | | production: { |
| | | titles: ["生产管理", "主生产计划", "生产订单", "生产报工"], |
| | | pathPrefixes: ["/productionmanagement", "/productionplan"], |
| | | }, |
| | | quality: { |
| | | titles: ["质量管理"], |
| | | pathPrefixes: ["/qualitymanagement"], |
| | | }, |
| | | equipment: { |
| | | titles: ["设备管理", "设备台账"], |
| | | pathPrefixes: ["/equipmentmanagement"], |
| | | }, |
| | | personnel: { |
| | | titles: ["人事管理", "员工台账", "在职员工台账"], |
| | | pathPrefixes: ["/personnelmanagement"], |
| | | }, |
| | | approval: { |
| | | titles: ["协同审批", "待办事项"], |
| | | pathPrefixes: ["/collaborativeapproval"], |
| | | }, |
| | | finance: { |
| | | titles: ["财务管理", "财务分析", "回款管理", "开票管理"], |
| | | pathPrefixes: ["/financesuite", "/financialmanagement"], |
| | | }, |
| | | }; |
| | | |
| | | const hasModuleAccess = (config) => |
| | | accessibleMenuRoutes.value.some((route) => { |
| | | const matchedTitle = (config.titles || []).some((title) => route.title === normalizeMenuTitle(title)); |
| | | const matchedPath = (config.pathPrefixes || []).some( |
| | | (prefix) => route.path === prefix || route.path.startsWith(`${prefix}/`) |
| | | ); |
| | | return matchedTitle || matchedPath; |
| | | }); |
| | | |
| | | const visibleModules = computed(() => ({ |
| | | sales: hasModuleAccess(moduleAccessConfig.sales), |
| | | procurement: hasModuleAccess(moduleAccessConfig.procurement), |
| | | inventory: hasModuleAccess(moduleAccessConfig.inventory), |
| | | production: hasModuleAccess(moduleAccessConfig.production), |
| | | quality: hasModuleAccess(moduleAccessConfig.quality), |
| | | equipment: hasModuleAccess(moduleAccessConfig.equipment), |
| | | personnel: hasModuleAccess(moduleAccessConfig.personnel), |
| | | approval: hasModuleAccess(moduleAccessConfig.approval), |
| | | finance: hasModuleAccess(moduleAccessConfig.finance), |
| | | })); |
| | | |
| | | const visiblePanels = computed(() => ({ |
| | | process: visibleModules.value.production, |
| | | order: visibleModules.value.production, |
| | | contract: visibleModules.value.sales, |
| | | quality: visibleModules.value.quality, |
| | | todo: visibleModules.value.approval, |
| | | realtime: visibleModules.value.production, |
| | | quick: quickEntries.value.length > 0, |
| | | plan: visibleModules.value.production, |
| | | receipt: visibleModules.value.sales || visibleModules.value.finance, |
| | | })); |
| | | |
| | | const hasLeftPanels = computed( |
| | | () => visiblePanels.value.process || visiblePanels.value.order || visiblePanels.value.contract || visiblePanels.value.quality |
| | | ); |
| | | const hasRightPanels = computed( |
| | | () => visiblePanels.value.todo || visiblePanels.value.realtime || visiblePanels.value.quick || visiblePanels.value.plan || visiblePanels.value.receipt |
| | | ); |
| | | const hasVisiblePanels = computed(() => hasLeftPanels.value || hasRightPanels.value); |
| | | |
| | | const quickEntries = computed(() => |
| | | quickEntryConfigs |
| | |
| | | onMounted(() => { |
| | | updateNowTime(); |
| | | clockTimer = setInterval(updateNowTime, 1000); |
| | | getBusinessData(); |
| | | analysisCustomer(); |
| | | todoInfoS(); |
| | | statisticsReceivable(); |
| | | qualityStatisticsInfo(); |
| | | getAmountHalfYearNum(); |
| | | getProcessList(); |
| | | refreshProcessStats(); |
| | | if (dashboardCards.value.length > 0) { |
| | | getBusinessData(); |
| | | } |
| | | if (visiblePanels.value.contract) { |
| | | analysisCustomer(); |
| | | } |
| | | if (visiblePanels.value.todo) { |
| | | todoInfoS(); |
| | | } |
| | | if (visiblePanels.value.quality) { |
| | | qualityStatisticsInfo(); |
| | | } |
| | | if (visiblePanels.value.receipt) { |
| | | statisticsReceivable(); |
| | | getAmountHalfYearNum(); |
| | | } |
| | | if (visiblePanels.value.process) { |
| | | getProcessList(); |
| | | refreshProcessStats(); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | |
| | | color: var(--text-tertiary); |
| | | } |
| | | |
| | | .empty-home-panel { |
| | | min-height: 220px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #64748b; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .main-grid { |
| | | display: grid; |
| | | grid-template-columns: minmax(0, 1fr) 400px; |