| | |
| | | <div |
| | | class="step-title" |
| | | :class="{ selected: idx === selectedIndex }" |
| | | @click="selectProcess(idx)" |
| | | @click.stop="selectProcess(idx)" |
| | | > |
| | | {{ `${idx + 1}. ${p.processName || "-"}` }} |
| | | </div> |
| | | </template> |
| | | <template #description> |
| | | <div class="step-panel"> |
| | | <div v-if="idx === active" class="current-progress"> |
| | | <div |
| | | class="step-panel" |
| | | :class="{ |
| | | 'step-panel-selected': idx === selectedIndex, |
| | | 'step-panel-current': idx === active, |
| | | }" |
| | | @click="selectProcess(idx)" |
| | | > |
| | | <span |
| | | v-if="p?.status" |
| | | class="step-status-badge" |
| | | :class="`step-status-badge-${p.status}`" |
| | | > |
| | | {{ statusTagText(p.status) }} |
| | | </span> |
| | | <div v-if="p.status !== 'wait'" class="current-progress"> |
| | | <div class="current-progress-head"> |
| | | <span class="current-progress-title">当前工序进度</span> |
| | | <!-- <span class="current-progress-value">{{ currentProcessPercentage }}%</span> --> |
| | | <span class="current-progress-title">工序进度</span> |
| | | <!-- <span class="current-progress-value">{{ processPercentage(p) }}%</span> --> |
| | | </div> |
| | | <el-progress |
| | | :percentage="currentProcessPercentage" |
| | | :status="currentProcessPercentage >= 100 ? 'success' : ''" |
| | | :percentage="processPercentage(p)" |
| | | :color="progressColor(processPercentage(p))" |
| | | :status="processPercentage(p) >= 100 ? 'success' : ''" |
| | | :stroke-width="10" |
| | | /> |
| | | </div> |
| | |
| | | </div> |
| | | |
| | | <div v-if="!selectedProcess" class="right-empty"> |
| | | 点击左侧某个工序,右侧展示该工序的报工明细。 |
| | | 暂无工序数据。 |
| | | </div> |
| | | |
| | | <div v-else class="right-content"> |
| | | <el-table :data="mockReports" border height="420"> |
| | | <el-table :data="reports" border height="420" v-loading="reportLoading"> |
| | | <el-table-column label="序号" type="index" width="60" align="center" /> |
| | | <el-table-column label="报工单号" prop="reportNo" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column label="报工人员" prop="reportUser" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="报工时间" prop="reportTime" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="产出数量" prop="outputQty" min-width="110" /> |
| | | <el-table-column label="不良数量" prop="badQty" min-width="110" /> |
| | | <el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="报工单号" prop="productNo" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column label="报工人员" prop="nickName" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="报工时间" prop="createTime" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="产出数量" prop="quantity" min-width="110" /> |
| | | <el-table-column label="合格数量" prop="qualifiedQty" min-width="110" /> |
| | | <el-table-column label="不良数量" prop="scrapQty" min-width="110" /> |
| | | <el-table-column label="不合格处理" prop="dealResult" min-width="160" show-overflow-tooltip /> |
| | | <el-table-column label="操作" width="150" fixed="right"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="viewReportRecord(row)"> |
| | | 生产记录 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <!-- <el-dialog--> |
| | | <!-- v-model="reportRecordDialogVisible"--> |
| | | <!-- title="报工生产记录"--> |
| | | <!-- width="680px"--> |
| | | <!-- destroy-on-close--> |
| | | <!-- >--> |
| | | <!-- <div class="report-record-placeholder">--> |
| | | <!-- <div>报工单号:{{ currentReportRow?.reportNo || "-" }}</div>--> |
| | | <!-- <div>工序:{{ selectedProcess?.processName || "-" }}</div>--> |
| | | <!-- <div class="placeholder-tip">弹框内容待定(后续补充详细生产记录)。</div>--> |
| | | <!-- </div>--> |
| | | <!-- <template #footer>--> |
| | | <!-- <el-button @click="reportRecordDialogVisible = false">关闭</el-button>--> |
| | | <!-- </template>--> |
| | | <!-- </el-dialog>--> |
| | | |
| | | <CopperPrintingForm |
| | | v-if="copperPrintingFormVisible" |
| | | v-model:isShow="copperPrintingFormVisible" |
| | | :isEdit="false" |
| | | :row="currentReportRow" |
| | | @refreshData="fetchReportsForProcess(selectedProcess.value)"/> |
| | | <VoltageSortingForm |
| | | v-if="voltageSortingFormVisible" |
| | | v-model:isShow="voltageSortingFormVisible" |
| | | :isEdit="false" |
| | | :row="currentReportRow" |
| | | @refreshData="fetchReportsForProcess(selectedProcess.value)"/> |
| | | <GranulationForm |
| | | v-if="granulationFormVisible" |
| | | v-model:isShow="granulationFormVisible" |
| | | :isEdit="false" |
| | | :row="currentReportRow" |
| | | @refreshData="fetchReportsForProcess(selectedProcess.value)"/> |
| | | <Detail |
| | | v-if="reportRecordDialogVisible" |
| | | v-model:isShow="reportRecordDialogVisible" |
| | | @refreshData="fetchReportsForProcess(selectedProcess.value)" |
| | | :row="currentReportRow"/> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from "vue"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | |
| | | import { computed, onMounted, ref, watch } from "vue"; |
| | | import { useRoute } from "vue-router"; |
| | | import { getByProductOrderId } from "@/api/productionManagement/workOrder.js"; |
| | | import { getByProductWorkOrderId } from "@/api/productionManagement/productionProductMain.js"; |
| | | const VoltageSortingForm = defineAsyncComponent(() => import("@/views/productionManagement/workOrder/components/VoltageSortingForm.vue")); |
| | | const CopperPrintingForm = defineAsyncComponent(() => import("@/views/productionManagement/workOrder/components/CopperPrintingForm.vue")); |
| | | const GranulationForm = defineAsyncComponent(() => import("@/views/productionManagement/workOrder/components/GranulationForm.vue")); |
| | | const Detail = defineAsyncComponent(() => import("@/views/productionManagement/productionReporting/components/Detail.vue")); |
| | | const route = useRoute(); |
| | | |
| | | const header = computed(() => ({ |
| | |
| | | specificationModel: route.query.specificationModel, |
| | | })); |
| | | |
| | | // 模拟工序数据(后续用接口替换) |
| | | const processes = computed(() => [ |
| | | { |
| | | processCode: "GX-001", |
| | | processName: "备料", |
| | | inputQty: 1000, |
| | | outputQty: 980, |
| | | qualifiedQty: 970, |
| | | badQty: 10, |
| | | status: "success", |
| | | }, |
| | | { |
| | | processCode: "GX-002", |
| | | processName: "成型", |
| | | inputQty: 980, |
| | | outputQty: 960, |
| | | qualifiedQty: 948, |
| | | badQty: 12, |
| | | status: "process", |
| | | }, |
| | | { |
| | | processCode: "GX-003", |
| | | processName: "烘干", |
| | | inputQty: 960, |
| | | outputQty: 950, |
| | | qualifiedQty: 948, |
| | | badQty: 2, |
| | | status: "wait", |
| | | }, |
| | | { |
| | | processCode: "GX-004", |
| | | processName: "包装入库", |
| | | inputQty: 950, |
| | | outputQty: 920, |
| | | qualifiedQty: 918, |
| | | badQty: 2, |
| | | status: "wait", |
| | | }, |
| | | ]); |
| | | // 工序数据(接口替换) |
| | | const processes = ref([]); |
| | | |
| | | const selectedIndex = ref(null); |
| | | const copperPrintingFormVisible = ref(false); |
| | | const voltageSortingFormVisible = ref(false); |
| | | const granulationFormVisible = ref(false); |
| | | |
| | | const normalizeStatus = (statusText, completionStatus, inputQty, outputQty) => { |
| | | const s = statusText === null || statusText === undefined ? "" : String(statusText).trim(); |
| | | |
| | | // 按接口实际三种状态:已生成 / 生产中 / 待生产 |
| | | if (s.includes("生产中")) return "process"; |
| | | if (s.includes("待生产")) return "wait"; |
| | | if (s.includes("已生产")) return "success"; |
| | | |
| | | // 兜底:仍按 completionStatus 做 0~100 判断 |
| | | const cs = Number(completionStatus); |
| | | if (Number.isFinite(cs)) { |
| | | if (cs >= 100) return "success"; |
| | | if (cs > 0) return "process"; |
| | | return "wait"; |
| | | } |
| | | |
| | | // 再兜底:用数量判断 |
| | | if (Number.isFinite(inputQty) && inputQty > 0 && Number.isFinite(outputQty) && outputQty >= inputQty) return "success"; |
| | | if (Number.isFinite(outputQty) && outputQty > 0) return "process"; |
| | | return "wait"; |
| | | }; |
| | | |
| | | const normalizeProcess = (item) => { |
| | | // 字段以接口约定为准(你给的截图字段映射) |
| | | // 工序:completionStatus/statusText/processNo/scrapRate/planQuantity/completeQuantity/completeQty/scrapQty |
| | | const inputQty = Number(item?.planQuantity ?? item?.inputQty ?? 0); |
| | | const outputQty = Number(item?.completeQuantity ?? item?.outputQty ?? 0); |
| | | const qualifiedQty = Number(item?.completeQty ?? item?.qualifiedQty ?? item?.goodQty ?? 0); |
| | | const badQty = Number(item?.scrapQty ?? item?.badQty ?? item?.defectQty ?? 0); |
| | | const completionStatus = Number(item?.completionStatus ?? 0); |
| | | const scrapRate = Number(item?.scrapRate ?? NaN); |
| | | |
| | | const status = normalizeStatus(item?.statusText ?? item?.status ?? item?.workStatus ?? item?.processStatus ?? item?.state, completionStatus, inputQty, outputQty); |
| | | |
| | | return { |
| | | processCode: item?.processNo ?? item?.processCode ?? item?.processWorkOrderCode ?? "", |
| | | processName: item?.processName ?? item?.processWorkOrderName ?? item?.processNo ?? "", |
| | | productWorkOrderId: item?.productWorkOrderId ?? item?.workOrderId ?? item?.id ?? null, |
| | | inputQty: Number.isFinite(inputQty) ? inputQty : 0, |
| | | outputQty: Number.isFinite(outputQty) ? outputQty : 0, |
| | | qualifiedQty: Math.max(0, Number.isFinite(qualifiedQty) ? qualifiedQty : 0), |
| | | badQty: Math.max(0, Number.isFinite(badQty) ? badQty : 0), |
| | | completionStatus: Number.isFinite(completionStatus) ? completionStatus : 0, |
| | | scrapRate: Number.isFinite(scrapRate) ? scrapRate : null, |
| | | status, |
| | | }; |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | const productOrderId = header.value?.orderId; |
| | | if (!productOrderId) return; |
| | | |
| | | try { |
| | | const res = await getByProductOrderId(productOrderId); |
| | | const payload = res?.data; |
| | | const list = Array.isArray(payload) ? payload : payload?.records || payload?.data || []; |
| | | processes.value = list.map((it) => normalizeProcess(it)); |
| | | } catch (e) { |
| | | console.error("获取工序工单列表失败:", e); |
| | | processes.value = []; |
| | | } |
| | | }); |
| | | |
| | | // 默认选中第一道序(接口数据就绪后仍可从 0 开始) |
| | | const selectedIndex = ref(0); |
| | | |
| | | const selectProcess = (idx) => { |
| | | selectedIndex.value = idx; |
| | | }; |
| | | |
| | | watch( |
| | | () => (processes.value || []).length, |
| | | (len) => { |
| | | if (!len) return; |
| | | if (selectedIndex.value >= len) selectedIndex.value = len - 1; |
| | | if (selectedIndex.value < 0) selectedIndex.value = 0; |
| | | } |
| | | ); |
| | | |
| | | const selectedProcess = computed(() => { |
| | | if (selectedIndex.value === null || selectedIndex.value === undefined) return null; |
| | | return (processes.value || [])[selectedIndex.value] || null; |
| | | const list = processes.value || []; |
| | | if (!list.length) return null; |
| | | const raw = selectedIndex.value; |
| | | const idx = |
| | | raw === null || raw === undefined |
| | | ? 0 |
| | | : Math.min(Math.max(0, raw), list.length - 1); |
| | | return list[idx] || null; |
| | | }); |
| | | |
| | | // 模拟报工信息(后续用接口替换) |
| | | const mockReports = computed(() => { |
| | | const p = selectedProcess.value; |
| | | if (!p) return []; |
| | | const code = p.processCode || "GX"; |
| | | return [ |
| | | { |
| | | reportNo: `${code}-BG-0001`, |
| | | reportUser: "张三", |
| | | reportTime: "2026-03-14 09:20", |
| | | outputQty: Math.floor((p.outputQty ?? 0) * 0.4), |
| | | badQty: Math.floor((p.badQty ?? 0) * 0.4), |
| | | remark: "正常报工", |
| | | }, |
| | | { |
| | | reportNo: `${code}-BG-0002`, |
| | | reportUser: "李四", |
| | | reportTime: "2026-03-14 13:45", |
| | | outputQty: Math.floor((p.outputQty ?? 0) * 0.35), |
| | | badQty: Math.floor((p.badQty ?? 0) * 0.35), |
| | | remark: "设备调试后恢复", |
| | | }, |
| | | { |
| | | reportNo: `${code}-BG-0003`, |
| | | reportUser: "王五", |
| | | reportTime: "2026-03-14 17:10", |
| | | outputQty: Math.max(0, (p.outputQty ?? 0) - Math.floor((p.outputQty ?? 0) * 0.75)), |
| | | badQty: Math.max(0, (p.badQty ?? 0) - Math.floor((p.badQty ?? 0) * 0.75)), |
| | | remark: "收尾", |
| | | }, |
| | | ]; |
| | | }); |
| | | const reports = ref([]); |
| | | const reportLoading = ref(false); |
| | | |
| | | const normalizeReport = (item) => { |
| | | return { |
| | | ...item, |
| | | quantity: Math.max(0, Number.isFinite(item.quantity) ? item.quantity : 0), |
| | | scrapQty: Math.max(0, Number.isFinite(item.scrapQty) ? item.scrapQty : 0), |
| | | qualifiedQty: Math.max(0, Number.isFinite(item.qualifiedQty) ? item.qualifiedQty : 0), |
| | | }; |
| | | }; |
| | | |
| | | const fetchReportsForProcess = async (p) => { |
| | | if (!p) { |
| | | reports.value = []; |
| | | return; |
| | | } |
| | | const productWorkOrderId = p.productWorkOrderId ?? p.id ?? p.workOrderId ?? null; |
| | | if (!productWorkOrderId) { |
| | | reports.value = []; |
| | | return; |
| | | } |
| | | |
| | | reportLoading.value = true; |
| | | try { |
| | | const res = await getByProductWorkOrderId(productWorkOrderId); |
| | | const payload = res?.data; |
| | | const list = Array.isArray(payload) |
| | | ? payload |
| | | : payload?.records || payload?.data || payload?.list || []; |
| | | reports.value = list.map((it) => normalizeReport(it)); |
| | | } catch (e) { |
| | | console.error("获取报工信息失败:", e); |
| | | reports.value = []; |
| | | } finally { |
| | | reportLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | watch( |
| | | () => selectedProcess.value, |
| | | (val) => { |
| | | fetchReportsForProcess(val); |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | const viewReportRecord = (row) => { |
| | | if (!row?.productNo) return; |
| | | currentReportRow.value = row; |
| | | if (row.process ==='印铜' || row.process ==='印银') { |
| | | copperPrintingFormVisible.value = true; |
| | | } else if (row.process === '电压分选') { |
| | | voltageSortingFormVisible.value = true; |
| | | } else if (row.process === '造粒') { |
| | | granulationFormVisible.value = true; |
| | | } else { |
| | | reportRecordDialogVisible.value = true; |
| | | } |
| | | }; |
| | | |
| | | const reportRecordDialogVisible = ref(false); |
| | | const currentReportRow = ref(null); |
| | | |
| | | const clampPercentage = (val) => { |
| | | const n = Number(val); |
| | |
| | | return Math.round(n); |
| | | }; |
| | | |
| | | const statusTagText = (status) => { |
| | | if (status === "success") return "已生产"; |
| | | if (status === "process") return "生产中"; |
| | | if (status === "wait") return "待生产"; |
| | | return String(status ?? ""); |
| | | }; |
| | | |
| | | // el-steps: active 为当前进行中的步骤下标(模拟) |
| | | const active = computed(() => { |
| | | const list = processes.value || []; |
| | | const idx = list.findIndex((p) => p.status === "process"); |
| | | return idx >= 0 ? idx : 0; |
| | | // 激活状态为“待生产”的上一条 |
| | | const firstWaitIdx = list.findIndex((p) => p?.status === "wait"); |
| | | if (firstWaitIdx > 0) return firstWaitIdx - 1; |
| | | |
| | | // 如果没有待生产: |
| | | // - 且最后一条是生产中:激活最后一条 |
| | | // - 否则:没有激活样式 |
| | | const lastIdx = list.length - 1; |
| | | if (lastIdx >= 0 && list[lastIdx]?.status === "process") return lastIdx; |
| | | return null; |
| | | }); |
| | | |
| | | const currentProcess = computed(() => { |
| | | const list = processes.value || []; |
| | | return list[active.value] || null; |
| | | }); |
| | | |
| | | // 当前工序进度:用产出/投入估算(UI 先跑通,后续按真实规则替换) |
| | | const currentProcessPercentage = computed(() => { |
| | | const p = currentProcess.value; |
| | | // 工序进度:用产出/投入估算(UI 先跑通,后续按真实规则替换) |
| | | const processPercentage = (p) => { |
| | | if (!p) return 0; |
| | | // 优先使用接口字段 completionStatus(你给的截图“工序进度”) |
| | | const cs = Number(p?.completionStatus ?? NaN); |
| | | if (Number.isFinite(cs)) return clampPercentage(cs); |
| | | |
| | | // 兜底:用产出/投入估算 |
| | | const input = Number(p.inputQty ?? 0); |
| | | const output = Number(p.outputQty ?? 0); |
| | | if (!Number.isFinite(input) || input <= 0) return 0; |
| | | return clampPercentage((output / input) * 100); |
| | | }); |
| | | }; |
| | | |
| | | // 30/50/80/100 分段颜色:红/橙/蓝/绿 |
| | | const progressColor = (percentage) => { |
| | | const p = clampPercentage(percentage); |
| | | if (p < 30) return "#f56c6c"; |
| | | if (p < 50) return "#e6a23c"; |
| | | if (p < 80) return "#409eff"; |
| | | return "#67c23a"; |
| | | }; |
| | | |
| | | // 不良率:不良数量 / 产出数量(先按此口径,后续对接接口可调整) |
| | | const defectRateText = (p) => { |
| | | // 优先使用接口字段 scrapRate(你给的截图“不良率”) |
| | | const scrapRate = Number(p?.scrapRate ?? NaN); |
| | | if (Number.isFinite(scrapRate)) { |
| | | // 有些接口 scrapRate 可能是 0~1 或 0~100,这里做一个简单判断 |
| | | const percent = scrapRate <= 1 ? scrapRate * 100 : scrapRate; |
| | | return `${percent.toFixed(2)}%`; |
| | | } |
| | | |
| | | // 兜底:不良数量 / 产出数量 |
| | | const bad = Number(p?.badQty ?? 0); |
| | | const output = Number(p?.outputQty ?? 0); |
| | | if (!Number.isFinite(bad) || bad <= 0) return "0%"; |
| | |
| | | align-items: center; |
| | | padding: 2px 6px; |
| | | border-radius: 6px; |
| | | transition: background-color 0.15s ease; |
| | | transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease; |
| | | &:hover { |
| | | background: #f5f7fa; |
| | | transform: translateX(2px); |
| | | } |
| | | &.selected { |
| | | background: rgba(64, 158, 255, 0.12); |
| | | background: rgba(64, 158, 255, 0.18); |
| | | color: #409eff; |
| | | font-weight: 700; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .step-panel { |
| | | position: relative; |
| | | cursor: pointer; |
| | | background: #f6f8fb; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 10px; |
| | |
| | | width: 100%; |
| | | max-width: none; |
| | | box-sizing: border-box; |
| | | transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, |
| | | background 0.25s ease; |
| | | &:hover { |
| | | border-color: #c6e2ff; |
| | | box-shadow: 0 4px 14px rgba(64, 158, 255, 0.12); |
| | | transform: translateY(-1px); |
| | | } |
| | | } |
| | | |
| | | /* 被选中:沿用原「高亮」语义并加呼吸动画 */ |
| | | .step-panel-selected { |
| | | background: #ecf5ff; |
| | | border-color: #409eff; |
| | | animation: step-panel-selected-pulse 2.2s ease-in-out infinite; |
| | | &:hover { |
| | | transform: translateY(-1px); |
| | | } |
| | | } |
| | | |
| | | @keyframes step-panel-selected-pulse { |
| | | 0%, |
| | | 100% { |
| | | box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.22); |
| | | } |
| | | 50% { |
| | | box-shadow: 0 0 0 6px rgba(64, 158, 255, 0.12); |
| | | } |
| | | } |
| | | |
| | | /* 当前生产中的工序:橙色主题,与选中区分;若同时选中则以选中为主,仅保留角标 */ |
| | | .step-panel-current:not(.step-panel-selected) { |
| | | background: #fdf6ec; |
| | | border-color: #e6a23c; |
| | | border-left: 4px solid #e6a23c; |
| | | padding-left: 9px; |
| | | &:hover { |
| | | box-shadow: 0 4px 14px rgba(230, 162, 60, 0.18); |
| | | } |
| | | } |
| | | |
| | | .step-current-badge { |
| | | position: absolute; |
| | | top: 8px; |
| | | right: 10px; |
| | | z-index: 1; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | color: #b88230; |
| | | background: rgba(230, 162, 60, 0.18); |
| | | border: 1px solid rgba(230, 162, 60, 0.45); |
| | | border-radius: 4px; |
| | | padding: 2px 8px; |
| | | line-height: 1.2; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .step-status-badge { |
| | | position: absolute; |
| | | top: 8px; |
| | | right: 10px; |
| | | z-index: 1; |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | border-radius: 4px; |
| | | padding: 2px 8px; |
| | | line-height: 1.2; |
| | | pointer-events: none; |
| | | white-space: nowrap; |
| | | border: 1px solid transparent; |
| | | } |
| | | |
| | | .step-status-badge-success { |
| | | color: #67c23a; |
| | | background: rgba(103, 194, 58, 0.14); |
| | | border-color: rgba(103, 194, 58, 0.35); |
| | | } |
| | | |
| | | .step-status-badge-process { |
| | | color: #b88230; |
| | | background: rgba(230, 162, 60, 0.18); |
| | | border-color: rgba(230, 162, 60, 0.45); |
| | | } |
| | | |
| | | .step-status-badge-wait { |
| | | color: #909399; |
| | | background: rgba(144, 147, 153, 0.12); |
| | | border-color: rgba(144, 147, 153, 0.35); |
| | | } |
| | | |
| | | .right-panel { |
| | |
| | | } |
| | | } |
| | | |
| | | .report-record-placeholder { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | min-height: 120px; |
| | | color: #303133; |
| | | .placeholder-tip { |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | .step-meta { |
| | | display: flex; |
| | | gap: 16px; |