You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2399 lines
57 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
7 days ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
1 month ago
7 days ago
1 month ago
1 month ago
7 days ago
1 month ago
1 month ago
7 days ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
7 days ago
1 month ago
1 month ago
2 months ago
7 days ago
1 month ago
7 days ago
1 month ago
1 month ago
7 days ago
7 days ago
1 month ago
1 month ago
1 month ago
7 days ago
1 month ago
1 month ago
1 month ago
1 month ago
7 days ago
1 month ago
1 month ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
1 month ago
3 months ago
3 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
3 months ago
3 months ago
1 month ago
  1. <template>
  2. <div class="pda-container">
  3. <!-- 头部栏 -->
  4. <div class="header-bar">
  5. <div class="header-left" @click="$router.back()">
  6. <i class="el-icon-arrow-left"></i>
  7. <span>客户发料</span>
  8. </div>
  9. <div class="header-right" @click="$router.push({ path: '/' })">首页</div>
  10. </div>
  11. <!-- 搜索框 -->
  12. <div class="search-container">
  13. <el-input clearable class="compact-input" v-model="scanCode" placeholder="请扫描物料标签" prefix-icon="el-icon-search"
  14. @keyup.enter.native="handleScan" ref="scanInput" />
  15. <div class="mode-switch">
  16. <el-switch class="custom-switch" v-model="isRemoveMode" active-color="#ff4949" inactive-color="#13ce66">
  17. </el-switch>
  18. <span v-if="isRemoveMode" class="switch-text">{{ "移除" }}</span>
  19. <span v-else class="switch-text2">{{ "添加" }}</span>
  20. </div>
  21. </div>
  22. <!-- 统一滚动容器 -->
  23. <div class="content-scroll-container">
  24. <!-- 订单信息卡片 -->
  25. <div class="work-order-list" v-if="orderNo">
  26. <div class="work-order-card">
  27. <div class="card-title">
  28. <span class="title-label">申请单号{{ orderInfo.orderNo }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
  29. <span class="title-label">本次: {{ totalScannedQty }}</span>
  30. </div>
  31. </div>
  32. </div>
  33. <!-- 发料信息确认标题 -->
  34. <div class="section-title">
  35. <div class="title-left">
  36. <i class="el-icon-circle-check"></i>
  37. <span>发料信息确认</span>
  38. </div>
  39. </div>
  40. <!-- 发料标签列表 - 卡片形式 -->
  41. <div class="label-card-container">
  42. <div v-for="(label, index) in labelList" :key="label.id" class="label-card">
  43. <div class="card-edit-icon" @click.stop="openEditDialog(label, index)">
  44. <i class="el-icon-edit"></i>
  45. </div>
  46. <div class="card-content" @click="openEditDialog(label, index)">
  47. <div class="card-row">
  48. <span class="card-label">标签号</span>
  49. <span class="card-value">{{ label.unitId }}</span>
  50. </div>
  51. <div class="card-row">
  52. <span class="card-label">物料号</span>
  53. <span class="card-value">{{ label.partNo || '-' }}</span>
  54. </div>
  55. <div class="card-row">
  56. <span class="card-label">批次号</span>
  57. <span class="card-value">{{ label.batchNo || '-' }}</span>
  58. </div>
  59. <div class="card-row">
  60. <span class="card-label">库位</span>
  61. <span class="card-value">{{ label.locationId || '-' }}</span>
  62. </div>
  63. <div class="card-row">
  64. <span class="card-label">发料数量</span>
  65. <span class="card-value highlight">{{ label.qtyToIssue || 0 }}</span>
  66. </div>
  67. </div>
  68. </div>
  69. <!-- 空状态 -->
  70. <div v-if="labelList.length === 0" class="empty-labels">
  71. <p>暂无扫描标签</p>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- 底部操作按钮 -->
  76. <div class="bottom-actions">
  77. <el-button class="action-btn primary" style="margin-left: 10px" @click="confirmIssue"
  78. :loading="loading || summaryLoading">
  79. 确定发料
  80. </el-button>
  81. <!-- <button class="action-btn secondary" style="margin-left: 10px" @click="openPrintDialog">
  82. 打印
  83. </button> -->
  84. <button class="action-btn secondary" style="margin-left: 10px" @click="cancelIssue">
  85. 取消
  86. </button>
  87. </div>
  88. <!-- 发料前比对弹框 -->
  89. <div v-if="showSummaryDialog" class="summary-overlay">
  90. <div class="summary-modal">
  91. <div class="modal-header">
  92. <span class="modal-title">发料物料比对</span>
  93. <i class="el-icon-close close-btn" @click="closeSummaryDialog"></i>
  94. </div>
  95. <div class="modal-body summary-body">
  96. <div v-if="summaryLoading" class="summary-loading">
  97. 正在获取申请单物料请稍候...
  98. </div>
  99. <template v-else>
  100. <div class="summary-section">
  101. <div class="section-title-row">
  102. <div>
  103. <span class="section-title-text">存在物料</span>
  104. <span class="section-sub">申请单与扫描均存在</span>
  105. </div>
  106. <span class="section-count"> {{ comparisonData.existing.length }} </span>
  107. </div>
  108. <div v-if="comparisonData.existing.length === 0" class="summary-empty">
  109. 暂无相关物料
  110. </div>
  111. <div v-else class="summary-table summary-table--4">
  112. <div class="summary-table-row summary-table-head">
  113. <div>物料号</div>
  114. <div>需求数量</div>
  115. <div>扫描数量</div>
  116. <div>差异</div>
  117. </div>
  118. <div v-for="item in comparisonData.existing" :key="'exist-' + item.partNo" class="summary-table-row">
  119. <div class="part">{{ item.partNo }}</div>
  120. <div>{{ formatQty(item.requiredQty) }}</div>
  121. <div>{{ formatQty(item.scannedQty) }}</div>
  122. <div class="badge" :class="{
  123. success: item.difference >= 0,
  124. danger: item.difference < 0,
  125. }">
  126. {{ formatQty(item.difference) }}
  127. </div>
  128. </div>
  129. </div>
  130. </div>
  131. <div class="summary-section" v-if="comparisonData.missing.length > 0">
  132. <div class="section-title-row">
  133. <div>
  134. <span class="section-title-text">未扫物料</span>
  135. <span class="section-sub">申请单存在但未扫描</span>
  136. </div>
  137. <span class="section-count"> {{ comparisonData.missing.length }} </span>
  138. </div>
  139. <div class="summary-table summary-table--3">
  140. <div class="summary-table-row summary-table-head">
  141. <div>物料号</div>
  142. <div>需求数量</div>
  143. <div>状态</div>
  144. </div>
  145. <div v-for="item in comparisonData.missing" :key="'missing-' + item.partNo" class="summary-table-row">
  146. <div class="part">{{ item.partNo }}</div>
  147. <div>{{ formatQty(item.requiredQty) }}</div>
  148. <div><span class="badge warn">未扫描</span></div>
  149. </div>
  150. </div>
  151. </div>
  152. <div class="summary-section" v-if="comparisonData.extra.length > 0">
  153. <div class="section-title-row">
  154. <div>
  155. <span class="section-title-text">多余物料</span>
  156. <span class="section-sub">扫描存在但申请单无记录</span>
  157. </div>
  158. <span class="section-count"> {{ comparisonData.extra.length }} </span>
  159. </div>
  160. <div class="summary-table summary-table--3">
  161. <div class="summary-table-row summary-table-head">
  162. <div>物料号</div>
  163. <div>扫描数量</div>
  164. <div>状态</div>
  165. </div>
  166. <div v-for="item in comparisonData.extra" :key="'extra-' + item.partNo" class="summary-table-row">
  167. <div class="part">{{ item.partNo }}</div>
  168. <div>{{ formatQty(item.scannedQty) }}</div>
  169. <div><span class="badge info">申请单未包含</span></div>
  170. </div>
  171. </div>
  172. </div>
  173. </template>
  174. </div>
  175. <div class="modal-footer">
  176. <el-button class="btn-cancel" @click="closeSummaryDialog" :disabled="loading">
  177. 返回修改
  178. </el-button>
  179. <el-button class="btn-confirm" @click="proceedIssueConfirm" :disabled="loading">
  180. {{ loading ? '发料中...' : '确定预留发料' }}
  181. </el-button>
  182. </div>
  183. </div>
  184. </div>
  185. <!-- 编辑标签弹框 -->
  186. <div v-if="showEditDialog" class="edit-overlay">
  187. <div class="edit-modal">
  188. <div class="modal-header">
  189. <span class="modal-title">编辑标签信息</span>
  190. <i class="el-icon-close close-btn" @click="closeEditDialog"></i>
  191. </div>
  192. <div class="modal-body scrollable">
  193. <div class="form-group">
  194. <label class="form-label">物料标签</label>
  195. <el-input v-model="editForm.labelCode" disabled class="form-input" />
  196. </div>
  197. <div class="form-group">
  198. <label class="form-label">物料号</label>
  199. <el-input v-model="editForm.partNo" disabled class="form-input" />
  200. </div>
  201. <div class="form-group">
  202. <label class="form-label">批次号</label>
  203. <el-input v-model="editForm.batchNo" placeholder="请输入批次号" disabled class="form-input" />
  204. </div>
  205. <div class="form-group">
  206. <label class="form-label">库位 <span class="required">*</span></label>
  207. <el-input v-model="editForm.locationId" placeholder="请输入库位" disabled class="form-input" />
  208. </div>
  209. <div class="form-group">
  210. <label class="form-label">发料数量 <span class="required">*</span></label>
  211. <el-input v-model="editForm.quantity" type="number" :min="0" placeholder="请输入发料数量" class="form-input" />
  212. </div>
  213. <div class="form-group">
  214. <label class="form-label">剩余高度</label>
  215. <el-input v-model="editForm.height" type="number" :min="0" placeholder="请输入高度" class="form-input" />
  216. </div>
  217. </div>
  218. <div class="modal-footer">
  219. <button class="btn-cancel" @click="closeEditDialog">取消</button>
  220. <button class="btn-confirm" @click="confirmEdit">确定</button>
  221. </div>
  222. </div>
  223. </div>
  224. <!-- 打印选择弹框 -->
  225. <div v-if="showPrintDialog" class="print-overlay">
  226. <div class="print-modal">
  227. <div class="modal-header">
  228. <span class="modal-title">选择打印标签</span>
  229. <i class="el-icon-close close-btn" @click="closePrintDialog"></i>
  230. </div>
  231. <div class="modal-body">
  232. <div class="print-section-title">
  233. <div class="title-left">
  234. <i class="el-icon-tickets"></i>
  235. <span>请选择需要打印的标签</span>
  236. </div>
  237. <div class="title-right">
  238. <el-checkbox v-model="printAllChecked" @change="togglePrintAll">全选</el-checkbox>
  239. </div>
  240. </div>
  241. <div class="print-label-list">
  242. <div class="list-header">
  243. <div class="col-no">NO.</div>
  244. <div class="col-label">物料标签</div>
  245. <div class="col-part">库位</div>
  246. <div class="col-qty">发料数量</div>
  247. <div class="col-check">选择</div>
  248. </div>
  249. <div v-for="(label, index) in printLabelList" :key="label.id" class="list-item">
  250. <div class="col-no">{{ index + 1 }}</div>
  251. <div class="col-label">{{ label.labelCode }}</div>
  252. <div class="col-part">{{ label.locationId }}</div>
  253. <div class="col-qty">
  254. {{
  255. Number(
  256. label.qtyToIssue
  257. ) || 0
  258. }}
  259. </div>
  260. <div class="col-check">
  261. <el-checkbox v-model="label.__checked"></el-checkbox>
  262. </div>
  263. </div>
  264. <div v-if="printLabelList.length === 0" class="empty-labels">
  265. <p>暂无扫描标签</p>
  266. </div>
  267. </div>
  268. </div>
  269. <div class="modal-footer">
  270. <button class="btn-secondary" @click="closePrintDialog">取消</button>
  271. <button class="btn-secondary" @click="openQuantityDialog('QC')">打印QC</button>
  272. <button class="btn-secondary" @click="openQuantityDialog('BOX')">打印BOX</button>
  273. </div>
  274. </div>
  275. </div>
  276. <!-- QC数量输入弹框 -->
  277. <div v-if="showQCDialog" class="quantity-overlay">
  278. <div class="quantity-modal">
  279. <div class="modal-header">
  280. <span class="modal-title">输入QC打印数量</span>
  281. <i class="el-icon-close close-btn" @click="closeQuantityDialog"></i>
  282. </div>
  283. <div class="modal-body">
  284. <div class="form-group">
  285. <label class="form-label">QC打印数量 <span class="required">*</span></label>
  286. <el-input v-model="qcQuantity" type="number" :min="1" placeholder="请输入QC打印数量" class="form-input" />
  287. </div>
  288. </div>
  289. <div class="modal-footer">
  290. <button class="btn-cancel" @click="closeQuantityDialog">取消</button>
  291. <button class="btn-confirm" @click="confirmQCPrint">确定打印</button>
  292. </div>
  293. </div>
  294. </div>
  295. <!-- BOX数量输入弹框 -->
  296. <div v-if="showBOXDialog" class="quantity-overlay">
  297. <div class="quantity-modal">
  298. <div class="modal-header">
  299. <span class="modal-title">输入BOX打印数量</span>
  300. <i class="el-icon-close close-btn" @click="closeQuantityDialog"></i>
  301. </div>
  302. <div class="modal-body">
  303. <div class="form-group">
  304. <label class="form-label">BOX打印数量 <span class="required">*</span></label>
  305. <el-input v-model="boxQuantity" type="number" :min="1" placeholder="请输入BOX打印数量" class="form-input" />
  306. </div>
  307. </div>
  308. <div class="modal-footer">
  309. <button class="btn-cancel" @click="closeQuantityDialog">取消</button>
  310. <button class="btn-confirm" @click="confirmBOXPrint">确定打印</button>
  311. </div>
  312. </div>
  313. </div>
  314. </div>
  315. </template>
  316. <script>
  317. import {
  318. getInventoryPart,
  319. scanCustomerIssueMaterialLabel,
  320. customerIssueConfirm,
  321. updateShipmentHuDetail,
  322. getShipmentHuDetail,
  323. removeShipmentHuDetail,
  324. getCustomerIssueNotifyHeaderOrderMaterialList,
  325. } from '@/api/customerIssue/customer-issue'
  326. import moment from 'moment'
  327. import { printLabelCommon } from '@/api/production/production-inbound.js'
  328. export default {
  329. data() {
  330. return {
  331. scanCode: '',
  332. orderInfo: {},
  333. labelList: [],
  334. orderNo: '',
  335. orderType: '',
  336. isRemoveMode: false, // 默认为添加模式
  337. partNo: '', // 物料编码
  338. transactionId: '', // 关联单号
  339. accountingId: '', // 会计科目
  340. quantity: '', // 本次发料数量
  341. qtyIssued: 0, // 已发料数量
  342. batchNo: '', // 批次号
  343. // 编辑弹框相关
  344. showEditDialog: false,
  345. editForm: {
  346. detailId: '',
  347. labelCode: '',
  348. partNo: '',
  349. batchNo: '',
  350. locationId: '',
  351. warehouseId: '',
  352. wdrNo: '',
  353. engChgLevel: '',
  354. quantity: 0,
  355. height: null,
  356. },
  357. editIndex: -1, // 当前编辑的标签索引
  358. unissureQty: 0, // 需求数量
  359. itemNo: '', // 物料ID
  360. loading: false,
  361. // 打印弹框相关
  362. showPrintDialog: false,
  363. printLabelList: [],
  364. printAllChecked: true,
  365. assignedQty: 0, // 已发数量
  366. // 数量输入弹框相关
  367. showQCDialog: false,
  368. showBOXDialog: false,
  369. qcQuantity: 1,
  370. boxQuantity: 1,
  371. currentPrintType: '', // 当前选择的打印类型
  372. // 发料前对比弹框
  373. showSummaryDialog: false,
  374. summaryLoading: false,
  375. comparisonData: {
  376. existing: [],
  377. missing: [],
  378. extra: [],
  379. },
  380. pendingIssueParams: null,
  381. shipmentLine: [],
  382. }
  383. },
  384. computed: {
  385. totalScannedQty() {
  386. return this.labelList.reduce(
  387. (sum, l) => sum + (Number(l.qtyToIssue) || 0),
  388. 0
  389. )
  390. },
  391. },
  392. methods: {
  393. formatDate(date) {
  394. return date ? moment(date).format('YYYY-MM-DD') : ''
  395. },
  396. formatQty(value) {
  397. const num = Number(value)
  398. if (!Number.isFinite(num)) {
  399. return 0
  400. }
  401. return Number.isInteger(num) ? num : Number(num.toFixed(3))
  402. },
  403. // 精度安全的加法,避免浮点数精度丢失
  404. safeAdd(a, b) {
  405. const num1 = Number(a) || 0
  406. const num2 = Number(b) || 0
  407. // 将数字转换为整数(保留4位小数精度),相加后再转换回小数
  408. const factor = 10000
  409. return Math.round((num1 * factor + num2 * factor)) / factor
  410. },
  411. pickFieldValue(item, candidates = [], defaultValue = null) {
  412. if (!item || !candidates || candidates.length === 0) {
  413. return defaultValue
  414. }
  415. for (const field of candidates) {
  416. if (
  417. Object.prototype.hasOwnProperty.call(item, field) &&
  418. item[field] !== undefined &&
  419. item[field] !== null &&
  420. item[field] !== ''
  421. ) {
  422. return item[field]
  423. }
  424. }
  425. return defaultValue
  426. },
  427. aggregateMaterials(list = [], keyFields = [], qtyFields = []) {
  428. const map = {}
  429. list.forEach((item) => {
  430. const partKey = this.pickFieldValue(item, keyFields)
  431. const normalizedKey =
  432. typeof partKey === 'string' ? partKey.trim() : partKey
  433. if (!normalizedKey) {
  434. return
  435. }
  436. const qtyValue = Number(this.pickFieldValue(item, qtyFields, 0)) || 0
  437. if (!map[normalizedKey]) {
  438. map[normalizedKey] = {
  439. partNo: normalizedKey,
  440. qty: 0,
  441. }
  442. }
  443. map[normalizedKey].qty = this.safeAdd(map[normalizedKey].qty, qtyValue)
  444. })
  445. return map
  446. },
  447. // 处理扫描
  448. handleScan() {
  449. if (!this.scanCode.trim()) {
  450. return
  451. }
  452. if (this.isRemoveMode) {
  453. this.removeLabelByCode(this.scanCode.trim())
  454. } else {
  455. this.validateAndAddLabel(this.scanCode.trim())
  456. }
  457. this.scanCode = ''
  458. },
  459. // 验证标签并添加到列表(保留客户发料功能)
  460. validateAndAddLabel(labelCode) {
  461. const params = {
  462. scannedLabel: labelCode,
  463. workOrderNo: this.orderNo,
  464. site: localStorage.getItem('site'),
  465. batchNo: this.batchNo,
  466. }
  467. // 检查是否已经扫描过
  468. const exists = this.labelList.find((item) => item.unitId === labelCode)
  469. if (exists) {
  470. this.$message.warning('该标签已扫描,请勿重复扫描')
  471. return
  472. }
  473. // 客户发料标签验证
  474. scanCustomerIssueMaterialLabel(params)
  475. .then(({ data }) => {
  476. if (data.code === 0 && data) {
  477. // 添加到列表,保存更多字段信息
  478. // 后端返回的是 ShipmentHuDetail 对象
  479. const labelInfo = data.labelInfo
  480. this.labelList.push({
  481. id: Date.now(),
  482. labelCode: labelCode,
  483. partNo: labelInfo.partNo || '',
  484. qtyToIssue: Number(labelInfo.qtyToIssue) || 0,
  485. batchNo: labelInfo.batchNo || '',
  486. locationId: labelInfo.locationId || '',
  487. warehouseId: labelInfo.warehouseId || '',
  488. wdrNo: labelInfo.wdr || '',
  489. engChgLevel: labelInfo.engChgLevel || '1',
  490. detailId: labelInfo.detailId || '',
  491. unitId: labelInfo.unitId || '',
  492. site: labelInfo.site || localStorage.getItem('site'),
  493. height: labelInfo.height || null,
  494. })
  495. this.$message.success('操作成功')
  496. } else {
  497. this.$message.error(data.msg || '该标签不符合发料要求,请检查')
  498. }
  499. })
  500. .catch(() => {
  501. this.$message.error('操作失败')
  502. })
  503. },
  504. // 通过条码移除标签
  505. removeLabelByCode(labelCode) {
  506. const exists = this.labelList.find((item) => item.unitId === labelCode)
  507. if (!exists) {
  508. this.$message.warning('未找到该标签')
  509. return
  510. }
  511. let params = {
  512. site: localStorage.getItem('site'),
  513. unitId: labelCode,
  514. detailId: exists.detailId,
  515. }
  516. removeShipmentHuDetail(params)
  517. .then(({ data }) => {
  518. if (data.code === 0) {
  519. const index = this.labelList.findIndex(
  520. (item) => item.unitId === labelCode
  521. )
  522. if (index !== -1) {
  523. this.labelList.splice(index, 1)
  524. this.$message.success('操作成功')
  525. } else {
  526. this.$message.warning('未找到该标签')
  527. }
  528. } else {
  529. this.$message.error(data.msg || '移除标签失败')
  530. }
  531. })
  532. .catch(() => {
  533. this.$message.error('移除标签失败')
  534. })
  535. },
  536. // 打开编辑弹框
  537. openEditDialog(label, index) {
  538. this.editForm = {
  539. detailId: label.detailId,
  540. labelCode: label.unitId,
  541. partNo: label.partNo,
  542. batchNo: label.batchNo || '',
  543. locationId: label.locationId || '',
  544. warehouseId: label.warehouseId || '',
  545. wdrNo: label.wdrNo || '',
  546. engChgLevel: label.engChgLevel || '1',
  547. quantity: label.qtyToIssue,
  548. height: label.height,
  549. }
  550. this.editIndex = index
  551. this.showEditDialog = true
  552. },
  553. // 关闭编辑弹框
  554. closeEditDialog() {
  555. this.showEditDialog = false
  556. this.editForm = {
  557. detailId: '',
  558. labelCode: '',
  559. partNo: '',
  560. batchNo: '',
  561. locationId: '',
  562. warehouseId: '',
  563. wdrNo: '',
  564. engChgLevel: '',
  565. quantity: 0,
  566. height: null,
  567. }
  568. this.editIndex = -1
  569. },
  570. // 确认编辑
  571. confirmEdit() {
  572. // 验证必填字段
  573. if (!this.editForm.locationId.trim()) {
  574. this.$message.warning('请输入库位')
  575. return
  576. }
  577. if (!this.editForm.quantity || this.editForm.quantity <= 0) {
  578. this.$message.warning('请输入有效的发料数量')
  579. return
  580. }
  581. // 调用后端接口更新
  582. const updateParams = {
  583. detailId: this.editForm.detailId,
  584. site:
  585. this.labelList[this.editIndex].site || localStorage.getItem('site'),
  586. unitId: this.labelList[this.editIndex].unitId,
  587. locationId: this.editForm.locationId,
  588. qtyToIssue: Number(this.editForm.quantity),
  589. batchNo: this.editForm.batchNo,
  590. warehouseId: this.editForm.warehouseId,
  591. engChgLevel: this.editForm.engChgLevel,
  592. wdr: this.editForm.wdrNo,
  593. height: this.editForm.height,
  594. }
  595. updateShipmentHuDetail(updateParams)
  596. .then(({ data }) => {
  597. if (data.code === 0) {
  598. // 更新本地列表
  599. if (this.editIndex >= 0 && this.editIndex < this.labelList.length) {
  600. this.labelList[this.editIndex].locationId =
  601. this.editForm.locationId
  602. this.labelList[this.editIndex].qtyToIssue = Number(
  603. this.editForm.quantity
  604. )
  605. this.labelList[this.editIndex].batchNo = this.editForm.batchNo
  606. this.labelList[this.editIndex].warehouseId =
  607. this.editForm.warehouseId
  608. this.labelList[this.editIndex].wdrNo = this.editForm.wdrNo
  609. this.labelList[this.editIndex].engChgLevel =
  610. this.editForm.engChgLevel
  611. this.labelList[this.editIndex].height = this.editForm.height
  612. }
  613. this.$message.success('修改成功')
  614. this.closeEditDialog()
  615. } else {
  616. this.$message.error(data.msg || '修改失败')
  617. }
  618. })
  619. .catch(() => {
  620. this.$message.error('修改失败')
  621. })
  622. },
  623. async confirmIssue() {
  624. if (this.labelList.length === 0) {
  625. this.$message.warning('请先扫描发料标签')
  626. return
  627. }
  628. if (this.totalScannedQty > this.orderInfo.quantity - this.qtyIssued) {
  629. this.$message.warning('扫描数量不能大于总发料数量和已发数量的差!')
  630. return
  631. }
  632. this.summaryLoading = true
  633. try {
  634. await this.prepareComparisonData()
  635. this.showSummaryDialog = true
  636. } catch (error) {
  637. this.$message.error(
  638. (error && error.message) || '获取申请单物料失败,请稍后重试'
  639. )
  640. this.pendingIssueParams = null
  641. } finally {
  642. this.summaryLoading = false
  643. }
  644. },
  645. buildIssueParams() {
  646. return {
  647. site: localStorage.getItem('site'),
  648. workOrderNo: this.orderNo,
  649. batchNo: this.batchNo,
  650. componentPartNo: this.partNo,
  651. transactionId: this.transactionId,
  652. accountingId: this.accountingId,
  653. itemNo: this.itemNo,
  654. ifsIssuedQty: this.qtyIssued,
  655. issueQty: this.quantity,
  656. selectedMaterials: this.labelList.map((label) => ({
  657. labelCode: label.labelCode || label.unitId,
  658. issueQty: Number(label.qtyToIssue) || 0,
  659. batchNo: label.batchNo,
  660. partNo: label.partNo,
  661. locationId: label.locationId,
  662. warehouseId: label.warehouseId,
  663. wdrNo: label.wdrNo || '*',
  664. engChgLevel: label.engChgLevel || '1',
  665. })),
  666. shipmentLine: this.shipmentLine || [],
  667. }
  668. },
  669. async prepareComparisonData() {
  670. this.shipmentLine = []
  671. const params = {
  672. site: localStorage.getItem('site'),
  673. workOrderNo: this.orderNo,
  674. }
  675. await getCustomerIssueNotifyHeaderOrderMaterialList(params).then(
  676. ({ data }) => {
  677. if (!data || data.code !== 0) {
  678. throw new Error((data && data.msg) || '获取申请单物料失败')
  679. }
  680. const materials = data.data || []
  681. this.$set(this,'shipmentLine',data.data)
  682. const labelMap = this.aggregateMaterials(
  683. this.labelList,
  684. ['partNo'],
  685. ['qtyToIssue', 'quantity', 'issueQty']
  686. )
  687. // 手动聚合订单物料,计算 INVENTORY_QTY - QTY_ASSIGNED 作为需求数量
  688. const orderMap = {}
  689. materials.forEach((item) => {
  690. const partNo = this.pickFieldValue(item, ['INVENTORY_PART_NO'])
  691. const normalizedKey =
  692. typeof partNo === 'string' ? partNo.trim() : partNo
  693. if (!normalizedKey) {
  694. return
  695. }
  696. const inventoryQty = Number(this.pickFieldValue(item, ['INVENTORY_QTY'], 0)) || 0
  697. const qtyAssigned = Number(this.pickFieldValue(item, ['QTY_ASSIGNED'], 0)) || 0
  698. const requiredQty = this.safeAdd(inventoryQty, -qtyAssigned)
  699. if (!orderMap[normalizedKey]) {
  700. orderMap[normalizedKey] = {
  701. partNo: normalizedKey,
  702. qty: 0,
  703. }
  704. }
  705. orderMap[normalizedKey].qty = this.safeAdd(orderMap[normalizedKey].qty, requiredQty)
  706. })
  707. const existing = []
  708. const missing = []
  709. Object.keys(orderMap).forEach((partNo) => {
  710. console.log('Comparing partNo:', orderMap[partNo])
  711. const requiredQty = orderMap[partNo].qty
  712. if (labelMap[partNo]) {
  713. const scannedQty = labelMap[partNo].qty
  714. existing.push({
  715. partNo,
  716. requiredQty,
  717. scannedQty,
  718. difference: scannedQty - requiredQty,
  719. })
  720. } else {
  721. missing.push({
  722. partNo,
  723. requiredQty,
  724. })
  725. }
  726. })
  727. const extra = []
  728. Object.keys(labelMap).forEach((partNo) => {
  729. if (!orderMap[partNo]) {
  730. extra.push({
  731. partNo,
  732. scannedQty: labelMap[partNo].qty,
  733. })
  734. }
  735. })
  736. this.comparisonData = {
  737. existing,
  738. missing,
  739. extra,
  740. }
  741. }
  742. )
  743. },
  744. closeSummaryDialog() {
  745. if (this.loading) {
  746. return
  747. }
  748. this.showSummaryDialog = false
  749. this.pendingIssueParams = null
  750. },
  751. proceedIssueConfirm() {
  752. if (!this.pendingIssueParams) {
  753. this.pendingIssueParams = this.buildIssueParams()
  754. }
  755. this.submitIssue()
  756. },
  757. submitIssue() {
  758. if (!this.pendingIssueParams) {
  759. this.$message.error('缺少发料参数,请重新确认')
  760. return
  761. }
  762. if (this.comparisonData.missing.length > 0) {
  763. this.$message.warning('存在未扫描物料,无法发料,请返回修改')
  764. return
  765. }
  766. if (this.comparisonData.extra.length > 0) {
  767. this.$message.warning('存在多余物料,无法发料,请返回修改')
  768. return
  769. }
  770. for (const item of this.comparisonData.existing) {
  771. if (item.difference != 0) {
  772. this.$message.warning(
  773. '存在发料数量超出或少于需求数量的物料,请返回修改'
  774. )
  775. return
  776. }
  777. }
  778. let params = this.pendingIssueParams
  779. params.shipmentType = this.orderType
  780. this.loading = true
  781. customerIssueConfirm(params)
  782. .then(({ data }) => {
  783. if (data.code === 0 && data) {
  784. if (data.unitIds.length > 0) {
  785. let printLabelType = '库存成品标签'
  786. // 调用打印方法,传入unitId数组和标签类型
  787. this.printViaServer(data.unitIds, printLabelType)
  788. }
  789. this.$message.success('客户发料成功')
  790. this.showSummaryDialog = false
  791. this.pendingIssueParams = null
  792. this.$router.push({ name: 'customerIssuePDA' })
  793. } else {
  794. this.$message.error(data.msg || '操作失败')
  795. }
  796. })
  797. .catch(() => {
  798. this.$message.error('操作失败')
  799. })
  800. .finally(() => {
  801. this.loading = false
  802. })
  803. },
  804. async printViaServer(unitIds, printLabelType) {
  805. if (!unitIds || unitIds.length === 0) {
  806. console.warn('没有可打印的标签')
  807. return
  808. }
  809. this.printLoading = true
  810. try {
  811. const printRequest = {
  812. userId: localStorage.getItem('userName'),
  813. username: localStorage.getItem('userName'),
  814. site: localStorage.getItem('site'),
  815. unitIds: unitIds,
  816. labelType: printLabelType,
  817. }
  818. console.log('打印请求:', printRequest)
  819. const { data } = await printLabelCommon(printRequest)
  820. if (data.code === 200 || data.code === 0) {
  821. this.$message.success(`打印任务已发送!`)
  822. this.clearData()
  823. } else {
  824. this.$message.error(data.msg || '打印失败')
  825. }
  826. } catch (error) {
  827. console.error('服务器打印失败:', error)
  828. this.$message.error(`打印失败: ${error.message || error}`)
  829. } finally {
  830. this.printLoading = false
  831. }
  832. },
  833. // 取消发料
  834. cancelIssue() {
  835. if (this.labelList.length > 0) {
  836. this.$confirm('取消后将清空已扫描的标签,确定取消吗?', '提示', {
  837. confirmButtonText: '确定',
  838. cancelButtonText: '继续操作',
  839. type: 'warning',
  840. })
  841. .then(() => {
  842. this.$router.back()
  843. })
  844. .catch(() => {
  845. // 用户选择继续操作
  846. })
  847. } else {
  848. this.$router.back()
  849. }
  850. },
  851. // 加载订单详情
  852. loadOrderDetails() {
  853. this.orderInfo.orderNo = this.orderNo
  854. this.orderInfo.itemNo = this.itemNo
  855. this.orderInfo.partNo = this.partNo
  856. this.getShipmentHuDetails()
  857. },
  858. getShipmentHuDetails() {
  859. let params = {
  860. shipmentId: this.orderNo,
  861. partNo: this.partNo,
  862. site: localStorage.getItem('site'),
  863. }
  864. this.labelList = []
  865. getShipmentHuDetail(params)
  866. .then(({ data }) => {
  867. if (data.code === 0 && data) {
  868. this.labelList = data.details || []
  869. } else {
  870. this.$message.error(data.msg || '获取订单详情失败')
  871. }
  872. })
  873. .catch(() => {
  874. this.$message.error('获取订单详情失败')
  875. })
  876. },
  877. // 打开打印弹框
  878. openPrintDialog() {
  879. if (this.labelList.length === 0) {
  880. this.$message.warning('请先扫描发料标签')
  881. return
  882. }
  883. // 复制标签列表并添加选中状态
  884. this.printLabelList = this.labelList.map((label) => ({
  885. ...label,
  886. __checked: true,
  887. }))
  888. this.printAllChecked = true
  889. this.showPrintDialog = true
  890. },
  891. // 关闭打印弹框
  892. closePrintDialog() {
  893. this.showPrintDialog = false
  894. this.printLabelList = []
  895. this.printAllChecked = true
  896. },
  897. // 全选/取消全选
  898. togglePrintAll(checked) {
  899. this.printLabelList.forEach((label) => {
  900. this.$set(label, '__checked', checked)
  901. })
  902. },
  903. // 执行打印
  904. onPrint(type) {
  905. const selected = this.printLabelList.filter((label) => label.__checked)
  906. if (selected.length === 0) {
  907. this.$message.warning('请至少选择一条记录')
  908. return
  909. }
  910. // 将选择结果暂存,供后续打印页面使用
  911. try {
  912. const payload = selected.map((label) => ({
  913. labelCode: label.labelCode || label.unitId,
  914. issueQty: Number(label.qtyToIssue) || 0,
  915. batchNo: label.batchNo,
  916. partNo: label.partNo,
  917. locationId: label.locationId,
  918. warehouseId: label.warehouseId,
  919. wdrNo: label.wdrNo || '*',
  920. }))
  921. sessionStorage.setItem(
  922. 'customerIssuePrintSelected',
  923. JSON.stringify(payload)
  924. )
  925. sessionStorage.setItem('customerIssuePrintType', type)
  926. this.$message.success('已准备打印:' + type)
  927. this.closePrintDialog()
  928. // 根据实际打印流程跳转或调用打印
  929. // this.$router.push({ name: 'somePrintPage' })
  930. } catch (e) {
  931. this.$message.error('打印准备失败')
  932. }
  933. },
  934. // 打开数量输入弹框
  935. openQuantityDialog(type) {
  936. const selected = this.printLabelList.filter((label) => label.__checked)
  937. if (selected.length === 0) {
  938. this.$message.warning('请至少选择一条记录')
  939. return
  940. }
  941. this.currentPrintType = type
  942. if (type === 'QC') {
  943. this.showQCDialog = true
  944. } else if (type === 'BOX') {
  945. this.showBOXDialog = true
  946. }
  947. },
  948. // 关闭数量输入弹框
  949. closeQuantityDialog() {
  950. this.showQCDialog = false
  951. this.showBOXDialog = false
  952. this.currentPrintType = ''
  953. },
  954. // 确认QC打印
  955. confirmQCPrint() {
  956. if (!this.qcQuantity || this.qcQuantity <= 0) {
  957. this.$message.warning('请输入有效的QC打印数量')
  958. return
  959. }
  960. this.executePrint('QC', this.qcQuantity)
  961. },
  962. // 确认BOX打印
  963. confirmBOXPrint() {
  964. if (!this.boxQuantity || this.boxQuantity <= 0) {
  965. this.$message.warning('请输入有效的BOX打印数量')
  966. return
  967. }
  968. this.executePrint('BOX', this.boxQuantity)
  969. },
  970. // 执行打印(带数量)
  971. executePrint(type, quantity) {
  972. const selected = this.printLabelList.filter((label) => label.__checked)
  973. try {
  974. const payload = selected.map((label) => ({
  975. labelCode: label.labelCode || label.unitId,
  976. issueQty: Number(label.qtyToIssue) || 0,
  977. batchNo: label.batchNo,
  978. partNo: label.partNo,
  979. locationId: label.locationId,
  980. warehouseId: label.warehouseId,
  981. wdrNo: label.wdrNo || '*',
  982. }))
  983. sessionStorage.setItem(
  984. 'customerIssuePrintSelected',
  985. JSON.stringify(payload)
  986. )
  987. sessionStorage.setItem('customerIssuePrintType', type)
  988. sessionStorage.setItem(
  989. 'customerIssuePrintQuantity',
  990. quantity.toString()
  991. )
  992. this.$message.success(`已准备打印:${type},数量:${quantity}`)
  993. this.closeQuantityDialog()
  994. this.closePrintDialog()
  995. // 根据实际打印流程跳转或调用打印
  996. // this.$router.push({ name: 'somePrintPage' })
  997. } catch (e) {
  998. this.$message.error('打印准备失败')
  999. }
  1000. },
  1001. },
  1002. mounted() {
  1003. // 获取路由参数
  1004. console.log('路由参数:', this.$route.query)
  1005. this.orderNo = this.$route.query.shipmentId
  1006. this.state = this.$route.query.state
  1007. this.orderType = this.$route.query.orderType
  1008. if (!this.orderNo) {
  1009. this.$message.error('参数错误')
  1010. this.$router.back()
  1011. return
  1012. }
  1013. // 聚焦扫描框
  1014. this.$nextTick(() => {
  1015. if (this.$refs.scanInput) {
  1016. this.$refs.scanInput.focus()
  1017. }
  1018. })
  1019. // 加载订单详情
  1020. this.loadOrderDetails()
  1021. },
  1022. }
  1023. </script>
  1024. <style scoped>
  1025. /* 复用生产领料页面的样式,只修改必要的部分 */
  1026. .pda-container {
  1027. width: 100vw;
  1028. height: 100vh;
  1029. display: flex;
  1030. flex-direction: column;
  1031. background: #f5f5f5;
  1032. overflow: hidden;
  1033. }
  1034. /* 头部栏 */
  1035. .header-bar {
  1036. display: flex;
  1037. justify-content: space-between;
  1038. align-items: center;
  1039. padding: 8px 16px;
  1040. background: #17b3a3;
  1041. color: white;
  1042. height: 40px;
  1043. min-height: 40px;
  1044. }
  1045. .header-left {
  1046. display: flex;
  1047. align-items: center;
  1048. cursor: pointer;
  1049. font-size: 16px;
  1050. font-weight: 500;
  1051. }
  1052. .header-left i {
  1053. margin-right: 8px;
  1054. font-size: 18px;
  1055. }
  1056. .header-right {
  1057. cursor: pointer;
  1058. font-size: 16px;
  1059. font-weight: 500;
  1060. }
  1061. /* 搜索容器 */
  1062. .search-container {
  1063. padding: 12px 16px;
  1064. background: white;
  1065. display: flex;
  1066. align-items: center;
  1067. gap: 12px;
  1068. }
  1069. .search-container .el-input {
  1070. width: 240px;
  1071. margin-right: 12px;
  1072. }
  1073. /* 紧凑型输入框样式 */
  1074. .compact-input ::v-deep .el-input__inner {
  1075. height: 36px;
  1076. padding: 0 12px 0 35px;
  1077. font-size: 14px;
  1078. }
  1079. .compact-input ::v-deep .el-input__prefix {
  1080. left: 10px;
  1081. }
  1082. .compact-input ::v-deep .el-input__suffix {
  1083. right: 30px;
  1084. }
  1085. /* 模式切换开关 */
  1086. .mode-switch {
  1087. position: relative;
  1088. display: inline-block;
  1089. }
  1090. .custom-switch {
  1091. transform: scale(1.3);
  1092. }
  1093. /* 中间文字 */
  1094. .switch-text {
  1095. position: absolute;
  1096. left: 25%;
  1097. transform: translateX(-50%);
  1098. top: 50%;
  1099. transform: translateY(-50%) translateX(-50%);
  1100. font-size: 12px;
  1101. font-weight: 500;
  1102. color: #606266;
  1103. white-space: nowrap;
  1104. pointer-events: none;
  1105. z-index: 1;
  1106. top: 53%;
  1107. transform: translate(-50%, -50%);
  1108. font-size: 12px;
  1109. font-weight: bold;
  1110. color: white;
  1111. pointer-events: none;
  1112. z-index: 2;
  1113. }
  1114. .switch-text2 {
  1115. position: absolute;
  1116. left: 75%;
  1117. transform: translateX(-50%);
  1118. top: 50%;
  1119. transform: translateY(-50%) translateX(-50%);
  1120. font-size: 12px;
  1121. font-weight: 500;
  1122. color: #606266;
  1123. white-space: nowrap;
  1124. pointer-events: none;
  1125. z-index: 1;
  1126. top: 53%;
  1127. transform: translate(-50%, -50%);
  1128. font-size: 12px;
  1129. font-weight: bold;
  1130. color: white;
  1131. pointer-events: none;
  1132. z-index: 2;
  1133. }
  1134. /* 调整 switch 尺寸以便容纳文字 */
  1135. .custom-switch ::v-deep .el-switch__core {
  1136. width: 60px;
  1137. height: 28px;
  1138. }
  1139. /* 物料信息卡片 */
  1140. .material-info-card {
  1141. background: white;
  1142. margin: 4px 16px;
  1143. padding: 6px 20px;
  1144. border-radius: 8px;
  1145. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  1146. border: 1px solid #f0f0f0;
  1147. border-left: 4px solid #17b3a3;
  1148. }
  1149. .card-title {
  1150. margin-bottom: 16px;
  1151. }
  1152. .title-label {
  1153. font-size: 11px;
  1154. color: #999;
  1155. display: block;
  1156. margin-bottom: 6px;
  1157. font-weight: normal;
  1158. }
  1159. .title-value {
  1160. font-size: 18px;
  1161. font-weight: bold;
  1162. color: #333;
  1163. line-height: 1.2;
  1164. margin-left: 20px;
  1165. }
  1166. .card-details {
  1167. display: flex;
  1168. justify-content: space-between;
  1169. align-items: flex-start;
  1170. gap: 4px;
  1171. }
  1172. .detail-item {
  1173. flex: 1;
  1174. text-align: center;
  1175. min-width: 60px;
  1176. max-width: 60px;
  1177. }
  1178. .detail-label {
  1179. font-size: 11px;
  1180. color: #999;
  1181. margin-bottom: 6px;
  1182. font-weight: normal;
  1183. line-height: 1.2;
  1184. margin-left: -12px;
  1185. }
  1186. .detail-value {
  1187. font-size: 13px;
  1188. color: #333;
  1189. font-weight: 500;
  1190. line-height: 1.2;
  1191. margin-left: -12px;
  1192. }
  1193. .detail-value .qualified {
  1194. color: #17b3a3;
  1195. font-weight: 500;
  1196. }
  1197. .detail-value .total {
  1198. color: #333;
  1199. font-weight: 500;
  1200. }
  1201. .detail-value .total::before {
  1202. content: '/';
  1203. color: #333;
  1204. }
  1205. /* 区域标题 */
  1206. .section-title {
  1207. display: flex;
  1208. align-items: center;
  1209. justify-content: space-between;
  1210. padding: 6px 10px;
  1211. background: white;
  1212. margin: 0 10px;
  1213. margin-top: 4px;
  1214. border-radius: 6px 6px 0 0;
  1215. border-bottom: 2px solid #17b3a3;
  1216. }
  1217. /* 统一滚动容器 */
  1218. .content-scroll-container {
  1219. flex: 1;
  1220. overflow-y: auto;
  1221. overflow-x: hidden;
  1222. background: #f5f5f5;
  1223. min-height: 0;
  1224. }
  1225. /* 对齐直接领料明细的工单卡片样式 */
  1226. /* 工单列表容器背景与间距 */
  1227. .work-order-list {
  1228. padding: 8px 10px 4px;
  1229. }
  1230. /* 工单卡片具有白色背景、圆角、阴影与边框一致性 */
  1231. .work-order-card {
  1232. background: white;
  1233. border-radius: 6px;
  1234. padding: 8px;
  1235. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  1236. border: 1px solid #e0e0e0;
  1237. }
  1238. /* 物料描述行(单独一行的小字说明) */
  1239. .part-desc-row {
  1240. margin-bottom: 12px;
  1241. padding: 0 4px;
  1242. }
  1243. .desc-text {
  1244. font-size: 12px;
  1245. color: #666;
  1246. line-height: 1.3;
  1247. word-break: break-all;
  1248. }
  1249. .title-left {
  1250. display: flex;
  1251. align-items: center;
  1252. }
  1253. .title-left i {
  1254. color: #17b3a3;
  1255. font-size: 16px;
  1256. margin-right: 8px;
  1257. }
  1258. .title-left span {
  1259. color: #17b3a3;
  1260. font-size: 14px;
  1261. font-weight: 500;
  1262. }
  1263. .title-right {
  1264. display: flex;
  1265. align-items: center;
  1266. }
  1267. .material-list-link {
  1268. color: #17b3a3;
  1269. font-size: 14px;
  1270. font-weight: 500;
  1271. cursor: pointer;
  1272. text-decoration: underline;
  1273. transition: color 0.2s ease;
  1274. }
  1275. .material-list-link:hover {
  1276. color: #0d8f7f;
  1277. }
  1278. /* 标签列表 - 卡片容器 */
  1279. .label-card-container {
  1280. padding: 0 8px 8px;
  1281. background: #f5f5f5;
  1282. }
  1283. /* 标签卡片 - 更紧凑样式 */
  1284. .label-card {
  1285. background: white;
  1286. border-radius: 4px;
  1287. margin-bottom: 6px;
  1288. padding: 6px 8px;
  1289. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  1290. border: 1px solid #e0e0e0;
  1291. cursor: pointer;
  1292. transition: all 0.2s ease;
  1293. position: relative;
  1294. }
  1295. .label-card:hover {
  1296. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
  1297. border-color: #17b3a3;
  1298. }
  1299. .label-card:active {
  1300. transform: scale(0.98);
  1301. }
  1302. /* 编辑图标 - 右上角 */
  1303. .card-edit-icon {
  1304. position: absolute;
  1305. top: 6px;
  1306. right: 6px;
  1307. width: 20px;
  1308. height: 20px;
  1309. display: flex;
  1310. align-items: center;
  1311. justify-content: center;
  1312. background: #f0f0f0;
  1313. border-radius: 3px;
  1314. cursor: pointer;
  1315. transition: all 0.2s ease;
  1316. z-index: 10;
  1317. }
  1318. .card-edit-icon:hover {
  1319. background: #17b3a3;
  1320. color: white;
  1321. }
  1322. .card-edit-icon i {
  1323. font-size: 12px;
  1324. color: #17b3a3;
  1325. }
  1326. .card-edit-icon:hover i {
  1327. color: white;
  1328. }
  1329. /* 卡片内容 */
  1330. .card-content {
  1331. padding-right: 28px;
  1332. }
  1333. .card-row {
  1334. display: flex;
  1335. align-items: center;
  1336. font-size: 11px;
  1337. line-height: 1.5;
  1338. margin-bottom: 3px;
  1339. }
  1340. .card-row:last-child {
  1341. margin-bottom: 0;
  1342. }
  1343. .card-label {
  1344. color: #666;
  1345. min-width: 65px;
  1346. flex-shrink: 0;
  1347. font-size: 11px;
  1348. }
  1349. .card-value {
  1350. color: #333;
  1351. flex: 1;
  1352. word-break: break-all;
  1353. font-size: 11px;
  1354. }
  1355. .card-value.highlight {
  1356. color: #17b3a3;
  1357. font-weight: bold;
  1358. font-size: 12px;
  1359. }
  1360. /* 标签列表(保留原有样式,以防其他地方使用) */
  1361. .label-list {
  1362. background: white;
  1363. margin: 0 10px 12px;
  1364. border-radius: 0 0 8px 8px;
  1365. overflow: hidden;
  1366. max-height: 300px;
  1367. overflow-y: auto;
  1368. }
  1369. .list-header {
  1370. display: flex;
  1371. background: #f8f9fa;
  1372. padding: 12px 8px;
  1373. border-bottom: 1px solid #e0e0e0;
  1374. font-size: 12px;
  1375. color: #666;
  1376. font-weight: 500;
  1377. position: sticky;
  1378. top: 0;
  1379. z-index: 1;
  1380. }
  1381. .list-item {
  1382. display: flex;
  1383. padding: 12px 8px;
  1384. border-bottom: 1px solid #f0f0f0;
  1385. font-size: 12px;
  1386. color: #333;
  1387. align-items: flex-start;
  1388. min-height: 40px;
  1389. }
  1390. .list-item:last-child {
  1391. border-bottom: none;
  1392. }
  1393. .col-no {
  1394. width: 20px;
  1395. text-align: center;
  1396. }
  1397. .col-label {
  1398. flex: 1.5;
  1399. text-align: center;
  1400. word-break: break-all;
  1401. white-space: normal;
  1402. line-height: 1.2;
  1403. }
  1404. .col-part {
  1405. flex: 1.5;
  1406. text-align: center;
  1407. }
  1408. .col-batch {
  1409. flex: 1.5;
  1410. text-align: center;
  1411. }
  1412. .col-qty {
  1413. width: 80px;
  1414. text-align: center;
  1415. cursor: pointer;
  1416. position: relative;
  1417. display: flex;
  1418. align-items: center;
  1419. justify-content: center;
  1420. gap: 4px;
  1421. }
  1422. .quantity-display {
  1423. font-size: 12px;
  1424. color: #333;
  1425. }
  1426. .edit-icon {
  1427. font-size: 12px;
  1428. color: #17b3a3;
  1429. opacity: 0.7;
  1430. transition: opacity 0.2s ease;
  1431. }
  1432. .col-qty:hover .edit-icon {
  1433. opacity: 1;
  1434. }
  1435. .col-qty:hover {
  1436. background-color: #f0fffe;
  1437. border-radius: 4px;
  1438. }
  1439. .empty-labels {
  1440. padding: 40px 20px;
  1441. text-align: center;
  1442. color: #999;
  1443. }
  1444. .empty-labels p {
  1445. margin: 0;
  1446. font-size: 14px;
  1447. }
  1448. /* 底部操作按钮 */
  1449. .bottom-actions {
  1450. display: flex;
  1451. padding: 16px;
  1452. gap: 20px;
  1453. background: white;
  1454. }
  1455. .action-btn {
  1456. flex: 1;
  1457. padding: 12px;
  1458. border-radius: 20px;
  1459. font-size: 14px;
  1460. cursor: pointer;
  1461. transition: all 0.2s ease;
  1462. }
  1463. .action-btn.primary {
  1464. border: 1px solid #17b3a3;
  1465. background: #17b3a3;
  1466. color: white;
  1467. }
  1468. .action-btn.primary:hover {
  1469. background: #13998c;
  1470. border-color: #13998c;
  1471. }
  1472. .action-btn.secondary {
  1473. border: 1px solid #17b3a3;
  1474. background: white;
  1475. color: #17b3a3;
  1476. }
  1477. .action-btn.secondary:hover {
  1478. background: #17b3a3;
  1479. color: white;
  1480. }
  1481. .action-btn:active {
  1482. transform: scale(0.98);
  1483. }
  1484. /* 编辑弹框样式 */
  1485. .edit-overlay {
  1486. position: fixed;
  1487. top: 0;
  1488. left: 0;
  1489. right: 0;
  1490. bottom: 0;
  1491. background: rgba(0, 0, 0, 0.5);
  1492. z-index: 9999;
  1493. display: flex;
  1494. align-items: center;
  1495. justify-content: center;
  1496. padding: 20px;
  1497. }
  1498. .edit-modal {
  1499. background: white;
  1500. border-radius: 12px;
  1501. width: 100%;
  1502. max-width: 400px;
  1503. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  1504. overflow: hidden;
  1505. display: flex;
  1506. flex-direction: column;
  1507. }
  1508. .edit-modal .modal-header {
  1509. background: #17b3a3;
  1510. color: white;
  1511. padding: 12px 16px;
  1512. display: flex;
  1513. justify-content: space-between;
  1514. align-items: center;
  1515. }
  1516. .edit-modal .modal-title {
  1517. font-size: 16px;
  1518. font-weight: 500;
  1519. margin: 0;
  1520. }
  1521. .edit-modal .close-btn {
  1522. font-size: 16px;
  1523. cursor: pointer;
  1524. color: white;
  1525. transition: color 0.2s ease;
  1526. padding: 4px;
  1527. display: flex;
  1528. align-items: center;
  1529. justify-content: center;
  1530. }
  1531. .edit-modal .close-btn:hover {
  1532. color: #e0e0e0;
  1533. }
  1534. .edit-modal .modal-body {
  1535. padding: 20px;
  1536. max-height: 60vh;
  1537. overflow-y: auto;
  1538. }
  1539. .edit-modal .modal-body.scrollable {
  1540. max-height: 60vh;
  1541. overflow-y: auto;
  1542. padding-right: 10px;
  1543. }
  1544. /* 滚动条样式 */
  1545. .edit-modal .modal-body.scrollable::-webkit-scrollbar {
  1546. width: 6px;
  1547. }
  1548. .edit-modal .modal-body.scrollable::-webkit-scrollbar-track {
  1549. background: #f1f1f1;
  1550. border-radius: 3px;
  1551. }
  1552. .edit-modal .modal-body.scrollable::-webkit-scrollbar-thumb {
  1553. background: #17b3a3;
  1554. border-radius: 3px;
  1555. }
  1556. .edit-modal .modal-body.scrollable::-webkit-scrollbar-thumb:hover {
  1557. background: #13998c;
  1558. }
  1559. .edit-modal .form-group {
  1560. margin-bottom: 16px;
  1561. }
  1562. .edit-modal .form-label {
  1563. display: block;
  1564. font-size: 14px;
  1565. color: #333;
  1566. margin-bottom: 6px;
  1567. font-weight: 500;
  1568. }
  1569. .edit-modal .required {
  1570. color: #ff4949;
  1571. }
  1572. .edit-modal .form-input {
  1573. width: 100%;
  1574. }
  1575. .edit-modal .form-input ::v-deep .el-input__inner {
  1576. height: 40px;
  1577. border: 2px solid #dcdfe6;
  1578. border-radius: 6px;
  1579. font-size: 14px;
  1580. padding: 0 12px;
  1581. }
  1582. .edit-modal .form-input ::v-deep .el-input__inner:focus {
  1583. border-color: #17b3a3;
  1584. outline: none;
  1585. }
  1586. .edit-modal .form-input ::v-deep .el-input__inner:disabled {
  1587. background: #f5f7fa;
  1588. color: #c0c4cc;
  1589. border-color: #e4e7ed;
  1590. }
  1591. .edit-modal .modal-footer {
  1592. padding: 16px 20px;
  1593. display: flex;
  1594. gap: 12px;
  1595. justify-content: flex-end;
  1596. border-top: 1px solid #f0f0f0;
  1597. }
  1598. .edit-modal .btn-cancel {
  1599. padding: 10px 20px;
  1600. border-radius: 6px;
  1601. font-size: 14px;
  1602. cursor: pointer;
  1603. transition: all 0.2s;
  1604. border: 1px solid #dcdfe6;
  1605. background: white;
  1606. color: #606266;
  1607. }
  1608. .edit-modal .btn-cancel:hover {
  1609. background: #f5f7fa;
  1610. border-color: #c0c4cc;
  1611. }
  1612. .edit-modal .btn-confirm {
  1613. padding: 10px 20px;
  1614. border-radius: 6px;
  1615. font-size: 14px;
  1616. cursor: pointer;
  1617. transition: all 0.2s;
  1618. border: 1px solid #17b3a3;
  1619. background: #17b3a3;
  1620. color: white;
  1621. }
  1622. .edit-modal .btn-confirm:hover {
  1623. background: #13998c;
  1624. border-color: #13998c;
  1625. }
  1626. /* 打印弹框样式 */
  1627. .print-overlay {
  1628. position: fixed;
  1629. top: 0;
  1630. left: 0;
  1631. right: 0;
  1632. bottom: 0;
  1633. background: rgba(0, 0, 0, 0.5);
  1634. z-index: 9999;
  1635. display: flex;
  1636. align-items: center;
  1637. justify-content: center;
  1638. padding: 10px;
  1639. }
  1640. .print-modal {
  1641. background: white;
  1642. border-radius: 12px;
  1643. width: 100%;
  1644. max-width: 500px;
  1645. max-height: 80vh;
  1646. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  1647. overflow: hidden;
  1648. display: flex;
  1649. flex-direction: column;
  1650. }
  1651. .print-modal .modal-header {
  1652. background: #17b3a3;
  1653. color: white;
  1654. padding: 12px 16px;
  1655. display: flex;
  1656. justify-content: space-between;
  1657. align-items: center;
  1658. }
  1659. .print-modal .modal-title {
  1660. font-size: 16px;
  1661. font-weight: 500;
  1662. margin: 0;
  1663. }
  1664. .print-modal .close-btn {
  1665. font-size: 16px;
  1666. cursor: pointer;
  1667. color: white;
  1668. transition: color 0.2s ease;
  1669. padding: 4px;
  1670. display: flex;
  1671. align-items: center;
  1672. justify-content: center;
  1673. }
  1674. .print-modal .close-btn:hover {
  1675. color: #e0e0e0;
  1676. }
  1677. .print-modal .modal-body {
  1678. padding: 6px;
  1679. flex: 1;
  1680. overflow: hidden;
  1681. display: flex;
  1682. flex-direction: column;
  1683. }
  1684. .print-section-title {
  1685. display: flex;
  1686. align-items: center;
  1687. justify-content: space-between;
  1688. padding: 8px 0;
  1689. margin-bottom: 12px;
  1690. border-bottom: 1px solid #e0e0e0;
  1691. }
  1692. .print-section-title .title-left {
  1693. display: flex;
  1694. align-items: center;
  1695. }
  1696. .print-section-title .title-left i {
  1697. color: #17b3a3;
  1698. font-size: 16px;
  1699. margin-right: 8px;
  1700. }
  1701. .print-section-title .title-left span {
  1702. color: #17b3a3;
  1703. font-size: 14px;
  1704. font-weight: 500;
  1705. }
  1706. .print-section-title .title-right {
  1707. display: flex;
  1708. align-items: center;
  1709. }
  1710. .print-label-list {
  1711. flex: 1;
  1712. /* 允许列表区域在弹框内上下滚动,避免只显示固定条数 */
  1713. max-height: 50vh;
  1714. overflow-y: auto;
  1715. overflow-x: hidden;
  1716. position: relative; /* 为粘性表头提供更高的层叠上下文控制 */
  1717. border: 1px solid #e0e0e0;
  1718. border-radius: 6px;
  1719. }
  1720. .print-label-list .list-header {
  1721. display: flex;
  1722. background: #f8f9fa;
  1723. padding: 10px 8px;
  1724. border-bottom: 1px solid #e0e0e0;
  1725. font-size: 12px;
  1726. color: #666;
  1727. font-weight: 500;
  1728. position: sticky;
  1729. top: 0;
  1730. z-index: 5; /* 保证表头覆盖列表项与复选框 */
  1731. }
  1732. .print-label-list .list-item {
  1733. display: flex;
  1734. padding: 10px 8px;
  1735. border-bottom: 1px solid #f0f0f0;
  1736. font-size: 12px;
  1737. color: #333;
  1738. align-items: center;
  1739. min-height: 36px;
  1740. }
  1741. .print-label-list .list-item:last-child {
  1742. border-bottom: none;
  1743. }
  1744. .print-label-list .col-no {
  1745. width: 30px;
  1746. text-align: center;
  1747. }
  1748. .print-label-list .col-label {
  1749. flex: 1.5;
  1750. text-align: center;
  1751. word-break: break-all;
  1752. white-space: normal;
  1753. line-height: 1.2;
  1754. }
  1755. .print-label-list .col-part {
  1756. flex: 1.2;
  1757. text-align: center;
  1758. }
  1759. .print-label-list .col-qty {
  1760. width: 70px;
  1761. text-align: center;
  1762. }
  1763. .print-label-list .col-check {
  1764. width: 50px;
  1765. text-align: center;
  1766. }
  1767. /* 放大复选框(仅作用于打印弹框列表区域) */
  1768. .print-label-list .col-check ::v-deep .el-checkbox {
  1769. transform: scale(1.2);
  1770. transform-origin: center center;
  1771. }
  1772. /* 调整放大后与行高的对齐 */
  1773. .print-label-list .col-check {
  1774. display: flex;
  1775. align-items: center;
  1776. justify-content: center;
  1777. }
  1778. .print-modal .modal-footer {
  1779. padding: 16px 20px;
  1780. display: flex;
  1781. gap: 12px;
  1782. justify-content: flex-end;
  1783. border-top: 1px solid #f0f0f0;
  1784. }
  1785. .print-modal .btn-secondary {
  1786. padding: 10px 20px;
  1787. border-radius: 6px;
  1788. font-size: 14px;
  1789. cursor: pointer;
  1790. transition: all 0.2s;
  1791. border: 1px solid #17b3a3;
  1792. background: white;
  1793. color: #17b3a3;
  1794. }
  1795. .print-modal .btn-secondary:hover {
  1796. background: #17b3a3;
  1797. color: white;
  1798. }
  1799. /* 响应式设计 */
  1800. @media (max-width: 360px) {
  1801. .header-bar {
  1802. padding: 8px 12px;
  1803. }
  1804. .search-container {
  1805. padding: 8px 12px;
  1806. }
  1807. .material-info-card {
  1808. margin: 4px 12px;
  1809. padding: 6px 16px;
  1810. }
  1811. .section-title {
  1812. margin: 0 12px;
  1813. margin-top: 4px;
  1814. }
  1815. .label-list {
  1816. margin: 0 12px 8px;
  1817. }
  1818. .card-details {
  1819. flex-wrap: wrap;
  1820. gap: 6px;
  1821. }
  1822. .detail-item {
  1823. flex: 0 0 48%;
  1824. margin-bottom: 6px;
  1825. min-width: 50px;
  1826. }
  1827. .list-header,
  1828. .list-item {
  1829. font-size: 11px;
  1830. }
  1831. .col-label,
  1832. .col-part,
  1833. .col-batch {
  1834. flex: 1.2;
  1835. }
  1836. }
  1837. /* 数量输入弹框样式 */
  1838. .quantity-overlay {
  1839. position: fixed;
  1840. top: 0;
  1841. left: 0;
  1842. right: 0;
  1843. bottom: 0;
  1844. background: rgba(0, 0, 0, 0.5);
  1845. z-index: 10000;
  1846. display: flex;
  1847. align-items: center;
  1848. justify-content: center;
  1849. padding: 20px;
  1850. }
  1851. .quantity-modal {
  1852. background: white;
  1853. border-radius: 12px;
  1854. width: 100%;
  1855. max-width: 400px;
  1856. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  1857. overflow: hidden;
  1858. display: flex;
  1859. flex-direction: column;
  1860. }
  1861. .quantity-modal .modal-header {
  1862. background: #17b3a3;
  1863. color: white;
  1864. padding: 12px 16px;
  1865. display: flex;
  1866. justify-content: space-between;
  1867. align-items: center;
  1868. }
  1869. .quantity-modal .modal-title {
  1870. font-size: 16px;
  1871. font-weight: 500;
  1872. margin: 0;
  1873. }
  1874. .quantity-modal .close-btn {
  1875. font-size: 16px;
  1876. cursor: pointer;
  1877. color: white;
  1878. transition: color 0.2s ease;
  1879. padding: 4px;
  1880. display: flex;
  1881. align-items: center;
  1882. justify-content: center;
  1883. }
  1884. .quantity-modal .close-btn:hover {
  1885. color: #e0e0e0;
  1886. }
  1887. .quantity-modal .modal-body {
  1888. padding: 20px;
  1889. }
  1890. .quantity-modal .form-group {
  1891. margin-bottom: 16px;
  1892. }
  1893. .quantity-modal .form-label {
  1894. display: block;
  1895. font-size: 14px;
  1896. color: #333;
  1897. margin-bottom: 6px;
  1898. font-weight: 500;
  1899. }
  1900. .quantity-modal .required {
  1901. color: #ff4949;
  1902. }
  1903. .quantity-modal .form-input {
  1904. width: 100%;
  1905. }
  1906. .quantity-modal .form-input ::v-deep .el-input__inner {
  1907. height: 40px;
  1908. border: 2px solid #dcdfe6;
  1909. border-radius: 6px;
  1910. font-size: 14px;
  1911. padding: 0 12px;
  1912. }
  1913. .quantity-modal .form-input ::v-deep .el-input__inner:focus {
  1914. border-color: #17b3a3;
  1915. outline: none;
  1916. }
  1917. .quantity-modal .modal-footer {
  1918. padding: 16px 20px;
  1919. display: flex;
  1920. gap: 12px;
  1921. justify-content: flex-end;
  1922. border-top: 1px solid #f0f0f0;
  1923. }
  1924. .quantity-modal .btn-cancel {
  1925. padding: 10px 20px;
  1926. border-radius: 6px;
  1927. font-size: 14px;
  1928. cursor: pointer;
  1929. transition: all 0.2s;
  1930. border: 1px solid #dcdfe6;
  1931. background: white;
  1932. color: #606266;
  1933. }
  1934. .quantity-modal .btn-cancel:hover {
  1935. background: #f5f7fa;
  1936. border-color: #c0c4cc;
  1937. }
  1938. .quantity-modal .btn-confirm {
  1939. padding: 10px 20px;
  1940. border-radius: 6px;
  1941. font-size: 14px;
  1942. cursor: pointer;
  1943. transition: all 0.2s;
  1944. border: 1px solid #17b3a3;
  1945. background: #17b3a3;
  1946. color: white;
  1947. }
  1948. .quantity-modal .btn-confirm:hover {
  1949. background: #13998c;
  1950. border-color: #13998c;
  1951. }
  1952. /* 发料对比弹框 */
  1953. .summary-overlay {
  1954. position: fixed;
  1955. top: 0;
  1956. left: 0;
  1957. right: 0;
  1958. bottom: 0;
  1959. background: rgba(0, 0, 0, 0.5);
  1960. z-index: 10000;
  1961. display: flex;
  1962. align-items: center;
  1963. justify-content: center;
  1964. padding: 6px;
  1965. }
  1966. .summary-modal {
  1967. width: 100%;
  1968. max-width: 560px;
  1969. background: white;
  1970. border-radius: 14px;
  1971. box-shadow: 0 14px 32px rgba(0, 0, 0, 0.25);
  1972. overflow: hidden;
  1973. display: flex;
  1974. flex-direction: column;
  1975. }
  1976. .summary-modal .modal-header {
  1977. background: #17b3a3;
  1978. color: white;
  1979. padding: 12px 20px;
  1980. display: flex;
  1981. justify-content: space-between;
  1982. align-items: center;
  1983. }
  1984. .summary-body {
  1985. max-height: 65vh;
  1986. overflow-y: auto;
  1987. padding-bottom: 4px;
  1988. }
  1989. .summary-loading {
  1990. padding: 40px 20px;
  1991. text-align: center;
  1992. color: #666;
  1993. }
  1994. .summary-header-stats {
  1995. padding: 14px 20px 4px;
  1996. display: flex;
  1997. gap: 12px;
  1998. }
  1999. .stat-card {
  2000. flex: 1;
  2001. background: #f7f8fa;
  2002. border-radius: 10px;
  2003. padding: 10px 12px;
  2004. display: flex;
  2005. flex-direction: column;
  2006. gap: 6px;
  2007. position: relative;
  2008. overflow: hidden;
  2009. }
  2010. .stat-card i {
  2011. font-size: 18px;
  2012. color: inherit;
  2013. }
  2014. .stat-card.exist {
  2015. border-left: 4px solid #2ec4b6;
  2016. color: #0f9d92;
  2017. }
  2018. .stat-card.missing {
  2019. border-left: 4px solid #ffb703;
  2020. color: #f18f01;
  2021. }
  2022. .stat-card.extra {
  2023. border-left: 4px solid #3a86ff;
  2024. color: #1d6fe3;
  2025. }
  2026. .stat-count {
  2027. font-size: 22px;
  2028. font-weight: 600;
  2029. color: #252525;
  2030. }
  2031. .stat-label {
  2032. font-size: 12px;
  2033. color: #777;
  2034. letter-spacing: 0.5px;
  2035. }
  2036. .summary-section {
  2037. padding: 12px 2px 18px;
  2038. border-bottom: 1px solid #f3f4f6;
  2039. }
  2040. .summary-section:last-child {
  2041. border-bottom: none;
  2042. }
  2043. .section-title-row {
  2044. display: flex;
  2045. justify-content: space-between;
  2046. align-items: flex-end;
  2047. margin-bottom: 8px;
  2048. }
  2049. .section-title-text {
  2050. font-size: 14px;
  2051. font-weight: 600;
  2052. color: #333;
  2053. }
  2054. .section-sub {
  2055. font-size: 12px;
  2056. color: #999;
  2057. margin-left: 6px;
  2058. }
  2059. .section-count {
  2060. font-size: 12px;
  2061. color: #a0a0a0;
  2062. }
  2063. .summary-table {
  2064. border: 1px solid #eef0f4;
  2065. border-radius: 10px;
  2066. overflow: hidden;
  2067. }
  2068. .summary-table-row {
  2069. display: grid;
  2070. gap: 8px;
  2071. padding: 8px 12px;
  2072. font-size: 12px;
  2073. align-items: center;
  2074. }
  2075. .summary-table--4 .summary-table-row {
  2076. grid-template-columns: 2fr 1fr 1fr 1fr;
  2077. }
  2078. .summary-table--3 .summary-table-row {
  2079. grid-template-columns: 2.2fr 1fr 1fr;
  2080. }
  2081. .summary-table-head {
  2082. background: #f8f9fb;
  2083. font-weight: 600;
  2084. color: #666;
  2085. text-transform: uppercase;
  2086. }
  2087. .summary-table-row .part {
  2088. font-weight: 600;
  2089. color: #333;
  2090. }
  2091. .summary-table-row:not(.summary-table-head) {
  2092. border-top: 1px solid #f0f2f5;
  2093. }
  2094. .summary-table-row:first-of-type {
  2095. border-top: none;
  2096. }
  2097. .badge {
  2098. display: inline-flex;
  2099. align-items: center;
  2100. justify-content: center;
  2101. min-width: 60px;
  2102. padding: 2px 8px;
  2103. border-radius: 20px;
  2104. font-size: 11px;
  2105. font-weight: 600;
  2106. }
  2107. .badge.success {
  2108. background: rgba(46, 196, 182, 0.15);
  2109. color: #0f9d92;
  2110. }
  2111. .badge.danger {
  2112. background: rgba(255, 99, 71, 0.15);
  2113. color: #ff4d4f;
  2114. }
  2115. .badge.warn {
  2116. background: rgba(255, 183, 3, 0.15);
  2117. color: #f18f01;
  2118. }
  2119. .badge.info {
  2120. background: rgba(58, 134, 255, 0.15);
  2121. color: #1d6fe3;
  2122. }
  2123. .summary-empty {
  2124. padding: 12px 0 4px;
  2125. font-size: 12px;
  2126. color: #999;
  2127. }
  2128. .summary-modal .modal-footer {
  2129. padding: 12px 20px;
  2130. display: flex;
  2131. justify-content: flex-end;
  2132. gap: 12px;
  2133. border-top: 1px solid #f0f0f0;
  2134. }
  2135. </style>