|
@@ -1,15 +1,29 @@
|
|
|
<script setup lang="ts" name="aidopS8OrderExecutionChain">
|
|
<script setup lang="ts" name="aidopS8OrderExecutionChain">
|
|
|
// ORDER-FLOW-CHAIN-SHELL-1:订单链路全景页壳。
|
|
// ORDER-FLOW-CHAIN-SHELL-1:订单链路全景页壳。
|
|
|
|
|
+// ORDER-FLOW-CHAIN-FILTER-STAGE-1:6 维多选筛选 + 关键环节默认全选 + 全图矩阵 + 点击列切换详情焦点。
|
|
|
// 入口:总览页订单卡「链路全景」按钮 → store.selectOrderForChain + router.push。
|
|
// 入口:总览页订单卡「链路全景」按钮 → store.selectOrderForChain + router.push。
|
|
|
// 刷新恢复:onMounted 调 store.restoreChainSelection() 从 sessionStorage 重建 selectedOrderNo。
|
|
// 刷新恢复:onMounted 调 store.restoreChainSelection() 从 sessionStorage 重建 selectedOrderNo。
|
|
|
-import { computed, onMounted } from 'vue';
|
|
|
|
|
|
|
+import { computed, onMounted, ref, watch } from 'vue';
|
|
|
import { useRouter } from 'vue-router';
|
|
import { useRouter } from 'vue-router';
|
|
|
import { useOrderExecutionStore } from '/@/stores/orderExecution';
|
|
import { useOrderExecutionStore } from '/@/stores/orderExecution';
|
|
|
-import StageMatrixCard from './components/order-execution/StageMatrixCard.vue';
|
|
|
|
|
|
|
+import type {
|
|
|
|
|
+ OrderNodeKey,
|
|
|
|
|
+ SalesOrderExecution,
|
|
|
|
|
+} from '/@/views/aidop/s8/monitoring/data/order-execution/types';
|
|
|
|
|
+import ChainStageFilterBar from './components/order-execution/ChainStageFilterBar.vue';
|
|
|
|
|
+import ChainOverviewList from './components/order-execution/ChainOverviewList.vue';
|
|
|
import NodeDetailCard from './components/order-execution/NodeDetailCard.vue';
|
|
import NodeDetailCard from './components/order-execution/NodeDetailCard.vue';
|
|
|
import ReviewSubStepsCard from './components/order-execution/ReviewSubStepsCard.vue';
|
|
import ReviewSubStepsCard from './components/order-execution/ReviewSubStepsCard.vue';
|
|
|
import ReviewBreakdownCard from './components/order-execution/ReviewBreakdownCard.vue';
|
|
import ReviewBreakdownCard from './components/order-execution/ReviewBreakdownCard.vue';
|
|
|
|
|
|
|
|
|
|
+const ALL_STAGE_KEYS: OrderNodeKey[] = [
|
|
|
|
|
+ 'order_review',
|
|
|
|
|
+ 'product_design',
|
|
|
|
|
+ 'material_procurement',
|
|
|
|
|
+ 'body_production',
|
|
|
|
|
+ 'final_assembly_shipping',
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
const store = useOrderExecutionStore();
|
|
const store = useOrderExecutionStore();
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
|
|
|
|
@@ -18,26 +32,55 @@ onMounted(() => {
|
|
|
store.restoreChainSelection();
|
|
store.restoreChainSelection();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const order = computed(() => store.selectedOrder);
|
|
|
|
|
-
|
|
|
|
|
-const focusStage = computed(() => {
|
|
|
|
|
- const o = order.value;
|
|
|
|
|
- if (!o) return null;
|
|
|
|
|
- const lifecycle = o.lifecycle ?? [];
|
|
|
|
|
- if (lifecycle.length === 0) return null;
|
|
|
|
|
- return (
|
|
|
|
|
- lifecycle.find((s) => s.key === o.focusNodeKey) ??
|
|
|
|
|
- lifecycle.find((s) => s.key === o.currentNodeKey) ??
|
|
|
|
|
- lifecycle[0] ??
|
|
|
|
|
- null
|
|
|
|
|
- );
|
|
|
|
|
|
|
+const selectedOrder = computed(() => store.selectedOrder);
|
|
|
|
|
+const allOrders = computed(() => store.allOrders);
|
|
|
|
|
+
|
|
|
|
|
+const selectedOrderNos = ref<string[]>([]);
|
|
|
|
|
+const selectedCustomerTypes = ref<string[]>([]);
|
|
|
|
|
+const selectedRegions = ref<string[]>([]);
|
|
|
|
|
+const selectedProductLines = ref<string[]>([]);
|
|
|
|
|
+const selectedWorkflowStatuses = ref<string[]>([]);
|
|
|
|
|
+const selectedStageKeys = ref<OrderNodeKey[]>([]);
|
|
|
|
|
+const activeStageKey = ref<OrderNodeKey | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+const filteredChainOrders = computed<SalesOrderExecution[]>(() => {
|
|
|
|
|
+ const orderNoSet = new Set(selectedOrderNos.value);
|
|
|
|
|
+ const ctSet = new Set(selectedCustomerTypes.value);
|
|
|
|
|
+ const regionSet = new Set(selectedRegions.value);
|
|
|
|
|
+ const plSet = new Set(selectedProductLines.value);
|
|
|
|
|
+ const wfSet = new Set(selectedWorkflowStatuses.value);
|
|
|
|
|
+ return allOrders.value.filter((o) => {
|
|
|
|
|
+ if (orderNoSet.size > 0 && !orderNoSet.has(o.soNo)) return false;
|
|
|
|
|
+ if (ctSet.size > 0 && !ctSet.has(o.customerType)) return false;
|
|
|
|
|
+ if (regionSet.size > 0 && !regionSet.has(o.region)) return false;
|
|
|
|
|
+ if (plSet.size > 0 && !plSet.has(o.productLine)) return false;
|
|
|
|
|
+ if (wfSet.size > 0 && !wfSet.has(o.workflowStatus)) return false;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const visibleStageKeys = computed<OrderNodeKey[]>(() => {
|
|
|
|
|
+ if (selectedStageKeys.value.length === 0) return [...ALL_STAGE_KEYS];
|
|
|
|
|
+ const set = new Set(selectedStageKeys.value);
|
|
|
|
|
+ return ALL_STAGE_KEYS.filter((k) => set.has(k));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const detailOrder = computed<SalesOrderExecution | null>(() => {
|
|
|
|
|
+ if (selectedOrder.value && filteredChainOrders.value.some((o) => o.soNo === selectedOrder.value!.soNo)) {
|
|
|
|
|
+ return selectedOrder.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ return filteredChainOrders.value[0] ?? selectedOrder.value ?? null;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const stages = computed(() => order.value?.lifecycle ?? []);
|
|
|
|
|
|
|
+const activeStage = computed(() => {
|
|
|
|
|
+ const o = detailOrder.value;
|
|
|
|
|
+ const key = activeStageKey.value;
|
|
|
|
|
+ if (!o || !key) return null;
|
|
|
|
|
+ return o.lifecycle.find((s) => s.key === key) ?? null;
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
-// 评审子节点:order_review 阶段(无论 focus 是否在它)的 subSteps;用于稳定展示。
|
|
|
|
|
-const reviewStage = computed(() =>
|
|
|
|
|
- (order.value?.lifecycle ?? []).find((s) => s.key === 'order_review') ?? null,
|
|
|
|
|
|
|
+const reviewStage = computed(
|
|
|
|
|
+ () => detailOrder.value?.lifecycle.find((s) => s.key === 'order_review') ?? null,
|
|
|
);
|
|
);
|
|
|
const reviewSubSteps = computed(() => reviewStage.value?.subSteps ?? null);
|
|
const reviewSubSteps = computed(() => reviewStage.value?.subSteps ?? null);
|
|
|
const opinionSubStep = computed(
|
|
const opinionSubStep = computed(
|
|
@@ -45,8 +88,43 @@ const opinionSubStep = computed(
|
|
|
);
|
|
);
|
|
|
const opinionBreakdown = computed(() => opinionSubStep.value?.breakdown ?? null);
|
|
const opinionBreakdown = computed(() => opinionSubStep.value?.breakdown ?? null);
|
|
|
|
|
|
|
|
-const showReviewSubSteps = computed(() => (reviewSubSteps.value?.length ?? 0) > 0);
|
|
|
|
|
-const showBreakdown = computed(() => (opinionBreakdown.value?.length ?? 0) > 0);
|
|
|
|
|
|
|
+const showReviewSubSteps = computed(
|
|
|
|
|
+ () => activeStageKey.value === 'order_review' && (reviewSubSteps.value?.length ?? 0) > 0,
|
|
|
|
|
+);
|
|
|
|
|
+const showBreakdown = computed(
|
|
|
|
|
+ () => activeStageKey.value === 'order_review' && (opinionBreakdown.value?.length ?? 0) > 0,
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ [detailOrder, visibleStageKeys],
|
|
|
|
|
+ ([order, visible]) => {
|
|
|
|
|
+ if (!order) {
|
|
|
|
|
+ activeStageKey.value = null;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (activeStageKey.value && visible.includes(activeStageKey.value)) return;
|
|
|
|
|
+ const focus = (order.focusNodeKey ?? order.currentNodeKey) as OrderNodeKey | undefined;
|
|
|
|
|
+ if (focus && visible.includes(focus)) {
|
|
|
|
|
+ activeStageKey.value = focus;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ activeStageKey.value = visible[0] ?? null;
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true },
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+function onStageClick(key: OrderNodeKey) {
|
|
|
|
|
+ activeStageKey.value = key;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onResetFilters() {
|
|
|
|
|
+ selectedOrderNos.value = [];
|
|
|
|
|
+ selectedCustomerTypes.value = [];
|
|
|
|
|
+ selectedRegions.value = [];
|
|
|
|
|
+ selectedProductLines.value = [];
|
|
|
|
|
+ selectedWorkflowStatuses.value = [];
|
|
|
|
|
+ selectedStageKeys.value = [];
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
function goBack() {
|
|
function goBack() {
|
|
|
if (window.history.length > 1) {
|
|
if (window.history.length > 1) {
|
|
@@ -55,6 +133,18 @@ function goBack() {
|
|
|
}
|
|
}
|
|
|
router.push('/aidop/s8/monitoring/order-execution');
|
|
router.push('/aidop/s8/monitoring/order-execution');
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+const STAGE_NAME_MAP: Record<OrderNodeKey, string> = {
|
|
|
|
|
+ order_review: '订单评审',
|
|
|
|
|
+ product_design: '产品设计',
|
|
|
|
|
+ material_procurement: '材料采购',
|
|
|
|
|
+ body_production: '本体生产',
|
|
|
|
|
+ final_assembly_shipping: '总装发货',
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const activeStageName = computed(() =>
|
|
|
|
|
+ activeStageKey.value ? STAGE_NAME_MAP[activeStageKey.value] : '--',
|
|
|
|
|
+);
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
@@ -67,46 +157,74 @@ function goBack() {
|
|
|
<span class="oe-chain__breadcrumb-current">订单链路全景</span>
|
|
<span class="oe-chain__breadcrumb-current">订单链路全景</span>
|
|
|
</p>
|
|
</p>
|
|
|
<h1 class="oe-chain__title-main">订单链路全景</h1>
|
|
<h1 class="oe-chain__title-main">订单链路全景</h1>
|
|
|
- <p v-if="order" class="oe-chain__title-sub">
|
|
|
|
|
- <span class="oe-chain__so">{{ order.soNo }}</span>
|
|
|
|
|
- <span class="oe-chain__sep">·</span>
|
|
|
|
|
- <span>{{ order.productName }}</span>
|
|
|
|
|
|
|
+ <p class="oe-chain__title-sub">
|
|
|
|
|
+ <template v-if="detailOrder">
|
|
|
|
|
+ <span class="oe-chain__so">{{ detailOrder.soNo }}</span>
|
|
|
|
|
+ <span class="oe-chain__sep">·</span>
|
|
|
|
|
+ <span>{{ detailOrder.productName }}</span>
|
|
|
|
|
+ <span class="oe-chain__sep">·</span>
|
|
|
|
|
+ <span>当前环节 {{ activeStageName }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-else>尚未选择订单</template>
|
|
|
</p>
|
|
</p>
|
|
|
- <p v-else class="oe-chain__title-sub">尚未选择订单</p>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
<el-button class="oe-chain__back" @click="goBack">‹ 返回总览</el-button>
|
|
<el-button class="oe-chain__back" @click="goBack">‹ 返回总览</el-button>
|
|
|
</header>
|
|
</header>
|
|
|
|
|
|
|
|
- <template v-if="order">
|
|
|
|
|
|
|
+ <ChainStageFilterBar
|
|
|
|
|
+ :orders="allOrders"
|
|
|
|
|
+ :hit-count="filteredChainOrders.length"
|
|
|
|
|
+ :total-count="allOrders.length"
|
|
|
|
|
+ :selected-order-nos="selectedOrderNos"
|
|
|
|
|
+ :selected-customer-types="selectedCustomerTypes"
|
|
|
|
|
+ :selected-regions="selectedRegions"
|
|
|
|
|
+ :selected-product-lines="selectedProductLines"
|
|
|
|
|
+ :selected-workflow-statuses="selectedWorkflowStatuses"
|
|
|
|
|
+ :selected-stage-keys="selectedStageKeys"
|
|
|
|
|
+ @update:selected-order-nos="selectedOrderNos = $event"
|
|
|
|
|
+ @update:selected-customer-types="selectedCustomerTypes = $event"
|
|
|
|
|
+ @update:selected-regions="selectedRegions = $event"
|
|
|
|
|
+ @update:selected-product-lines="selectedProductLines = $event"
|
|
|
|
|
+ @update:selected-workflow-statuses="selectedWorkflowStatuses = $event"
|
|
|
|
|
+ @update:selected-stage-keys="selectedStageKeys = $event"
|
|
|
|
|
+ @reset="onResetFilters"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <template v-if="detailOrder">
|
|
|
<section class="oe-chain__summary">
|
|
<section class="oe-chain__summary">
|
|
|
<div class="oe-chain__summary-cell">
|
|
<div class="oe-chain__summary-cell">
|
|
|
<span class="oe-chain__summary-label">客户</span>
|
|
<span class="oe-chain__summary-label">客户</span>
|
|
|
- <span class="oe-chain__summary-value">{{ order.customerName }}</span>
|
|
|
|
|
|
|
+ <span class="oe-chain__summary-value">{{ detailOrder.customerName }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="oe-chain__summary-cell">
|
|
<div class="oe-chain__summary-cell">
|
|
|
<span class="oe-chain__summary-label">产品</span>
|
|
<span class="oe-chain__summary-label">产品</span>
|
|
|
- <span class="oe-chain__summary-value">{{ order.productName }}</span>
|
|
|
|
|
|
|
+ <span class="oe-chain__summary-value">{{ detailOrder.productName }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="oe-chain__summary-cell">
|
|
<div class="oe-chain__summary-cell">
|
|
|
<span class="oe-chain__summary-label">当前节点</span>
|
|
<span class="oe-chain__summary-label">当前节点</span>
|
|
|
- <span class="oe-chain__summary-value">{{ order.currentNode }}</span>
|
|
|
|
|
|
|
+ <span class="oe-chain__summary-value">{{ detailOrder.currentNode }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="oe-chain__summary-cell">
|
|
<div class="oe-chain__summary-cell">
|
|
|
<span class="oe-chain__summary-label">异常次数</span>
|
|
<span class="oe-chain__summary-label">异常次数</span>
|
|
|
<span class="oe-chain__summary-value oe-chain__summary-value--accent">
|
|
<span class="oe-chain__summary-value oe-chain__summary-value--accent">
|
|
|
- {{ order.exceptionCount }}
|
|
|
|
|
|
|
+ {{ detailOrder.exceptionCount }}
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="oe-chain__summary-cell">
|
|
<div class="oe-chain__summary-cell">
|
|
|
<span class="oe-chain__summary-label">工作流</span>
|
|
<span class="oe-chain__summary-label">工作流</span>
|
|
|
<span class="oe-chain__summary-value">
|
|
<span class="oe-chain__summary-value">
|
|
|
- {{ order.workflowStatus === 'in_progress' ? '执行中' : '已完成' }}
|
|
|
|
|
|
|
+ {{ detailOrder.workflowStatus === 'in_progress' ? '执行中' : '已完成' }}
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
- <StageMatrixCard :stages="stages" :focus-key="focusStage?.key ?? null" />
|
|
|
|
|
- <NodeDetailCard :stage="focusStage" />
|
|
|
|
|
|
|
+ <ChainOverviewList
|
|
|
|
|
+ :orders="filteredChainOrders"
|
|
|
|
|
+ :selected-stage-keys="selectedStageKeys"
|
|
|
|
|
+ :active-stage-key="activeStageKey"
|
|
|
|
|
+ @stage-click="onStageClick"
|
|
|
|
|
+ />
|
|
|
|
|
+ <NodeDetailCard :stage="activeStage" />
|
|
|
<ReviewSubStepsCard
|
|
<ReviewSubStepsCard
|
|
|
v-if="showReviewSubSteps"
|
|
v-if="showReviewSubSteps"
|
|
|
:sub-steps="reviewSubSteps"
|
|
:sub-steps="reviewSubSteps"
|
|
@@ -219,6 +337,7 @@ function goBack() {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
gap: 8px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.oe-chain__so {
|
|
.oe-chain__so {
|