| | |
| | | <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" :class="{ 'step-panel-active': idx === active }"> |
| | | <div |
| | | class="step-panel" |
| | | :class="{ |
| | | 'step-panel-selected': idx === selectedIndex, |
| | | 'step-panel-current': idx === active, |
| | | }" |
| | | @click="selectProcess(idx)" |
| | | > |
| | | <span v-if="idx === active" class="step-current-badge">生产中</span> |
| | | <div v-if="p.status !== 'wait'" class="current-progress"> |
| | | <div class="current-progress-head"> |
| | | <span class="current-progress-title">工序进度</span> |
| | |
| | | </div> |
| | | |
| | | <div v-if="!selectedProcess" class="right-empty"> |
| | | 点击左侧某个工序,右侧展示该工序的报工明细。 |
| | | 暂无工序数据。 |
| | | </div> |
| | | |
| | | <div v-else class="right-content"> |
| | |
| | | <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="qualifiedQty" 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="操作" 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> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from "vue"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | import { computed, ref, watch } from "vue"; |
| | | import { useRoute } from "vue-router"; |
| | | |
| | | const route = useRoute(); |
| | | |
| | |
| | | }, |
| | | ]); |
| | | |
| | | const selectedIndex = ref(null); |
| | | // 默认选中第一道序(接口数据就绪后仍可从 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 p = selectedProcess.value; |
| | | if (!p) return []; |
| | | const code = p.processCode || "GX"; |
| | | return [ |
| | | const reports = [ |
| | | { |
| | | reportNo: `${code}-BG-0001`, |
| | | reportUser: "张三", |
| | |
| | | remark: "收尾", |
| | | }, |
| | | ]; |
| | | return reports.map((item) => ({ |
| | | ...item, |
| | | qualifiedQty: Math.max(0, Number(item.outputQty ?? 0) - Number(item.badQty ?? 0)), |
| | | })); |
| | | }); |
| | | |
| | | const viewReportRecord = (row) => { |
| | | if (!row?.reportNo) return; |
| | | currentReportRow.value = row; |
| | | reportRecordDialogVisible.value = true; |
| | | }; |
| | | |
| | | const reportRecordDialogVisible = ref(false); |
| | | const currentReportRow = ref(null); |
| | | |
| | | const clampPercentage = (val) => { |
| | | const n = Number(val); |
| | |
| | | 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-active { |
| | | |
| | | /* 被选中:沿用原「高亮」语义并加呼吸动画 */ |
| | | .step-panel-selected { |
| | | background: #ecf5ff; |
| | | border-color: #79bbff; |
| | | box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15); |
| | | 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; |
| | | } |
| | | |
| | | .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; |