|
|
<template> <div class="pda-container"> <!-- 头部栏 --> <div class="header-bar"> <div class="header-left" @click="$router.back()"> <i class="el-icon-arrow-left"></i> <span>客户发料</span> </div> <div class="header-right" @click="$router.push({ path: '/' })">首页</div> </div>
<!-- 搜索框 --> <div class="search-container"> <el-input clearable class="compact-input" v-model="scanCode" placeholder="请扫描物料标签" prefix-icon="el-icon-search" @keyup.enter.native="handleScan" ref="scanInput" /> <div class="mode-switch"> <el-switch class="custom-switch" v-model="isRemoveMode" active-color="#ff4949" inactive-color="#13ce66"> </el-switch> <span v-if="isRemoveMode" class="switch-text">{{ "移除" }}</span> <span v-else class="switch-text2">{{ "添加" }}</span> </div> </div>
<!-- 统一滚动容器 --> <div class="content-scroll-container"> <!-- 订单信息卡片 --> <div class="work-order-list" v-if="orderNo"> <div class="work-order-card"> <div class="card-title"> <span class="title-label">申请单号:{{ orderInfo.orderNo }} </span> <span class="title-label">本次: {{ totalScannedQty }}</span> </div> </div> </div>
<!-- 发料信息确认标题 --> <div class="section-title"> <div class="title-left"> <i class="el-icon-circle-check"></i> <span>发料信息确认</span> </div> </div>
<!-- 发料标签列表 - 卡片形式 --> <div class="label-card-container"> <div v-for="(label, index) in labelList" :key="label.id" class="label-card"> <div class="card-edit-icon" @click.stop="openEditDialog(label, index)"> <i class="el-icon-edit"></i> </div> <div class="card-content" @click="openEditDialog(label, index)"> <div class="card-row"> <span class="card-label">标签号:</span> <span class="card-value">{{ label.unitId }}</span> </div> <div class="card-row"> <span class="card-label">物料号:</span> <span class="card-value">{{ label.partNo || '-' }}</span> </div> <div class="card-row"> <span class="card-label">批次号:</span> <span class="card-value">{{ label.batchNo || '-' }}</span> </div> <div class="card-row"> <span class="card-label">库位:</span> <span class="card-value">{{ label.locationId || '-' }}</span> </div> <div class="card-row"> <span class="card-label">发料数量:</span> <span class="card-value highlight">{{ label.qtyToIssue || 0 }}</span> </div> </div> </div>
<!-- 空状态 --> <div v-if="labelList.length === 0" class="empty-labels"> <p>暂无扫描标签</p> </div> </div> </div>
<!-- 底部操作按钮 --> <div class="bottom-actions"> <el-button class="action-btn primary" style="margin-left: 10px" @click="confirmIssue" :loading="loading || summaryLoading"> 确定发料 </el-button> <!-- <button class="action-btn secondary" style="margin-left: 10px" @click="openPrintDialog"> 打印 </button> -->
<button class="action-btn secondary" style="margin-left: 10px" @click="cancelIssue"> 取消 </button> </div>
<!-- 发料前比对弹框 --> <div v-if="showSummaryDialog" class="summary-overlay"> <div class="summary-modal"> <div class="modal-header"> <span class="modal-title">发料物料比对</span> <i class="el-icon-close close-btn" @click="closeSummaryDialog"></i> </div> <div class="modal-body summary-body"> <div v-if="summaryLoading" class="summary-loading"> 正在获取申请单物料,请稍候... </div> <template v-else> <div class="summary-section"> <div class="section-title-row"> <div> <span class="section-title-text">存在物料</span> <span class="section-sub">申请单与扫描均存在</span> </div> <span class="section-count">共 {{ comparisonData.existing.length }} 条</span> </div> <div v-if="comparisonData.existing.length === 0" class="summary-empty"> 暂无相关物料 </div> <div v-else class="summary-table summary-table--4"> <div class="summary-table-row summary-table-head"> <div>物料号</div> <div>需求数量</div> <div>扫描数量</div> <div>差异</div> </div> <div v-for="item in comparisonData.existing" :key="'exist-' + item.partNo" class="summary-table-row"> <div class="part">{{ item.partNo }}</div> <div>{{ formatQty(item.requiredQty) }}</div> <div>{{ formatQty(item.scannedQty) }}</div> <div class="badge" :class="{ success: item.difference >= 0, danger: item.difference < 0, }"> {{ formatQty(item.difference) }} </div> </div> </div> </div>
<div class="summary-section" v-if="comparisonData.missing.length > 0"> <div class="section-title-row"> <div> <span class="section-title-text">未扫物料</span> <span class="section-sub">申请单存在但未扫描</span> </div> <span class="section-count">共 {{ comparisonData.missing.length }} 条</span> </div> <div class="summary-table summary-table--3"> <div class="summary-table-row summary-table-head"> <div>物料号</div> <div>需求数量</div> <div>状态</div> </div> <div v-for="item in comparisonData.missing" :key="'missing-' + item.partNo" class="summary-table-row"> <div class="part">{{ item.partNo }}</div> <div>{{ formatQty(item.requiredQty) }}</div> <div><span class="badge warn">未扫描</span></div> </div> </div> </div>
<div class="summary-section" v-if="comparisonData.extra.length > 0"> <div class="section-title-row"> <div> <span class="section-title-text">多余物料</span> <span class="section-sub">扫描存在但申请单无记录</span> </div> <span class="section-count">共 {{ comparisonData.extra.length }} 条</span> </div> <div class="summary-table summary-table--3"> <div class="summary-table-row summary-table-head"> <div>物料号</div> <div>扫描数量</div> <div>状态</div> </div> <div v-for="item in comparisonData.extra" :key="'extra-' + item.partNo" class="summary-table-row"> <div class="part">{{ item.partNo }}</div> <div>{{ formatQty(item.scannedQty) }}</div> <div><span class="badge info">申请单未包含</span></div> </div> </div> </div> </template> </div> <div class="modal-footer"> <el-button class="btn-cancel" @click="closeSummaryDialog" :disabled="loading"> 返回修改 </el-button> <el-button class="btn-confirm" @click="proceedIssueConfirm" :disabled="loading"> {{ loading ? '发料中...' : '确定预留发料' }} </el-button> </div> </div> </div>
<!-- 编辑标签弹框 --> <div v-if="showEditDialog" class="edit-overlay"> <div class="edit-modal"> <div class="modal-header"> <span class="modal-title">编辑标签信息</span> <i class="el-icon-close close-btn" @click="closeEditDialog"></i> </div>
<div class="modal-body scrollable"> <div class="form-group"> <label class="form-label">物料标签</label> <el-input v-model="editForm.labelCode" disabled class="form-input" /> </div>
<div class="form-group"> <label class="form-label">物料号</label> <el-input v-model="editForm.partNo" disabled class="form-input" /> </div>
<div class="form-group"> <label class="form-label">批次号</label> <el-input v-model="editForm.batchNo" placeholder="请输入批次号" disabled class="form-input" /> </div>
<div class="form-group"> <label class="form-label">库位 <span class="required">*</span></label> <el-input v-model="editForm.locationId" placeholder="请输入库位" disabled class="form-input" /> </div>
<div class="form-group"> <label class="form-label">发料数量 <span class="required">*</span></label> <el-input v-model="editForm.quantity" type="number" :min="0" placeholder="请输入发料数量" class="form-input" /> </div>
<div class="form-group"> <label class="form-label">剩余高度</label> <el-input v-model="editForm.height" type="number" :min="0" placeholder="请输入高度" class="form-input" /> </div> </div>
<div class="modal-footer"> <button class="btn-cancel" @click="closeEditDialog">取消</button> <button class="btn-confirm" @click="confirmEdit">确定</button> </div> </div> </div>
<!-- 打印选择弹框 --> <div v-if="showPrintDialog" class="print-overlay"> <div class="print-modal"> <div class="modal-header"> <span class="modal-title">选择打印标签</span> <i class="el-icon-close close-btn" @click="closePrintDialog"></i> </div>
<div class="modal-body"> <div class="print-section-title"> <div class="title-left"> <i class="el-icon-tickets"></i> <span>请选择需要打印的标签</span> </div> <div class="title-right"> <el-checkbox v-model="printAllChecked" @change="togglePrintAll">全选</el-checkbox> </div> </div>
<div class="print-label-list"> <div class="list-header"> <div class="col-no">NO.</div> <div class="col-label">物料标签</div> <div class="col-part">库位</div> <div class="col-qty">发料数量</div> <div class="col-check">选择</div> </div> <div v-for="(label, index) in printLabelList" :key="label.id" class="list-item"> <div class="col-no">{{ index + 1 }}</div> <div class="col-label">{{ label.labelCode }}</div> <div class="col-part">{{ label.locationId }}</div> <div class="col-qty"> {{ Number( label.qtyToIssue ) || 0 }} </div> <div class="col-check"> <el-checkbox v-model="label.__checked"></el-checkbox> </div> </div>
<div v-if="printLabelList.length === 0" class="empty-labels"> <p>暂无扫描标签</p> </div> </div> </div>
<div class="modal-footer"> <button class="btn-secondary" @click="closePrintDialog">取消</button> <button class="btn-secondary" @click="openQuantityDialog('QC')">打印QC</button> <button class="btn-secondary" @click="openQuantityDialog('BOX')">打印BOX</button> </div> </div> </div>
<!-- QC数量输入弹框 --> <div v-if="showQCDialog" class="quantity-overlay"> <div class="quantity-modal"> <div class="modal-header"> <span class="modal-title">输入QC打印数量</span> <i class="el-icon-close close-btn" @click="closeQuantityDialog"></i> </div>
<div class="modal-body"> <div class="form-group"> <label class="form-label">QC打印数量 <span class="required">*</span></label> <el-input v-model="qcQuantity" type="number" :min="1" placeholder="请输入QC打印数量" class="form-input" /> </div> </div>
<div class="modal-footer"> <button class="btn-cancel" @click="closeQuantityDialog">取消</button> <button class="btn-confirm" @click="confirmQCPrint">确定打印</button> </div> </div> </div>
<!-- BOX数量输入弹框 --> <div v-if="showBOXDialog" class="quantity-overlay"> <div class="quantity-modal"> <div class="modal-header"> <span class="modal-title">输入BOX打印数量</span> <i class="el-icon-close close-btn" @click="closeQuantityDialog"></i> </div>
<div class="modal-body"> <div class="form-group"> <label class="form-label">BOX打印数量 <span class="required">*</span></label> <el-input v-model="boxQuantity" type="number" :min="1" placeholder="请输入BOX打印数量" class="form-input" /> </div> </div>
<div class="modal-footer"> <button class="btn-cancel" @click="closeQuantityDialog">取消</button> <button class="btn-confirm" @click="confirmBOXPrint">确定打印</button> </div> </div> </div> </div></template>
<script>import { getInventoryPart, scanCustomerIssueMaterialLabel, customerIssueConfirm, updateShipmentHuDetail, getShipmentHuDetail, removeShipmentHuDetail, getCustomerIssueNotifyHeaderOrderMaterialList,} from '@/api/customerIssue/customer-issue'import moment from 'moment'import { printLabelCommon } from '@/api/production/production-inbound.js'
export default { data() { return { scanCode: '', orderInfo: {}, labelList: [], orderNo: '', orderType: '', isRemoveMode: false, // 默认为添加模式
partNo: '', // 物料编码
transactionId: '', // 关联单号
accountingId: '', // 会计科目
quantity: '', // 本次发料数量
qtyIssued: 0, // 已发料数量
batchNo: '', // 批次号
// 编辑弹框相关
showEditDialog: false, editForm: { detailId: '', labelCode: '', partNo: '', batchNo: '', locationId: '', warehouseId: '', wdrNo: '', engChgLevel: '', quantity: 0, height: null, }, editIndex: -1, // 当前编辑的标签索引
unissureQty: 0, // 需求数量
itemNo: '', // 物料ID
loading: false, // 打印弹框相关
showPrintDialog: false, printLabelList: [], printAllChecked: true, assignedQty: 0, // 已发数量
// 数量输入弹框相关
showQCDialog: false, showBOXDialog: false, qcQuantity: 1, boxQuantity: 1, currentPrintType: '', // 当前选择的打印类型
// 发料前对比弹框
showSummaryDialog: false, summaryLoading: false, comparisonData: { existing: [], missing: [], extra: [], }, pendingIssueParams: null, shipmentLine: [], } }, computed: { totalScannedQty() { return this.labelList.reduce( (sum, l) => sum + (Number(l.qtyToIssue) || 0), 0 ) }, }, methods: { formatDate(date) { return date ? moment(date).format('YYYY-MM-DD') : '' }, formatQty(value) { const num = Number(value) if (!Number.isFinite(num)) { return 0 } return Number.isInteger(num) ? num : Number(num.toFixed(3)) }, // 精度安全的加法,避免浮点数精度丢失
safeAdd(a, b) { const num1 = Number(a) || 0 const num2 = Number(b) || 0 // 将数字转换为整数(保留4位小数精度),相加后再转换回小数
const factor = 10000 return Math.round((num1 * factor + num2 * factor)) / factor }, pickFieldValue(item, candidates = [], defaultValue = null) { if (!item || !candidates || candidates.length === 0) { return defaultValue } for (const field of candidates) { if ( Object.prototype.hasOwnProperty.call(item, field) && item[field] !== undefined && item[field] !== null && item[field] !== '' ) { return item[field] } } return defaultValue }, aggregateMaterials(list = [], keyFields = [], qtyFields = []) { const map = {} list.forEach((item) => { const partKey = this.pickFieldValue(item, keyFields) const normalizedKey = typeof partKey === 'string' ? partKey.trim() : partKey if (!normalizedKey) { return } const qtyValue = Number(this.pickFieldValue(item, qtyFields, 0)) || 0 if (!map[normalizedKey]) { map[normalizedKey] = { partNo: normalizedKey, qty: 0, } } map[normalizedKey].qty = this.safeAdd(map[normalizedKey].qty, qtyValue) }) return map }, // 处理扫描
handleScan() { if (!this.scanCode.trim()) { return }
if (this.isRemoveMode) { this.removeLabelByCode(this.scanCode.trim()) } else { this.validateAndAddLabel(this.scanCode.trim()) } this.scanCode = '' },
// 验证标签并添加到列表(保留客户发料功能)
validateAndAddLabel(labelCode) { const params = { scannedLabel: labelCode, workOrderNo: this.orderNo, site: localStorage.getItem('site'), batchNo: this.batchNo, } // 检查是否已经扫描过
const exists = this.labelList.find((item) => item.unitId === labelCode) if (exists) { this.$message.warning('该标签已扫描,请勿重复扫描') return } // 客户发料标签验证
scanCustomerIssueMaterialLabel(params) .then(({ data }) => { if (data.code === 0 && data) { // 添加到列表,保存更多字段信息
// 后端返回的是 ShipmentHuDetail 对象
const labelInfo = data.labelInfo this.labelList.push({ id: Date.now(), labelCode: labelCode, partNo: labelInfo.partNo || '', qtyToIssue: Number(labelInfo.qtyToIssue) || 0, batchNo: labelInfo.batchNo || '', locationId: labelInfo.locationId || '', warehouseId: labelInfo.warehouseId || '', wdrNo: labelInfo.wdr || '', engChgLevel: labelInfo.engChgLevel || '1', detailId: labelInfo.detailId || '', unitId: labelInfo.unitId || '', site: labelInfo.site || localStorage.getItem('site'), height: labelInfo.height || null, })
this.$message.success('操作成功') } else { this.$message.error(data.msg || '该标签不符合发料要求,请检查') } }) .catch(() => { this.$message.error('操作失败') }) },
// 通过条码移除标签
removeLabelByCode(labelCode) { const exists = this.labelList.find((item) => item.unitId === labelCode) if (!exists) { this.$message.warning('未找到该标签') return } let params = { site: localStorage.getItem('site'), unitId: labelCode, detailId: exists.detailId, } removeShipmentHuDetail(params) .then(({ data }) => { if (data.code === 0) { const index = this.labelList.findIndex( (item) => item.unitId === labelCode ) if (index !== -1) { this.labelList.splice(index, 1) this.$message.success('操作成功') } else { this.$message.warning('未找到该标签') } } else { this.$message.error(data.msg || '移除标签失败') } }) .catch(() => { this.$message.error('移除标签失败') }) },
// 打开编辑弹框
openEditDialog(label, index) { this.editForm = { detailId: label.detailId, labelCode: label.unitId, partNo: label.partNo, batchNo: label.batchNo || '', locationId: label.locationId || '', warehouseId: label.warehouseId || '', wdrNo: label.wdrNo || '', engChgLevel: label.engChgLevel || '1', quantity: label.qtyToIssue, height: label.height, } this.editIndex = index this.showEditDialog = true },
// 关闭编辑弹框
closeEditDialog() { this.showEditDialog = false this.editForm = { detailId: '', labelCode: '', partNo: '', batchNo: '', locationId: '', warehouseId: '', wdrNo: '', engChgLevel: '', quantity: 0, height: null, } this.editIndex = -1 },
// 确认编辑
confirmEdit() { // 验证必填字段
if (!this.editForm.locationId.trim()) { this.$message.warning('请输入库位') return }
if (!this.editForm.quantity || this.editForm.quantity <= 0) { this.$message.warning('请输入有效的发料数量') return }
// 调用后端接口更新
const updateParams = { detailId: this.editForm.detailId, site: this.labelList[this.editIndex].site || localStorage.getItem('site'), unitId: this.labelList[this.editIndex].unitId, locationId: this.editForm.locationId, qtyToIssue: Number(this.editForm.quantity), batchNo: this.editForm.batchNo, warehouseId: this.editForm.warehouseId, engChgLevel: this.editForm.engChgLevel, wdr: this.editForm.wdrNo, height: this.editForm.height, }
updateShipmentHuDetail(updateParams) .then(({ data }) => { if (data.code === 0) { // 更新本地列表
if (this.editIndex >= 0 && this.editIndex < this.labelList.length) { this.labelList[this.editIndex].locationId = this.editForm.locationId this.labelList[this.editIndex].qtyToIssue = Number( this.editForm.quantity ) this.labelList[this.editIndex].batchNo = this.editForm.batchNo this.labelList[this.editIndex].warehouseId = this.editForm.warehouseId this.labelList[this.editIndex].wdrNo = this.editForm.wdrNo this.labelList[this.editIndex].engChgLevel = this.editForm.engChgLevel this.labelList[this.editIndex].height = this.editForm.height } this.$message.success('修改成功') this.closeEditDialog() } else { this.$message.error(data.msg || '修改失败') } }) .catch(() => { this.$message.error('修改失败') }) },
async confirmIssue() { if (this.labelList.length === 0) { this.$message.warning('请先扫描发料标签') return }
if (this.totalScannedQty > this.orderInfo.quantity - this.qtyIssued) { this.$message.warning('扫描数量不能大于总发料数量和已发数量的差!') return } this.summaryLoading = true try { await this.prepareComparisonData() this.showSummaryDialog = true } catch (error) { this.$message.error( (error && error.message) || '获取申请单物料失败,请稍后重试' ) this.pendingIssueParams = null } finally { this.summaryLoading = false } }, buildIssueParams() { return { site: localStorage.getItem('site'), workOrderNo: this.orderNo, batchNo: this.batchNo, componentPartNo: this.partNo, transactionId: this.transactionId, accountingId: this.accountingId, itemNo: this.itemNo, ifsIssuedQty: this.qtyIssued, issueQty: this.quantity, selectedMaterials: this.labelList.map((label) => ({ labelCode: label.labelCode || label.unitId, issueQty: Number(label.qtyToIssue) || 0, batchNo: label.batchNo, partNo: label.partNo, locationId: label.locationId, warehouseId: label.warehouseId, wdrNo: label.wdrNo || '*', engChgLevel: label.engChgLevel || '1', })), shipmentLine: this.shipmentLine || [], } }, async prepareComparisonData() { this.shipmentLine = [] const params = { site: localStorage.getItem('site'), workOrderNo: this.orderNo, } await getCustomerIssueNotifyHeaderOrderMaterialList(params).then( ({ data }) => { if (!data || data.code !== 0) { throw new Error((data && data.msg) || '获取申请单物料失败') } const materials = data.data || [] this.$set(this,'shipmentLine',data.data) const labelMap = this.aggregateMaterials( this.labelList, ['partNo'], ['qtyToIssue', 'quantity', 'issueQty'] ) // 手动聚合订单物料,计算 INVENTORY_QTY - QTY_ASSIGNED 作为需求数量
const orderMap = {} materials.forEach((item) => { const partNo = this.pickFieldValue(item, ['INVENTORY_PART_NO']) const normalizedKey = typeof partNo === 'string' ? partNo.trim() : partNo if (!normalizedKey) { return } const inventoryQty = Number(this.pickFieldValue(item, ['INVENTORY_QTY'], 0)) || 0 const qtyAssigned = Number(this.pickFieldValue(item, ['QTY_ASSIGNED'], 0)) || 0 const requiredQty = this.safeAdd(inventoryQty, -qtyAssigned) if (!orderMap[normalizedKey]) { orderMap[normalizedKey] = { partNo: normalizedKey, qty: 0, } } orderMap[normalizedKey].qty = this.safeAdd(orderMap[normalizedKey].qty, requiredQty) })
const existing = [] const missing = [] Object.keys(orderMap).forEach((partNo) => { console.log('Comparing partNo:', orderMap[partNo])
const requiredQty = orderMap[partNo].qty if (labelMap[partNo]) { const scannedQty = labelMap[partNo].qty existing.push({ partNo, requiredQty, scannedQty, difference: scannedQty - requiredQty, }) } else { missing.push({ partNo, requiredQty, }) } })
const extra = [] Object.keys(labelMap).forEach((partNo) => { if (!orderMap[partNo]) { extra.push({ partNo, scannedQty: labelMap[partNo].qty, }) } })
this.comparisonData = { existing, missing, extra, } } ) }, closeSummaryDialog() { if (this.loading) { return } this.showSummaryDialog = false this.pendingIssueParams = null }, proceedIssueConfirm() { if (!this.pendingIssueParams) { this.pendingIssueParams = this.buildIssueParams() } this.submitIssue() }, submitIssue() { if (!this.pendingIssueParams) { this.$message.error('缺少发料参数,请重新确认') return } if (this.comparisonData.missing.length > 0) { this.$message.warning('存在未扫描物料,无法发料,请返回修改') return } if (this.comparisonData.extra.length > 0) { this.$message.warning('存在多余物料,无法发料,请返回修改') return } for (const item of this.comparisonData.existing) { if (item.difference != 0) { this.$message.warning( '存在发料数量超出或少于需求数量的物料,请返回修改' ) return } } let params = this.pendingIssueParams params.shipmentType = this.orderType
this.loading = true customerIssueConfirm(params) .then(({ data }) => { if (data.code === 0 && data) { if (data.unitIds.length > 0) { let printLabelType = '库存成品标签'
// 调用打印方法,传入unitId数组和标签类型
this.printViaServer(data.unitIds, printLabelType) } this.$message.success('客户发料成功') this.showSummaryDialog = false this.pendingIssueParams = null this.$router.push({ name: 'customerIssuePDA' }) } else { this.$message.error(data.msg || '操作失败') } }) .catch(() => { this.$message.error('操作失败') }) .finally(() => { this.loading = false }) },
async printViaServer(unitIds, printLabelType) { if (!unitIds || unitIds.length === 0) { console.warn('没有可打印的标签') return }
this.printLoading = true
try { const printRequest = { userId: localStorage.getItem('userName'), username: localStorage.getItem('userName'), site: localStorage.getItem('site'), unitIds: unitIds, labelType: printLabelType, } console.log('打印请求:', printRequest)
const { data } = await printLabelCommon(printRequest)
if (data.code === 200 || data.code === 0) { this.$message.success(`打印任务已发送!`) this.clearData() } else { this.$message.error(data.msg || '打印失败') } } catch (error) { console.error('服务器打印失败:', error) this.$message.error(`打印失败: ${error.message || error}`) } finally { this.printLoading = false } },
// 取消发料
cancelIssue() { if (this.labelList.length > 0) { this.$confirm('取消后将清空已扫描的标签,确定取消吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '继续操作', type: 'warning', }) .then(() => { this.$router.back() }) .catch(() => { // 用户选择继续操作
}) } else { this.$router.back() } },
// 加载订单详情
loadOrderDetails() { this.orderInfo.orderNo = this.orderNo this.orderInfo.itemNo = this.itemNo this.orderInfo.partNo = this.partNo this.getShipmentHuDetails() }, getShipmentHuDetails() { let params = { shipmentId: this.orderNo, partNo: this.partNo, site: localStorage.getItem('site'), } this.labelList = [] getShipmentHuDetail(params) .then(({ data }) => { if (data.code === 0 && data) { this.labelList = data.details || [] } else { this.$message.error(data.msg || '获取订单详情失败') } }) .catch(() => { this.$message.error('获取订单详情失败') }) },
// 打开打印弹框
openPrintDialog() { if (this.labelList.length === 0) { this.$message.warning('请先扫描发料标签') return }
// 复制标签列表并添加选中状态
this.printLabelList = this.labelList.map((label) => ({ ...label, __checked: true, })) this.printAllChecked = true this.showPrintDialog = true },
// 关闭打印弹框
closePrintDialog() { this.showPrintDialog = false this.printLabelList = [] this.printAllChecked = true },
// 全选/取消全选
togglePrintAll(checked) { this.printLabelList.forEach((label) => { this.$set(label, '__checked', checked) }) },
// 执行打印
onPrint(type) { const selected = this.printLabelList.filter((label) => label.__checked) if (selected.length === 0) { this.$message.warning('请至少选择一条记录') return }
// 将选择结果暂存,供后续打印页面使用
try { const payload = selected.map((label) => ({ labelCode: label.labelCode || label.unitId, issueQty: Number(label.qtyToIssue) || 0, batchNo: label.batchNo, partNo: label.partNo, locationId: label.locationId, warehouseId: label.warehouseId, wdrNo: label.wdrNo || '*', })) sessionStorage.setItem( 'customerIssuePrintSelected', JSON.stringify(payload) ) sessionStorage.setItem('customerIssuePrintType', type) this.$message.success('已准备打印:' + type) this.closePrintDialog()
// 根据实际打印流程跳转或调用打印
// this.$router.push({ name: 'somePrintPage' })
} catch (e) { this.$message.error('打印准备失败') } },
// 打开数量输入弹框
openQuantityDialog(type) { const selected = this.printLabelList.filter((label) => label.__checked) if (selected.length === 0) { this.$message.warning('请至少选择一条记录') return }
this.currentPrintType = type if (type === 'QC') { this.showQCDialog = true } else if (type === 'BOX') { this.showBOXDialog = true } },
// 关闭数量输入弹框
closeQuantityDialog() { this.showQCDialog = false this.showBOXDialog = false this.currentPrintType = '' },
// 确认QC打印
confirmQCPrint() { if (!this.qcQuantity || this.qcQuantity <= 0) { this.$message.warning('请输入有效的QC打印数量') return }
this.executePrint('QC', this.qcQuantity) },
// 确认BOX打印
confirmBOXPrint() { if (!this.boxQuantity || this.boxQuantity <= 0) { this.$message.warning('请输入有效的BOX打印数量') return }
this.executePrint('BOX', this.boxQuantity) },
// 执行打印(带数量)
executePrint(type, quantity) { const selected = this.printLabelList.filter((label) => label.__checked)
try { const payload = selected.map((label) => ({ labelCode: label.labelCode || label.unitId, issueQty: Number(label.qtyToIssue) || 0, batchNo: label.batchNo, partNo: label.partNo, locationId: label.locationId, warehouseId: label.warehouseId, wdrNo: label.wdrNo || '*', })) sessionStorage.setItem( 'customerIssuePrintSelected', JSON.stringify(payload) ) sessionStorage.setItem('customerIssuePrintType', type) sessionStorage.setItem( 'customerIssuePrintQuantity', quantity.toString() ) this.$message.success(`已准备打印:${type},数量:${quantity}`) this.closeQuantityDialog() this.closePrintDialog()
// 根据实际打印流程跳转或调用打印
// this.$router.push({ name: 'somePrintPage' })
} catch (e) { this.$message.error('打印准备失败') } }, },
mounted() { // 获取路由参数
console.log('路由参数:', this.$route.query)
this.orderNo = this.$route.query.shipmentId this.state = this.$route.query.state this.orderType = this.$route.query.orderType
if (!this.orderNo) { this.$message.error('参数错误') this.$router.back() return }
// 聚焦扫描框
this.$nextTick(() => { if (this.$refs.scanInput) { this.$refs.scanInput.focus() } })
// 加载订单详情
this.loadOrderDetails() },}</script>
<style scoped>/* 复用生产领料页面的样式,只修改必要的部分 */.pda-container { width: 100vw; height: 100vh; display: flex; flex-direction: column; background: #f5f5f5; overflow: hidden;}
/* 头部栏 */.header-bar { display: flex; justify-content: space-between; align-items: center; padding: 8px 16px; background: #17b3a3; color: white; height: 40px; min-height: 40px;}
.header-left { display: flex; align-items: center; cursor: pointer; font-size: 16px; font-weight: 500;}
.header-left i { margin-right: 8px; font-size: 18px;}
.header-right { cursor: pointer; font-size: 16px; font-weight: 500;}
/* 搜索容器 */.search-container { padding: 12px 16px; background: white; display: flex; align-items: center; gap: 12px;}
.search-container .el-input { width: 240px; margin-right: 12px;}
/* 紧凑型输入框样式 */.compact-input ::v-deep .el-input__inner { height: 36px; padding: 0 12px 0 35px; font-size: 14px;}
.compact-input ::v-deep .el-input__prefix { left: 10px;}
.compact-input ::v-deep .el-input__suffix { right: 30px;}
/* 模式切换开关 */.mode-switch { position: relative; display: inline-block;}
.custom-switch { transform: scale(1.3);}
/* 中间文字 */.switch-text { position: absolute; left: 25%; transform: translateX(-50%); top: 50%; transform: translateY(-50%) translateX(-50%); font-size: 12px; font-weight: 500; color: #606266; white-space: nowrap; pointer-events: none; z-index: 1; top: 53%; transform: translate(-50%, -50%); font-size: 12px; font-weight: bold; color: white; pointer-events: none; z-index: 2;}
.switch-text2 { position: absolute; left: 75%; transform: translateX(-50%); top: 50%; transform: translateY(-50%) translateX(-50%); font-size: 12px; font-weight: 500; color: #606266; white-space: nowrap; pointer-events: none; z-index: 1; top: 53%; transform: translate(-50%, -50%); font-size: 12px; font-weight: bold; color: white; pointer-events: none; z-index: 2;}
/* 调整 switch 尺寸以便容纳文字 */.custom-switch ::v-deep .el-switch__core { width: 60px; height: 28px;}
/* 物料信息卡片 */.material-info-card { background: white; margin: 4px 16px; padding: 6px 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); border: 1px solid #f0f0f0; border-left: 4px solid #17b3a3;}
.card-title { margin-bottom: 16px;}
.title-label { font-size: 11px; color: #999; display: block; margin-bottom: 6px; font-weight: normal;}
.title-value { font-size: 18px; font-weight: bold; color: #333; line-height: 1.2; margin-left: 20px;}
.card-details { display: flex; justify-content: space-between; align-items: flex-start; gap: 4px;}
.detail-item { flex: 1; text-align: center; min-width: 60px; max-width: 60px;}
.detail-label { font-size: 11px; color: #999; margin-bottom: 6px; font-weight: normal; line-height: 1.2; margin-left: -12px;}
.detail-value { font-size: 13px; color: #333; font-weight: 500; line-height: 1.2; margin-left: -12px;}
.detail-value .qualified { color: #17b3a3; font-weight: 500;}
.detail-value .total { color: #333; font-weight: 500;}
.detail-value .total::before { content: '/'; color: #333;}
/* 区域标题 */.section-title { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: white; margin: 0 10px; margin-top: 4px; border-radius: 6px 6px 0 0; border-bottom: 2px solid #17b3a3;}
/* 统一滚动容器 */.content-scroll-container { flex: 1; overflow-y: auto; overflow-x: hidden; background: #f5f5f5; min-height: 0;}
/* 对齐直接领料明细的工单卡片样式 *//* 工单列表容器背景与间距 */.work-order-list { padding: 8px 10px 4px;}
/* 工单卡片具有白色背景、圆角、阴影与边框一致性 */.work-order-card { background: white; border-radius: 6px; padding: 8px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); border: 1px solid #e0e0e0;}
/* 物料描述行(单独一行的小字说明) */.part-desc-row { margin-bottom: 12px; padding: 0 4px;}
.desc-text { font-size: 12px; color: #666; line-height: 1.3; word-break: break-all;}
.title-left { display: flex; align-items: center;}
.title-left i { color: #17b3a3; font-size: 16px; margin-right: 8px;}
.title-left span { color: #17b3a3; font-size: 14px; font-weight: 500;}
.title-right { display: flex; align-items: center;}
.material-list-link { color: #17b3a3; font-size: 14px; font-weight: 500; cursor: pointer; text-decoration: underline; transition: color 0.2s ease;}
.material-list-link:hover { color: #0d8f7f;}
/* 标签列表 - 卡片容器 */.label-card-container { padding: 0 8px 8px; background: #f5f5f5;}
/* 标签卡片 - 更紧凑样式 */.label-card { background: white; border-radius: 4px; margin-bottom: 6px; padding: 6px 8px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.2s ease; position: relative;}
.label-card:hover { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12); border-color: #17b3a3;}
.label-card:active { transform: scale(0.98);}
/* 编辑图标 - 右上角 */.card-edit-icon { position: absolute; top: 6px; right: 6px; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; background: #f0f0f0; border-radius: 3px; cursor: pointer; transition: all 0.2s ease; z-index: 10;}
.card-edit-icon:hover { background: #17b3a3; color: white;}
.card-edit-icon i { font-size: 12px; color: #17b3a3;}
.card-edit-icon:hover i { color: white;}
/* 卡片内容 */.card-content { padding-right: 28px;}
.card-row { display: flex; align-items: center; font-size: 11px; line-height: 1.5; margin-bottom: 3px;}
.card-row:last-child { margin-bottom: 0;}
.card-label { color: #666; min-width: 65px; flex-shrink: 0; font-size: 11px;}
.card-value { color: #333; flex: 1; word-break: break-all; font-size: 11px;}
.card-value.highlight { color: #17b3a3; font-weight: bold; font-size: 12px;}
/* 标签列表(保留原有样式,以防其他地方使用) */.label-list { background: white; margin: 0 10px 12px; border-radius: 0 0 8px 8px; overflow: hidden; max-height: 300px; overflow-y: auto;}
.list-header { display: flex; background: #f8f9fa; padding: 12px 8px; border-bottom: 1px solid #e0e0e0; font-size: 12px; color: #666; font-weight: 500; position: sticky; top: 0; z-index: 1;}
.list-item { display: flex; padding: 12px 8px; border-bottom: 1px solid #f0f0f0; font-size: 12px; color: #333; align-items: flex-start; min-height: 40px;}
.list-item:last-child { border-bottom: none;}
.col-no { width: 20px; text-align: center;}
.col-label { flex: 1.5; text-align: center; word-break: break-all; white-space: normal; line-height: 1.2;}
.col-part { flex: 1.5; text-align: center;}
.col-batch { flex: 1.5; text-align: center;}
.col-qty { width: 80px; text-align: center; cursor: pointer; position: relative; display: flex; align-items: center; justify-content: center; gap: 4px;}
.quantity-display { font-size: 12px; color: #333;}
.edit-icon { font-size: 12px; color: #17b3a3; opacity: 0.7; transition: opacity 0.2s ease;}
.col-qty:hover .edit-icon { opacity: 1;}
.col-qty:hover { background-color: #f0fffe; border-radius: 4px;}
.empty-labels { padding: 40px 20px; text-align: center; color: #999;}
.empty-labels p { margin: 0; font-size: 14px;}
/* 底部操作按钮 */.bottom-actions { display: flex; padding: 16px; gap: 20px; background: white;}
.action-btn { flex: 1; padding: 12px; border-radius: 20px; font-size: 14px; cursor: pointer; transition: all 0.2s ease;}
.action-btn.primary { border: 1px solid #17b3a3; background: #17b3a3; color: white;}
.action-btn.primary:hover { background: #13998c; border-color: #13998c;}
.action-btn.secondary { border: 1px solid #17b3a3; background: white; color: #17b3a3;}
.action-btn.secondary:hover { background: #17b3a3; color: white;}
.action-btn:active { transform: scale(0.98);}
/* 编辑弹框样式 */.edit-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 20px;}
.edit-modal { background: white; border-radius: 12px; width: 100%; max-width: 400px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column;}
.edit-modal .modal-header { background: #17b3a3; color: white; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center;}
.edit-modal .modal-title { font-size: 16px; font-weight: 500; margin: 0;}
.edit-modal .close-btn { font-size: 16px; cursor: pointer; color: white; transition: color 0.2s ease; padding: 4px; display: flex; align-items: center; justify-content: center;}
.edit-modal .close-btn:hover { color: #e0e0e0;}
.edit-modal .modal-body { padding: 20px; max-height: 60vh; overflow-y: auto;}
.edit-modal .modal-body.scrollable { max-height: 60vh; overflow-y: auto; padding-right: 10px;}
/* 滚动条样式 */.edit-modal .modal-body.scrollable::-webkit-scrollbar { width: 6px;}
.edit-modal .modal-body.scrollable::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 3px;}
.edit-modal .modal-body.scrollable::-webkit-scrollbar-thumb { background: #17b3a3; border-radius: 3px;}
.edit-modal .modal-body.scrollable::-webkit-scrollbar-thumb:hover { background: #13998c;}
.edit-modal .form-group { margin-bottom: 16px;}
.edit-modal .form-label { display: block; font-size: 14px; color: #333; margin-bottom: 6px; font-weight: 500;}
.edit-modal .required { color: #ff4949;}
.edit-modal .form-input { width: 100%;}
.edit-modal .form-input ::v-deep .el-input__inner { height: 40px; border: 2px solid #dcdfe6; border-radius: 6px; font-size: 14px; padding: 0 12px;}
.edit-modal .form-input ::v-deep .el-input__inner:focus { border-color: #17b3a3; outline: none;}
.edit-modal .form-input ::v-deep .el-input__inner:disabled { background: #f5f7fa; color: #c0c4cc; border-color: #e4e7ed;}
.edit-modal .modal-footer { padding: 16px 20px; display: flex; gap: 12px; justify-content: flex-end; border-top: 1px solid #f0f0f0;}
.edit-modal .btn-cancel { padding: 10px 20px; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s; border: 1px solid #dcdfe6; background: white; color: #606266;}
.edit-modal .btn-cancel:hover { background: #f5f7fa; border-color: #c0c4cc;}
.edit-modal .btn-confirm { padding: 10px 20px; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s; border: 1px solid #17b3a3; background: #17b3a3; color: white;}
.edit-modal .btn-confirm:hover { background: #13998c; border-color: #13998c;}
/* 打印弹框样式 */.print-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 10px;}
.print-modal { background: white; border-radius: 12px; width: 100%; max-width: 500px; max-height: 80vh; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column;}
.print-modal .modal-header { background: #17b3a3; color: white; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center;}
.print-modal .modal-title { font-size: 16px; font-weight: 500; margin: 0;}
.print-modal .close-btn { font-size: 16px; cursor: pointer; color: white; transition: color 0.2s ease; padding: 4px; display: flex; align-items: center; justify-content: center;}
.print-modal .close-btn:hover { color: #e0e0e0;}
.print-modal .modal-body { padding: 6px; flex: 1; overflow: hidden; display: flex; flex-direction: column;}
.print-section-title { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; margin-bottom: 12px; border-bottom: 1px solid #e0e0e0;}
.print-section-title .title-left { display: flex; align-items: center;}
.print-section-title .title-left i { color: #17b3a3; font-size: 16px; margin-right: 8px;}
.print-section-title .title-left span { color: #17b3a3; font-size: 14px; font-weight: 500;}
.print-section-title .title-right { display: flex; align-items: center;}
.print-label-list { flex: 1; /* 允许列表区域在弹框内上下滚动,避免只显示固定条数 */ max-height: 50vh; overflow-y: auto; overflow-x: hidden; position: relative; /* 为粘性表头提供更高的层叠上下文控制 */ border: 1px solid #e0e0e0; border-radius: 6px;}
.print-label-list .list-header { display: flex; background: #f8f9fa; padding: 10px 8px; border-bottom: 1px solid #e0e0e0; font-size: 12px; color: #666; font-weight: 500; position: sticky; top: 0; z-index: 5; /* 保证表头覆盖列表项与复选框 */}
.print-label-list .list-item { display: flex; padding: 10px 8px; border-bottom: 1px solid #f0f0f0; font-size: 12px; color: #333; align-items: center; min-height: 36px;}
.print-label-list .list-item:last-child { border-bottom: none;}
.print-label-list .col-no { width: 30px; text-align: center;}
.print-label-list .col-label { flex: 1.5; text-align: center; word-break: break-all; white-space: normal; line-height: 1.2;}
.print-label-list .col-part { flex: 1.2; text-align: center;}
.print-label-list .col-qty { width: 70px; text-align: center;}
.print-label-list .col-check { width: 50px; text-align: center;}
/* 放大复选框(仅作用于打印弹框列表区域) */.print-label-list .col-check ::v-deep .el-checkbox { transform: scale(1.2); transform-origin: center center;}
/* 调整放大后与行高的对齐 */.print-label-list .col-check { display: flex; align-items: center; justify-content: center;}
.print-modal .modal-footer { padding: 16px 20px; display: flex; gap: 12px; justify-content: flex-end; border-top: 1px solid #f0f0f0;}
.print-modal .btn-secondary { padding: 10px 20px; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s; border: 1px solid #17b3a3; background: white; color: #17b3a3;}
.print-modal .btn-secondary:hover { background: #17b3a3; color: white;}
/* 响应式设计 */@media (max-width: 360px) { .header-bar { padding: 8px 12px; }
.search-container { padding: 8px 12px; }
.material-info-card { margin: 4px 12px; padding: 6px 16px; }
.section-title { margin: 0 12px; margin-top: 4px; }
.label-list { margin: 0 12px 8px; }
.card-details { flex-wrap: wrap; gap: 6px; }
.detail-item { flex: 0 0 48%; margin-bottom: 6px; min-width: 50px; }
.list-header, .list-item { font-size: 11px; }
.col-label, .col-part, .col-batch { flex: 1.2; }}
/* 数量输入弹框样式 */.quantity-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 20px;}
.quantity-modal { background: white; border-radius: 12px; width: 100%; max-width: 400px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column;}
.quantity-modal .modal-header { background: #17b3a3; color: white; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center;}
.quantity-modal .modal-title { font-size: 16px; font-weight: 500; margin: 0;}
.quantity-modal .close-btn { font-size: 16px; cursor: pointer; color: white; transition: color 0.2s ease; padding: 4px; display: flex; align-items: center; justify-content: center;}
.quantity-modal .close-btn:hover { color: #e0e0e0;}
.quantity-modal .modal-body { padding: 20px;}
.quantity-modal .form-group { margin-bottom: 16px;}
.quantity-modal .form-label { display: block; font-size: 14px; color: #333; margin-bottom: 6px; font-weight: 500;}
.quantity-modal .required { color: #ff4949;}
.quantity-modal .form-input { width: 100%;}
.quantity-modal .form-input ::v-deep .el-input__inner { height: 40px; border: 2px solid #dcdfe6; border-radius: 6px; font-size: 14px; padding: 0 12px;}
.quantity-modal .form-input ::v-deep .el-input__inner:focus { border-color: #17b3a3; outline: none;}
.quantity-modal .modal-footer { padding: 16px 20px; display: flex; gap: 12px; justify-content: flex-end; border-top: 1px solid #f0f0f0;}
.quantity-modal .btn-cancel { padding: 10px 20px; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s; border: 1px solid #dcdfe6; background: white; color: #606266;}
.quantity-modal .btn-cancel:hover { background: #f5f7fa; border-color: #c0c4cc;}
.quantity-modal .btn-confirm { padding: 10px 20px; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s; border: 1px solid #17b3a3; background: #17b3a3; color: white;}
.quantity-modal .btn-confirm:hover { background: #13998c; border-color: #13998c;}
/* 发料对比弹框 */.summary-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 6px;}
.summary-modal { width: 100%; max-width: 560px; background: white; border-radius: 14px; box-shadow: 0 14px 32px rgba(0, 0, 0, 0.25); overflow: hidden; display: flex; flex-direction: column;}
.summary-modal .modal-header { background: #17b3a3; color: white; padding: 12px 20px; display: flex; justify-content: space-between; align-items: center;}
.summary-body { max-height: 65vh; overflow-y: auto; padding-bottom: 4px;}
.summary-loading { padding: 40px 20px; text-align: center; color: #666;}
.summary-header-stats { padding: 14px 20px 4px; display: flex; gap: 12px;}
.stat-card { flex: 1; background: #f7f8fa; border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; position: relative; overflow: hidden;}
.stat-card i { font-size: 18px; color: inherit;}
.stat-card.exist { border-left: 4px solid #2ec4b6; color: #0f9d92;}
.stat-card.missing { border-left: 4px solid #ffb703; color: #f18f01;}
.stat-card.extra { border-left: 4px solid #3a86ff; color: #1d6fe3;}
.stat-count { font-size: 22px; font-weight: 600; color: #252525;}
.stat-label { font-size: 12px; color: #777; letter-spacing: 0.5px;}
.summary-section { padding: 12px 2px 18px; border-bottom: 1px solid #f3f4f6;}
.summary-section:last-child { border-bottom: none;}
.section-title-row { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 8px;}
.section-title-text { font-size: 14px; font-weight: 600; color: #333;}
.section-sub { font-size: 12px; color: #999; margin-left: 6px;}
.section-count { font-size: 12px; color: #a0a0a0;}
.summary-table { border: 1px solid #eef0f4; border-radius: 10px; overflow: hidden;}
.summary-table-row { display: grid; gap: 8px; padding: 8px 12px; font-size: 12px; align-items: center;}
.summary-table--4 .summary-table-row { grid-template-columns: 2fr 1fr 1fr 1fr;}
.summary-table--3 .summary-table-row { grid-template-columns: 2.2fr 1fr 1fr;}
.summary-table-head { background: #f8f9fb; font-weight: 600; color: #666; text-transform: uppercase;}
.summary-table-row .part { font-weight: 600; color: #333;}
.summary-table-row:not(.summary-table-head) { border-top: 1px solid #f0f2f5;}
.summary-table-row:first-of-type { border-top: none;}
.badge { display: inline-flex; align-items: center; justify-content: center; min-width: 60px; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 600;}
.badge.success { background: rgba(46, 196, 182, 0.15); color: #0f9d92;}
.badge.danger { background: rgba(255, 99, 71, 0.15); color: #ff4d4f;}
.badge.warn { background: rgba(255, 183, 3, 0.15); color: #f18f01;}
.badge.info { background: rgba(58, 134, 255, 0.15); color: #1d6fe3;}
.summary-empty { padding: 12px 0 4px; font-size: 12px; color: #999;}
.summary-modal .modal-footer { padding: 12px 20px; display: flex; justify-content: flex-end; gap: 12px; border-top: 1px solid #f0f0f0;}</style>
|