@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.
- package/dist/index.js +550 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +550 -339
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +3 -11
- package/package.json +1 -1
- package/src/components/u-qrcode/u-qrcode.vue +389 -177
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<view class="u-qrcode"
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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,
|
|
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
|
-
|
|
32
|
+
// 生成唯一 canvas ID
|
|
33
|
+
const canvasId = ref(`qrcode_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`)
|
|
45
34
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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(
|
|
59
|
+
private static getVersion(dataLength: number): number {
|
|
76
60
|
for (let v = 1; v <= 40; v++) {
|
|
77
|
-
if (this.
|
|
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
|
|
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
|
-
|
|
74
|
+
modules[i] = new Array(size).fill(0)
|
|
92
75
|
}
|
|
93
76
|
|
|
94
|
-
//
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
97
|
-
|
|
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.
|
|
85
|
+
// 编码数据
|
|
86
|
+
const codewords = this.encodeCodewords(data, version, ecl)
|
|
101
87
|
|
|
102
|
-
//
|
|
103
|
-
this.
|
|
88
|
+
// 填充数据
|
|
89
|
+
this.fillData(modules, codewords, size)
|
|
104
90
|
|
|
105
|
-
//
|
|
106
|
-
this.
|
|
91
|
+
// 应用最佳掩码
|
|
92
|
+
const bestMask = this.findBestMask(modules, size)
|
|
93
|
+
this.applyMask(modules, size, bestMask)
|
|
107
94
|
|
|
108
|
-
//
|
|
109
|
-
this.addFormatInfo(
|
|
95
|
+
// 添加格式信息
|
|
96
|
+
this.addFormatInfo(modules, size, ecl, bestMask)
|
|
110
97
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
161
|
+
// 获取定位图案位置
|
|
162
|
+
private static getAlignmentPositions(version: number, size: number): number[] {
|
|
156
163
|
if (version === 1) return []
|
|
157
|
-
|
|
158
|
-
const size = version * 4 + 17
|
|
159
|
-
const step = Math.ceil((size - 13) / intervals)
|
|
164
|
+
|
|
160
165
|
const positions = [6]
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
294
|
+
// 填充格式信息保留区
|
|
190
295
|
for (let i = 0; i < 9; i++) {
|
|
191
|
-
if (
|
|
192
|
-
if (
|
|
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 (
|
|
196
|
-
if (
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
let bitIndex = 0
|
|
304
|
+
// 填充数据
|
|
305
|
+
let bitIdx = 0
|
|
203
306
|
let upward = true
|
|
204
307
|
|
|
205
|
-
for (let col = size - 1; col >=
|
|
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
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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,
|
|
247
|
-
watch(() => props.
|
|
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
|
-
|
|
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
|
-
&
|
|
269
|
-
display:
|
|
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>
|