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.

2007 lines
51 KiB

7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
2 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
2 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
2 months ago
7 months ago
7 months ago
7 months ago
2 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
4 months ago
7 months 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: '/' })">
  10. 首页
  11. </div>
  12. </div>
  13. <!-- 入库单信息卡片 -->
  14. <div class="material-info-card" v-if="inboundInfo.inboundNo">
  15. <!-- 第一行入库单号 -->
  16. <div class="card-row-first">
  17. <div class="title-item">
  18. <span class="title-label">入库单号</span>
  19. <span class="title-value">{{ inboundInfo.inboundNo }}</span>
  20. </div>
  21. </div>
  22. <!-- 第二行关联单号行号+ 标签张数物料总数右靠 -->
  23. <div class="card-row-second">
  24. <div class="card-row-left">
  25. <div class="title-item">
  26. <span class="title-label">关联单号</span>
  27. <span class="title-value title-value-sm">{{ inboundInfo.relatedOrderNo || '-' }}</span>
  28. </div>
  29. <div class="title-item">
  30. <span class="title-label">行号</span>
  31. <span class="title-value title-value-sm">{{ inboundInfo.relatedOrderLineNo || '-' }}</span>
  32. </div>
  33. </div>
  34. <div class="card-row-right">
  35. <div class="detail-item">
  36. <div class="detail-label">标签张数</div>
  37. <div class="detail-value">
  38. <span class="qualified">{{ inboundInfo.totalLabels || 0 }}</span>
  39. </div>
  40. </div>
  41. <div class="detail-item">
  42. <div class="detail-label">物料总数</div>
  43. <div class="detail-value">
  44. <span class="qualified">{{ inboundInfo.totalQty || 0 }}</span>
  45. </div>
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. <!-- 物料输入区域 -->
  51. <div class="input-section">
  52. <!-- 物料信息输入区域 -->
  53. <div class="material-input-grid">
  54. <!-- 第一行物料编码需求数量入库数量批次号 -->
  55. <div class="input-row-top">
  56. <!-- 物料编码 -->
  57. <div class="input-item material-code-item">
  58. <span class="input-label">物料编码</span>
  59. <el-select
  60. v-model="materialCode"
  61. placeholder="请选择物料"
  62. filterable
  63. @change="handleMaterialSelect"
  64. ref="materialSelect"
  65. class="input-field"
  66. >
  67. <el-option
  68. v-for="item in materialOptions"
  69. :key="item.materialCode"
  70. :label="item.materialCode"
  71. :value="item.materialCode"
  72. >
  73. <span style="float: left">{{ item.materialCode }}</span>
  74. <span style="float: right; color: #8492a6; font-size: 13px">{{ item.materialName }}</span>
  75. </el-option>
  76. </el-select>
  77. </div>
  78. <!-- 需求数量 -->
  79. <div class="input-item required-qty-item">
  80. <span class="input-label">需求数量</span>
  81. <el-input
  82. v-model="currentMaterial.requiredQty"
  83. disabled
  84. class="input-field"
  85. />
  86. </div>
  87. <!-- 入库数量 -->
  88. <div class="input-item quantity-item">
  89. <span class="input-label">入库数量</span>
  90. <el-input class="inlineNumber numInput" v-model="actualQty" placeholder="入库数量" type="number" @keyup.enter.native="handleAddMaterial" clearable/>
  91. </div>
  92. <!-- 批次号 -->
  93. <div class="input-item batch-item">
  94. <span class="input-label">批次号</span>
  95. <el-input class="inlineNumber numInput" v-model="batchNo" placeholder="*" @keyup.enter.native="handleAddMaterial" ref="batchInput" clearable/>
  96. </div>
  97. </div>
  98. <!-- 第二行单位+ 物料名称 -->
  99. <div class="input-row-bottom">
  100. <div class="input-item unit-item-bottom">
  101. <span class="input-label">单位</span>
  102. <el-input
  103. v-model="currentMaterial.unit"
  104. disabled
  105. class="input-field"
  106. />
  107. </div>
  108. <div class="field-area material-name-area">
  109. <div class="field-title">物料名称</div>
  110. <div class="info-box">{{ currentMaterial.materialName }}</div>
  111. </div>
  112. </div>
  113. </div>
  114. <!-- 添加物料按钮 -->
  115. <div class="add-material-section">
  116. <button class="add-material-btn" @click="handleAddMaterial">
  117. <i class="el-icon-plus"></i>
  118. <span>添加物料</span>
  119. </button>
  120. </div>
  121. </div>
  122. <!-- 入库信息确认标题 -->
  123. <div class="section-title">
  124. <div class="title-left">
  125. <i class="el-icon-circle-check"></i>
  126. <span>入库信息确认</span>
  127. </div>
  128. <div class="title-right">
  129. <span class="material-list-link" @click="showMaterialListDialog">物料清单</span>
  130. </div>
  131. </div>
  132. <!-- 物料列表 -->
  133. <div class="label-list">
  134. <div class="list-header">
  135. <div class="col-no">NO.</div>
  136. <div class="col-label">标签条码</div>
  137. <div class="col-part">物料编码</div>
  138. <div class="col-batch">批次号</div>
  139. <div class="col-qty">标签数量</div>
  140. </div>
  141. <div class="list-body">
  142. <!-- 左滑删除容器 -->
  143. <div
  144. v-for="(item, index) in materialList"
  145. :key="item.id"
  146. class="swipe-item-wrapper"
  147. >
  148. <!-- 可滑动的内容区域 -->
  149. <div
  150. class="swipe-content"
  151. :style="{
  152. transform: item.swipeOffset ? `translateX(${item.swipeOffset}px)` : 'translateX(0)',
  153. transition: item.isAnimating ? 'transform 0.3s ease' : 'none'
  154. }"
  155. @touchstart="handleTouchStart($event, item, index)"
  156. @touchmove="handleTouchMove($event, item)"
  157. @touchend="handleTouchEnd($event, item)"
  158. >
  159. <div class="list-item">
  160. <div class="col-no">{{ materialList.length - index }}</div>
  161. <div class="col-label">{{ item.labelCode }}</div>
  162. <div class="col-part">{{ item.materialCode }}</div>
  163. <div class="col-batch">{{ item.batchNo || '-' }}</div>
  164. <div class="col-qty">{{ item.actualQty }}</div>
  165. </div>
  166. </div>
  167. <!-- 删除按钮隐藏在右侧 -->
  168. <div class="delete-action" @click="handleDeleteItem(item, index)">
  169. <i class="el-icon-delete"></i>
  170. </div>
  171. </div>
  172. <!-- 空状态 -->
  173. <div v-if="materialList.length === 0" class="empty-labels">
  174. <p>暂无入库物料</p>
  175. </div>
  176. </div>
  177. </div>
  178. <!-- 底部操作按钮 -->
  179. <div class="bottom-actions">
  180. <button class="action-btn secondary" @click="confirmInbound">
  181. 确定
  182. </button>
  183. <button class="action-btn secondary" @click="cancelInbound">
  184. 取消
  185. </button>
  186. </div>
  187. <!-- 库位号输入覆盖层 -->
  188. <div v-if="showLocationDialog" class="location-overlay">
  189. <div class="location-modal">
  190. <div class="modal-header">
  191. <span class="modal-title">扫描库位号</span>
  192. </div>
  193. <div class="modal-body">
  194. <div class="location-input-section">
  195. <div class="input-wrapper">
  196. <i class="el-icon-location-outline input-icon"></i>
  197. <input
  198. v-model="locationCode"
  199. placeholder="请扫描库位号"
  200. ref="locationInput"
  201. class="location-input"
  202. />
  203. </div>
  204. </div>
  205. </div>
  206. <div class="modal-footer">
  207. <button class="btn-cancel" @click="cancelLocationInput">取消</button>
  208. <button class="btn-confirm" @click="submitInbound" :disabled="!locationCode.trim()">
  209. 确认上架
  210. </button>
  211. </div>
  212. </div>
  213. </div>
  214. <!-- 物料清单弹窗 -->
  215. <div v-if="showMaterialDialog" class="material-overlay">
  216. <div class="material-modal">
  217. <div class="modal-header">
  218. <span class="modal-title">物料清单</span>
  219. <i class="el-icon-close close-btn" @click="closeMaterialDialog"></i>
  220. </div>
  221. <div class="modal-body">
  222. <!-- 加载状态 -->
  223. <div v-if="materialListLoading" class="loading-container">
  224. <i class="el-icon-loading"></i>
  225. <span>加载中...</span>
  226. </div>
  227. <!-- 物料表格 -->
  228. <div v-else-if="originalMaterialList.length > 0" class="material-table">
  229. <div class="table-header">
  230. <div class="col-no">NO.</div>
  231. <div class="col-material-code">物料编码</div>
  232. <div class="col-part-name">物料名称</div>
  233. <div class="col-required-qty">需求数量</div>
  234. <div class="col-available-qty">可用数量</div>
  235. <div class="col-scans-qty">扫描数量</div>
  236. </div>
  237. <div class="table-body">
  238. <div
  239. v-for="(item, index) in originalMaterialList"
  240. :key="index"
  241. class="table-row"
  242. >
  243. <div class="col-no">{{ index + 1 }}</div>
  244. <div class="col-material-code clickable-part" @click="showStockDialogFn(item)">{{ item.materialCode || item.partNo }}</div>
  245. <div class="col-part-name">
  246. <span class="part-name-text" @click.stop="showPartNameTip(item.materialName, $event)">{{ item.materialName || '-' }}</span>
  247. </div>
  248. <div class="col-required-qty">{{ item.requiredQty || 0 }}</div>
  249. <div class="col-available-qty">{{ item.pickedQty || 0 }}</div>
  250. <div class="col-scans-qty">{{ item.scansQty || 0 }}</div>
  251. </div>
  252. </div>
  253. </div>
  254. <!-- 空数据状态 -->
  255. <div v-else class="empty-material">
  256. <i class="el-icon-document"></i>
  257. <p>暂无物料数据</p>
  258. </div>
  259. </div>
  260. <div class="modal-footer">
  261. <button class="btn-close" @click="closeMaterialDialog">关闭</button>
  262. </div>
  263. </div>
  264. <!-- 物料名称提示框 -->
  265. <div v-if="showPartNameTooltip" class="part-name-tooltip" :style="tooltipStyle" @click.stop>
  266. <div class="tooltip-content">{{ currentPartName }}</div>
  267. </div>
  268. </div>
  269. <!-- 可用库存弹窗 -->
  270. <div v-if="showStockDialog" class="stock-overlay">
  271. <div class="stock-modal">
  272. <div class="modal-header">
  273. <span class="modal-title">可用库存 - {{ currentPartNo }}</span>
  274. <i class="el-icon-close close-btn" @click="closeStockDialog"></i>
  275. </div>
  276. <div class="modal-body">
  277. <div v-if="stockLoading" class="loading-container"><i class="el-icon-loading"></i><span>加载中...</span></div>
  278. <div v-else-if="stockList.length > 0" class="stock-table">
  279. <div class="table-header">
  280. <div class="col-roll-no">标签条码</div>
  281. <div class="col-qty">数量</div>
  282. <div class="col-location">库位</div>
  283. <div class="col-batch">合约号码</div>
  284. <div class="col-date">入库天数</div>
  285. </div>
  286. <div class="table-body">
  287. <div v-for="(item, index) in stockList" :key="index" class="table-row">
  288. <div class="col-roll-no">{{ item.rollNo }}</div>
  289. <div class="col-qty">{{ item.rollQty }} <span class="unit-text">{{ item.um }}</span></div>
  290. <div class="col-location">{{ item.location }}</div>
  291. <div class="col-batch">{{ item.batchNo }}</div>
  292. <div class="col-date">{{ item.daysInStock }}</div>
  293. </div>
  294. </div>
  295. </div>
  296. <div v-else class="empty-stock"><i class="el-icon-box"></i><p>暂无可用库存</p></div>
  297. </div>
  298. <div class="modal-footer"><button class="btn-close" @click="closeStockDialog">关闭</button></div>
  299. </div>
  300. </div>
  301. </div>
  302. </template>
  303. <script>
  304. import { getOtherInboundDetails, getMaterialList, validateLabelWithOtherInbound, confirmOtherInbound, getScannedLabelList, getUserDefaultPrinter } from "@/api/other-inbound/other-inbound.js";
  305. import { getInventoryStock } from "@/api/inbound.js";
  306. import { getCurrentWarehouse } from '@/utils'
  307. import moment from 'moment';
  308. import getLodop from '@/utils/LodopFuncs.js';
  309. import labelPrintTemplates from '@/mixins/labelPrintTemplates.js';
  310. export default {
  311. mixins: [labelPrintTemplates],
  312. data() {
  313. return {
  314. materialCode: '',
  315. actualQty: '',
  316. batchNo: '*',
  317. inboundInfo: {},
  318. materialList: [],
  319. originalMaterialList: [],
  320. materialOptions: [], // 物料下拉选项
  321. currentMaterial: {},
  322. inboundNo: '',
  323. buNo: '',
  324. showMaterialDialog: false,
  325. materialListLoading: false,
  326. showStockDialog: false,
  327. stockList: [],
  328. stockLoading: false,
  329. currentPartNo: '',
  330. showLocationDialog: false,
  331. locationCode: '',
  332. // 左滑删除相关
  333. touchStartX: 0,
  334. touchStartY: 0,
  335. deleteButtonWidth: 80, // 删除按钮宽度
  336. // 物料名称提示框相关
  337. showPartNameTooltip: false,
  338. currentPartName: '',
  339. tooltipStyle: { top: '0px', left: '0px' },
  340. relatedOrderNo: ''
  341. };
  342. },
  343. methods: {
  344. formatDate(date) {
  345. return date ? moment(date).format('YYYY-MM-DD') : '';
  346. },
  347. // ==================== 左滑删除功能 ====================
  348. // 触摸开始
  349. handleTouchStart(event, item, index) {
  350. this.touchStartX = event.touches[0].clientX;
  351. this.touchStartY = event.touches[0].clientY;
  352. // 关闭其他已打开的项
  353. this.materialList.forEach((material, idx) => {
  354. if (idx !== index && material.swipeOffset) {
  355. this.$set(material, 'isAnimating', true);
  356. this.$set(material, 'swipeOffset', 0);
  357. }
  358. });
  359. this.$set(item, 'isAnimating', false);
  360. },
  361. // 触摸移动
  362. handleTouchMove(event, item) {
  363. const touchCurrentX = event.touches[0].clientX;
  364. const touchCurrentY = event.touches[0].clientY;
  365. const deltaX = touchCurrentX - this.touchStartX;
  366. const deltaY = touchCurrentY - this.touchStartY;
  367. // 如果垂直滑动距离大于水平滑动,则认为是滚动操作,不处理左滑
  368. if (Math.abs(deltaY) > Math.abs(deltaX)) {
  369. return;
  370. }
  371. // 阻止默认的滚动行为
  372. if (Math.abs(deltaX) > 10) {
  373. event.preventDefault();
  374. }
  375. // 只允许向左滑动
  376. if (deltaX < 0) {
  377. const offset = Math.max(deltaX, -this.deleteButtonWidth);
  378. this.$set(item, 'swipeOffset', offset);
  379. } else if (item.swipeOffset) {
  380. // 如果已经打开,允许向右滑动关闭
  381. const offset = Math.min(deltaX + (item.swipeOffset || 0), 0);
  382. this.$set(item, 'swipeOffset', offset);
  383. }
  384. },
  385. // 触摸结束
  386. handleTouchEnd(event, item) {
  387. const currentOffset = item.swipeOffset || 0;
  388. this.$set(item, 'isAnimating', true);
  389. // 如果滑动距离超过删除按钮宽度的一半,则完全展开,否则收回
  390. if (currentOffset < -this.deleteButtonWidth / 2) {
  391. this.$set(item, 'swipeOffset', -this.deleteButtonWidth);
  392. } else {
  393. this.$set(item, 'swipeOffset', 0);
  394. }
  395. },
  396. // 删除物料项
  397. handleDeleteItem(item, index) {
  398. this.$confirm('确定要删除该物料吗?', '提示', {
  399. confirmButtonText: '确定',
  400. cancelButtonText: '取消',
  401. type: 'warning'
  402. }).then(() => {
  403. // 调用存储过程删除(operationType: 'D')
  404. const params = {
  405. site: this.inboundInfo.site,
  406. buNo: this.inboundInfo.buNo,
  407. inboundNo: this.inboundNo,
  408. materialCode: item.materialCode,
  409. labelCode: item.labelCode,
  410. actualQty: item.actualQty,
  411. batchNo: item.batchNo,
  412. warehouseId: getCurrentWarehouse(),
  413. operationType: 'D', // D表示删除
  414. documentType: '其他入库'
  415. };
  416. validateLabelWithOtherInbound(params).then(({ data }) => {
  417. if (data && data.code === 0) {
  418. this.$message.success('删除成功');
  419. // 重新加载已扫描标签列表
  420. this.loadScannedLabelList();
  421. } else {
  422. this.$message.error(data.msg || '删除失败');
  423. }
  424. }).catch(error => {
  425. console.error('删除物料失败:', error);
  426. this.$message.error('删除失败');
  427. });
  428. }).catch(() => {
  429. // 用户取消删除,收回滑动
  430. this.$set(item, 'isAnimating', true);
  431. this.$set(item, 'swipeOffset', 0);
  432. });
  433. },
  434. // ==================== 原有功能 ====================
  435. // 处理物料选择
  436. handleMaterialSelect() {
  437. if (!this.materialCode) {
  438. this.currentMaterial = {};
  439. return;
  440. }
  441. // 从选项中找到对应的物料信息
  442. const material = this.materialOptions.find(m => m.materialCode === this.materialCode);
  443. if (material) {
  444. this.currentMaterial = {
  445. materialCode: material.materialCode,
  446. materialName: material.materialName,
  447. unit: material.unit,
  448. requiredQty: material.requiredQty
  449. };
  450. }
  451. },
  452. // 加载物料选项(从入库明细获取)
  453. loadMaterialOptions() {
  454. if (!this.inboundInfo.site || !this.inboundInfo.buNo || !this.inboundNo) {
  455. return;
  456. }
  457. const params = {
  458. site: this.inboundInfo.site,
  459. buNo: this.inboundInfo.buNo,
  460. inboundNo: this.inboundNo,
  461. documentType: '其他入库', // 单据类型
  462. warehouseId: getCurrentWarehouse(), // 当前仓库
  463. relatedOrderNo: this.relatedOrderNo
  464. }
  465. getMaterialList(params).then(({ data }) => {
  466. if (data && data.code === 0) {
  467. this.materialOptions = (data.data || []).map(item => ({
  468. materialCode: item.materialCode,
  469. materialName: item.materialName,
  470. unit: item.unit,
  471. requiredQty: item.requiredQty
  472. }));
  473. } else {
  474. console.error('获取物料选项失败:', data.msg);
  475. this.materialOptions = [];
  476. }
  477. }).catch(error => {
  478. console.error('获取物料选项失败:', error);
  479. this.materialOptions = [];
  480. });
  481. },
  482. // 添加物料到列表(通过存储过程GetScanLabelVerification)
  483. handleAddMaterial() {
  484. if (!this.materialCode.trim()) {
  485. this.$message.warning('请选择物料编码');
  486. return;
  487. }
  488. if (!this.actualQty || this.actualQty <= 0) {
  489. this.$message.warning('请输入有效的入库数量');
  490. return;
  491. }
  492. if (!this.batchNo.trim()) {
  493. this.$message.warning('请输入批次号');
  494. this.$refs.batchInput.focus();
  495. return;
  496. }
  497. // 调用存储过程GetScanLabelVerification
  498. const params = {
  499. site: this.inboundInfo.site,
  500. buNo: this.inboundInfo.buNo,
  501. inboundNo: this.inboundNo,
  502. materialCode: this.materialCode.trim(),
  503. actualQty: parseFloat(this.actualQty),
  504. batchNo: this.batchNo.trim(),
  505. warehouseId: getCurrentWarehouse(),
  506. operationType: 'I', // I表示添加
  507. documentType: '其他入库', // 单据类型
  508. relatedOrderNo: this.inboundInfo.relatedOrderNo,
  509. relatedOrderLineNo: this.inboundInfo.relatedOrderLineNo
  510. };
  511. validateLabelWithOtherInbound(params).then(({ data }) => {
  512. if (data && data.code === 0) {
  513. this.$message.success('操作成功');
  514. // 重新加载已扫描标签列表
  515. this.loadScannedLabelList();
  516. // 清空输入
  517. this.materialCode = '';
  518. this.actualQty = '';
  519. this.batchNo = '*';
  520. this.currentMaterial = {};
  521. } else {
  522. this.$message.error(data.msg || '操作失败');
  523. }
  524. }).catch(error => {
  525. console.error('添加物料失败:', error);
  526. this.$message.error('操作失败');
  527. });
  528. },
  529. // 确认入库
  530. confirmInbound() {
  531. if (this.materialList.length === 0) {
  532. this.$message.warning('请先添加入库物料');
  533. return;
  534. }
  535. // 显示库位号输入对话框
  536. this.showLocationDialog = true;
  537. this.locationCode = '';
  538. // 聚焦到库位号输入框
  539. this.$nextTick(() => {
  540. if (this.$refs.locationInput) {
  541. this.$refs.locationInput.focus();
  542. }
  543. });
  544. },
  545. // 取消库位号输入
  546. cancelLocationInput() {
  547. this.showLocationDialog = false;
  548. this.locationCode = '';
  549. },
  550. // 提交入库(通过存储过程GetSaveLabelVerification)
  551. async submitInbound() {
  552. if (!this.locationCode.trim()) {
  553. this.$message.warning('请输入库位号');
  554. return;
  555. }
  556. const params = {
  557. site: this.inboundInfo.site,
  558. buNo: this.inboundInfo.buNo,
  559. inboundNo: this.inboundNo,
  560. locationCode: this.locationCode.trim(),
  561. documentType: '其他入库', // 单据类型
  562. relatedOrderNo: this.inboundInfo.relatedOrderNo,
  563. relatedOrderLineNo: this.inboundInfo.relatedOrderLineNo
  564. }
  565. try {
  566. const { data } = await confirmOtherInbound(params);
  567. if (data && data.code === 0) {
  568. this.$message.success('入库成功');
  569. this.showLocationDialog = false;
  570. // 自动打印标签
  571. const printList = data.printList || [];
  572. if (printList.length > 0) {
  573. await this.printLabelsWithTemplate(printList);
  574. }
  575. this.$router.back();
  576. } else {
  577. this.$message.error(data.msg || '入库失败');
  578. }
  579. } catch (error) {
  580. console.error('入库失败:', error);
  581. this.$message.error('入库失败');
  582. }
  583. },
  584. /**
  585. * 获取当前用户配置的默认打印机
  586. * @param {String} labelNo - 标签模板编号A001/A002/A003
  587. * @returns {Object} 打印机配置 {printerName, printerIp}
  588. */
  589. async fetchUserDefaultPrinter(labelNo) {
  590. try {
  591. const params = {
  592. userName: localStorage.getItem('userName'),
  593. labelNo: labelNo || ''
  594. };
  595. const { data } = await getUserDefaultPrinter(params);
  596. if (data && data.code === 0 && data.printerName) {
  597. return {
  598. printerName: data.printerName,
  599. printerIp: data.printerIp,
  600. labelNo: data.labelNo
  601. };
  602. }
  603. return null;
  604. } catch (error) {
  605. console.error('获取用户打印机配置失败:', error);
  606. return null;
  607. }
  608. },
  609. /**
  610. * 使用模板打印标签
  611. * @param {Array} printList - 打印数据列表存储过程UspPartLabelTemplate返回
  612. */
  613. async printLabelsWithTemplate(printList) {
  614. try {
  615. // 1. 获取 LODOP 打印控件
  616. const LODOP = getLodop();
  617. if (!LODOP) {
  618. console.warn('无法连接到打印控件,跳过打印');
  619. this.$message.warning('无法连接到打印控件,请确保已安装并启动打印服务');
  620. return;
  621. }
  622. // 2. 检测打印机数量
  623. const printerCount = LODOP.GET_PRINTER_COUNT();
  624. if (printerCount === 0) {
  625. console.warn('未检测到打印机,跳过打印');
  626. this.$message.warning('未检测到打印机,请检查打印机连接');
  627. return;
  628. }
  629. // 3. 获取用户配置的默认打印机
  630. // 使用第一个标签的 labelNo 来查询打印机配置
  631. const firstLabelNo = printList.length > 0 ? printList[0].labelNo : '';
  632. const printerConfig = await this.fetchUserDefaultPrinter(firstLabelNo);
  633. let printerName = null;
  634. if (printerConfig && printerConfig.printerName) {
  635. printerName = printerConfig.printerName;
  636. console.log('使用用户配置的打印机:', printerName);
  637. } else {
  638. console.warn('未找到用户打印机配置,跳过打印');
  639. this.$message.warning('未配置用户打印机,请在系统中配置默认打印机后再打印');
  640. return;
  641. }
  642. // 4. 执行打印
  643. await this.executePrintWithTemplate(LODOP, printList, printerName);
  644. this.$message.success('标签打印任务已发送!');
  645. } catch (error) {
  646. console.error('模板打印失败:', error);
  647. this.$message.warning('标签打印失败,请手动打印');
  648. }
  649. },
  650. /**
  651. * 执行模板打印
  652. * @param {Object} LODOP - 打印控件对象
  653. * @param {Array} printDataList - 打印数据列表
  654. * @param {String} printerName - 用户配置的打印机名称可选
  655. */
  656. async executePrintWithTemplate(LODOP, printDataList, printerName) {
  657. console.log('开始打印,标签数量:', printDataList.length, '打印机:', printerName || '默认', '标签数据:', printDataList);
  658. // 循环打印每个标签(每个标签单独打印一次)
  659. for (let i = 0; i < printDataList.length; i++) {
  660. const printData = printDataList[i];
  661. // 获取标签模板编号(存储过程返回)
  662. const labelNo = printData.labelNo;
  663. // 每个标签单独初始化一个打印任务
  664. LODOP.PRINT_INIT('其他入库标签打印_' + (i + 1));
  665. // 设置用户配置的打印机(如果有)
  666. if (printerName) {
  667. LODOP.SET_PRINTER_INDEX(printerName);
  668. }
  669. // 设置打印模式
  670. LODOP.SET_PRINT_MODE("PRINT_NOCOLLATE", true);
  671. // 根据 labelNo 调用不同的打印方法(来自 labelPrintTemplates mixin)
  672. // 注意:不需要 NEWPAGE,因为每个标签是独立的打印任务
  673. if (labelNo === 'A001') {
  674. await this.printLabelA001(LODOP, printData, false);
  675. } else if (labelNo === 'A002') {
  676. this.printLabelA002(LODOP, printData, false);
  677. } else if (labelNo === 'A003') {
  678. this.printLabelA003(LODOP, printData, false);
  679. } else if (labelNo === 'A004') {
  680. this.printLabelA004(LODOP, printData, false);
  681. }
  682. // 执行打印
  683. //LODOP.PREVIEW();
  684. LODOP.PRINT();
  685. console.log(`${i + 1}张标签已发送打印, 打印机: ${printerName || '默认'}, 标签条码: ${printData.rollNo}`);
  686. }
  687. },
  688. // 取消入库
  689. cancelInbound() {
  690. if (this.materialList.length > 0) {
  691. this.$confirm('取消后将清空已添加的物料,确定取消吗?', '提示', {
  692. confirmButtonText: '确定',
  693. cancelButtonText: '继续操作',
  694. type: 'warning'
  695. }).then(() => {
  696. this.$router.back();
  697. }).catch(() => {
  698. // 用户选择继续操作
  699. });
  700. } else {
  701. this.$router.back();
  702. }
  703. },
  704. // 显示物料清单弹窗
  705. showMaterialListDialog() {
  706. this.showMaterialDialog = true;
  707. this.loadOriginalMaterialList();
  708. },
  709. // 加载原始物料清单
  710. loadOriginalMaterialList() {
  711. if (!this.inboundInfo.site || !this.inboundInfo.buNo || !this.inboundNo) {
  712. this.$message.error('缺少必要参数,无法获取物料清单');
  713. return;
  714. }
  715. this.materialListLoading = true;
  716. const params = {
  717. site: this.inboundInfo.site,
  718. buNo: this.inboundInfo.buNo,
  719. inboundNo: this.inboundNo,
  720. warehouseId: getCurrentWarehouse() // 当前仓库
  721. };
  722. getMaterialList(params).then(({ data }) => {
  723. this.materialListLoading = false;
  724. if (data && data.code === 0) {
  725. this.originalMaterialList = data.data || [];
  726. } else {
  727. this.$message.error(data.msg || '获取物料清单失败');
  728. this.originalMaterialList = [];
  729. }
  730. }).catch(error => {
  731. this.materialListLoading = false;
  732. console.error('获取物料清单失败:', error);
  733. this.$message.error('获取物料清单失败');
  734. this.originalMaterialList = [];
  735. });
  736. },
  737. // 关闭物料清单弹窗
  738. closeMaterialDialog() {
  739. this.showMaterialDialog = false;
  740. this.hidePartNameTip();
  741. },
  742. // 显示物料名称提示框
  743. showPartNameTip(partName, event) {
  744. if (!partName || partName === '-') return;
  745. const rect = event.target.getBoundingClientRect();
  746. this.tooltipStyle = { top: (rect.top - 5) + 'px', left: rect.left + 'px' };
  747. this.currentPartName = partName;
  748. this.showPartNameTooltip = true;
  749. setTimeout(() => { document.addEventListener('click', this.hidePartNameTip); }, 0);
  750. },
  751. // 隐藏物料名称提示框
  752. hidePartNameTip() {
  753. this.showPartNameTooltip = false;
  754. this.currentPartName = '';
  755. document.removeEventListener('click', this.hidePartNameTip);
  756. },
  757. showStockDialogFn(item) {
  758. this.currentPartNo = item.materialCode || item.partNo;
  759. this.showStockDialog = true;
  760. this.loadInventoryStock(this.currentPartNo);
  761. },
  762. loadInventoryStock(partNo) {
  763. this.stockLoading = true;
  764. const params = {
  765. site: this.inboundInfo.site,
  766. notifyNo: this.inboundNo,
  767. notifyType: '其他入库',
  768. orderNo: '',
  769. orderLineNo: '',
  770. partNo: partNo,
  771. warehouseId: localStorage.getItem('warehouseId') || ''
  772. };
  773. getInventoryStock(params).then(({ data }) => {
  774. this.stockLoading = false;
  775. if (data && data.code === 0) { this.stockList = data.data || []; }
  776. else { this.$message.error(data.msg || '获取可用库存失败'); this.stockList = []; }
  777. }).catch(error => { this.stockLoading = false; this.$message.error('获取可用库存失败'); this.stockList = []; });
  778. },
  779. closeStockDialog() {
  780. this.showStockDialog = false;
  781. this.stockList = [];
  782. this.currentPartNo = '';
  783. },
  784. // 加载入库单详情
  785. loadInboundDetails() {
  786. const params = {
  787. inboundNo: this.inboundNo,
  788. buNo: this.buNo,
  789. warehouseId: getCurrentWarehouse(),
  790. site: localStorage.getItem('site'),
  791. relatedOrderNo: this.relatedOrderNo
  792. };
  793. getOtherInboundDetails(params).then(({ data }) => {
  794. if (data && data.code === 0) {
  795. this.inboundInfo = data.data;
  796. // 加载入库单详情成功后,加载物料选项和已扫描标签列表
  797. this.loadMaterialOptions();
  798. this.loadScannedLabelList();
  799. } else {
  800. this.$message.error(data.msg || '获取入库单详情失败');
  801. }
  802. }).catch(error => {
  803. console.error('获取入库单详情失败:', error);
  804. this.$message.error('获取入库单详情失败');
  805. });
  806. },
  807. // 加载已扫描标签列表(从ScannedRollTempTable缓存表)
  808. loadScannedLabelList() {
  809. if (!this.inboundInfo.site || !this.inboundInfo.buNo || !this.inboundNo) {
  810. return;
  811. }
  812. const params = {
  813. site: this.inboundInfo.site,
  814. buNo: this.inboundInfo.buNo,
  815. inboundNo: this.inboundNo,
  816. documentType: '其他入库' // 单据类型
  817. };
  818. getScannedLabelList(params).then(({ data }) => {
  819. if (data && data.code === 0) {
  820. // 将查询结果转换为materialList格式
  821. this.materialList = (data.data || []).map((item, index) => ({
  822. id: Date.now() + index,
  823. labelCode: item.labelCode,
  824. materialCode: item.materialCode,
  825. actualQty: item.actualQty,
  826. batchNo: item.batchNo,
  827. swipeOffset: 0, // 左滑偏移量
  828. isAnimating: false // 是否正在动画
  829. }));
  830. // 更新入库信息卡片的统计数据
  831. this.inboundInfo.totalLabels = this.materialList.length;
  832. this.inboundInfo.totalQty = this.materialList.reduce((sum, item) => sum + parseFloat(item.actualQty || 0), 0);
  833. } else {
  834. console.error('获取已扫描标签列表失败:', data.msg);
  835. }
  836. }).catch(error => {
  837. console.error('获取已扫描标签列表失败:', error);
  838. });
  839. }
  840. },
  841. mounted() {
  842. // 获取路由参数
  843. this.inboundNo = this.$route.params.inboundNo;
  844. this.buNo = this.$route.params.buNo;
  845. this.relatedOrderNo = this.$route.params.relatedOrderNo;
  846. if (!this.inboundNo || !this.buNo) {
  847. this.$message.error('参数错误');
  848. this.$router.back();
  849. return;
  850. }
  851. // 初始化字段
  852. this.materialCode = '';
  853. this.actualQty = '';
  854. this.batchNo = '*';
  855. // 加载入库单详情
  856. this.loadInboundDetails();
  857. }
  858. };
  859. </script>
  860. <style scoped>
  861. /* 复用其他出库的样式,保持一致性 */
  862. .pda-container {
  863. width: 100vw;
  864. height: 100vh;
  865. display: flex;
  866. flex-direction: column;
  867. background: #f5f5f5;
  868. overflow-y: auto;
  869. }
  870. /* 头部栏 */
  871. .header-bar {
  872. display: flex;
  873. justify-content: space-between;
  874. align-items: center;
  875. padding: 8px 16px;
  876. background: #17B3A3;
  877. color: white;
  878. height: 40px;
  879. min-height: 40px;
  880. }
  881. .header-left {
  882. display: flex;
  883. align-items: center;
  884. cursor: pointer;
  885. font-size: 16px;
  886. font-weight: 500;
  887. }
  888. .header-left i {
  889. margin-right: 8px;
  890. font-size: 18px;
  891. }
  892. .header-right {
  893. cursor: pointer;
  894. font-size: 16px;
  895. font-weight: 500;
  896. }
  897. /* 物料信息卡片 */
  898. .material-info-card {
  899. background: white;
  900. margin: 4px 16px;
  901. padding: 6px 20px;
  902. border-radius: 8px;
  903. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  904. border: 1px solid #f0f0f0;
  905. }
  906. /* 卡片第一行:入库单号 */
  907. .card-row-first {
  908. margin-bottom: 8px;
  909. }
  910. /* 卡片第二行:关联单号+行号(左) + 标签张数+物料总数(右) */
  911. .card-row-second {
  912. display: flex;
  913. justify-content: space-between;
  914. align-items: flex-end;
  915. gap: 8px;
  916. }
  917. .card-row-left {
  918. display: flex;
  919. gap: 12px;
  920. align-items: flex-end;
  921. flex: 1;
  922. min-width: 0;
  923. }
  924. .card-row-left .title-item {
  925. flex: 1;
  926. }
  927. .card-row-right {
  928. display: flex;
  929. gap: 10px;
  930. align-items: flex-end;
  931. flex-shrink: 0;
  932. }
  933. .title-item {
  934. display: flex;
  935. flex-direction: column;
  936. min-width: 0;
  937. }
  938. .title-label {
  939. font-size: 11px;
  940. color: #999;
  941. margin-bottom: 4px;
  942. white-space: nowrap;
  943. }
  944. .title-value {
  945. font-size: 15px;
  946. font-weight: bold;
  947. color: #333;
  948. word-break: break-all;
  949. }
  950. .title-value-sm {
  951. font-size: 13px;
  952. font-weight: 600;
  953. }
  954. .detail-item {
  955. text-align: center;
  956. }
  957. .detail-label {
  958. font-size: 11px;
  959. color: #999;
  960. margin-bottom: 4px;
  961. font-weight: normal;
  962. line-height: 1.2;
  963. white-space: nowrap;
  964. }
  965. .detail-value {
  966. font-size: 13px;
  967. color: #333;
  968. font-weight: 500;
  969. line-height: 1.2;
  970. }
  971. .detail-value .qualified {
  972. color: #17B3A3;
  973. font-weight: 500;
  974. }
  975. .detail-value .total {
  976. color: #333;
  977. font-weight: 500;
  978. }
  979. .detail-value .total::before {
  980. content: '/';
  981. color: #333;
  982. }
  983. /* 物料输入区域 */
  984. .input-section {
  985. background: white;
  986. margin: 8px 16px;
  987. padding: 12px 16px 16px;
  988. border-radius: 10px;
  989. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  990. border: 1px solid #f0f0f0;
  991. }
  992. /* 物料信息网格布局 */
  993. .material-input-grid {
  994. display: flex;
  995. flex-direction: column;
  996. gap: 10px;
  997. margin-bottom: 12px;
  998. }
  999. /* 第一行:物料编码、需求数量、入库数量、批次号 */
  1000. .input-row-top {
  1001. display: flex;
  1002. align-items: flex-end;
  1003. gap: 10px;
  1004. }
  1005. /* 第二行:单位(左)+ 物料名称 */
  1006. .input-row-bottom {
  1007. display: flex;
  1008. align-items: flex-end;
  1009. gap: 10px;
  1010. width: 100%;
  1011. margin-top: 2px;
  1012. }
  1013. .input-item {
  1014. display: flex;
  1015. flex-direction: column;
  1016. flex: 1;
  1017. }
  1018. .material-code-item {
  1019. flex: 1.6;
  1020. }
  1021. .required-qty-item {
  1022. flex: 1;
  1023. min-width: 60px;
  1024. }
  1025. .quantity-item {
  1026. flex: 1;
  1027. }
  1028. .batch-item {
  1029. flex: 1;
  1030. }
  1031. /* 第二行单位:固定小宽度,居左 */
  1032. .unit-item-bottom {
  1033. display: flex;
  1034. flex-direction: column;
  1035. flex: 0 0 64px;
  1036. width: 64px;
  1037. }
  1038. .material-name-area {
  1039. flex: 1;
  1040. min-width: 0;
  1041. }
  1042. .input-label {
  1043. font-size: 13px;
  1044. color: #666;
  1045. margin-bottom: 6px;
  1046. text-align: left;
  1047. font-weight: 500;
  1048. line-height: 1.2;
  1049. }
  1050. .input-field {
  1051. width: 100%;
  1052. }
  1053. .input-field ::v-deep .el-input__inner {
  1054. font-size: 13px;
  1055. padding: 0 10px;
  1056. border: 1px solid #17B3A3;
  1057. border-radius: 6px;
  1058. height: 44px;
  1059. text-align: center;
  1060. font-weight: 500;
  1061. }
  1062. /* disabled状态的输入框样式 */
  1063. .input-field ::v-deep .el-input__inner[disabled] {
  1064. background-color: #f5f7fa;
  1065. border-color: #d0d4d9;
  1066. color: #333;
  1067. cursor: not-allowed;
  1068. }
  1069. /* 添加物料按钮区域 */
  1070. .add-material-section {
  1071. display: flex;
  1072. justify-content: center;
  1073. padding-top: 4px;
  1074. }
  1075. .add-material-btn {
  1076. background: linear-gradient(135deg, #17B3A3 0%, #0d8f7f 100%);
  1077. color: white;
  1078. border: none;
  1079. border-radius: 5px;
  1080. padding: 6px 14px;
  1081. cursor: pointer;
  1082. display: flex;
  1083. align-items: center;
  1084. justify-content: center;
  1085. gap: 5px;
  1086. font-size: 13px;
  1087. font-weight: 600;
  1088. transition: all 0.3s ease;
  1089. min-width: 90px;
  1090. box-shadow: 0 2px 6px rgba(23, 179, 163, 0.2);
  1091. letter-spacing: 0.2px;
  1092. }
  1093. .add-material-btn:hover {
  1094. background: linear-gradient(135deg, #0d8f7f 0%, #17B3A3 100%);
  1095. transform: translateY(-2px);
  1096. box-shadow: 0 6px 16px rgba(23, 179, 163, 0.35);
  1097. }
  1098. .add-material-btn:active {
  1099. transform: translateY(0);
  1100. box-shadow: 0 3px 8px rgba(23, 179, 163, 0.3);
  1101. }
  1102. .add-material-btn i {
  1103. font-size: 14px;
  1104. font-weight: bold;
  1105. }
  1106. .field-area {
  1107. margin-bottom: 0;
  1108. }
  1109. .field-title {
  1110. font-size: 13px;
  1111. color: #666;
  1112. margin-bottom: 6px;
  1113. font-weight: 500;
  1114. text-align: left;
  1115. line-height: 1.2;
  1116. }
  1117. .field-input {
  1118. width: 100%;
  1119. }
  1120. .field-input ::v-deep .el-input__inner {
  1121. font-size: 13px;
  1122. padding: 0 10px;
  1123. border: 1px solid #dcdfe6;
  1124. border-radius: 6px;
  1125. height: 44px;
  1126. }
  1127. .info-box {
  1128. background: #f5f7fa;
  1129. padding: 0 10px;
  1130. border-radius: 6px;
  1131. font-size: 13px;
  1132. color: #333;
  1133. height: 44px;
  1134. display: flex;
  1135. align-items: center;
  1136. justify-content: center;
  1137. border: 1px solid #d0d4d9;
  1138. font-weight: 500;
  1139. line-height: 1.2;
  1140. text-align: center;
  1141. }
  1142. /* 物料名称信息框 - 与其他输入框等高 */
  1143. .material-name-area .info-box {
  1144. height: 44px;
  1145. padding: 0 12px;
  1146. justify-content: flex-start;
  1147. text-align: left;
  1148. overflow: hidden;
  1149. text-overflow: ellipsis;
  1150. white-space: nowrap;
  1151. }
  1152. .numInput ::v-deep .el-input__inner {
  1153. font-size: 13px;
  1154. padding: 0 10px;
  1155. border: 1px solid #17B3A3;
  1156. border-radius: 6px;
  1157. height: 44px;
  1158. text-align: center;
  1159. font-weight: 500;
  1160. }
  1161. /* 区域标题 */
  1162. .section-title {
  1163. display: flex;
  1164. align-items: center;
  1165. justify-content: space-between;
  1166. padding: 6px 8px;
  1167. background: white;
  1168. margin: 0 16px;
  1169. margin-top: 4px;
  1170. border-radius: 8px 8px 0 0;
  1171. border-bottom: 2px solid #17B3A3;
  1172. }
  1173. .title-left {
  1174. display: flex;
  1175. align-items: center;
  1176. }
  1177. .title-left i {
  1178. color: #17B3A3;
  1179. font-size: 16px;
  1180. margin-right: 8px;
  1181. }
  1182. .title-left span {
  1183. color: #17B3A3;
  1184. font-size: 14px;
  1185. font-weight: 500;
  1186. }
  1187. /* 物料列表 */
  1188. .label-list {
  1189. background: white;
  1190. margin: 0 16px 12px;
  1191. border-radius: 0 0 8px 8px;
  1192. overflow: hidden;
  1193. flex: 1;
  1194. display: flex;
  1195. flex-direction: column;
  1196. max-height: 300px;
  1197. }
  1198. .label-list .list-body {
  1199. flex: 1;
  1200. overflow-y: auto;
  1201. max-height: 250px;
  1202. }
  1203. .label-list .list-body::-webkit-scrollbar {
  1204. width: 4px;
  1205. }
  1206. .label-list .list-body::-webkit-scrollbar-track {
  1207. background: #f1f1f1;
  1208. border-radius: 2px;
  1209. }
  1210. .label-list .list-body::-webkit-scrollbar-thumb {
  1211. background: #17B3A3;
  1212. border-radius: 2px;
  1213. }
  1214. .label-list .list-body::-webkit-scrollbar-thumb:hover {
  1215. background: #0d8f7f;
  1216. }
  1217. /* 左滑删除容器 */
  1218. .swipe-item-wrapper {
  1219. position: relative;
  1220. overflow: hidden;
  1221. background: white;
  1222. }
  1223. /* 可滑动的内容区域 */
  1224. .swipe-content {
  1225. position: relative;
  1226. z-index: 2;
  1227. background: white;
  1228. touch-action: pan-y; /* 允许垂直滚动 */
  1229. }
  1230. /* 删除按钮(隐藏在右侧) */
  1231. .delete-action {
  1232. position: absolute;
  1233. right: 0;
  1234. top: 0;
  1235. bottom: 0;
  1236. width: 80px;
  1237. background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
  1238. display: flex;
  1239. flex-direction: column;
  1240. align-items: center;
  1241. justify-content: center;
  1242. color: white;
  1243. cursor: pointer;
  1244. z-index: 1;
  1245. transition: background 0.2s ease;
  1246. }
  1247. .delete-action:active {
  1248. background: linear-gradient(135deg, #ee5a52 0%, #ff6b6b 100%);
  1249. }
  1250. .delete-action i {
  1251. font-size: 20px;
  1252. margin-bottom: 4px;
  1253. }
  1254. .delete-action span {
  1255. font-size: 12px;
  1256. font-weight: 500;
  1257. }
  1258. .list-header {
  1259. display: flex;
  1260. background: #f8f9fa;
  1261. padding: 12px 8px;
  1262. border-bottom: 1px solid #e0e0e0;
  1263. font-size: 12px;
  1264. color: #666;
  1265. font-weight: 500;
  1266. }
  1267. .list-item {
  1268. display: flex;
  1269. padding: 12px 8px;
  1270. border-bottom: 1px solid #f0f0f0;
  1271. font-size: 12px;
  1272. color: #333;
  1273. }
  1274. .list-item:last-child {
  1275. border-bottom: none;
  1276. }
  1277. .col-no {
  1278. width: 20px;
  1279. text-align: center;
  1280. }
  1281. .col-label {
  1282. flex: 2;
  1283. text-align: center;
  1284. }
  1285. .col-part {
  1286. flex: 2;
  1287. text-align: center;
  1288. }
  1289. .col-batch {
  1290. flex: 1.5;
  1291. text-align: center;
  1292. }
  1293. .col-qty {
  1294. width: 60px;
  1295. text-align: center;
  1296. }
  1297. .empty-labels {
  1298. padding: 40px 20px;
  1299. text-align: center;
  1300. color: #999;
  1301. }
  1302. .empty-labels p {
  1303. margin: 0;
  1304. font-size: 14px;
  1305. }
  1306. /* 底部操作按钮 */
  1307. .bottom-actions {
  1308. display: flex;
  1309. padding: 16px;
  1310. gap: 20px;
  1311. background: white;
  1312. margin-top: auto;
  1313. }
  1314. .action-btn {
  1315. flex: 1;
  1316. padding: 12px;
  1317. border: 1px solid #17B3A3;
  1318. background: white;
  1319. color: #17B3A3;
  1320. border-radius: 20px;
  1321. font-size: 14px;
  1322. cursor: pointer;
  1323. transition: all 0.2s ease;
  1324. }
  1325. .action-btn:hover {
  1326. background: #17B3A3;
  1327. color: white;
  1328. }
  1329. .action-btn:active {
  1330. transform: scale(0.98);
  1331. }
  1332. /* 物料清单弹窗样式 */
  1333. .material-overlay {
  1334. position: fixed;
  1335. top: 0;
  1336. left: 0;
  1337. right: 0;
  1338. bottom: 0;
  1339. background: rgba(0, 0, 0, 0.5);
  1340. z-index: 9999;
  1341. display: flex;
  1342. align-items: center;
  1343. justify-content: center;
  1344. padding: 20px;
  1345. }
  1346. .material-modal {
  1347. background: white;
  1348. border-radius: 12px;
  1349. width: 100%;
  1350. max-width: 800px;
  1351. max-height: 80vh;
  1352. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  1353. overflow: hidden;
  1354. display: flex;
  1355. flex-direction: column;
  1356. }
  1357. .material-modal .modal-header {
  1358. background: #17B3A3;
  1359. color: white;
  1360. padding: 5px 16px;
  1361. display: flex;
  1362. justify-content: space-between;
  1363. align-items: center;
  1364. min-height: 28px;
  1365. }
  1366. .close-btn {
  1367. font-size: 16px;
  1368. cursor: pointer;
  1369. color: white;
  1370. transition: color 0.2s ease;
  1371. padding: 4px;
  1372. display: flex;
  1373. align-items: center;
  1374. justify-content: center;
  1375. }
  1376. .close-btn:hover {
  1377. color: #e0e0e0;
  1378. }
  1379. .material-modal .modal-title {
  1380. font-size: 16px;
  1381. font-weight: 500;
  1382. margin: 0;
  1383. line-height: 1.2;
  1384. }
  1385. .material-modal .modal-body {
  1386. flex: 1;
  1387. overflow: auto;
  1388. padding: 0;
  1389. }
  1390. .material-table {
  1391. min-width: 500px;
  1392. width: max-content;
  1393. }
  1394. .table-header {
  1395. display: flex;
  1396. background: #f8f9fa;
  1397. padding: 10px 6px;
  1398. border-bottom: 2px solid #17B3A3;
  1399. font-size: 12px;
  1400. color: #333;
  1401. font-weight: 600;
  1402. position: sticky;
  1403. top: 0;
  1404. z-index: 1;
  1405. }
  1406. .table-body {
  1407. /* 垂直滚动由modal-body处理 */
  1408. }
  1409. .table-row {
  1410. display: flex;
  1411. padding: 10px 6px;
  1412. border-bottom: 1px solid #f0f0f0;
  1413. font-size: 12px;
  1414. color: #333;
  1415. transition: background-color 0.2s ease;
  1416. }
  1417. .table-row:hover {
  1418. background-color: #f8f9fa;
  1419. }
  1420. .table-row:last-child {
  1421. border-bottom: none;
  1422. }
  1423. .material-table .col-no {
  1424. width: 25px;
  1425. text-align: center;
  1426. flex-shrink: 0;
  1427. font-size: 12px;
  1428. }
  1429. .material-table .col-material-code {
  1430. flex: 1.8;
  1431. text-align: center;
  1432. min-width: 100px;
  1433. font-size: 12px;
  1434. word-break: break-all;
  1435. }
  1436. .clickable-part { color: #17B3A3; font-weight: 500; cursor: pointer; text-decoration: underline; }
  1437. .clickable-part:hover { color: #0d8f7f; }
  1438. .stock-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; }
  1439. .stock-modal { background: white; border-radius: 12px; width: 100%; max-width: 800px; max-height: 80vh; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column; }
  1440. .stock-modal .modal-header { background: #17B3A3; color: white; padding: 5px 16px; display: flex; justify-content: space-between; align-items: center; min-height: 28px; }
  1441. .stock-modal .modal-body { flex: 1; overflow: auto; padding: 0; }
  1442. .stock-table { min-width: 500px; width: max-content; }
  1443. .stock-table .table-header { display: flex; background: #f8f9fa; padding: 10px 6px; border-bottom: 2px solid #17B3A3; font-size: 12px; color: #333; font-weight: 600; position: sticky; top: 0; }
  1444. .stock-table .table-body { /* 垂直滚动由modal-body处理 */ }
  1445. .stock-table .table-row { display: flex; padding: 10px 6px; border-bottom: 1px solid #f0f0f0; font-size: 12px; color: #333; }
  1446. .stock-table .table-row:hover { background-color: #f8f9fa; }
  1447. .stock-table .col-roll-no { flex: 1.5; text-align: center; min-width: 100px; }
  1448. .stock-table .col-qty { flex: 0.8; text-align: center; min-width: 60px; }
  1449. .unit-text { color: #999; font-size: 11px; margin-left: 2px; }
  1450. .stock-table .col-location { flex: 0.8; text-align: center; min-width: 60px; }
  1451. .stock-table .col-batch { flex: 1; text-align: center; min-width: 80px; }
  1452. .stock-table .col-date { flex: 1; text-align: center; min-width: 80px; }
  1453. .stock-modal .modal-footer { padding: 15px 20px; display: flex; justify-content: center; border-top: 1px solid #f0f0f0; }
  1454. .empty-stock { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #999; }
  1455. .empty-stock i { font-size: 48px; margin-bottom: 16px; color: #ddd; }
  1456. .empty-stock p { margin: 0; font-size: 14px; }
  1457. .material-table .col-required-qty {
  1458. flex: 0.8;
  1459. text-align: center;
  1460. min-width: 65px;
  1461. font-size: 12px;
  1462. }
  1463. .material-table .col-available-qty {
  1464. flex: 0.8;
  1465. text-align: center;
  1466. min-width: 65px;
  1467. font-size: 12px;
  1468. }
  1469. .material-table .col-scans-qty {
  1470. flex: 0.8;
  1471. text-align: center;
  1472. min-width: 65px;
  1473. font-size: 12px;
  1474. }
  1475. /* 物料名称列样式 */
  1476. .material-table .col-part-name {
  1477. flex: 1.2;
  1478. text-align: left;
  1479. min-width: 80px;
  1480. font-size: 12px;
  1481. overflow: hidden;
  1482. }
  1483. .material-table .col-part-name .part-name-text {
  1484. display: block;
  1485. white-space: nowrap;
  1486. overflow: hidden;
  1487. text-overflow: ellipsis;
  1488. max-width: 100%;
  1489. color: #17B3A3;
  1490. cursor: pointer;
  1491. }
  1492. .material-table .col-part-name .part-name-text:active {
  1493. color: #0d8f7f;
  1494. }
  1495. /* 物料名称提示框样式 */
  1496. .part-name-tooltip {
  1497. position: fixed;
  1498. z-index: 10001;
  1499. transform: translateY(-100%);
  1500. max-width: 200px;
  1501. animation: tooltipFadeIn 0.2s ease;
  1502. }
  1503. @keyframes tooltipFadeIn {
  1504. from { opacity: 0; transform: translateY(-100%) translateY(5px); }
  1505. to { opacity: 1; transform: translateY(-100%) translateY(0); }
  1506. }
  1507. .part-name-tooltip .tooltip-content {
  1508. background: #303133;
  1509. color: #fff;
  1510. padding: 8px 12px;
  1511. border-radius: 4px;
  1512. font-size: 12px;
  1513. line-height: 1.4;
  1514. word-break: break-all;
  1515. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
  1516. }
  1517. .part-name-tooltip::after {
  1518. content: '';
  1519. position: absolute;
  1520. bottom: -6px;
  1521. left: 15px;
  1522. border-width: 6px 6px 0 6px;
  1523. border-style: solid;
  1524. border-color: #303133 transparent transparent transparent;
  1525. }
  1526. .material-modal .modal-footer {
  1527. padding: 15px 20px;
  1528. display: flex;
  1529. justify-content: center;
  1530. border-top: 1px solid #f0f0f0;
  1531. }
  1532. .btn-close {
  1533. padding: 10px 20px;
  1534. border-radius: 6px;
  1535. font-size: 14px;
  1536. cursor: pointer;
  1537. transition: all 0.2s;
  1538. border: 1px solid #17B3A3;
  1539. background: white;
  1540. color: #17B3A3;
  1541. }
  1542. .btn-close:hover {
  1543. background: #17B3A3;
  1544. color: white;
  1545. }
  1546. /* 加载状态样式 */
  1547. .loading-container {
  1548. display: flex;
  1549. flex-direction: column;
  1550. align-items: center;
  1551. justify-content: center;
  1552. padding: 60px 20px;
  1553. color: #666;
  1554. }
  1555. .loading-container i {
  1556. font-size: 24px;
  1557. margin-bottom: 12px;
  1558. color: #17B3A3;
  1559. }
  1560. .loading-container span {
  1561. font-size: 14px;
  1562. }
  1563. /* 空数据状态样式 */
  1564. .empty-material {
  1565. display: flex;
  1566. flex-direction: column;
  1567. align-items: center;
  1568. justify-content: center;
  1569. padding: 60px 20px;
  1570. color: #999;
  1571. }
  1572. .empty-material i {
  1573. font-size: 48px;
  1574. margin-bottom: 16px;
  1575. color: #ddd;
  1576. }
  1577. .empty-material p {
  1578. margin: 0;
  1579. font-size: 14px;
  1580. }
  1581. /* 库位号覆盖层样式 */
  1582. .location-overlay {
  1583. position: fixed;
  1584. top: 0;
  1585. left: 0;
  1586. right: 0;
  1587. bottom: 0;
  1588. background: rgba(0, 0, 0, 0.5);
  1589. z-index: 9999;
  1590. display: flex;
  1591. align-items: center;
  1592. justify-content: center;
  1593. padding: 20px;
  1594. }
  1595. .location-modal {
  1596. background: white;
  1597. border-radius: 12px;
  1598. width: 100%;
  1599. max-width: 400px;
  1600. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  1601. overflow: hidden;
  1602. }
  1603. .location-modal .modal-header {
  1604. background: #17B3A3;
  1605. color: white;
  1606. padding: 12px 20px;
  1607. text-align: center;
  1608. }
  1609. .location-modal .modal-title {
  1610. font-size: 16px;
  1611. font-weight: 500;
  1612. }
  1613. .location-modal .modal-body {
  1614. padding: 20px;
  1615. }
  1616. .location-input-section {
  1617. margin-bottom: 20px;
  1618. }
  1619. .input-wrapper {
  1620. position: relative;
  1621. display: flex;
  1622. align-items: center;
  1623. }
  1624. .input-icon {
  1625. position: absolute;
  1626. left: 12px;
  1627. font-size: 18px;
  1628. color: #17B3A3;
  1629. z-index: 1;
  1630. }
  1631. .location-input {
  1632. width: 100%;
  1633. height: 48px;
  1634. padding: 0 16px 0 40px;
  1635. border: 1px solid #dcdfe6;
  1636. border-radius: 8px;
  1637. font-size: 16px;
  1638. outline: none;
  1639. transition: border-color 0.2s;
  1640. }
  1641. .location-input:focus {
  1642. border-color: #17B3A3;
  1643. }
  1644. .location-input::placeholder {
  1645. color: #c0c4cc;
  1646. }
  1647. .location-modal .modal-footer {
  1648. padding: 16px 20px;
  1649. display: flex;
  1650. justify-content: center;
  1651. gap: 12px;
  1652. border-top: 1px solid #f0f0f0;
  1653. }
  1654. .btn-cancel,
  1655. .btn-confirm {
  1656. padding: 10px 20px;
  1657. border-radius: 6px;
  1658. font-size: 14px;
  1659. cursor: pointer;
  1660. transition: all 0.2s;
  1661. border: none;
  1662. outline: none;
  1663. }
  1664. .btn-cancel {
  1665. background: #f5f5f5;
  1666. color: #666;
  1667. }
  1668. .btn-cancel:hover {
  1669. background: #e6e6e6;
  1670. }
  1671. .btn-confirm {
  1672. background: #17B3A3;
  1673. color: white;
  1674. }
  1675. .btn-confirm:hover:not(:disabled) {
  1676. background: #0d8f7f;
  1677. }
  1678. .btn-confirm:disabled {
  1679. background: #c0c4cc;
  1680. cursor: not-allowed;
  1681. }
  1682. /* 响应式设计 */
  1683. @media (max-width: 360px) {
  1684. .header-bar {
  1685. padding: 8px 12px;
  1686. }
  1687. .material-info-card {
  1688. margin: 4px 12px;
  1689. padding: 8px 16px;
  1690. }
  1691. .input-section {
  1692. margin: 6px 12px;
  1693. padding: 8px 12px;
  1694. }
  1695. .material-input-grid {
  1696. gap: 10px;
  1697. }
  1698. .input-row-top {
  1699. gap: 6px;
  1700. }
  1701. .input-row-bottom {
  1702. margin-top: 4px;
  1703. }
  1704. .add-material-btn {
  1705. padding: 8px 16px;
  1706. font-size: 13px;
  1707. min-width: 90px;
  1708. }
  1709. .section-title {
  1710. margin: 0 12px;
  1711. margin-top: 4px;
  1712. }
  1713. .label-list {
  1714. margin: 0 12px 8px;
  1715. }
  1716. .card-row-second {
  1717. flex-wrap: wrap;
  1718. gap: 6px;
  1719. }
  1720. .card-row-left {
  1721. flex: 1 1 100%;
  1722. }
  1723. .card-row-right {
  1724. justify-content: flex-end;
  1725. }
  1726. .list-header, .list-item {
  1727. font-size: 11px;
  1728. }
  1729. .col-label, .col-part {
  1730. flex: 1.5;
  1731. }
  1732. .col-batch {
  1733. flex: 1;
  1734. }
  1735. }
  1736. .material-list-link {
  1737. color: #17B3A3;
  1738. font-size: 14px;
  1739. font-weight: 500;
  1740. cursor: pointer;
  1741. text-decoration: underline;
  1742. transition: color 0.2s ease;
  1743. }
  1744. .material-list-link:hover {
  1745. color: #0d8f7f;
  1746. }
  1747. /deep/ .inlineNumber input::-webkit-outer-spin-button,
  1748. /deep/ .inlineNumber input::-webkit-inner-spin-button {
  1749. -webkit-appearance: none;
  1750. }
  1751. /deep/ .inlineNumber input[type="number"]{
  1752. -moz-appearance: textfield;
  1753. padding-right: 5px !important;
  1754. }
  1755. </style>