@atooyu/uxto-ui 1.1.30 → 1.1.32

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/style.css CHANGED
@@ -2482,19 +2482,19 @@ to {
2482
2482
  letter-spacing: 0.5px;
2483
2483
  color: #12b6af;
2484
2484
  font-weight: 500;
2485
- }.u-qrcode[data-v-d5a07c6c] {
2485
+ }.u-qrcode[data-v-eebb43a8] {
2486
2486
  display: inline-block;
2487
2487
  position: relative;
2488
2488
  }
2489
- .u-qrcode__matrix[data-v-d5a07c6c] {
2489
+ .u-qrcode__matrix[data-v-eebb43a8] {
2490
2490
  display: flex;
2491
2491
  flex-direction: column;
2492
2492
  }
2493
- .u-qrcode__row[data-v-d5a07c6c] {
2493
+ .u-qrcode__row[data-v-eebb43a8] {
2494
2494
  display: flex;
2495
2495
  flex-direction: row;
2496
2496
  }
2497
- .u-qrcode__cell[data-v-d5a07c6c] {
2497
+ .u-qrcode__cell[data-v-eebb43a8] {
2498
2498
  flex-shrink: 0;
2499
2499
  }.u-barcode[data-v-4ecc744d] {
2500
2500
  display: inline-flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atooyu/uxto-ui",
3
- "version": "1.1.30",
3
+ "version": "1.1.32",
4
4
  "description": "跨平台 UI 组件库 - 支持 Android、iOS、鸿蒙",
5
5
  "keywords": [
6
6
  "uxto-ui",
@@ -2,18 +2,18 @@
2
2
  <view class="u-qrcode" :style="{ width: `${size}px`, height: `${size}px` }">
3
3
  <view class="u-qrcode__matrix" :style="{ backgroundColor: bgColor }">
4
4
  <view
5
- v-for="(row, rowIndex) in moduleCount"
5
+ v-for="(row, rowIndex) in modules"
6
6
  :key="rowIndex"
7
7
  class="u-qrcode__row"
8
8
  >
9
9
  <view
10
- v-for="(col, colIndex) in moduleCount"
10
+ v-for="(cell, colIndex) in row"
11
11
  :key="colIndex"
12
12
  class="u-qrcode__cell"
13
13
  :style="{
14
14
  width: `${cellSize}px`,
15
15
  height: `${cellSize}px`,
16
- backgroundColor: isDark(rowIndex, colIndex) ? color : bgColor
16
+ backgroundColor: cell ? color : bgColor
17
17
  }"
18
18
  />
19
19
  </view>
@@ -23,8 +23,6 @@
23
23
 
24
24
  <script setup lang="ts">
25
25
  import { ref, computed, watch, onMounted } from 'vue'
26
- // @ts-ignore
27
- import qrcode from 'qrcode-generator'
28
26
 
29
27
  interface Props {
30
28
  value: string
@@ -42,59 +40,378 @@ const props = withDefaults(defineProps<Props>(), {
42
40
  errorCorrectLevel: 'M'
43
41
  })
44
42
 
45
- // QR Code 实例
46
- const qrInstance = ref<ReturnType<typeof qrcode> | null>(null)
47
- const moduleCount = ref(0)
43
+ const modules = ref<boolean[][]>([])
48
44
 
49
45
  // 计算单元格大小
50
46
  const cellSize = computed(() => {
51
- if (moduleCount.value === 0) return 0
52
- return props.size / moduleCount.value
47
+ if (modules.value.length === 0) return 0
48
+ return props.size / modules.value.length
53
49
  })
54
50
 
55
- // 判断某个位置是否是黑色模块
56
- const isDark = (row: number, col: number): boolean => {
57
- if (!qrInstance.value) return false
58
- try {
59
- return qrInstance.value.isDark(row, col)
60
- } catch {
61
- return false
51
+ // ====== QR Code 生成器 ======
52
+ // 纠错级别映射
53
+ const ECL_MAP: Record<string, number> = { L: 0, M: 1, Q: 2, H: 3 }
54
+
55
+ // 每个版本的数据容量 (字节模式) [L, M, Q, H]
56
+ const DATA_CAPACITY = [
57
+ [17, 14, 11, 7],
58
+ [32, 26, 20, 14],
59
+ [53, 42, 32, 24],
60
+ [78, 62, 46, 34],
61
+ [106, 84, 60, 44],
62
+ [134, 106, 74, 58],
63
+ [154, 122, 86, 64],
64
+ [192, 152, 108, 84],
65
+ [230, 180, 130, 98],
66
+ [271, 213, 151, 119],
67
+ [321, 251, 177, 137],
68
+ [367, 287, 203, 155],
69
+ [425, 331, 241, 177],
70
+ [458, 362, 258, 194],
71
+ [520, 412, 292, 220],
72
+ [586, 450, 322, 250],
73
+ [644, 504, 364, 280],
74
+ [718, 560, 394, 310],
75
+ [792, 624, 442, 338],
76
+ [858, 666, 482, 382],
77
+ [929, 711, 509, 403],
78
+ [1003, 779, 565, 439],
79
+ [1091, 857, 611, 461],
80
+ [1171, 911, 661, 511],
81
+ [1273, 997, 715, 535],
82
+ [1367, 1059, 751, 593],
83
+ [1465, 1125, 805, 625],
84
+ [1528, 1190, 868, 658],
85
+ [1628, 1264, 908, 698],
86
+ [1732, 1370, 982, 742],
87
+ [1840, 1452, 1030, 790],
88
+ [1952, 1538, 1112, 842],
89
+ [2068, 1628, 1168, 898],
90
+ [2188, 1722, 1228, 958],
91
+ [2303, 1809, 1283, 983],
92
+ [2431, 1911, 1351, 1051],
93
+ [2563, 1989, 1423, 1093],
94
+ [2699, 2099, 1499, 1139],
95
+ [2809, 2213, 1579, 1219],
96
+ [2953, 2331, 1663, 1273]
97
+ ]
98
+
99
+ // 每个版本的纠错码字数 [L, M, Q, H]
100
+ const ECC_CODEWORDS = [
101
+ [7, 10, 13, 17],
102
+ [10, 16, 22, 28],
103
+ [15, 26, 36, 44],
104
+ [20, 36, 52, 64],
105
+ [26, 48, 72, 88],
106
+ [36, 64, 96, 112],
107
+ [40, 72, 108, 130],
108
+ [48, 88, 132, 156],
109
+ [60, 110, 160, 192],
110
+ [72, 130, 192, 224],
111
+ [80, 150, 224, 264],
112
+ [96, 176, 260, 308],
113
+ [104, 198, 288, 352],
114
+ [120, 216, 320, 384],
115
+ [132, 240, 360, 432],
116
+ [144, 280, 408, 480],
117
+ [168, 308, 448, 532],
118
+ [180, 338, 504, 588],
119
+ [196, 364, 546, 650],
120
+ [224, 416, 600, 700],
121
+ [224, 442, 644, 750],
122
+ [252, 476, 690, 816],
123
+ [270, 504, 750, 900],
124
+ [300, 560, 810, 960],
125
+ [312, 588, 870, 1050],
126
+ [336, 644, 952, 1110],
127
+ [360, 700, 1020, 1200],
128
+ [390, 728, 1050, 1260],
129
+ [420, 784, 1140, 1350],
130
+ [450, 812, 1200, 1440],
131
+ [480, 868, 1290, 1530],
132
+ [510, 924, 1350, 1620],
133
+ [540, 980, 1440, 1710],
134
+ [570, 1036, 1530, 1800],
135
+ [570, 1064, 1590, 1890],
136
+ [600, 1120, 1680, 1980],
137
+ [630, 1204, 1770, 2100],
138
+ [660, 1260, 1860, 2220],
139
+ [720, 1316, 1950, 2310],
140
+ [750, 1372, 2040, 2430]
141
+ ]
142
+
143
+ // GF(2^8) 运算表
144
+ const GF_EXP: number[] = []
145
+ const GF_LOG: number[] = []
146
+
147
+ // 初始化 GF 表
148
+ ;(function initGF() {
149
+ let x = 1
150
+ for (let i = 0; i < 255; i++) {
151
+ GF_EXP[i] = x
152
+ GF_LOG[x] = i
153
+ x = x * 2
154
+ if (x >= 256) x ^= 285
155
+ }
156
+ for (let i = 255; i < 512; i++) {
157
+ GF_EXP[i] = GF_EXP[i - 255]
158
+ }
159
+ })()
160
+
161
+ function gfMul(a: number, b: number): number {
162
+ if (a === 0 || b === 0) return 0
163
+ return GF_EXP[GF_LOG[a] + GF_LOG[b]]
164
+ }
165
+
166
+ // Reed-Solomon 编码
167
+ function rsEncode(data: number[], eccCount: number): number[] {
168
+ // 生成多项式
169
+ let gen = [1]
170
+ for (let i = 0; i < eccCount; i++) {
171
+ const temp: number[] = new Array(gen.length + 1).fill(0)
172
+ for (let j = 0; j < gen.length; j++) {
173
+ temp[j] ^= gen[j]
174
+ temp[j + 1] ^= gfMul(gen[j], GF_EXP[i])
175
+ }
176
+ gen = temp
177
+ }
178
+
179
+ // 计算纠错码
180
+ const ecc: number[] = new Array(eccCount).fill(0)
181
+ for (const byte of data) {
182
+ const factor = byte ^ ecc[0]
183
+ ecc.shift()
184
+ ecc.push(0)
185
+ for (let i = 0; i < eccCount; i++) {
186
+ ecc[i] ^= gfMul(gen[i], factor)
187
+ }
188
+ }
189
+ return ecc
190
+ }
191
+
192
+ // 获取版本
193
+ function getVersion(dataLength: number, ecl: string): number {
194
+ const eclIdx = ECL_MAP[ecl] ?? 1
195
+ for (let v = 0; v < 40; v++) {
196
+ if (DATA_CAPACITY[v][eclIdx] >= dataLength) return v + 1
62
197
  }
198
+ return 40
63
199
  }
64
200
 
65
201
  // 生成二维码
66
- const generateQRCode = () => {
67
- if (!props.value) {
68
- qrInstance.value = null
69
- moduleCount.value = 0
70
- return
202
+ function generateQRCode(data: string, ecl: string): boolean[][] {
203
+ const eclIdx = ECL_MAP[ecl] ?? 1
204
+ const version = getVersion(data.length, ecl)
205
+ const size = version * 4 + 17
206
+
207
+ // 初始化矩阵
208
+ const matrix: number[][] = []
209
+ for (let i = 0; i < size; i++) {
210
+ matrix[i] = new Array(size).fill(-1)
71
211
  }
72
212
 
73
- try {
74
- // 创建二维码实例
75
- const typeNumber = 0 // 自动选择版本
76
- const qr = qrcode(typeNumber, props.errorCorrectLevel)
213
+ // 添加定位图案
214
+ function addFinder(row: number, col: number) {
215
+ for (let r = -1; r <= 7; r++) {
216
+ for (let c = -1; c <= 7; c++) {
217
+ const tr = row + r, tc = col + c
218
+ if (tr >= 0 && tr < size && tc >= 0 && tc < size) {
219
+ if (r === -1 || r === 7 || c === -1 || c === 7) {
220
+ matrix[tr][tc] = 0
221
+ } else if (r === 0 || r === 6 || c === 0 || c === 6 || (r >= 2 && r <= 4 && c >= 2 && c <= 4)) {
222
+ matrix[tr][tc] = 1
223
+ } else {
224
+ matrix[tr][tc] = 0
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ addFinder(0, 0)
231
+ addFinder(size - 7, 0)
232
+ addFinder(0, size - 7)
233
+
234
+ // 添加时序图案
235
+ for (let i = 8; i < size - 8; i++) {
236
+ matrix[6][i] = i % 2 === 0 ? 1 : 0
237
+ matrix[i][6] = i % 2 === 0 ? 1 : 0
238
+ }
239
+
240
+ // 添加对齐图案
241
+ if (version >= 2) {
242
+ const positions: number[] = [6]
243
+ const step = Math.ceil((size - 13) / (Math.floor(version / 7) + 1))
244
+ let pos = size - 7
245
+ while (pos > 6) {
246
+ positions.unshift(pos)
247
+ pos -= step
248
+ }
249
+
250
+ for (const row of positions) {
251
+ for (const col of positions) {
252
+ if (matrix[row][col] !== -1) continue
253
+ for (let r = -2; r <= 2; r++) {
254
+ for (let c = -2; c <= 2; c++) {
255
+ const tr = row + r, tc = col + c
256
+ if (tr >= 0 && tr < size && tc >= 0 && tc < size && matrix[tr][tc] === -1) {
257
+ matrix[tr][tc] = (Math.abs(r) === 2 || Math.abs(c) === 2 || (r === 0 && c === 0)) ? 1 : 0
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ // 暗模块
266
+ matrix[size - 8][8] = 1
267
+
268
+ // 保留格式信息区域
269
+ for (let i = 0; i < 9; i++) {
270
+ if (matrix[8][i] === -1) matrix[8][i] = 0
271
+ if (matrix[i][8] === -1) matrix[i][8] = 0
272
+ }
273
+ for (let i = 0; i < 8; i++) {
274
+ if (matrix[8][size - 1 - i] === -1) matrix[8][size - 1 - i] = 0
275
+ if (matrix[size - 1 - i][8] === -1) matrix[size - 1 - i][8] = 0
276
+ }
277
+
278
+ // 编码数据
279
+ const eccCount = ECC_CODEWORDS[version - 1][eclIdx]
280
+ const dataCapacity = DATA_CAPACITY[version - 1][eclIdx]
281
+ const bits: number[] = []
77
282
 
78
- // 添加数据
79
- qr.addData(props.value)
283
+ // 模式指示符 (字节模式 = 0100)
284
+ bits.push(0, 1, 0, 0)
80
285
 
81
- // 生成
82
- qr.make()
286
+ // 字符计数
287
+ const cciBits = version <= 9 ? 8 : 16
288
+ for (let i = cciBits - 1; i >= 0; i--) {
289
+ bits.push((data.length >> i) & 1)
290
+ }
291
+
292
+ // 数据编码
293
+ for (const char of data) {
294
+ const code = char.charCodeAt(0)
295
+ for (let i = 7; i >= 0; i--) {
296
+ bits.push((code >> i) & 1)
297
+ }
298
+ }
299
+
300
+ // 终止符
301
+ const termLen = Math.min(4, dataCapacity * 8 - bits.length)
302
+ for (let i = 0; i < termLen; i++) bits.push(0)
303
+
304
+ // 补齐到字节边界
305
+ while (bits.length % 8 !== 0) bits.push(0)
306
+
307
+ // 填充码字
308
+ const padBytes = [0xEC, 0x11]
309
+ let padIdx = 0
310
+ while (bits.length < dataCapacity * 8) {
311
+ for (let i = 7; i >= 0; i--) {
312
+ bits.push((padBytes[padIdx] >> i) & 1)
313
+ }
314
+ padIdx = (padIdx + 1) % 2
315
+ }
316
+
317
+ // 转为字节
318
+ const dataBytes: number[] = []
319
+ for (let i = 0; i < bits.length; i += 8) {
320
+ let b = 0
321
+ for (let j = 0; j < 8; j++) b = (b << 1) | (bits[i + j] || 0)
322
+ dataBytes.push(b)
323
+ }
324
+
325
+ // 纠错码
326
+ const eccBytes = rsEncode(dataBytes, eccCount)
327
+ const codewords = [...dataBytes, ...eccBytes]
328
+
329
+ // 填充数据到矩阵
330
+ let bitIdx = 0
331
+ let upward = true
332
+ for (let col = size - 1; col >= 0; col -= 2) {
333
+ if (col === 6) col = 5
334
+ for (let row = upward ? size - 1 : 0; upward ? row >= 0 : row < size; upward ? row-- : row++) {
335
+ for (let c = 0; c < 2; c++) {
336
+ const tc = col - c
337
+ if (tc >= 0 && matrix[row][tc] === -1) {
338
+ let bit = 0
339
+ if (bitIdx < codewords.length * 8) {
340
+ bit = (codewords[Math.floor(bitIdx / 8)] >> (7 - (bitIdx % 8))) & 1
341
+ }
342
+ matrix[row][tc] = bit
343
+ bitIdx++
344
+ }
345
+ }
346
+ }
347
+ upward = !upward
348
+ }
349
+
350
+ // 应用掩码 (模式 0: (row + col) % 2 === 0)
351
+ for (let row = 0; row < size; row++) {
352
+ for (let col = 0; col < size; col++) {
353
+ if (matrix[row][col] >= 0 && (row + col) % 2 === 0) {
354
+ matrix[row][col] = matrix[row][col] === 1 ? 0 : 1
355
+ }
356
+ }
357
+ }
358
+
359
+ // 添加格式信息
360
+ const eclBits = { L: 1, M: 0, Q: 3, H: 2 }[ecl] ?? 0
361
+ let formatData = (eclBits << 3) | 0
362
+ let rem = formatData
363
+ for (let i = 0; i < 10; i++) {
364
+ rem = (rem << 1) ^ ((rem >> 9) * 0x537)
365
+ }
366
+ const formatBits = ((formatData << 10) | (rem & 0x3FF)) ^ 0x5412
367
+
368
+ // 填充格式信息
369
+ const fb: number[] = []
370
+ for (let i = 14; i >= 0; i--) {
371
+ fb.push((formatBits >> i) & 1)
372
+ }
373
+
374
+ for (let i = 0; i < 6; i++) matrix[8][i] = fb[i]
375
+ matrix[8][7] = fb[6]
376
+ matrix[8][8] = fb[7]
377
+ matrix[7][8] = fb[8]
378
+ for (let i = 9; i < 15; i++) matrix[14 - i][8] = fb[i]
83
379
 
84
- qrInstance.value = qr
85
- moduleCount.value = qr.getModuleCount()
380
+ for (let i = 0; i < 8; i++) matrix[8][size - 8 + i] = fb[14 - i]
381
+ for (let i = 8; i < 15; i++) matrix[size - 15 + i][8] = fb[14 - i]
382
+
383
+ // 转换为布尔矩阵
384
+ const result: boolean[][] = []
385
+ for (let row = 0; row < size; row++) {
386
+ result[row] = []
387
+ for (let col = 0; col < size; col++) {
388
+ result[row][col] = matrix[row][col] === 1
389
+ }
390
+ }
391
+
392
+ return result
393
+ }
394
+
395
+ // 生成二维码
396
+ const generate = () => {
397
+ if (!props.value) {
398
+ modules.value = []
399
+ return
400
+ }
401
+
402
+ try {
403
+ modules.value = generateQRCode(props.value, props.errorCorrectLevel)
86
404
  } catch (e) {
87
405
  console.error('QR Code generation failed:', e)
88
- qrInstance.value = null
89
- moduleCount.value = 0
406
+ modules.value = []
90
407
  }
91
408
  }
92
409
 
93
- watch(() => props.value, generateQRCode)
94
- watch(() => props.errorCorrectLevel, generateQRCode)
410
+ watch(() => props.value, generate)
411
+ watch(() => props.errorCorrectLevel, generate)
95
412
 
96
413
  onMounted(() => {
97
- generateQRCode()
414
+ generate()
98
415
  })
99
416
  </script>
100
417