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.

1226 lines
29 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
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
  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="search-container">
  15. <el-input clearable class="compact-input"
  16. v-model="scanCode"
  17. placeholder="请扫描标签条码"
  18. prefix-icon="el-icon-search"
  19. @keyup.enter.native="handleScan"
  20. ref="scanInput"
  21. />
  22. <div class="mode-switch">
  23. <el-switch
  24. class="custom-switch"
  25. v-model="isRemoveMode"
  26. active-color="#ff4949"
  27. inactive-color="#13ce66">
  28. </el-switch>
  29. <span v-if="isRemoveMode" class="switch-text">{{ '移除' }}</span>
  30. <span v-else class="switch-text2">{{ '添加' }}</span>
  31. </div>
  32. </div>
  33. <!-- 出库单信息卡片 -->
  34. <div class="material-info-card" v-if="outboundInfo.outboundNo">
  35. <div class="card-header">
  36. <div class="card-title-group">
  37. <span class="title-label">出库单号</span>
  38. <span class="title-value">{{ outboundInfo.outboundNo }}</span>
  39. </div>
  40. </div>
  41. <div class="card-body">
  42. <div class="info-row">
  43. <div class="info-item">
  44. <span class="info-label">关联单号:</span>
  45. <span class="info-value">{{ outboundInfo.relatedNo || '-' }}</span>
  46. </div>
  47. <div class="info-item">
  48. <span class="info-label">关联行号:</span>
  49. <span class="info-value highlight">{{ outboundInfo.relatedLineNo || '-' }}</span>
  50. </div>
  51. </div>
  52. <div class="stats-row">
  53. <div class="stat-item">
  54. <div class="stat-label">标签张数</div>
  55. <div class="stat-value">
  56. <span class="qualified">{{ outboundInfo.availableLabels }}</span>
  57. <span class="separator">/</span>
  58. <span class="total">{{ outboundInfo.totalLabels }}</span>
  59. </div>
  60. </div>
  61. <div class="stat-item">
  62. <div class="stat-label">物料总数</div>
  63. <div class="stat-value">
  64. <span class="qualified">{{ outboundInfo.availableQty }}</span>
  65. <span class="separator">/</span>
  66. <span class="total">{{ outboundInfo.totalQty }}</span>
  67. </div>
  68. </div>
  69. <div class="stat-item">
  70. <div class="stat-label">创建日期</div>
  71. <div class="stat-value single">{{ formatDate(outboundInfo.createDate) }}</div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <!-- 出库信息确认标题 -->
  77. <div class="section-title">
  78. <div class="title-left">
  79. <i class="el-icon-circle-check"></i>
  80. <span>出库信息确认</span>
  81. </div>
  82. <div class="title-right">
  83. <span class="material-list-link" @click="showMaterialListDialog">物料清单</span>
  84. </div>
  85. </div>
  86. <!-- 标签列表 -->
  87. <div class="label-list">
  88. <div class="list-header">
  89. <div class="col-no">NO.</div>
  90. <div class="col-label">标签条码</div>
  91. <div class="col-part">物料编码</div>
  92. <div class="col-qty">标签数量</div>
  93. </div>
  94. <div class="list-body">
  95. <div
  96. v-for="(label, index) in labelList"
  97. :key="label.id"
  98. class="list-item"
  99. >
  100. <div class="col-no">{{ labelList.length - index }}</div>
  101. <div class="col-label">{{ label.labelCode }}</div>
  102. <div class="col-part">{{ label.partNo }}</div>
  103. <div class="col-qty">{{ label.quantity }}</div>
  104. </div>
  105. <!-- 空状态 -->
  106. <div v-if="labelList.length === 0" class="empty-labels">
  107. <p>暂无扫描标签</p>
  108. </div>
  109. </div>
  110. </div>
  111. <!-- 底部操作按钮 -->
  112. <div class="bottom-actions">
  113. <button class="action-btn secondary" @click="confirmOutbound">
  114. 确定
  115. </button>
  116. <button class="action-btn secondary" style="margin-left: 10px;" @click="printLabels">
  117. 打印
  118. </button>
  119. <button class="action-btn secondary" style="margin-left: 10px;" @click="cancelOutbound">
  120. 取消
  121. </button>
  122. </div>
  123. <!-- 物料清单弹窗 -->
  124. <div v-if="showMaterialDialog" class="material-overlay">
  125. <div class="material-modal">
  126. <div class="modal-header">
  127. <span class="modal-title">物料清单</span>
  128. <i class="el-icon-close close-btn" @click="closeMaterialDialog"></i>
  129. </div>
  130. <div class="modal-body">
  131. <!-- 加载状态 -->
  132. <div v-if="materialListLoading" class="loading-container">
  133. <i class="el-icon-loading"></i>
  134. <span>加载中...</span>
  135. </div>
  136. <!-- 物料表格 -->
  137. <div v-else-if="materialList.length > 0" class="material-table">
  138. <div class="table-header">
  139. <div class="col-no">NO.</div>
  140. <div class="col-material-code">物料编码</div>
  141. <div class="col-required-qty">需求数量</div>
  142. <div class="col-available-qty">出库数量</div>
  143. </div>
  144. <div class="table-body">
  145. <div
  146. v-for="(item, index) in materialList"
  147. :key="index"
  148. class="table-row"
  149. >
  150. <div class="col-no">{{ index + 1 }}</div>
  151. <div class="col-material-code clickable-part" @click="showStockDialogFn(item)">{{ item.materialCode || item.partNo }}</div>
  152. <div class="col-required-qty">{{ item.requiredQty || 0 }}</div>
  153. <div class="col-available-qty">{{ item.availableQty || 0 }}</div>
  154. </div>
  155. </div>
  156. </div>
  157. <!-- 空数据状态 -->
  158. <div v-else class="empty-material">
  159. <i class="el-icon-document"></i>
  160. <p>暂无物料数据</p>
  161. </div>
  162. </div>
  163. <div class="modal-footer">
  164. <button class="btn-close" @click="closeMaterialDialog">关闭</button>
  165. </div>
  166. </div>
  167. </div>
  168. <!-- 可用库存弹窗 -->
  169. <div v-if="showStockDialog" class="stock-overlay">
  170. <div class="stock-modal">
  171. <div class="modal-header">
  172. <span class="modal-title">可用库存 - {{ currentPartNo }}</span>
  173. <i class="el-icon-close close-btn" @click="closeStockDialog"></i>
  174. </div>
  175. <div class="modal-body">
  176. <div v-if="stockLoading" class="loading-container"><i class="el-icon-loading"></i><span>加载中...</span></div>
  177. <div v-else-if="stockList.length > 0" class="stock-table">
  178. <div class="table-header">
  179. <div class="col-roll-no">标签条码</div>
  180. <div class="col-qty">数量</div>
  181. <div class="col-location">库位</div>
  182. <div class="col-batch">合约号码</div>
  183. <div class="col-date">入库日期</div>
  184. </div>
  185. <div class="table-body">
  186. <div v-for="(item, index) in stockList" :key="index" class="table-row">
  187. <div class="col-roll-no">{{ item.rollNo }}</div>
  188. <div class="col-qty">{{ item.rollQty }} <span class="unit-text">{{ item.um }}</span></div>
  189. <div class="col-location">{{ item.location }}</div>
  190. <div class="col-batch">{{ item.batchNo }}</div>
  191. <div class="col-date">{{ item.daysInStock }}</div>
  192. </div>
  193. </div>
  194. </div>
  195. <div v-else class="empty-stock"><i class="el-icon-box"></i><p>暂无可用库存</p></div>
  196. </div>
  197. <div class="modal-footer"><button class="btn-close" @click="closeStockDialog">关闭</button></div>
  198. </div>
  199. </div>
  200. </div>
  201. </template>
  202. <script>
  203. import { getOutboundDetails, validateLabelWithOutbound, confirmSalesOutbound, getMaterialList, getScannedLabelList } from "@/api/sales/sales-outbound.js";
  204. import { getInventoryStock } from "@/api/inbound.js";
  205. import { getCurrentWarehouse } from '@/utils'
  206. import moment from 'moment';
  207. export default {
  208. data() {
  209. return {
  210. scanCode: '',
  211. outboundInfo: {},
  212. labelList: [],
  213. outboundNo: '',
  214. relatedNo: '',
  215. relatedLineNo: '',
  216. showMaterialDialog: false,
  217. materialList: [],
  218. materialListLoading: false,
  219. isRemoveMode: false, // 默认为添加模式
  220. showStockDialog: false,
  221. stockList: [],
  222. stockLoading: false,
  223. currentPartNo: ''
  224. };
  225. },
  226. methods: {
  227. formatDate(date) {
  228. return date ? moment(date).format('YYYY-MM-DD') : '';
  229. },
  230. // 处理扫描
  231. handleScan() {
  232. if (!this.scanCode.trim()) {
  233. return;
  234. }
  235. if (this.isRemoveMode) {
  236. this.removeLabelByCode(this.scanCode.trim());
  237. } else {
  238. this.validateAndAddLabel(this.scanCode.trim());
  239. }
  240. this.scanCode = '';
  241. },
  242. // 验证标签并添加到列表(调用存储过程)
  243. validateAndAddLabel(labelCode) {
  244. const params = {
  245. labelCode: labelCode,
  246. outboundNo: this.outboundNo,
  247. relatedNo: this.relatedNo,
  248. relatedLineNo: this.relatedLineNo,
  249. site: localStorage.getItem('site'),
  250. buNo: this.buNo,
  251. operationType: 'I', // 添加标签
  252. warehouseId: getCurrentWarehouse() // 当前仓库
  253. };
  254. validateLabelWithOutbound(params).then(({ data }) => {
  255. if (data && data.code === 0) {
  256. this.$message.success('标签添加成功');
  257. // 重新加载已扫描标签列表
  258. this.loadScannedLabelList();
  259. } else {
  260. this.$message.error(data.msg || '该标签与出库单不符,请检查');
  261. }
  262. }).catch(error => {
  263. console.error('标签验证失败:', error);
  264. this.$message.error('操作失败');
  265. });
  266. },
  267. // 通过条码移除标签(调用存储过程)
  268. removeLabelByCode(labelCode) {
  269. const params = {
  270. labelCode: labelCode,
  271. outboundNo: this.outboundNo,
  272. relatedNo: this.relatedNo,
  273. relatedLineNo: this.relatedLineNo,
  274. site: localStorage.getItem('site'),
  275. buNo: this.buNo,
  276. operationType: 'D', // 移除标签
  277. warehouseId: getCurrentWarehouse() // 当前仓库
  278. };
  279. validateLabelWithOutbound(params).then(({ data }) => {
  280. if (data && data.code === 0) {
  281. this.$message.success('标签移除成功');
  282. // 重新加载已扫描标签列表
  283. this.loadScannedLabelList();
  284. } else {
  285. this.$message.error(data.msg || '移除失败');
  286. }
  287. }).catch(error => {
  288. console.error('标签移除失败:', error);
  289. this.$message.error('移除失败');
  290. });
  291. },
  292. // 确认出库(调用存储过程)
  293. confirmOutbound() {
  294. if (this.labelList.length === 0) {
  295. this.$message.warning('请先扫描标签');
  296. return;
  297. }
  298. // 获取库位(使用第一个标签的库位,如果有的话)
  299. const locationCode = this.labelList.length > 0 && this.labelList[0].locationId
  300. ? this.labelList[0].locationId
  301. : '';
  302. const params = {
  303. site: this.outboundInfo.site,
  304. buNo: this.outboundInfo.buNo,
  305. outboundNo: this.outboundNo,
  306. relatedNo: this.relatedNo,
  307. relatedLineNo: this.relatedLineNo,
  308. locationCode: locationCode
  309. };
  310. confirmSalesOutbound(params).then(({ data }) => {
  311. if (data && data.code === 0) {
  312. this.$message.success('出库成功');
  313. this.$router.back();
  314. } else {
  315. this.$message.error(data.msg || '出库失败');
  316. }
  317. }).catch(error => {
  318. console.error('出库失败:', error);
  319. this.$message.error('出库失败');
  320. });
  321. },
  322. // 打印标签
  323. printLabels() {
  324. if (this.labelList.length === 0) {
  325. this.$message.warning('暂无标签可打印');
  326. return;
  327. }
  328. this.$message.warning('打印功能开发中...');
  329. },
  330. // 取消出库
  331. cancelOutbound() {
  332. if (this.labelList.length > 0) {
  333. this.$confirm('取消后将清空已扫描的标签,确定取消吗?', '提示', {
  334. confirmButtonText: '确定',
  335. cancelButtonText: '继续操作',
  336. type: 'warning'
  337. }).then(() => {
  338. this.$router.back();
  339. }).catch(() => {
  340. // 用户选择继续操作
  341. });
  342. } else {
  343. this.$router.back();
  344. }
  345. },
  346. // 显示物料清单弹窗
  347. showMaterialListDialog() {
  348. this.showMaterialDialog = true;
  349. this.loadMaterialList();
  350. },
  351. // 加载物料清单
  352. loadMaterialList() {
  353. if (!this.outboundInfo.site || !this.outboundInfo.buNo || !this.outboundNo) {
  354. this.$message.error('缺少必要参数,无法获取物料清单');
  355. return;
  356. }
  357. this.materialListLoading = true;
  358. const params = {
  359. site: this.outboundInfo.site,
  360. buNo: this.outboundInfo.buNo,
  361. outboundNo: this.outboundNo
  362. };
  363. getMaterialList(params).then(({ data }) => {
  364. this.materialListLoading = false;
  365. if (data && data.code === 0) {
  366. this.materialList = data.data || [];
  367. } else {
  368. this.$message.error(data.msg || '获取物料清单失败');
  369. this.materialList = [];
  370. }
  371. }).catch(error => {
  372. this.materialListLoading = false;
  373. console.error('获取物料清单失败:', error);
  374. this.$message.error('获取物料清单失败');
  375. this.materialList = [];
  376. });
  377. },
  378. // 关闭物料清单弹窗
  379. closeMaterialDialog() {
  380. this.showMaterialDialog = false;
  381. },
  382. showStockDialogFn(item) {
  383. this.currentPartNo = item.materialCode || item.partNo;
  384. this.showStockDialog = true;
  385. this.loadInventoryStock(this.currentPartNo);
  386. },
  387. loadInventoryStock(partNo) {
  388. this.stockLoading = true;
  389. const params = {
  390. site: this.outboundInfo.site,
  391. notifyNo: this.outboundNo,
  392. notifyType: '销售出库',
  393. orderNo: '',
  394. orderLineNo: '',
  395. partNo: partNo,
  396. warehouseId: localStorage.getItem('warehouseId') || ''
  397. };
  398. getInventoryStock(params).then(({ data }) => {
  399. this.stockLoading = false;
  400. if (data && data.code === 0) { this.stockList = data.data || []; }
  401. else { this.$message.error(data.msg || '获取可用库存失败'); this.stockList = []; }
  402. }).catch(error => { this.stockLoading = false; this.$message.error('获取可用库存失败'); this.stockList = []; });
  403. },
  404. closeStockDialog() {
  405. this.showStockDialog = false;
  406. this.stockList = [];
  407. this.currentPartNo = '';
  408. },
  409. // 加载出库单详情
  410. loadOutboundDetails() {
  411. const params = {
  412. outboundNo: this.outboundNo,
  413. buNo: this.buNo,
  414. warehouseId: getCurrentWarehouse(),
  415. site: localStorage.getItem('site'),
  416. };
  417. getOutboundDetails(params).then(({ data }) => {
  418. if (data && data.code === 0) {
  419. this.outboundInfo = data.data;
  420. // 从出库单详情中获取关联单号和关联单行号
  421. if (data.data.relatedNo) {
  422. this.relatedNo = data.data.relatedNo;
  423. }
  424. if (data.data.relatedLineNo) {
  425. this.relatedLineNo = data.data.relatedLineNo;
  426. }
  427. // 加载已扫描的标签列表
  428. this.loadScannedLabelList();
  429. } else {
  430. this.$message.error(data.msg || '获取出库单详情失败');
  431. }
  432. }).catch(error => {
  433. console.error('获取出库单详情失败:', error);
  434. this.$message.error('获取出库单详情失败');
  435. });
  436. },
  437. // 加载已扫描标签列表(从临时表)
  438. loadScannedLabelList() {
  439. const params = {
  440. site: localStorage.getItem('site'),
  441. buNo: this.buNo,
  442. outboundNo: this.outboundNo,
  443. relatedNo: this.relatedNo,
  444. relatedLineNo: this.relatedLineNo
  445. };
  446. getScannedLabelList(params).then(({ data }) => {
  447. if (data && data.code === 0) {
  448. this.labelList = (data.data || []).map(item => ({
  449. id: Date.now() + Math.random(),
  450. labelCode: item.labelCode || item.RollNo,
  451. partNo: item.partNo || item.part_no,
  452. quantity: item.quantity || item.RollQty,
  453. batchNo: item.batchNo || '',
  454. locationId: item.locationId || ''
  455. }));
  456. } else {
  457. console.error('获取已扫描标签列表失败:', data.msg);
  458. this.labelList = [];
  459. }
  460. }).catch(error => {
  461. console.error('获取已扫描标签列表失败:', error);
  462. this.labelList = [];
  463. });
  464. }
  465. },
  466. mounted() {
  467. // 获取路由参数
  468. this.outboundNo = this.$route.params.outboundNo;
  469. this.buNo = this.$route.params.buNo;
  470. if (!this.outboundNo || !this.buNo) {
  471. this.$message.error('参数错误');
  472. this.$router.back();
  473. return;
  474. }
  475. // 聚焦扫描框
  476. this.$nextTick(() => {
  477. if (this.$refs.scanInput) {
  478. this.$refs.scanInput.focus();
  479. }
  480. });
  481. // 加载出库单详情
  482. this.loadOutboundDetails();
  483. }
  484. };
  485. </script>
  486. <style scoped>
  487. /* 复用生产入库的样式,保持一致性 */
  488. .pda-container {
  489. width: 100vw;
  490. height: 100vh;
  491. display: flex;
  492. flex-direction: column;
  493. background: #f5f5f5;
  494. }
  495. /* 头部栏 */
  496. .header-bar {
  497. display: flex;
  498. justify-content: space-between;
  499. align-items: center;
  500. padding: 8px 16px;
  501. background: #17B3A3;
  502. color: white;
  503. height: 40px;
  504. min-height: 40px;
  505. }
  506. .header-left {
  507. display: flex;
  508. align-items: center;
  509. cursor: pointer;
  510. font-size: 16px;
  511. font-weight: 500;
  512. }
  513. .header-left i {
  514. margin-right: 8px;
  515. font-size: 18px;
  516. }
  517. .header-right {
  518. cursor: pointer;
  519. font-size: 16px;
  520. font-weight: 500;
  521. }
  522. /* 搜索容器 */
  523. .search-container {
  524. padding: 12px 16px;
  525. background: white;
  526. display: flex;
  527. align-items: center;
  528. gap: 12px;
  529. }
  530. .search-container .el-input {
  531. width: 240px;
  532. margin-right: 12px;
  533. }
  534. /* 紧凑型输入框样式 */
  535. .compact-input ::v-deep .el-input__inner {
  536. height: 36px;
  537. padding: 0 12px 0 35px;
  538. font-size: 14px;
  539. }
  540. .compact-input ::v-deep .el-input__prefix {
  541. left: 10px;
  542. }
  543. .compact-input ::v-deep .el-input__suffix {
  544. right: 30px;
  545. }
  546. /* 模式切换开关 */
  547. .mode-switch {
  548. position: relative;
  549. display: inline-block;
  550. }
  551. .custom-switch {
  552. transform: scale(1.3);
  553. }
  554. /* 中间文字 */
  555. .switch-text {
  556. position: absolute;
  557. left: 25%;
  558. transform: translateX(-50%);
  559. top: 50%;
  560. transform: translateY(-50%) translateX(-50%);
  561. font-size: 12px;
  562. font-weight: 500;
  563. color: #606266;
  564. white-space: nowrap;
  565. pointer-events: none;
  566. z-index: 1;
  567. top: 53%;
  568. transform: translate(-50%, -50%);
  569. font-size: 12px;
  570. font-weight: bold;
  571. color: white;
  572. pointer-events: none;
  573. z-index: 2;
  574. }
  575. .switch-text2 {
  576. position: absolute;
  577. left: 75%;
  578. transform: translateX(-50%);
  579. top: 50%;
  580. transform: translateY(-50%) translateX(-50%);
  581. font-size: 12px;
  582. font-weight: 500;
  583. color: #606266;
  584. white-space: nowrap;
  585. pointer-events: none;
  586. z-index: 1;
  587. top: 53%;
  588. transform: translate(-50%, -50%);
  589. font-size: 12px;
  590. font-weight: bold;
  591. color: white;
  592. pointer-events: none;
  593. z-index: 2;
  594. }
  595. /* 调整 switch 尺寸以便容纳文字 */
  596. .custom-switch ::v-deep .el-switch__core {
  597. width: 60px;
  598. height: 28px;
  599. }
  600. /* 物料信息卡片 */
  601. .material-info-card {
  602. background: white;
  603. margin: 4px 16px;
  604. border-radius: 12px;
  605. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  606. border: 1px solid #f0f0f0;
  607. overflow: hidden;
  608. }
  609. /* 卡片头部 */
  610. .card-header {
  611. background: white;
  612. padding: 10px 16px;
  613. border-bottom: 1px solid #e8e8e8;
  614. }
  615. .card-title-group {
  616. display: flex;
  617. flex-direction: column;
  618. gap: 3px;
  619. }
  620. .title-label {
  621. font-size: 11px;
  622. color: #999;
  623. font-weight: normal;
  624. }
  625. .title-value {
  626. font-size: 16px;
  627. font-weight: bold;
  628. color: #333;
  629. line-height: 1.2;
  630. letter-spacing: 0.5px;
  631. }
  632. /* 卡片主体 */
  633. .card-body {
  634. padding: 12px 16px;
  635. }
  636. /* 信息行(关联单号和行号) */
  637. .info-row {
  638. display: flex;
  639. gap: 12px;
  640. margin-bottom: 10px;
  641. padding-bottom: 10px;
  642. border-bottom: 1px dashed #e8e8e8;
  643. }
  644. .info-item {
  645. flex: 1;
  646. display: flex;
  647. align-items: center;
  648. gap: 6px;
  649. }
  650. .info-label {
  651. font-size: 11px;
  652. color: #666;
  653. font-weight: 500;
  654. white-space: nowrap;
  655. }
  656. .info-value {
  657. font-size: 12px;
  658. color: #333;
  659. font-weight: 600;
  660. overflow: hidden;
  661. text-overflow: ellipsis;
  662. white-space: nowrap;
  663. }
  664. .info-value.highlight {
  665. color: #333;
  666. font-weight: 600;
  667. }
  668. /* 统计数据行 */
  669. .stats-row {
  670. display: flex;
  671. justify-content: space-between;
  672. gap: 6px;
  673. }
  674. .stat-item {
  675. flex: 1;
  676. text-align: center;
  677. padding: 6px 4px;
  678. background: white;
  679. border-radius: 6px;
  680. }
  681. .stat-label {
  682. font-size: 10px;
  683. color: #666;
  684. margin-bottom: 4px;
  685. font-weight: normal;
  686. }
  687. .stat-value {
  688. font-size: 12px;
  689. color: #333;
  690. font-weight: 600;
  691. line-height: 1.3;
  692. }
  693. .stat-value.single {
  694. color: #666;
  695. }
  696. .stat-value .qualified {
  697. color: #333;
  698. font-weight: 700;
  699. }
  700. .stat-value .total {
  701. color: #666;
  702. font-weight: 500;
  703. }
  704. .stat-value .separator {
  705. color: #ccc;
  706. margin: 0 2px;
  707. }
  708. /* 区域标题 */
  709. .section-title {
  710. display: flex;
  711. align-items: center;
  712. justify-content: space-between;
  713. padding: 6px 8px;
  714. background: white;
  715. margin: 0 16px;
  716. margin-top: 4px;
  717. border-radius: 8px 8px 0 0;
  718. border-bottom: 2px solid #17B3A3;
  719. }
  720. .title-left {
  721. display: flex;
  722. align-items: center;
  723. }
  724. .title-left i {
  725. color: #17B3A3;
  726. font-size: 16px;
  727. margin-right: 8px;
  728. }
  729. .title-left span {
  730. color: #17B3A3;
  731. font-size: 14px;
  732. font-weight: 500;
  733. }
  734. .title-right {
  735. display: flex;
  736. align-items: center;
  737. }
  738. .material-list-link {
  739. color: #17B3A3;
  740. font-size: 14px;
  741. font-weight: 500;
  742. cursor: pointer;
  743. text-decoration: underline;
  744. transition: color 0.2s ease;
  745. }
  746. .material-list-link:hover {
  747. color: #0d8f7f;
  748. }
  749. /* 标签列表 */
  750. .label-list {
  751. background: white;
  752. margin: 0 16px 12px;
  753. border-radius: 0 0 8px 8px;
  754. overflow: hidden;
  755. flex: 1;
  756. display: flex;
  757. flex-direction: column;
  758. max-height: 300px; /* 限制最大高度 */
  759. }
  760. .label-list .list-body {
  761. flex: 1;
  762. overflow-y: auto; /* 允许垂直滚动 */
  763. max-height: 250px; /* 内容区域最大高度 */
  764. }
  765. /* 滚动条样式优化 */
  766. .label-list .list-body::-webkit-scrollbar {
  767. width: 4px;
  768. }
  769. .label-list .list-body::-webkit-scrollbar-track {
  770. background: #f1f1f1;
  771. border-radius: 2px;
  772. }
  773. .label-list .list-body::-webkit-scrollbar-thumb {
  774. background: #17B3A3;
  775. border-radius: 2px;
  776. }
  777. .label-list .list-body::-webkit-scrollbar-thumb:hover {
  778. background: #0d8f7f;
  779. }
  780. .list-header {
  781. display: flex;
  782. background: #f8f9fa;
  783. padding: 12px 8px;
  784. border-bottom: 1px solid #e0e0e0;
  785. font-size: 12px;
  786. color: #666;
  787. font-weight: 500;
  788. }
  789. .list-item {
  790. display: flex;
  791. padding: 12px 8px;
  792. border-bottom: 1px solid #f0f0f0;
  793. font-size: 12px;
  794. color: #333;
  795. }
  796. .list-item:last-child {
  797. border-bottom: none;
  798. }
  799. .col-no {
  800. width: 20px;
  801. text-align: center;
  802. }
  803. .col-label {
  804. flex: 2;
  805. text-align: center;
  806. }
  807. .col-part {
  808. flex: 2;
  809. text-align: center;
  810. }
  811. .col-qty {
  812. width: 60px;
  813. text-align: center;
  814. }
  815. .empty-labels {
  816. padding: 40px 20px;
  817. text-align: center;
  818. color: #999;
  819. }
  820. .empty-labels p {
  821. margin: 0;
  822. font-size: 14px;
  823. }
  824. /* 底部操作按钮 */
  825. .bottom-actions {
  826. display: flex;
  827. padding: 16px;
  828. gap: 20px;
  829. background: white;
  830. margin-top: auto;
  831. }
  832. .action-btn {
  833. flex: 1;
  834. padding: 12px;
  835. border: 1px solid #17B3A3;
  836. background: white;
  837. color: #17B3A3;
  838. border-radius: 20px;
  839. font-size: 14px;
  840. cursor: pointer;
  841. transition: all 0.2s ease;
  842. }
  843. .action-btn:hover {
  844. background: #17B3A3;
  845. color: white;
  846. }
  847. .action-btn:active {
  848. transform: scale(0.98);
  849. }
  850. /* 物料清单弹窗样式 */
  851. .material-overlay {
  852. position: fixed;
  853. top: 0;
  854. left: 0;
  855. right: 0;
  856. bottom: 0;
  857. background: rgba(0, 0, 0, 0.5);
  858. z-index: 9999;
  859. display: flex;
  860. align-items: center;
  861. justify-content: center;
  862. padding: 20px;
  863. }
  864. .material-modal {
  865. background: white;
  866. border-radius: 12px;
  867. width: 100%;
  868. max-width: 800px;
  869. max-height: 80vh;
  870. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  871. overflow: hidden;
  872. display: flex;
  873. flex-direction: column;
  874. }
  875. .material-modal .modal-header {
  876. background: #17B3A3;
  877. color: white;
  878. padding: 5px 16px;
  879. display: flex;
  880. justify-content: space-between;
  881. align-items: center;
  882. min-height: 28px;
  883. }
  884. .close-btn {
  885. font-size: 16px;
  886. cursor: pointer;
  887. color: white;
  888. transition: color 0.2s ease;
  889. padding: 4px;
  890. display: flex;
  891. align-items: center;
  892. justify-content: center;
  893. }
  894. .close-btn:hover {
  895. color: #e0e0e0;
  896. }
  897. .material-modal .modal-title {
  898. font-size: 16px;
  899. font-weight: 500;
  900. margin: 0;
  901. line-height: 1.2;
  902. }
  903. .material-modal .modal-body {
  904. flex: 1;
  905. overflow: auto;
  906. padding: 0;
  907. }
  908. .material-table {
  909. width: 100%;
  910. }
  911. .table-header {
  912. display: flex;
  913. background: #f8f9fa;
  914. padding: 10px 6px;
  915. border-bottom: 2px solid #17B3A3;
  916. font-size: 12px;
  917. color: #333;
  918. font-weight: 600;
  919. position: sticky;
  920. top: 0;
  921. z-index: 1;
  922. }
  923. .table-body {
  924. max-height: 400px;
  925. overflow-y: auto;
  926. }
  927. .table-row {
  928. display: flex;
  929. padding: 10px 6px;
  930. border-bottom: 1px solid #f0f0f0;
  931. font-size: 12px;
  932. color: #333;
  933. transition: background-color 0.2s ease;
  934. }
  935. .table-row:hover {
  936. background-color: #f8f9fa;
  937. }
  938. .table-row:last-child {
  939. border-bottom: none;
  940. }
  941. .material-table .col-no {
  942. width: 25px;
  943. text-align: center;
  944. flex-shrink: 0;
  945. font-size: 12px;
  946. }
  947. .material-table .col-material-code {
  948. flex: 1.8;
  949. text-align: center;
  950. min-width: 100px;
  951. font-size: 12px;
  952. word-break: break-all;
  953. }
  954. .clickable-part { color: #17B3A3; font-weight: 500; cursor: pointer; text-decoration: underline; }
  955. .clickable-part:hover { color: #0d8f7f; }
  956. .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; }
  957. .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; }
  958. .stock-modal .modal-header { background: #17B3A3; color: white; padding: 5px 16px; display: flex; justify-content: space-between; align-items: center; min-height: 28px; }
  959. .stock-modal .modal-body { flex: 1; overflow: auto; padding: 0; }
  960. .stock-table { width: 100%; }
  961. .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; }
  962. .stock-table .table-body { max-height: 400px; overflow-y: auto; }
  963. .stock-table .table-row { display: flex; padding: 10px 6px; border-bottom: 1px solid #f0f0f0; font-size: 12px; color: #333; }
  964. .stock-table .table-row:hover { background-color: #f8f9fa; }
  965. .stock-table .col-roll-no { flex: 1.5; text-align: center; min-width: 100px; }
  966. .stock-table .col-qty { flex: 0.8; text-align: center; min-width: 60px; }
  967. .unit-text { color: #999; font-size: 11px; margin-left: 2px; }
  968. .stock-table .col-location { flex: 0.8; text-align: center; min-width: 60px; }
  969. .stock-table .col-batch { flex: 1; text-align: center; min-width: 80px; }
  970. .stock-table .col-date { flex: 1; text-align: center; min-width: 80px; }
  971. .stock-modal .modal-footer { padding: 15px 20px; display: flex; justify-content: center; border-top: 1px solid #f0f0f0; }
  972. .empty-stock { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #999; }
  973. .empty-stock i { font-size: 48px; margin-bottom: 16px; color: #ddd; }
  974. .empty-stock p { margin: 0; font-size: 14px; }
  975. .material-table .col-required-qty {
  976. flex: 0.8;
  977. text-align: center;
  978. min-width: 65px;
  979. font-size: 12px;
  980. }
  981. .material-table .col-available-qty {
  982. flex: 0.8;
  983. text-align: center;
  984. min-width: 65px;
  985. font-size: 12px;
  986. }
  987. .material-modal .modal-footer {
  988. padding: 15px 20px;
  989. display: flex;
  990. justify-content: center;
  991. border-top: 1px solid #f0f0f0;
  992. }
  993. .btn-close {
  994. padding: 10px 20px;
  995. border-radius: 6px;
  996. font-size: 14px;
  997. cursor: pointer;
  998. transition: all 0.2s;
  999. border: 1px solid #17B3A3;
  1000. background: white;
  1001. color: #17B3A3;
  1002. }
  1003. .btn-close:hover {
  1004. background: #17B3A3;
  1005. color: white;
  1006. }
  1007. /* 加载状态样式 */
  1008. .loading-container {
  1009. display: flex;
  1010. flex-direction: column;
  1011. align-items: center;
  1012. justify-content: center;
  1013. padding: 60px 20px;
  1014. color: #666;
  1015. }
  1016. .loading-container i {
  1017. font-size: 24px;
  1018. margin-bottom: 12px;
  1019. color: #17B3A3;
  1020. }
  1021. .loading-container span {
  1022. font-size: 14px;
  1023. }
  1024. /* 空数据状态样式 */
  1025. .empty-material {
  1026. display: flex;
  1027. flex-direction: column;
  1028. align-items: center;
  1029. justify-content: center;
  1030. padding: 60px 20px;
  1031. color: #999;
  1032. }
  1033. .empty-material i {
  1034. font-size: 48px;
  1035. margin-bottom: 16px;
  1036. color: #ddd;
  1037. }
  1038. .empty-material p {
  1039. margin: 0;
  1040. font-size: 14px;
  1041. }
  1042. /* 响应式设计 */
  1043. @media (max-width: 360px) {
  1044. .header-bar {
  1045. padding: 8px 12px;
  1046. }
  1047. .search-container {
  1048. padding: 8px 12px;
  1049. }
  1050. .material-info-card {
  1051. margin: 4px 12px;
  1052. }
  1053. .card-header {
  1054. padding: 10px 12px;
  1055. }
  1056. .card-body {
  1057. padding: 10px 12px;
  1058. }
  1059. .info-row {
  1060. flex-direction: column;
  1061. gap: 8px;
  1062. }
  1063. .stats-row {
  1064. flex-wrap: wrap;
  1065. gap: 6px;
  1066. }
  1067. .stat-item {
  1068. flex: 0 0 48%;
  1069. min-width: 45%;
  1070. }
  1071. .section-title {
  1072. margin: 0 12px;
  1073. margin-top: 4px;
  1074. }
  1075. .label-list {
  1076. margin: 0 12px 8px;
  1077. }
  1078. .list-header, .list-item {
  1079. font-size: 11px;
  1080. }
  1081. .col-label, .col-part {
  1082. flex: 1.5;
  1083. }
  1084. }
  1085. </style>