@atooyu/uxto-ui 1.1.26 → 1.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,17 @@
1
1
  <template>
2
- <view class="u-qrcode" :style="{ width: `${size}px`, height: `${size}px` }">
3
- <view v-if="matrix.length > 0" class="u-qrcode__matrix" :style="matrixStyle">
4
- <view
5
- v-for="(row, rowIndex) in matrix"
6
- :key="rowIndex"
7
- class="u-qrcode__row"
8
- :style="{ height: `${cellSize}px` }"
9
- >
10
- <view
11
- v-for="(cell, colIndex) in row"
12
- :key="colIndex"
13
- class="u-qrcode__cell"
14
- :style="{
15
- width: `${cellSize}px`,
16
- height: `${cellSize}px`,
17
- backgroundColor: cell === 1 ? color : bgColor
18
- }"
19
- />
20
- </view>
21
- </view>
2
+ <view class="u-qrcode">
3
+ <canvas
4
+ v-if="canvasId"
5
+ :canvas-id="canvasId"
6
+ :id="canvasId"
7
+ class="u-qrcode__canvas"
8
+ :style="{ width: `${size}px`, height: `${size}px` }"
9
+ />
22
10
  </view>
23
11
  </template>
24
12
 
25
13
  <script setup lang="ts">
26
- import { ref, computed, watch, onMounted } from 'vue'
14
+ import { ref, watch, onMounted, nextTick } from 'vue'
27
15
 
