Sfoglia il codice sorgente

feat: 签到功能初始化

XiaoYu 1 anno fa
parent
commit
d3d93875cd
73 ha cambiato i file con 5547 aggiunte e 23 eliminazioni
  1. 1201 0
      components/tki-qrcode/qrcode.js
  2. 210 0
      components/tki-qrcode/tki-qrcode.vue
  3. 5 0
      node_modules/.package-lock.json
  4. 201 0
      node_modules/uqrcodejs/LICENSE.md
  5. 207 0
      node_modules/uqrcodejs/README.md
  6. 27 0
      node_modules/uqrcodejs/package.json
  7. 33 0
      node_modules/uqrcodejs/uqrcode.js
  8. 12 1
      package-lock.json
  9. 2 1
      package.json
  10. 43 0
      pages.json
  11. 315 0
      pages/checkin/index.vue
  12. 180 0
      pages/checkin/teacher/index.vue
  13. 66 0
      pages/checkin/teacher/number-setup.vue
  14. 166 0
      pages/checkin/teacher/qr-active.vue
  15. 117 0
      pages/checkin/teacher/select-method.vue
  16. 1 1
      pages/profile/index.vue
  17. 110 0
      store/checkin.ts
  18. 1 1
      unpackage/dist/dev/.sourcemap/mp-weixin/app.js.map
  19. 1 1
      unpackage/dist/dev/.sourcemap/mp-weixin/common/assets.js.map
  20. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/common/vendor.js.map
  21. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/components/tki-qrcode/qrcode.js.map
  22. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/components/tki-qrcode/tki-qrcode.js.map
  23. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.js.map
  24. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/index.js.map
  25. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/index.js.map
  26. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/number-setup.js.map
  27. 1 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/number-setup/number-setup.js.map
  28. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/qr-active.js.map
  29. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/select-method.js.map
  30. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/components/About/AboutModal/AboutModal.js.map
  31. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/pages/profile/index.js.map
  32. 0 0
      unpackage/dist/dev/.sourcemap/mp-weixin/store/checkin.js.map
  33. 5 0
      unpackage/dist/dev/mp-weixin/app.js
  34. 7 1
      unpackage/dist/dev/mp-weixin/app.json
  35. 4 2
      unpackage/dist/dev/mp-weixin/common/assets.js
  36. 89 11
      unpackage/dist/dev/mp-weixin/common/vendor.js
  37. 1049 0
      unpackage/dist/dev/mp-weixin/components/tki-qrcode/qrcode.js
  38. 215 0
      unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.js
  39. 4 0
      unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.json
  40. 1 0
      unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.wxml
  41. 10 0
      unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.wxss
  42. 193 0
      unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.js
  43. 4 0
      unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.json
  44. 1 0
      unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.wxml
  45. 0 0
      unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.wxss
  46. 150 0
      unpackage/dist/dev/mp-weixin/pages/checkin/index.js
  47. 7 0
      unpackage/dist/dev/mp-weixin/pages/checkin/index.json
  48. 1 0
      unpackage/dist/dev/mp-weixin/pages/checkin/index.wxml
  49. 153 0
      unpackage/dist/dev/mp-weixin/pages/checkin/index.wxss
  50. 105 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.js
  51. 6 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.json
  52. 0 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.wxml
  53. 80 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.wxss
  54. 66 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.js
  55. 6 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.json
  56. 1 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.wxml
  57. 27 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.wxss
  58. 66 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.js
  59. 4 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.json
  60. 2 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.wxml
  61. 1 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.wxss
  62. 76 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.js
  63. 7 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.json
  64. 1 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.wxml
  65. 68 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.wxss
  66. 48 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.js
  67. 6 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.json
  68. 1 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.wxml
  69. 103 0
      unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.wxss
  70. 1 1
      unpackage/dist/dev/mp-weixin/pages/components/About/AboutModal/AboutModal.js
  71. 1 1
      unpackage/dist/dev/mp-weixin/pages/profile/index.js
  72. 16 2
      unpackage/dist/dev/mp-weixin/project.private.config.json
  73. 63 0
      unpackage/dist/dev/mp-weixin/store/checkin.js

+ 1201 - 0
components/tki-qrcode/qrcode.js

@@ -0,0 +1,1201 @@
+let QRCode = {};
+(function () {
+    /**
+     * 获取单个字符的utf8编码
+     * unicode BMP平面约65535个字符
+     * @param {num} code
+     * return {array}
+     */
+    function unicodeFormat8(code) {
+        // 1 byte
+        var c0, c1, c2;
+        if (code < 128) {
+            return [code];
+            // 2 bytes
+        } else if (code < 2048) {
+            c0 = 192 + (code >> 6);
+            c1 = 128 + (code & 63);
+            return [c0, c1];
+            // 3 bytes
+        } else {
+            c0 = 224 + (code >> 12);
+            c1 = 128 + (code >> 6 & 63);
+            c2 = 128 + (code & 63);
+            return [c0, c1, c2];
+        }
+    }
+    /**
+     * 获取字符串的utf8编码字节串
+     * @param {string} string
+     * @return {array}
+     */
+    function getUTF8Bytes(string) {
+        var utf8codes = [];
+        for (var i = 0; i < string.length; i++) {
+            var code = string.charCodeAt(i);
+            var utf8 = unicodeFormat8(code);
+            for (var j = 0; j < utf8.length; j++) {
+                utf8codes.push(utf8[j]);
+            }
+        }
+        return utf8codes;
+    }
+    /**
+     * 二维码算法实现
+     * @param {string} data              要编码的信息字符串
+     * @param {num} errorCorrectLevel 纠错等级
+     */
+    function QRCodeAlg(data, errorCorrectLevel) {
+        this.typeNumber = -1; //版本
+        this.errorCorrectLevel = errorCorrectLevel;
+        this.modules = null; //二维矩阵,存放最终结果
+        this.moduleCount = 0; //矩阵大小
+        this.dataCache = null; //数据缓存
+        this.rsBlocks = null; //版本数据信息
+        this.totalDataCount = -1; //可使用的数据量
+        this.data = data;
+        this.utf8bytes = getUTF8Bytes(data);
+        this.make();
+    }
+    QRCodeAlg.prototype = {
+        constructor: QRCodeAlg,
+        /**
+         * 获取二维码矩阵大小
+         * @return {num} 矩阵大小
+         */
+        getModuleCount: function () {
+            return this.moduleCount;
+        },
+        /**
+         * 编码
+         */
+        make: function () {
+            this.getRightType();
+            this.dataCache = this.createData();
+            this.createQrcode();
+        },
+        /**
+         * 设置二位矩阵功能图形
+         * @param  {bool} test 表示是否在寻找最好掩膜阶段
+         * @param  {num} maskPattern 掩膜的版本
+         */
+        makeImpl: function (maskPattern) {
+            this.moduleCount = this.typeNumber * 4 + 17;
+            this.modules = new Array(this.moduleCount);
+            for (var row = 0; row < this.moduleCount; row++) {
+                this.modules[row] = new Array(this.moduleCount);
+            }
+            this.setupPositionProbePattern(0, 0);
+            this.setupPositionProbePattern(this.moduleCount - 7, 0);
+            this.setupPositionProbePattern(0, this.moduleCount - 7);
+            this.setupPositionAdjustPattern();
+            this.setupTimingPattern();
+            this.setupTypeInfo(true, maskPattern);
+            if (this.typeNumber >= 7) {
+                this.setupTypeNumber(true);
+            }
+            this.mapData(this.dataCache, maskPattern);
+        },
+        /**
+         * 设置二维码的位置探测图形
+         * @param  {num} row 探测图形的中心横坐标
+         * @param  {num} col 探测图形的中心纵坐标
+         */
+        setupPositionProbePattern: function (row, col) {
+            for (var r = -1; r <= 7; r++) {
+                if (row + r <= -1 || this.moduleCount <= row + r) continue;
+                for (var c = -1; c <= 7; c++) {
+                    if (col + c <= -1 || this.moduleCount <= col + c) continue;
+                    if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
+                        this.modules[row + r][col + c] = true;
+                    } else {
+                        this.modules[row + r][col + c] = false;
+                    }
+                }
+            }
+        },
+        /**
+         * 创建二维码
+         * @return {[type]} [description]
+         */
+        createQrcode: function () {
+            var minLostPoint = 0;
+            var pattern = 0;
+            var bestModules = null;
+            for (var i = 0; i < 8; i++) {
+                this.makeImpl(i);
+                var lostPoint = QRUtil.getLostPoint(this);
+                if (i == 0 || minLostPoint > lostPoint) {
+                    minLostPoint = lostPoint;
+                    pattern = i;
+                    bestModules = this.modules;
+                }
+            }
+            this.modules = bestModules;
+            this.setupTypeInfo(false, pattern);
+            if (this.typeNumber >= 7) {
+                this.setupTypeNumber(false);
+            }
+        },
+        /**
+         * 设置定位图形
+         * @return {[type]} [description]
+         */
+        setupTimingPattern: function () {
+            for (var r = 8; r < this.moduleCount - 8; r++) {
+                if (this.modules[r][6] != null) {
+                    continue;
+                }
+                this.modules[r][6] = (r % 2 == 0);
+                if (this.modules[6][r] != null) {
+                    continue;
+                }
+                this.modules[6][r] = (r % 2 == 0);
+            }
+        },
+        /**
+         * 设置矫正图形
+         * @return {[type]} [description]
+         */
+        setupPositionAdjustPattern: function () {
+            var pos = QRUtil.getPatternPosition(this.typeNumber);
+            for (var i = 0; i < pos.length; i++) {
+                for (var j = 0; j < pos.length; j++) {
+                    var row = pos[i];
+                    var col = pos[j];
+                    if (this.modules[row][col] != null) {
+                        continue;
+                    }
+                    for (var r = -2; r <= 2; r++) {
+                        for (var c = -2; c <= 2; c++) {
+                            if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) {
+                                this.modules[row + r][col + c] = true;
+                            } else {
+                                this.modules[row + r][col + c] = false;
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        /**
+         * 设置版本信息(7以上版本才有)
+         * @param  {bool} test 是否处于判断最佳掩膜阶段
+         * @return {[type]}      [description]
+         */
+        setupTypeNumber: function (test) {
+            var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+            for (var i = 0; i < 18; i++) {
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
+                this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+            }
+        },
+        /**
+         * 设置格式信息(纠错等级和掩膜版本)
+         * @param  {bool} test
+         * @param  {num} maskPattern 掩膜版本
+         * @return {}
+         */
+        setupTypeInfo: function (test, maskPattern) {
+            var data = (QRErrorCorrectLevel[this.errorCorrectLevel] << 3) | maskPattern;
+            var bits = QRUtil.getBCHTypeInfo(data);
+            // vertical
+            for (var i = 0; i < 15; i++) {
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                if (i < 6) {
+                    this.modules[i][8] = mod;
+                } else if (i < 8) {
+                    this.modules[i + 1][8] = mod;
+                } else {
+                    this.modules[this.moduleCount - 15 + i][8] = mod;
+                }
+                // horizontal
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                if (i < 8) {
+                    this.modules[8][this.moduleCount - i - 1] = mod;
+                } else if (i < 9) {
+                    this.modules[8][15 - i - 1 + 1] = mod;
+                } else {
+                    this.modules[8][15 - i - 1] = mod;
+                }
+            }
+            // fixed module
+            this.modules[this.moduleCount - 8][8] = (!test);
+        },
+        /**
+         * 数据编码
+         * @return {[type]} [description]
+         */
+        createData: function () {
+            var buffer = new QRBitBuffer();
+            var lengthBits = this.typeNumber > 9 ? 16 : 8;
+            buffer.put(4, 4); //添加模式
+            buffer.put(this.utf8bytes.length, lengthBits);
+            for (var i = 0, l = this.utf8bytes.length; i < l; i++) {
+                buffer.put(this.utf8bytes[i], 8);
+            }
+            if (buffer.length + 4 <= this.totalDataCount * 8) {
+                buffer.put(0, 4);
+            }
+            // padding
+            while (buffer.length % 8 != 0) {
+                buffer.putBit(false);
+            }
+            // padding
+            while (true) {
+                if (buffer.length >= this.totalDataCount * 8) {
+                    break;
+                }
+                buffer.put(QRCodeAlg.PAD0, 8);
+                if (buffer.length >= this.totalDataCount * 8) {
+                    break;
+                }
+                buffer.put(QRCodeAlg.PAD1, 8);
+            }
+            return this.createBytes(buffer);
+        },
+        /**
+         * 纠错码编码
+         * @param  {buffer} buffer 数据编码
+         * @return {[type]}
+         */
+        createBytes: function (buffer) {
+            var offset = 0;
+            var maxDcCount = 0;
+            var maxEcCount = 0;
+            var length = this.rsBlock.length / 3;
+            var rsBlocks = new Array();
+            for (var i = 0; i < length; i++) {
+                var count = this.rsBlock[i * 3 + 0];
+                var totalCount = this.rsBlock[i * 3 + 1];
+                var dataCount = this.rsBlock[i * 3 + 2];
+                for (var j = 0; j < count; j++) {
+                    rsBlocks.push([dataCount, totalCount]);
+                }
+            }
+            var dcdata = new Array(rsBlocks.length);
+            var ecdata = new Array(rsBlocks.length);
+            for (var r = 0; r < rsBlocks.length; r++) {
+                var dcCount = rsBlocks[r][0];
+                var ecCount = rsBlocks[r][1] - dcCount;
+                maxDcCount = Math.max(maxDcCount, dcCount);
+                maxEcCount = Math.max(maxEcCount, ecCount);
+                dcdata[r] = new Array(dcCount);
+                for (var i = 0; i < dcdata[r].length; i++) {
+                    dcdata[r][i] = 0xff & buffer.buffer[i + offset];
+                }
+                offset += dcCount;
+                var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+                var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+                var modPoly = rawPoly.mod(rsPoly);
+                ecdata[r] = new Array(rsPoly.getLength() - 1);
+                for (var i = 0; i < ecdata[r].length; i++) {
+                    var modIndex = i + modPoly.getLength() - ecdata[r].length;
+                    ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
+                }
+            }
+            var data = new Array(this.totalDataCount);
+            var index = 0;
+            for (var i = 0; i < maxDcCount; i++) {
+                for (var r = 0; r < rsBlocks.length; r++) {
+                    if (i < dcdata[r].length) {
+                        data[index++] = dcdata[r][i];
+                    }
+                }
+            }
+            for (var i = 0; i < maxEcCount; i++) {
+                for (var r = 0; r < rsBlocks.length; r++) {
+                    if (i < ecdata[r].length) {
+                        data[index++] = ecdata[r][i];
+                    }
+                }
+            }
+            return data;
+
+        },
+        /**
+         * 布置模块,构建最终信息
+         * @param  {} data
+         * @param  {} maskPattern
+         * @return {}
+         */
+        mapData: function (data, maskPattern) {
+            var inc = -1;
+            var row = this.moduleCount - 1;
+            var bitIndex = 7;
+            var byteIndex = 0;
+            for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+                if (col == 6) col--;
+                while (true) {
+                    for (var c = 0; c < 2; c++) {
+                        if (this.modules[row][col - c] == null) {
+                            var dark = false;
+                            if (byteIndex < data.length) {
+                                dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
+                            }
+                            var mask = QRUtil.getMask(maskPattern, row, col - c);
+                            if (mask) {
+                                dark = !dark;
+                            }
+                            this.modules[row][col - c] = dark;
+                            bitIndex--;
+                            if (bitIndex == -1) {
+                                byteIndex++;
+                                bitIndex = 7;
+                            }
+                        }
+                    }
+                    row += inc;
+                    if (row < 0 || this.moduleCount <= row) {
+                        row -= inc;
+                        inc = -inc;
+                        break;
+                    }
+                }
+            }
+        }
+    };
+    /**
+     * 填充字段
+     */
+    QRCodeAlg.PAD0 = 0xEC;
+    QRCodeAlg.PAD1 = 0x11;
+    //---------------------------------------------------------------------
+    // 纠错等级对应的编码
+    //---------------------------------------------------------------------
+    var QRErrorCorrectLevel = [1, 0, 3, 2];
+    //---------------------------------------------------------------------
+    // 掩膜版本
+    //---------------------------------------------------------------------
+    var QRMaskPattern = {
+        PATTERN000: 0,
+        PATTERN001: 1,
+        PATTERN010: 2,
+        PATTERN011: 3,
+        PATTERN100: 4,
+        PATTERN101: 5,
+        PATTERN110: 6,
+        PATTERN111: 7
+    };
+    //---------------------------------------------------------------------
+    // 工具类
+    //---------------------------------------------------------------------
+    var QRUtil = {
+        /*
+        每个版本矫正图形的位置
+         */
+        PATTERN_POSITION_TABLE: [
+            [],
+            [6, 18],
+            [6, 22],
+            [6, 26],
+            [6, 30],
+            [6, 34],
+            [6, 22, 38],
+            [6, 24, 42],
+            [6, 26, 46],
+            [6, 28, 50],
+            [6, 30, 54],
+            [6, 32, 58],
+            [6, 34, 62],
+            [6, 26, 46, 66],
+            [6, 26, 48, 70],
+            [6, 26, 50, 74],
+            [6, 30, 54, 78],
+            [6, 30, 56, 82],
+            [6, 30, 58, 86],
+            [6, 34, 62, 90],
+            [6, 28, 50, 72, 94],
+            [6, 26, 50, 74, 98],
+            [6, 30, 54, 78, 102],
+            [6, 28, 54, 80, 106],
+            [6, 32, 58, 84, 110],
+            [6, 30, 58, 86, 114],
+            [6, 34, 62, 90, 118],
+            [6, 26, 50, 74, 98, 122],
+            [6, 30, 54, 78, 102, 126],
+            [6, 26, 52, 78, 104, 130],
+            [6, 30, 56, 82, 108, 134],
+            [6, 34, 60, 86, 112, 138],
+            [6, 30, 58, 86, 114, 142],
+            [6, 34, 62, 90, 118, 146],
+            [6, 30, 54, 78, 102, 126, 150],
+            [6, 24, 50, 76, 102, 128, 154],
+            [6, 28, 54, 80, 106, 132, 158],
+            [6, 32, 58, 84, 110, 136, 162],
+            [6, 26, 54, 82, 110, 138, 166],
+            [6, 30, 58, 86, 114, 142, 170]
+        ],
+        G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
+        G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
+        G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
+        /*
+        BCH编码格式信息
+         */
+        getBCHTypeInfo: function (data) {
+            var d = data << 10;
+            while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+                d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)));
+            }
+            return ((data << 10) | d) ^ QRUtil.G15_MASK;
+        },
+        /*
+        BCH编码版本信息
+         */
+        getBCHTypeNumber: function (data) {
+            var d = data << 12;
+            while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+                d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)));
+            }
+            return (data << 12) | d;
+        },
+        /*
+        获取BCH位信息
+         */
+        getBCHDigit: function (data) {
+            var digit = 0;
+            while (data != 0) {
+                digit++;
+                data >>>= 1;
+            }
+            return digit;
+        },
+        /*
+        获取版本对应的矫正图形位置
+         */
+        getPatternPosition: function (typeNumber) {
+            return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+        },
+        /*
+        掩膜算法
+         */
+        getMask: function (maskPattern, i, j) {
+            switch (maskPattern) {
+                case QRMaskPattern.PATTERN000:
+                    return (i + j) % 2 == 0;
+                case QRMaskPattern.PATTERN001:
+                    return i % 2 == 0;
+                case QRMaskPattern.PATTERN010:
+                    return j % 3 == 0;
+                case QRMaskPattern.PATTERN011:
+                    return (i + j) % 3 == 0;
+                case QRMaskPattern.PATTERN100:
+                    return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
+                case QRMaskPattern.PATTERN101:
+                    return (i * j) % 2 + (i * j) % 3 == 0;
+                case QRMaskPattern.PATTERN110:
+                    return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
+                case QRMaskPattern.PATTERN111:
+                    return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
+                default:
+                    throw new Error("bad maskPattern:" + maskPattern);
+            }
+        },
+        /*
+        获取RS的纠错多项式
+         */
+        getErrorCorrectPolynomial: function (errorCorrectLength) {
+            var a = new QRPolynomial([1], 0);
+            for (var i = 0; i < errorCorrectLength; i++) {
+                a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
+            }
+            return a;
+        },
+        /*
+        获取评价
+         */
+        getLostPoint: function (qrCode) {
+            var moduleCount = qrCode.getModuleCount(),
+                lostPoint = 0,
+                darkCount = 0;
+            for (var row = 0; row < moduleCount; row++) {
+                var sameCount = 0;
+                var head = qrCode.modules[row][0];
+                for (var col = 0; col < moduleCount; col++) {
+                    var current = qrCode.modules[row][col];
+                    //level 3 评价
+                    if (col < moduleCount - 6) {
+                        if (current && !qrCode.modules[row][col + 1] && qrCode.modules[row][col + 2] && qrCode.modules[row][col + 3] && qrCode.modules[row][col + 4] && !qrCode.modules[row][col + 5] && qrCode.modules[row][col + 6]) {
+                            if (col < moduleCount - 10) {
+                                if (qrCode.modules[row][col + 7] && qrCode.modules[row][col + 8] && qrCode.modules[row][col + 9] && qrCode.modules[row][col + 10]) {
+                                    lostPoint += 40;
+                                }
+                            } else if (col > 3) {
+                                if (qrCode.modules[row][col - 1] && qrCode.modules[row][col - 2] && qrCode.modules[row][col - 3] && qrCode.modules[row][col - 4]) {
+                                    lostPoint += 40;
+                                }
+                            }
+                        }
+                    }
+                    //level 2 评价
+                    if ((row < moduleCount - 1) && (col < moduleCount - 1)) {
+                        var count = 0;
+                        if (current) count++;
+                        if (qrCode.modules[row + 1][col]) count++;
+                        if (qrCode.modules[row][col + 1]) count++;
+                        if (qrCode.modules[row + 1][col + 1]) count++;
+                        if (count == 0 || count == 4) {
+                            lostPoint += 3;
+                        }
+                    }
+                    //level 1 评价
+                    if (head ^ current) {
+                        sameCount++;
+                    } else {
+                        head = current;
+                        if (sameCount >= 5) {
+                            lostPoint += (3 + sameCount - 5);
+                        }
+                        sameCount = 1;
+                    }
+                    //level 4 评价
+                    if (current) {
+                        darkCount++;
+                    }
+                }
+            }
+            for (var col = 0; col < moduleCount; col++) {
+                var sameCount = 0;
+                var head = qrCode.modules[0][col];
+                for (var row = 0; row < moduleCount; row++) {
+                    var current = qrCode.modules[row][col];
+                    //level 3 评价
+                    if (row < moduleCount - 6) {
+                        if (current && !qrCode.modules[row + 1][col] && qrCode.modules[row + 2][col] && qrCode.modules[row + 3][col] && qrCode.modules[row + 4][col] && !qrCode.modules[row + 5][col] && qrCode.modules[row + 6][col]) {
+                            if (row < moduleCount - 10) {
+                                if (qrCode.modules[row + 7][col] && qrCode.modules[row + 8][col] && qrCode.modules[row + 9][col] && qrCode.modules[row + 10][col]) {
+                                    lostPoint += 40;
+                                }
+                            } else if (row > 3) {
+                                if (qrCode.modules[row - 1][col] && qrCode.modules[row - 2][col] && qrCode.modules[row - 3][col] && qrCode.modules[row - 4][col]) {
+                                    lostPoint += 40;
+                                }
+                            }
+                        }
+                    }
+                    //level 1 评价
+                    if (head ^ current) {
+                        sameCount++;
+                    } else {
+                        head = current;
+                        if (sameCount >= 5) {
+                            lostPoint += (3 + sameCount - 5);
+                        }
+                        sameCount = 1;
+                    }
+                }
+            }
+            // LEVEL4
+            var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+            lostPoint += ratio * 10;
+            return lostPoint;
+        }
+
+    };
+    //---------------------------------------------------------------------
+    // QRMath使用的数学工具
+    //---------------------------------------------------------------------
+    var QRMath = {
+        /*
+        将n转化为a^m
+         */
+        glog: function (n) {
+            if (n < 1) {
+                throw new Error("glog(" + n + ")");
+            }
+            return QRMath.LOG_TABLE[n];
+        },
+        /*
+        将a^m转化为n
+         */
+        gexp: function (n) {
+            while (n < 0) {
+                n += 255;
+            }
+            while (n >= 256) {
+                n -= 255;
+            }
+            return QRMath.EXP_TABLE[n];
+        },
+        EXP_TABLE: new Array(256),
+        LOG_TABLE: new Array(256)
+
+    };
+    for (var i = 0; i < 8; i++) {
+        QRMath.EXP_TABLE[i] = 1 << i;
+    }
+    for (var i = 8; i < 256; i++) {
+        QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8];
+    }
+    for (var i = 0; i < 255; i++) {
+        QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+    }
+    //---------------------------------------------------------------------
+    // QRPolynomial 多项式
+    //---------------------------------------------------------------------
+    /**
+     * 多项式类
+     * @param {Array} num   系数
+     * @param {num} shift a^shift
+     */
+    function QRPolynomial(num, shift) {
+        if (num.length == undefined) {
+            throw new Error(num.length + "/" + shift);
+        }
+        var offset = 0;
+        while (offset < num.length && num[offset] == 0) {
+            offset++;
+        }
+        this.num = new Array(num.length - offset + shift);
+        for (var i = 0; i < num.length - offset; i++) {
+            this.num[i] = num[i + offset];
+        }
+    }
+    QRPolynomial.prototype = {
+        get: function (index) {
+            return this.num[index];
+        },
+        getLength: function () {
+            return this.num.length;
+        },
+        /**
+         * 多项式乘法
+         * @param  {QRPolynomial} e 被乘多项式
+         * @return {[type]}   [description]
+         */
+        multiply: function (e) {
+            var num = new Array(this.getLength() + e.getLength() - 1);
+            for (var i = 0; i < this.getLength(); i++) {
+                for (var j = 0; j < e.getLength(); j++) {
+                    num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
+                }
+            }
+            return new QRPolynomial(num, 0);
+        },
+        /**
+         * 多项式模运算
+         * @param  {QRPolynomial} e 模多项式
+         * @return {}
+         */
+        mod: function (e) {
+            var tl = this.getLength(),
+                el = e.getLength();
+            if (tl - el < 0) {
+                return this;
+            }
+            var num = new Array(tl);
+            for (var i = 0; i < tl; i++) {
+                num[i] = this.get(i);
+            }
+            while (num.length >= el) {
+                var ratio = QRMath.glog(num[0]) - QRMath.glog(e.get(0));
+
+                for (var i = 0; i < e.getLength(); i++) {
+                    num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
+                }
+                while (num[0] == 0) {
+                    num.shift();
+                }
+            }
+            return new QRPolynomial(num, 0);
+        }
+    };
+
+    //---------------------------------------------------------------------
+    // RS_BLOCK_TABLE
+    //---------------------------------------------------------------------
+    /*
+    二维码各个版本信息[块数, 每块中的数据块数, 每块中的信息块数]
+     */
+    var RS_BLOCK_TABLE = [
+        // L
+        // M
+        // Q
+        // H
+        // 1
+        [1, 26, 19],
+        [1, 26, 16],
+        [1, 26, 13],
+        [1, 26, 9],
+
+        // 2
+        [1, 44, 34],
+        [1, 44, 28],
+        [1, 44, 22],
+        [1, 44, 16],
+
+        // 3
+        [1, 70, 55],
+        [1, 70, 44],
+        [2, 35, 17],
+        [2, 35, 13],
+
+        // 4
+        [1, 100, 80],
+        [2, 50, 32],
+        [2, 50, 24],
+        [4, 25, 9],
+
+        // 5
+        [1, 134, 108],
+        [2, 67, 43],
+        [2, 33, 15, 2, 34, 16],
+        [2, 33, 11, 2, 34, 12],
+
+        // 6
+        [2, 86, 68],
+        [4, 43, 27],
+        [4, 43, 19],
+        [4, 43, 15],
+
+        // 7
+        [2, 98, 78],
+        [4, 49, 31],
+        [2, 32, 14, 4, 33, 15],
+        [4, 39, 13, 1, 40, 14],
+
+        // 8
+        [2, 121, 97],
+        [2, 60, 38, 2, 61, 39],
+        [4, 40, 18, 2, 41, 19],
+        [4, 40, 14, 2, 41, 15],
+
+        // 9
+        [2, 146, 116],
+        [3, 58, 36, 2, 59, 37],
+        [4, 36, 16, 4, 37, 17],
+        [4, 36, 12, 4, 37, 13],
+
+        // 10
+        [2, 86, 68, 2, 87, 69],
+        [4, 69, 43, 1, 70, 44],
+        [6, 43, 19, 2, 44, 20],
+        [6, 43, 15, 2, 44, 16],
+
+        // 11
+        [4, 101, 81],
+        [1, 80, 50, 4, 81, 51],
+        [4, 50, 22, 4, 51, 23],
+        [3, 36, 12, 8, 37, 13],
+
+        // 12
+        [2, 116, 92, 2, 117, 93],
+        [6, 58, 36, 2, 59, 37],
+        [4, 46, 20, 6, 47, 21],
+        [7, 42, 14, 4, 43, 15],
+
+        // 13
+        [4, 133, 107],
+        [8, 59, 37, 1, 60, 38],
+        [8, 44, 20, 4, 45, 21],
+        [12, 33, 11, 4, 34, 12],
+
+        // 14
+        [3, 145, 115, 1, 146, 116],
+        [4, 64, 40, 5, 65, 41],
+        [11, 36, 16, 5, 37, 17],
+        [11, 36, 12, 5, 37, 13],
+
+        // 15
+        [5, 109, 87, 1, 110, 88],
+        [5, 65, 41, 5, 66, 42],
+        [5, 54, 24, 7, 55, 25],
+        [11, 36, 12],
+
+        // 16
+        [5, 122, 98, 1, 123, 99],
+        [7, 73, 45, 3, 74, 46],
+        [15, 43, 19, 2, 44, 20],
+        [3, 45, 15, 13, 46, 16],
+
+        // 17
+        [1, 135, 107, 5, 136, 108],
+        [10, 74, 46, 1, 75, 47],
+        [1, 50, 22, 15, 51, 23],
+        [2, 42, 14, 17, 43, 15],
+
+        // 18
+        [5, 150, 120, 1, 151, 121],
+        [9, 69, 43, 4, 70, 44],
+        [17, 50, 22, 1, 51, 23],
+        [2, 42, 14, 19, 43, 15],
+
+        // 19
+        [3, 141, 113, 4, 142, 114],
+        [3, 70, 44, 11, 71, 45],
+        [17, 47, 21, 4, 48, 22],
+        [9, 39, 13, 16, 40, 14],
+
+        // 20
+        [3, 135, 107, 5, 136, 108],
+        [3, 67, 41, 13, 68, 42],
+        [15, 54, 24, 5, 55, 25],
+        [15, 43, 15, 10, 44, 16],
+
+        // 21
+        [4, 144, 116, 4, 145, 117],
+        [17, 68, 42],
+        [17, 50, 22, 6, 51, 23],
+        [19, 46, 16, 6, 47, 17],
+
+        // 22
+        [2, 139, 111, 7, 140, 112],
+        [17, 74, 46],
+        [7, 54, 24, 16, 55, 25],
+        [34, 37, 13],
+
+        // 23
+        [4, 151, 121, 5, 152, 122],
+        [4, 75, 47, 14, 76, 48],
+        [11, 54, 24, 14, 55, 25],
+        [16, 45, 15, 14, 46, 16],
+
+        // 24
+        [6, 147, 117, 4, 148, 118],
+        [6, 73, 45, 14, 74, 46],
+        [11, 54, 24, 16, 55, 25],
+        [30, 46, 16, 2, 47, 17],
+
+        // 25
+        [8, 132, 106, 4, 133, 107],
+        [8, 75, 47, 13, 76, 48],
+        [7, 54, 24, 22, 55, 25],
+        [22, 45, 15, 13, 46, 16],
+
+        // 26
+        [10, 142, 114, 2, 143, 115],
+        [19, 74, 46, 4, 75, 47],
+        [28, 50, 22, 6, 51, 23],
+        [33, 46, 16, 4, 47, 17],
+
+        // 27
+        [8, 152, 122, 4, 153, 123],
+        [22, 73, 45, 3, 74, 46],
+        [8, 53, 23, 26, 54, 24],
+        [12, 45, 15, 28, 46, 16],
+
+        // 28
+        [3, 147, 117, 10, 148, 118],
+        [3, 73, 45, 23, 74, 46],
+        [4, 54, 24, 31, 55, 25],
+        [11, 45, 15, 31, 46, 16],
+
+        // 29
+        [7, 146, 116, 7, 147, 117],
+        [21, 73, 45, 7, 74, 46],
+        [1, 53, 23, 37, 54, 24],
+        [19, 45, 15, 26, 46, 16],
+
+        // 30
+        [5, 145, 115, 10, 146, 116],
+        [19, 75, 47, 10, 76, 48],
+        [15, 54, 24, 25, 55, 25],
+        [23, 45, 15, 25, 46, 16],
+
+        // 31
+        [13, 145, 115, 3, 146, 116],
+        [2, 74, 46, 29, 75, 47],
+        [42, 54, 24, 1, 55, 25],
+        [23, 45, 15, 28, 46, 16],
+
+        // 32
+        [17, 145, 115],
+        [10, 74, 46, 23, 75, 47],
+        [10, 54, 24, 35, 55, 25],
+        [19, 45, 15, 35, 46, 16],
+
+        // 33
+        [17, 145, 115, 1, 146, 116],
+        [14, 74, 46, 21, 75, 47],
+        [29, 54, 24, 19, 55, 25],
+        [11, 45, 15, 46, 46, 16],
+
+        // 34
+        [13, 145, 115, 6, 146, 116],
+        [14, 74, 46, 23, 75, 47],
+        [44, 54, 24, 7, 55, 25],
+        [59, 46, 16, 1, 47, 17],
+
+        // 35
+        [12, 151, 121, 7, 152, 122],
+        [12, 75, 47, 26, 76, 48],
+        [39, 54, 24, 14, 55, 25],
+        [22, 45, 15, 41, 46, 16],
+
+        // 36
+        [6, 151, 121, 14, 152, 122],
+        [6, 75, 47, 34, 76, 48],
+        [46, 54, 24, 10, 55, 25],
+        [2, 45, 15, 64, 46, 16],
+
+        // 37
+        [17, 152, 122, 4, 153, 123],
+        [29, 74, 46, 14, 75, 47],
+        [49, 54, 24, 10, 55, 25],
+        [24, 45, 15, 46, 46, 16],
+
+        // 38
+        [4, 152, 122, 18, 153, 123],
+        [13, 74, 46, 32, 75, 47],
+        [48, 54, 24, 14, 55, 25],
+        [42, 45, 15, 32, 46, 16],
+
+        // 39
+        [20, 147, 117, 4, 148, 118],
+        [40, 75, 47, 7, 76, 48],
+        [43, 54, 24, 22, 55, 25],
+        [10, 45, 15, 67, 46, 16],
+
+        // 40
+        [19, 148, 118, 6, 149, 119],
+        [18, 75, 47, 31, 76, 48],
+        [34, 54, 24, 34, 55, 25],
+        [20, 45, 15, 61, 46, 16]
+    ];
+
+    /**
+     * 根据数据获取对应版本
+     * @return {[type]} [description]
+     */
+    QRCodeAlg.prototype.getRightType = function () {
+        for (var typeNumber = 1; typeNumber < 41; typeNumber++) {
+            var rsBlock = RS_BLOCK_TABLE[(typeNumber - 1) * 4 + this.errorCorrectLevel];
+            if (rsBlock == undefined) {
+                throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + this.errorCorrectLevel);
+            }
+            var length = rsBlock.length / 3;
+            var totalDataCount = 0;
+            for (var i = 0; i < length; i++) {
+                var count = rsBlock[i * 3 + 0];
+                var dataCount = rsBlock[i * 3 + 2];
+                totalDataCount += dataCount * count;
+            }
+            var lengthBytes = typeNumber > 9 ? 2 : 1;
+            if (this.utf8bytes.length + lengthBytes < totalDataCount || typeNumber == 40) {
+                this.typeNumber = typeNumber;
+                this.rsBlock = rsBlock;
+                this.totalDataCount = totalDataCount;
+                break;
+            }
+        }
+    };
+
+    //---------------------------------------------------------------------
+    // QRBitBuffer
+    //---------------------------------------------------------------------
+    function QRBitBuffer() {
+        this.buffer = new Array();
+        this.length = 0;
+    }
+    QRBitBuffer.prototype = {
+        get: function (index) {
+            var bufIndex = Math.floor(index / 8);
+            return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1);
+        },
+        put: function (num, length) {
+            for (var i = 0; i < length; i++) {
+                this.putBit(((num >>> (length - i - 1)) & 1));
+            }
+        },
+        putBit: function (bit) {
+            var bufIndex = Math.floor(this.length / 8);
+            if (this.buffer.length <= bufIndex) {
+                this.buffer.push(0);
+            }
+            if (bit) {
+                this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
+            }
+            this.length++;
+        }
+    };
+
+
+
+    // xzedit
+    let qrcodeAlgObjCache = [];
+    /**
+     * 二维码构造函数,主要用于绘制
+     * @param  {参数列表} opt 传递参数
+     * @return {}
+     */
+    QRCode = function (opt) {
+        //设置默认参数
+        this.options = {
+            text: '',
+            size: 256,
+            correctLevel: 3,
+            background: '#ffffff',
+            foreground: '#000000',
+            pdground: '#000000',
+            image: '',
+            imageSize: 30,
+            canvasId: opt.canvasId,
+            context: opt.context,
+            usingComponents: opt.usingComponents,
+            showLoading: opt.showLoading,
+            loadingText: opt.loadingText,
+        };
+        if (typeof opt === 'string') { // 只编码ASCII字符串
+            opt = {
+                text: opt
+            };
+        }
+        if (opt) {
+            for (var i in opt) {
+                this.options[i] = opt[i];
+            }
+        }
+        //使用QRCodeAlg创建二维码结构
+        var qrCodeAlg = null;
+        for (var i = 0, l = qrcodeAlgObjCache.length; i < l; i++) {
+            if (qrcodeAlgObjCache[i].text == this.options.text && qrcodeAlgObjCache[i].text.correctLevel == this.options.correctLevel) {
+                qrCodeAlg = qrcodeAlgObjCache[i].obj;
+                break;
+            }
+        }
+        if (i == l) {
+            qrCodeAlg = new QRCodeAlg(this.options.text, this.options.correctLevel);
+            qrcodeAlgObjCache.push({
+                text: this.options.text,
+                correctLevel: this.options.correctLevel,
+                obj: qrCodeAlg
+            });
+        }
+        /**
+         * 计算矩阵点的前景色
+         * @param {Obj} config
+         * @param {Number} config.row 点x坐标
+         * @param {Number} config.col 点y坐标
+         * @param {Number} config.count 矩阵大小
+         * @param {Number} config.options 组件的options
+         * @return {String}
+         */
+        let getForeGround = function (config) {
+            var options = config.options;
+            if (options.pdground && (
+                (config.row > 1 && config.row < 5 && config.col > 1 && config.col < 5) ||
+                (config.row > (config.count - 6) && config.row < (config.count - 2) && config.col > 1 && config.col < 5) ||
+                (config.row > 1 && config.row < 5 && config.col > (config.count - 6) && config.col < (config.count - 2))
+            )) {
+                return options.pdground;
+            }
+            return options.foreground;
+        }
+        // 创建canvas
+        let createCanvas = function (options) {
+            if (options.showLoading) {
+                uni.showLoading({
+                    title: options.loadingText,
+                    mask: true
+                });
+            }
+            var ctx = uni.createCanvasContext(options.canvasId, options.context);
+            var count = qrCodeAlg.getModuleCount();
+            var ratioSize = options.size;
+            var ratioImgSize = options.imageSize;
+            //计算每个点的长宽
+            var tileW = (ratioSize / count).toPrecision(4);
+            var tileH = (ratioSize / count).toPrecision(4);
+            //绘制
+            for (var row = 0; row < count; row++) {
+                for (var col = 0; col < count; col++) {
+                    var w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW));
+                    var h = (Math.ceil((row + 1) * tileW) - Math.floor(row * tileW));
+                    var foreground = getForeGround({
+                        row: row,
+                        col: col,
+                        count: count,
+                        options: options
+                    });
+                    ctx.setFillStyle(qrCodeAlg.modules[row][col] ? foreground : options.background);
+                    ctx.fillRect(Math.round(col * tileW), Math.round(row * tileH), w, h);
+                }
+            }
+            if (options.image) {
+                var x = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+                var y = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+                drawRoundedRect(ctx, x, y, ratioImgSize, ratioImgSize, 2, 6, true, true)
+                ctx.drawImage(options.image, x, y, ratioImgSize, ratioImgSize);
+                // 画圆角矩形
+                function drawRoundedRect(ctxi, x, y, width, height, r, lineWidth, fill, stroke) {
+                    ctxi.setLineWidth(lineWidth);
+                    ctxi.setFillStyle(options.background);
+                    ctxi.setStrokeStyle(options.background);
+                    ctxi.beginPath(); // draw top and top right corner 
+                    ctxi.moveTo(x + r, y);
+                    ctxi.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner 
+                    ctxi.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner 
+                    ctxi.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner 
+                    ctxi.arcTo(x, y, x + r, y, r);
+                    ctxi.closePath();
+                    if (fill) {
+                        ctxi.fill();
+                    }
+                    if (stroke) {
+                        ctxi.stroke();
+                    }
+                }
+            }
+            setTimeout(() => {
+                ctx.draw(true, () => {
+                    // 保存到临时区域
+                    setTimeout(() => {
+                        uni.canvasToTempFilePath({
+                            width: options.width,
+                            height: options.height,
+                            destWidth: options.width,
+                            destHeight: options.height,
+                            canvasId: options.canvasId,
+                            quality: Number(1),
+                            success: function (res) {
+                                if (options.cbResult) {
+                                    options.cbResult(res.tempFilePath)
+                                }
+                            },
+                            fail: function (res) {
+                                if (options.cbResult) {
+                                    options.cbResult(res)
+                                }
+                            },
+                            complete: function () {
+                                if (options.showLoading){
+                                    uni.hideLoading();
+                                }
+                            },
+                        }, options.context);
+                    }, options.text.length + 100);
+                });
+            }, options.usingComponents ? 0 : 150);
+        }
+        createCanvas(this.options);
+        // 空判定
+        let empty = function (v) {
+            let tp = typeof v,
+                rt = false;
+            if (tp == "number" && String(v) == "") {
+                rt = true
+            } else if (tp == "undefined") {
+                rt = true
+            } else if (tp == "object") {
+                if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
+            } else if (tp == "string") {
+                if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
+            } else if (tp == "function") {
+                rt = false
+            }
+            return rt
+        }
+    };
+    QRCode.prototype.clear = function (fn) {
+        var ctx = uni.createCanvasContext(this.options.canvasId, this.options.context)
+        ctx.clearRect(0, 0, this.options.size, this.options.size)
+        ctx.draw(false, () => {
+            if (fn) {
+                fn()
+            }
+        })
+    };
+})()
+
+export default QRCode

