Просмотр исходного кода

fix(s8): enable order review unit drilldown

S8-ORDER-REVIEW-L2-UNIT-DRILLDOWN-FIX-1

订单评审下 5 个二级节点(意见评审 / 意见反馈 / 二次评审 / 领导意见 /
合同盖章)均可点击切换,下方"涉及部门 / 组"L3 矩阵随当前激活 L2 动态
切换内容与标题。DB / API / 后端已完整,仅前端写死绑定问题。

- ReviewSubStepsCard.vue:
  - 新增 activeName prop + select emit
  - L2 列头加 @click / @keydown.enter / @keydown.space + role + tabindex
  - 新增 oe-substeps__col-head--active 视觉态(沿用既有色板)
- OrderChainOverviewPage.vue:
  - 新增 activeSubstepName ref(默认意见评审入口)
  - onSubstepSelect 监听 L2 列头点击事件
  - opinionBreakdown 从写死意见评审改为按 activeSubstepName 动态 find
  - watch(reviewSubSteps) 守门:切换订单后 fallback 防悬空
  - ReviewBreakdownCard title 动态化:${activeSubstepName} — 涉及部门 / 组

已知边界:baseline 态 opinionBreakdown 仍固定 PPT_OPINION_DRILLDOWN
(PPT 常量仅意见评审有 L3 数据),title 跟随但内容固定。

Web version bump 2.4.158 -> 2.4.159
YY968XX 1 месяц назад
Родитель
Сommit
c79d954ce8

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.158",
+	"version": "2.4.159",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 29 - 4
Web/src/views/aidop/s8/monitoring/OrderChainOverviewPage.vue

@@ -96,6 +96,14 @@ const selectedWorkflowStatuses = ref<string[]>([]);
 const selectedStageKeys = ref<OrderNodeKey[]>([]);
 const activeStageKey = ref<OrderNodeKey | null>(null);
 
