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.

1136 lines
24 KiB

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