+ 210 - 0
components/tki-qrcode/tki-qrcode.vue

@@ -0,0 +1,210 @@
+<template xlang="wxml" minapp="mpvue">
+	<view class="tki-qrcode">
+		<!-- #ifndef MP-ALIPAY -->
+		<canvas class="tki-qrcode-canvas" :canvas-id="cid" :style="{width:cpSize+'px',height:cpSize+'px'}" />
+		<!-- #endif -->
+		<!-- #ifdef MP-ALIPAY -->
+		<canvas :id="cid" :width="cpSize" :height="cpSize" class="tki-qrcode-canvas" />
+		<!-- #endif -->
+		<image v-show="show" :src="result" :style="{width:cpSize+'px',height:cpSize+'px'}" />
+	</view>
+</template>
+
+<script>
+import QRCode from "./qrcode.js"
+let qrcode
+export default {
+	name: "tki-qrcode",
+	props: {
+		cid: {
+			type: String,
+			default: 'tki-qrcode-canvas'
+		},
+		size: {
+			type: Number,
+			default: 200
+		},
+		unit: {
+			type: String,
+			default: 'upx'
+		},
+		show: {
+			type: Boolean,
+			default: true
+		},
+		val: {
+			type: String,
+			default: ''
+		},
+		background: {
+			type: String,
+			default: '#ffffff'
+		},
+		foreground: {
+			type: String,
+			default: '#000000'
+		},
+		pdground: {
+			type: String,
+			default: '#000000'
+		},
+		icon: {
+			type: String,
+			default: ''
+		},
+		iconSize: {
+			type: Number,
+			default: 40
+		},
+		lv: {
+			type: Number,
+			default: 3
+		},
+		onval: {
+			type: Boolean,
+			default: false
+		},
+		loadMake: {
+			type: Boolean,
+			default: false
+		},
+		usingComponents: {
+			type: Boolean,
+			default: true
+		},
+		showLoading: {
+			type: Boolean,
+			default: true
+		},
+		loadingText: {
+			type: String,
+			default: '二维码生成中'
+		},
+	},
+	data() {
+		return {
+			result: '',
+		}
+	},
+	methods: {
+		_makeCode() {
+			let that = this
+			if (!this._empty(this.val)) {
+				qrcode = new QRCode({
+					context: that, // 上下文环境
+					canvasId:that.cid, // canvas-id
+					usingComponents: that.usingComponents, // 是否是自定义组件
+					showLoading: that.showLoading, // 是否显示loading
+					loadingText: that.loadingText, // loading文字
+					text: that.val, // 生成内容
+					size: that.cpSize, // 二维码大小
+					background: that.background, // 背景色
+					foreground: that.foreground, // 前景色
+					pdground: that.pdground, // 定位角点颜色
+					correctLevel: that.lv, // 容错级别
+					image: that.icon, // 二维码图标
+					imageSize: that.iconSize,// 二维码图标大小
+					cbResult: function (res) { // 生成二维码的回调
+						that._result(res)
+					},
+				});
+			} else {
+				uni.showToast({
+					title: '二维码内容不能为空',
+					icon: 'none',
+					duration: 2000
+				});
+			}
+		},
+		_clearCode() {
+			this._result('')
+			qrcode.clear()
+		},
+		_saveCode() {
+			let that = this;
+			if (this.result != "") {
+				uni.saveImageToPhotosAlbum({
+					filePath: that.result,
+					success: function () {
+						uni.showToast({
+							title: '二维码保存成功',
+							icon: 'success',
+							duration: 2000
+						});
+					}
+				});
+			}
+		},
+		_result(res) {
+			this.result = res;
+			this.$emit('result', res)
+		},
+		_empty(v) {
+			let tp = typeof v,
+				rt = false;
+			if (tp == "number" && String(v) == "") {
+				rt = true
+			} else if (tp == "undefined") {
+				rt = true
+			} else if (tp == "object") {
+				if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
+			} else if (tp == "string") {
+				if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
+			} else if (tp == "function") {
+				rt = false
+			}
+			return rt
+		}
+	},
+	watch: {
+		size: function (n, o) {
+			if (n != o && !this._empty(n)) {
+				this.cSize = n
+				if (!this._empty(this.val)) {
+					setTimeout(() => {
+						this._makeCode()
+					}, 100);
+				}
+			}
+		},
+		val: function (n, o) {
+			if (this.onval) {
+				if (n != o && !this._empty(n)) {
+					setTimeout(() => {
+						this._makeCode()
+					}, 0);
+				}
+			}
+		}
+	},
+	computed: {
+		cpSize() {
+			if(this.unit == "upx"){
+				return uni.upx2px(this.size)
+			}else{
+				return this.size
+			}
+		}
+	},
+	mounted: function () {
+		if (this.loadMake) {
+			if (!this._empty(this.val)) {
+				setTimeout(() => {
+					this._makeCode()
+				}, 0);
+			}
+		}
+	},
+}
+</script>
+<style>
+.tki-qrcode {
+  position: relative;
+}
+.tki-qrcode-canvas {
+  position: fixed;
+  top: -99999upx;
+  left: -99999upx;
+  z-index: -99999;
+}
+</style>

