Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

1032 wiersze
29 KiB

  1. <template>
  2. <view class="u-barcode" v-if="calcSizeDone">
  3. <up-canvas
  4. v-if="showCanvas && !error"
  5. ref="barcodeCanvas"
  6. :canvas-id="canvasId"
  7. :width="canvasWidth"
  8. :height="canvasHeight"
  9. bg-color="transparent"
  10. :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
  11. ></up-canvas>
  12. <image
  13. v-else-if="!showCanvas && !error"
  14. :src="barcodeImage"
  15. :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
  16. mode="aspectFit"
  17. />
  18. <view
  19. v-else
  20. class="error-container"
  21. :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
  22. >
  23. <text class="error-text">{{ error }}</text>
  24. </view>
  25. <up-canvas
  26. v-if="!useCanvas && calcSizeDone"
  27. ref="barcodeImageCanvas"
  28. class="u-barcode__hidden-canvas"
  29. :canvas-id="tempCanvasId"
  30. :width="canvasWidth"
  31. :height="canvasHeight"
  32. bg-color="transparent"
  33. ></up-canvas>
  34. </view>
  35. </template>
  36. <script>
  37. import { nextTick } from 'vue'
  38. import { t } from '../../libs/i18n'
  39. export default {
  40. name: 'u-barcode',
  41. props: {
  42. // 条码值
  43. value: {
  44. type: [String, Number],
  45. required: true
  46. },
  47. // 条码格式
  48. format: {
  49. type: String,
  50. default: 'auto',
  51. validator: function (value) {
  52. return [
  53. 'auto',
  54. 'CODE128', 'CODE128A', 'CODE128B', 'CODE128C',
  55. 'EAN13', 'EAN8', 'EAN5', 'EAN2',
  56. 'UPC', 'UPCA', 'UPCE',
  57. 'CODE39',
  58. 'ITF', 'ITF14',
  59. 'MSI', 'MSI10', 'MSI11', 'MSI1010', 'MSI1110',
  60. 'pharmacode',
  61. 'codabar'
  62. ].includes(value)
  63. }
  64. },
  65. // 宽度
  66. width: {
  67. type: Number,
  68. default: 200
  69. },
  70. // 高度
  71. height: {
  72. type: Number,
  73. default: 80
  74. },
  75. // 是否显示文本
  76. displayValue: {
  77. type: Boolean,
  78. default: true
  79. },
  80. // 文本内容
  81. text: {
  82. type: String,
  83. default: undefined
  84. },
  85. // 字体选项
  86. fontOptions: {
  87. type: String,
  88. default: ''
  89. },
  90. // 字体
  91. font: {
  92. type: String,
  93. default: 'monospace'
  94. },
  95. // 文本对齐方式
  96. textAlign: {
  97. type: String,
  98. default: 'center'
  99. },
  100. // 文本位置
  101. textPosition: {
  102. type: String,
  103. default: 'bottom'
  104. },
  105. // 文本边距
  106. textMargin: {
  107. type: Number,
  108. default: 2
  109. },
  110. // 字体大小
  111. fontSize: {
  112. type: Number,
  113. default: 14
  114. },
  115. // 背景色
  116. background: {
  117. type: String,
  118. default: '#ffffff'
  119. },
  120. // 条码颜色
  121. lineColor: {
  122. type: String,
  123. default: '#000000'
  124. },
  125. // 边距
  126. margin: {
  127. type: Number,
  128. default: 10
  129. },
  130. // 上边距
  131. marginTop: {
  132. type: Number,
  133. default: undefined
  134. },
  135. // 下边距
  136. marginBottom: {
  137. type: Number,
  138. default: undefined
  139. },
  140. // 左边距
  141. marginLeft: {
  142. type: Number,
  143. default: undefined
  144. },
  145. // 右边距
  146. marginRight: {
  147. type: Number,
  148. default: undefined
  149. },
  150. // 使用canvas还是生成图片
  151. useCanvas: {
  152. type: Boolean,
  153. default: true
  154. }
  155. },
  156. data() {
  157. return {
  158. canvasId: 'barcode-' + Math.random().toString(36).substr(2, 9),
  159. tempCanvasId: 'barcode-temp-' + Math.random().toString(36).substr(2, 9),
  160. barcodeImage: '',
  161. showCanvas: false,
  162. canvasWidth: 0,
  163. canvasHeight: 0,
  164. calcSizeDone: false,
  165. error: ''
  166. }
  167. },
  168. watch: {
  169. value() {
  170. this.generateBarcode()
  171. },
  172. format() {
  173. this.generateBarcode()
  174. },
  175. width() {
  176. this.generateBarcode()
  177. },
  178. height() {
  179. this.generateBarcode()
  180. },
  181. displayValue() {
  182. this.generateBarcode()
  183. },
  184. text() {
  185. this.generateBarcode()
  186. },
  187. font() {
  188. this.generateBarcode()
  189. },
  190. textAlign() {
  191. this.generateBarcode()
  192. },
  193. textPosition() {
  194. this.generateBarcode()
  195. },
  196. textMargin() {
  197. this.generateBarcode()
  198. },
  199. fontSize() {
  200. this.generateBarcode()
  201. },
  202. background() {
  203. this.generateBarcode()
  204. },
  205. lineColor() {
  206. this.generateBarcode()
  207. },
  208. margin() {
  209. this.generateBarcode()
  210. }
  211. },
  212. mounted() {
  213. /**
  214. * @author jry <ijry@qq.com>
  215. */
  216. this.$nextTick(() => {
  217. this.generateBarcode()
  218. })
  219. },
  220. methods: {
  221. /**
  222. * 生成条形码
  223. * @author jry <ijry@qq.com>
  224. * @param {String|Number} value - 条码值
  225. * @param {Object} options - 条码配置选项
  226. */
  227. generateBarcode() {
  228. // 统一处理默认值
  229. const margin = this.margin
  230. const options = {
  231. format: this.format || 'auto',
  232. width: this.width,
  233. height: this.height,
  234. displayValue: this.displayValue,
  235. text: this.text,
  236. fontOptions: this.fontOptions || '',
  237. font: this.font || 'monospace',
  238. textAlign: this.textAlign || 'center',
  239. textPosition: this.textPosition || 'bottom',
  240. textMargin: this.textMargin !== undefined ? this.textMargin : 2,
  241. fontSize: this.fontSize || 20,
  242. background: this.background || '#ffffff',
  243. lineColor: this.lineColor || '#000000',
  244. margin: margin,
  245. marginTop: this.marginTop !== undefined ? this.marginTop : margin,
  246. marginBottom: this.marginBottom !== undefined ? this.marginBottom : margin,
  247. marginLeft: this.marginLeft !== undefined ? this.marginLeft : margin,
  248. marginRight: this.marginRight !== undefined ? this.marginRight : margin
  249. }
  250. // 清理未定义的选项
  251. Object.keys(options).forEach(key => {
  252. if (options[key] === undefined) {
  253. delete options[key]
  254. }
  255. })
  256. if (this.useCanvas) {
  257. // 使用canvas渲染
  258. this.showCanvas = true
  259. this.$nextTick(() => {
  260. this.renderToCanvas(options)
  261. })
  262. } else {
  263. // 生成图片
  264. this.showCanvas = false
  265. this.renderToImage(options)
  266. }
  267. },
  268. /**
  269. * 渲染条形码到canvas
  270. * @author jry <ijry@qq.com>
  271. * @param {Object} options - 条码配置选项
  272. */
  273. async renderToCanvas(options) {
  274. try {
  275. // 计算canvas尺寸
  276. this.calculateCanvasSize(options)
  277. await nextTick()
  278. const ctx = await this.getCanvasRef('barcodeCanvas')
  279. // 清空画布
  280. ctx.setFillStyle(options.background)
  281. ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
  282. // 生成条形码数据
  283. const barcodeData = this.encodeBarcode(this.value, options.format)
  284. if (barcodeData) {
  285. // 绘制条形码
  286. this.drawBarcode(ctx, barcodeData, options)
  287. }
  288. // 绘制到canvas
  289. ctx.draw(false, () => {
  290. // 绘制完成回调
  291. this.$emit('rendered', { type: 'canvas', id: this.canvasId })
  292. })
  293. } catch (error) {
  294. console.error('生成条码失败:', error)
  295. this.error = error.message || t('up.barcode.error')
  296. this.$emit('error', error)
  297. }
  298. },
  299. /**
  300. * 渲染条形码为图片
  301. * @author jry <ijry@qq.com>
  302. * @param {Object} options - 条码配置选项
  303. */
  304. async renderToImage(options) {
  305. try {
  306. // 计算canvas尺寸
  307. this.calculateCanvasSize(options)
  308. await nextTick()
  309. const canvas = await this.getCanvasRef('barcodeImageCanvas')
  310. const ctx = canvas
  311. // 清空画布
  312. ctx.setFillStyle(options.background)
  313. ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
  314. // 生成条形码数据
  315. const barcodeData = this.encodeBarcode(this.value, options.format)
  316. if (barcodeData) {
  317. // 绘制条形码
  318. this.drawBarcode(ctx, barcodeData, options)
  319. }
  320. // 绘制到临时canvas并生成图片
  321. ctx.draw(false, () => {
  322. // 延迟一小段时间确保canvas绘制完成
  323. setTimeout(() => {
  324. canvas.toTempFilePath({
  325. width: this.canvasWidth,
  326. height: this.canvasHeight,
  327. destWidth: this.canvasWidth,
  328. destHeight: this.canvasHeight,
  329. success: (res) => {
  330. this.barcodeImage = res.tempFilePath
  331. this.$emit('rendered', { type: 'image', value: this.value, path: res.tempFilePath })
  332. },
  333. fail: (error) => {
  334. console.error('生成条码图片失败:', error)
  335. this.$emit('error', error)
  336. }
  337. })
  338. }, 100)
  339. })
  340. } catch (error) {
  341. console.error('生成条码图片失败:', error)
  342. this.$emit('error', error)
  343. }
  344. },
  345. async getCanvasRef(refName) {
  346. await nextTick()
  347. const canvas = this.$refs[refName]
  348. if (!canvas) {
  349. throw new Error(`Canvas ref not found: ${refName}`)
  350. }
  351. await canvas.initCanvas(true)
  352. return canvas
  353. },
  354. /**
  355. * 计算canvas尺寸
  356. * @author jry <ijry@qq.com>
  357. * @param {Object} options - 条码配置选项
  358. */
  359. calculateCanvasSize(options) {
  360. // 基础宽度计算
  361. let width = options.width
  362. let height = options.height
  363. // 考虑边距
  364. const marginLeft = options.marginLeft
  365. const marginRight = options.marginRight
  366. const marginTop = options.marginTop
  367. const marginBottom = options.marginBottom
  368. // 考虑文本高度
  369. let textHeight = 0
  370. if (options.displayValue !== false) {
  371. textHeight = options.fontSize + options.textMargin
  372. }
  373. // 根据文本位置调整高度
  374. if (options.textPosition === 'top' || options.textPosition === 'bottom') {
  375. height += textHeight
  376. }
  377. // 添加边距
  378. width += marginLeft + marginRight
  379. height += marginTop + marginBottom
  380. this.canvasWidth = Math.max(width, 100)
  381. this.canvasHeight = Math.max(height, 60 + textHeight)
  382. this.calcSizeDone = true
  383. },
  384. /**
  385. * 编码条形码数据
  386. * @author jry <ijry@qq.com>
  387. * @param {String|Number} value - 条码值
  388. * @param {String} format - 条码格式
  389. * @returns {String|null} 条形码编码数据
  390. */
  391. encodeBarcode(value, format) {
  392. try {
  393. switch (format) {
  394. case 'CODE128':
  395. case 'auto':
  396. return this.encodeCode128(value)
  397. case 'CODE39':
  398. return this.encodeCode39(value)
  399. case 'EAN13':
  400. return this.encodeEAN13(value)
  401. case 'EAN8':
  402. return this.encodeEAN8(value)
  403. case 'EAN5':
  404. case 'EAN2':
  405. return this.encodeEAN52(value, format)
  406. case 'UPC':
  407. case 'UPCA':
  408. return this.encodeUPCA(value)
  409. case 'UPCE':
  410. return this.encodeUPCE(value)
  411. default:
  412. // 默认使用CODE128
  413. return this.encodeCode128(value)
  414. }
  415. } catch (error) {
  416. console.error('条码编码失败:', error)
  417. throw error
  418. }
  419. },
  420. /**
  421. * 添加右侧安静区(至少2个模块宽度的空白)
  422. * @author jry <ijry@qq.com>
  423. * @param {String} data - 要编码的数据
  424. * @returns {String|null} 编码后的条形码数据
  425. */
  426. encodeCode128(data) {
  427. const CODE128_START_CODE_B = 104
  428. const CODE128_STOP = 106
  429. // CODE128 Code B 字符集
  430. const CODE128_CODE_B_CHARS = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
  431. // 条形码模式 (B 模式)
  432. const codes = []
  433. let checksum = CODE128_START_CODE_B
  434. // 添加起始字符
  435. codes.push(CODE128_START_CODE_B)
  436. // 编码每个字符
  437. for (let i = 0; i < data.length; i++) {
  438. const char = data[i]
  439. const code = CODE128_CODE_B_CHARS.indexOf(char)
  440. if (code === -1) {
  441. throw new Error('Invalid character in CODE128: ' + char)
  442. }
  443. codes.push(code)
  444. checksum += code * (i + 1)
  445. }
  446. // 添加校验字符
  447. codes.push(checksum % 103)
  448. // 添加结束字符
  449. codes.push(CODE128_STOP)
  450. // 转换为条形码模式 (1 = 黑条, 0 = 白条)
  451. let barcode = ''
  452. for (let i = 0; i < codes.length; i++) {
  453. const code = codes[i]
  454. barcode += this.getCode128Pattern(code)
  455. }
  456. // 添加右侧安静区(至少2个模块宽度的空白)
  457. barcode += '00000'
  458. return barcode
  459. },
  460. /**
  461. * 获取CODE128编码模式
  462. * @author jry <ijry@qq.com>
  463. * @param {Number} code - 字符编码
  464. * @returns {String} 条形码二进制模式
  465. */
  466. getCode128Pattern(code) {
  467. // CODE128编码表
  468. const patterns = [
  469. "11011001100", "11001101100", "11001100110", "10010011000",
  470. "10010001100", "10001001100", "10011001000", "10011000100",
  471. "10001100100", "11001001000", "11001000100", "11000100100",
  472. "10110011100", "10011011100", "10011001110", "10111001100",
  473. "10011101100", "10011100110", "11001110010", "11001011100",
  474. "11001001110", "11011100100", "11001110100", "11101101110",
  475. "11101001100", "11100101100", "11100100110", "11101100100",
  476. "11100110100", "11100110010", "11011011000", "11011000110",
  477. "11000110110", "10100011000", "10001011000", "10001000110",
  478. "10110001000", "10001101000", "10001100010", "11010001000",
  479. "11000101000", "11000100010", "10110111000", "10110001110",
  480. "10001101110", "10111011000", "10111000110", "10001110110",
  481. "11101110110", "11010001110", "11000101110", "11011101000",
  482. "11011100010", "11011101110", "11101011000", "11101000110",
  483. "11100010110", "11101101000", "11101100010", "11100011010",
  484. "11101111010", "11001000010", "11110001010", "10100110000",
  485. "10100001100", "10010110000", "10010000110", "10000101100",
  486. "10000100110", "10110010000", "10110000100", "10011010000",
  487. "10011000010", "10000110100", "10000110010", "11000010010",
  488. "11001010000", "11110111010", "11000010100", "10001111010",
  489. "10100111100", "10010111100", "10010011110", "10111100100",
  490. "10011110100", "10011110010", "11110100100", "11110010100",
  491. "11110010010", "11011011110", "11011110110", "11110110110",
  492. "10101111000", "10100011110", "10001011110", "10111101000",
  493. "10111100010", "11110101000", "11110100010", "10111011110",
  494. "10111101110", "11101011110", "11110101110", "11010000100",
  495. "11010010000", "11010011100", "11000111010"
  496. ];
  497. return patterns[code] || "";
  498. },
  499. /**
  500. * CODE39编码实现
  501. * @author jry <ijry@qq.com>
  502. * @param {String} data - 要编码的数据
  503. * @returns {String|null} 编码后的条形码数据
  504. */
  505. encodeCode39(data) {
  506. const codes = {
  507. '0': '101000111011101',
  508. '1': '111010001010111',
  509. '2': '101110001010111',
  510. '3': '111011100010101',
  511. '4': '101000111010111',
  512. '5': '111010001110101',
  513. '6': '101110001110101',
  514. '7': '101000101110111',
  515. '8': '111010001011101',
  516. '9': '101110001011101',
  517. 'A': '111010100010111',
  518. 'B': '101110100010111',
  519. 'C': '111011101000101',
  520. 'D': '101011100010111',
  521. 'E': '111010111000101',
  522. 'F': '101110111000101',
  523. 'G': '101010001110111',
  524. 'H': '111010100011101',
  525. 'I': '101110100011101',
  526. 'J': '101011100011101',
  527. 'K': '111010101000111',
  528. 'L': '101110101000111',
  529. 'M': '111011101010001',
  530. 'N': '101011101000111',
  531. 'O': '111010111010001',
  532. 'P': '101110111010001',
  533. 'Q': '101010111000111',
  534. 'R': '111010101110001',
  535. 'S': '101110101110001',
  536. 'T': '101011101110001',
  537. 'U': '111000101010111',
  538. 'V': '100011101010111',
  539. 'W': '111000111010101',
  540. 'X': '100010111010111',
  541. 'Y': '111000101110101',
  542. 'Z': '100011101110101',
  543. '-': '100010101110111',
  544. '.': '111000101011101',
  545. ' ': '100011101011101',
  546. '*': '100010111011101', // 起始和终止字符
  547. '$': '100010001000101',
  548. '/': '100010001010001',
  549. '+': '100010100010001',
  550. '%': '101000100010001'
  551. }
  552. // 转为大写
  553. data = data.toUpperCase()
  554. // 添加起始和终止字符
  555. let barcode = codes['*']
  556. // 添加数据字符
  557. for (let i = 0; i < data.length; i++) {
  558. const char = data[i]
  559. if (codes[char]) {
  560. barcode += '0' // 字符间隔
  561. barcode += codes[char]
  562. } else {
  563. throw new Error('Invalid character in CODE39: ' + char)
  564. }
  565. }
  566. // 添加终止字符
  567. barcode += '0' // 字符间隔
  568. barcode += codes['*']
  569. return barcode
  570. },
  571. /**
  572. * EAN13编码实现
  573. * @author jry <ijry@qq.com>
  574. * @param {String} data - 13位数字字符串
  575. * @returns {String|null} 编码后的条形码数据
  576. */
  577. encodeEAN13(data) {
  578. // 确保数据是13位数字
  579. if (!/^\d{13}$/.test(data)) {
  580. throw new Error('EAN13 must be 13 digits')
  581. }
  582. // 验证校验位
  583. let sum = 0
  584. for (let i = 0; i < 12; i++) {
  585. const digit = parseInt(data[i])
  586. sum += (i % 2 === 0) ? digit : digit * 3
  587. }
  588. const checkDigit = (10 - (sum % 10)) % 10
  589. if (parseInt(data[12]) !== checkDigit) {
  590. throw new Error('Invalid EAN13 check digit')
  591. }
  592. // 左侧数据
  593. const leftData = data.substring(1, 7)
  594. const rightData = data.substring(7, 13)
  595. // 起始符
  596. let barcode = '101'
  597. // 左侧数据编码 (根据第一位数字决定编码方式)
  598. const firstDigit = parseInt(data[0])
  599. const leftPatterns = [
  600. ['LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG',
  601. 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL']
  602. ]
  603. const pattern = leftPatterns[0][firstDigit]
  604. // 左侧奇偶编码模式
  605. const leftOdd = [
  606. '0001101', '0011001', '0010011', '0111101', '0100011',
  607. '0110001', '0101111', '0111011', '0110111', '0001011'
  608. ]
  609. const leftEven = [
  610. '0100111', '0110011', '0011011', '0100001', '0011101',
  611. '0111001', '0000101', '0010001', '0001001', '0010111'
  612. ]
  613. // 编码左侧数据
  614. for (let i = 0; i < leftData.length; i++) {
  615. const digit = parseInt(leftData[i])
  616. if (pattern[i] === 'L') {
  617. barcode += leftOdd[digit]
  618. } else {
  619. barcode += leftEven[digit]
  620. }
  621. }
  622. // 中间分隔符
  623. barcode += '01010'
  624. // 右侧数据编码 (始终使用右编码)
  625. const rightCodes = [
  626. '1110010', '1100110', '1101100', '1000010', '1011100',
  627. '1001110', '1010000', '1000100', '1001000', '1110100'
  628. ]
  629. for (let i = 0; i < rightData.length; i++) {
  630. const digit = parseInt(rightData[i])
  631. barcode += rightCodes[digit]
  632. }
  633. // 结束符
  634. barcode += '101'
  635. return barcode
  636. },
  637. /**
  638. * EAN8编码实现
  639. * @author jry <ijry@qq.com>
  640. * @param {String} data - 8位数字字符串
  641. * @returns {String|null} 编码后的条形码数据
  642. */
  643. encodeEAN8(data) {
  644. // 确保数据是8位数字
  645. if (!/^\d{8}$/.test(data)) {
  646. throw new Error('EAN8 must be 8 digits')
  647. }
  648. // 验证校验位
  649. let sum = 0
  650. for (let i = 0; i < 7; i++) {
  651. const digit = parseInt(data[i])
  652. sum += digit * (i % 2 === 0 ? 3 : 1)
  653. }
  654. const checkDigit = (10 - (sum % 10)) % 10
  655. if (parseInt(data[7]) !== checkDigit) {
  656. throw new Error('Invalid EAN8 check digit')
  657. }
  658. // 左侧数据(4位)
  659. const leftData = data.substring(0, 4)
  660. // 右侧数据(4位)
  661. const rightData = data.substring(4, 8)
  662. // 起始符
  663. let barcode = '101'
  664. // 左侧奇偶编码模式
  665. const leftOdd = [
  666. '0001101', '0011001', '0010011', '0111101', '0100011',
  667. '0110001', '0101111', '0111011', '0110111', '0001011'
  668. ]
  669. // 编码左侧数据
  670. for (let i = 0; i < leftData.length; i++) {
  671. const digit = parseInt(leftData[i])
  672. barcode += leftOdd[digit]
  673. }
  674. // 中间分隔符
  675. barcode += '01010'
  676. // 右侧数据编码 (始终使用右编码)
  677. const rightCodes = [
  678. '1110010', '1100110', '1101100', '1000010', '1011100',
  679. '1001110', '1010000', '1000100', '1001000', '1110100'
  680. ]
  681. // 编码右侧数据
  682. for (let i = 0; i < rightData.length; i++) {
  683. const digit = parseInt(rightData[i])
  684. barcode += rightCodes[digit]
  685. }
  686. // 结束符
  687. barcode += '101'
  688. return barcode
  689. },
  690. /**
  691. * EAN5/EAN2编码实现
  692. * @author jry <ijry@qq.com>
  693. * @param {String} data - 2位或5位数字字符串
  694. * @param {String} format - 格式类型(EAN5或EAN2)
  695. * @returns {String|null} 编码后的条形码数据
  696. */
  697. encodeEAN52(data, format) {
  698. const length = format === 'EAN5' ? 5 : 2
  699. // 确保数据是相应位数的数字
  700. if (!new RegExp(`^\\d{${length}}$`).test(data)) {
  701. throw new Error(`${format} must be ${length} digits`)
  702. }
  703. // EAN5/2编码表
  704. const codes = [
  705. '0001101', '0011001', '0010011', '0111101', '0100011',
  706. '0110001', '0101111', '0111011', '0110111', '0001011'
  707. ]
  708. // 计算校验和
  709. let checksum = 0
  710. for (let i = 0; i < data.length; i++) {
  711. checksum += parseInt(data[i]) * (i % 2 === 0 ? 3 : 1)
  712. }
  713. // 根据校验和确定编码模式
  714. const patterns = format === 'EAN5' ?
  715. ['00001', '00010', '00100', '01000', '10000', // 0-4
  716. '00000', '00011', '00101', '00110', '01001', // 5-9
  717. '01010', '01100', '10001', '10010', '10100', // 10-14
  718. '11000', '11001', '11010', '11100'] : // 15-18
  719. ['00', '01', '10', '11'] // EAN2只有4种模式
  720. const pattern = format === 'EAN5' ?
  721. patterns[checksum % 10] :
  722. patterns[parseInt(data) % 4]
  723. // 起始符
  724. let barcode = '1011'
  725. // 编码数据
  726. for (let i = 0; i < data.length; i++) {
  727. // 添加分隔符(除了第一个字符)
  728. if (i > 0) {
  729. barcode += '01' // 字符间分隔符
  730. }
  731. // 根据模式确定编码方式
  732. const digit = parseInt(data[i])
  733. const code = codes[digit]
  734. barcode += code
  735. }
  736. return barcode
  737. },
  738. /**
  739. * UPCA编码实现
  740. * @author jry <ijry@qq.com>
  741. * @param {String} data - 11位或12位数字字符串
  742. * @returns {String|null} 编码后的条形码数据
  743. */
  744. encodeUPCA(data) {
  745. // 如果是11位,计算校验位
  746. if (/^\d{11}$/.test(data)) {
  747. let sum = 0
  748. for (let i = 0; i < 11; i++) {
  749. const digit = parseInt(data[i])
  750. sum += (i % 2 === 0) ? digit * 3 : digit
  751. }
  752. const checkDigit = (10 - (sum % 10)) % 10
  753. data += checkDigit
  754. }
  755. // 确保数据是12位数字
  756. if (!/^\d{12}$/.test(data)) {
  757. throw new Error('UPC-A must be 11 or 12 digits')
  758. }
  759. // UPCA实际上是EAN13的第一个数字为0的特殊情况
  760. return this.encodeEAN13('0' + data)
  761. },
  762. /**
  763. * UPCE编码实现
  764. * @author jry <ijry@qq.com>
  765. * @param {String} data - 6位或8位数字字符串
  766. * @returns {String|null} 编码后的条形码数据
  767. */
  768. encodeUPCE(data) {
  769. // 如果是7位,计算校验位
  770. if (/^\d{7}$/.test(data)) {
  771. let sum = 0
  772. for (let i = 0; i < 7; i++) {
  773. const digit = parseInt(data[i])
  774. sum += (i % 2 === 0) ? digit * 3 : digit
  775. }
  776. const checkDigit = (10 - (sum % 10)) % 10
  777. data += checkDigit
  778. }
  779. // 确保数据是8位数字
  780. if (!/^\d{8}$/.test(data)) {
  781. throw new Error('UPC-E must be 7 or 8 digits')
  782. }
  783. // 检查是否是有效的UPC-E格式
  784. if (data[0] !== '0' && data[0] !== '1') {
  785. throw new Error('UPC-E must start with 0 or 1')
  786. }
  787. // UPCE编码表
  788. const leftOdd = [
  789. '0001101', '0011001', '0010011', '0111101', '0100011',
  790. '0110001', '0101111', '0111011', '0110111', '0001011'
  791. ]
  792. const leftEven = [
  793. '0100111', '0110011', '0011011', '0100001', '0011101',
  794. '0111001', '0000101', '0010001', '0001001', '0010111'
  795. ]
  796. // 根据系统数字确定编码模式
  797. const systemDigit = data[0]
  798. const checkDigit = data[7]
  799. // 提取中间6位
  800. const middleData = data.substring(1, 7)
  801. // 确定编码模式
  802. let pattern
  803. if (checkDigit === '0' || checkDigit === '1' || checkDigit === '2') {
  804. pattern = 'EEEEOO' // 0,1,2
  805. } else if (checkDigit === '3') {
  806. pattern = 'EEEEOO' // 3
  807. } else if (checkDigit === '4') {
  808. pattern = 'EEEOOO' // 4
  809. } else {
  810. pattern = 'EEOOOO' // 5,6,7,8,9
  811. }
  812. // 起始符
  813. let barcode = '101'
  814. // 编码中间6位数据
  815. for (let i = 0; i < middleData.length; i++) {
  816. const digit = parseInt(middleData[i])
  817. if (pattern[i] === 'E') {
  818. barcode += leftEven[digit]
  819. } else {
  820. barcode += leftOdd[digit]
  821. }
  822. }
  823. // 中间分隔符
  824. barcode += '010101'
  825. // 结束符
  826. barcode += '101'
  827. return barcode
  828. },
  829. /**
  830. * 绘制条形码
  831. * @author jry <ijry@qq.com>
  832. * @param {Object} ctx - canvas上下文
  833. * @param {String} barcodeData - 条形码数据
  834. * @param {Object} options - 条码配置选项
  835. */
  836. drawBarcode(ctx, barcodeData, options) {
  837. if (!barcodeData) return
  838. const marginLeft = options.marginLeft
  839. const marginTop = options.marginTop
  840. const marginBottom = options.marginBottom
  841. const textHeight = options.displayValue !== false ? options.fontSize + options.textMargin : 0
  842. const height = options.height
  843. // 恢复: 根据总长度计算模块宽度
  844. const moduleWidth = Math.max(1, (this.canvasWidth - marginLeft - (options.marginRight || 10)) / barcodeData.length)
  845. ctx.setFillStyle(options.lineColor)
  846. // 计算条形码绘制的Y坐标
  847. let barcodeY = marginTop
  848. // 如果文本在顶部,需要调整条形码绘制位置
  849. if (options.displayValue !== false && options.textPosition === 'top') {
  850. barcodeY += textHeight
  851. }
  852. // 恢复: 使用计算出的模块宽度绘制条形码
  853. let x = marginLeft
  854. for (let i = 0; i < barcodeData.length; i++) {
  855. if (barcodeData[i] === '1') {
  856. // 使用计算的模块宽度绘制每个条
  857. ctx.fillRect(x, barcodeY, moduleWidth, height)
  858. }
  859. // 每个条/空都占用一个模块宽度
  860. x += moduleWidth
  861. }
  862. // 绘制文本
  863. if (options.displayValue !== false) {
  864. const text = options.text || this.value
  865. let textY
  866. ctx.setFillStyle(options.lineColor)
  867. ctx.setFontSize(options.fontSize)
  868. ctx.setTextAlign(options.textAlign)
  869. let textX
  870. switch (options.textAlign) {
  871. case 'left':
  872. textX = marginLeft
  873. break
  874. case 'right':
  875. textX = this.canvasWidth - options.marginRight
  876. break
  877. default: // center
  878. textX = this.canvasWidth / 2
  879. }
  880. // 根据文本位置确定Y坐标
  881. if (options.textPosition === 'top') {
  882. textY = marginTop + options.fontSize - 3
  883. } else { // bottom
  884. // 修复:正确计算底部文本位置,确保文本完全显示
  885. textY = barcodeY + height + options.textMargin + options.fontSize
  886. // 确保文本不会超出画布边界
  887. if (textY > this.canvasHeight - marginBottom) {
  888. textY = this.canvasHeight - marginBottom - 2
  889. }
  890. }
  891. ctx.fillText(text, textX, textY)
  892. }
  893. }
  894. }
  895. }
  896. </script>
  897. <style scoped>
  898. .u-barcode {
  899. display: flex;
  900. flex-direction: row;
  901. justify-content: center;
  902. align-items: center;
  903. }
  904. .u-barcode__hidden-canvas {
  905. position: fixed;
  906. top: -10000px;
  907. left: -10000px;
  908. z-index: -1;
  909. }
  910. .error-container {
  911. display: flex;
  912. justify-content: center;
  913. align-items: center;
  914. background-color: #f0f0f0;
  915. color: #ff0000;
  916. }
  917. .error-text {
  918. font-size: 14px;
  919. }
  920. </style>