|
@@ -33,6 +33,8 @@
|
|
|
<div class="section-title">
|
|
<div class="section-title">
|
|
|
<div class="section-title__bar" />
|
|
<div class="section-title__bar" />
|
|
|
<h2 class="section-title__text">供应异常类型</h2>
|
|
<h2 class="section-title__text">供应异常类型</h2>
|
|
|
|
|
+ <!-- S8-PROD-SUPPLY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:与下方「近7日供应异常趋势」做时间窗口区分。 -->
|
|
|
|
|
+ <span class="anomaly-monitor__time-badge">近24h</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="anomaly-monitor__type-list">
|
|
<div class="anomaly-monitor__type-list">
|
|
@@ -67,26 +69,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"
|
|
|
|
|
- :min-w="3"
|
|
|
|
|
- @update:layout="onAnalysisLayoutUpdate"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- S8-MONITORING-TOP-STATUS-STRIP-REMOVE-1:移除底部全局状态条。 -->
|
|
|
|
|
|
|
+ <!-- S8-PROD-SUPPLY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:原「供应异常多维分析」7 张卡与右侧「供应异常类型」重复,
|
|
|
|
|
+ 替换为近 7 日供应异常趋势图(接口 /monitoring/supply-trend,前端按近7日总量选 Top5 非零类型展示)。 -->
|
|
|
|
|
+ <S8SupplyTrendChart :data="supplyTrendData" />
|
|
|
</section>
|
|
</section>
|
|
|
</template>
|
|
</template>
|
|
|
</S8MonitoringResizableShell>
|
|
</S8MonitoringResizableShell>
|
|
@@ -98,7 +83,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"
|
|
|
/>
|
|
/>
|
|
@@ -107,16 +92,16 @@
|
|
|
|
|
|
|
|
<script setup lang="ts" name="aidopS8MonitoringSupply">
|
|
<script setup lang="ts" name="aidopS8MonitoringSupply">
|
|
|
import { computed, onMounted, reactive, shallowRef, type CSSProperties, type Component } from 'vue';
|
|
import { computed, onMounted, reactive, shallowRef, type CSSProperties, type Component } from 'vue';
|
|
|
-import { ShoppingBag, Tools, DataAnalysis, Box, Van, Document, Promotion } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
+import { ShoppingBag, Tools, DataAnalysis } 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 { s8SupplyMonitoringApi, type SupplyAnomalyType } from '../api/s8SupplyMonitoringApi';
|
|
import { s8SupplyMonitoringApi, type SupplyAnomalyType } from '../api/s8SupplyMonitoringApi';
|
|
|
-import type { S8MonitoringSummary, S8ModuleOrderSummary } from '../api/s8MonitoringApi';
|
|
|
|
|
|
|
+import { s8MonitoringApi, type S8MonitoringSummary, type S8ModuleOrderSummary, type S8SupplyTrendData } 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 S8SupplyTrendChart from './components/S8SupplyTrendChart.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';
|
|
@@ -150,7 +135,9 @@ const { layout: persistedLayout, save, resetToDefault, restoreDemo } = useSupply
|
|
|
// ─── 卡片配置 ──────────────────────────────────────────────────────────────
|
|
// ─── 卡片配置 ──────────────────────────────────────────────────────────────
|
|
|
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-PROD-SUPPLY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:保留 categoryConfigState 与 reset 给抽屉状态绑定,
|
|
|
|
|
+// 数据绑定 categoryCards 已废弃;initializeFromCategories / applyCategoryConfig 不再使用。
|
|
|
|
|
+const { categoryConfigState, reset: resetCategoryConfigState } = useS8CategoryConfig();
|
|
|
|
|
|
|
|
// ─── 布局状态 ──────────────────────────────────────────────────────────────
|
|
// ─── 布局状态 ──────────────────────────────────────────────────────────────
|
|
|
const editMode = shallowRef(false);
|
|
const editMode = shallowRef(false);
|
|
@@ -236,16 +223,9 @@ const ANOMALY_TYPES_FALLBACK: ReadonlyArray<{ key: string; label: string }> = [
|
|
|
{ key: 'work-order-issue', label: '仓库工单发料异常' },
|
|
{ key: 'work-order-issue', label: '仓库工单发料异常' },
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
-const SUPPLY_CATEGORY_ICONS: ReadonlyArray<Component> = [ShoppingBag, Van, Box, DataAnalysis, Document, Promotion, Tools];
|
|
|
|
|
-
|
|
|
|
|
-const CATEGORY_DEFS_FALLBACK: ReadonlyArray<{ key: string; title: string; icon: Component }> = ANOMALY_TYPES_FALLBACK.map((t, idx) => ({
|
|
|
|
|
- key: t.key,
|
|
|
|
|
- title: t.label,
|
|
|
|
|
- icon: SUPPLY_CATEGORY_ICONS[idx % SUPPLY_CATEGORY_ICONS.length],
|
|
|
|
|
-}));
|
|
|
|
|
|
|
+// S8-PROD-SUPPLY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:原 SUPPLY_CATEGORY_ICONS / CATEGORY_DEFS_FALLBACK 仅服务被替换的多维分析区,已移除。
|
|
|
|
|
|
|
|
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<SupplyAnomalyType[]>(
|
|
const anomalyTypes = reactive<SupplyAnomalyType[]>(
|
|
|
ANOMALY_TYPES_FALLBACK.map((d) => ({ ...d, total: 0, avgProcessHours: 0, closeRate: 0 })),
|
|
ANOMALY_TYPES_FALLBACK.map((d) => ({ ...d, total: 0, avgProcessHours: 0, closeRate: 0 })),
|
|
@@ -315,26 +295,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-PROD-SUPPLY-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-PROD-SUPPLY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:近 7 日供应异常趋势数据。
|
|
|
|
|
+const supplyTrendData = shallowRef<S8SupplyTrendData | null>(null);
|
|
|
|
|
+async function loadSupplyTrend() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ supplyTrendData.value = await s8MonitoringApi.supplyTrend({ tenantId: 1, factoryId: 1, days: 7 });
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('[S8MonitoringSupplyPage] loadSupplyTrend failed:', err);
|
|
|
|
|
+ supplyTrendData.value = null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
const layoutVars = computed<CSSProperties>(() => ({ '--grid-gap': '24px' }));
|
|
const layoutVars = computed<CSSProperties>(() => ({ '--grid-gap': '24px' }));
|
|
|
|
|
|
|
|
// ─── 工具函数 ──────────────────────────────────────────────────────────────
|
|
// ─── 工具函数 ──────────────────────────────────────────────────────────────
|
|
@@ -385,13 +358,13 @@ 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 () => {
|
|
|
await loadPageConfig();
|
|
await loadPageConfig();
|
|
|
initializeFromCards(stageCards.value);
|
|
initializeFromCards(stageCards.value);
|
|
|
- initializeFromCategories(categoryCards.value);
|
|
|
|
|
void loadData();
|
|
void loadData();
|
|
|
|
|
+ void loadSupplyTrend();
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -604,6 +577,21 @@ onMounted(async () => {
|
|
|
|
|
|
|
|
.section-title { display: flex; align-items: center; gap: 12px; }
|
|
.section-title { display: flex; align-items: center; gap: 12px; }
|
|
|
|
|
|
|
|
|
|
+/* S8-PROD-SUPPLY-TREND-CHART-REPLACE-DUPLICATE-SECTION-1:右侧「供应异常类型」时间窗口徽标。 */
|
|
|
|
|
+.anomaly-monitor__time-badge {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ padding: 0 8px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #6de039;
|
|
|
|
|
+ background: rgba(109, 224, 57, 0.12);
|
|
|
|
|
+ border: 1px solid rgba(109, 224, 57, 0.28);
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ letter-spacing: 0.04em;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.section-title__bar {
|
|
.section-title__bar {
|
|
|
width: 6px;
|
|
width: 6px;
|
|
|
height: 24px;
|
|
height: 24px;
|