|
@@ -68,25 +68,9 @@
|
|
|
|
|
|
|
|
<template #analysis>
|
|
<template #analysis>
|
|
|
<section class="anomaly-monitor__analysis-panel glass-panel">
|
|
<section class="anomaly-monitor__analysis-panel glass-panel">
|
|
|
- <div class="anomaly-monitor__analysis-head">
|
|
|
|
|
- <div class="section-title">
|
|
|
|
|
- <div class="section-title__bar section-title__bar--accent" />
|
|
|
|
|
- <h2 class="section-title__text">交付异常多维分析</h2>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="anomaly-monitor__analysis-grid">
|
|
|
|
|
- <S8MonitoringCategoryGrid
|
|
|
|
|
- :cards="categoryCards"
|
|
|
|
|
- :layout="draftLayout.analysis"
|
|
|
|
|
- :editable="editMode"
|
|
|
|
|
- :row-height="56"
|
|
|
|
|
- :gap="12"
|
|
|
|
|
- @update:layout="onAnalysisLayoutUpdate"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- S8-MONITORING-TOP-STATUS-STRIP-REMOVE-1:移除底部全局状态条。 -->
|
|
|
|
|
|
|
+ <!-- S8-DELIVERY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:原「交付异常多维分析」三连卡与右侧「交付异常类型」重复,
|
|
|
|
|
+ 替换为近 7 日交付异常趋势图(接口 /monitoring/delivery-trend)。 -->
|
|
|
|
|
+ <S8DeliveryTrendChart :data="deliveryTrendData" />
|
|
|
</section>
|
|
</section>
|
|
|
</template>
|
|
</template>
|
|
|
</S8MonitoringResizableShell>
|
|
</S8MonitoringResizableShell>
|
|
@@ -98,7 +82,7 @@
|
|
|
:stage-keys="stageKeys"
|
|
:stage-keys="stageKeys"
|
|
|
v-model:selected-cat-key="categoryConfigState.selectedKey"
|
|
v-model:selected-cat-key="categoryConfigState.selectedKey"
|
|
|
v-model:cat-card="categoryConfigState.items[categoryConfigState.selectedKey]"
|
|
v-model:cat-card="categoryConfigState.items[categoryConfigState.selectedKey]"
|
|
|
- :category-keys="categoryKeys"
|
|
|
|
|
|
|
+ :category-keys="[]"
|
|
|
@reset="resetStageConfig"
|
|
@reset="resetStageConfig"
|
|
|
@reset-cat="resetCategoryConfig"
|
|
@reset-cat="resetCategoryConfig"
|
|
|
/>
|
|
/>
|
|
@@ -111,12 +95,12 @@ import { Checked, Van } from '@element-plus/icons-vue';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
import { deepClone, createPageLayout, LAYOUT_VERSION, type S8LayoutSchema, type GridItem } from './useS8Layout';
|
|
import { deepClone, createPageLayout, LAYOUT_VERSION, type S8LayoutSchema, type GridItem } from './useS8Layout';
|
|
|
import { s8DeliveryMonitoringApi, type DeliveryAnomalyType } from '../api/s8DeliveryMonitoringApi';
|
|
import { s8DeliveryMonitoringApi, type DeliveryAnomalyType } from '../api/s8DeliveryMonitoringApi';
|
|
|
-import type { S8MonitoringSummary, S8ModuleOrderSummary } from '../api/s8MonitoringApi';
|
|
|
|
|
|
|
+import { s8MonitoringApi, type S8MonitoringSummary, type S8ModuleOrderSummary, type S8DeliveryTrendData } from '../api/s8MonitoringApi';
|
|
|
import S8MonitoringResizableShell from './components/S8MonitoringResizableShell.vue';
|
|
import S8MonitoringResizableShell from './components/S8MonitoringResizableShell.vue';
|
|
|
import S8MonitoringModulesGrid from './components/S8MonitoringModulesGrid.vue';
|
|
import S8MonitoringModulesGrid from './components/S8MonitoringModulesGrid.vue';
|
|
|
-import S8MonitoringCategoryGrid, { type CategoryGridCardData } from './components/S8MonitoringCategoryGrid.vue';
|
|
|
|
|
import S8MonitoringEditToolbar from './components/S8MonitoringEditToolbar.vue';
|
|
import S8MonitoringEditToolbar from './components/S8MonitoringEditToolbar.vue';
|
|
|
import S8MonitoringStageConfigDrawer from './components/S8MonitoringStageConfigDrawer.vue';
|
|
import S8MonitoringStageConfigDrawer from './components/S8MonitoringStageConfigDrawer.vue';
|
|
|
|
|
+import S8DeliveryTrendChart from './components/S8DeliveryTrendChart.vue';
|
|
|
import { useS8UnsavedLayoutEditGuard } from './useS8UnsavedLayoutEditGuard';
|
|
import { useS8UnsavedLayoutEditGuard } from './useS8UnsavedLayoutEditGuard';
|
|
|
import { useS8StageConfig, buildModuleTriColorStats, DEFAULT_NORMAL_WORK_ORDER_COUNT } from './useS8StageConfig';
|
|
import { useS8StageConfig, buildModuleTriColorStats, DEFAULT_NORMAL_WORK_ORDER_COUNT } from './useS8StageConfig';
|
|
|
import { useS8CategoryConfig } from './useS8CategoryConfig';
|
|
import { useS8CategoryConfig } from './useS8CategoryConfig';
|
|
@@ -144,7 +128,9 @@ const { layout: persistedLayout, save, resetToDefault, restoreDemo } = useDelive
|
|
|
// ─── 卡片配置 ──────────────────────────────────────────────────────────────
|
|
// ─── 卡片配置 ──────────────────────────────────────────────────────────────
|
|
|
const configDrawerVisible = shallowRef(false);
|
|
const configDrawerVisible = shallowRef(false);
|
|
|
const { stageConfigState, initializeFromCards, applyConfig, reset: resetStageConfigState } = useS8StageConfig();
|
|
const { stageConfigState, initializeFromCards, applyConfig, reset: resetStageConfigState } = useS8StageConfig();
|
|
|
-const { categoryConfigState, initializeFromCategories, applyConfig: applyCategoryConfig, reset: resetCategoryConfigState } = useS8CategoryConfig();
|
|
|
|
|
|
|
+// S8-DELIVERY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:保留 categoryConfigState 与 reset 给抽屉状态绑定,
|
|
|
|
|
+// 数据绑定 categoryCards 已废弃;initializeFromCategories / applyCategoryConfig 不再使用。
|
|
|
|
|
+const { categoryConfigState, reset: resetCategoryConfigState } = useS8CategoryConfig();
|
|
|
|
|
|
|
|
// ─── 布局状态 ──────────────────────────────────────────────────────────────
|
|
// ─── 布局状态 ──────────────────────────────────────────────────────────────
|
|
|
const editMode = shallowRef(false);
|
|
const editMode = shallowRef(false);
|
|
@@ -226,14 +212,10 @@ const ANOMALY_TYPES_FALLBACK: ReadonlyArray<{ key: string; label: string }> = [
|
|
|
{ key: 'stock-pending', label: '入库待发' },
|
|
{ key: 'stock-pending', label: '入库待发' },
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
-const CATEGORY_DEFS_FALLBACK: ReadonlyArray<{ key: string; title: string; icon: Component }> = [
|
|
|
|
|
- { key: 'order-change', title: '订单变更', icon: Checked },
|
|
|
|
|
- { key: 'delivery-delay', title: '交期延迟', icon: Van },
|
|
|
|
|
- { key: 'stock-pending', title: '入库待发', icon: Van },
|
|
|
|
|
-];
|
|
|
|
|
|
|
+// S8-DELIVERY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:原 CATEGORY_DEFS_FALLBACK 仅服务 analysis 区块,
|
|
|
|
|
+// 该区块已被趋势图替换,常量与 effectiveCategoryDefs / categoryCards 一并移除。
|
|
|
|
|
|
|
|
const effectiveStageMeta = computed(() => pageConfig.effectiveStageMeta(STAGE_META_FALLBACK));
|
|
const effectiveStageMeta = computed(() => pageConfig.effectiveStageMeta(STAGE_META_FALLBACK));
|
|
|
-const effectiveCategoryDefs = computed(() => pageConfig.effectiveCategoryDefs(CATEGORY_DEFS_FALLBACK));
|
|
|
|
|
|
|
|
|
|
const anomalyTypes = reactive<DeliveryAnomalyType[]>(
|
|
const anomalyTypes = reactive<DeliveryAnomalyType[]>(
|
|
|
ANOMALY_TYPES_FALLBACK.map((d) => ({ ...d, total: 0, avgProcessHours: 0, closeRate: 0 })),
|
|
ANOMALY_TYPES_FALLBACK.map((d) => ({ ...d, total: 0, avgProcessHours: 0, closeRate: 0 })),
|
|
@@ -305,26 +287,19 @@ const stageCards = computed(() =>
|
|
|
|
|
|
|
|
const stageKeys = computed(() => stageCards.value.map((c) => c.code));
|
|
const stageKeys = computed(() => stageCards.value.map((c) => c.code));
|
|
|
|
|
|
|
|
-const categoryCards = computed<CategoryGridCardData[]>(() => {
|
|
|
|
|
- const typeMap = new Map(anomalyTypes.map((t) => [t.key, t]));
|
|
|
|
|
- const raw = effectiveCategoryDefs.value.map((def) => {
|
|
|
|
|
- const t = typeMap.get(def.key);
|
|
|
|
|
- return {
|
|
|
|
|
- key: def.key,
|
|
|
|
|
- title: def.title,
|
|
|
|
|
- icon: def.icon,
|
|
|
|
|
- totalText: formatInteger(t?.total ?? 0),
|
|
|
|
|
- avgHoursText: formatHours(t?.avgProcessHours ?? 0),
|
|
|
|
|
- closeRateText: formatPercent(t?.closeRate ?? 0),
|
|
|
|
|
- tone: resolveTone(t?.closeRate ?? 0),
|
|
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
- return raw.map((c) => applyCategoryConfig(c));
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-const categoryKeys = computed(() => categoryCards.value.map((c) => c.key));
|
|
|
|
|
-
|
|
|
|
|
|
|
+// S8-DELIVERY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:原 categoryCards / categoryKeys 仅服务被替换的多维分析区,已移除。
|
|
|
const totalAnomalies = computed(() => anomalyTypes.reduce((s, t) => s + t.total, 0));
|
|
const totalAnomalies = computed(() => anomalyTypes.reduce((s, t) => s + t.total, 0));
|
|
|
|
|
+
|
|
|
|
|
+// S8-DELIVERY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:近 7 日交付异常趋势数据。
|
|
|
|
|
+const deliveryTrendData = shallowRef<S8DeliveryTrendData | null>(null);
|
|
|
|
|
+async function loadDeliveryTrend() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ deliveryTrendData.value = await s8MonitoringApi.deliveryTrend({ tenantId: 1, factoryId: 1, days: 7 });
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('[S8MonitoringDeliveryPage] loadDeliveryTrend failed:', err);
|
|
|
|
|
+ deliveryTrendData.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
const layoutVars = computed<CSSProperties>(() => ({ '--grid-gap': '24px' }));
|
|
const layoutVars = computed<CSSProperties>(() => ({ '--grid-gap': '24px' }));
|
|
|
|
|
|
|
|
// ─── 工具函数 ──────────────────────────────────────────────────────────────
|
|
// ─── 工具函数 ──────────────────────────────────────────────────────────────
|
|
@@ -376,16 +351,16 @@ async function loadData() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function resetStageConfig() { resetStageConfigState(stageCards.value); }
|
|
function resetStageConfig() { resetStageConfigState(stageCards.value); }
|
|
|
-function resetCategoryConfig() { resetCategoryConfigState(categoryCards.value); }
|
|
|
|
|
|
|
+function resetCategoryConfig() { resetCategoryConfigState([]); }
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
// 1. 先拉 page-config(决定卡片结构)
|
|
// 1. 先拉 page-config(决定卡片结构)
|
|
|
await loadPageConfig();
|
|
await loadPageConfig();
|
|
|
- // 2. 用最终的 stageCards / categoryCards 初始化 localStorage 覆盖层
|
|
|
|
|
|
|
+ // 2. 用最终的 stageCards 初始化 stage 配置覆盖层(applyConfig 已 passthrough,仅维护抽屉状态)
|
|
|
initializeFromCards(stageCards.value);
|
|
initializeFromCards(stageCards.value);
|
|
|
- initializeFromCategories(categoryCards.value);
|
|
|
|
|
- // 3. 拉业务数据(loadData 按 key 合入 anomalyTypes,与 page-config 顺序解耦)
|
|
|
|
|
|
|
+ // 3. 拉业务数据 + 趋势
|
|
|
void loadData();
|
|
void loadData();
|
|
|
|
|
+ void loadDeliveryTrend();
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|