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.

820 lines
21 KiB

5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
  1. <template>
  2. <div class="property-form">
  3. <!-- 文本属性 -->
  4. <div v-if="element.type === 'text'" class="form-section">
  5. <el-form label-position="top" size="small">
  6. <el-form-item label="文本内容">
  7. <div class="input-with-button">
  8. <el-input v-model="element.data" placeholder="请输入文本" />
  9. <el-button type="primary" size="mini" @click="$emit('data-source', element)">
  10. 数据源
  11. </el-button>
  12. </div>
  13. </el-form-item>
  14. <el-form-item label="字体大小">
  15. <div class="font-size-row">
  16. <el-input style="margin-right: 36px"
  17. v-model="element.fontSize"
  18. :min="8"
  19. :max="200"
  20. controls-position="right"
  21. size="mini"
  22. class="font-size-input"
  23. />
  24. <el-checkbox v-model="element.bold" size="small" class="inline-checkbox">加粗</el-checkbox>
  25. <el-checkbox v-model="element.newline" size="small" class="inline-checkbox">自动换行</el-checkbox>
  26. </div>
  27. </el-form-item>
  28. <template v-if="element.newline">
  29. <div class="form-row">
  30. <el-form-item label="文本宽度" class="form-item-half">
  31. <el-input
  32. v-model="element.lineWidth"
  33. :min="50"
  34. :max="1000"
  35. controls-position="right"
  36. size="mini"
  37. />
  38. </el-form-item>
  39. <el-form-item label="文本行数" class="form-item-half">
  40. <el-input
  41. v-model="element.lineRows"
  42. :min="1"
  43. :max="10"
  44. controls-position="right"
  45. size="mini"
  46. />
  47. </el-form-item>
  48. </div>
  49. </template>
  50. </el-form>
  51. </div>
  52. <!-- 条码/一维码属性 -->
  53. <div v-else-if="['barcode', 'onecode'].includes(element.type)" class="form-section">
  54. <el-form label-position="top" size="small">
  55. <el-form-item label="数据内容">
  56. <div class="input-with-button">
  57. <el-input v-model="element.data" placeholder="请输入条码数据" />
  58. <el-button type="primary" size="mini" @click="$emit('data-source', element)">
  59. 数据源
  60. </el-button>
  61. </div>
  62. </el-form-item>
  63. <div class="form-row">
  64. <el-form-item label="宽度" class="form-item-half">
  65. <el-input
  66. v-model="element.width"
  67. :min="1"
  68. :max="10"
  69. controls-position="right"
  70. size="mini"
  71. />
  72. </el-form-item>
  73. <el-form-item label="高度" class="form-item-half">
  74. <el-input
  75. v-model="element.height"
  76. :min="10"
  77. :max="500"
  78. controls-position="right"
  79. size="mini"
  80. />
  81. </el-form-item>
  82. </div>
  83. </el-form>
  84. </div>
  85. <!-- 二维码属性 -->
  86. <div v-else-if="element.type === 'qrcode'" class="form-section">
  87. <el-form label-position="top" size="small">
  88. <el-form-item label="数据内容">
  89. <div class="input-with-button">
  90. <el-input v-model="element.data" placeholder="请输入二维码数据" />
  91. <el-button type="primary" size="mini" @click="$emit('data-source', element)">
  92. 数据源
  93. </el-button>
  94. </div>
  95. </el-form-item>
  96. <el-form-item label="尺寸">
  97. <el-input
  98. v-model="element.height"
  99. :min="1"
  100. :max="10"
  101. controls-position="right"
  102. @change="validateQRSize"
  103. />
  104. <div class="form-tip">二维码最大尺寸为10</div>
  105. </el-form-item>
  106. </el-form>
  107. </div>
  108. <!-- 图片属性 -->
  109. <div v-else-if="element.type === 'pic'" class="form-section">
  110. <el-form label-position="top" size="small">
  111. <el-form-item label="图片上传">
  112. <div class="image-upload">
  113. <input
  114. ref="fileInput"
  115. type="file"
  116. accept="image/*"
  117. @change="handleImageUpload"
  118. style="display: none;"
  119. />
  120. <el-button
  121. type="primary"
  122. icon="el-icon-upload"
  123. @click="handleSelectImage"
  124. >
  125. 选择图片
  126. </el-button>
  127. </div>
  128. </el-form-item>
  129. <div v-if="element.previewUrl" class="image-preview">
  130. <img :src="element.previewUrl" alt="预览" />
  131. </div>
  132. </el-form>
  133. </div>
  134. <!-- 线条属性 -->
  135. <div v-else-if="['hLine', 'vLine'].includes(element.type)" class="form-section">
  136. <el-form label-position="top" size="small">
  137. <div class="form-row">
  138. <el-form-item label="宽度" class="form-item-half">
  139. <el-input
  140. v-model="element.width"
  141. :min="1"
  142. :max="1000"
  143. controls-position="right"
  144. size="mini"
  145. />
  146. </el-form-item>
  147. <el-form-item label="高度" class="form-item-half">
  148. <el-input
  149. v-model="element.height"
  150. :min="1"
  151. :max="1000"
  152. controls-position="right"
  153. size="mini"
  154. />
  155. </el-form-item>
  156. </div>
  157. </el-form>
  158. </div>
  159. <!-- 流水号属性 -->
  160. <div v-else-if="element.type === 'serialNumber'" class="form-section">
  161. <el-form label-position="top" size="small">
  162. <div class="form-row">
  163. <el-form-item label="位数" class="form-item-half">
  164. <el-input
  165. v-model="element.digits"
  166. :min="1"
  167. :max="10"
  168. controls-position="right"
  169. size="mini"
  170. placeholder="6"
  171. />
  172. </el-form-item>
  173. <el-form-item label="步长" class="form-item-half">
  174. <el-input
  175. v-model="element.step"
  176. :min="1"
  177. :max="100"
  178. controls-position="right"
  179. size="mini"
  180. placeholder="1"
  181. />
  182. </el-form-item>
  183. </div>
  184. <div class="form-row">
  185. <el-form-item label="字体大小" class="form-item-half">
  186. <el-input
  187. v-model="element.fontSize"
  188. :min="8"
  189. :max="200"
  190. controls-position="right"
  191. size="mini"
  192. placeholder="30"
  193. />
  194. </el-form-item>
  195. <el-form-item label="加粗" class="form-item-half">
  196. <el-checkbox v-model="element.bold" size="middle"></el-checkbox>
  197. </el-form-item>
  198. </div>
  199. <div class="form-row">
  200. <el-form-item label="流水号规则" class="form-item-half">
  201. <el-input v-model="element.data" placeholder="请输入流水号规则" @focus="$emit('data-source', element)"/>
  202. </el-form-item>
  203. <el-form-item label="流水号信息" class="form-item-half">
  204. <el-button type="primary" size="mini" @click="serialInfoModal(element)">
  205. 查看
  206. </el-button>
  207. </el-form-item>
  208. </div>
  209. </el-form>
  210. </div>
  211. <!-- 标签内容流水号信息 -->
  212. <comShowLabelSerialInfo ref="comShowLabelSerialInfo" @refreshCurrentPageTable="refreshCurrentPageTable" v-drag></comShowLabelSerialInfo>
  213. </div>
  214. </template>
  215. <script>
  216. import comShowLabelSerialInfo from "../com_show_label_serial_info";
  217. export default {
  218. name: 'PropertyForm',
  219. props: {
  220. element: {
  221. type: Object,
  222. required: true
  223. }
  224. },
  225. emits: ['data-source', 'image-upload'],
  226. /*组件*/
  227. components: {
  228. comShowLabelSerialInfo,/*标签内容流水号信息的組件*/
  229. },
  230. watch: {
  231. // 监听元素变化,确保文件输入框状态正确
  232. 'element.id'() {
  233. // 当切换到不同元素时,重置文件输入框
  234. this.$nextTick(() => {
  235. if (this.$refs.fileInput) {
  236. this.$refs.fileInput.value = ''
  237. }
  238. })
  239. }
  240. },
  241. methods: {
  242. handleSelectImage() {
  243. // 确保文件输入框被重置,然后触发点击
  244. if (this.$refs.fileInput) {
  245. this.$refs.fileInput.value = ''
  246. this.$refs.fileInput.click()
  247. }
  248. },
  249. /*流水号信息的modal*/
  250. serialInfoModal(row){
  251. let rowData = {
  252. labelNo: row.reportId,
  253. itemNo: row.itemNo,
  254. };
  255. //打开组件 去做复制其他标签业务
  256. this.$nextTick(() => {
  257. this.$refs.comShowLabelSerialInfo.init(rowData);
  258. })
  259. },
  260. validateQRSize() {
  261. if (this.element.height > 10) {
  262. this.$message.warning('二维码最大尺寸为10')
  263. this.element.height = 10
  264. }
  265. },
  266. async handleImageUpload(event) {
  267. const file = event.target.files[0]
  268. if (!file) return
  269. try {
  270. const imageData = await this.processImage(file)
  271. this.$emit('image-upload', imageData)
  272. // 重置文件输入框的值,确保下次选择相同文件时也能触发change事件
  273. event.target.value = ''
  274. } catch (error) {
  275. this.$message.error('图片处理失败')
  276. // 即使出错也要重置文件输入框
  277. event.target.value = ''
  278. }
  279. },
  280. processImage(file) {
  281. return new Promise((resolve, reject) => {
  282. const reader = new FileReader()
  283. reader.onload = (e) => {
  284. const img = new Image()
  285. img.onload = () => {
  286. try {
  287. const { zplData, previewUrl } = this.convertImageToZPL(img)
  288. resolve({ zplData, previewUrl })
  289. } catch (error) {
  290. reject(error)
  291. }
  292. }
  293. img.onerror = reject
  294. img.src = e.target.result
  295. }
  296. reader.onerror = reject
  297. reader.readAsDataURL(file)
  298. })
  299. },
  300. convertImageToZPL(img) {
  301. const targetWidth = 200
  302. const scale = targetWidth / img.width
  303. const canvas = document.createElement('canvas')
  304. canvas.width = targetWidth
  305. canvas.height = Math.round(img.height * scale)
  306. const ctx = canvas.getContext('2d')
  307. ctx.fillStyle = '#fff'
  308. ctx.fillRect(0, 0, canvas.width, canvas.height)
  309. ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
  310. // 转换为ZPL格式
  311. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
  312. const { width, height, data } = imageData
  313. const bytesPerRow = Math.ceil(width / 8)
  314. const totalBytes = bytesPerRow * height
  315. let binaryData = ''
  316. for (let y = 0; y < height; y++) {
  317. for (let byteIdx = 0; byteIdx < bytesPerRow; byteIdx++) {
  318. let byteStr = ''
  319. for (let bit = 0; bit < 8; bit++) {
  320. const x = byteIdx * 8 + bit
  321. if (x >= width) {
  322. byteStr += '0'
  323. } else {
  324. const i = (y * width + x) * 4
  325. const r = data[i], g = data[i + 1], b = data[i + 2]
  326. const grayscale = (r + g + b) / 3
  327. byteStr += grayscale < 128 ? '1' : '0'
  328. }
  329. }
  330. const hex = parseInt(byteStr, 2).toString(16).padStart(2, '0').toUpperCase()
  331. binaryData += hex
  332. }
  333. }
  334. return {
  335. zplData: `${totalBytes},${totalBytes},${bytesPerRow},${binaryData}`,
  336. previewUrl: canvas.toDataURL()
  337. }
  338. },
  339. getSerialNumberPreview() {
  340. if (this.element.type !== 'serialNumber') return ''
  341. const prefix = this.element.prefix || ''
  342. const startValue = parseInt(this.element.startValue) || 1
  343. const digits = parseInt(this.element.digits) || 6
  344. const paddedNumber = startValue.toString().padStart(digits, '0')
  345. return `${prefix}${paddedNumber}`
  346. }
  347. }
  348. }
  349. </script>
  350. <style scoped>
  351. .property-form {
  352. overflow-y: auto;
  353. }
  354. .property-form::-webkit-scrollbar {
  355. width: 4px;
  356. }
  357. .property-form::-webkit-scrollbar-track {
  358. background: #f1f1f1;
  359. border-radius: 2px;
  360. }
  361. .property-form::-webkit-scrollbar-thumb {
  362. //background: #409eff;
  363. border-radius: 2px;
  364. }
  365. .form-section {
  366. padding: 10px;
  367. background: #fafafa;
  368. border-radius: 4px;
  369. border: 1px solid #e8e8e8;
  370. }
  371. .input-with-button {
  372. display: flex;
  373. gap: 6px;
  374. align-items: flex-start;
  375. }
  376. .input-with-button .el-input {
  377. flex: 1;
  378. }
  379. .input-with-button .el-button {
  380. font-size: 11px;
  381. padding: 5px 8px;
  382. height: 28px;
  383. }
  384. .form-tip {
  385. font-size: 11px;
  386. color: #909399;
  387. margin-top: 3px;
  388. padding: 3px 6px;
  389. background: #f8f9fa;
  390. border-radius: 3px;
  391. border-left: 2px solid #409eff;
  392. }
  393. .image-upload {
  394. margin-bottom: 8px;
  395. }
  396. .image-upload .el-button {
  397. width: 100%;
  398. height: 60px;
  399. border: 1px dashed #d9d9d9;
  400. border-radius: 4px;
  401. background: #fafafa;
  402. font-size: 12px;
  403. }
  404. .image-upload .el-button:hover {
  405. border-color: #409eff;
  406. background: #f0f9ff;
  407. }
  408. .image-preview {
  409. text-align: center;
  410. padding: 8px;
  411. background: #f8f9fa;
  412. border-radius: 4px;
  413. border: 1px solid #e8e8e8;
  414. }
  415. .image-preview img {
  416. max-width: 80px;
  417. max-height: 80px;
  418. border-radius: 4px;
  419. border: 1px solid #ddd;
  420. }
  421. /* Element UI 表单项紧凑化 */
  422. .el-form-item {
  423. margin-bottom: 2px;
  424. }
  425. .el-form-item:last-child {
  426. margin-bottom: 0;
  427. }
  428. .el-form-item__label {
  429. font-size: 12px !important;
  430. padding-bottom: 6px !important;
  431. line-height: 1.2 !important;
  432. color: #606266 !important;
  433. font-weight: 500 !important;
  434. }
  435. .el-input__inner {
  436. height: 32px !important;
  437. font-size: 13px !important;
  438. border-radius: 6px !important;
  439. border: 1px solid #dcdfe6 !important;
  440. padding: 0 12px !important;
  441. transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
  442. }
  443. .el-input__inner:hover {
  444. border-color: #c0c4cc !important;
  445. }
  446. .el-input__inner:focus {
  447. border-color: #409eff !important;
  448. outline: none !important;
  449. box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1) !important;
  450. }
  451. .el-input {
  452. width: 100% !important;
  453. }
  454. .el-input .el-input__inner {
  455. text-align: left !important;
  456. height: 32px !important;
  457. padding: 0 35px 0 12px !important;
  458. }
  459. .el-checkbox {
  460. font-size: 12px !important;
  461. height: 20px !important;
  462. line-height: 20px !important;
  463. }
  464. .el-checkbox__label {
  465. font-size: 12px !important;
  466. padding-left: 6px !important;
  467. }
  468. .el-checkbox__inner {
  469. width: 12px !important;
  470. height: 12px !important;
  471. }
  472. .el-checkbox__inner::after {
  473. height: 5px !important;
  474. left: 3px !important;
  475. top: 1px !important;
  476. width: 2px !important;
  477. }
  478. /* 行布局样式 */
  479. .form-row {
  480. display: flex;
  481. gap: 2px;
  482. align-items: flex-start;
  483. margin-bottom: 2px;
  484. }
  485. .form-row:last-child {
  486. margin-bottom: 0;
  487. }
  488. .form-item-half {
  489. flex: 1;
  490. margin-bottom: 0 !important;
  491. }
  492. .form-item-half .el-form-item__label {
  493. margin-bottom: 2px !important;
  494. }
  495. /* 数字输入框统一样式 */
  496. .form-item-half .el-input {
  497. width: 100% !important;
  498. }
  499. .form-item-half .el-input .el-input__inner {
  500. height: 32px !important;
  501. font-size: 13px !important;
  502. padding: 0 35px 0 12px !important;
  503. border: 1px solid #dcdfe6 !important;
  504. border-radius: 6px !important;
  505. transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
  506. }
  507. .form-item-half .el-input .el-input__inner:hover {
  508. border-color: #c0c4cc !important;
  509. }
  510. .form-item-half .el-input .el-input__inner:focus {
  511. border-color: #409eff !important;
  512. outline: none !important;
  513. box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1) !important;
  514. }
  515. .form-item-half .el-input .el-input__increase,
  516. .form-item-half .el-input .el-input__decrease {
  517. position: absolute !important;
  518. right: 1px !important;
  519. width: 32px !important;
  520. height: 15px !important;
  521. line-height: 15px !important;
  522. background: #f5f7fa !important;
  523. color: #606266 !important;
  524. cursor: pointer !important;
  525. font-size: 12px !important;
  526. border: none !important;
  527. border-radius: 0 !important;
  528. transition: all 0.2s !important;
  529. }
  530. .form-item-half .el-input .el-input__increase {
  531. top: 1px !important;
  532. border-radius: 0 5px 0 0 !important;
  533. }
  534. .form-item-half .el-input .el-input__decrease {
  535. bottom: 1px !important;
  536. border-radius: 0 0 5px 0 !important;
  537. }
  538. .form-item-half .el-input .el-input__increase:hover,
  539. .form-item-half .el-input .el-input__decrease:hover {
  540. background: #e6a23c !important;
  541. color: #fff !important;
  542. }
  543. .form-item-half .el-input .el-input__increase:active,
  544. .form-item-half .el-input .el-input__decrease:active {
  545. background: #cf9236 !important;
  546. }
  547. /* 复选框在行中的样式 */
  548. .form-item-half .el-checkbox {
  549. display: block !important;
  550. margin-bottom: 1px !important;
  551. margin-right: 0 !important;
  552. padding-top: 5px;
  553. padding-left: 1px;
  554. }
  555. .form-item-half .el-checkbox:last-child {
  556. margin-bottom: 0 !important;
  557. }
  558. /* 复选框组样式 */
  559. .checkbox-group {
  560. position: relative;
  561. display: flex;
  562. flex-direction: column;
  563. justify-content: flex-start;
  564. align-items: flex-start;
  565. }
  566. .checkbox-label {
  567. font-size: 12px !important;
  568. color: #606266;
  569. margin-bottom: 4px !important;
  570. font-weight: 500;
  571. line-height: 1.2 !important;
  572. height: 14px;
  573. padding-bottom: 4px !important;
  574. display: block;
  575. }
  576. .checkbox-group .el-checkbox {
  577. margin-bottom: 2px !important;
  578. align-self: flex-start;
  579. height: 16px !important;
  580. line-height: 16px !important;
  581. display: flex !important;
  582. align-items: center !important;
  583. }
  584. .checkbox-group .el-checkbox:last-child {
  585. margin-bottom: 0 !important;
  586. }
  587. .checkbox-group .el-checkbox .el-checkbox__input {
  588. line-height: 1 !important;
  589. }
  590. .checkbox-group .el-checkbox .el-checkbox__label {
  591. line-height: 1 !important;
  592. padding-left: 4px !important;
  593. }
  594. /* 字体大小行布局 */
  595. .font-size-row {
  596. display: flex;
  597. align-items: center;
  598. gap: 12px;
  599. }
  600. .font-size-input {
  601. width: 120px !important;
  602. flex-shrink: 0;
  603. }
  604. .font-size-input .el-input__inner {
  605. height: 32px !important;
  606. font-size: 13px !important;
  607. border: 1px solid #dcdfe6 !important;
  608. border-radius: 6px !important;
  609. padding: 0 35px 0 12px !important;
  610. background-color: #fff !important;
  611. transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
  612. }
  613. .font-size-input .el-input__inner:hover {
  614. border-color: #c0c4cc !important;
  615. }
  616. .font-size-input .el-input__inner:focus {
  617. border-color: #409eff !important;
  618. outline: none !important;
  619. box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1) !important;
  620. }
  621. .font-size-input .el-input__increase,
  622. .font-size-input .el-input__decrease {
  623. position: absolute !important;
  624. right: 1px !important;
  625. width: 32px !important;
  626. height: 15px !important;
  627. line-height: 15px !important;
  628. background: #f5f7fa !important;
  629. color: #606266 !important;
  630. cursor: pointer !important;
  631. font-size: 12px !important;
  632. border: none !important;
  633. border-radius: 0 !important;
  634. transition: all 0.2s !important;
  635. }
  636. .font-size-input .el-input__increase {
  637. top: 1px !important;
  638. border-radius: 0 5px 0 0 !important;
  639. }
  640. .font-size-input .el-input__decrease {
  641. bottom: 1px !important;
  642. border-radius: 0 0 5px 0 !important;
  643. }
  644. .font-size-input .el-input__increase:hover,
  645. .font-size-input .el-input__decrease:hover {
  646. background: #e6a23c !important;
  647. color: #fff !important;
  648. }
  649. .font-size-input .el-input__increase:active,
  650. .font-size-input .el-input__decrease:active {
  651. background: #cf9236 !important;
  652. }
  653. .inline-checkbox {
  654. margin: 0 !important;
  655. height: 28px !important;
  656. line-height: 28px !important;
  657. display: flex !important;
  658. align-items: center !important;
  659. }
  660. .inline-checkbox .el-checkbox__label {
  661. font-size: 12px !important;
  662. padding-left: 6px !important;
  663. line-height: 1 !important;
  664. }
  665. .inline-checkbox .el-checkbox__input {
  666. line-height: 1 !important;
  667. }
  668. /* 统一所有输入框样式 */
  669. .form-section .el-input {
  670. width: 150px !important;
  671. }
  672. .form-section .el-input .el-input__inner {
  673. height: 32px !important;
  674. font-size: 13px !important;
  675. border: 1px solid #dcdfe6 !important;
  676. border-radius: 6px !important;
  677. padding: 0 35px 0 12px !important;
  678. transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
  679. }
  680. .form-section .el-input .el-input__inner:hover {
  681. border-color: #c0c4cc !important;
  682. }
  683. .form-section .el-input .el-input__inner:focus {
  684. border-color: #409eff !important;
  685. outline: none !important;
  686. box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1) !important;
  687. }
  688. .form-section .el-input .el-input__increase,
  689. .form-section .el-input .el-input__decrease {
  690. position: absolute !important;
  691. right: 1px !important;
  692. width: 32px !important;
  693. height: 15px !important;
  694. line-height: 15px !important;
  695. background: #f5f7fa !important;
  696. color: #606266 !important;
  697. cursor: pointer !important;
  698. font-size: 12px !important;
  699. border: none !important;
  700. border-radius: 0 !important;
  701. transition: all 0.2s !important;
  702. }
  703. .form-section .el-input .el-input__increase {
  704. top: 1px !important;
  705. border-radius: 0 5px 0 0 !important;
  706. }
  707. .form-section .el-input .el-input__decrease {
  708. bottom: 1px !important;
  709. border-radius: 0 0 5px 0 !important;
  710. }
  711. .form-section .el-input .el-input__increase:hover,
  712. .form-section .el-input .el-input__decrease:hover {
  713. background: #e6a23c !important;
  714. color: #fff !important;
  715. }
  716. .form-section .el-input .el-input__increase:active,
  717. .form-section .el-input .el-input__decrease:active {
  718. background: #cf9236 !important;
  719. }
  720. </style>