+ 5 - 0
node_modules/.package-lock.json

@@ -895,6 +895,11 @@
         "untyped": "dist/cli.mjs"
       }
     },
+    "node_modules/uqrcodejs": {
+      "version": "4.0.7",
+      "resolved": "https://artifactory.devops.xiaohongshu.com/artifactory/api/npm/npm-public/uqrcodejs/-/uqrcodejs-4.0.7.tgz",
+      "integrity": "sha512-84+aZmD2godCVI+93lxE3YUAPNY8zAJvNA7xRS7R7U+q57KzMDepBSfNCwoRUhWOfR6eHFoAOcHRPwsP6ka1cA=="
+    },
     "node_modules/vue": {
       "version": "3.5.16",
       "resolved": "https://artifactory.devops.xiaohongshu.com/artifactory/api/npm/npm-public/vue/-/vue-3.5.16.tgz",

+ 201 - 0
node_modules/uqrcodejs/LICENSE.md

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

File diff suppressed because it is too large
+ 207 - 0
node_modules/uqrcodejs/README.md


+ 27 - 0
node_modules/uqrcodejs/package.json

@@ -0,0 +1,27 @@
+{
+  "name": "uqrcodejs",
+  "version": "4.0.7",
+  "description": "uQRCode是一款基于Javascript环境开发的二维码生成插件,适用所有Javascript运行环境的前端应用和Node.js。",
+  "main": "uqrcode.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/Sansnn/uQRCode.git"
+  },
+  "keywords": [
+    "uQRCode",
+    "QR",
+    "QRCode",
+    "artQRCode",
+    "二维码",
+    "艺术码"
+  ],
+  "author": "Sansnn",
+  "license": "Apache-2.0",
+  "bugs": {
+    "url": "https://github.com/Sansnn/uQRCode/issues"
+  },
+  "homepage": "https://github.com/Sansnn/uQRCode#readme"
+}

File diff suppressed because it is too large
+ 33 - 0
node_modules/uqrcodejs/uqrcode.js


+ 12 - 1
package-lock.json

@@ -6,7 +6,8 @@
     "": {
       "dependencies": {
         "pinia": "^3.0.3",
-        "pinia-plugin-persistedstate": "^4.3.0"
+        "pinia-plugin-persistedstate": "^4.3.0",
+        "uqrcodejs": "^4.0.7"
       }
     },
     "node_modules/@babel/helper-string-parser": {
@@ -901,6 +902,11 @@
         "untyped": "dist/cli.mjs"
       }
     },
+    "node_modules/uqrcodejs": {
+      "version": "4.0.7",
+      "resolved": "https://artifactory.devops.xiaohongshu.com/artifactory/api/npm/npm-public/uqrcodejs/-/uqrcodejs-4.0.7.tgz",
+      "integrity": "sha512-84+aZmD2godCVI+93lxE3YUAPNY8zAJvNA7xRS7R7U+q57KzMDepBSfNCwoRUhWOfR6eHFoAOcHRPwsP6ka1cA=="
+    },
     "node_modules/vue": {
       "version": "3.5.16",
       "resolved": "https://artifactory.devops.xiaohongshu.com/artifactory/api/npm/npm-public/vue/-/vue-3.5.16.tgz",
@@ -1604,6 +1610,11 @@
         "scule": "^1.3.0"
       }
     },
+    "uqrcodejs": {
+      "version": "4.0.7",
+      "resolved": "https://artifactory.devops.xiaohongshu.com/artifactory/api/npm/npm-public/uqrcodejs/-/uqrcodejs-4.0.7.tgz",
+      "integrity": "sha512-84+aZmD2godCVI+93lxE3YUAPNY8zAJvNA7xRS7R7U+q57KzMDepBSfNCwoRUhWOfR6eHFoAOcHRPwsP6ka1cA=="
+    },
     "vue": {
       "version": "3.5.16",
       "resolved": "https://artifactory.devops.xiaohongshu.com/artifactory/api/npm/npm-public/vue/-/vue-3.5.16.tgz",

+ 2 - 1
package.json

@@ -1,6 +1,7 @@
 {
   "dependencies": {
     "pinia": "^3.0.3",
-    "pinia-plugin-persistedstate": "^4.3.0"
+    "pinia-plugin-persistedstate": "^4.3.0",
+    "uqrcodejs": "^4.0.7"
   }
 }

+ 43 - 0
pages.json

@@ -27,6 +27,49 @@
 		        "navigationBarTitleText": "个人中心",
 		        "navigationStyle": "custom"
 		      }