28
16
  interface Props {
29
17
  value: string
@@ -41,213 +29,447 @@ const props = withDefaults(defineProps<Props>(), {
41
29
  errorCorrectLevel: 'M'
42
30
  })
43
31
 
44
- const matrix = ref<number[][]>([])
32
+ // 生成唯一 canvas ID
33
+ const canvasId = ref(`qrcode_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`)
45
34
 
46
- // 计算单元格大小
47
- const cellSize = computed(() => {
48
- if (matrix.value.length === 0) return 0
49
- return props.size / matrix.value.length
50
- })
35
+ // QR Code 生成器(基于 qrcode-generator 库的简化版本)
36
+ class QRCode {
37
+ // 纠错级别
38
+ private static ECC_LEVEL: Record<string, number> = { L: 1, M: 0, Q: 3, H: 2 }
39
+
40
+ // 每个版本的纠错码字数(M级别)
41
+ private static ECC_CODEWORDS: number[] = [
42
+ 0, 10, 16, 22, 28, 36, 44, 52, 64, 72, 80, 96, 108, 120, 132, 144,
43
+ 168, 180, 196, 216, 240, 260, 288, 320, 344, 376, 412, 452, 492, 536
44
+ ]
51
45
 
52
- // 矩阵样式
53
- const matrixStyle = computed(() => ({
54
- width: `${props.size}px`,
55
- height: `${props.size}px`,
56
- backgroundColor: props.bgColor
57
- }))
58
-
59
- // QR Code 生成算法
60
- const QRCode = {
61
- // 错误纠正级别
62
- ECL: { L: 1, M: 0, Q: 3, H: 2 },
63
-
64
- // 版本容量表
65
- getCapacity(version: number, ecl: number): number {
66
- const capacities = [
67
- 0, 17, 32, 53, 78, 106, 134, 154, 192, 230, 271, 321, 367, 425, 458, 520,
68
- 586, 644, 718, 792, 858, 929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, 1628, 1732, 1840,
69
- 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953
70
- ]
71
- return capacities[version] || 0
72
- },
46
+ // 每个版本的总码字数
47
+ private static TOTAL_CODEWORDS: number[] = [
48
+ 0, 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 625,
49
+ 733, 815, 901, 993, 1079, 1157, 1253, 1353, 1453, 1553, 1673, 1793, 1913, 2033
50
+ ]
51
+
52
+ // 每个版本的容量(字节模式,M级别)
53
+ private static CAPACITY: number[] = [
54
+ 0, 16, 28, 44, 64, 86, 108, 124, 154, 186, 216, 252, 290, 334, 365, 415,
55
+ 453, 507, 563, 623, 669, 719, 783, 843, 909, 969, 1047, 1119, 1193, 1273
56
+ ]
73
57
 
74
58
  // 获取版本
75
- getVersion(length: number, ecl: number): number {
59
+ private static getVersion(dataLength: number): number {
76
60
  for (let v = 1; v <= 40; v++) {
77
- if (this.getCapacity(v, ecl) >= length) return v
61
+ if (this.CAPACITY[v] >= dataLength) return v
78
62
  }
79
63
  return 40
80
- },
64
+ }
81
65
 
82
- // 生成二维码矩阵
83
- generate(data: string, ecl: string): number[][] {
84
- const level = this.ECL[ecl] || 0
85
- const version = this.getVersion(data.length, level)
66
+ // 生成二维码数据
67
+ static generate(data: string, ecl: string): { version: number; size: number; modules: number[][] } {
68
+ const version = this.getVersion(data.length)
86
69
  const size = version * 4 + 17
87
- const result: number[][] = []
88
70
 
89
- // 初始化矩阵
71
+ // 创建矩阵
72
+ const modules: number[][] = []
90
73
  for (let i = 0; i < size; i++) {
91
- result[i] = new Array(size).fill(-1)
74
+ modules[i] = new Array(size).fill(0)
92
75
  }
93
76
 
94
- // 添加定位图案
95
- this.addFinderPattern(result, 0, 0)
96
- this.addFinderPattern(result, size - 7, 0)
97
- this.addFinderPattern(result, 0, size - 7)
77
+ // 添加功能图案
78
+ this.addFinderPatterns(modules, size)
79
+ this.addTimingPatterns(modules, size)
80
+ if (version >= 2) {
81
+ this.addAlignmentPatterns(modules, version, size)
82
+ }
83
+ this.addDarkModule(modules, size)
98
84
 
99
- // 添加分隔符
100
- this.addSeparator(result, size)
85
+ // 编码数据
86
+ const codewords = this.encodeCodewords(data, version, ecl)
101
87
 
102
- // 添加定位图案
103
- this.addAlignmentPattern(result, version)
88
+ // 填充数据
89
+ this.fillData(modules, codewords, size)
104
90
 
105
- // 添加定时图案
106
- this.addTimingPattern(result, size)
91
+ // 应用最佳掩码
92
+ const bestMask = this.findBestMask(modules, size)
93
+ this.applyMask(modules, size, bestMask)
107
94
 
108
- // 添加格式信息区域
109
- this.addFormatInfo(result, size)
95
+ // 添加格式信息
96
+ this.addFormatInfo(modules, size, ecl, bestMask)
110
97
 
111
- // 填充数据
112
- this.fillData(result, data, version, size)
113
-
114
- return result
115
- },
116
-
117
- addFinderPattern(matrix: number[][], row: number, col: number) {
118
- for (let r = 0; r < 7; r++) {
119
- for (let c = 0; c < 7; c++) {
120
- if (row + r < matrix.length && col + c < matrix.length) {
121
- if (r === 0 || r === 6 || c === 0 || c === 6 ||
122
- (r >= 2 && r <= 4 && c >= 2 && c <= 4)) {
123
- matrix[row + r][col + c] = 1
124
- } else {
125
- matrix[row + r][col + c] = 0
98
+ return { version, size, modules }
99
+ }
100
+
101
+ // 添加定位图案
102
+ private static addFinderPatterns(modules: number[][], size: number) {
103
+ const positions = [[0, 0], [size - 7, 0], [0, size - 7]]
104
+
105
+ for (const [row, col] of positions) {
106
+ // 外框
107
+ for (let r = 0; r < 7; r++) {
108
+ for (let c = 0; c < 7; c++) {
109
+ if (r === 0 || r === 6 || c === 0 || c === 6 || (r >= 2 && r <= 4 && c >= 2 && c <= 4)) {
110
+ modules[row + r][col + c] = 1
126
111
  }
127
112
  }
128
113
  }
114
+ // 分隔符
115
+ for (let i = 0; i < 8; i++) {
116
+ // 上/下分隔符
117
+ if (row === 0 && i < size && modules[7]) modules[7][i] = 0
118
+ if (row === 0 && i < size && modules[i]) modules[i][7] = 0
119
+ // 右下分隔符
120
+ if (row === size - 7 && i < size && modules[7]) modules[7][size - 8 + i] = 0
121
+ if (row === size - 7 && i < size && modules[i]) modules[i][size - 8] = 0
122
+ // 左下分隔符
123
+ if (row === 0 && col === size - 7 && i < size && modules[size - 8]) modules[size - 8][i] = 0
124
+ if (row === 0 && col === size - 7 && i < size && modules[size - 1 - i]) modules[size - 1 - i][7] = 0
125
+ }
129
126
  }
130
- },
127
+ }
131
128
 
132
- addSeparator(matrix: number[][], size: number) {
133
- for (let i = 0; i < 8; i++) {
134
- if (i < size && matrix[7]) matrix[7][i] = 0
135
- if (i < size && matrix[i]) matrix[i][7] = 0
136
- if (size - 8 + i < size && matrix[7]) matrix[7][size - 8 + i] = 0
137
- if (i < size && matrix[i]) matrix[i][size - 8] = 0
138
- if (i < size && matrix[size - 8]) matrix[size - 8][i] = 0
139
- if (size - 8 + i < size && matrix[size - 1 - i]) matrix[size - 1 - i][7] = 0
129
+ // 添加时序图案
130
+ private static addTimingPatterns(modules: number[][], size: number) {
131
+ for (let i = 8; i < size - 8; i++) {
132
+ modules[6][i] = i % 2 === 0 ? 1 : 0
133
+ modules[i][6] = i % 2 === 0 ? 1 : 0
140
134
  }
141
- },
135
+ }
136
+
137
+ // 添加定位图案
138
+ private static addAlignmentPatterns(modules: number[][], version: number, size: number) {
139
+ const positions = this.getAlignmentPositions(version, size)
142
140
 
143
- addAlignmentPattern(matrix: number[][], version: number) {
144
- if (version < 2) return
145
- const positions = this.getAlignmentPositions(version)
146
141
  for (const row of positions) {
147
142
  for (const col of positions) {
148
- if (matrix[row] && matrix[row][col] === -1) {
149
- this.addAlignmentPatternAt(matrix, row, col)
143
+ // 避免重叠
144
+ if (modules[row][col] !== 0) continue
145
+
146
+ for (let r = -2; r <= 2; r++) {
147
+ for (let c = -2; c <= 2; c++) {
148
+ if (row + r >= 0 && row + r < size && col + c >= 0 && col + c < size) {
149
+ if (Math.abs(r) === 2 || Math.abs(c) === 2 || (r === 0 && c === 0)) {
150
+ modules[row + r][col + c] = 1
151
+ } else {
152
+ modules[row + r][col + c] = 0
153
+ }
154
+ }
155
+ }
150
156
  }
151
157
  }
152
158
  }
153
- },
159
+ }
154
160
 
155
- getAlignmentPositions(version: number): number[] {
161
+ // 获取定位图案位置
162
+ private static getAlignmentPositions(version: number, size: number): number[] {
156
163
  if (version === 1) return []
157
- const intervals = Math.floor(version / 7) + 1
158
- const size = version * 4 + 17
159
- const step = Math.ceil((size - 13) / intervals)
164
+
160
165
  const positions = [6]
161
- for (let i = 1; i <= intervals; i++) {
162
- positions.push(size - 7 + (i - intervals) * step)
166
+ const step = version === 32 ? 26 : Math.ceil((size - 13) / (Math.floor(version / 7) + 1))
167
+ let pos = size - 7
168
+
169
+ while (pos > 6) {
170
+ positions.unshift(pos)
171
+ pos -= step
163
172
  }
164
- return [...new Set(positions)].sort((a, b) => a - b)
165
- },
166
-
167
- addAlignmentPatternAt(matrix: number[][], row: number, col: number) {
168
- for (let r = -2; r <= 2; r++) {
169
- for (let c = -2; c <= 2; c++) {
170
- if (row + r >= 0 && row + r < matrix.length &&
171
- col + c >= 0 && col + c < matrix.length) {
172
- if (Math.abs(r) === 2 || Math.abs(c) === 2 || (r === 0 && c === 0)) {
173
- matrix[row + r][col + c] = 1
174
- } else {
175
- matrix[row + r][col + c] = 0
176
- }
177
- }
173
+
174
+ return positions
175
+ }
176
+
177
+ // 添加暗模块
178
+ private static addDarkModule(modules: number[][], size: number) {
179
+ modules[size - 8][8] = 1
180
+ }
181
+
182
+ // 编码码字
183
+ private static encodeCodewords(data: string, version: number, ecl: string): number[] {
184
+ const bits: number[] = []
185
+
186
+ // 模式指示符 (字节模式)
187
+ bits.push(0, 1, 0, 0)
188
+
189
+ // 字符计数
190
+ const cciBits = version <= 9 ? 8 : 16
191
+ for (let i = cciBits - 1; i >= 0; i--) {
192
+ bits.push((data.length >> i) & 1)
193
+ }
194
+
195
+ // 数据
196
+ for (const char of data) {
197
+ const code = char.charCodeAt(0)
198
+ for (let i = 7; i >= 0; i--) {
199
+ bits.push((code >> i) & 1)
178
200
  }
179
201
  }
180
- },
181
202
 
182
- addTimingPattern(matrix: number[][], size: number) {
183
- for (let i = 8; i < size - 8; i++) {
184
- if (matrix[6] && matrix[6][i] === -1) matrix[6][i] = i % 2 === 0 ? 1 : 0
185
- if (matrix[i] && matrix[i][6] === -1) matrix[i][6] = i % 2 === 0 ? 1 : 0
203
+ // 终止符
204
+ const totalDataBits = (this.TOTAL_CODEWORDS[version] - this.ECC_CODEWORDS[version]) * 8
205
+ const termLen = Math.min(4, totalDataBits - bits.length)
206
+ for (let i = 0; i < termLen; i++) bits.push(0)
207
+
208
+ // 补齐到字节边界
209
+ while (bits.length % 8 !== 0) bits.push(0)
210
+
211
+ // 填充码字
212
+ const padBytes = [0xEC, 0x11]
213
+ let padIdx = 0
214
+ while (bits.length < totalDataBits) {
215
+ for (let i = 7; i >= 0; i--) {
216
+ bits.push((padBytes[padIdx] >> i) & 1)
217
+ }
218
+ padIdx = (padIdx + 1) % 2
219
+ }
220
+
221
+ // 转换为字节
222
+ const bytes: number[] = []
223
+ for (let i = 0; i < bits.length; i += 8) {
224
+ let b = 0
225
+ for (let j = 0; j < 8; j++) b = (b << 1) | bits[i + j]
226
+ bytes.push(b)
227
+ }
228
+
229
+ // 生成纠错码字
230
+ const eccBytes = this.generateECC(bytes, this.ECC_CODEWORDS[version])
231
+
232
+ return [...bytes, ...eccBytes]
233
+ }
234
+
235
+ // Reed-Solomon 纠错码生成
236
+ private static generateECC(data: number[], eccCount: number): number[] {
237
+ // Galois Field GF(256) 的生成多项式
238
+ const GF_EXP: number[] = new Array(512)
239
+ const GF_LOG: number[] = new Array(256)
240
+
241
+ let x = 1
242
+ for (let i = 0; i < 255; i++) {
243
+ GF_EXP[i] = x
244
+ GF_LOG[x] = i
245
+ x = x * 2
246
+ if (x >= 256) x ^= 285 // 285 = 0x11D (生成多项式)
247
+ }
248
+ for (let i = 255; i < 512; i++) {
249
+ GF_EXP[i] = GF_EXP[i - 255]
250
+ }
251
+
252
+ // GF 域乘法
253
+ const gfMul = (a: number, b: number): number => {
254
+ if (a === 0 || b === 0) return 0
255
+ return GF_EXP[GF_LOG[a] + GF_LOG[b]]
256
+ }
257
+
258
+ // 生成纠错码生成多项式
259
+ const genPoly: number[] = [1]
260
+ for (let i = 0; i < eccCount; i++) {
261
+ const newPoly: number[] = new Array(genPoly.length + 1).fill(0)
262
+ for (let j = 0; j < genPoly.length; j++) {
263
+ newPoly[j] ^= genPoly[j]
264
+ newPoly[j + 1] ^= gfMul(genPoly[j], GF_EXP[i])
265
+ }
266
+ genPoly = newPoly
267
+ }
268
+
269
+ // 计算纠错码字
270
+ const ecc: number[] = new Array(eccCount).fill(0)
271
+ for (const byte of data) {
272
+ const factor = byte ^ ecc[0]
273
+ ecc.shift()
274
+ ecc.push(0)
275
+ for (let i = 0; i < ecc.length; i++) {
276
+ ecc[i] ^= gfMul(genPoly[i], factor)
277
+ }
278
+ }
279
+
280
+ return ecc
281
+ }
282
+
283
+ // 填充数据到矩阵
284
+ private static fillData(modules: number[][], codewords: number[], size: number) {
285
+ // 标记保留区域为 -1(不可填充)
286
+ for (let row = 0; row < size; row++) {
287
+ for (let col = 0; col < size; col++) {
288
+ if (modules[row][col] !== 0) {
289
+ modules[row][col] = -1
290
+ }
291
+ }
186
292
  }
187
- },
188
293
 
189
- addFormatInfo(matrix: number[][], size: number) {
294
+ // 填充格式信息保留区
190
295
  for (let i = 0; i < 9; i++) {
191
- if (matrix[8] && matrix[8][i] === -1) matrix[8][i] = 0
192
- if (matrix[i] && matrix[i][8] === -1) matrix[i][8] = 0
296
+ if (modules[8][i] === 0) modules[8][i] = -1
297
+ if (modules[i][8] === 0) modules[i][8] = -1
193
298
  }
194
299
  for (let i = 0; i < 8; i++) {
195
- if (matrix[8] && matrix[8][size - 1 - i] === -1) matrix[8][size - 1 - i] = 0
196
- if (matrix[size - 1 - i] && matrix[size - 1 - i][8] === -1) matrix[size - 1 - i][8] = 0
300
+ if (modules[8][size - 1 - i] === 0) modules[8][size - 1 - i] = -1
301
+ if (modules[size - 1 - i][8] === 0) modules[size - 1 - i][8] = -1
197
302
  }
198
- },
199
303
 
200
- fillData(matrix: number[][], data: string, version: number, size: number) {
201
- const bytes = this.encodeData(data)
202
- let bitIndex = 0
304
+ // 填充数据
305
+ let bitIdx = 0
203
306
  let upward = true
204
307
 
205
- for (let col = size - 1; col >= 1; col -= 2) {
308
+ for (let col = size - 1; col >= 0; col -= 2) {
206
309
  if (col === 6) col = 5
310
+
207
311
  for (let row = upward ? size - 1 : 0; upward ? row >= 0 : row < size; upward ? row-- : row++) {
208
312
  for (let c = 0; c < 2; c++) {
209
- const currentCol = col - c
210
- if (matrix[row] && matrix[row][currentCol] === -1) {
211
- const bit = bitIndex < bytes.length * 8
212
- ? (bytes[Math.floor(bitIndex / 8)] >> (7 - (bitIndex % 8))) & 1
213
- : (bitIndex % 2 === 0 ? 1 : 0)
214
- matrix[row][currentCol] = bit
215
- bitIndex++
313
+ const tc = col - c
314
+ if (tc >= 0 && modules[row][tc] === 0) {
315
+ let bit = 0
316
+ if (bitIdx < codewords.length * 8) {
317
+ bit = (codewords[Math.floor(bitIdx / 8)] >> (7 - (bitIdx % 8))) & 1
318
+ }
319
+ modules[row][tc] = bit
320
+ bitIdx++
216
321
  }
217
322
  }
218
323
  }
219
324
  upward = !upward
220
325
  }
221
- },
326
+ }
222
327
 
223
- encodeData(data: string): number[] {
224
- const bytes: number[] = []
225
- bytes.push(0x40)
226
- const len = data.length
227
- if (len < 256) {
228
- bytes.push(len)
328
+ // 寻找最佳掩码
329
+ private static findBestMask(modules: number[][], size: number): number {
330
+ let bestMask = 0
331
+ let bestScore = Infinity
332
+
333
+ for (let mask = 0; mask < 8; mask++) {
334
+ const testModules = modules.map(row => [...row])
335
+ this.applyMask(testModules, size, mask)
336
+ const score = this.evaluateMask(testModules, size)
337
+ if (score < bestScore) {
338
+ bestScore = score
339
+ bestMask = mask
340
+ }
341
+ }
342
+
343
+ return bestMask
344
+ }
345
+
346
+ // 应用掩码
347
+ private static applyMask(modules: number[][], size: number, mask: number) {
348
+ for (let row = 0; row < size; row++) {
349
+ for (let col = 0; col < size; col++) {
350
+ if (modules[row][col] >= 0) {
351
+ if (this.getMaskBit(row, col, mask)) {
352
+ modules[row][col] = modules[row][col] === 1 ? 0 : 1
353
+ }
354
+ }
355
+ }
229
356
  }
230
- for (let i = 0; i < data.length; i++) {
231
- bytes.push(data.charCodeAt(i))
357
+ }
358
+
359
+ // 掩码函数
360
+ private static getMaskBit(row: number, col: number, mask: number): boolean {
361
+ switch (mask) {
362
+ case 0: return (row + col) % 2 === 0
363
+ case 1: return row % 2 === 0
364
+ case 2: return col % 3 === 0
365
+ case 3: return (row + col) % 3 === 0
366
+ case 4: return (Math.floor(row / 2) + Math.floor(col / 3)) % 2 === 0
367
+ case 5: return ((row * col) % 2) + ((row * col) % 3) === 0
368
+ case 6: return (((row * col) % 2) + ((row * col) % 3)) % 2 === 0
369
+ case 7: return (((row + col) % 2) + ((row * col) % 3)) % 2 === 0
370
+ default: return false
232
371
  }
233
- bytes.push(0, 0, 0, 0)
234
- return bytes
235
372
  }
236
- }
237
373
 
238
- const generateQRCode = () => {
239
- if (!props.value) {
240
- matrix.value = []
241
- return
374
+ // 评估掩码质量
375
+ private static evaluateMask(modules: number[][], size: number): number {
376
+ let score = 0
377
+
378
+ // 规则1:连续同色模块
379
+ for (let row = 0; row < size; row++) {
380
+ let count = 1
381
+ for (let col = 1; col < size; col++) {
382
+ if (modules[row][col] === modules[row][col - 1]) {
383
+ count++
384
+ } else {
385
+ if (count >= 5) score += count - 2
386
+ count = 1
387
+ }
388
+ }
389
+ if (count >= 5) score += count - 2
390
+ }
391
+
392
+ for (let col = 0; col < size; col++) {
393
+ let count = 1
394
+ for (let row = 1; row < size; row++) {
395
+ if (modules[row][col] === modules[row - 1][col]) {
396
+ count++
397
+ } else {
398
+ if (count >= 5) score += count - 2
399
+ count = 1
400
+ }
401
+ }
402
+ if (count >= 5) score += count - 2
403
+ }
404
+
405
+ return score
242
406
  }
243
- matrix.value = QRCode.generate(props.value, props.errorCorrectLevel)
407
+
408
+ // 添加格式信息
409
+ private static addFormatInfo(modules: number[][], size: number, ecl: string, mask: number) {
410
+ const eclBits = this.ECC_LEVEL[ecl]
411
+ const data = (eclBits << 3) | mask
412
+
413
+ // BCH 编码
414
+ let rem = data
415
+ for (let i = 0; i < 10; i++) {
416
+ rem = (rem << 1) ^ ((rem >> 14) * 0x537)
417
+ }
418
+ const format = ((data << 10) | rem) ^ 0x5412
419
+
420
+ // 填充格式信息
421
+ const bits: number[] = []
422
+ for (let i = 14; i >= 0; i--) {
423
+ bits.push((format >> i) & 1)
424
+ }
425
+
426
+ // 左上角 + 右上角 + 左下角
427
+ for (let i = 0; i < 6; i++) modules[8][i] = bits[i]
428
+ modules[8][7] = bits[6]
429
+ modules[8][8] = bits[7]
430
+ modules[7][8] = bits[8]
431
+ for (let i = 9; i < 15; i++) modules[14 - i][8] = bits[i]
432
+
433
+ for (let i = 0; i < 8; i++) modules[8][size - 1 - i] = bits[14 - i]
434
+ for (let i = 8; i < 15; i++) modules[size - 15 + i][8] = bits[14 - i]
435
+ }
436
+ }
437
+
438
+ // 绘制二维码到 canvas
439
+ const drawQRCode = () => {
440
+ if (!props.value) return
441
+
442
+ nextTick(() => {
443
+ const result = QRCode.generate(props.value, props.errorCorrectLevel)
444
+ const cellSize = props.size / result.size
445
+
446
+ const ctx = uni.createCanvasContext(canvasId.value)
447
+
448
+ // 背景
449
+ ctx.setFillStyle(props.bgColor)
450
+ ctx.fillRect(0, 0, props.size, props.size)
451
+
452
+ // 绘制模块
453
+ ctx.setFillStyle(props.color)
454
+ for (let row = 0; row < result.size; row++) {
455
+ for (let col = 0; col < result.size; col++) {
456
+ if (result.modules[row][col] === 1) {
457
+ ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize)
458
+ }
459
+ }
460
+ }
461
+
462
+ ctx.draw()
463
+ })
244
464
  }
245
465
 
246
- watch(() => props.value, generateQRCode)
247
- watch(() => props.errorCorrectLevel, generateQRCode)
466
+ watch(() => props.value, drawQRCode)
467
+ watch(() => props.size, drawQRCode)
468
+ watch(() => props.color, drawQRCode)
469
+ watch(() => props.bgColor, drawQRCode)
248
470
 
249
471
  onMounted(() => {
250
- generateQRCode()
472
+ drawQRCode()
251
473
  })
252
474
  </script>
253
475
 
@@ -265,18 +487,8 @@ export default {
265
487
  display: inline-block;
266
488
  position: relative;
267
489
 
268
- &__matrix {
269
- display: flex;
270
- flex-direction: column;
271
- }
272
-
273
- &__row {
274
- display: flex;
275
- flex-direction: row;
276
- }
277
-
278
- &__cell {
279
- flex-shrink: 0;
490
+ &__canvas {
491
+ display: block;
280
492
  }
281
493
  }
282
- </style>
494
+ </style>