| | |
| | | <script lang="ts" setup> |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; |
| | | import type { ErpSaleOrderApi } from '#/api/erp/sale/order'; |
| | | import type { MesHomeApi } from '#/api/mes/home'; |
| | | |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { Card, Col, Row } from 'ant-design-vue'; |
| | | import { Col, Row } from 'ant-design-vue'; |
| | | |
| | | import { getPurchaseOrderPage } from '#/api/erp/purchase/order'; |
| | | import { getSaleOrderPage } from '#/api/erp/sale/order'; |
| | | import { getHomeSummary } from '#/api/mes/home'; |
| | | |
| | | import SummaryCard from '#/views/erp/home/modules/summary-card.vue'; |
| | | |
| | | import { defaultSummary } from './data'; |
| | | import AiInsights from './modules/ai-insights.vue'; |
| | | import AlertPanel from './modules/alert-panel.vue'; |
| | | import BizCards from './modules/biz-cards.vue'; |
| | | import KpiCards from './modules/kpi-cards.vue'; |
| | | import ProductionTrend from './modules/production-trend.vue'; |
| | | import QuickActions from './modules/quick-actions.vue'; |
| | | |
| | | defineOptions({ name: 'DashboardAnalytics' }); |
| | | |
| | | const router = useRouter(); |
| | | const summary = ref(defaultSummary); |
| | | |
| | | const greeting = computed(() => { |
| | | const hour = new Date().getHours(); |
| | |
| | | router.push({ name }); |
| | | } |
| | | |
| | | async function loadSummary() { |
| | | summary.value = await getHomeSummary(); |
| | | interface PriceSummary { |
| | | monthPrice: number; |
| | | todayPrice: number; |
| | | yearPrice: number; |
| | | yesterdayPrice: number; |
| | | } |
| | | |
| | | const saleSummary = ref<PriceSummary>(); |
| | | const purchaseSummary = ref<PriceSummary>(); |
| | | const homeSummary = ref<MesHomeApi.Summary>(defaultSummary); |
| | | |
| | | const PAGE_SIZE = 200; |
| | | |
| | | /** 逐页拉取全部分页数据(后端 pageSize 上限 200) */ |
| | | async function fetchAllPages<T>( |
| | | fetcher: (params: PageParam) => Promise<PageResult<T>>, |
| | | ): Promise<T[]> { |
| | | const first = await fetcher({ pageNo: 1, pageSize: PAGE_SIZE }); |
| | | const list: T[] = [...(first.list ?? [])]; |
| | | const total = first.total ?? list.length; |
| | | const pageCount = Math.ceil(total / PAGE_SIZE); |
| | | for (let pageNo = 2; pageNo <= pageCount; pageNo++) { |
| | | const res = await fetcher({ pageNo, pageSize: PAGE_SIZE }); |
| | | list.push(...(res.list ?? [])); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | function toDayStr(time?: Date | string): string { |
| | | if (!time) return ''; |
| | | if (typeof time === 'string') return time.slice(0, 10); |
| | | const d = new Date(time); |
| | | if (Number.isNaN(d.getTime())) return ''; |
| | | const m = String(d.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(d.getDate()).padStart(2, '0'); |
| | | return `${d.getFullYear()}-${m}-${day}`; |
| | | } |
| | | |
| | | /** 按订单时间聚合金额:今日 / 昨日 / 本月 / 今年 */ |
| | | function aggregatePrice( |
| | | orders: Array<{ orderTime?: Date | string; totalPrice?: number }>, |
| | | ): PriceSummary { |
| | | const now = new Date(); |
| | | const today = toDayStr(now); |
| | | const yesterdayDate = new Date(now); |
| | | yesterdayDate.setDate(now.getDate() - 1); |
| | | const yesterday = toDayStr(yesterdayDate); |
| | | const month = today.slice(0, 7); |
| | | const year = today.slice(0, 4); |
| | | |
| | | const summary: PriceSummary = { |
| | | monthPrice: 0, |
| | | todayPrice: 0, |
| | | yearPrice: 0, |
| | | yesterdayPrice: 0, |
| | | }; |
| | | for (const order of orders) { |
| | | const day = toDayStr(order.orderTime); |
| | | if (!day) continue; |
| | | const price = order.totalPrice ?? 0; |
| | | if (day === today) summary.todayPrice += price; |
| | | if (day === yesterday) summary.yesterdayPrice += price; |
| | | if (day.startsWith(month)) summary.monthPrice += price; |
| | | if (day.startsWith(year)) summary.yearPrice += price; |
| | | } |
| | | return summary; |
| | | } |
| | | |
| | | async function loadStatistics() { |
| | | try { |
| | | const saleOrders = await fetchAllPages<ErpSaleOrderApi.SaleOrder>( |
| | | getSaleOrderPage, |
| | | ); |
| | | saleSummary.value = aggregatePrice(saleOrders); |
| | | } catch { |
| | | /* 接口异常或无权限 */ |
| | | } |
| | | try { |
| | | const purchaseOrders = await fetchAllPages<ErpPurchaseOrderApi.PurchaseOrder>( |
| | | getPurchaseOrderPage, |
| | | ); |
| | | purchaseSummary.value = aggregatePrice(purchaseOrders); |
| | | } catch { |
| | | /* 接口异常或无权限 */ |
| | | } |
| | | } |
| | | |
| | | async function loadHomeSummary() { |
| | | try { |
| | | homeSummary.value = await getHomeSummary(); |
| | | } catch { |
| | | /* 接口异常或无权限 */ |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadSummary(); |
| | | loadHomeSummary(); |
| | | loadStatistics(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- MES 生产 KPI --> |
| | | <KpiCards :summary="summary" @navigate="handleNavigate" /> |
| | | <!-- 质量 / 设备 KPI --> |
| | | <KpiCards :summary="homeSummary" @navigate="handleNavigate" /> |
| | | |
| | | <!-- ERP/WMS/BPM 经营 KPI --> |
| | | <BizCards @navigate="handleNavigate" /> |
| | | |
| | | <!-- AI 智能分析 --> |
| | | <AiInsights |
| | | :active-order-count="summary.workOrderActiveCount" |
| | | @navigate="handleNavigate" |
| | | /> |
| | | |
| | | <!-- 生产趋势 + 预警面板 --> |
| | | <Row :gutter="16" class="mb-4"> |
| | | <Col :lg="14" :md="24" :sm="24" :xl="14" :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天) |
| | | </h3> |
| | | <ProductionTrend /> |
| | | </div> |
| | | </Col> |
| | | <Col :lg="10" :md="24" :sm="24" :xl="10" :xs="24" class="mb-4"> |
| | | <AlertPanel |
| | | :repair-active-count="summary.repairActiveCount" |
| | | @navigate="handleNavigate" |
| | | /> |
| | | </Col> |
| | | </Row> |
| | | <!-- 销售 / 采购 金额概览 --> |
| | | <div class="mb-4"> |
| | | <SummaryCard |
| | | :purchase-summary="purchaseSummary" |
| | | :sale-summary="saleSummary" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- 快捷操作 --> |
| | | <Row :gutter="16" class="mb-4"> |