encodingDialog.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <template>
  2. <ele-modal
  3. title="打码"
  4. :visible.sync="visible"
  5. v-if="visible"
  6. width="40%"
  7. :maxable="true"
  8. append-to-body
  9. >
  10. <div class="encoding-dialog">
  11. <div class="encoding-content">
  12. <div v-for="(item, index) in batchList" :key="index" class="batch-card">
  13. <div class="card-header">
  14. <span class="card-title">{{
  15. item.materialName || '产品标识卡'
  16. }}</span>
  17. </div>
  18. <div class="card-body">
  19. <div class="info-area">
  20. <div class="info-row">
  21. <span class="label">产品编码</span>
  22. <span class="value">{{ item.productCode || '-' }}</span>
  23. </div>
  24. <div class="info-row">
  25. <span class="label">零(部)件编码</span>
  26. <span class="value">{{ item.materialCode || '-' }}</span>
  27. </div>
  28. <div class="info-row">
  29. <span class="label">零(部)件名称</span>
  30. <span class="value">{{ item.materialName || '-' }}</span>
  31. </div>
  32. <div class="info-row">
  33. <span class="label">本厂批次号</span>
  34. <span class="value">{{ item.batchNo || '-' }}</span>
  35. </div>
  36. <div class="info-row">
  37. <span class="label">入库数量</span>
  38. <span class="value">{{ item.quantity || '-' }}</span>
  39. </div>
  40. <div class="info-row">
  41. <span class="label">合格证号</span>
  42. <span class="value">{{ item.certificateNumber || '-' }}</span>
  43. </div>
  44. <div class="info-row">
  45. <span class="label">原厂批号</span>
  46. <span class="value">{{ item.originalBatchNo || '-' }}</span>
  47. </div>
  48. <div class="info-row">
  49. <span class="label">原厂合格证号</span>
  50. <span class="value">{{
  51. item.originalCertificateNumber || '-'
  52. }}</span>
  53. </div>
  54. </div>
  55. <div class="qr-area">
  56. <div class="qr-box">
  57. <img
  58. v-if="item.qrCodeUrl"
  59. :src="item.qrCodeUrl"
  60. alt="二维码"
  61. class="qr-img"
  62. />
  63. </div>
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. <div slot="footer">
  70. <el-button size="small" @click="printPreview">打印预览</el-button>
  71. <el-button size="small" @click="close">关闭</el-button>
  72. </div>
  73. </ele-modal>
  74. </template>
  75. <script>
  76. import QRCode from 'qrcode';
  77. export default {
  78. name: 'EncodingDialog',
  79. data() {
  80. return {
  81. visible: false,
  82. dataList: [],
  83. batchList: []
  84. };
  85. },
  86. methods: {
  87. open(data) {
  88. this.visible = true;
  89. this.dataList = Array.isArray(data) ? data : [data];
  90. this.buildBatchList();
  91. },
  92. close() {
  93. this.visible = false;
  94. this.batchList = [];
  95. },
  96. buildBatchList() {
  97. this.batchList = this.dataList.map((item) => ({
  98. productCode: item.productCode || '',
  99. materialCode: item.categorycodes || '',
  100. materialName: item.categoryNames || '',
  101. batchNo: item.batchNo || '',
  102. quantity: item.chNo || '',
  103. originalBatchNo: item.originalBatchNo || '',
  104. certificateNumber: item.certificateNumber || '',
  105. originalCertificateNumber: item.originalCertificateNumber || '',
  106. codeStr: [
  107. item.productCode,
  108. item.categorycodes,
  109. item.categoryNames,
  110. item.batchNo,
  111. item.chNo,
  112. item.originalBatchNo,
  113. item.certificateNumber,
  114. item.originalCertificateNumber
  115. ]
  116. .filter(Boolean)
  117. .join(';'),
  118. qrCodeUrl: ''
  119. }));
  120. this.generateQRCodes();
  121. },
  122. generateQRCodes() {
  123. this.batchList.forEach((item, index) => {
  124. const content = item.codeStr || item.productCode || String(index);
  125. QRCode.toDataURL(content, { width: 240, margin: 1 })
  126. .then((url) => {
  127. this.$set(this.batchList[index], 'qrCodeUrl', url);
  128. })
  129. .catch((err) => {
  130. console.error('QRCode生成失败:', err);
  131. });
  132. });
  133. },
  134. refresh() {
  135. this.buildBatchList();
  136. },
  137. printPreview() {
  138. const printContent = this.$el.querySelector('.encoding-content');
  139. if (!printContent) return;
  140. const html = printContent.innerHTML;
  141. const printWindow = window.open('', '_blank');
  142. printWindow.document.write(`
  143. <html>
  144. <head>
  145. <title>打码打印预览</title>
  146. <style>
  147. @page {
  148. size: 100mm 80mm;
  149. margin: 0;
  150. }
  151. * { box-sizing: border-box; margin: 0; padding: 0; }
  152. html, body {
  153. font-family: 'Microsoft Yahei', Arial, sans-serif;
  154. width: 100mm;
  155. margin: 0;
  156. padding: 0;
  157. color: #1f2329;
  158. -webkit-print-color-adjust: exact;
  159. print-color-adjust: exact;
  160. }
  161. .encoding-content { width: 100mm; }
  162. .batch-card {
  163. width: 100mm;
  164. height: 80mm;
  165. padding: 2.5mm;
  166. page-break-after: always;
  167. page-break-inside: avoid;
  168. break-inside: avoid;
  169. overflow: hidden;
  170. display: flex;
  171. flex-direction: column;
  172. }
  173. .batch-card:last-child { page-break-after: avoid; }
  174. .batch-card > .card-header,
  175. .batch-card > .card-body {
  176. width: 100%;
  177. }
  178. .card-header {
  179. min-height: 8mm;
  180. background: #1f2329;
  181. color: #fff;
  182. display: flex;
  183. align-items: center;
  184. justify-content: space-between;
  185. padding: 1mm 3mm;
  186. border-radius: 1mm 1mm 0 0;
  187. flex-shrink: 0;
  188. }
  189. .card-header .card-title {
  190. flex: 1;
  191. font-size: 9pt;
  192. font-weight: 700;
  193. letter-spacing: 0.3mm;
  194. line-height: 1.3;
  195. word-break: break-all;
  196. }
  197. .card-body {
  198. flex: 1;
  199. border: 1px solid #000;
  200. border-top: none;
  201. border-radius: 0 0 1mm 1mm;
  202. display: flex;
  203. align-items: stretch;
  204. overflow: hidden;
  205. }
  206. .info-area {
  207. flex: 1;
  208. display: flex;
  209. flex-direction: column;
  210. border-right: 1px solid #000;
  211. }
  212. .info-row {
  213. flex: 1;
  214. display: flex;
  215. align-items: center;
  216. border-bottom: 1px dashed #c0c4cc;
  217. padding: 0 2mm;
  218. min-height: 0;
  219. }
  220. @media print {
  221. .info-row {
  222. border-bottom: none;
  223. background-image: linear-gradient(
  224. to right,
  225. #000 50%,
  226. transparent 50%
  227. );
  228. background-size: 2px 1px;
  229. background-repeat: repeat-x;
  230. background-position: left bottom;
  231. }
  232. .info-row:last-child { background-image: none; }
  233. }
  234. .info-row:last-child { border-bottom: none; }
  235. .info-row .label {
  236. width: 18mm;
  237. font-size: 8pt;
  238. color: #606266;
  239. flex-shrink: 0;
  240. line-height: 1.2;
  241. height: 100%;
  242. display: flex;
  243. align-items: center;
  244. background-image: linear-gradient(
  245. to bottom,
  246. #000 50%,
  247. transparent 50%
  248. );
  249. background-size: 1px 2px;
  250. background-repeat: repeat-y;
  251. background-position: right top;
  252. }
  253. .info-row .value {
  254. flex: 1;
  255. font-size: 8pt;
  256. font-weight: 600;
  257. color: #1f2329;
  258. word-break: break-all;
  259. line-height: 1.2;
  260. padding-left: 2mm;
  261. }
  262. .qr-area {
  263. width: 32mm;
  264. display: flex;
  265. flex-direction: column;
  266. align-items: center;
  267. justify-content: center;
  268. padding: 1.5mm;
  269. background: #fafbfc;
  270. }
  271. .qr-box {
  272. width: 28mm;
  273. height: 28mm;
  274. padding: 1mm;
  275. background: #fff;
  276. border: 1px solid #000;
  277. border-radius: 0.8mm;
  278. display: flex;
  279. align-items: center;
  280. justify-content: center;
  281. }
  282. .qr-img { width: 100%; height: 100%; }
  283. </style>
  284. </head>
  285. <body>${html}</body>
  286. </html>
  287. `);
  288. printWindow.document.close();
  289. printWindow.onload = function () {
  290. setTimeout(() => {
  291. printWindow.print();
  292. }, 300);
  293. };
  294. }
  295. }
  296. };
  297. </script>
  298. <style lang="scss" scoped>
  299. .encoding-dialog {
  300. .encoding-header {
  301. display: flex;
  302. align-items: center;
  303. margin-bottom: 16px;
  304. gap: 10px;
  305. }
  306. .encoding-content {
  307. max-height: 60vh;
  308. overflow-y: auto;
  309. }
  310. .batch-card {
  311. margin-bottom: 16px;
  312. border-radius: 6px;
  313. overflow: hidden;
  314. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  315. border: 1px solid #ebeef5;
  316. background: #fff;
  317. .card-header {
  318. min-height: 40px;
  319. background: linear-gradient(90deg, #1f2329 0%, #2c3138 100%);
  320. color: #fff;
  321. display: flex;
  322. align-items: center;
  323. justify-content: space-between;
  324. padding: 8px 16px;
  325. .card-title {
  326. flex: 1;
  327. font-size: 15px;
  328. font-weight: 600;
  329. letter-spacing: 1px;
  330. line-height: 1.4;
  331. word-break: break-all;
  332. }
  333. .card-sub {
  334. flex-shrink: 0;
  335. margin-left: 12px;
  336. font-size: 12px;
  337. opacity: 0.75;
  338. letter-spacing: 2px;
  339. }
  340. }
  341. .card-body {
  342. display: flex;
  343. align-items: stretch;
  344. }
  345. .info-area {
  346. flex: 1;
  347. display: flex;
  348. flex-direction: column;
  349. border-right: 1px dashed #dcdfe6;
  350. }
  351. .info-row {
  352. flex: 1;
  353. display: flex;
  354. align-items: center;
  355. padding: 10px 14px;
  356. border-bottom: 1px solid #f2f3f5;
  357. min-height: 38px;
  358. &:last-child {
  359. border-bottom: none;
  360. }
  361. .label {
  362. width: 110px;
  363. font-size: 13px;
  364. color: #909399;
  365. flex-shrink: 0;
  366. }
  367. .value {
  368. flex: 1;
  369. font-size: 14px;
  370. color: #1f2329;
  371. font-weight: 500;
  372. word-break: break-all;
  373. }
  374. }
  375. .qr-area {
  376. width: 160px;
  377. display: flex;
  378. flex-direction: column;
  379. align-items: center;
  380. justify-content: center;
  381. padding: 12px;
  382. background: #fafbfc;
  383. .qr-box {
  384. width: 124px;
  385. height: 124px;
  386. padding: 6px;
  387. background: #fff;
  388. border: 1px solid #ebeef5;
  389. border-radius: 4px;
  390. display: flex;
  391. align-items: center;
  392. justify-content: center;
  393. .qr-img {
  394. width: 100%;
  395. height: 100%;
  396. }
  397. }
  398. .qr-tip {
  399. margin-top: 8px;
  400. font-size: 12px;
  401. color: #909399;
  402. letter-spacing: 1px;
  403. }
  404. }
  405. }
  406. }
  407. </style>