|
|
@@ -0,0 +1,213 @@
|
|
|
+<template>
|
|
|
+ <el-drawer v-model="innerVisible" :size="820" direction="rtl" @closed="onClosed">
|
|
|
+ <template #header>
|
|
|
+ <div class="drawer-head">
|
|
|
+ <span class="drawer-title">订单发货周期 · 订单明细</span>
|
|
|
+ <span class="drawer-sub">S7_L1_001 配置化维度下钻</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div v-loading="loading" class="dim-drawer">
|
|
|
+ <!-- 顶部摘要:聚合值来自后端,前端不复算 -->
|
|
|
+ <el-descriptions v-if="capState === 'configured'" :column="4" border size="small" class="dim-summary">
|
|
|
+ <el-descriptions-item label="当前筛选平均发货周期">
|
|
|
+ <span class="agg-value">{{ aggregateDisplay }}</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="订单样本">{{ detail?.total ?? 0 }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="汇总版本">v{{ detail?.summaryConfigVersion ?? '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="维度版本">v{{ detail?.dimensionConfigVersion ?? '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <!-- 筛选 -->
|
|
|
+ <div v-if="capState === 'configured'" class="dim-filter">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="dateRange" type="daterange" value-format="YYYY-MM-DD" size="small"
|
|
|
+ start-placeholder="开始日期" end-placeholder="结束日期" range-separator="至" style="width: 260px" />
|
|
|
+ <el-input v-model="orderNo" placeholder="订单号" clearable size="small" style="width: 180px" />
|
|
|
+ <el-button size="small" type="primary" @click="onQuery">查询</el-button>
|
|
|
+ <el-button size="small" @click="onReset">重置</el-button>
|
|
|
+ <el-button size="small" :icon="RefreshRight" @click="loadDetail">刷新</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 明细表 -->
|
|
|
+ <el-table
|
|
|
+ v-if="capState === 'configured' && (detail?.list?.length ?? 0) > 0"
|
|
|
+ :data="detail!.list" size="small" border stripe class="dim-table"
|
|
|
+ @sort-change="onSortChange">
|
|
|
+ <el-table-column prop="valueDate" label="日期" width="120" sortable="custom" />
|
|
|
+ <el-table-column prop="dimensionCode" label="订单号" min-width="160" />
|
|
|
+ <el-table-column prop="metricValue" label="发货周期(天)" width="130" sortable="custom" align="right">
|
|
|
+ <template #default="{ row }">{{ fmt(row.metricValue) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sumValue" label="周期合计" width="110" align="right">
|
|
|
+ <template #default="{ row }">{{ fmt(row.sumValue) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sampleCount" label="样本数" width="90" align="right" />
|
|
|
+ <el-table-column prop="batchId" label="批次" min-width="200" show-overflow-tooltip />
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 空态:区分技术状态,不统一“暂无数据” -->
|
|
|
+ <el-empty v-else-if="capState === 'pending'" description="当前 KPI 尚未配置维度 SQL" />
|
|
|
+ <el-empty v-else-if="capState === 'version_mismatch'" :image-size="90">
|
|
|
+ <template #description>配置版本不一致,请重新发布维度配置</template>
|
|
|
+ </el-empty>
|
|
|
+ <el-empty v-else-if="capState === 'failed'" description="维度数据计算失败" />
|
|
|
+ <el-empty v-else-if="capState === 'configured'" description="当前筛选范围暂无数据" />
|
|
|
+
|
|
|
+ <el-pagination
|
|
|
+ v-if="capState === 'configured' && (detail?.total ?? 0) > 0"
|
|
|
+ class="dim-pager" layout="total, prev, pager, next, sizes" background
|
|
|
+ :total="detail!.total" :page-size="pageSize" :current-page="page"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ @current-change="onPageChange" @size-change="onSizeChange" />
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, watch } from 'vue';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import { RefreshRight } from '@element-plus/icons-vue';
|
|
|
+import { getDimensionCapability, getDimensionDetail, type KpiDimensionDetailResult } from '/@/views/aidop/api/kpiDimensionConfigApi';
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ modelValue: boolean;
|
|
|
+ moduleCode?: string;
|
|
|
+ initialFilterMode?: string | null;
|
|
|
+ initialStartDate?: string | null;
|
|
|
+ initialEndDate?: string | null;
|
|
|
+ initialOrderNo?: string | null;
|
|
|
+}>();
|
|
|
+const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void; (e: 'closed'): void }>();
|
|
|
+
|
|
|
+const METRIC = 'S7_L1_001';
|
|
|
+const moduleCode = computed(() => props.moduleCode || 'S7');
|
|
|
+
|
|
|
+const innerVisible = computed({
|
|
|
+ get: () => props.modelValue,
|
|
|
+ set: (v: boolean) => emit('update:modelValue', v),
|
|
|
+});
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
+const detail = ref<KpiDimensionDetailResult | null>(null);
|
|
|
+const capState = ref<'pending' | 'configured' | 'version_mismatch' | 'failed'>('configured');
|
|
|
+const dateRange = ref<[string, string] | null>(null);
|
|
|
+const orderNo = ref('');
|
|
|
+const page = ref(1);
|
|
|
+const pageSize = ref(20);
|
|
|
+const sortField = ref('value_date');
|
|
|
+const sortOrder = ref('desc');
|
|
|
+
|
|
|
+function unwrap(res: any) {
|
|
|
+ return (res as any)?.data?.result ?? (res as any)?.data ?? res;
|
|
|
+}
|
|
|
+function fmt(v: number | null | undefined) {
|
|
|
+ return v === null || v === undefined ? '—' : v;
|
|
|
+}
|
|
|
+const aggregateDisplay = computed(() => {
|
|
|
+ const v = detail.value?.aggregateValue;
|
|
|
+ return v === null || v === undefined ? '—' : `${v} 天`;
|
|
|
+});
|
|
|
+
|
|
|
+async function loadDetail() {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ // 能力优先:未配置直接空态,不请求明细
|
|
|
+ const cap = unwrap(await getDimensionCapability(METRIC, moduleCode.value));
|
|
|
+ if (!cap || cap.supported !== true) {
|
|
|
+ capState.value = 'pending';
|
|
|
+ detail.value = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 版本不匹配以卡片传入 filterMode 为准(详情端点只读当前版本,不作版本裁决)
|
|
|
+ if (props.initialFilterMode === 'version_mismatch') {
|
|
|
+ capState.value = 'version_mismatch';
|
|
|
+ detail.value = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const res = unwrap(await getDimensionDetail(METRIC, {
|
|
|
+ moduleCode: moduleCode.value,
|
|
|
+ startDate: dateRange.value?.[0] || undefined,
|
|
|
+ endDate: dateRange.value?.[1] || undefined,
|
|
|
+ orderNo: orderNo.value.trim() || undefined,
|
|
|
+ sortField: sortField.value,
|
|
|
+ sortOrder: sortOrder.value,
|
|
|
+ page: page.value,
|
|
|
+ pageSize: pageSize.value,
|
|
|
+ }));
|
|
|
+ if (!res || res.status === 'NOT_CONFIGURED') {
|
|
|
+ capState.value = 'pending';
|
|
|
+ detail.value = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ capState.value = 'configured';
|
|
|
+ detail.value = res;
|
|
|
+ } catch (e: any) {
|
|
|
+ capState.value = 'failed';
|
|
|
+ detail.value = null;
|
|
|
+ ElMessage.error('加载订单明细失败:' + (e?.response?.data?.message || e?.message || e));
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onQuery() {
|
|
|
+ page.value = 1;
|
|
|
+ loadDetail();
|
|
|
+}
|
|
|
+function onReset() {
|
|
|
+ dateRange.value = null;
|
|
|
+ orderNo.value = '';
|
|
|
+ page.value = 1;
|
|
|
+ sortField.value = 'value_date';
|
|
|
+ sortOrder.value = 'desc';
|
|
|
+ loadDetail();
|
|
|
+}
|
|
|
+function onPageChange(p: number) {
|
|
|
+ page.value = p;
|
|
|
+ loadDetail();
|
|
|
+}
|
|
|
+function onSizeChange(s: number) {
|
|
|
+ pageSize.value = s;
|
|
|
+ page.value = 1;
|
|
|
+ loadDetail();
|
|
|
+}
|
|
|
+function onSortChange({ prop, order }: { prop: string; order: string | null }) {
|
|
|
+ sortField.value = prop === 'metricValue' ? 'metric_value' : 'value_date';
|
|
|
+ sortOrder.value = order === 'ascending' ? 'asc' : 'desc';
|
|
|
+ page.value = 1;
|
|
|
+ loadDetail();
|
|
|
+}
|
|
|
+function onClosed() {
|
|
|
+ // 关闭清理,避免残留上次状态
|
|
|
+ detail.value = null;
|
|
|
+ capState.value = 'configured';
|
|
|
+ emit('closed');
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.modelValue,
|
|
|
+ (v) => {
|
|
|
+ if (v) {
|
|
|
+ dateRange.value = props.initialStartDate && props.initialEndDate ? [props.initialStartDate, props.initialEndDate] : null;
|
|
|
+ orderNo.value = props.initialOrderNo || '';
|
|
|
+ page.value = 1;
|
|
|
+ sortField.value = 'value_date';
|
|
|
+ sortOrder.value = 'desc';
|
|
|
+ loadDetail();
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.drawer-head { display: flex; flex-direction: column; }
|
|
|
+.drawer-title { font-size: 15px; font-weight: 600; }
|
|
|
+.drawer-sub { font-size: 12px; color: var(--el-text-color-secondary); }
|
|
|
+.dim-drawer { display: flex; flex-direction: column; gap: 12px; }
|
|
|
+.dim-summary { margin-bottom: 4px; }
|
|
|
+.agg-value { font-size: 16px; font-weight: 600; color: var(--el-color-primary); }
|
|
|
+.dim-filter { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
|
+.dim-table { margin-top: 4px; }
|
|
|
+.dim-pager { margin-top: 8px; justify-content: flex-end; }
|
|
|
+</style>
|