+		},
+		{
+			"path" : "pages/checkin/index",
+			"style" : 
+			{
+				"navigationBarTitleText" : "课堂签到",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path" : "pages/checkin/teacher/index",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
+		},
+		{
+			"path" : "pages/checkin/teacher/select-method",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
+		},
+		{
+			"path" : "pages/checkin/teacher/number-setup/number-setup",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
+		},
+		{
+			"path" : "pages/checkin/teacher/number-setup",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
+		},
+		{
+			"path" : "pages/checkin/teacher/qr-active",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
 		}
 	],
 	"globalStyle": {

+ 315 - 0
pages/checkin/index.vue

@@ -0,0 +1,315 @@
+<template>
+  <!-- 学生视图:仅当用户不是教师时渲染 -->
+  <view v-if="!isTeacher" class="student-container">
+    <NavBar ref="navBarRef" :title="navTitle" :showBack="studentView !== 'main'" @back="onBack" />
+    <view class="content-wrapper" :style="contentWrapperStyle">
+        
+        <!-- 视图1: 学生主界面 -->
+        <view v-if="studentView === 'main'" class="student-card">
+          <view class="header-content">
+            <text class="title">课堂签到</text>
+            <view class="greeting-wrapper student">
+              <text class="greeting">{{ studentGreeting }}</text>
+            </view>
+            <text class="timestamp">{{ formattedCurrentTime }}</text>
+          </view>
+          <view class="course-info">
+            <text class="label">当前课程</text>
+            <text class="value">英语写作</text>
+          </view>
+          <view class="checkin-action">
+            <text class="label">当前签到方式:</text>
+            <button class="checkin-button" :disabled="!checkinStore.session.isActive || isAlreadyCheckedIn" @click="studentAction">
+              {{ studentButtonText }}
+            </button>
+          </view>
+        </view>
+
+        <!-- 视图2: 数字签到输入界面 -->
+        <view v-else-if="studentView === 'numberInput'" class="student-card">
+           <view class="header-content"><text class="title">输入签到码</text></view>
+           <view class="code-input-area">
+            <text class="label">输入老师指定的4位数字</text>
+            <view class="code-inputs">
+              <input v-for="(item, index) in studentCode" :key="index" v-model="studentCode[index]" type="number" maxlength="1"/>
+            </view>
+          </view>
+          <button class="start-button student" :disabled="!isStudentCodeValid" @click="submitNumberCode">确认签到</button>
+        </view>
+    </view>
+  </view>
+  
+  <!-- 教师重定向时的占位符 -->
+  <view v-else class="loading-container">
+    <text>正在加载教师端...</text>
+  </view>
+</template>
+
+<script setup>
+// --- 您的 <script setup> 部分代码完全正确,无需任何修改 ---
+// --- 为了简洁,这里省略,请保留您原来的 script 内容 ---
+import { ref, onMounted, computed, watch } from 'vue'
+import NavBar from '@/pages/components/NavBar/NavBar.vue' 
+import { useUserStore } from '@/store/user'
+import { useCheckinStore } from '@/store/checkin'
+import { onShow } from '@dcloudio/uni-app'
+
+const userStore = useUserStore()
+const checkinStore = useCheckinStore()
+const isTeacher = computed(() => userStore.userInfo.role === 'TEACHER')
+
+onShow(() => {
+  if (isTeacher.value) {
+    uni.redirectTo({
+      url: '/pages/checkin/teacher/index'
+    })
+  }
+})
+
+const studentView = ref('main') 
+const navBarRef = ref(null)
+const navHeight = ref(88)
+
+const navTitle = computed(() => {
+    if (studentView.value === 'numberInput') return '输入签到码'
+    return '课堂签到'
+})
+
+const studentCode = ref(['', '', '', ''])
+const isStudentCodeValid = computed(() => studentCode.value.every(c => c !== '' && !isNaN(c)))
+const isAlreadyCheckedIn = computed(() => checkinStore.isStudentCheckedIn(userStore.userInfo.id))
+
+const studentGreeting = computed(() => {
+  if (isAlreadyCheckedIn.value) return '你已成功签到,表现不错!👍'
+  if (!checkinStore.session.isActive) return '签到还未开始,请稍作等待。'
+  return `${userStore.userInfo.name || userStore.userInfo.englishName || '同学'}, 签到开始啦~`
+})
+
+const studentButtonText = computed(() => {
+  if (isAlreadyCheckedIn.value) return '已签到'
+  if (!checkinStore.session.isActive) return '暂未设置'
+  return checkinStore.session.method === 'number' ? '数字签到' : '二维码签到'
+})
+
+const studentAction = () => {
+  if (checkinStore.session.method === 'number') {
+    studentView.value = 'numberInput'
+  } else {
+    uni.scanCode({
+      onlyFromCamera: true,
+      success: (res) => {
+        if(res.result === checkinStore.session.code) {
+          const result = checkinStore.attemptCheckin({ id: userStore.userInfo.id, name: userStore.userInfo.name })
+          uni.showToast({ title: result.message, icon: result.success ? 'success' : 'none' })
+        } else {
+          uni.showToast({ title: '无效的签到码', icon: 'none' })
+        }
+      },
+      fail: () => {
+        uni.showToast({ title: '取消扫码', icon: 'none' })
+      }
+    })
+  }
+}
+
+const submitNumberCode = () => {
+  const code = studentCode.value.join('')
+  const result = checkinStore.attemptCheckin({ id: userStore.userInfo.id, name: userStore.userInfo.name }, code)
+  uni.showToast({ title: result.message, icon: result.success ? 'success' : 'none' })
+  if(result.success) {
+    studentView.value = 'main'
+  }
+}
+
+const onBack = () => {
+    if (studentView.value !== 'main') {
+        studentView.value = 'main';
+    }
+}
+
+const formattedCurrentTime = computed(() => {
+  const now = new Date();
+  const year = now.getFullYear();
+  const month = now.getMonth() + 1;
+  const day = now.getDate();
+  const hours = String(now.getHours()).padStart(2, '0');
+  const minutes = String(now.getMinutes()).padStart(2, '0');
+  return `${year}年${month}月${day}日 ${hours}:${minutes}`;
+})
+
+const contentWrapperStyle = computed(() => ({
+  paddingTop: `${navHeight.value}px`,
+  // 让内容区撑满剩余屏幕
+  minHeight: `calc(100vh - ${navHeight.value}px)` 
+}))
+
+watch(() => checkinStore.session.isActive, (newVal) => {
+  if(!newVal && !isTeacher.value) {
+    studentView.value = 'main'
+  }
+})
+
+onMounted(() => {
+    watch(
+       () => navBarRef.value?.totalNavHeight,
+       (val) => { if (val) navHeight.value = val },
+       { immediate: true }
+     )
+})
+
+</script>
+
+<style scoped>
+/* 容器样式 */
+.student-container, .loading-container {
+  min-height: 100vh;
+  background-color: #f5f5f7;
+}
+.loading-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #999;
+  font-size: 30rpx;
+}
+.content-wrapper {
+  padding: 20rpx 30rpx;
+  box-sizing: border-box;
+  /* 【新增】让内容包装器也成为flex容器,使其子元素可以撑开 */
+  display: flex;
+}
+
+/* 学生卡片样式 */
+.student-card {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  padding: 60rpx 40rpx; /* 增加垂直内边距 */
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  border-top: 8rpx solid #5CB85C;
+
+  flex: 1; /* 让卡片撑满父容器(.content-wrapper)的高度 */
+  justify-content: space-evenly; /* 让卡片内的元素在垂直方向上均匀分布 */
+}
+
+/* 头部内容 */
+.header-content {
+  /* 【修改】使用flex进行垂直居中对齐 */
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16rpx; /* 统一设置元素间距 */
+}
+.title {
+  /* 移除 display: flex */
+  font-size: 48rpx;
+  font-weight: 600;
+  text-align: center;
+}
+.greeting-wrapper {
+  /* 移除 margin-top */
+  display: inline-flex;
+  padding: 10rpx 24rpx;
+  border-radius: 30rpx;
+}
+.greeting-wrapper.student {
+  background-color: #EAF7EA;
+}
+.greeting-wrapper.student .greeting {
+  color: #5CB85C;
+  font-size: 28rpx;
+  font-weight: 500;
+}
+.timestamp {
+  font-size: 28rpx;
+  color: #999;
+  text-align: center;
+  /* 移除 margin-top */
+}
+.label {
+  font-size: 30rpx;
+  color: #666;
+}
+.value {
+  font-size: 32rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+/* 课程和操作区域 */
+.course-info, .checkin-action {
+  width: 100%;
+  text-align: center;
+  /* 【移除】移除固定的 margin-top,交由 flex 布局自动分配 */
+}
+.course-info .value {
+  font-size: 44rpx;
+  font-weight: 600;
+  margin-top: 10rpx;
+  display: block;
+}
+.checkin-action .label {
+  margin-bottom: 30rpx;
+  display: block;
+}
+.checkin-button {
+  width: 80%;
+  height: 90rpx;
+  line-height: 90rpx;
+  background-color: #5CB85C;
+  color: #fff;
+  border-radius: 45rpx;
+  border: none;
+  font-size: 34rpx;
+}
+.checkin-button[disabled] {
+  background-color: #ccc;
+  color: #666;
+}
+
+/* 数字码输入区 */
+.code-input-area {
+  text-align: center;
+  width: 100%;
+  /* 【移除】移除固定的 margin-top */
+}
+.code-input-area .label {
+  font-size: 28rpx;
+  color: #999;
+}
+.code-inputs {
+  display: flex;
+  gap: 20rpx;
+  margin-top: 30rpx;
+  justify-content: center;
+}
+.code-inputs input {
+  width: 100rpx;
+  height: 120rpx;
+  border: 1px solid #ccc;
+  border-radius: 12rpx;
+  text-align: center;
+  font-size: 48rpx;
+  font-weight: bold;
+}
+.start-button.student {
+  width: 80%;
+  height: 90rpx;
+  line-height: 90rpx;
+  background-color: #5CB85C;
+  color: #fff;
+  border-radius: 45rpx;
+  border: none;
+  font-size: 34rpx;
+  /* 【移除】移除固定的 margin-top */
+}
+.start-button[disabled] {
+  background-color: #ccc;
+  color: #666;
+}
+</style>

+ 180 - 0
pages/checkin/teacher/index.vue

@@ -0,0 +1,180 @@
+<template>
+  <view class="teacher-container">
+    <NavBar ref="navBarRef" title="课堂签到" />
+    <view class="content-wrapper" :style="contentWrapperStyle">
+      <view class="teacher-card">
+        <!-- 头部内容 - 清晰分行布局 -->
+        <view class="header-content">
+          <text class="title">课堂签到</text>
+          <view class="greeting-wrapper">
+            <text class="greeting">{{ greetingText }}</text>
+          </view>
+          <!-- 日期和时间合并显示,确保分行 -->
+          <text class="timestamp">{{ formattedCurrentTime }}</text>
+        </view>
+
+        <!-- 签到进行中的新布局 (这部分保持不变) -->
+        <view v-if="checkinStore.session.isActive" class="checkin-active-view">
+          <view class="checkin-info-entry" @click="reEnterActiveSession">
+            <view v-if="checkinStore.session.method === 'number'" class="number-display">
+              <text class="entry-label">数字签到码</text>
+              <text class="entry-value">{{ checkinStore.session.code }}</text>
+            </view>
+            <view v-else class="qr-display">
+              <image src="/static/icon/qrcode.png" class="qr-icon"></image>
+              <text class="entry-label">查看二维码</text>
+            </view>
+            <text class="arrow">></text>
+          </view>
+          <view class="checkin-status">
+            <view class="status-item">
+              <text class="label">签到人数: {{ checkinStore.checkedInCount }}/{{ checkinStore.session.totalStudentCount }}</text>
+            </view>
+            <view class="status-item">
+              <text class="label">已签到 ({{ checkinStore.checkedInCount }})</text>
+              <view v-if="checkinStore.checkedInCount > 0" class="student-list">
+                 <text v-for="s in checkinStore.session.checkedInStudents" :key="s.id">{{ s.name }}</text>
+              </view>
+              <view v-else class="student-list-empty">
+                <text>暂无学生签到</text>
+              </view>
+            </view>
+          </view>
+          <button class="end-button" @click="endCheckinConfirm">结束签到</button>
+        </view>
+
+        <!-- 未开始签到 (这部分保持不变) -->
+        <view v-else class="info-list">
+          <view class="info-item" @click="navigateTo('/pages/checkin/teacher/select-class')">
+            <text class="label">签到班级</text>
+            <view class="value-wrapper"><text class="arrow">></text></view>
+          </view>
+          <view class="info-item" @click="navigateTo('/pages/checkin/teacher/select-time')">
+            <text class="label">签到时间</text>
+            <view class="value-wrapper"><text class="arrow">></text></view>
+          </view>
+          <view class="info-item" @click="navigateTo('/pages/checkin/teacher/select-method')">
+            <text class="label">签到方式</text>
+            <view class="value-wrapper"><text class="arrow">></text></view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from 'vue'
+import NavBar from '@/pages/components/NavBar/NavBar.vue'
+import { useCheckinStore } from '@/store/checkin'
+
+const navBarRef = ref(null)
+const navHeight = ref(88)
+const contentWrapperStyle = computed(() => ({ paddingTop: `${navHeight.value}px` }))
+onMounted(() => {
+  watch(() => navBarRef.value?.totalNavHeight, (val) => { if (val) navHeight.value = val }, { immediate: true })
+})
+
+const checkinStore = useCheckinStore()
+const greetingText = computed(() => checkinStore.session.isActive ? '签到进行中...' : '准备开始上课啦~')
+
+// --- 恢复为单个时间戳计算属性 ---
+const formattedCurrentTime = computed(() => {
+  const now = new Date();
+  const year = now.getFullYear();
+  const month = now.getMonth() + 1;
+  const day = now.getDate();
+  const hours = String(now.getHours()).padStart(2, '0');
+  const minutes = String(now.getMinutes()).padStart(2, '0');
+  return `${year}年${month}月${day}日  ${hours}:${minutes}`;
+});
+// ---
+
+const reEnterActiveSession = () => {
+  if (!checkinStore.session.isActive) return;
+  if (checkinStore.session.method === 'qr') {
+    uni.navigateTo({ url: '/pages/checkin/teacher/qr-active' });
+  } else {
+    uni.showToast({ title: '数字码已在此页面显示', icon: 'none' });
+  }
+}
+
+const navigateTo = (url) => {
+  if (url.includes('select-class') || url.includes('select-time')) {
+    uni.showToast({ title: '此功能暂未开放', icon: 'none' })
+    return
+  }
+  uni.navigateTo({ url })
+}
+
+const endCheckinConfirm = () => {
+  uni.showModal({
+    title: '确认',
+    content: '确定要结束本次签到吗?',
+    success: (res) => { if (res.confirm) checkinStore.endCheckin() }
+  })
+}
+</script>
+
+<style scoped>
+.teacher-container { min-height: 100vh; background-color: #f5f5f7; }
+.content-wrapper { padding: 20rpx 30rpx; box-sizing: border-box; }
+.teacher-card { width: 100%; background-color: #fff; border-radius: 20rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); padding: 40rpx; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; border-top: 8rpx solid #7DB4D6; }
+
+/* --- 样式修改的核心区域 --- */
+.header-content {
+  width: 100%;
+  display: flex; /* 使用 Flexbox 布局 */
+  flex-direction: column; /* 垂直排列,确保每个元素占一行 */
+  align-items: center; /* 所有元素水平居中 */
+  gap: 16rpx; /* 设置元素之间的垂直间距 */
+  margin-bottom: 20rpx; /* 与下方内容区域的间距 */
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.greeting-wrapper {
+  /* margin 将由 flex gap 控制,无需单独设置 */
+  display: inline-flex; /* 保持标签包裹性 */
+  padding: 8rpx 20rpx;
+  border-radius: 30rpx;
+  background-color: #EBF5FA;
+}
+.greeting-wrapper .greeting {
+  color: #7DB4D6;
+  font-size: 26rpx;
+  font-weight: 500;
+}
+
+.timestamp {
+  /* margin 将由 flex gap 控制,无需单独设置 */
+  font-size: 28rpx;
+  color: #999;
+}
+/* --- 样式修改结束 --- */
+
+.label { font-size: 30rpx; color: #333; }
+.arrow { font-size: 36rpx; color: #cccccc; font-weight: bold; }
+.info-list { width: 100%; margin-top: 40rpx; }
+.info-item { padding: 35rpx 20rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #f0f0f0; }
+.info-item:last-child { border-bottom: none; }
+.checkin-active-view { width: 100%; margin-top: 40rpx; display: flex; flex-direction: column; }
+.checkin-info-entry { background-color: #f0f8ff; border: 1px solid #d6e8f5; border-radius: 16rpx; padding: 30rpx; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: background-color 0.2s; }
+.checkin-info-entry:active { background-color: #e6f2fa; }
+.entry-label { color: #555; font-size: 28rpx; }
+.entry-value { color: #007aff; font-size: 48rpx; font-weight: bold; letter-spacing: 8rpx; margin-top: 10rpx; display: block; }
+.number-display, .qr-display { flex: 1; }
+.qr-display { display: flex; align-items: center; gap: 20rpx; }
+.qr-icon { width: 60rpx; height: 60rpx; }
+.qr-display .entry-label { font-size: 34rpx; font-weight: 500; color: #333; }
+.checkin-status { width: 100%; margin-top: 40rpx; text-align: left; padding-top: 30rpx; border-top: 1rpx solid #f0f0f0; }
+.status-item { margin-bottom: 30rpx; }
+.student-list, .student-list-empty { max-height: 200rpx; overflow-y: auto; background-color: #f9f9f9; padding: 20rpx; border-radius: 8rpx; margin-top: 10rpx; display: flex; flex-wrap: wrap; gap: 15rpx; }
+.student-list-empty { justify-content: center; align-items: center; color: #999; font-size: 26rpx; }
+.student-list text { background: #e9e9e9; padding: 8rpx 12rpx; border-radius: 6rpx; font-size: 26rpx; color: #555; }
+.end-button { background-color: #e54d42; color: #fff; margin-top: 20rpx; width: 100%; height: 80rpx; line-height: 80rpx; font-size: 30rpx; }
+</style>

+ 66 - 0
pages/checkin/teacher/number-setup.vue

@@ -0,0 +1,66 @@
+<template>
+  <view class="teacher-container">
+    <NavBar ref="navBarRef" title="数字签到设置" :showBack="true"/>
+    <view class="content-wrapper" :style="contentWrapperStyle">
+      <view class="teacher-card">
+        <view class="header-content"><text class="title">数字签到</text></view>
+        <view class="code-input-area">
+          <text class="area-label">指定4位签到数字</text>
+          <view class="code-inputs">
+            <input v-for="(item, index) in numberCode" :key="index" v-model="numberCode[index]" type="number" maxlength="1"/>
+          </view>
+          <button class="random-button" @click="generateRandomCode">随机生成</button>
+        </view>
+        <button class="start-button" :disabled="!isCodeValid" @click="startNumberCheckin">开始签到</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from 'vue'
+import NavBar from '@/pages/components/NavBar/NavBar.vue'
+import { useCheckinStore } from '@/store/checkin'
+
+const navBarRef = ref(null)
+const navHeight = ref(88)
+const contentWrapperStyle = computed(() => ({ paddingTop: `${navHeight.value}px` }))
+onMounted(() => {
+  watch(() => navBarRef.value?.totalNavHeight, (val) => { if (val) navHeight.value = val }, { immediate: true })
+})
+
+const checkinStore = useCheckinStore()
+const numberCode = ref(['', '', '', ''])
+const isCodeValid = computed(() => numberCode.value.every(c => c !== '' && !isNaN(c)))
+
+const generateRandomCode = () => {
+  let code = ''
+  for (let i = 0; i < 4; i++) {
+    code += Math.floor(Math.random() * 10)
+  }
+  numberCode.value = code.split('')
+}
+
+const startNumberCheckin = () => {
+  if (!isCodeValid.value) return;
+  const code = numberCode.value.join('')
+  checkinStore.startCheckin({ method: 'number', code })
+  uni.reLaunch({ url: '/pages/checkin/teacher/index' })
+}
+</script>
+
+<style scoped>
+.teacher-container { min-height: 100vh; background-color: #f5f5f7; }
+.content-wrapper { padding: 20rpx 30rpx; box-sizing: border-box; }
+.teacher-card { width: 100%; background-color: #fff; border-radius: 20rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); padding: 40rpx; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; border-top: 8rpx solid #7DB4D6; }
+.header-content { text-align: center; width: 100%; }
+.title { font-size: 48rpx; font-weight: 600; }
+.code-input-area { margin-top: 80rpx; text-align: center; }
+.area-label { font-size: 28rpx; color: #999; }
+.code-inputs { display: flex; gap: 20rpx; margin-top: 30rpx; justify-content: center; }
+.code-inputs input { width: 100rpx; height: 120rpx; border: 1px solid #ccc; border-radius: 12rpx; text-align: center; font-size: 48rpx; font-weight: bold; }
+.random-button { background-color: transparent; color: #7DB4D6; border: none; margin-top: 40rpx; font-size: 28rpx; padding: 0; line-height: 1.5; }
+.random-button::after { border: none; }
+.start-button { width: 100%; height: 90rpx; line-height: 90rpx; background-color: #7DB4D6; color: #fff; border-radius: 45rpx; margin-top: 80rpx; font-size: 32rpx; }
+.start-button[disabled] { background-color: #ccc; }
+</style>

+ 166 - 0
pages/checkin/teacher/qr-active.vue

@@ -0,0 +1,166 @@
+<template>
+  <view class="teacher-container">
+    <NavBar ref="navBarRef" title="二维码签到" :showBack="true"/>
+    <view class="content-wrapper" :style="contentWrapperStyle">
+      <view class="teacher-card">
+        <view class="header-content">
+          <text class="title">二维码签到</text>
+        </view>
+        <view class="qr-code-area">
+          <!-- 
+            使用 tki-qrcode 组件, 这是一个专门为 uni-app 打造的组件,https://ext.dcloud.net.cn/plugin?id=39
+			太不容易了,看到这个插件能生效都要哭了。
+			之前试了uqrcodejs,怎么都不行
+			weapp-qrcode-vue3,在小红书公司私有npm库中没有
+			vue-qr, 为web设计不兼容小程序这种没有window/document挂载的情况
+            - ref="qrcodeRef":  用来在脚本中获取组件实例,以备不时之需。
+            - cid="qrcode-canvas": 给内部的 canvas 一个唯一的ID,防止冲突。
+            - :val="qrVal":     【核心】将二维码的内容绑定到 qrVal 这个响应式变量上。
+            - :onval="true":    【核心】当 val 的值变化时,自动重新生成二维码。这让更新变得非常简单。
+            - :loadMake="true": 页面加载时,如果 val 有值,就自动生成。
+            - :size="400":      二维码的尺寸。
+            - unit="rpx":       尺寸的单位是 rpx,符合 uni-app 开发习惯。
+           -->
+          <tki-qrcode
+            v-if="qrVal"
+            ref="qrcodeRef"
+            cid="qrcode-canvas"
+            :val="qrVal"
+            :size="180"
+            unit="rpx"
+            :loadMake="true"
+            :onval="true"
+            background="#ffffff"
+            foreground="#000000"
+          />
+          <text class="tip">让学生扫描此码进行签到</text>
+        </view>
+        <view class="status-preview">
+            <text>已签到: {{ checkinStore.checkedInCount }}/{{ checkinStore.session.totalStudentCount }}</text>
+        </view>
+        <button class="update-button" @click="regenerateQrCode">更新二维码</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from 'vue'
+import NavBar from '@/pages/components/NavBar/NavBar.vue'
+import { useCheckinStore } from '@/store/checkin'
+import { onReady } from '@dcloudio/uni-app'
+// 导入 tki-qrcode 组件。
+// 注意:请根据您项目中插件的实际存放路径调整此行代码。
+// 如果您的 HBuilderX 使用了 uni_modules 模式,可能无需手动导入,可以先注释掉这行试试。
+import TkiQrcode from '@/components/tki-qrcode/tki-qrcode.vue' 
+
+// --- NavBar 相关的代码保持不变 ---
+const navBarRef = ref(null)
+const navHeight = ref(88)
+const contentWrapperStyle = computed(() => ({ paddingTop: `${navHeight.value}px` }))
+onMounted(() => {
+  watch(() => navBarRef.value?.totalNavHeight, (val) => { 
+    if (val) navHeight.value = val 
+  }, { immediate: true })
+})
+// ---
+
+const checkinStore = useCheckinStore()
+
+// 创建一个 ref 来持有二维码组件的实例 (可选,因为 onval=true 已经实现了自动更新)
+const qrcodeRef = ref(null)
+// 创建一个 ref 来绑定二维码的内容,这是与组件通信的关键
+const qrVal = ref('')
+
+// 更新二维码的函数
+const regenerateQrCode = () => {
+  const newCode = `checkin_qr_${Date.now()}`
+  checkinStore.regenerateCode(newCode)
+  
+  // 核心逻辑:只需要更新这个值,组件就会因为 :onval="true" 而自动重绘
+  qrVal.value = newCode
+  
+  uni.showToast({ title: '二维码已更新', icon: 'success' })
+}
+
+// 页面加载完成后设置初始的二维码内容
+onReady(() => {
+  if (checkinStore.session.isActive && checkinStore.session.code) {
+    // 设置初始值,组件会因为 :loadMake="true" 自动生成
+    qrVal.value = checkinStore.session.code
+  } else {
+    uni.showToast({ title: '签到已结束', icon: 'none', duration: 1500, mask: true });
+    setTimeout(() => uni.navigateBack(), 1500);
+  }
+})
+</script>
+
+<style scoped>
+.teacher-container { 
+  min-height: 100vh; 
+  background-color: #f5f5f7; 
+}
+.content-wrapper { 
+  padding: 20rpx 30rpx; 
+  box-sizing: border-box; 
+}
+.teacher-card { 
+  width: 100%; 
+  background-color: #fff; 
+  border-radius: 20rpx; 
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); 
+  padding: 40rpx; 
+  box-sizing: border-box; 
+  display: flex; 
+  flex-direction: column; 
+  align-items: center; 
+  border-top: 8rpx solid #7DB4D6; 
+}
+.header-content { 
+  text-align: center; 
+  width: 100%; 
+}
+.title { 
+  font-size: 48rpx; 
+  font-weight: 600; 
+}
+.qr-code-area { 
+  margin-top: 60rpx; 
+  text-align: center;
+  /* 为 tki-qrcode 组件预留空间 */
+  width: 400rpx;
+  height: 400rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+.qr-code-area .tip { 
+  display: block; 
+  margin-top: 20rpx; 
+  color: #999; 
+  font-size: 26rpx; 
+}
+.status-preview { 
+  margin-top: 40rpx; 
+  background: #f5f5f7; 
+  padding: 15rpx 30rpx; 
+  border-radius: 30rpx; 
+  color: #666; 
+  font-size: 28rpx; 
+}
+.update-button { 
+  width: 100%; 
+  height: 90rpx; 
+  line-height: 90rpx; 
+  background-color: #fff; 
+  color: #7DB4D6; 
+  border: 1px solid #7DB4D6; 
+  border-radius: 45rpx; 
+  margin-top: 60rpx; 
+  font-size: 32rpx; 
+}
+.update-button:active { 
+  background-color: #f0f8ff; 
+}
+</style>

+ 117 - 0
pages/checkin/teacher/select-method.vue

@@ -0,0 +1,117 @@
+<style scoped>
+/* 定义新的淡蓝色主题色变量 */
+:root {
+  --theme-color: #A8D8F0; /* 【核心修改】新的、更淡的主题色 */
+  --theme-color-dark: #97c2d9; /* 新的点击加深色 */
+  --theme-color-shadow: rgba(168, 216, 240, 0.45); /* 新的阴影颜色 */
+  --theme-text-color: #3d708a; /* 【新增】在淡色背景上使用的深色文字,保证可读性 */
+  --card-border-color: #7DB4D6; /* 卡片顶部边框依然使用原始的深色,形成对比 */
+}
+
+.teacher-container {
+  min-height: 100vh;
+  background-color: #f5f5f7;
+}
+
+.content-wrapper {
+  padding: 20rpx 30rpx;
+  box-sizing: border-box;
+}
+
+.teacher-card {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  padding: 60rpx 40rpx;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  border-top: 8rpx solid var(--card-border-color); /* 顶部边框使用原始深色,保持视觉重点 */
+}
+
+.header-content {
+  text-align: center;
+  width: 100%;
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: 600;
+  color: #333;
+}
+.subtitle {
+  display: block;
+  font-size: 28rpx;
+  color: #999;
+  margin-top: 16rpx;
+}
+
+.method-selection {
+  display: flex;
+  flex-direction: column;
+  gap: 40rpx;
+  width: 100%;
+  margin-top: 80rpx;
+}
+
+/* --- 按钮美化核心区域 --- */
+.method-button {
+  display: flex;
+  align-items: center;
+  gap: 30rpx;
+  
+  width: 100%;
+  padding: 40rpx;
+  border-radius: 24rpx;
+  text-align: left;
+  line-height: 1.5;
+  
+  /* 【核心修改】使用新的淡蓝色作为背景 */
+  background-color: var(--theme-color);
+  border: none;
+  
+  box-shadow: 0 8rpx 20rpx -5rpx var(--theme-color-shadow);
+  transition: all 0.2s ease-in-out;
+}
+
+/* 移除 uni-app 按钮默认样式 */
+.method-button::after {
+  border: none;
+}
+
+/* 按钮点击时的动态效果 */
+.method-button:active {
+  background-color: var(--theme-color-dark); /* 背景色变深 */
+  transform: scale(0.98);
+  box-shadow: 0 4rpx 10rpx -3rpx var(--theme-color-shadow);
+}
+
+.button-icon {
+  font-size: 52rpx;
+  /* 【核心修改】图标颜色改为深色,以在浅色背景上突出 */
+  color: var(--theme-text-color);
+}
+
+.button-text-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.method-title {
+  font-size: 36rpx;
+  font-weight: 600;
+  /* 【核心修改】文字颜色改为深色,保证可读性 */
+  color: var(--theme-text-color);
+  display: block;
+}
+
+.method-desc {
+  font-size: 26rpx;
+  /* 【核心修改】描述文字颜色改为略浅的深色 */
+  color: #6a95ac;
+  display: block;
+  margin-top: 8rpx;
+}
+</style>

+ 1 - 1
pages/profile/index.vue

@@ -340,7 +340,7 @@ function onBirthdayChange(e) {
 }
 
 // 身份
-const roleList = ['学生', '教师', '管理员', '职工']
+const roleList = ['学生', '教师']
 const roleIndex = ref(0)
 onMounted(() => {
   setRolePickerIndex()

+ 110 - 0
store/checkin.ts

@@ -0,0 +1,110 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+// 定义签到会话的数据结构
+interface ICheckinSession {
+  isActive: boolean; // 是否有签到正在进行
+  method: 'none' | 'number' | 'qr'; // 当前签到方式
+  code: string; // 4位数字码或二维码的唯一标识
+  classes: string[]; // 签到班级
+  startTime: number | null; // 开始时间戳
+  endTime: number | null; // 结束时间戳
+  checkedInStudents: { id: number; name: string; time: number }[]; // 已签到学生列表
+  totalStudentCount: number; // 签到总人数
+}
+
+// 默认状态
+const defaultSession: ICheckinSession = {
+  isActive: false,
+  method: 'none',
+  code: '',
+  classes: [],
+  startTime: null,
+  endTime: null,
+  checkedInStudents: [],
+  totalStudentCount: 48 // 模拟一个班级总人数
+}
+
+export const useCheckinStore = defineStore('checkin', () => {
+  // 签到会话的实时状态
+  const session = ref<ICheckinSession>({ ...defaultSession })
+
+  // --- 计算属性 ---
+  // 已签到人数
+  const checkedInCount = computed(() => session.value.checkedInStudents.length)
+  // 检查某个学生是否已签到
+  const isStudentCheckedIn = (studentId: number) => {
+    return session.value.checkedInStudents.some(s => s.id === studentId)
+  }
+
+  // --- 教师端 Actions ---
+  /**
+   * 教师发起一个签到
+   * @param config - 签到配置
+   */
+  const startCheckin = (config: { method: 'number' | 'qr'; code: string; }) => {
+    session.value.isActive = true
+    session.value.method = config.method
+    session.value.code = config.code
+    session.value.startTime = Date.now()
+    // 可以在这里设置一个默认的结束时间,比如5分钟后
+    session.value.endTime = Date.now() + 5 * 60 * 1000 
+    session.value.checkedInStudents = [] // 清空上次签到的人员
+  }
+
+  /**
+   * 教师更新签到码
+   * @param newCode - 新的码
+   */
+  const regenerateCode = (newCode: string) => {
+    if (session.value.isActive) {
+      session.value.code = newCode
+    }
+  }
+
+  /**
+   * 教师结束签到
+   */
+  const endCheckin = () => {
+    session.value = { ...defaultSession }
+  }
+
+
+  // --- 学生端 Actions ---
+  /**
+   * 学生尝试签到
+   * @param student - 学生信息
+   * @param inputCode - 学生输入的数字码 (可选)
+   * @returns { success: boolean, message: string }
+   */
+  const attemptCheckin = (student: { id: number, name: string }, inputCode?: string) => {
+    if (!session.value.isActive) {
+      return { success: false, message: '签到尚未开始' }
+    }
+    if (isStudentCheckedIn(student.id)) {
+      return { success: false, message: '您已签到,请勿重复操作' }
+    }
+    // 如果是数字签到,验证数字码
+    if (session.value.method === 'number' && inputCode !== session.value.code) {
+      return { success: false, message: '签到码错误' }
+    }
+
+    // 签到成功
+    session.value.checkedInStudents.push({ ...student, time: Date.now() })
+    return { success: true, message: '签到成功!' }
+  }
+
+
+  return {
+    session,
+    checkedInCount,
+    isStudentCheckedIn,
+    startCheckin,
+    regenerateCode,
+    endCheckin,
+    attemptCheckin,
+  }
+}, {
+  // 推荐:为了让签到状态在小程序关闭后也能保留(模拟真实场景),可以开启持久化
+  persist: true
+})

+ 1 - 1
unpackage/dist/dev/.sourcemap/mp-weixin/app.js.map

@@ -1 +1 @@
-{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\n","import App from './App'\n\n// #ifndef VUE3\nimport Vue from 'vue'\nimport './uni.promisify.adaptor'\nVue.config.productionTip = false\nApp.mpType = 'app'\nconst app = new Vue({\n  ...App\n})\napp.$mount()\n// #endif\n\n// #ifdef VUE3\nimport { createSSRApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport { createPersistedState } from 'pinia-plugin-persistedstate'\n\nexport function createApp() {\n  const app = createSSRApp(App)\n  \n  // 创建pinia实例\n  const pinia = createPinia()\n  \n  // 配置持久化插件\n  pinia.use(createPersistedState({\n    storage: {\n      getItem(key) {\n        return uni.getStorageSync(key)\n      },\n      setItem(key, value) {\n        uni.setStorageSync(key, value)\n      }\n    }\n  }))\n  \n  app.use(pinia)\n  \n  return {\n    app\n  }\n}\n// #endif\n\n"],"names":["uni","createSSRApp","App","createPinia","createPersistedState"],"mappings":";;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACOM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAG5B,QAAM,QAAQC,cAAAA,YAAa;AAG3B,QAAM,IAAIC,mCAAqB;AAAA,IAC7B,SAAS;AAAA,MACP,QAAQ,KAAK;AACX,eAAOJ,cAAG,MAAC,eAAe,GAAG;AAAA,MAC9B;AAAA,MACD,QAAQ,KAAK,OAAO;AAClBA,4BAAI,eAAe,KAAK,KAAK;AAAA,MAC9B;AAAA,IACF;AAAA,EACL,CAAG,CAAC;AAEF,MAAI,IAAI,KAAK;AAEb,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}
+{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\n","import App from './App'\n\n// #ifndef VUE3\nimport Vue from 'vue'\nimport './uni.promisify.adaptor'\nVue.config.productionTip = false\nApp.mpType = 'app'\nconst app = new Vue({\n  ...App\n})\napp.$mount()\n// #endif\n\n// #ifdef VUE3\nimport { createSSRApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport { createPersistedState } from 'pinia-plugin-persistedstate'\n\nexport function createApp() {\n  const app = createSSRApp(App)\n  \n  // 创建pinia实例\n  const pinia = createPinia()\n  \n  // 配置持久化插件\n  pinia.use(createPersistedState({\n    storage: {\n      getItem(key) {\n        return uni.getStorageSync(key)\n      },\n      setItem(key, value) {\n        uni.setStorageSync(key, value)\n      }\n    }\n  }))\n  \n  app.use(pinia)\n  \n  return {\n    app\n  }\n}\n// #endif\n\n"],"names":["uni","createSSRApp","App","createPinia","createPersistedState"],"mappings":";;;;;;;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACOM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAG5B,QAAM,QAAQC,cAAAA,YAAa;AAG3B,QAAM,IAAIC,mCAAqB;AAAA,IAC7B,SAAS;AAAA,MACP,QAAQ,KAAK;AACX,eAAOJ,cAAG,MAAC,eAAe,GAAG;AAAA,MAC9B;AAAA,MACD,QAAQ,KAAK,OAAO;AAClBA,4BAAI,eAAe,KAAK,KAAK;AAAA,MAC9B;AAAA,IACF;AAAA,EACL,CAAG,CAAC;AAEF,MAAI,IAAI,KAAK;AAEb,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}

+ 1 - 1
unpackage/dist/dev/.sourcemap/mp-weixin/common/assets.js.map

@@ -1 +1 @@
-{"version":3,"file":"assets.js","sources":["static/seecoderLogo.png"],"sourcesContent":["export default \"__VITE_ASSET__316116ea__\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}
+{"version":3,"file":"assets.js","sources":["static/seecoderLogo.png","../../../../../static/icon/qrcode.png"],"sourcesContent":["export default \"__VITE_ASSET__316116ea__\"","export default \"/static/icon/qrcode.png\""],"names":[],"mappings":";AAAA,MAAe,eAAA;ACAf,MAAe,aAAA;;;"}

File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/common/vendor.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/components/tki-qrcode/qrcode.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/components/tki-qrcode/tki-qrcode.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/index.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/index.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/number-setup.js.map


+ 1 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/number-setup/number-setup.js.map

@@ -0,0 +1 @@
+{"version":3,"file":"number-setup.js","sources":["pages/checkin/teacher/number-setup/number-setup.vue","pages/checkin/teacher/number-setup/number-setup.vue?type=page"],"sourcesContent":["<template>\n\t<view>\n\t\t\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t\n\t\t}\n\t}\n</script>\n\n<style>\n\n</style>\n","import MiniProgramPage from '/Users/xiaoyu10/Documents/HBuilderProjects/EAIminiapp/pages/checkin/teacher/number-setup/number-setup.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;AAOC,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,CAEP;AAAA,EACA;AAAA,EACD,SAAS,CAET;AACD;;;;;ACfD,GAAG,WAAW,eAAe;"}

File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/qr-active.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/checkin/teacher/select-method.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/components/About/AboutModal/AboutModal.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/pages/profile/index.js.map


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/.sourcemap/mp-weixin/store/checkin.js.map


+ 5 - 0
unpackage/dist/dev/mp-weixin/app.js

@@ -6,6 +6,11 @@ if (!Math) {
   "./pages/login/index.js";
   "./pages/index/index.js";
   "./pages/profile/index.js";
+  "./pages/checkin/index.js";
+  "./pages/checkin/teacher/index.js";
+  "./pages/checkin/teacher/select-method.js";
+  "./pages/checkin/teacher/number-setup.js";
+  "./pages/checkin/teacher/qr-active.js";
 }
 const _sfc_main = {
   onLaunch: function() {

+ 7 - 1
unpackage/dist/dev/mp-weixin/app.json

@@ -3,7 +3,13 @@
     "pages/welcome/index",
     "pages/login/index",
     "pages/index/index",
-    "pages/profile/index"
+    "pages/profile/index",
+    "pages/checkin/index",
+    "pages/checkin/teacher/index",
+    "pages/checkin/teacher/select-method",
+    "pages/checkin/teacher/number-setup/number-setup",
+    "pages/checkin/teacher/number-setup",
+    "pages/checkin/teacher/qr-active"
   ],
   "window": {
     "navigationBarTextStyle": "black",

+ 4 - 2
unpackage/dist/dev/mp-weixin/common/assets.js

@@ -1,4 +1,6 @@
 "use strict";
-const _imports_0 = "/static/seecoderLogo.png";
-exports._imports_0 = _imports_0;
+const _imports_0$1 = "/static/seecoderLogo.png";
+const _imports_0 = "/static/icon/qrcode.png";
+exports._imports_0 = _imports_0$1;
+exports._imports_0$1 = _imports_0;
 //# sourceMappingURL=../../.sourcemap/mp-weixin/common/assets.js.map

+ 89 - 11
unpackage/dist/dev/mp-weixin/common/vendor.js

@@ -2655,21 +2655,21 @@ function injectHook(type, hook, target = currentInstance, prepend = false) {
     );
   }
 }
-const createHook = (lifecycle) => (hook, target = currentInstance) => (
+const createHook$1 = (lifecycle) => (hook, target = currentInstance) => (
   // post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
   (!isInSSRComponentSetup || lifecycle === "sp") && injectHook(lifecycle, (...args) => hook(...args), target)
 );
-const onBeforeMount = createHook("bm");
-const onMounted = createHook("m");
-const onBeforeUpdate = createHook("bu");
-const onUpdated = createHook("u");
-const onBeforeUnmount = createHook("bum");
-const onUnmounted = createHook("um");
-const onServerPrefetch = createHook("sp");
-const onRenderTriggered = createHook(
+const onBeforeMount = createHook$1("bm");
+const onMounted = createHook$1("m");
+const onBeforeUpdate = createHook$1("bu");
+const onUpdated = createHook$1("u");
+const onBeforeUnmount = createHook$1("bum");
+const onUnmounted = createHook$1("um");
+const onServerPrefetch = createHook$1("sp");
+const onRenderTriggered = createHook$1(
   "rtg"
 );
-const onRenderTracked = createHook(
+const onRenderTracked = createHook$1(
   "rtc"
 );
 function onErrorCaptured(hook, target = currentInstance) {
@@ -5188,11 +5188,44 @@ function patchStopImmediatePropagation(e2, value) {
     return value;
   }
 }
+function vFor(source, renderItem) {
+  let ret;
+  if (isArray(source) || isString(source)) {
+    ret = new Array(source.length);
+    for (let i2 = 0, l2 = source.length; i2 < l2; i2++) {
+      ret[i2] = renderItem(source[i2], i2, i2);
+    }
+  } else if (typeof source === "number") {
+    if (!Number.isInteger(source)) {
+      warn(`The v-for range expect an integer value but got ${source}.`);
+      return [];
+    }
+    ret = new Array(source);
+    for (let i2 = 0; i2 < source; i2++) {
+      ret[i2] = renderItem(i2 + 1, i2, i2);
+    }
+  } else if (isObject(source)) {
+    if (source[Symbol.iterator]) {
+      ret = Array.from(source, (item, i2) => renderItem(item, i2, i2));
+    } else {
+      const keys = Object.keys(source);
+      ret = new Array(keys.length);
+      for (let i2 = 0, l2 = keys.length; i2 < l2; i2++) {
+        const key = keys[i2];
+        ret[i2] = renderItem(source[key], key, i2);
+      }
+    }
+  } else {
+    ret = [];
+  }
+  return ret;
+}
 function setRef(ref2, id, opts = {}) {
   const { $templateRefs } = getCurrentInstance();
   $templateRefs.push({ i: id, r: ref2, k: opts.k, f: opts.f });
 }
 const o$1 = (value, key) => vOn(value, key);
+const f$1 = (source, renderItem) => vFor(source, renderItem);
 const s$1 = (value) => stringifyStyle(value);
 const e$1 = (target, ...sources) => extend(target, ...sources);
 const t$1 = (val) => toDisplayString(val);
@@ -7019,7 +7052,7 @@ function isConsoleWritable() {
 function initRuntimeSocketService() {
   const hosts = "127.0.0.1,10.23.157.59,10.251.1.1";
   const port = "8090";
-  const id = "mp-weixin_ZDvxU5";
+  const id = "mp-weixin_fpiSe9";
   const lazy = typeof swan !== "undefined";
   let restoreError = lazy ? () => {
   } : initOnError();
@@ -8812,6 +8845,43 @@ const pages = [
       navigationBarTitleText: "个人中心",
       navigationStyle: "custom"
     }
+  },
+  {
+    path: "pages/checkin/index",
+    style: {
+      navigationBarTitleText: "课堂签到",
+      navigationStyle: "custom"
+    }
+  },
+  {
+    path: "pages/checkin/teacher/index",
+    style: {
+      navigationBarTitleText: ""
+    }
+  },
+  {
+    path: "pages/checkin/teacher/select-method",
+    style: {
+      navigationBarTitleText: ""
+    }
+  },
+  {
+    path: "pages/checkin/teacher/number-setup/number-setup",
+    style: {
+      navigationBarTitleText: ""
+    }
+  },
+  {
+    path: "pages/checkin/teacher/number-setup",
+    style: {
+      navigationBarTitleText: ""
+    }
+  },
+  {
+    path: "pages/checkin/teacher/qr-active",
+    style: {
+      navigationBarTitleText: ""
+    }
   }
 ];
 const globalStyle = {
@@ -11666,6 +11736,11 @@ let tr = new class {
     ;
 })();
 var nr = tr;
+const createHook = (lifecycle) => (hook, target = getCurrentInstance()) => {
+  !isInSSRComponentSetup && injectHook(lifecycle, hook, target);
+};
+const onShow = /* @__PURE__ */ createHook(ON_SHOW);
+const onReady = /* @__PURE__ */ createHook(ON_READY);
 exports._export_sfc = _export_sfc;
 exports.computed = computed;
 exports.createPersistedState = createPersistedState;
@@ -11673,11 +11748,14 @@ exports.createPinia = createPinia;
 exports.createSSRApp = createSSRApp;
 exports.defineStore = defineStore;
 exports.e = e$1;
+exports.f = f$1;
 exports.index = index;
 exports.nextTick$1 = nextTick$1;
 exports.nr = nr;
 exports.o = o$1;
 exports.onMounted = onMounted;
+exports.onReady = onReady;
+exports.onShow = onShow;
 exports.p = p$1;
 exports.ref = ref;
 exports.s = s$1;

+ 1049 - 0
unpackage/dist/dev/mp-weixin/components/tki-qrcode/qrcode.js

@@ -0,0 +1,1049 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+let QRCode = {};
+(function() {
+  function unicodeFormat8(code) {
+    var c0, c1, c2;
+    if (code < 128) {
+      return [code];
+    } else if (code < 2048) {
+      c0 = 192 + (code >> 6);
+      c1 = 128 + (code & 63);
+      return [c0, c1];
+    } else {
+      c0 = 224 + (code >> 12);
+      c1 = 128 + (code >> 6 & 63);
+      c2 = 128 + (code & 63);
+      return [c0, c1, c2];
+    }
+  }
+  function getUTF8Bytes(string) {
+    var utf8codes = [];
+    for (var i2 = 0; i2 < string.length; i2++) {
+      var code = string.charCodeAt(i2);
+      var utf8 = unicodeFormat8(code);
+      for (var j = 0; j < utf8.length; j++) {
+        utf8codes.push(utf8[j]);
+      }
+    }
+    return utf8codes;
+  }
+  function QRCodeAlg(data, errorCorrectLevel) {
+    this.typeNumber = -1;
+    this.errorCorrectLevel = errorCorrectLevel;
+    this.modules = null;
+    this.moduleCount = 0;
+    this.dataCache = null;
+    this.rsBlocks = null;
+    this.totalDataCount = -1;
+    this.data = data;
+    this.utf8bytes = getUTF8Bytes(data);
+    this.make();
+  }
+  QRCodeAlg.prototype = {
+    constructor: QRCodeAlg,
+    /**
+     * 获取二维码矩阵大小
+     * @return {num} 矩阵大小
+     */
+    getModuleCount: function() {
+      return this.moduleCount;
+    },
+    /**
+     * 编码
+     */
+    make: function() {
+      this.getRightType();
+      this.dataCache = this.createData();
+      this.createQrcode();
+    },
+    /**
+     * 设置二位矩阵功能图形
+     * @param  {bool} test 表示是否在寻找最好掩膜阶段
+     * @param  {num} maskPattern 掩膜的版本
+     */
+    makeImpl: function(maskPattern) {
+      this.moduleCount = this.typeNumber * 4 + 17;
+      this.modules = new Array(this.moduleCount);
+      for (var row = 0; row < this.moduleCount; row++) {
+        this.modules[row] = new Array(this.moduleCount);
+      }
+      this.setupPositionProbePattern(0, 0);
+      this.setupPositionProbePattern(this.moduleCount - 7, 0);
+      this.setupPositionProbePattern(0, this.moduleCount - 7);
+      this.setupPositionAdjustPattern();
+      this.setupTimingPattern();
+      this.setupTypeInfo(true, maskPattern);
+      if (this.typeNumber >= 7) {
+        this.setupTypeNumber(true);
+      }
+      this.mapData(this.dataCache, maskPattern);
+    },
+    /**
+     * 设置二维码的位置探测图形
+     * @param  {num} row 探测图形的中心横坐标
+     * @param  {num} col 探测图形的中心纵坐标
+     */
+    setupPositionProbePattern: function(row, col) {
+      for (var r = -1; r <= 7; r++) {
+        if (row + r <= -1 || this.moduleCount <= row + r)
+          continue;
+        for (var c = -1; c <= 7; c++) {
+          if (col + c <= -1 || this.moduleCount <= col + c)
+            continue;
+          if (0 <= r && r <= 6 && (c == 0 || c == 6) || 0 <= c && c <= 6 && (r == 0 || r == 6) || 2 <= r && r <= 4 && 2 <= c && c <= 4) {
+            this.modules[row + r][col + c] = true;
+          } else {
+            this.modules[row + r][col + c] = false;
+          }
+        }
+      }
+    },
+    /**
+     * 创建二维码
+     * @return {[type]} [description]
+     */
+    createQrcode: function() {
+      var minLostPoint = 0;
+      var pattern = 0;
+      var bestModules = null;
+      for (var i2 = 0; i2 < 8; i2++) {
+        this.makeImpl(i2);
+        var lostPoint = QRUtil.getLostPoint(this);
+        if (i2 == 0 || minLostPoint > lostPoint) {
+          minLostPoint = lostPoint;
+          pattern = i2;
+          bestModules = this.modules;
+        }
+      }
+      this.modules = bestModules;
+      this.setupTypeInfo(false, pattern);
+      if (this.typeNumber >= 7) {
+        this.setupTypeNumber(false);
+      }
+    },
+    /**
+     * 设置定位图形
+     * @return {[type]} [description]
+     */
+    setupTimingPattern: function() {
+      for (var r = 8; r < this.moduleCount - 8; r++) {
+        if (this.modules[r][6] != null) {
+          continue;
+        }
+        this.modules[r][6] = r % 2 == 0;
+        if (this.modules[6][r] != null) {
+          continue;
+        }
+        this.modules[6][r] = r % 2 == 0;
+      }
+    },
+    /**
+     * 设置矫正图形
+     * @return {[type]} [description]
+     */
+    setupPositionAdjustPattern: function() {
+      var pos = QRUtil.getPatternPosition(this.typeNumber);
+      for (var i2 = 0; i2 < pos.length; i2++) {
+        for (var j = 0; j < pos.length; j++) {
+          var row = pos[i2];
+          var col = pos[j];
+          if (this.modules[row][col] != null) {
+            continue;
+          }
+          for (var r = -2; r <= 2; r++) {
+            for (var c = -2; c <= 2; c++) {
+              if (r == -2 || r == 2 || c == -2 || c == 2 || r == 0 && c == 0) {
+                this.modules[row + r][col + c] = true;
+              } else {
+                this.modules[row + r][col + c] = false;
+              }
+            }
+          }
+        }
+      }
+    },
+    /**
+     * 设置版本信息(7以上版本才有)
+     * @param  {bool} test 是否处于判断最佳掩膜阶段
+     * @return {[type]}      [description]
+     */
+    setupTypeNumber: function(test) {
+      var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+      for (var i2 = 0; i2 < 18; i2++) {
+        var mod = !test && (bits >> i2 & 1) == 1;
+        this.modules[Math.floor(i2 / 3)][i2 % 3 + this.moduleCount - 8 - 3] = mod;
+        this.modules[i2 % 3 + this.moduleCount - 8 - 3][Math.floor(i2 / 3)] = mod;
+      }
+    },
+    /**
+     * 设置格式信息(纠错等级和掩膜版本)
+     * @param  {bool} test
+     * @param  {num} maskPattern 掩膜版本
+     * @return {}
+     */
+    setupTypeInfo: function(test, maskPattern) {
+      var data = QRErrorCorrectLevel[this.errorCorrectLevel] << 3 | maskPattern;
+      var bits = QRUtil.getBCHTypeInfo(data);
+      for (var i2 = 0; i2 < 15; i2++) {
+        var mod = !test && (bits >> i2 & 1) == 1;
+        if (i2 < 6) {
+          this.modules[i2][8] = mod;
+        } else if (i2 < 8) {
+          this.modules[i2 + 1][8] = mod;
+        } else {
+          this.modules[this.moduleCount - 15 + i2][8] = mod;
+        }
+        var mod = !test && (bits >> i2 & 1) == 1;
+        if (i2 < 8) {
+          this.modules[8][this.moduleCount - i2 - 1] = mod;
+        } else if (i2 < 9) {
+          this.modules[8][15 - i2 - 1 + 1] = mod;
+        } else {
+          this.modules[8][15 - i2 - 1] = mod;
+        }
+      }
+      this.modules[this.moduleCount - 8][8] = !test;
+    },
+    /**
+     * 数据编码
+     * @return {[type]} [description]
+     */
+    createData: function() {
+      var buffer = new QRBitBuffer();
+      var lengthBits = this.typeNumber > 9 ? 16 : 8;
+      buffer.put(4, 4);
+      buffer.put(this.utf8bytes.length, lengthBits);
+      for (var i2 = 0, l = this.utf8bytes.length; i2 < l; i2++) {
+        buffer.put(this.utf8bytes[i2], 8);
+      }
+      if (buffer.length + 4 <= this.totalDataCount * 8) {
+        buffer.put(0, 4);
+      }
+      while (buffer.length % 8 != 0) {
+        buffer.putBit(false);
+      }
+      while (true) {
+        if (buffer.length >= this.totalDataCount * 8) {
+          break;
+        }
+        buffer.put(QRCodeAlg.PAD0, 8);
+        if (buffer.length >= this.totalDataCount * 8) {
+          break;
+        }
+        buffer.put(QRCodeAlg.PAD1, 8);
+      }
+      return this.createBytes(buffer);
+    },
+    /**
+     * 纠错码编码
+     * @param  {buffer} buffer 数据编码
+     * @return {[type]}
+     */
+    createBytes: function(buffer) {
+      var offset = 0;
+      var maxDcCount = 0;
+      var maxEcCount = 0;
+      var length = this.rsBlock.length / 3;
+      var rsBlocks = new Array();
+      for (var i2 = 0; i2 < length; i2++) {
+        var count = this.rsBlock[i2 * 3 + 0];
+        var totalCount = this.rsBlock[i2 * 3 + 1];
+        var dataCount = this.rsBlock[i2 * 3 + 2];
+        for (var j = 0; j < count; j++) {
+          rsBlocks.push([dataCount, totalCount]);
+        }
+      }
+      var dcdata = new Array(rsBlocks.length);
+      var ecdata = new Array(rsBlocks.length);
+      for (var r = 0; r < rsBlocks.length; r++) {
+        var dcCount = rsBlocks[r][0];
+        var ecCount = rsBlocks[r][1] - dcCount;
+        maxDcCount = Math.max(maxDcCount, dcCount);
+        maxEcCount = Math.max(maxEcCount, ecCount);
+        dcdata[r] = new Array(dcCount);
+        for (var i2 = 0; i2 < dcdata[r].length; i2++) {
+          dcdata[r][i2] = 255 & buffer.buffer[i2 + offset];
+        }
+        offset += dcCount;
+        var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+        var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+        var modPoly = rawPoly.mod(rsPoly);
+        ecdata[r] = new Array(rsPoly.getLength() - 1);
+        for (var i2 = 0; i2 < ecdata[r].length; i2++) {
+          var modIndex = i2 + modPoly.getLength() - ecdata[r].length;
+          ecdata[r][i2] = modIndex >= 0 ? modPoly.get(modIndex) : 0;
+        }
+      }
+      var data = new Array(this.totalDataCount);
+      var index = 0;
+      for (var i2 = 0; i2 < maxDcCount; i2++) {
+        for (var r = 0; r < rsBlocks.length; r++) {
+          if (i2 < dcdata[r].length) {
+            data[index++] = dcdata[r][i2];
+          }
+        }
+      }
+      for (var i2 = 0; i2 < maxEcCount; i2++) {
+        for (var r = 0; r < rsBlocks.length; r++) {
+          if (i2 < ecdata[r].length) {
+            data[index++] = ecdata[r][i2];
+          }
+        }
+      }
+      return data;
+    },
+    /**
+     * 布置模块,构建最终信息
+     * @param  {} data
+     * @param  {} maskPattern
+     * @return {}
+     */
+    mapData: function(data, maskPattern) {
+      var inc = -1;
+      var row = this.moduleCount - 1;
+      var bitIndex = 7;
+      var byteIndex = 0;
+      for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+        if (col == 6)
+          col--;
+        while (true) {
+          for (var c = 0; c < 2; c++) {
+            if (this.modules[row][col - c] == null) {
+              var dark = false;
+              if (byteIndex < data.length) {
+                dark = (data[byteIndex] >>> bitIndex & 1) == 1;
+              }
+              var mask = QRUtil.getMask(maskPattern, row, col - c);
+              if (mask) {
+                dark = !dark;
+              }
+              this.modules[row][col - c] = dark;
+              bitIndex--;
+              if (bitIndex == -1) {
+                byteIndex++;
+                bitIndex = 7;
+              }
+            }
+          }
+          row += inc;
+          if (row < 0 || this.moduleCount <= row) {
+            row -= inc;
+            inc = -inc;
+            break;
+          }
+        }
+      }
+    }
+  };
+  QRCodeAlg.PAD0 = 236;
+  QRCodeAlg.PAD1 = 17;
+  var QRErrorCorrectLevel = [1, 0, 3, 2];
+  var QRMaskPattern = {
+    PATTERN000: 0,
+    PATTERN001: 1,
+    PATTERN010: 2,
+    PATTERN011: 3,
+    PATTERN100: 4,
+    PATTERN101: 5,
+    PATTERN110: 6,
+    PATTERN111: 7
+  };
+  var QRUtil = {
+    /*
+    每个版本矫正图形的位置
+     */
+    PATTERN_POSITION_TABLE: [
+      [],
+      [6, 18],
+      [6, 22],
+      [6, 26],
+      [6, 30],
+      [6, 34],
+      [6, 22, 38],
+      [6, 24, 42],
+      [6, 26, 46],
+      [6, 28, 50],
+      [6, 30, 54],
+      [6, 32, 58],
+      [6, 34, 62],
+      [6, 26, 46, 66],
+      [6, 26, 48, 70],
+      [6, 26, 50, 74],
+      [6, 30, 54, 78],
+      [6, 30, 56, 82],
+      [6, 30, 58, 86],
+      [6, 34, 62, 90],
+      [6, 28, 50, 72, 94],
+      [6, 26, 50, 74, 98],
+      [6, 30, 54, 78, 102],
+      [6, 28, 54, 80, 106],
+      [6, 32, 58, 84, 110],
+      [6, 30, 58, 86, 114],
+      [6, 34, 62, 90, 118],
+      [6, 26, 50, 74, 98, 122],
+      [6, 30, 54, 78, 102, 126],
+      [6, 26, 52, 78, 104, 130],
+      [6, 30, 56, 82, 108, 134],
+      [6, 34, 60, 86, 112, 138],
+      [6, 30, 58, 86, 114, 142],
+      [6, 34, 62, 90, 118, 146],
+      [6, 30, 54, 78, 102, 126, 150],
+      [6, 24, 50, 76, 102, 128, 154],
+      [6, 28, 54, 80, 106, 132, 158],
+      [6, 32, 58, 84, 110, 136, 162],
+      [6, 26, 54, 82, 110, 138, 166],
+      [6, 30, 58, 86, 114, 142, 170]
+    ],
+    G15: 1 << 10 | 1 << 8 | 1 << 5 | 1 << 4 | 1 << 2 | 1 << 1 | 1 << 0,
+    G18: 1 << 12 | 1 << 11 | 1 << 10 | 1 << 9 | 1 << 8 | 1 << 5 | 1 << 2 | 1 << 0,
+    G15_MASK: 1 << 14 | 1 << 12 | 1 << 10 | 1 << 4 | 1 << 1,
+    /*
+    BCH编码格式信息
+     */
+    getBCHTypeInfo: function(data) {
+      var d = data << 10;
+      while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+        d ^= QRUtil.G15 << QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15);
+      }
+      return (data << 10 | d) ^ QRUtil.G15_MASK;
+    },
+    /*
+    BCH编码版本信息
+     */
+    getBCHTypeNumber: function(data) {
+      var d = data << 12;
+      while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+        d ^= QRUtil.G18 << QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18);
+      }
+      return data << 12 | d;
+    },
+    /*
+    获取BCH位信息
+     */
+    getBCHDigit: function(data) {
+      var digit = 0;
+      while (data != 0) {
+        digit++;
+        data >>>= 1;
+      }
+      return digit;
+    },
+    /*
+    获取版本对应的矫正图形位置
+     */
+    getPatternPosition: function(typeNumber) {
+      return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+    },
+    /*
+    掩膜算法
+     */
+    getMask: function(maskPattern, i2, j) {
+      switch (maskPattern) {
+        case QRMaskPattern.PATTERN000:
+          return (i2 + j) % 2 == 0;
+        case QRMaskPattern.PATTERN001:
+          return i2 % 2 == 0;
+        case QRMaskPattern.PATTERN010:
+          return j % 3 == 0;
+        case QRMaskPattern.PATTERN011:
+          return (i2 + j) % 3 == 0;
+        case QRMaskPattern.PATTERN100:
+          return (Math.floor(i2 / 2) + Math.floor(j / 3)) % 2 == 0;
+        case QRMaskPattern.PATTERN101:
+          return i2 * j % 2 + i2 * j % 3 == 0;
+        case QRMaskPattern.PATTERN110:
+          return (i2 * j % 2 + i2 * j % 3) % 2 == 0;
+        case QRMaskPattern.PATTERN111:
+          return (i2 * j % 3 + (i2 + j) % 2) % 2 == 0;
+        default:
+          throw new Error("bad maskPattern:" + maskPattern);
+      }
+    },
+    /*
+    获取RS的纠错多项式
+     */
+    getErrorCorrectPolynomial: function(errorCorrectLength) {
+      var a = new QRPolynomial([1], 0);
+      for (var i2 = 0; i2 < errorCorrectLength; i2++) {
+        a = a.multiply(new QRPolynomial([1, QRMath.gexp(i2)], 0));
+      }
+      return a;
+    },
+    /*
+    获取评价
+     */
+    getLostPoint: function(qrCode) {
+      var moduleCount = qrCode.getModuleCount(), lostPoint = 0, darkCount = 0;
+      for (var row = 0; row < moduleCount; row++) {
+        var sameCount = 0;
+        var head = qrCode.modules[row][0];
+        for (var col = 0; col < moduleCount; col++) {
+          var current = qrCode.modules[row][col];
+          if (col < moduleCount - 6) {
+            if (current && !qrCode.modules[row][col + 1] && qrCode.modules[row][col + 2] && qrCode.modules[row][col + 3] && qrCode.modules[row][col + 4] && !qrCode.modules[row][col + 5] && qrCode.modules[row][col + 6]) {
+              if (col < moduleCount - 10) {
+                if (qrCode.modules[row][col + 7] && qrCode.modules[row][col + 8] && qrCode.modules[row][col + 9] && qrCode.modules[row][col + 10]) {
+                  lostPoint += 40;
+                }
+              } else if (col > 3) {
+                if (qrCode.modules[row][col - 1] && qrCode.modules[row][col - 2] && qrCode.modules[row][col - 3] && qrCode.modules[row][col - 4]) {
+                  lostPoint += 40;
+                }
+              }
+            }
+          }
+          if (row < moduleCount - 1 && col < moduleCount - 1) {
+            var count = 0;
+            if (current)
+              count++;
+            if (qrCode.modules[row + 1][col])
+              count++;
+            if (qrCode.modules[row][col + 1])
+              count++;
+            if (qrCode.modules[row + 1][col + 1])
+              count++;
+            if (count == 0 || count == 4) {
+              lostPoint += 3;
+            }
+          }
+          if (head ^ current) {
+            sameCount++;
+          } else {
+            head = current;
+            if (sameCount >= 5) {
+              lostPoint += 3 + sameCount - 5;
+            }
+            sameCount = 1;
+          }
+          if (current) {
+            darkCount++;
+          }
+        }
+      }
+      for (var col = 0; col < moduleCount; col++) {
+        var sameCount = 0;
+        var head = qrCode.modules[0][col];
+        for (var row = 0; row < moduleCount; row++) {
+          var current = qrCode.modules[row][col];
+          if (row < moduleCount - 6) {
+            if (current && !qrCode.modules[row + 1][col] && qrCode.modules[row + 2][col] && qrCode.modules[row + 3][col] && qrCode.modules[row + 4][col] && !qrCode.modules[row + 5][col] && qrCode.modules[row + 6][col]) {
+              if (row < moduleCount - 10) {
+                if (qrCode.modules[row + 7][col] && qrCode.modules[row + 8][col] && qrCode.modules[row + 9][col] && qrCode.modules[row + 10][col]) {
+                  lostPoint += 40;
+                }
+              } else if (row > 3) {
+                if (qrCode.modules[row - 1][col] && qrCode.modules[row - 2][col] && qrCode.modules[row - 3][col] && qrCode.modules[row - 4][col]) {
+                  lostPoint += 40;
+                }
+              }
+            }
+          }
+          if (head ^ current) {
+            sameCount++;
+          } else {
+            head = current;
+            if (sameCount >= 5) {
+              lostPoint += 3 + sameCount - 5;
+            }
+            sameCount = 1;
+          }
+        }
+      }
+      var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+      lostPoint += ratio * 10;
+      return lostPoint;
+    }
+  };
+  var QRMath = {
+    /*
+    将n转化为a^m
+     */
+    glog: function(n) {
+      if (n < 1) {
+        throw new Error("glog(" + n + ")");
+      }
+      return QRMath.LOG_TABLE[n];
+    },
+    /*
+    将a^m转化为n
+     */
+    gexp: function(n) {
+      while (n < 0) {
+        n += 255;
+      }
+      while (n >= 256) {
+        n -= 255;
+      }
+      return QRMath.EXP_TABLE[n];
+    },
+    EXP_TABLE: new Array(256),
+    LOG_TABLE: new Array(256)
+  };
+  for (var i = 0; i < 8; i++) {
+    QRMath.EXP_TABLE[i] = 1 << i;
+  }
+  for (var i = 8; i < 256; i++) {
+    QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8];
+  }
+  for (var i = 0; i < 255; i++) {
+    QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+  }
+  function QRPolynomial(num, shift) {
+    if (num.length == void 0) {
+      throw new Error(num.length + "/" + shift);
+    }
+    var offset = 0;
+    while (offset < num.length && num[offset] == 0) {
+      offset++;
+    }
+    this.num = new Array(num.length - offset + shift);
+    for (var i2 = 0; i2 < num.length - offset; i2++) {
+      this.num[i2] = num[i2 + offset];
+    }
+  }
+  QRPolynomial.prototype = {
+    get: function(index) {
+      return this.num[index];
+    },
+    getLength: function() {
+      return this.num.length;
+    },
+    /**
+     * 多项式乘法
+     * @param  {QRPolynomial} e 被乘多项式
+     * @return {[type]}   [description]
+     */
+    multiply: function(e) {
+      var num = new Array(this.getLength() + e.getLength() - 1);
+      for (var i2 = 0; i2 < this.getLength(); i2++) {
+        for (var j = 0; j < e.getLength(); j++) {
+          num[i2 + j] ^= QRMath.gexp(QRMath.glog(this.get(i2)) + QRMath.glog(e.get(j)));
+        }
+      }
+      return new QRPolynomial(num, 0);
+    },
+    /**
+     * 多项式模运算
+     * @param  {QRPolynomial} e 模多项式
+     * @return {}
+     */
+    mod: function(e) {
+      var tl = this.getLength(), el = e.getLength();
+      if (tl - el < 0) {
+        return this;
+      }
+      var num = new Array(tl);
+      for (var i2 = 0; i2 < tl; i2++) {
+        num[i2] = this.get(i2);
+      }
+      while (num.length >= el) {
+        var ratio = QRMath.glog(num[0]) - QRMath.glog(e.get(0));
+        for (var i2 = 0; i2 < e.getLength(); i2++) {
+          num[i2] ^= QRMath.gexp(QRMath.glog(e.get(i2)) + ratio);
+        }
+        while (num[0] == 0) {
+          num.shift();
+        }
+      }
+      return new QRPolynomial(num, 0);
+    }
+  };
+  var RS_BLOCK_TABLE = [
+    // L
+    // M
+    // Q
+    // H
+    // 1
+    [1, 26, 19],
+    [1, 26, 16],
+    [1, 26, 13],
+    [1, 26, 9],
+    // 2
+    [1, 44, 34],
+    [1, 44, 28],
+    [1, 44, 22],
+    [1, 44, 16],
+    // 3
+    [1, 70, 55],
+    [1, 70, 44],
+    [2, 35, 17],
+    [2, 35, 13],
+    // 4
+    [1, 100, 80],
+    [2, 50, 32],
+    [2, 50, 24],
+    [4, 25, 9],
+    // 5
+    [1, 134, 108],
+    [2, 67, 43],
+    [2, 33, 15, 2, 34, 16],
+    [2, 33, 11, 2, 34, 12],
+    // 6
+    [2, 86, 68],
+    [4, 43, 27],
+    [4, 43, 19],
+    [4, 43, 15],
+    // 7
+    [2, 98, 78],
+    [4, 49, 31],
+    [2, 32, 14, 4, 33, 15],
+    [4, 39, 13, 1, 40, 14],
+    // 8
+    [2, 121, 97],
+    [2, 60, 38, 2, 61, 39],
+    [4, 40, 18, 2, 41, 19],
+    [4, 40, 14, 2, 41, 15],
+    // 9
+    [2, 146, 116],
+    [3, 58, 36, 2, 59, 37],
+    [4, 36, 16, 4, 37, 17],
+    [4, 36, 12, 4, 37, 13],
+    // 10
+    [2, 86, 68, 2, 87, 69],
+    [4, 69, 43, 1, 70, 44],
+    [6, 43, 19, 2, 44, 20],
+    [6, 43, 15, 2, 44, 16],
+    // 11
+    [4, 101, 81],
+    [1, 80, 50, 4, 81, 51],
+    [4, 50, 22, 4, 51, 23],
+    [3, 36, 12, 8, 37, 13],
+    // 12
+    [2, 116, 92, 2, 117, 93],
+    [6, 58, 36, 2, 59, 37],
+    [4, 46, 20, 6, 47, 21],
+    [7, 42, 14, 4, 43, 15],
+    // 13
+    [4, 133, 107],
+    [8, 59, 37, 1, 60, 38],
+    [8, 44, 20, 4, 45, 21],
+    [12, 33, 11, 4, 34, 12],
+    // 14
+    [3, 145, 115, 1, 146, 116],
+    [4, 64, 40, 5, 65, 41],
+    [11, 36, 16, 5, 37, 17],
+    [11, 36, 12, 5, 37, 13],
+    // 15
+    [5, 109, 87, 1, 110, 88],
+    [5, 65, 41, 5, 66, 42],
+    [5, 54, 24, 7, 55, 25],
+    [11, 36, 12],
+    // 16
+    [5, 122, 98, 1, 123, 99],
+    [7, 73, 45, 3, 74, 46],
+    [15, 43, 19, 2, 44, 20],
+    [3, 45, 15, 13, 46, 16],
+    // 17
+    [1, 135, 107, 5, 136, 108],
+    [10, 74, 46, 1, 75, 47],
+    [1, 50, 22, 15, 51, 23],
+    [2, 42, 14, 17, 43, 15],
+    // 18
+    [5, 150, 120, 1, 151, 121],
+    [9, 69, 43, 4, 70, 44],
+    [17, 50, 22, 1, 51, 23],
+    [2, 42, 14, 19, 43, 15],
+    // 19
+    [3, 141, 113, 4, 142, 114],
+    [3, 70, 44, 11, 71, 45],
+    [17, 47, 21, 4, 48, 22],
+    [9, 39, 13, 16, 40, 14],
+    // 20
+    [3, 135, 107, 5, 136, 108],
+    [3, 67, 41, 13, 68, 42],
+    [15, 54, 24, 5, 55, 25],
+    [15, 43, 15, 10, 44, 16],
+    // 21
+    [4, 144, 116, 4, 145, 117],
+    [17, 68, 42],
+    [17, 50, 22, 6, 51, 23],
+    [19, 46, 16, 6, 47, 17],
+    // 22
+    [2, 139, 111, 7, 140, 112],
+    [17, 74, 46],
+    [7, 54, 24, 16, 55, 25],
+    [34, 37, 13],
+    // 23
+    [4, 151, 121, 5, 152, 122],
+    [4, 75, 47, 14, 76, 48],
+    [11, 54, 24, 14, 55, 25],
+    [16, 45, 15, 14, 46, 16],
+    // 24
+    [6, 147, 117, 4, 148, 118],
+    [6, 73, 45, 14, 74, 46],
+    [11, 54, 24, 16, 55, 25],
+    [30, 46, 16, 2, 47, 17],
+    // 25
+    [8, 132, 106, 4, 133, 107],
+    [8, 75, 47, 13, 76, 48],
+    [7, 54, 24, 22, 55, 25],
+    [22, 45, 15, 13, 46, 16],
+    // 26
+    [10, 142, 114, 2, 143, 115],
+    [19, 74, 46, 4, 75, 47],
+    [28, 50, 22, 6, 51, 23],
+    [33, 46, 16, 4, 47, 17],
+    // 27
+    [8, 152, 122, 4, 153, 123],
+    [22, 73, 45, 3, 74, 46],
+    [8, 53, 23, 26, 54, 24],
+    [12, 45, 15, 28, 46, 16],
+    // 28
+    [3, 147, 117, 10, 148, 118],
+    [3, 73, 45, 23, 74, 46],
+    [4, 54, 24, 31, 55, 25],
+    [11, 45, 15, 31, 46, 16],
+    // 29
+    [7, 146, 116, 7, 147, 117],
+    [21, 73, 45, 7, 74, 46],
+    [1, 53, 23, 37, 54, 24],
+    [19, 45, 15, 26, 46, 16],
+    // 30
+    [5, 145, 115, 10, 146, 116],
+    [19, 75, 47, 10, 76, 48],
+    [15, 54, 24, 25, 55, 25],
+    [23, 45, 15, 25, 46, 16],
+    // 31
+    [13, 145, 115, 3, 146, 116],
+    [2, 74, 46, 29, 75, 47],
+    [42, 54, 24, 1, 55, 25],
+    [23, 45, 15, 28, 46, 16],
+    // 32
+    [17, 145, 115],
+    [10, 74, 46, 23, 75, 47],
+    [10, 54, 24, 35, 55, 25],
+    [19, 45, 15, 35, 46, 16],
+    // 33
+    [17, 145, 115, 1, 146, 116],
+    [14, 74, 46, 21, 75, 47],
+    [29, 54, 24, 19, 55, 25],
+    [11, 45, 15, 46, 46, 16],
+    // 34
+    [13, 145, 115, 6, 146, 116],
+    [14, 74, 46, 23, 75, 47],
+    [44, 54, 24, 7, 55, 25],
+    [59, 46, 16, 1, 47, 17],
+    // 35
+    [12, 151, 121, 7, 152, 122],
+    [12, 75, 47, 26, 76, 48],
+    [39, 54, 24, 14, 55, 25],
+    [22, 45, 15, 41, 46, 16],
+    // 36
+    [6, 151, 121, 14, 152, 122],
+    [6, 75, 47, 34, 76, 48],
+    [46, 54, 24, 10, 55, 25],
+    [2, 45, 15, 64, 46, 16],
+    // 37
+    [17, 152, 122, 4, 153, 123],
+    [29, 74, 46, 14, 75, 47],
+    [49, 54, 24, 10, 55, 25],
+    [24, 45, 15, 46, 46, 16],
+    // 38
+    [4, 152, 122, 18, 153, 123],
+    [13, 74, 46, 32, 75, 47],
+    [48, 54, 24, 14, 55, 25],
+    [42, 45, 15, 32, 46, 16],
+    // 39
+    [20, 147, 117, 4, 148, 118],
+    [40, 75, 47, 7, 76, 48],
+    [43, 54, 24, 22, 55, 25],
+    [10, 45, 15, 67, 46, 16],
+    // 40
+    [19, 148, 118, 6, 149, 119],
+    [18, 75, 47, 31, 76, 48],
+    [34, 54, 24, 34, 55, 25],
+    [20, 45, 15, 61, 46, 16]
+  ];
+  QRCodeAlg.prototype.getRightType = function() {
+    for (var typeNumber = 1; typeNumber < 41; typeNumber++) {
+      var rsBlock = RS_BLOCK_TABLE[(typeNumber - 1) * 4 + this.errorCorrectLevel];
+      if (rsBlock == void 0) {
+        throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + this.errorCorrectLevel);
+      }
+      var length = rsBlock.length / 3;
+      var totalDataCount = 0;
+      for (var i2 = 0; i2 < length; i2++) {
+        var count = rsBlock[i2 * 3 + 0];
+        var dataCount = rsBlock[i2 * 3 + 2];
+        totalDataCount += dataCount * count;
+      }
+      var lengthBytes = typeNumber > 9 ? 2 : 1;
+      if (this.utf8bytes.length + lengthBytes < totalDataCount || typeNumber == 40) {
+        this.typeNumber = typeNumber;
+        this.rsBlock = rsBlock;
+        this.totalDataCount = totalDataCount;
+        break;
+      }
+    }
+  };
+  function QRBitBuffer() {
+    this.buffer = new Array();
+    this.length = 0;
+  }
+  QRBitBuffer.prototype = {
+    get: function(index) {
+      var bufIndex = Math.floor(index / 8);
+      return this.buffer[bufIndex] >>> 7 - index % 8 & 1;
+    },
+    put: function(num, length) {
+      for (var i2 = 0; i2 < length; i2++) {
+        this.putBit(num >>> length - i2 - 1 & 1);
+      }
+    },
+    putBit: function(bit) {
+      var bufIndex = Math.floor(this.length / 8);
+      if (this.buffer.length <= bufIndex) {
+        this.buffer.push(0);
+      }
+      if (bit) {
+        this.buffer[bufIndex] |= 128 >>> this.length % 8;
+      }
+      this.length++;
+    }
+  };
+  let qrcodeAlgObjCache = [];
+  QRCode = function(opt) {
+    this.options = {
+      text: "",
+      size: 256,
+      correctLevel: 3,
+      background: "#ffffff",
+      foreground: "#000000",
+      pdground: "#000000",
+      image: "",
+      imageSize: 30,
+      canvasId: opt.canvasId,
+      context: opt.context,
+      usingComponents: opt.usingComponents,
+      showLoading: opt.showLoading,
+      loadingText: opt.loadingText
+    };
+    if (typeof opt === "string") {
+      opt = {
+        text: opt
+      };
+    }
+    if (opt) {
+      for (var i2 in opt) {
+        this.options[i2] = opt[i2];
+      }
+    }
+    var qrCodeAlg = null;
+    for (var i2 = 0, l = qrcodeAlgObjCache.length; i2 < l; i2++) {
+      if (qrcodeAlgObjCache[i2].text == this.options.text && qrcodeAlgObjCache[i2].text.correctLevel == this.options.correctLevel) {
+        qrCodeAlg = qrcodeAlgObjCache[i2].obj;
+        break;
+      }
+    }
+    if (i2 == l) {
+      qrCodeAlg = new QRCodeAlg(this.options.text, this.options.correctLevel);
+      qrcodeAlgObjCache.push({
+        text: this.options.text,
+        correctLevel: this.options.correctLevel,
+        obj: qrCodeAlg
+      });
+    }
+    let getForeGround = function(config) {
+      var options = config.options;
+      if (options.pdground && (config.row > 1 && config.row < 5 && config.col > 1 && config.col < 5 || config.row > config.count - 6 && config.row < config.count - 2 && config.col > 1 && config.col < 5 || config.row > 1 && config.row < 5 && config.col > config.count - 6 && config.col < config.count - 2)) {
+        return options.pdground;
+      }
+      return options.foreground;
+    };
+    let createCanvas = function(options) {
+      if (options.showLoading) {
+        common_vendor.index.showLoading({
+          title: options.loadingText,
+          mask: true
+        });
+      }
+      var ctx = common_vendor.index.createCanvasContext(options.canvasId, options.context);
+      var count = qrCodeAlg.getModuleCount();
+      var ratioSize = options.size;
+      var ratioImgSize = options.imageSize;
+      var tileW = (ratioSize / count).toPrecision(4);
+      var tileH = (ratioSize / count).toPrecision(4);
+      for (var row = 0; row < count; row++) {
+        for (var col = 0; col < count; col++) {
+          var w = Math.ceil((col + 1) * tileW) - Math.floor(col * tileW);
+          var h = Math.ceil((row + 1) * tileW) - Math.floor(row * tileW);
+          var foreground = getForeGround({
+            row,
+            col,
+            count,
+            options
+          });
+          ctx.setFillStyle(qrCodeAlg.modules[row][col] ? foreground : options.background);
+          ctx.fillRect(Math.round(col * tileW), Math.round(row * tileH), w, h);
+        }
+      }
+      if (options.image) {
+        let drawRoundedRect = function(ctxi, x2, y2, width, height, r, lineWidth, fill, stroke) {
+          ctxi.setLineWidth(lineWidth);
+          ctxi.setFillStyle(options.background);
+          ctxi.setStrokeStyle(options.background);
+          ctxi.beginPath();
+          ctxi.moveTo(x2 + r, y2);
+          ctxi.arcTo(x2 + width, y2, x2 + width, y2 + r, r);
+          ctxi.arcTo(x2 + width, y2 + height, x2 + width - r, y2 + height, r);
+          ctxi.arcTo(x2, y2 + height, x2, y2 + height - r, r);
+          ctxi.arcTo(x2, y2, x2 + r, y2, r);
+          ctxi.closePath();
+          if (fill) {
+            ctxi.fill();
+          }
+          if (stroke) {
+            ctxi.stroke();
+          }
+        };
+        var x = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+        var y = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+        drawRoundedRect(ctx, x, y, ratioImgSize, ratioImgSize, 2, 6, true, true);
+        ctx.drawImage(options.image, x, y, ratioImgSize, ratioImgSize);
+      }
+      setTimeout(() => {
+        ctx.draw(true, () => {
+          setTimeout(() => {
+            common_vendor.index.canvasToTempFilePath({
+              width: options.width,
+              height: options.height,
+              destWidth: options.width,
+              destHeight: options.height,
+              canvasId: options.canvasId,
+              quality: Number(1),
+              success: function(res) {
+                if (options.cbResult) {
+                  options.cbResult(res.tempFilePath);
+                }
+              },
+              fail: function(res) {
+                if (options.cbResult) {
+                  options.cbResult(res);
+                }
+              },
+              complete: function() {
+                if (options.showLoading) {
+                  common_vendor.index.hideLoading();
+                }
+              }
+            }, options.context);
+          }, options.text.length + 100);
+        });
+      }, options.usingComponents ? 0 : 150);
+    };
+    createCanvas(this.options);
+  };
+  QRCode.prototype.clear = function(fn) {
+    var ctx = common_vendor.index.createCanvasContext(this.options.canvasId, this.options.context);
+    ctx.clearRect(0, 0, this.options.size, this.options.size);
+    ctx.draw(false, () => {
+      if (fn) {
+        fn();
+      }
+    });
+  };
+})();
+const QRCode$1 = QRCode;
+exports.QRCode = QRCode$1;
+//# sourceMappingURL=../../../.sourcemap/mp-weixin/components/tki-qrcode/qrcode.js.map

+ 215 - 0
unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.js

@@ -0,0 +1,215 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+const components_tkiQrcode_qrcode = require("./qrcode.js");
+let qrcode;
+const _sfc_main = {
+  name: "tki-qrcode",
+  props: {
+    cid: {
+      type: String,
+      default: "tki-qrcode-canvas"
+    },
+    size: {
+      type: Number,
+      default: 200
+    },
+    unit: {
+      type: String,
+      default: "upx"
+    },
+    show: {
+      type: Boolean,
+      default: true
+    },
+    val: {
+      type: String,
+      default: ""
+    },
+    background: {
+      type: String,
+      default: "#ffffff"
+    },
+    foreground: {
+      type: String,
+      default: "#000000"
+    },
+    pdground: {
+      type: String,
+      default: "#000000"
+    },
+    icon: {
+      type: String,
+      default: ""
+    },
+    iconSize: {
+      type: Number,
+      default: 40
+    },
+    lv: {
+      type: Number,
+      default: 3
+    },
+    onval: {
+      type: Boolean,
+      default: false
+    },
+    loadMake: {
+      type: Boolean,
+      default: false
+    },
+    usingComponents: {
+      type: Boolean,
+      default: true
+    },
+    showLoading: {
+      type: Boolean,
+      default: true
+    },
+    loadingText: {
+      type: String,
+      default: "二维码生成中"
+    }
+  },
+  data() {
+    return {
+      result: ""
+    };
+  },
+  methods: {
+    _makeCode() {
+      let that = this;
+      if (!this._empty(this.val)) {
+        qrcode = new components_tkiQrcode_qrcode.QRCode({
+          context: that,
+          // 上下文环境
+          canvasId: that.cid,
+          // canvas-id
+          usingComponents: that.usingComponents,
+          // 是否是自定义组件
+          showLoading: that.showLoading,
+          // 是否显示loading
+          loadingText: that.loadingText,
+          // loading文字
+          text: that.val,
+          // 生成内容
+          size: that.cpSize,
+          // 二维码大小
+          background: that.background,
+          // 背景色
+          foreground: that.foreground,
+          // 前景色
+          pdground: that.pdground,
+          // 定位角点颜色
+          correctLevel: that.lv,
+          // 容错级别
+          image: that.icon,
+          // 二维码图标
+          imageSize: that.iconSize,
+          // 二维码图标大小
+          cbResult: function(res) {
+            that._result(res);
+          }
+        });
+      } else {
+        common_vendor.index.showToast({
+          title: "二维码内容不能为空",
+          icon: "none",
+          duration: 2e3
+        });
+      }
+    },
+    _clearCode() {
+      this._result("");
+      qrcode.clear();
+    },
+    _saveCode() {
+      let that = this;
+      if (this.result != "") {
+        common_vendor.index.saveImageToPhotosAlbum({
+          filePath: that.result,
+          success: function() {
+            common_vendor.index.showToast({
+              title: "二维码保存成功",
+              icon: "success",
+              duration: 2e3
+            });
+          }
+        });
+      }
+    },
+    _result(res) {
+      this.result = res;
+      this.$emit("result", res);
+    },
+    _empty(v) {
+      let tp = typeof v, rt = false;
+      if (tp == "number" && String(v) == "") {
+        rt = true;
+      } else if (tp == "undefined") {
+        rt = true;
+      } else if (tp == "object") {
+        if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null)
+          rt = true;
+      } else if (tp == "string") {
+        if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]")
+          rt = true;
+      } else if (tp == "function") {
+        rt = false;
+      }
+      return rt;
+    }
+  },
+  watch: {
+    size: function(n, o) {
+      if (n != o && !this._empty(n)) {
+        this.cSize = n;
+        if (!this._empty(this.val)) {
+          setTimeout(() => {
+            this._makeCode();
+          }, 100);
+        }
+      }
+    },
+    val: function(n, o) {
+      if (this.onval) {
+        if (n != o && !this._empty(n)) {
+          setTimeout(() => {
+            this._makeCode();
+          }, 0);
+        }
+      }
+    }
+  },
+  computed: {
+    cpSize() {
+      if (this.unit == "upx") {
+        return common_vendor.index.upx2px(this.size);
+      } else {
+        return this.size;
+      }
+    }
+  },
+  mounted: function() {
+    if (this.loadMake) {
+      if (!this._empty(this.val)) {
+        setTimeout(() => {
+          this._makeCode();
+        }, 0);
+      }
+    }
+  }
+};
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return {
+    a: $props.cid,
+    b: $options.cpSize + "px",
+    c: $options.cpSize + "px",
+    d: $props.show,
+    e: $data.result,
+    f: $options.cpSize + "px",
+    g: $options.cpSize + "px"
+  };
+}
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
+wx.createComponent(Component);
+//# sourceMappingURL=../../../.sourcemap/mp-weixin/components/tki-qrcode/tki-qrcode.js.map

+ 4 - 0
unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.wxml

@@ -0,0 +1 @@
+<view class="tki-qrcode"><block wx:if="{{r0}}"><canvas class="tki-qrcode-canvas" canvas-id="{{a}}" style="{{'width:' + b + ';' + ('height:' + c)}}"/></block><image hidden="{{!d}}" src="{{e}}" style="{{'width:' + f + ';' + ('height:' + g)}}"/></view>

+ 10 - 0
unpackage/dist/dev/mp-weixin/components/tki-qrcode/tki-qrcode.wxss

@@ -0,0 +1,10 @@
+
+.tki-qrcode {
+  position: relative;
+}
+.tki-qrcode-canvas {
+  position: fixed;
+  top: -99999rpx;
+  left: -99999rpx;
+  z-index: -99999;
+}

+ 193 - 0
unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.js

@@ -0,0 +1,193 @@
+"use strict";
+const common_vendor = require("../../../../common/vendor.js");
+const _sfc_main = {
+  props: {
+    text: {
+      type: String,
+      required: true
+    },
+    qid: {
+      type: String
+    },
+    correctLevel: {
+      type: Number,
+      default: 1
+    },
+    size: {
+      type: Number,
+      default: 200
+    },
+    margin: {
+      type: Number,
+      default: 20
+    },
+    colorDark: {
+      type: String,
+      default: "#000000"
+    },
+    colorLight: {
+      type: String,
+      default: "#FFFFFF"
+    },
+    bgSrc: {
+      type: String,
+      default: void 0
+    },
+    background: {
+      type: String,
+      default: "rgba(0,0,0,0)"
+    },
+    backgroundDimming: {
+      type: String,
+      default: "rgba(0,0,0,0)"
+    },
+    logoSrc: {
+      type: String,
+      default: void 0
+    },
+    logoBackgroundColor: {
+      type: String,
+      default: "rgba(255,255,255,1)"
+    },
+    gifBgSrc: {
+      type: String,
+      default: void 0
+    },
+    logoScale: {
+      type: Number,
+      default: 0.2
+    },
+    logoMargin: {
+      type: Number,
+      default: 0
+    },
+    logoCornerRadius: {
+      type: Number,
+      default: 8
+    },
+    whiteMargin: {
+      type: [Boolean, String],
+      default: true
+    },
+    dotScale: {
+      type: Number,
+      default: 1
+    },
+    autoColor: {
+      type: [Boolean, String],
+      default: true
+    },
+    binarize: {
+      type: [Boolean, String],
+      default: false
+    },
+    binarizeThreshold: {
+      type: Number,
+      default: 128
+    },
+    callback: {
+      type: Function,
+      default: function() {
+        return void 0;
+      }
+    },
+    bindElement: {
+      type: Boolean,
+      default: true
+    },
+    backgroundColor: {
+      type: String,
+      default: "#FFFFFF"
+    },
+    components: {
+      default: function() {
+        return {
+          data: {
+            scale: 1
+          },
+          timing: {
+            scale: 1,
+            protectors: false
+          },
+          alignment: {
+            scale: 1,
+            protectors: false
+          },
+          cornerAlignment: {
+            scale: 1,
+            protectors: true
+          }
+        };
+      }
+    }
+  },
+  name: "vue-qr",
+  data() {
+    return {
+      imgUrl: ""
+    };
+  },
+  watch: {
+    $props: {
+      deep: true,
+      handler() {
+        this.main();
+      }
+    }
+  },
+  mounted() {
+    this.main();
+  },
+  methods: {
+    async main() {
+      if (this.gifBgSrc) {
+        const gifImg = await common_vendor.readAsArrayBuffer(this.gifBgSrc);
+        const logoImg2 = this.logoSrc;
+        this.render(void 0, logoImg2, gifImg);
+        return;
+      }
+      const bgImg = this.bgSrc;
+      const logoImg = this.logoSrc;
+      this.render(bgImg, logoImg);
+    },
+    async render(img, logoImg, gifBgSrc) {
+      const that = this;
+      new common_vendor.AwesomeQR({
+        gifBackground: gifBgSrc,
+        text: that.text,
+        size: that.size,
+        margin: that.margin,
+        colorDark: that.colorDark,
+        colorLight: that.colorLight,
+        backgroundColor: that.backgroundColor,
+        backgroundImage: img,
+        backgroundDimming: that.backgroundDimming,
+        logoImage: logoImg,
+        logoScale: that.logoScale,
+        logoBackgroundColor: that.logoBackgroundColor,
+        correctLevel: that.correctLevel,
+        logoMargin: that.logoMargin,
+        logoCornerRadius: that.logoCornerRadius,
+        whiteMargin: common_vendor.toBoolean(that.whiteMargin),
+        dotScale: that.dotScale,
+        autoColor: common_vendor.toBoolean(that.autoColor),
+        binarize: common_vendor.toBoolean(that.binarize),
+        binarizeThreshold: that.binarizeThreshold,
+        components: that.components
+      }).draw().then((dataUri) => {
+        this.imgUrl = dataUri;
+        that.callback && that.callback(dataUri, that.qid);
+      });
+    }
+  }
+};
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return common_vendor.e({
+    a: $props.bindElement
+  }, $props.bindElement ? {
+    b: $data.imgUrl
+  } : {});
+}
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
+wx.createComponent(Component);
+//# sourceMappingURL=../../../../../.sourcemap/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.js.map

+ 4 - 0
unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.wxml

@@ -0,0 +1 @@
+<image wx:if="{{a}}" style="display:inline-block" src="{{b}}"/>

+ 0 - 0
unpackage/dist/dev/mp-weixin/node-modules/vue-qr/src/packages/vue-qr.wxss


+ 150 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/index.js

@@ -0,0 +1,150 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+const store_user = require("../../store/user.js");
+const store_checkin = require("../../store/checkin.js");
+if (!Math) {
+  NavBar();
+}
+const NavBar = () => "../components/NavBar/NavBar.js";
+const _sfc_main = {
+  __name: "index",
+  setup(__props) {
+    const userStore = store_user.useUserStore();
+    const checkinStore = store_checkin.useCheckinStore();
+    const isTeacher = common_vendor.computed(() => userStore.userInfo.role === "TEACHER");
+    common_vendor.onShow(() => {
+      if (isTeacher.value) {
+        common_vendor.index.redirectTo({
+          url: "/pages/checkin/teacher/index"
+        });
+      }
+    });
+    const studentView = common_vendor.ref("main");
+    const navBarRef = common_vendor.ref(null);
+    const navHeight = common_vendor.ref(88);
+    const navTitle = common_vendor.computed(() => {
+      if (studentView.value === "numberInput")
+        return "输入签到码";
+      return "课堂签到";
+    });
+    const studentCode = common_vendor.ref(["", "", "", ""]);
+    const isStudentCodeValid = common_vendor.computed(() => studentCode.value.every((c) => c !== "" && !isNaN(c)));
+    const isAlreadyCheckedIn = common_vendor.computed(() => checkinStore.isStudentCheckedIn(userStore.userInfo.id));
+    const studentGreeting = common_vendor.computed(() => {
+      if (isAlreadyCheckedIn.value)
+        return "你已成功签到,表现不错!👍";
+      if (!checkinStore.session.isActive)
+        return "签到还未开始,请稍作等待。";
+      return `${userStore.userInfo.name || userStore.userInfo.englishName || "同学"}, 签到开始啦~`;
+    });
+    const studentButtonText = common_vendor.computed(() => {
+      if (isAlreadyCheckedIn.value)
+        return "已签到";
+      if (!checkinStore.session.isActive)
+        return "暂未设置";
+      return checkinStore.session.method === "number" ? "数字签到" : "二维码签到";
+    });
+    const studentAction = () => {
+      if (checkinStore.session.method === "number") {
+        studentView.value = "numberInput";
+      } else {
+        common_vendor.index.scanCode({
+          onlyFromCamera: true,
+          success: (res) => {
+            if (res.result === checkinStore.session.code) {
+              const result = checkinStore.attemptCheckin({ id: userStore.userInfo.id, name: userStore.userInfo.name });
+              common_vendor.index.showToast({ title: result.message, icon: result.success ? "success" : "none" });
+            } else {
+              common_vendor.index.showToast({ title: "无效的签到码", icon: "none" });
+            }
+          },
+          fail: () => {
+            common_vendor.index.showToast({ title: "取消扫码", icon: "none" });
+          }
+        });
+      }
+    };
+    const submitNumberCode = () => {
+      const code = studentCode.value.join("");
+      const result = checkinStore.attemptCheckin({ id: userStore.userInfo.id, name: userStore.userInfo.name }, code);
+      common_vendor.index.showToast({ title: result.message, icon: result.success ? "success" : "none" });
+      if (result.success) {
+        studentView.value = "main";
+      }
+    };
+    const onBack = () => {
+      if (studentView.value !== "main") {
+        studentView.value = "main";
+      }
+    };
+    const formattedCurrentTime = common_vendor.computed(() => {
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = now.getMonth() + 1;
+      const day = now.getDate();
+      const hours = String(now.getHours()).padStart(2, "0");
+      const minutes = String(now.getMinutes()).padStart(2, "0");
+      return `${year}年${month}月${day}日 ${hours}:${minutes}`;
+    });
+    const contentWrapperStyle = common_vendor.computed(() => ({
+      paddingTop: `${navHeight.value}px`,
+      // 让内容区撑满剩余屏幕
+      minHeight: `calc(100vh - ${navHeight.value}px)`
+    }));
+    common_vendor.watch(() => checkinStore.session.isActive, (newVal) => {
+      if (!newVal && !isTeacher.value) {
+        studentView.value = "main";
+      }
+    });
+    common_vendor.onMounted(() => {
+      common_vendor.watch(
+        () => {
+          var _a;
+          return (_a = navBarRef.value) == null ? void 0 : _a.totalNavHeight;
+        },
+        (val) => {
+          if (val)
+            navHeight.value = val;
+        },
+        { immediate: true }
+      );
+    });
+    return (_ctx, _cache) => {
+      return common_vendor.e({
+        a: !isTeacher.value
+      }, !isTeacher.value ? common_vendor.e({
+        b: common_vendor.sr(navBarRef, "1410bd6b-0", {
+          "k": "navBarRef"
+        }),
+        c: common_vendor.o(onBack),
+        d: common_vendor.p({
+          title: navTitle.value,
+          showBack: studentView.value !== "main"
+        }),
+        e: studentView.value === "main"
+      }, studentView.value === "main" ? {
+        f: common_vendor.t(studentGreeting.value),
+        g: common_vendor.t(formattedCurrentTime.value),
+        h: common_vendor.t(studentButtonText.value),
+        i: !common_vendor.unref(checkinStore).session.isActive || isAlreadyCheckedIn.value,
+        j: common_vendor.o(studentAction)
+      } : studentView.value === "numberInput" ? {
+        l: common_vendor.f(studentCode.value, (item, index, i0) => {
+          return {
+            a: index,
+            b: studentCode.value[index],
+            c: common_vendor.o(($event) => studentCode.value[index] = $event.detail.value, index)
+          };
+        }),
+        m: !isStudentCodeValid.value,
+        n: common_vendor.o(submitNumberCode)
+      } : {}, {
+        k: studentView.value === "numberInput",
+        o: common_vendor.s(contentWrapperStyle.value)
+      }) : {});
+    };
+  }
+};
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-1410bd6b"]]);
+wx.createPage(MiniProgramPage);
+//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/checkin/index.js.map

+ 7 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/index.json

@@ -0,0 +1,7 @@
+{
+  "navigationBarTitleText": "课堂签到",
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "nav-bar": "../components/NavBar/NavBar"
+  }
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/index.wxml

@@ -0,0 +1 @@
+<view wx:if="{{a}}" class="student-container data-v-1410bd6b"><nav-bar wx:if="{{d}}" class="r data-v-1410bd6b" u-r="navBarRef" bindback="{{c}}" u-i="1410bd6b-0" bind:__l="__l" u-p="{{d}}"/><view class="content-wrapper data-v-1410bd6b" style="{{o}}"><view wx:if="{{e}}" class="student-card data-v-1410bd6b"><view class="header-content data-v-1410bd6b"><text class="title data-v-1410bd6b">课堂签到</text><view class="greeting-wrapper student data-v-1410bd6b"><text class="greeting data-v-1410bd6b">{{f}}</text></view><text class="timestamp data-v-1410bd6b">{{g}}</text></view><view class="course-info data-v-1410bd6b"><text class="label data-v-1410bd6b">当前课程</text><text class="value data-v-1410bd6b">英语写作</text></view><view class="checkin-action data-v-1410bd6b"><text class="label data-v-1410bd6b">当前签到方式:</text><button class="checkin-button data-v-1410bd6b" disabled="{{i}}" bindtap="{{j}}">{{h}}</button></view></view><view wx:elif="{{k}}" class="student-card data-v-1410bd6b"><view class="header-content data-v-1410bd6b"><text class="title data-v-1410bd6b">输入签到码</text></view><view class="code-input-area data-v-1410bd6b"><text class="label data-v-1410bd6b">输入老师指定的4位数字</text><view class="code-inputs data-v-1410bd6b"><input wx:for="{{l}}" wx:for-item="item" wx:key="a" class="data-v-1410bd6b" type="number" maxlength="1" value="{{item.b}}" bindinput="{{item.c}}"/></view></view><button class="start-button student data-v-1410bd6b" disabled="{{m}}" bindtap="{{n}}">确认签到</button></view></view></view><view wx:else class="loading-container data-v-1410bd6b"><text class="data-v-1410bd6b">正在加载教师端...</text></view>

+ 153 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/index.wxss

@@ -0,0 +1,153 @@
+
+/* 容器样式 */
+.student-container.data-v-1410bd6b, .loading-container.data-v-1410bd6b {
+  min-height: 100vh;
+  background-color: #f5f5f7;
+}
+.loading-container.data-v-1410bd6b {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #999;
+  font-size: 30rpx;
+}
+.content-wrapper.data-v-1410bd6b {
+  padding: 20rpx 30rpx;
+  box-sizing: border-box;
+  /* 【新增】让内容包装器也成为flex容器,使其子元素可以撑开 */
+  display: flex;
+}
+
+/* 学生卡片样式 */
+.student-card.data-v-1410bd6b {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  padding: 60rpx 40rpx; /* 增加垂直内边距 */
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  border-top: 8rpx solid #5CB85C;
+
+  flex: 1; /* 让卡片撑满父容器(.content-wrapper)的高度 */
+  justify-content: space-evenly; /* 让卡片内的元素在垂直方向上均匀分布 */
+}
+
+/* 头部内容 */
+.header-content.data-v-1410bd6b {
+  /* 【修改】使用flex进行垂直居中对齐 */
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16rpx; /* 统一设置元素间距 */
+}
+.title.data-v-1410bd6b {
+  /* 移除 display: flex */
+  font-size: 48rpx;
+  font-weight: 600;
+  text-align: center;
+}
+.greeting-wrapper.data-v-1410bd6b {
+  /* 移除 margin-top */
+  display: inline-flex;
+  padding: 10rpx 24rpx;
+  border-radius: 30rpx;
+}
+.greeting-wrapper.student.data-v-1410bd6b {
+  background-color: #EAF7EA;
+}
+.greeting-wrapper.student .greeting.data-v-1410bd6b {
+  color: #5CB85C;
+  font-size: 28rpx;
+  font-weight: 500;
+}
+.timestamp.data-v-1410bd6b {
+  font-size: 28rpx;
+  color: #999;
+  text-align: center;
+  /* 移除 margin-top */
+}
+.label.data-v-1410bd6b {
+  font-size: 30rpx;
+  color: #666;
+}
+.value.data-v-1410bd6b {
+  font-size: 32rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+/* 课程和操作区域 */
+.course-info.data-v-1410bd6b, .checkin-action.data-v-1410bd6b {
+  width: 100%;
+  text-align: center;
+  /* 【移除】移除固定的 margin-top,交由 flex 布局自动分配 */
+}
+.course-info .value.data-v-1410bd6b {
+  font-size: 44rpx;
+  font-weight: 600;
+  margin-top: 10rpx;
+  display: block;
+}
+.checkin-action .label.data-v-1410bd6b {
+  margin-bottom: 30rpx;
+  display: block;
+}
+.checkin-button.data-v-1410bd6b {
+  width: 80%;
+  height: 90rpx;
+  line-height: 90rpx;
+  background-color: #5CB85C;
+  color: #fff;
+  border-radius: 45rpx;
+  border: none;
+  font-size: 34rpx;
+}
+.checkin-button[disabled].data-v-1410bd6b {
+  background-color: #ccc;
+  color: #666;
+}
+
+/* 数字码输入区 */
+.code-input-area.data-v-1410bd6b {
+  text-align: center;
+  width: 100%;
+  /* 【移除】移除固定的 margin-top */
+}
+.code-input-area .label.data-v-1410bd6b {
+  font-size: 28rpx;
+  color: #999;
+}
+.code-inputs.data-v-1410bd6b {
+  display: flex;
+  gap: 20rpx;
+  margin-top: 30rpx;
+  justify-content: center;
+}
+.code-inputs input.data-v-1410bd6b {
+  width: 100rpx;
+  height: 120rpx;
+  border: 1px solid #ccc;
+  border-radius: 12rpx;
+  text-align: center;
+  font-size: 48rpx;
+  font-weight: bold;
+}
+.start-button.student.data-v-1410bd6b {
+  width: 80%;
+  height: 90rpx;
+  line-height: 90rpx;
+  background-color: #5CB85C;
+  color: #fff;
+  border-radius: 45rpx;
+  border: none;
+  font-size: 34rpx;
+  /* 【移除】移除固定的 margin-top */
+}
+.start-button[disabled].data-v-1410bd6b {
+  background-color: #ccc;
+  color: #666;
+}

+ 105 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.js

@@ -0,0 +1,105 @@
+"use strict";
+const common_vendor = require("../../../common/vendor.js");
+const common_assets = require("../../../common/assets.js");
+const store_checkin = require("../../../store/checkin.js");
+if (!Math) {
+  NavBar();
+}
+const NavBar = () => "../../components/NavBar/NavBar.js";
+const _sfc_main = {
+  __name: "index",
+  setup(__props) {
+    const navBarRef = common_vendor.ref(null);
+    const navHeight = common_vendor.ref(88);
+    const contentWrapperStyle = common_vendor.computed(() => ({ paddingTop: `${navHeight.value}px` }));
+    common_vendor.onMounted(() => {
+      common_vendor.watch(() => {
+        var _a;
+        return (_a = navBarRef.value) == null ? void 0 : _a.totalNavHeight;
+      }, (val) => {
+        if (val)
+          navHeight.value = val;
+      }, { immediate: true });
+    });
+    const checkinStore = store_checkin.useCheckinStore();
+    const greetingText = common_vendor.computed(() => checkinStore.session.isActive ? "签到进行中..." : "准备开始上课啦~");
+    const formattedCurrentTime = common_vendor.computed(() => {
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = now.getMonth() + 1;
+      const day = now.getDate();
+      const hours = String(now.getHours()).padStart(2, "0");
+      const minutes = String(now.getMinutes()).padStart(2, "0");
+      return `${year}年${month}月${day}日  ${hours}:${minutes}`;
+    });
+    const reEnterActiveSession = () => {
+      if (!checkinStore.session.isActive)
+        return;
+      if (checkinStore.session.method === "qr") {
+        common_vendor.index.navigateTo({ url: "/pages/checkin/teacher/qr-active" });
+      } else {
+        common_vendor.index.showToast({ title: "数字码已在此页面显示", icon: "none" });
+      }
+    };
+    const navigateTo = (url) => {
+      if (url.includes("select-class") || url.includes("select-time")) {
+        common_vendor.index.showToast({ title: "此功能暂未开放", icon: "none" });
+        return;
+      }
+      common_vendor.index.navigateTo({ url });
+    };
+    const endCheckinConfirm = () => {
+      common_vendor.index.showModal({
+        title: "确认",
+        content: "确定要结束本次签到吗?",
+        success: (res) => {
+          if (res.confirm)
+            checkinStore.endCheckin();
+        }
+      });
+    };
+    return (_ctx, _cache) => {
+      return common_vendor.e({
+        a: common_vendor.sr(navBarRef, "1908beae-0", {
+          "k": "navBarRef"
+        }),
+        b: common_vendor.p({
+          title: "课堂签到"
+        }),
+        c: common_vendor.t(greetingText.value),
+        d: common_vendor.t(formattedCurrentTime.value),
+        e: common_vendor.unref(checkinStore).session.isActive
+      }, common_vendor.unref(checkinStore).session.isActive ? common_vendor.e({
+        f: common_vendor.unref(checkinStore).session.method === "number"
+      }, common_vendor.unref(checkinStore).session.method === "number" ? {
+        g: common_vendor.t(common_vendor.unref(checkinStore).session.code)
+      } : {
+        h: common_assets._imports_0$1
+      }, {
+        i: common_vendor.o(reEnterActiveSession),
+        j: common_vendor.t(common_vendor.unref(checkinStore).checkedInCount),
+        k: common_vendor.t(common_vendor.unref(checkinStore).session.totalStudentCount),
+        l: common_vendor.t(common_vendor.unref(checkinStore).checkedInCount),
+        m: common_vendor.unref(checkinStore).checkedInCount > 0
+      }, common_vendor.unref(checkinStore).checkedInCount > 0 ? {
+        n: common_vendor.f(common_vendor.unref(checkinStore).session.checkedInStudents, (s, k0, i0) => {
+          return {
+            a: common_vendor.t(s.name),
+            b: s.id
+          };
+        })
+      } : {}, {
+        o: common_vendor.o(endCheckinConfirm)
+      }) : {
+        p: common_vendor.o(($event) => navigateTo("/pages/checkin/teacher/select-class")),
+        q: common_vendor.o(($event) => navigateTo("/pages/checkin/teacher/select-time")),
+        r: common_vendor.o(($event) => navigateTo("/pages/checkin/teacher/select-method"))
+      }, {
+        s: common_vendor.s(contentWrapperStyle.value)
+      });
+    };
+  }
+};
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-1908beae"]]);
+wx.createPage(MiniProgramPage);
+//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/checkin/teacher/index.js.map

+ 6 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.json

@@ -0,0 +1,6 @@
+{
+  "navigationBarTitleText": "",
+  "usingComponents": {
+    "nav-bar": "../../components/NavBar/NavBar"
+  }
+}

File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.wxml


+ 80 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/index.wxss

@@ -0,0 +1,80 @@
+
+.teacher-container.data-v-1908beae { min-height: 100vh; background-color: #f5f5f7;
+}
+.content-wrapper.data-v-1908beae { padding: 20rpx 30rpx; box-sizing: border-box;
+}
+.teacher-card.data-v-1908beae { width: 100%; background-color: #fff; border-radius: 20rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); padding: 40rpx; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; border-top: 8rpx solid #7DB4D6;
+}
+
+/* --- 样式修改的核心区域 --- */
+.header-content.data-v-1908beae {
+  width: 100%;
+  display: flex; /* 使用 Flexbox 布局 */
+  flex-direction: column; /* 垂直排列,确保每个元素占一行 */
+  align-items: center; /* 所有元素水平居中 */
+  gap: 16rpx; /* 设置元素之间的垂直间距 */
+  margin-bottom: 20rpx; /* 与下方内容区域的间距 */
+}
+.title.data-v-1908beae {
+  font-size: 48rpx;
+  font-weight: 600;
+  color: #333;
+}
+.greeting-wrapper.data-v-1908beae {
+  /* margin 将由 flex gap 控制,无需单独设置 */
+  display: inline-flex; /* 保持标签包裹性 */
+  padding: 8rpx 20rpx;
+  border-radius: 30rpx;
+  background-color: #EBF5FA;
+}
+.greeting-wrapper .greeting.data-v-1908beae {
+  color: #7DB4D6;
+  font-size: 26rpx;
+  font-weight: 500;
+}
+.timestamp.data-v-1908beae {
+  /* margin 将由 flex gap 控制,无需单独设置 */
+  font-size: 28rpx;
+  color: #999;
+}
+/* --- 样式修改结束 --- */
+.label.data-v-1908beae { font-size: 30rpx; color: #333;
+}
+.arrow.data-v-1908beae { font-size: 36rpx; color: #cccccc; font-weight: bold;
+}
+.info-list.data-v-1908beae { width: 100%; margin-top: 40rpx;
+}
+.info-item.data-v-1908beae { padding: 35rpx 20rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #f0f0f0;
+}
+.info-item.data-v-1908beae:last-child { border-bottom: none;
+}
+.checkin-active-view.data-v-1908beae { width: 100%; margin-top: 40rpx; display: flex; flex-direction: column;
+}
+.checkin-info-entry.data-v-1908beae { background-color: #f0f8ff; border: 1px solid #d6e8f5; border-radius: 16rpx; padding: 30rpx; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: background-color 0.2s;
+}
+.checkin-info-entry.data-v-1908beae:active { background-color: #e6f2fa;
+}
+.entry-label.data-v-1908beae { color: #555; font-size: 28rpx;
+}
+.entry-value.data-v-1908beae { color: #007aff; font-size: 48rpx; font-weight: bold; letter-spacing: 8rpx; margin-top: 10rpx; display: block;
+}
+.number-display.data-v-1908beae, .qr-display.data-v-1908beae { flex: 1;
+}
+.qr-display.data-v-1908beae { display: flex; align-items: center; gap: 20rpx;
+}
+.qr-icon.data-v-1908beae { width: 60rpx; height: 60rpx;
+}
+.qr-display .entry-label.data-v-1908beae { font-size: 34rpx; font-weight: 500; color: #333;
+}
+.checkin-status.data-v-1908beae { width: 100%; margin-top: 40rpx; text-align: left; padding-top: 30rpx; border-top: 1rpx solid #f0f0f0;
+}
+.status-item.data-v-1908beae { margin-bottom: 30rpx;
+}
+.student-list.data-v-1908beae, .student-list-empty.data-v-1908beae { max-height: 200rpx; overflow-y: auto; background-color: #f9f9f9; padding: 20rpx; border-radius: 8rpx; margin-top: 10rpx; display: flex; flex-wrap: wrap; gap: 15rpx;
+}
+.student-list-empty.data-v-1908beae { justify-content: center; align-items: center; color: #999; font-size: 26rpx;
+}
+.student-list text.data-v-1908beae { background: #e9e9e9; padding: 8rpx 12rpx; border-radius: 6rpx; font-size: 26rpx; color: #555;
+}
+.end-button.data-v-1908beae { background-color: #e54d42; color: #fff; margin-top: 20rpx; width: 100%; height: 80rpx; line-height: 80rpx; font-size: 30rpx;
+}

+ 66 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.js

@@ -0,0 +1,66 @@
+"use strict";
+const common_vendor = require("../../../common/vendor.js");
+const store_checkin = require("../../../store/checkin.js");
+if (!Math) {
+  NavBar();
+}
+const NavBar = () => "../../components/NavBar/NavBar.js";
+const _sfc_main = {
+  __name: "number-setup",
+  setup(__props) {
+    const navBarRef = common_vendor.ref(null);
+    const navHeight = common_vendor.ref(88);
+    const contentWrapperStyle = common_vendor.computed(() => ({ paddingTop: `${navHeight.value}px` }));
+    common_vendor.onMounted(() => {
+      common_vendor.watch(() => {
+        var _a;
+        return (_a = navBarRef.value) == null ? void 0 : _a.totalNavHeight;
+      }, (val) => {
+        if (val)
+          navHeight.value = val;
+      }, { immediate: true });
+    });
+    const checkinStore = store_checkin.useCheckinStore();
+    const numberCode = common_vendor.ref(["", "", "", ""]);
+    const isCodeValid = common_vendor.computed(() => numberCode.value.every((c) => c !== "" && !isNaN(c)));
+    const generateRandomCode = () => {
+      let code = "";
+      for (let i = 0; i < 4; i++) {
+        code += Math.floor(Math.random() * 10);
+      }
+      numberCode.value = code.split("");
+    };
+    const startNumberCheckin = () => {
+      if (!isCodeValid.value)
+        return;
+      const code = numberCode.value.join("");
+      checkinStore.startCheckin({ method: "number", code });
+      common_vendor.index.reLaunch({ url: "/pages/checkin/teacher/index" });
+    };
+    return (_ctx, _cache) => {
+      return {
+        a: common_vendor.sr(navBarRef, "94c4d70d-0", {
+          "k": "navBarRef"
+        }),
+        b: common_vendor.p({
+          title: "数字签到设置",
+          showBack: true
+        }),
+        c: common_vendor.f(numberCode.value, (item, index, i0) => {
+          return {
+            a: index,
+            b: numberCode.value[index],
+            c: common_vendor.o(($event) => numberCode.value[index] = $event.detail.value, index)
+          };
+        }),
+        d: common_vendor.o(generateRandomCode),
+        e: !isCodeValid.value,
+        f: common_vendor.o(startNumberCheckin),
+        g: common_vendor.s(contentWrapperStyle.value)
+      };
+    };
+  }
+};
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-94c4d70d"]]);
+wx.createPage(MiniProgramPage);
+//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/checkin/teacher/number-setup.js.map

+ 6 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.json

@@ -0,0 +1,6 @@
+{
+  "navigationBarTitleText": "",
+  "usingComponents": {
+    "nav-bar": "../../components/NavBar/NavBar"
+  }
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.wxml

@@ -0,0 +1 @@
+<view class="teacher-container data-v-94c4d70d"><nav-bar wx:if="{{b}}" class="r data-v-94c4d70d" u-r="navBarRef" u-i="94c4d70d-0" bind:__l="__l" u-p="{{b}}"/><view class="content-wrapper data-v-94c4d70d" style="{{g}}"><view class="teacher-card data-v-94c4d70d"><view class="header-content data-v-94c4d70d"><text class="title data-v-94c4d70d">数字签到</text></view><view class="code-input-area data-v-94c4d70d"><text class="area-label data-v-94c4d70d">指定4位签到数字</text><view class="code-inputs data-v-94c4d70d"><input wx:for="{{c}}" wx:for-item="item" wx:key="a" class="data-v-94c4d70d" type="number" maxlength="1" value="{{item.b}}" bindinput="{{item.c}}"/></view><button class="random-button data-v-94c4d70d" bindtap="{{d}}">随机生成</button></view><button class="start-button data-v-94c4d70d" disabled="{{e}}" bindtap="{{f}}">开始签到</button></view></view></view>

+ 27 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup.wxss

@@ -0,0 +1,27 @@
+
+.teacher-container.data-v-94c4d70d { min-height: 100vh; background-color: #f5f5f7;
+}
+.content-wrapper.data-v-94c4d70d { padding: 20rpx 30rpx; box-sizing: border-box;
+}
+.teacher-card.data-v-94c4d70d { width: 100%; background-color: #fff; border-radius: 20rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); padding: 40rpx; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; border-top: 8rpx solid #7DB4D6;
+}
+.header-content.data-v-94c4d70d { text-align: center; width: 100%;
+}
+.title.data-v-94c4d70d { font-size: 48rpx; font-weight: 600;
+}
+.code-input-area.data-v-94c4d70d { margin-top: 80rpx; text-align: center;
+}
+.area-label.data-v-94c4d70d { font-size: 28rpx; color: #999;
+}
+.code-inputs.data-v-94c4d70d { display: flex; gap: 20rpx; margin-top: 30rpx; justify-content: center;
+}
+.code-inputs input.data-v-94c4d70d { width: 100rpx; height: 120rpx; border: 1px solid #ccc; border-radius: 12rpx; text-align: center; font-size: 48rpx; font-weight: bold;
+}
+.random-button.data-v-94c4d70d { background-color: transparent; color: #7DB4D6; border: none; margin-top: 40rpx; font-size: 28rpx; padding: 0; line-height: 1.5;
+}
+.random-button.data-v-94c4d70d::after { border: none;
+}
+.start-button.data-v-94c4d70d { width: 100%; height: 90rpx; line-height: 90rpx; background-color: #7DB4D6; color: #fff; border-radius: 45rpx; margin-top: 80rpx; font-size: 32rpx;
+}
+.start-button[disabled].data-v-94c4d70d { background-color: #ccc;
+}

+ 66 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.js

@@ -0,0 +1,66 @@
+// pages/checkin/teacher/number-setup/number-setup.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 4 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "",
+  "usingComponents": {}
+}

+ 2 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.wxml

@@ -0,0 +1,2 @@
+<!--pages/checkin/teacher/number-setup/number-setup.wxml-->
+<text>pages/checkin/teacher/number-setup/number-setup.wxml</text>

+ 1 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/number-setup/number-setup.wxss

@@ -0,0 +1 @@
+/* pages/checkin/teacher/number-setup/number-setup.wxss */

+ 76 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.js

@@ -0,0 +1,76 @@
+"use strict";
+const common_vendor = require("../../../common/vendor.js");
+const store_checkin = require("../../../store/checkin.js");
+if (!Math) {
+  (NavBar + TkiQrcode)();
+}
+const NavBar = () => "../../components/NavBar/NavBar.js";
+const TkiQrcode = () => "../../../components/tki-qrcode/tki-qrcode.js";
+const _sfc_main = {
+  __name: "qr-active",
+  setup(__props) {
+    const navBarRef = common_vendor.ref(null);
+    const navHeight = common_vendor.ref(88);
+    const contentWrapperStyle = common_vendor.computed(() => ({ paddingTop: `${navHeight.value}px` }));
+    common_vendor.onMounted(() => {
+      common_vendor.watch(() => {
+        var _a;
+        return (_a = navBarRef.value) == null ? void 0 : _a.totalNavHeight;
+      }, (val) => {
+        if (val)
+          navHeight.value = val;
+      }, { immediate: true });
+    });
+    const checkinStore = store_checkin.useCheckinStore();
+    const qrcodeRef = common_vendor.ref(null);
+    const qrVal = common_vendor.ref("");
+    const regenerateQrCode = () => {
+      const newCode = `checkin_qr_${Date.now()}`;
+      checkinStore.regenerateCode(newCode);
+      qrVal.value = newCode;
+      common_vendor.index.showToast({ title: "二维码已更新", icon: "success" });
+    };
+    common_vendor.onReady(() => {
+      if (checkinStore.session.isActive && checkinStore.session.code) {
+        qrVal.value = checkinStore.session.code;
+      } else {
+        common_vendor.index.showToast({ title: "签到已结束", icon: "none", duration: 1500, mask: true });
+        setTimeout(() => common_vendor.index.navigateBack(), 1500);
+      }
+    });
+    return (_ctx, _cache) => {
+      return common_vendor.e({
+        a: common_vendor.sr(navBarRef, "f8113518-0", {
+          "k": "navBarRef"
+        }),
+        b: common_vendor.p({
+          title: "二维码签到",
+          showBack: true
+        }),
+        c: qrVal.value
+      }, qrVal.value ? {
+        d: common_vendor.sr(qrcodeRef, "f8113518-1", {
+          "k": "qrcodeRef"
+        }),
+        e: common_vendor.p({
+          cid: "qrcode-canvas",
+          val: qrVal.value,
+          size: 180,
+          unit: "rpx",
+          loadMake: true,
+          onval: true,
+          background: "#ffffff",
+          foreground: "#000000"
+        })
+      } : {}, {
+        f: common_vendor.t(common_vendor.unref(checkinStore).checkedInCount),
+        g: common_vendor.t(common_vendor.unref(checkinStore).session.totalStudentCount),
+        h: common_vendor.o(regenerateQrCode),
+        i: common_vendor.s(contentWrapperStyle.value)
+      });
+    };
+  }
+};
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-f8113518"]]);
+wx.createPage(MiniProgramPage);
+//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/checkin/teacher/qr-active.js.map

+ 7 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.json

@@ -0,0 +1,7 @@
+{
+  "navigationBarTitleText": "",
+  "usingComponents": {
+    "nav-bar": "../../components/NavBar/NavBar",
+    "tki-qrcode": "../../../components/tki-qrcode/tki-qrcode"
+  }
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.wxml

@@ -0,0 +1 @@
+<view class="teacher-container data-v-f8113518"><nav-bar wx:if="{{b}}" class="r data-v-f8113518" u-r="navBarRef" u-i="f8113518-0" bind:__l="__l" u-p="{{b}}"/><view class="content-wrapper data-v-f8113518" style="{{i}}"><view class="teacher-card data-v-f8113518"><view class="header-content data-v-f8113518"><text class="title data-v-f8113518">二维码签到</text></view><view class="qr-code-area data-v-f8113518"><tki-qrcode wx:if="{{c}}" class="r data-v-f8113518" u-r="qrcodeRef" u-i="f8113518-1" bind:__l="__l" u-p="{{e}}"/><text class="tip data-v-f8113518">让学生扫描此码进行签到</text></view><view class="status-preview data-v-f8113518"><text class="data-v-f8113518">已签到: {{f}}/{{g}}</text></view><button class="update-button data-v-f8113518" bindtap="{{h}}">更新二维码</button></view></view></view>

+ 68 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/qr-active.wxss

@@ -0,0 +1,68 @@
+
+.teacher-container.data-v-f8113518 { 
+  min-height: 100vh; 
+  background-color: #f5f5f7;
+}
+.content-wrapper.data-v-f8113518 { 
+  padding: 20rpx 30rpx; 
+  box-sizing: border-box;
+}
+.teacher-card.data-v-f8113518 { 
+  width: 100%; 
+  background-color: #fff; 
+  border-radius: 20rpx; 
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); 
+  padding: 40rpx; 
+  box-sizing: border-box; 
+  display: flex; 
+  flex-direction: column; 
+  align-items: center; 
+  border-top: 8rpx solid #7DB4D6;
+}
+.header-content.data-v-f8113518 { 
+  text-align: center; 
+  width: 100%;
+}
+.title.data-v-f8113518 { 
+  font-size: 48rpx; 
+  font-weight: 600;
+}
+.qr-code-area.data-v-f8113518 { 
+  margin-top: 60rpx; 
+  text-align: center;
+  /* 为 tki-qrcode 组件预留空间 */
+  width: 400rpx;
+  height: 400rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+.qr-code-area .tip.data-v-f8113518 { 
+  display: block; 
+  margin-top: 20rpx; 
+  color: #999; 
+  font-size: 26rpx;
+}
+.status-preview.data-v-f8113518 { 
+  margin-top: 40rpx; 
+  background: #f5f5f7; 
+  padding: 15rpx 30rpx; 
+  border-radius: 30rpx; 
+  color: #666; 
+  font-size: 28rpx;
+}
+.update-button.data-v-f8113518 { 
+  width: 100%; 
+  height: 90rpx; 
+  line-height: 90rpx; 
+  background-color: #fff; 
+  color: #7DB4D6; 
+  border: 1px solid #7DB4D6; 
+  border-radius: 45rpx; 
+  margin-top: 60rpx; 
+  font-size: 32rpx;
+}
+.update-button.data-v-f8113518:active { 
+  background-color: #f0f8ff;
+}

+ 48 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.js

@@ -0,0 +1,48 @@
+"use strict";
+const common_vendor = require("../../../common/vendor.js");
+const store_checkin = require("../../../store/checkin.js");
+if (!Math) {
+  NavBar();
+}
+const NavBar = () => "../../components/NavBar/NavBar.js";
+const _sfc_main = {
+  __name: "select-method",
+  setup(__props) {
+    const navBarRef = common_vendor.ref(null);
+    const navHeight = common_vendor.ref(88);
+    const contentWrapperStyle = common_vendor.computed(() => ({ paddingTop: `${navHeight.value}px` }));
+    common_vendor.onMounted(() => {
+      common_vendor.watch(() => {
+        var _a;
+        return (_a = navBarRef.value) == null ? void 0 : _a.totalNavHeight;
+      }, (val) => {
+        if (val)
+          navHeight.value = val;
+      }, { immediate: true });
+    });
+    const checkinStore = store_checkin.useCheckinStore();
+    const navigateTo = (url) => common_vendor.index.navigateTo({ url });
+    const startQrCheckin = () => {
+      const code = `checkin_qr_${Date.now()}`;
+      checkinStore.startCheckin({ method: "qr", code });
+      common_vendor.index.redirectTo({ url: "/pages/checkin/teacher/qr-active" });
+    };
+    return (_ctx, _cache) => {
+      return {
+        a: common_vendor.sr(navBarRef, "4629ead4-0", {
+          "k": "navBarRef"
+        }),
+        b: common_vendor.p({
+          title: "选择签到方式",
+          showBack: true
+        }),
+        c: common_vendor.o(($event) => navigateTo("/pages/checkin/teacher/number-setup")),
+        d: common_vendor.o(startQrCheckin),
+        e: common_vendor.s(contentWrapperStyle.value)
+      };
+    };
+  }
+};
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-4629ead4"]]);
+wx.createPage(MiniProgramPage);
+//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/checkin/teacher/select-method.js.map

+ 6 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.json

@@ -0,0 +1,6 @@
+{
+  "navigationBarTitleText": "",
+  "usingComponents": {
+    "nav-bar": "../../components/NavBar/NavBar"
+  }
+}

+ 1 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.wxml

@@ -0,0 +1 @@
+<view class="teacher-container data-v-4629ead4"><nav-bar wx:if="{{b}}" class="r data-v-4629ead4" u-r="navBarRef" u-i="4629ead4-0" bind:__l="__l" u-p="{{b}}"/><view class="content-wrapper data-v-4629ead4" style="{{e}}"><view class="teacher-card data-v-4629ead4"><view class="header-content data-v-4629ead4"><text class="title data-v-4629ead4">选择签到方式</text><text class="subtitle data-v-4629ead4">请选择一种方式来开始课堂签到</text></view><view class="method-selection data-v-4629ead4"><button class="method-button data-v-4629ead4" bindtap="{{c}}"><view class="button-icon data-v-4629ead4">🔢</view><view class="button-text-content data-v-4629ead4"><text class="method-title data-v-4629ead4">数字签到</text><text class="method-desc data-v-4629ead4">学生通过输入4位数字码完成签到</text></view></button><button class="method-button data-v-4629ead4" bindtap="{{d}}"><view class="button-icon data-v-4629ead4">📱</view><view class="button-text-content data-v-4629ead4"><text class="method-title data-v-4629ead4">二维码签到</text><text class="method-desc data-v-4629ead4">系统生成动态二维码,学生扫码签到</text></view></button></view></view></view></view>

+ 103 - 0
unpackage/dist/dev/mp-weixin/pages/checkin/teacher/select-method.wxss

@@ -0,0 +1,103 @@
+
+/* 定义主题色变量,方便维护 */
+.data-v-4629ead4:root {
+  --theme-color: #7DB4D6;
+  --theme-color-dark: #6fa5c6; /* 用于点击状态的深色 */
+  --theme-color-shadow: rgba(125, 180, 214, 0.4);
+}
+.teacher-container.data-v-4629ead4 {
+  min-height: 100vh;
+  background-color: #f5f5f7;
+}
+.content-wrapper.data-v-4629ead4 {
+  padding: 20rpx 30rpx;
+  box-sizing: border-box;
+}
+.teacher-card.data-v-4629ead4 {
+  width: 100%;
+  background-color: #fff;
+  border-radius: 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  padding: 60rpx 40rpx; /* 增加垂直内边距 */
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  border-top: 8rpx solid var(--theme-color); /* 使用主题色 */
+}
+.header-content.data-v-4629ead4 {
+  text-align: center;
+  width: 100%;
+}
+.title.data-v-4629ead4 {
+  font-size: 48rpx;
+  font-weight: 600;
+  color: #333;
+}
+.subtitle.data-v-4629ead4 {
+  display: block;
+  font-size: 28rpx;
+  color: #999;
+  margin-top: 16rpx;
+}
+.method-selection.data-v-4629ead4 {
+  display: flex;
+  flex-direction: column;
+  gap: 40rpx; /* 按钮之间的间距 */
+  width: 100%;
+  margin-top: 80rpx; /* 与标题的间距 */
+}
+
+/* --- 按钮美化核心区域 --- */
+.method-button.data-v-4629ead4 {
+  display: flex; /* 使用 Flexbox 布局 */
+  align-items: center; /* 垂直居中对齐图标和文字 */
+  gap: 30rpx; /* 图标和文字之间的间距 */
+  
+  width: 100%; /* 按钮宽度撑满容器 */
+  padding: 40rpx; /* 增大内边距,让按钮更大气 */
+  border-radius: 24rpx; /* 更圆润的边角 */
+  text-align: left;
+  line-height: 1.5;
+  
+  color: #fff; /* 默认文字颜色 */
+  background-color: var(--theme-color); /* 使用主题色作为背景 */
+  border: none; /* 移除边框 */
+  
+  /* 添加精致的阴影效果 */
+  box-shadow: 0 8rpx 20rpx -5rpx var(--theme-color-shadow);
+  
+  /* 添加过渡效果,让交互更平滑 */
+  transition: all 0.2s ease-in-out;
+}
+
+/* 移除 uni-app 按钮默认样式 */
+.method-button.data-v-4629ead4::after {
+  border: none;
+}
+
+/* 按钮点击时的动态效果 */
+.method-button.data-v-4629ead4:active {
+  background-color: var(--theme-color-dark); /* 背景色变深 */
+  transform: scale(0.98); /* 轻微缩小,产生按压感 */
+  box-shadow: 0 4rpx 10rpx -3rpx var(--theme-color-shadow); /* 阴影变小 */
+}
+.button-icon.data-v-4629ead4 {
+  font-size: 52rpx; /* 图标尺寸 */
+}
+.button-text-content.data-v-4629ead4 {
+  display: flex;
+  flex-direction: column;
+}
+.method-title.data-v-4629ead4 {
+  font-size: 36rpx; /* 增大标题字号 */
+  font-weight: 600; /* 加粗 */
+  color: #fff;
+  display: block;
+}
+.method-desc.data-v-4629ead4 {
+  font-size: 26rpx;
+  color: #ffffffb3; /* 使用带透明度的白色,更有层次感 */
+  display: block;
+  margin-top: 8rpx;
+}

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/components/About/AboutModal/AboutModal.js

@@ -1,6 +1,6 @@
 "use strict";
 const common_vendor = require("../../../../common/vendor.js");
-const teacherUrl = "https://cs.nju.edu.cn/liuqiu";
+const teacherUrl = "https://software.nju.edu.cn//qinliu/index.html";
 const _sfc_main = {
   __name: "AboutModal",
   props: {

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/profile/index.js

@@ -118,7 +118,7 @@ const _sfc_main = {
       editBirthday.value = dateStr;
       userStore.setUserInfo({ birthday: dateStr });
     }
-    const roleList = ["学生", "教师", "管理员", "职工"];
+    const roleList = ["学生", "教师"];
     const roleIndex = common_vendor.ref(0);
     common_vendor.onMounted(() => {
       setRolePickerIndex();

+ 16 - 2
unpackage/dist/dev/mp-weixin/project.private.config.json

@@ -3,12 +3,26 @@
     "miniprogram": {
       "list": [
         {
-          "name": "pages/welcome/index",
-          "pathName": "pages/welcome/index",
+          "name": "pages/checkin/teacher/index",
+          "pathName": "pages/checkin/teacher/index",
           "query": "",
           "scene": null,
           "launchMode": "default"
         },
+        {
+          "name": "pages/checkin/index",
+          "pathName": "pages/checkin/index",
+          "query": "",
+          "launchMode": "default",
+          "scene": null
+        },
+        {
+          "name": "pages/welcome/index",
+          "pathName": "pages/welcome/index",
+          "query": "",
+          "launchMode": "default",
+          "scene": null
+        },
         {
           "name": "pages/index/index",
           "pathName": "pages/index/index",

+ 63 - 0
unpackage/dist/dev/mp-weixin/store/checkin.js

@@ -0,0 +1,63 @@
+"use strict";
+const common_vendor = require("../common/vendor.js");
+const defaultSession = {
+  isActive: false,
+  method: "none",
+  code: "",
+  classes: [],
+  startTime: null,
+  endTime: null,
+  checkedInStudents: [],
+  totalStudentCount: 48
+  // 模拟一个班级总人数
+};
+const useCheckinStore = common_vendor.defineStore("checkin", () => {
+  const session = common_vendor.ref({ ...defaultSession });
+  const checkedInCount = common_vendor.computed(() => session.value.checkedInStudents.length);
+  const isStudentCheckedIn = (studentId) => {
+    return session.value.checkedInStudents.some((s) => s.id === studentId);
+  };
+  const startCheckin = (config) => {
+    session.value.isActive = true;
+    session.value.method = config.method;
+    session.value.code = config.code;
+    session.value.startTime = Date.now();
+    session.value.endTime = Date.now() + 5 * 60 * 1e3;
+    session.value.checkedInStudents = [];
+  };
+  const regenerateCode = (newCode) => {
+    if (session.value.isActive) {
+      session.value.code = newCode;
+    }
+  };
+  const endCheckin = () => {
+    session.value = { ...defaultSession };
+  };
+  const attemptCheckin = (student, inputCode) => {
+    if (!session.value.isActive) {
+      return { success: false, message: "签到尚未开始" };
+    }
+    if (isStudentCheckedIn(student.id)) {
+      return { success: false, message: "您已签到,请勿重复操作" };
+    }
+    if (session.value.method === "number" && inputCode !== session.value.code) {
+      return { success: false, message: "签到码错误" };
+    }
+    session.value.checkedInStudents.push({ ...student, time: Date.now() });
+    return { success: true, message: "签到成功!" };
+  };
+  return {
+    session,
+    checkedInCount,
+    isStudentCheckedIn,
+    startCheckin,
+    regenerateCode,
+    endCheckin,
+    attemptCheckin
+  };
+}, {
+  // 推荐:为了让签到状态在小程序关闭后也能保留(模拟真实场景),可以开启持久化
+  persist: true
+});
+exports.useCheckinStore = useCheckinStore;
+//# sourceMappingURL=../../.sourcemap/mp-weixin/store/checkin.js.map

Some files were not shown because too many files changed in this diff