+// 订单评审下钻:当前激活的 L2 名称,默认意见评审作为入口默认。
+// reviewSubSteps 切换(订单/筛选变化)时若 activeSubstepName 不在新集合中,watch 守门 fallback。
+const activeSubstepName = ref<string>('意见评审');
+
+function onSubstepSelect(name: string) {
+	activeSubstepName.value = name;
+}
+
 const filteredChainOrders = computed<SalesOrderExecution[]>(() => {
 	const orderNoSet = new Set(selectedOrderNos.value);
 	const ctSet = new Set(selectedCustomerTypes.value);
@@ -175,7 +183,8 @@ const reviewSubSteps = computed<SubStepDetail[]>(() => {
 	return aggregateByIndex(ordersSubSteps);
 });
 
-// L3 三态:baseline → PPT 常量;单订单 → 该订单自身意见评审 breakdown;多订单 → filteredChainOrders 逐位 avg。
+// L3 三态:baseline → PPT 常量(默认意见评审快照);单订单 → 该订单当前激活 L2 的 breakdown;
+// 多订单 → filteredChainOrders 对当前激活 L2 逐位 avg。
 const opinionBreakdown = computed<SubStepDetail[]>(() => {
 	if (activeStageKey.value !== 'order_review') return [];
 	if (isUnfiltered.value) return PPT_OPINION_DRILLDOWN;
@@ -184,7 +193,7 @@ const opinionBreakdown = computed<SubStepDetail[]>(() => {
 			(o) =>
 				o.lifecycle
 					.find((s) => s.key === 'order_review')
-					?.subSteps?.find((ss) => ss.name === '意见评审')?.breakdown,
+					?.subSteps?.find((ss) => ss.name === activeSubstepName.value)?.breakdown,
 		)
 		.filter((x): x is SubStepDetail[] => Array.isArray(x) && x.length > 0);
 	if (ordersBreakdowns.length === 0) return [];
@@ -192,6 +201,18 @@ const opinionBreakdown = computed<SubStepDetail[]>(() => {
 	return aggregateByIndex(ordersBreakdowns);
 });
 
+// reviewSubSteps 变化守门:若 activeSubstepName 不在当前可见 L2 集合中,fallback 到第一个 L2 或意见评审。
+watch(
+	() => reviewSubSteps.value,
+	(next) => {
+		if (next.length === 0) return;
+		const names = next.map((s) => s.name);
+		if (!names.includes(activeSubstepName.value)) {
+			activeSubstepName.value = next[0]?.name ?? '意见评审';
+		}
+	},
+);
+
 const showReviewSubSteps = computed(
 	() => activeStageKey.value === 'order_review' && reviewSubSteps.value.length > 0,
 );
@@ -378,12 +399,14 @@ watch(
 			<ReviewSubStepsCard
 				v-if="showReviewSubSteps"
 				:sub-steps="reviewSubSteps"
+				:active-name="activeSubstepName"
 				title="节点详情:评审 / 排产 / 测算"
+				@select="onSubstepSelect"
 			/>
 			<ReviewBreakdownCard
 				v-if="showBreakdown"
 				:breakdown="opinionBreakdown"
-				title="意见评审 — 详细执行明细"
+				:title="`${activeSubstepName} — 涉及部门 / 组`"
 			/>
 			<DesignDetailPanel v-if="showDesignDetail" :detail="DESIGN_DETAIL_FIXTURE" />
 			<ProcurementDetailPanel
@@ -437,12 +460,14 @@ watch(
 			<ReviewSubStepsCard
 				v-if="showReviewSubSteps"
 				:sub-steps="reviewSubSteps"
+				:active-name="activeSubstepName"
 				title="节点详情:评审 / 排产 / 测算"
+				@select="onSubstepSelect"
 			/>
 			<ReviewBreakdownCard
 				v-if="showBreakdown"
 				:breakdown="opinionBreakdown"
-				title="意见评审 — 详细执行明细"
+				:title="`${activeSubstepName} — 涉及部门 / 组`"
 			/>
 			<DesignDetailPanel v-if="showDesignDetail" :detail="DESIGN_DETAIL_FIXTURE" />
 			<ProcurementDetailPanel

+ 31 - 0
Web/src/views/aidop/s8/monitoring/components/order-execution/ReviewSubStepsCard.vue

@@ -7,12 +7,18 @@ import type { SubStepDetail } from '/@/views/aidop/s8/monitoring/data/order-exec
 interface Props {
 	subSteps: SubStepDetail[] | undefined | null;
 	title?: string;
+	activeName?: string;
 }
 
 const props = withDefaults(defineProps<Props>(), {
 	title: '节点详情:评审 / 排产 / 测算',
+	activeName: '',
 });
 
+const emit = defineEmits<{
+	select: [name: string];
+}>();
+
 const STATUS_LABEL: Record<SubStepDetail['status'], string> = {
 	green: '正常',
 	yellow: '关注',
@@ -47,6 +53,12 @@ const cells = computed(() =>
 							v-for="cell in cells"
 							:key="`name-${cell.name}`"
 							class="oe-substeps__col-head"
+							:class="{ 'oe-substeps__col-head--active': activeName === cell.name }"
+							role="button"
+							tabindex="0"
+							@click="emit('select', cell.name)"
+							@keydown.enter.prevent="emit('select', cell.name)"
+							@keydown.space.prevent="emit('select', cell.name)"
 						>
 							{{ cell.name }}
 						</td>
@@ -172,10 +184,29 @@ const cells = computed(() =>
 }
 
 .oe-substeps__col-head {
+	cursor: pointer;
+	user-select: none;
 	background: rgba(15, 19, 26, 0.7);
 	color: var(--order-text-primary, #e1e2eb);
 	font-weight: 700;
 	font-family: 'PingFang SC', sans-serif;
+	transition: background 120ms ease, color 120ms ease, box-shadow 120ms ease;
+}
+
+.oe-substeps__col-head:hover {
+	background: rgba(123, 208, 255, 0.08);
+	color: #7bd0ff;
+}
+
+.oe-substeps__col-head:focus-visible {
+	outline: 2px solid rgba(123, 208, 255, 0.5);
+	outline-offset: -2px;
+}
+
+.oe-substeps__col-head--active {
+	background: rgba(123, 208, 255, 0.16);
+	color: #ffffff;
+	box-shadow: inset 0 0 0 1px rgba(123, 208, 255, 0.7);
 }
 
 .oe-substeps__data--pi {