diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..4b2d34a6be5e0c686841fd50797c220c34b31897 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +**/node_modules +public +frontend/dist +frontend/node_modules +.vulcan +.vulcan-error-reporter.js +*.log +.git diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 8b69c9f686d2ee33442940a158bc8a10de069dc2..0000000000000000000000000000000000000000 --- a/.gitattributes +++ /dev/null @@ -1,11 +0,0 @@ -*.mp4 filter=lfs diff=lfs merge=lfs -text -*.MP4 filter=lfs diff=lfs merge=lfs -text -*.mov filter=lfs diff=lfs merge=lfs -text -*.MOV filter=lfs diff=lfs merge=lfs -text -*.mp3 filter=lfs diff=lfs merge=lfs -text -*.png filter=lfs diff=lfs merge=lfs -text -*.PNG filter=lfs diff=lfs merge=lfs -text -*.jpg filter=lfs diff=lfs merge=lfs -text -*.webp filter=lfs diff=lfs merge=lfs -text -*.HEIC filter=lfs diff=lfs merge=lfs -text -BeadQR/docs/source-photo.jpeg filter=lfs diff=lfs merge=lfs -text diff --git a/BeadQR/README.md b/BeadQR/README.md deleted file mode 100644 index 58e1c1be66ddca4b41d2a437f4899b49d3837beb..0000000000000000000000000000000000000000 --- a/BeadQR/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# 豆维 · 可扫码的拼豆 - -输入一句话(可以再选一张照片),生成**用微信扫一扫就能读出内容**的 MARD 拼豆图纸,并支持导出 PDF 或图片。 - -## 本版重点 - -- **一眼看懂**:首屏直接说明用途,页面按“写一句话 → 选张照片 → 导出图纸”三步走,打开就能看到示例效果。 -- **更快**:配色查表加速,色格数据不再走页面数据通道;PDF 改为矢量直出(毫秒级,文件更小);导出时不再重复绘制。 -- **更好看、更好扫**: - - 感知更准的配色(OKLab) - - 照片按面积取平均色,不再有锯齿 - - 相邻色块自动平滑,零散颜色自动合并,所需豆子种类更少 - - 二维码定位图形强制高对比 - - 本机扫码自检:扫不出时一键优化 -- **打印友好**:PDF 为 A4 矢量图纸,大作品会自动分块并附总览页;每一格都印有色号,另附带用量的清单。 -- **精选色板**:除标准 221 色和完整 291 色外,还提供 144/96/72/48/24 色精选套装,方便少买豆。 - -## 内容安全链路(按微信平台要求) - -| 环节 | 措施 | -| --- | --- | -| 二维码文字、扫码导入的文字、水印 | 云函数调用 `security.msgSecCheck` v2(带 openid、按场景值检测),结果为 risky 或 review 均拦截 | -| 照片 | 选图后立即压缩到官方推荐的 750×1334 以内,由云函数调用 `security.imgSecCheck`;**检测通过前不能应用照片** | -| 检测异常 | 一律视为未通过,不允许生成与导出(fail-closed) | -| 导出前 | 再次校验全部文本(已通过的走缓存,不增加等待) | -| 云函数加固 | 必须有 OPENID;只接受 `sec-check/` 目录下的 jpg;通过文件头判断格式;限制 1MB;检测后立即删除云端临时文件 | -| 隐私 | 已开启 `__usePrivacyCheck__`,内置隐私授权弹窗;页脚设有“隐私保护指引”入口 | -| 投诉与反馈 | 页脚提供“内容规范”说明及 `open-type="feedback"` 的投诉反馈入口 | -| 分享 | 分享出去的只是小程序卡片,不带用户内容 | - -## 部署步骤 - -1. 用微信开发者工具打开本目录(appid 已配置)。 -2. 右键 `cloudfunctions/quickstartFunctions` →「上传并部署:云端安装依赖」。**本版云函数有改动,必须重新部署。** -3. 在云开发控制台 → 存储中确认可以写入 `sec-check/` 目录(默认权限即可)。 -4. 在小程序管理后台 →「设置 → 服务内容声明 → 用户隐私保护指引」中声明:选择图片/拍照(`chooseMedia`)、保存到相册(`saveImageToPhotosAlbum`)、扫码(`scanCode`)。 -5. 真机测试:正常/违规文字、正常/违规图片、断网时的检测异常,确认都会被正确拦截和提示。 - -## 说明 - -- 为保证扫码成功率,**二维码默认保留 1 格白边**。如需原来的“无白边”效果,可在「高级设置 → 白边」中选择“无”。 -- 圆豆、中空豆样式只影响图纸的显示效果,实际拼出来的位置与方豆完全一致。 -- 第三方代码:`utils/qrcore`(node-qrcode,MIT)、`utils/vendor/jsqr.js`(jsQR,Apache-2.0)。 - -## 首屏演示动画 & 宣传视频 - -- 首次打开(还没写内容)时,预览区播放 12 秒 1:1 演示动画:写一句话 → 编成二维码 → 融进照片 → 微信一扫 → 结尾卡片。 -- 动画由 `miniprogram/utils/intro.js` 纯代码实时绘制,**不含任何视频/图片素材,几乎不占包体积**(约 20KB)。演示作品由真实配色引擎生成,画面里的二维码真的能扫出 “Happy Birthday!”。 -- 滑出屏幕、切到后台、打开裁剪时自动暂停;点一下预览区重新播放;写下内容(或选了照片)后自动切回真实二维码。 -- `docs/promo/` 里是用同一套代码渲染的宣传视频(1080 / 720,含原创合成配乐)和封面图,可直接发公众号。该目录不在 `miniprogram/` 内,不计入小程序包体积。 diff --git a/BeadQR/cloudfunctions/quickstartFunctions/config.json b/BeadQR/cloudfunctions/quickstartFunctions/config.json deleted file mode 100644 index a1d2eac6ed64d91b41f32dd6a796a2fb91328985..0000000000000000000000000000000000000000 --- a/BeadQR/cloudfunctions/quickstartFunctions/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "timeout": 20, - "permissions": { - "openapi": [ - "security.msgSecCheck", - "security.imgSecCheck" - ] - } -} diff --git a/BeadQR/cloudfunctions/quickstartFunctions/index.js b/BeadQR/cloudfunctions/quickstartFunctions/index.js deleted file mode 100644 index 24bdb9b2b2dba5b4e592e6b61c46ec3124794dd7..0000000000000000000000000000000000000000 --- a/BeadQR/cloudfunctions/quickstartFunctions/index.js +++ /dev/null @@ -1,92 +0,0 @@ -// 豆维 · 内容安全云函数 -// type: checkMessage | checkMessages | checkImage -// 返回约定:{ risk: boolean, skipped?: boolean };skipped 表示检测未能完成,前端必须拦截 -const cloud = require("wx-server-sdk"); -cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); - -const MAX_TEXT = 2500; -const MAX_IMAGE_BYTES = 1024 * 1024; -const MAX_BATCH = 5; - -const extractErrCode = (error) => { - if (!error) return undefined; - if (typeof error.errCode === "number") return error.errCode; - if (typeof error.code === "number") return error.code; - const match = typeof error.errMsg === "string" ? error.errMsg.match(/-?\d{3,}/) : null; - return match ? Number(match[0]) : undefined; -}; - -// msgSecCheck v2:scene 1 资料 / 2 评论 / 3 论坛 / 4 社交日志 -async function checkOneMessage(content, scene, openid) { - const value = String(content || "").slice(0, MAX_TEXT); - if (!value.trim()) return { risk: false }; - const safeScene = [1, 2, 3, 4].includes(Number(scene)) ? Number(scene) : 2; - try { - const response = await cloud.openapi.security.msgSecCheck({ - version: 2, - scene: safeScene, - openid, - content: value, - }); - const suggest = response && response.result && response.result.suggest; - if (!suggest) return { risk: false, skipped: true }; - // review(疑似)按违规处理,宁严勿松 - return { risk: suggest === "risky" || suggest === "review", verdict: suggest }; - } catch (e) { - if (extractErrCode(e) === 87014) return { risk: true, verdict: "risky" }; - return { risk: false, skipped: true, errCode: extractErrCode(e) }; - } -} - -// 通过文件头判断真实格式,不信任客户端传入的类型 -function sniffType(buffer) { - if (buffer.length > 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return "image/jpeg"; - if (buffer.length > 8 && buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) return "image/png"; - return ""; -} - -async function checkImage(fileID) { - // 只允许检测本小程序上传到 sec-check/ 目录的临时文件,防止借接口删除/读取其他文件 - if (typeof fileID !== "string" || !/^cloud:\/\/[^/]+\/sec-check\/[\w.-]+\.jpg$/.test(fileID)) { - return { risk: false, skipped: true, reason: "INVALID_FILE" }; - } - try { - const download = await cloud.downloadFile({ fileID }); - const buffer = download && download.fileContent; - const contentType = buffer ? sniffType(buffer) : ""; - if (!buffer || !buffer.length || buffer.length > MAX_IMAGE_BYTES || !contentType) { - return { risk: false, skipped: true, reason: "BAD_IMAGE" }; - } - await cloud.openapi.security.imgSecCheck({ - media: { contentType, value: buffer }, - }); - return { risk: false }; - } catch (e) { - if (extractErrCode(e) === 87014) return { risk: true, verdict: "risky" }; - return { risk: false, skipped: true, errCode: extractErrCode(e) }; - } finally { - try { - await cloud.deleteFile({ fileList: [fileID] }); - } catch (e) { - // 清理失败不影响检测结果 - } - } -} - -exports.main = async (event) => { - const { OPENID } = cloud.getWXContext(); - if (!OPENID) return { risk: false, skipped: true, reason: "NO_OPENID" }; - const type = event && event.type; - if (type === "checkMessage") { - return checkOneMessage(event.content, event.scene, OPENID); - } - if (type === "checkMessages") { - const items = Array.isArray(event.items) ? event.items.slice(0, MAX_BATCH) : []; - const results = await Promise.all(items.map((it) => checkOneMessage(it && it.value, it && it.scene, OPENID))); - return { results }; - } - if (type === "checkImage") { - return checkImage(event.fileID); - } - return { risk: false, skipped: true, reason: "UNKNOWN_TYPE" }; -}; diff --git a/BeadQR/cloudfunctions/quickstartFunctions/package-lock.json b/BeadQR/cloudfunctions/quickstartFunctions/package-lock.json deleted file mode 100644 index 26d0d9d6e69b043888fa78b3e1a003670970cf1c..0000000000000000000000000000000000000000 --- a/BeadQR/cloudfunctions/quickstartFunctions/package-lock.json +++ /dev/null @@ -1,1157 +0,0 @@ -{ - "name": "quickstartFunctions", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "quickstartFunctions", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "wx-server-sdk": "~4.0.2" - } - }, - "node_modules/@cloudbase/adapter-interface": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@cloudbase/adapter-interface/-/adapter-interface-0.7.1.tgz", - "integrity": "sha512-BjHTAvIMSLxbp26SeT6qLiSSBygz3kAVno/PMwwBwjgzVkVUvuZGVcvf1mhHxY/BjdRGLrd6X60OgvymV/aAUw==", - "license": "ISC" - }, - "node_modules/@cloudbase/adapter-wx_mp": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@cloudbase/adapter-wx_mp/-/adapter-wx_mp-1.3.1.tgz", - "integrity": "sha512-rS0N1aD1a0zc421ds17/HzM4RChQ6T2ixMz87bhPJ1yveqJt/KUqYxiXJDWhZ803wwTmJs8aqNkgugsNusNhbg==", - "license": "ISC", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.0", - "web-streams-polyfill": "^4.0.0" - } - }, - "node_modules/@cloudbase/ai": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/@cloudbase/ai/-/ai-2.30.0.tgz", - "integrity": "sha512-RtBux47omXWcMhiiDDVJ96d2JlqnQ/bzKC6GG3ROgupYG3ReQyv1IssCpg5cnzmZ+l651IqdOpt27VTuO3kEGQ==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/types": "2.30.0", - "@cloudbase/utilities": "2.30.0", - "@mattiasbuelens/web-streams-adapter": "^0.1.0", - "text-encoding-shim": "^1.0.5", - "web-streams-polyfill": "^4.0.0" - } - }, - "node_modules/@cloudbase/ai/node_modules/@cloudbase/types": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/@cloudbase/types/-/types-2.30.0.tgz", - "integrity": "sha512-tVapfLLDA1MlmG2OwC5eq5eSra9l3EgpeJnx1kNR4CGh8zQRrVvFRyaRLo4BrzY0p/5xqX3iJliRmcASF7Ip7g==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.1" - } - }, - "node_modules/@cloudbase/app": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/@cloudbase/app/-/app-2.32.0.tgz", - "integrity": "sha512-78+eSo8OLO4iUiifdynZo4dK4wNlyaz5x9423muSibyl4FLwt5/Xt0t9s7StFmGaBdvB75UYM9dGWxOdIFRzaw==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.1", - "@cloudbase/adapter-wx_mp": "^1.3.1", - "@cloudbase/types": "2.32.0", - "@cloudbase/utilities": "2.32.0" - }, - "peerDependencies": { - "@cloudbase/signature-nodejs": ">=1.0.0", - "jsonwebtoken": ">=8.0.0", - "ws": "^8.18.0" - }, - "peerDependenciesMeta": { - "@cloudbase/signature-nodejs": { - "optional": true - }, - "jsonwebtoken": { - "optional": true - }, - "ws": { - "optional": true - } - } - }, - "node_modules/@cloudbase/app/node_modules/@cloudbase/utilities": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/@cloudbase/utilities/-/utilities-2.32.0.tgz", - "integrity": "sha512-vYrr1aXOpU+/IrkHMSTxi/k2d4N8raNu+E3IrKtWt4w7Y0Ke3DLvCHeUJcuWECvQBVhzmg1ert4DcXVUymSKlg==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.1", - "@cloudbase/types": "2.32.0", - "jwt-decode": "^3.1.2" - } - }, - "node_modules/@cloudbase/database": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@cloudbase/database/-/database-1.4.3.tgz", - "integrity": "sha512-JzmdsGjy9LwzSQQ0Fv4OlNHNK61BRXdJSembTpy4408AQA3q2Ip3N5eB3v9OpAlFMvP/6MGmOQ/ZuUPKfJddJA==", - "license": "ISC", - "dependencies": { - "bson": "^4.0.3", - "lodash.clonedeep": "4.5.0", - "lodash.set": "4.3.2", - "lodash.unset": "4.5.2" - } - }, - "node_modules/@cloudbase/node-sdk": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@cloudbase/node-sdk/-/node-sdk-3.17.2.tgz", - "integrity": "sha512-74ekvRsdmeGch9hFHkQnjHPTEfk8k09iQFvI1aAo48ZsbLqK3czqj/3GoVpSkw5+eYe05tZV13Ct/kWiV5tuGg==", - "license": "MIT", - "dependencies": { - "@cloudbase/ai": "^2.23.0", - "@cloudbase/app": "^2.23.0", - "@cloudbase/database": "1.4.3", - "@cloudbase/signature-nodejs": "2.2.0", - "@cloudbase/types": "^2.23.0", - "@cloudbase/wx-cloud-client-sdk": "1.7.1", - "agentkeepalive": "^4.3.0", - "axios": "0.27.2", - "form-data": "^4.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "jsonwebtoken": "^9.0.2", - "retry": "^0.13.1", - "web-streams-polyfill": "^4.2.0", - "xml2js": "^0.6.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cloudbase/signature-nodejs": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@cloudbase/signature-nodejs/-/signature-nodejs-2.2.0.tgz", - "integrity": "sha512-2y2rieOpJNnBA2MW7sLJKek3iFOjgr8TNicw5qjBkcdVczLR0cFzxMhip75NLCwarFTIAONE3oeNxoQ19eVHcA==", - "dependencies": { - "@types/clone": "^2.1.0", - "clone": "^2.1.2", - "is-stream": "^2.0.0", - "url": "^0.11.0" - } - }, - "node_modules/@cloudbase/types": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/@cloudbase/types/-/types-2.32.0.tgz", - "integrity": "sha512-HPsMEHHt5SAyD3tpv/C/v2nYudSawE48ixjK5Y9w3R/82auJag0AUsZGfCExgdPa4Zud9OvGx5127ssEXLgadw==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.1" - } - }, - "node_modules/@cloudbase/utilities": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/@cloudbase/utilities/-/utilities-2.30.0.tgz", - "integrity": "sha512-52DfQH5qgfOtrwj26KCGw6cz0pUjuJludJ7fccUKCzBtn/QwPudwtXo7gOUTtKKDubW3+VUCjjBm7UiDwFu+ag==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.1", - "@cloudbase/types": "2.30.0", - "jwt-decode": "^3.1.2" - } - }, - "node_modules/@cloudbase/utilities/node_modules/@cloudbase/types": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/@cloudbase/types/-/types-2.30.0.tgz", - "integrity": "sha512-tVapfLLDA1MlmG2OwC5eq5eSra9l3EgpeJnx1kNR4CGh8zQRrVvFRyaRLo4BrzY0p/5xqX3iJliRmcASF7Ip7g==", - "license": "Apache-2.0", - "dependencies": { - "@cloudbase/adapter-interface": "^0.7.1" - } - }, - "node_modules/@cloudbase/wx-cloud-client-sdk": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@cloudbase/wx-cloud-client-sdk/-/wx-cloud-client-sdk-1.7.1.tgz", - "integrity": "sha512-NzLOoYDAeoRh2lRjCtvr6yXu4KPVHqZtPxyv70e+3b2+dZIDE5S5gtU8daE7xS5u4TwPqKB/E2vmgRTYOsCS9w==", - "dependencies": { - "core-js-pure": "^3.45.0" - } - }, - "node_modules/@mattiasbuelens/web-streams-adapter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@mattiasbuelens/web-streams-adapter/-/web-streams-adapter-0.1.0.tgz", - "integrity": "sha512-oV4PyZfwJNtmFWhvlJLqYIX1Nn22ML8FZpS16ZUKv0hg7414xV1fjsGqxQzLT2dyK92TKxsJSwMOd7VNHAtPmA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", - "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", - "license": "BSD-3-Clause" - }, - "node_modules/@tootallnate/once": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", - "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/clone": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/clone/-/clone-2.1.4.tgz", - "integrity": "sha512-NKRWaEGaVGVLnGLB2GazvDaZnyweW9FJLLFL5LhywGJB3aqGMT9R/EUoJoSRP4nzofYnZysuDmrEJtJdAqUOtQ==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.3.0.tgz", - "integrity": "sha512-L3fgrnchriRC2ExBflb8j4uZZURHZfQsmQeyVzhjcHW4kkwVyo8/0h1B2MVzMTrYUJYu6G7EWs14hW/L9putqw==", - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "license": "Apache-2.0", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/core-js-pure": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.50.0.tgz", - "integrity": "sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==", - "hasInstallScript": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==", - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==", - "license": "MIT" - }, - "node_modules/lodash.unset": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.5.2.tgz", - "integrity": "sha512-bwKX88k2JhCV9D1vtE8+naDKlLiGrSmf8zi/Y9ivFHwbmRfA8RxS/aVJ+sIht2XOwqoNr4xUPUkGZpc1sHFEKg==", - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/protobufjs": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", - "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", - "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/text-encoding-shim": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/text-encoding-shim/-/text-encoding-shim-1.0.5.tgz", - "integrity": "sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "license": "MIT" - }, - "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", - "license": "MIT", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.12.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/web-streams-polyfill": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.3.0.tgz", - "integrity": "sha512-/Gnggvj9oSrEvJbDyyPtAnxBt5fGQM2iWOKQNu7ie1OxDgK40iZpyV3TKaRiEzVj1oA1UxKnEy9XPXh6PW3eVw==", - "license": "MIT", - "workspaces": [ - "test/benchmark-test", - "test/rollup-test", - "test/webpack-test" - ], - "engines": { - "node": ">= 8" - } - }, - "node_modules/wx-server-sdk": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/wx-server-sdk/-/wx-server-sdk-4.0.2.tgz", - "integrity": "sha512-dgpgWxYvOFWnQdGOb78sU+uQGELoEQSKzmJ7m8Y6eTCJKsfkdIHLMhRrdN5S5oDfAqr8Pc83jZKQeNRMZASIcg==", - "license": "MIT", - "dependencies": { - "@cloudbase/node-sdk": "3.17.2", - "json-bigint": "^1.0.0", - "protobufjs": "^7.2.4", - "tslib": "^1.9.3" - } - }, - "node_modules/xml2js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - } - } -} diff --git a/BeadQR/cloudfunctions/quickstartFunctions/package.json b/BeadQR/cloudfunctions/quickstartFunctions/package.json deleted file mode 100644 index 63727cda9c2e7d038ad78f36ae111268eadd9258..0000000000000000000000000000000000000000 --- a/BeadQR/cloudfunctions/quickstartFunctions/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "quickstartFunctions", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "wx-server-sdk": "~4.0.2" - } -} diff --git a/BeadQR/docs/promo/douwei-1080-cover.png b/BeadQR/docs/promo/douwei-1080-cover.png deleted file mode 100644 index f08e99fd0f86d17f8467e6b059637e3b989fc1ec..0000000000000000000000000000000000000000 --- a/BeadQR/docs/promo/douwei-1080-cover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aeac46fce4028cbfc2d7bae0dad427ed5f67262582ad411a00ccb2db65f8f03b -size 319823 diff --git a/BeadQR/docs/promo/douwei-1080.mp4 b/BeadQR/docs/promo/douwei-1080.mp4 deleted file mode 100644 index 8ad3a7bd6f7099c1305b7b42b367c9c7db85cdbb..0000000000000000000000000000000000000000 --- a/BeadQR/docs/promo/douwei-1080.mp4 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89ba1505c4ce4aa36316841c497dc96b8997bac534bb534426c23ca96ce87dcc -size 3693122 diff --git a/BeadQR/docs/promo/douwei-720-cover.png b/BeadQR/docs/promo/douwei-720-cover.png deleted file mode 100644 index ccaf575a9b83c1b8b0eb3255f0e3a37673424a67..0000000000000000000000000000000000000000 --- a/BeadQR/docs/promo/douwei-720-cover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bdb68c450700c1e65492e708440a29d07c9288606ca0ad7b464133052855fb2a -size 195403 diff --git a/BeadQR/docs/promo/douwei-720.mp4 b/BeadQR/docs/promo/douwei-720.mp4 deleted file mode 100644 index b95f5525b899e8a0144b1b7da6234149348de007..0000000000000000000000000000000000000000 --- a/BeadQR/docs/promo/douwei-720.mp4 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:626ea04f449f33fe29c4ec6c4aa94a2db88600cababab4f5a11b74155b4eee4e -size 1650037 diff --git a/BeadQR/docs/source-photo.jpeg b/BeadQR/docs/source-photo.jpeg deleted file mode 100644 index a1c44940c051799c8db7d283a3939078a54d52ba..0000000000000000000000000000000000000000 --- a/BeadQR/docs/source-photo.jpeg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ceae6708b13c1c7863c9a73b51f5eb775abcfe6f667b1c86ea993c8fe26995a8 -size 204431 diff --git a/BeadQR/docs/video-script.md b/BeadQR/docs/video-script.md deleted file mode 100644 index bc42e2b8af7f963da3dc1c90067b2aa461061fcc..0000000000000000000000000000000000000000 --- a/BeadQR/docs/video-script.md +++ /dev/null @@ -1,116 +0,0 @@ -# 豆维 · 视频文案与分镜脚本 - -> 目标:用一个短视频介绍「豆维 · 可扫码的拼豆」小程序,让观众理解它是什么、为什么有趣、怎么做出来。 -> 结构:提出想法 → 实现想法 → 制作例子 → 完成例子。 - ---- - -## 一、视频定位与调性 - -| 维度 | 建议 | -|------|------| -| 时长 | 主版本 60–90 秒(B站 / 朋友圈 / 公众号),另附 15–30 秒短视频版 | -| 调性 | 温暖手作 × 科技惊喜,不是冷冰冰的产品演示 | -| 核心情绪 | 「拼豆不止是一幅画,它是一封能被读到的信」 | -| 目标观众 | 拼豆爱好者、手工创作者、喜欢送独特礼物的人 | -| 画面风格 | 暖色调,木桌面,自然光;手机屏幕录制与实物特写交替出现 | - ---- - -## 二、核心文案(Slogan / 一句话介绍) - -以下任选一条作为标题或结尾定版字: - -1. **豆维 · 可扫码的拼豆** -2. **每一颗珠子,都藏着一句话。** -3. **拼出来的二维码,扫出来的心意。** -4. **把想说的话,拼进一幅画里。** -5. **看得见,也读得到。** - -推荐主标题:**看得见,也读得到。——豆维,可扫码的拼豆** - ---- - -## 三、主版本分镜脚本(60–90 秒) - -> 旁白以第一人称「我」叙述,语气像朋友分享,不念广告稿。 -> 「画面」列标注了你需要准备的素材类型;如果你已有类似镜头,直接对应即可。 - -| 幕 | 时间 | 画面 | 旁白 / 字幕 | 剪辑提示 | -|----|------|------|-------------|----------| -| **第一幕:提出想法** | 0:00–0:08 | 桌面上摆着几幅拼豆作品,阳光照进来。手拿起其中一幅,翻到背面,又翻回来。 | **旁白:** 我做了很多拼豆作品,挂在墙上很好看。但我总在想——它能不能,不止是一幅画? | 慢推镜头,配温暖的钢琴或指弹吉他。最后一帧画面停在作品特写。 | -| | 0:08–0:14 | 手拿起手机对准拼豆作品「扫一扫」——屏幕弹出一条消息或链接。 | **旁白:** 能不能扫一下,就能看到我想说的话? | 这是全片第一处「惊喜时刻」,音效清脆,屏幕内容要清晰可见。 | -| **第二幕:实现想法** | 0:14–0:24 | 切到手机屏幕录屏:打开豆维小程序,输入一段文字。 | **旁白:** 所以我做了「豆维」——一个可扫码的拼豆图纸生成器。输入一段话,或者一个链接。 | 屏幕录制加速 2–3 倍,打字过程快放。 | -| | 0:24–0:34 | 录屏:导入一张照片,裁切、去背景。 | **旁白:** 再选一张你喜欢的照片。它会把照片和二维码,合成一幅完整的拼豆图纸。 | 展示裁切弹窗操作,去背景效果前后对比。 | -| | 0:34–0:44 | 录屏:拖动滑块调整二维码大小和位置,切换「丰富 / 平衡 / 稳定」模式。 | **旁白:** 二维码可以放大缩小、移到任何位置。颜色会自动匹配 221 色的拼豆色库,还能选择更注重美观、还是更保证能扫出来。 | 滑块操作用慢动作展示,让观众看到图案实时变化。 | -| **第三幕:制作例子** | 0:44–0:52 | 导出的图纸打印出来,摆在桌上。开始一颗一颗摆珠子。 | **旁白:** 图纸保存下来,打印出来,就可以开始拼了。 | 延时摄影或快剪,展示摆珠过程,保留几秒真实速度的特写。 | -| | 0:52–0:58 | 熨斗烫过,拼豆板冷却,掀开,成品完成。 | **旁白:** 熨好,冷却,掀开—— | 掀开那一刻音效「叮」,画面干净利落。 | -| **第四幕:完成例子** | 0:58–1:10 | 手持成品对镜头展示,然后用手机扫描。屏幕上弹出一开始输入的那段话。 | **旁白:** 它不只是一幅画。你把手机对准它——就能读到里面藏着的话。 | 这是全片情绪最高点。扫描过程用真实速度,屏幕内容要清楚。 | -| | 1:10–1:20 | 多个成品排列展示(不同图案、不同颜色),最后画面淡出到品牌 Logo 和小程序码。 | **旁白:** 豆维,可扫码的拼豆。看得见,也读得到。 | 结尾定格 3 秒,放小程序码,方便观众扫码体验。 | - ---- - -## 四、15 秒短视频版(抖音 / 视频号快闪) - -| 时间 | 画面 | 字幕(无旁白,纯字幕流) | -|------|------|--------------------------| -| 0–3s | 手拿起拼豆作品 → 手机扫描 → 屏幕弹出消息 | 拼豆里,藏着一条消息 | -| 3–8s | 快剪:输入文字 → 导入照片 → 图纸生成 → 摆珠 | 豆维:输入一句话,选一张图,生成拼豆图纸 | -| 8–13s | 熨烫 → 掀开 → 成品再扫一次 | 拼好,烫平,扫一下—— | -| 13–15s | 品牌 Logo + 小程序码 | 消息就出来了。豆维 · 可扫码的拼豆 | - -> 快闪版不需要旁白,靠音乐节奏 + 字幕推进。音乐选节奏感强的电子或 Lo-Fi。 - ---- - -## 五、30 秒版本(朋友圈 / 公众号内嵌) - -节奏比 15 秒版稍慢,保留 4 个关键节点: - -1. **悬念**(5s):拿起作品 → 扫码 → 弹出内容 -2. **怎么做**(10s):小程序操作快剪(输入 → 导图 → 调整 → 导出) -3. **手作过程**(8s):摆珠延时 + 熨烫 + 掀开 -4. **结尾**(7s):成品特写 + 再扫一次 + 定版 - -配乐:轻快指弹吉他或 Lo-Fi,音量压低,让旁白清晰。 - ---- - -## 六、素材清单 - -拍摄前对照这份清单,确认已拍到以下镜头(√ 为已有,× 为待补): - -| 素材 | 用途 | 状态 | -|------|------|------| -| 多幅完成的拼豆作品摆拍(正面 / 斜角 / 背面) | 开头悬念、结尾展示 | ☐ | -| 手机扫描拼豆作品并弹出内容的实拍 | 全片核心「惊喜时刻」,需拍 2–3 次 | ☐ | -| 豆维小程序屏幕录制:输入文字、导入图片、裁切、调 QR 位置、切模式 | 第二幕 | ☐ | -| 图纸导出 / 打印的镜头 | 第三幕开头 | ☐ | -| 摆珠延时或快剪素材 | 第三幕 | ☐ | -| 熨斗烫拼豆 + 掀开的特写 | 第三幕高潮 | ☐ | -| 成品多角度特写(自然光 / 桌面) | 结尾 | ☐ | - ---- - -## 七、剪辑建议 - -- **转场**:幕与幕之间用干净的硬切或快速模糊,不要花哨转场。产品视频要显得克制。 -- **音乐**:主版本选温暖的原声吉他或钢琴;快闪版选节奏感强的 Lo-Fi / 电子。全片一首歌,不要中途换。 -- **节奏**:第三幕(摆珠)可以稍慢,让观众感受手作质感;第二幕(录屏)加速推进。 -- **字幕**:全程加简体中文字幕,关键词用品牌色 `#416d62`(深绿)高亮。 -- **结尾**:小程序码至少停留 3 秒,让观众来得及扫码。 - ---- - -## 八、旁白全文(主版本,可直接朗读) - -> 我做了很多拼豆作品,挂在墙上很好看。但我总在想——它能不能,不止是一幅画?能不能扫一下,就能看到我想说的话? -> -> 所以我做了「豆维」。输入一段话,或者一个链接,再选一张你喜欢的照片。它会把照片和二维码,合成一幅完整的拼豆图纸。二维码可以放大缩小、移到任何位置,颜色会自动匹配 221 色的拼豆色库,还能选择更注重美观、还是更保证能扫出来。 -> -> 图纸保存下来,打印出来,就可以开始拼了。熨好,冷却,掀开—— -> -> 它不只是一幅画。你把手机对准它,就能读到里面藏着的话。 -> -> 豆维,可扫码的拼豆。看得见,也读得到。 - diff --git "a/BeadQR/docs/\345\256\241\346\240\270\351\251\263\345\233\236\345\244\204\347\220\206-\346\226\207\346\241\210\344\270\216\346\255\245\351\252\244.md" "b/BeadQR/docs/\345\256\241\346\240\270\351\251\263\345\233\236\345\244\204\347\220\206-\346\226\207\346\241\210\344\270\216\346\255\245\351\252\244.md" deleted file mode 100644 index 32154ecf4a6048b2035090856a2d3e244d5fcf13..0000000000000000000000000000000000000000 --- "a/BeadQR/docs/\345\256\241\346\240\270\351\251\263\345\233\236\345\244\204\347\220\206-\346\226\207\346\241\210\344\270\216\346\255\245\351\252\244.md" +++ /dev/null @@ -1,58 +0,0 @@ -# 审核驳回处理:深度合成误判 — 文案与步骤 - -> 驳回原因(2026-09-30):小程序服务内容涉及深度合成技术(如 AI 问答、AI 换脸视频、AI 绘画等),属个人主体尚未开放服务类目,建议申请企业主体类型小程序。 - -## 结论 - -代码里没有任何 AI 功能:文字是标准二维码编码;"照片转图纸"是确定性本地图像处理(按网格取平均色 → 映射到 221 种固定色号),不用任何模型、不生成新图像。被判"深度合成"大概率是**后台简介 / 服务类目 / 服务内容声明的表述**或审核员看到"照片→生成图纸"产生的误判。 - -## 路径 A(推荐,最快):修正表述后重新提审 - -### 1. 检查后台三处(mp.weixin.qq.com) - -| 位置 | 检查项 | -| --- | --- | -| 服务类目 | 改为 **工具 - 图片处理**(个人主体可选);删除任何 AI / 深度合成相关类目 | -| 服务内容声明 / 提审问卷 | 凡问"是否提供 AI 生成内容、AI 问答、AI 绘画、深度合成"一律选 **否** | -| 小程序简介 | 换成下方"新简介",去掉"生成器 / 一键生成 / 智能"等字眼 | - -### 2. 新简介(约 90 字,上限 120) - -``` -手工拼豆图纸工具:输入一句话或选一张照片,按 MARD 标准色号排成拼豆图纸,拼好微信一扫就能读出内容。支持 A4 矢量 PDF 与图片导出、用量清单。照片仅在本地做取色处理,并经微信内容安全检测。 -``` - -### 3. 版本描述 / 审核备注(重新提审时填写) - -``` -本小程序为拼豆手工图纸工具,不使用任何人工智能或深度合成技术,无 AIGC 功能: -1. "照片转图纸"为确定性本地图像处理:将用户上传照片按网格取平均色,再映射到固定的 221 种拼豆标准色号(色彩量化 + 最近邻查表),不生成、不合成任何新图像内容,不涉及人脸或生物特征编辑; -2. 文字功能是将文本编码为标准二维码点阵,非内容生成; -3. 所有处理均在用户手机本地完成,且已接入微信内容安全接口(security.msgSecCheck / imgSecCheck),检测未通过时禁止生成与导出(fail-closed)。 -请按"工具-图片处理"类目审核。 -``` - -## 路径 B:申诉 - -若不想改简介,可直接对本次审核结果发起申诉(审核结果页 → 申诉),粘贴下文: - -``` -本小程序为拼豆手工图纸工具,不涉及深度合成技术: -(1) 不使用 AI 模型 / 神经网络,无 AI 问答、AI 绘画、AI 换脸等 AIGC 功能; -(2) 照片功能仅做确定性色彩量化:按网格取平均色并映射到 221 种固定标准色号,属于常规图像处理,不生成新图像、不做人脸或生物特征编辑; -(3) 文字功能为标准二维码编码; -(4) 已接入微信内容安全检测(msgSecCheck / imgSecCheck),检测未通过不能生成或导出。 -恳请复核,建议按"工具-图片处理"类目审核。 -``` - -申诉未通过时:修改简介与类目后按路径 A 重新提审(改了表述基本都能过)。 - -## 路径 C:仅当二次被拒或想保留"生成"叙事 - -1. **隐藏照片功能**(只保留文字 → 二维码图纸,纯编码无争议)——我可以直接改代码; -2. **升级主体**:注册个体工商户 / 企业 → 主体变更 → 申请深度合成相关类目 + 算法备案。仅在以后真要做 AI 功能时才值得,当前工具用不到。 - -## 顺手可做的降风险小改动(可选) - -- 首页徽标 `MARD 拼豆图纸生成器` → `MARD 拼豆图纸工具` -- 分享标题 `豆维 · 可扫码的拼豆图纸生成器` → `豆维 · 可扫码的拼豆图纸` diff --git a/BeadQR/miniprogram/app.js b/BeadQR/miniprogram/app.js deleted file mode 100644 index 77c44453096b7460e86e268a5079a6b79c45d25d..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/app.js +++ /dev/null @@ -1,14 +0,0 @@ -// 豆维 · 可扫码的拼豆 -App({ - onLaunch() { - if (!wx.cloud) { - console.error('当前基础库不支持云开发,内容安全检测不可用') - return - } - wx.cloud.init({ - env: 'cloud1-d6gthxado7f6c9d14', - traceUser: true, - }) - }, - globalData: {}, -}) diff --git a/BeadQR/miniprogram/app.json b/BeadQR/miniprogram/app.json deleted file mode 100644 index c65531905dd809351665a2e54b7406068e24fe14..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/app.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "pages": [ - "pages/index/index" - ], - "window": { - "backgroundColor": "#f6f5f1", - "backgroundTextStyle": "dark", - "navigationBarBackgroundColor": "#f6f5f1", - "navigationBarTitleText": "豆维 · 可扫码的拼豆", - "navigationBarTextStyle": "black" - }, - "usingComponents": { - "privacy-popup": "/components/privacy-popup/index" - }, - "__usePrivacyCheck__": true, - "sitemapLocation": "sitemap.json", - "style": "v2", - "lazyCodeLoading": "requiredComponents" -} diff --git a/BeadQR/miniprogram/app.wxss b/BeadQR/miniprogram/app.wxss deleted file mode 100644 index 284c483d7c36686985f29a1abfdb8de7b83aead0..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/app.wxss +++ /dev/null @@ -1,37 +0,0 @@ -/* 豆维 · 设计变量 */ -page { - --ink: #16211f; - --ink-2: #3a4643; - --muted: #6f7a76; - --faint: #a4aca8; - --bg: #f6f5f1; - --card: #ffffff; - --line: #ebe8e1; - --brand: #0f766e; - --brand-2: #14b8a6; - --brand-soft: #e6f4f1; - --brand-deep: #0f3d3a; - --accent: #ff7a45; - --accent-soft: #fff1ea; - --danger: #e5484d; - --danger-soft: #fdecec; - --ok: #12a150; - --ok-soft: #e7f6ed; - --warn: #c77700; - --warn-soft: #fff5e0; - --r-s: 16rpx; - --r-m: 24rpx; - --r-l: 36rpx; - --shadow: 0 8rpx 32rpx rgba(22, 33, 31, 0.06); - - background: var(--bg); - color: var(--ink); - font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', sans-serif; - font-size: 28rpx; - line-height: 1.55; -} - -view, text, textarea, input, button, scroll-view, canvas, image { box-sizing: border-box; } - -button { padding: 0; margin: 0; background: none; border: 0; border-radius: 0; line-height: inherit; font-size: inherit; color: inherit; text-align: center; } -button::after { border: 0; } diff --git a/BeadQR/miniprogram/assets/icon.png b/BeadQR/miniprogram/assets/icon.png deleted file mode 100644 index 7d10be4c5227e54d35f75210df674fac3218ff05..0000000000000000000000000000000000000000 Binary files a/BeadQR/miniprogram/assets/icon.png and /dev/null differ diff --git a/BeadQR/miniprogram/assets/miniprogram-code.png b/BeadQR/miniprogram/assets/miniprogram-code.png deleted file mode 100644 index 5a295d47d3f983270485c2eefceb296ce2729810..0000000000000000000000000000000000000000 Binary files a/BeadQR/miniprogram/assets/miniprogram-code.png and /dev/null differ diff --git a/BeadQR/miniprogram/assets/shield.svg b/BeadQR/miniprogram/assets/shield.svg deleted file mode 100644 index 50456c42007effc88842ab4b474c62b6b64af781..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/assets/shield.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/BeadQR/miniprogram/components/privacy-popup/index.js b/BeadQR/miniprogram/components/privacy-popup/index.js deleted file mode 100644 index 4e5d889e8cec37f4235f72742b66926a17825115..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/components/privacy-popup/index.js +++ /dev/null @@ -1,40 +0,0 @@ -// 隐私保护指引弹窗:用户首次调用相册/相机/保存到相册等隐私接口时由微信触发 -Component({ - data: { visible: false, contractName: '《豆维隐私保护指引》' }, - - lifetimes: { - attached() { - if (!wx.onNeedPrivacyAuthorization) return - this._listener = (resolve) => { - this._resolve = resolve - this.setData({ visible: true }) - } - wx.onNeedPrivacyAuthorization(this._listener) - if (wx.getPrivacySetting) { - wx.getPrivacySetting({ - success: (res) => { - if (res.privacyContractName) this.setData({ contractName: res.privacyContractName }) - }, - }) - } - }, - }, - - methods: { - openContract() { - if (wx.openPrivacyContract) wx.openPrivacyContract({}) - }, - handleAgree() { - if (this._resolve) this._resolve({ buttonId: 'privacy-agree-btn', event: 'agree' }) - this._resolve = null - this.setData({ visible: false }) - }, - handleDisagree() { - if (this._resolve) this._resolve({ event: 'disagree' }) - this._resolve = null - this.setData({ visible: false }) - wx.showToast({ title: '未同意将无法选择照片或保存图纸', icon: 'none' }) - }, - noop() {}, - }, -}) diff --git a/BeadQR/miniprogram/components/privacy-popup/index.json b/BeadQR/miniprogram/components/privacy-popup/index.json deleted file mode 100644 index a89ef4dbeefa01f5cd7971973aa4db6498d139f7..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/components/privacy-popup/index.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "component": true, - "usingComponents": {} -} diff --git a/BeadQR/miniprogram/components/privacy-popup/index.wxml b/BeadQR/miniprogram/components/privacy-popup/index.wxml deleted file mode 100644 index 52eb9a5d528a586383b94527a8a6814bea849ea0..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/components/privacy-popup/index.wxml +++ /dev/null @@ -1,13 +0,0 @@ - - - 隐私保护提示 - - 为了帮你把照片做成拼豆图纸,豆维需要读取你选择的照片、保存图纸到相册。照片仅在本机处理,送检的压缩副本会在内容安全检测后立即删除。使用前请阅读 - {{contractName}}。 - - - - - - - diff --git a/BeadQR/miniprogram/components/privacy-popup/index.wxss b/BeadQR/miniprogram/components/privacy-popup/index.wxss deleted file mode 100644 index 0310cb844f9df69a543c3477b2353f044a03cd4a..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/components/privacy-popup/index.wxss +++ /dev/null @@ -1,10 +0,0 @@ -.mask { position: fixed; inset: 0; z-index: 999; background: rgba(15, 23, 22, 0.55); display: flex; align-items: flex-end; } -.sheet { width: 100%; background: #fff; border-radius: 40rpx 40rpx 0 0; padding: 48rpx 40rpx calc(40rpx + env(safe-area-inset-bottom)); box-sizing: border-box; } -.title { font-size: 36rpx; font-weight: 700; color: #1d2b2a; margin-bottom: 20rpx; } -.desc { font-size: 28rpx; line-height: 1.7; color: #55615c; } -.link { color: #0f766e; font-weight: 600; } -.actions { display: flex; gap: 20rpx; margin-top: 40rpx; } -.btn { flex: 1; height: 92rpx; line-height: 92rpx; border-radius: 999rpx; font-size: 30rpx; font-weight: 600; margin: 0 !important; padding: 0 !important; width: auto !important; } -.btn::after { border: 0; } -.ghost { background: #f1f3f2; color: #55615c; } -.primary { background: #0f766e; color: #fff; } diff --git a/BeadQR/miniprogram/pages/index/index.js b/BeadQR/miniprogram/pages/index/index.js deleted file mode 100644 index 59dd120e7362c0a310d02be21431a930cfd65ba3..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/pages/index/index.js +++ /dev/null @@ -1,1073 +0,0 @@ -// 豆维主页面 -// 大数据(色格、图片、画布节点)一律挂在 this._ 上,不进 setData,保证滑动/输入流畅 -const { MATCH_MODES, PRESETS, preparePalette } = require('../../utils/palette') -const { makeQr, createPlacement, SAMPLE_TEXT } = require('../../utils/qr') -const { getImageDimensions, drawCropped, sampleImage, removeSimpleBackground } = require('../../utils/imaging') -const { computeArtwork } = require('../../utils/engine') -const { selfCheck } = require('../../utils/scancheck') -const { renderPreview, patternPageLayout, drawPatternPage, buildInfoPages, drawWatermark } = require('../../utils/pattern') -const { PdfDoc, planPatternPages, addPatternPages, addImagePage } = require('../../utils/pdf') -const security = require('../../utils/security') -const { createIntro } = require('../../utils/intro') - -const LEVELS = [ - { id: 'M', label: 'M', hint: '格子少' }, - { id: 'Q', label: 'Q', hint: '均衡' }, - { id: 'H', label: 'H', hint: '最耐磨' }, -] -const QUIET_OPTIONS = [ - { id: 0, label: '无' }, - { id: 1, label: '1 格' }, - { id: 2, label: '2 格' }, -] -const BEAD_STYLES = [ - { id: 'square', label: '方豆' }, - { id: 'round', label: '圆豆' }, - { id: 'hollow', label: '中空豆' }, -] -const GRID_COLORS = ['#0f766e', '#16211f', '#e5484d', '#3b82f6', '#a855f7'] -const POSITIONS = [0, 50, 100] -const MAX_CANVAS_SIDE = 8000 -const MAX_CANVAS_PIXELS = 16000000 -const CROP_SIZE = 640 -const FILE_PREFIX = 'douwei-' - -function fitScale(w, h) { - return Math.min(1, MAX_CANVAS_SIDE / w, MAX_CANVAS_SIDE / h, Math.sqrt(MAX_CANVAS_PIXELS / (w * h))) -} - -function formatNumber(n) { - return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',') -} - -Page({ - data: { - // 导航 - statusBarHeight: 20, - navHeight: 44, - navSolid: false, - // 1. 内容 - text: '', - textStatus: 'idle', // idle | checking | pass | risky | error - scanImportLoading: false, - // 2. 照片 - hasImage: false, - imageThumb: '', - imageMeta: '', - removeBackground: false, - bgTolerance: 22, - // 3. 效果 - qrScale: 100, - posX: 50, - posY: 50, - positions: POSITIONS, - modeId: 'balanced', - matchModes: MATCH_MODES, - advancedOpen: false, - fineTuneOpen: false, - presets: PRESETS.map((p) => ({ id: p.id, short: p.short, detail: p.detail })), - paletteId: '221', - coherence: 40, - cleanup: true, - levels: LEVELS, - level: 'H', - quietOptions: QUIET_OPTIONS, - quiet: 1, - // 预览与统计 - generating: false, - isSample: true, - showIntro: true, // 无内容时播放动态演示,写下内容后切回真实预览 - textFocus: false, - gridText: '—', - beadsText: '—', - colorsText: '—', - qrGridText: '', - scanState: 'idle', // idle | checking | ok | fail - usage: [], - usageMore: 0, - usageExpanded: false, - usageAll: [], - // 裁切 - cropVisible: false, - cropCheck: 'idle', // checking | pass | risky | error - draftFocusX: 50, - draftFocusY: 50, - draftZoom: 100, - draftFlipH: false, - draftFlipV: false, - draftRemoveBg: false, - draftBgTolerance: 22, - // 导出 - exportVisible: false, - exportFormat: 'pdf', - exportLayout: 'combined', - exportQuality: 'hd', - beadStyles: BEAD_STYLES, - beadStyle: 'square', - gridEnabled: true, - gridInterval: 10, - gridColors: GRID_COLORS, - gridColor: '#0f766e', - watermark: '', - watermarkStatus: 'idle', - exporting: false, - exportStep: '', - exportResult: null, // { kind, files: [{ name, path }] } - }, - - // —— 生命周期 —— - - onLoad() { - const win = wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync() - let navHeight = 44 - try { - const capsule = wx.getMenuButtonBoundingClientRect() - if (capsule && capsule.top) navHeight = (capsule.top - win.statusBarHeight) * 2 + capsule.height - } catch (e) { /* 使用默认高度 */ } - this._dpr = Math.min(3, win.pixelRatio || 2) - this._timers = {} - this._nodes = {} - this._token = 0 - this.setData({ statusBarHeight: win.statusBarHeight || 20, navHeight }) - }, - - onReady() { - wx.createSelectorQuery().in(this) - .select('#preview').fields({ node: true, size: true }) - .select('#work').fields({ node: true }) - .select('#security').fields({ node: true }) - .exec((res) => { - this._nodes.preview = res[0] && res[0].node - this._previewCss = (res[0] && res[0].width) || 300 - this._nodes.work = res[1] && res[1].node - this._nodes.security = res[2] && res[2].node - this.loadMpCode() - this.schedule(0) - }) - }, - - onUnload() { - Object.keys(this._timers).forEach((k) => clearTimeout(this._timers[k])) - this.stopIntro() - }, - - onShow() { - this._hidden = false - if (this._nodes && this._nodes.preview) this.syncIntro() - }, - - onHide() { - this._hidden = true - this.pauseIntro() - }, - - onPageScroll(e) { - const solid = e.scrollTop > 40 - if (solid !== this.data.navSolid) this.setData({ navSolid: solid }) - // 演示区滚出屏幕后暂停动画,省电 - const away = e.scrollTop > 650 - if (away !== this._scrolledAway) { - this._scrolledAway = away - this.syncIntro() - } - }, - - // 分享的是小程序页面卡片,不携带用户内容 - onShareAppMessage() { - return { title: '把一句话藏进拼豆里,微信一扫就能读出来', path: '/pages/index/index' } - }, - onShareTimeline() { - return { title: '豆维 · 可扫码的拼豆图纸生成器' } - }, - - loadMpCode() { - if (!this._nodes.work) return - const image = this._nodes.work.createImage() - image.onload = () => { this._mpCode = image } - image.onerror = () => { this._mpCode = null } - image.src = '/assets/miniprogram-code.png' - }, - - debounce(key, fn, wait) { - clearTimeout(this._timers[key]) - this._timers[key] = setTimeout(fn, wait) - }, - - noop() {}, - - openPrivacy() { - if (wx.openPrivacyContract) { - wx.openPrivacyContract({ fail: () => wx.showToast({ title: '请在微信内查看隐私保护指引', icon: 'none' }) }) - } - }, - - showRules() { - wx.showModal({ - title: '内容规范', - content: '二维码内容、水印和照片都会经过微信内容安全检测。请勿输入或上传违法违规、色情低俗、暴力恐怖、侵犯他人权益等内容;检测未通过或检测异常时无法生成与导出。如发现不良内容,可通过页面底部“投诉与反馈”告诉我们。', - showCancel: false, - confirmText: '我知道了', - }) - }, - - // —— 生成管线 —— - - schedule(wait = 120) { - this.debounce('run', () => this.run(), wait) - }, - - run() { - if (!this._nodes.preview || !this._nodes.work) return - // 没写内容、也没选照片:示例二维码没有意义,改为播放动态演示 - if (!this.data.text.trim() && !this._source) { - this._token += 1 - this.setData({ - showIntro: true, generating: false, isSample: true, scanState: 'idle', usage: [], usageAll: [], usageMore: 0, - }, () => this.syncIntro()) - return - } - this.stopIntro() - if (this.data.showIntro) this.setData({ showIntro: false }) - const token = ++this._token - this.setData({ generating: true }, () => { - // 先让“生成中”状态渲染一帧,再做同步计算 - setTimeout(() => { - if (token !== this._token) return - try { - this.compute() - this.afterCompute(token) - } catch (error) { - console.error('生成失败', error) - this.setData({ generating: false }) - wx.showToast({ title: '内容太长或图片异常,请调整后重试', icon: 'none' }) - } - }, 16) - }) - }, - - compute() { - const d = this.data - const text = d.text.trim() - const qrKey = `${text}|${d.level}` - if (!this._qr || this._qrKey !== qrKey) { - this._qr = makeQr(text, d.level) - this._qrKey = qrKey - } - const src = this._source - const placement = createPlacement(this._qr.size, { - scale: d.qrScale, posX: d.posX, posY: d.posY, quiet: d.quiet, hasImage: Boolean(src), - }) - let base = null - if (src) { - const c = src.crop - const baseKey = `${placement.size}|${c.focusX}|${c.focusY}|${c.zoom}|${c.flipH}|${c.flipV}|${d.removeBackground}|${d.bgTolerance}` - if (this._baseKey !== baseKey) { - const sampled = sampleImage(this._nodes.work, src.image, placement.size, c) - if (d.removeBackground) sampled.mask = removeSimpleBackground(sampled.rgb, placement.size, d.bgTolerance) - this._base = sampled - this._baseKey = baseKey - } - base = this._base - } - const pal = preparePalette(d.paletteId) - this._artwork = computeArtwork(base, this._qr, placement, pal, { - modeId: d.modeId, coherence: d.coherence, cleanup: d.cleanup, - }) - }, - - afterCompute(token) { - const art = this._artwork - this.drawPreview() - const usageAll = art.usage.map((u) => ({ id: u.id, hex: u.hex, count: u.count })) - this.setData({ - generating: false, - isSample: this._qr.isSample, - gridText: `${art.size}×${art.size}`, - beadsText: formatNumber(art.beads), - colorsText: String(art.usage.length), - qrGridText: `${this._qr.size}×${this._qr.size}`, - usage: usageAll.slice(0, 12), - usageAll: this.data.usageExpanded ? usageAll : [], - usageMore: Math.max(0, usageAll.length - 12), - scanState: 'checking', - }) - this._usageAll = usageAll - // 扫码自检放到下一轮,避免阻塞预览出图 - this.debounce('scan', () => { - if (token !== this._token) return - const expected = this.data.text.trim() || SAMPLE_TEXT - const result = selfCheck(art, expected) - if (token === this._token) this.setData({ scanState: result.ok ? 'ok' : 'fail' }) - }, 60) - }, - - drawPreview() { - const node = this._nodes.preview - if (!node || !this._artwork) return - const px = Math.round(this._previewCss * this._dpr) - renderPreview(node, this._artwork, px, this.data.beadStyle) - }, - - // —— 动态演示 —— - - syncIntro() { - const should = this.data.showIntro && !this._hidden && !this._scrolledAway && !this.data.cropVisible - if (should) this.startIntro() - else this.pauseIntro() - }, - - startIntro() { - if (this._introPlayer) return - const node = this._nodes.preview - if (!node || !this._nodes.work) return - if (!this._intro) { - try { - this._intro = createIntro({ workCanvas: this._nodes.work }) - } catch (error) { - console.error('演示动画初始化失败', error) - return - } - } - const px = Math.round(this._previewCss * this._dpr) - node.width = px - node.height = px - const ctx = node.getContext('2d') - const raf = node.requestAnimationFrame ? (f) => node.requestAnimationFrame(f) : (f) => setTimeout(f, 16) - const caf = node.cancelAnimationFrame ? (id) => node.cancelAnimationFrame(id) : (id) => clearTimeout(id) - const player = { start: Date.now() - (this._introElapsed || 0), last: 0, id: 0, caf } - const tick = () => { - if (this._introPlayer !== player) return - const now = Date.now() - if (now - player.last >= 30) { // 约 30 帧/秒,流畅且省电 - player.last = now - this._introElapsed = now - player.start - this._intro.draw(ctx, this._introElapsed / 1000, px) - } - player.id = raf(tick) - } - this._introPlayer = player - tick() - }, - - pauseIntro() { - const player = this._introPlayer - if (!player) return - this._introPlayer = null - player.caf(player.id) - }, - - stopIntro() { - this.pauseIntro() - this._introElapsed = 0 - }, - - replayIntro() { - if (!this.data.showIntro) return - this.stopIntro() - this.syncIntro() - }, - - // 引导用户开始:滚到第 1 步并弹出键盘 - startCreate() { - wx.pageScrollTo({ - selector: '#step1', - offsetTop: -(this.data.statusBarHeight + this.data.navHeight + 12), - duration: 300, - }) - setTimeout(() => this.setData({ textFocus: true }), 320) - }, - - onTextBlur() { - if (this.data.textFocus) this.setData({ textFocus: false }) - }, - - // —— 1. 内容 —— - - onTextInput(e) { - const text = e.detail.value - this.setData({ text, textStatus: text.trim() ? 'checking' : 'idle' }) - this.schedule(220) - this.debounce('textCheck', () => this.verifyText(text), 650) - }, - - clearText() { - this.setData({ text: '', textStatus: 'idle' }) - this.schedule(0) - }, - - verifyText(raw) { - const value = String(raw || '').trim() - if (!value) return - security.checkText(value, 2).then(({ status }) => { - if (this.data.text.trim() !== value) return - this.setData({ textStatus: status }) - if (status === 'risky') wx.showToast({ title: security.MESSAGES.risky, icon: 'none' }) - }) - }, - - scanImport() { - wx.scanCode({ - onlyFromCamera: false, - scanType: ['qrCode'], - success: (res) => { - const decoded = String(res.result || '').trim().slice(0, 120) - if (!decoded) { wx.showToast({ title: '二维码里没有内容', icon: 'none' }); return } - this.setData({ scanImportLoading: true }) - // 扫码得到的内容同样先过内容安全检测,通过后才填入 - security.checkText(decoded, 2).then(({ status }) => { - this.setData({ scanImportLoading: false }) - if (status !== 'pass') { wx.showToast({ title: security.MESSAGES[status], icon: 'none' }); return } - this.setData({ text: decoded, textStatus: 'pass' }) - this.schedule(0) - wx.showToast({ title: '已导入二维码内容', icon: 'none' }) - }) - }, - fail: (err) => { - if (err && /cancel/i.test(err.errMsg || '')) return - wx.showToast({ title: '没有识别到二维码', icon: 'none' }) - }, - }) - }, - - // —— 2. 照片 —— - - loadImage(path, info) { - return new Promise((resolve, reject) => { - const image = this._nodes.work.createImage() - image.onload = () => { - if (info && info.width) { image._w = info.width; image._h = info.height } - resolve(image) - } - image.onerror = () => reject(new Error('IMAGE_LOAD_FAILED')) - image.src = path - }) - }, - - chooseImage() { - wx.chooseMedia({ - count: 1, - mediaType: ['image'], - sizeType: ['compressed'], - sourceType: ['album', 'camera'], - success: (res) => { - const file = res.tempFiles && res.tempFiles[0] - if (file && file.tempFilePath) this.prepareImage(file.tempFilePath) - }, - fail: (err) => { - if (err && /cancel/i.test(err.errMsg || '')) return - wx.showToast({ title: '没有选到照片', icon: 'none' }) - }, - }) - }, - - prepareImage(path) { - wx.showLoading({ title: '读取照片…', mask: true }) - new Promise((resolve) => { - wx.getImageInfo({ src: path, success: resolve, fail: () => resolve(null) }) - }) - .then((info) => this.loadImage(path, info).then((image) => ({ image, info }))) - .then(({ image }) => { - wx.hideLoading() - this._pending = { image, path } - // 选图后立即在后台做内容安全检测;同时打开构图编辑,检测通过后才能使用 - this.startImageCheck(this._pending) - this.openCrop(true) - }) - .catch(() => { - wx.hideLoading() - wx.showToast({ title: '照片读取失败,请换一张 JPG / PNG', icon: 'none' }) - }) - }, - - startImageCheck(pending) { - pending.status = 'checking' - this.setData({ cropCheck: 'checking' }) - pending.check = security.checkImage(pending.path, pending.image, this._nodes.security).then(({ status }) => { - pending.status = status - if (this._pending === pending) { - this.setData({ cropCheck: status }) - if (pending.applyWhenPassed && status === 'pass') this.applyCrop() - pending.applyWhenPassed = false - } - return status - }) - }, - - retryImageCheck() { - if (this._pending) this.startImageCheck(this._pending) - }, - - editImage() { - if (!this._source) return - this._pending = { image: this._source.image, path: this._source.path, status: 'pass' } - this.setData({ cropCheck: 'pass' }) - this.openCrop(false) - }, - - removeImage() { - this._source = null - this._base = null - this._baseKey = '' - this.setData({ hasImage: false, imageThumb: '', imageMeta: '', removeBackground: false }) - this.schedule(0) - }, - - // —— 裁切编辑 —— - - openCrop(isNew) { - const crop = !isNew && this._source ? this._source.crop : null - this.pauseIntro() - this.setData({ - cropVisible: true, - draftFocusX: crop ? crop.focusX : 50, - draftFocusY: crop ? crop.focusY : 50, - draftZoom: crop ? Math.round(crop.zoom * 100) : 100, - draftFlipH: crop ? crop.flipH : false, - draftFlipV: crop ? crop.flipV : false, - draftRemoveBg: isNew ? false : this.data.removeBackground, - draftBgTolerance: isNew ? 22 : this.data.bgTolerance, - }, () => { - wx.createSelectorQuery().in(this) - .select('#crop').fields({ node: true, size: true }) - .select('#crop').boundingClientRect() - .exec((res) => { - this._nodes.crop = res[0] && res[0].node - this._cropCss = (res[0] && res[0].width) || 300 - this._cropRect = res[1] || { left: 0, top: 0 } - this.drawCrop() - }) - }) - }, - - drawCrop() { - const node = this._nodes.crop - const pending = this._pending - if (!node || !pending) return - node.width = CROP_SIZE - node.height = CROP_SIZE - const ctx = node.getContext('2d') - const d = this.data - ctx.imageSmoothingEnabled = true - drawCropped(ctx, pending.image, CROP_SIZE, CROP_SIZE, { - focusX: d.draftFocusX, focusY: d.draftFocusY, zoom: d.draftZoom / 100, flipH: d.draftFlipH, flipV: d.draftFlipV, - }) - // 三分线,仅辅助构图 - ctx.strokeStyle = 'rgba(255,255,255,0.5)' - ctx.lineWidth = 1.5 - ctx.beginPath() - for (let i = 1; i < 3; i += 1) { - const p = Math.round(CROP_SIZE * i / 3) + 0.5 - ctx.moveTo(p, 0); ctx.lineTo(p, CROP_SIZE) - ctx.moveTo(0, p); ctx.lineTo(CROP_SIZE, p) - } - ctx.stroke() - }, - - touchPoint(t) { - const rect = this._cropRect || { left: 0, top: 0 } - const x = t.clientX == null ? (t.pageX == null ? t.x : t.pageX) : t.clientX - const y = t.clientY == null ? (t.pageY == null ? t.y : t.pageY) : t.clientY - return { x: x - rect.left, y: y - rect.top } - }, - - touchDistance(touches) { - const a = this.touchPoint(touches[0]) - const b = this.touchPoint(touches[1]) - return Math.hypot(b.x - a.x, b.y - a.y) - }, - - onCropStart(e) { - const touches = e.touches || [] - if (!touches[0]) return - if (touches.length >= 2) { - this._gesture = { type: 'pinch', distance: this.touchDistance(touches), zoom: this.data.draftZoom } - return - } - this._gesture = { type: 'drag', ...this.touchPoint(touches[0]), focusX: this.data.draftFocusX, focusY: this.data.draftFocusY } - }, - - onCropMove(e) { - const touches = e.touches || [] - const g = this._gesture - const pending = this._pending - if (!touches[0] || !g || !pending) return - if (touches.length >= 2) { - if (g.type !== 'pinch') { - this._gesture = { type: 'pinch', distance: this.touchDistance(touches), zoom: this.data.draftZoom } - return - } - const zoom = Math.max(100, Math.min(400, Math.round(g.zoom * this.touchDistance(touches) / g.distance))) - this.setData({ draftZoom: zoom }) - this.debounce('crop', () => this.drawCrop(), 16) - return - } - if (g.type !== 'drag') return - const p = this.touchPoint(touches[0]) - const cssScale = CROP_SIZE / (this._cropCss || CROP_SIZE) - const src = getImageDimensions(pending.image) - const scale = Math.max(CROP_SIZE / src.width, CROP_SIZE / src.height) * (this.data.draftZoom / 100) - const w = src.width * scale - const h = src.height * scale - const clamp = (v) => Math.max(0, Math.min(100, v)) - const dirX = this.data.draftFlipH ? -1 : 1 - const dirY = this.data.draftFlipV ? -1 : 1 - const fx = Math.abs(w - CROP_SIZE) < 0.01 ? g.focusX : clamp(g.focusX + dirX * ((p.x - g.x) * cssScale / (CROP_SIZE - w)) * 100) - const fy = Math.abs(h - CROP_SIZE) < 0.01 ? g.focusY : clamp(g.focusY + dirY * ((p.y - g.y) * cssScale / (CROP_SIZE - h)) * 100) - this.setData({ draftFocusX: fx, draftFocusY: fy }) - this.debounce('crop', () => this.drawCrop(), 16) - }, - - onCropEnd() { - this._gesture = null - clearTimeout(this._timers.crop) - this.drawCrop() - }, - - onDraftZoom(e) { - this.setData({ draftZoom: Number(e.detail.value) }) - this.debounce('crop', () => this.drawCrop(), 16) - }, - resetCrop() { this.setData({ draftFocusX: 50, draftFocusY: 50, draftZoom: 100 }, () => this.drawCrop()) }, - flipH() { this.setData({ draftFlipH: !this.data.draftFlipH }, () => this.drawCrop()) }, - flipV() { this.setData({ draftFlipV: !this.data.draftFlipV }, () => this.drawCrop()) }, - onDraftRemoveBg(e) { this.setData({ draftRemoveBg: e.detail.value }) }, - onDraftBgTolerance(e) { this.setData({ draftBgTolerance: Number(e.detail.value) }) }, - - cancelCrop() { - if (this._pending) this._pending.applyWhenPassed = false - this.setData({ cropVisible: false }, () => this.syncIntro()) - }, - - applyCrop() { - const pending = this._pending - if (!pending) return - if (pending.status === 'checking') { - // 检测未完成:记住用户意图,通过后自动应用 - pending.applyWhenPassed = true - wx.showToast({ title: '正在做内容安全检测,通过后自动应用', icon: 'none' }) - return - } - if (pending.status !== 'pass') { - wx.showToast({ title: security.MESSAGES[pending.status] || security.MESSAGES.error, icon: 'none' }) - return - } - const d = this.data - const src = getImageDimensions(pending.image) - this._source = { - image: pending.image, - path: pending.path, - crop: { focusX: d.draftFocusX, focusY: d.draftFocusY, zoom: d.draftZoom / 100, flipH: d.draftFlipH, flipV: d.draftFlipV }, - } - this._baseKey = '' - this.setData({ - cropVisible: false, - hasImage: true, - imageThumb: pending.path, - imageMeta: `${src.width}×${src.height} · 已通过安全检测`, - removeBackground: d.draftRemoveBg, - bgTolerance: d.draftBgTolerance, - // 首次导入照片时,二维码默认铺满全图;用户可再缩小 - }) - this.schedule(0) - }, - - // —— 3. 效果 —— - - onScaleChanging(e) { this.setData({ qrScale: Number(e.detail.value) }); this.schedule(160) }, - onScaleChange(e) { this.setData({ qrScale: Number(e.detail.value) }); this.schedule(0) }, - pickPosition(e) { - const { x, y } = e.currentTarget.dataset - this.setData({ posX: Number(x), posY: Number(y) }) - this.schedule(0) - }, - toggleFineTune() { this.setData({ fineTuneOpen: !this.data.fineTuneOpen }) }, - onPosX(e) { this.setData({ posX: Number(e.detail.value) }); this.schedule(100) }, - onPosY(e) { this.setData({ posY: Number(e.detail.value) }); this.schedule(100) }, - pickMode(e) { this.setData({ modeId: e.currentTarget.dataset.id }); this.schedule(0) }, - toggleAdvanced() { this.setData({ advancedOpen: !this.data.advancedOpen }) }, - pickPalette(e) { this.setData({ paletteId: e.currentTarget.dataset.id }); this.schedule(0) }, - onCoherence(e) { this.setData({ coherence: Number(e.detail.value) }); this.schedule(200) }, - onCleanup(e) { this.setData({ cleanup: e.detail.value }); this.schedule(0) }, - pickLevel(e) { this.setData({ level: e.currentTarget.dataset.id }); this.schedule(0) }, - pickQuiet(e) { this.setData({ quiet: Number(e.currentTarget.dataset.id) }); this.schedule(0) }, - onBgToggle(e) { this.setData({ removeBackground: e.detail.value }); this.schedule(0) }, - - applyScanFix() { - // 自检未通过时的一键修复:扫码优先 + 1 格白边 + H 级纠错 - this.setData({ modeId: 'strict', quiet: Math.max(1, this.data.quiet), level: 'H' }) - this.schedule(0) - wx.showToast({ title: '已切换为扫码优先', icon: 'none' }) - }, - - toggleUsage() { - const expanded = !this.data.usageExpanded - this.setData({ usageExpanded: expanded, usageAll: expanded ? (this._usageAll || []) : [] }) - }, - - // 生成当前预览图,用微信“识别图中二维码”实测 - verifyScan() { - const node = this._nodes.preview - if (!node) return - wx.canvasToTempFilePath({ - canvas: node, - fileType: 'png', - success: (res) => { - wx.previewImage({ urls: [res.tempFilePath], showmenu: true }) - wx.showToast({ title: '长按图片,选择“识别图中二维码”', icon: 'none', duration: 2500 }) - }, - fail: () => wx.showToast({ title: '预览生成失败,请重试', icon: 'none' }), - }) - }, - - // —— 导出 —— - - openExport() { - if (!this.data.text.trim()) { - this.startCreate() - return - } - if (!this._artwork) return - if (this.data.textStatus === 'risky') { - wx.showToast({ title: security.MESSAGES.risky, icon: 'none' }) - return - } - this.setData({ exportVisible: true, exportResult: null }) - }, - closeExport() { - if (this.data.exporting) return - this.setData({ exportVisible: false }) - }, - pickFormat(e) { this.setData({ exportFormat: e.currentTarget.dataset.id, exportResult: null }) }, - pickLayout(e) { this.setData({ exportLayout: e.currentTarget.dataset.id, exportResult: null }) }, - pickQuality(e) { this.setData({ exportQuality: e.currentTarget.dataset.id, exportResult: null }) }, - pickBeadStyle(e) { - this.setData({ beadStyle: e.currentTarget.dataset.id, exportResult: null }, () => this.drawPreview()) - }, - onGridToggle(e) { this.setData({ gridEnabled: e.detail.value, exportResult: null }) }, - pickInterval(e) { this.setData({ gridInterval: Number(e.currentTarget.dataset.id), exportResult: null }) }, - pickGridColor(e) { this.setData({ gridColor: e.currentTarget.dataset.color, exportResult: null }) }, - onWatermark(e) { - const watermark = e.detail.value - this.setData({ watermark, watermarkStatus: watermark.trim() ? 'checking' : 'idle', exportResult: null }) - this.debounce('wmCheck', () => { - const value = watermark.trim() - if (!value) return - security.checkText(value, 1).then(({ status }) => { - if (this.data.watermark.trim() === value) this.setData({ watermarkStatus: status }) - }) - }, 650) - }, - - gridSettings() { - return { enabled: this.data.gridEnabled, interval: this.data.gridInterval, color: this.data.gridColor, opacity: 60 } - }, - - capture(fileType, quality) { - return new Promise((resolve, reject) => { - wx.canvasToTempFilePath({ - canvas: this._nodes.work, - fileType, - quality, - success: (res) => resolve(res.tempFilePath), - fail: reject, - }) - }) - }, - - step(label) { - return new Promise((resolve) => this.setData({ exportStep: label }, () => setTimeout(resolve, 16))) - }, - - async startExport() { - if (this.data.exporting || !this._artwork) return - this.setData({ exporting: true, exportResult: null }) - try { - // 平台合规:导出前再次确认所有用户文本均已通过内容安全检测(已通过的走缓存,无额外等待) - await this.step('内容安全校验…') - const check = await security.checkTexts([ - { value: this.data.text, scene: 2 }, - { value: this.data.watermark, scene: 1 }, - ]) - if (check.status !== 'pass') { - wx.showToast({ title: security.MESSAGES[check.status], icon: 'none' }) - if (check.status === 'risky') this.setData({ textStatus: security.isTextPassed(this.data.text) ? 'pass' : 'risky' }) - return - } - if (this._source && !this.data.hasImage) throw new Error('IMAGE_STATE') - this.cleanOldFiles() - if (this.data.exportFormat === 'png') await this.exportPng() - else await this.exportPdf() - } catch (error) { - console.error('导出失败', error) - wx.showToast({ title: '导出失败,请重试(可尝试“标准”清晰度)', icon: 'none' }) - } finally { - this.setData({ exporting: false, exportStep: '' }) - } - }, - - // 按清晰度求每格像素:高清长边约 4000px,标准约 2400px - pngCellSize() { - const size = this._artwork.size - const maxSide = this.data.exportQuality === 'hd' ? 4000 : 2400 - let cell = Math.max(12, Math.min(72, Math.floor(maxSide / (size + 5)))) - while (cell > 12) { - const L = patternPageLayout(size, cell) - if (Math.max(L.width, L.height) <= maxSide) break - cell -= 1 - } - return cell - }, - - patternMeta() { - const art = this._artwork - return `${art.size}×${art.size} 格 · ${formatNumber(art.beads)} 颗 · ${art.usage.length} 色 · ${art.pal.label} · 扫码内容:${this.data.text.trim()}` - }, - - infoParams(width) { - return { - artwork: this._artwork, - width, - text: this.data.text.trim(), - paletteLabel: `${this._artwork.pal.label}(本作品用 ${this._artwork.usage.length} 色)`, - mpCode: this._mpCode || null, - thumbnail: true, - } - }, - - async exportPng() { - const node = this._nodes.work - const art = this._artwork - const cell = this.pngCellSize() - const L = patternPageLayout(art.size, cell) - const wm = this.data.watermark.trim() - const drawPattern = (ctx) => drawPatternPage(ctx, art, { cell, style: this.data.beadStyle, grid: this.gridSettings(), meta: this.patternMeta() }) - const files = [] - if (this.data.exportLayout === 'separate') { - await this.step('绘制图纸…') - node.width = L.width - node.height = L.height - const ctx = node.getContext('2d') - drawPattern(ctx) - drawWatermark(ctx, L.width, L.height, wm) - files.push({ name: '拼豆图纸', path: await this.capture('png') }) - const info = buildInfoPages(this.infoParams(this.data.exportQuality === 'hd' ? 2000 : 1400)) - for (let p = 0; p < info.pageCount; p += 1) { - await this.step(`绘制用豆清单 ${p + 1}/${info.pageCount}…`) - node.width = info.width - node.height = info.height - const c = node.getContext('2d') - info.draw(c, p) - drawWatermark(c, info.width, info.height, wm) - files.push({ name: info.pageCount > 1 ? `用豆清单 ${p + 1}` : '用豆清单', path: await this.capture('png') }) - } - } else { - await this.step('绘制图纸与清单…') - const info = buildInfoPages(this.infoParams(L.width)) - const gap = Math.round(L.width * 0.02) - const totalH = L.height + info.pageCount * (info.height + gap) - const scale = fitScale(L.width, totalH) - node.width = Math.round(L.width * scale) - node.height = Math.round(totalH * scale) - const ctx = node.getContext('2d') - ctx.fillStyle = '#e8e5de' - ctx.fillRect(0, 0, node.width, node.height) - ctx.scale(scale, scale) - drawPattern(ctx) - for (let p = 0; p < info.pageCount; p += 1) { - ctx.save() - ctx.translate(0, L.height + gap + p * (info.height + gap)) - info.draw(ctx, p) - ctx.restore() - } - ctx.setTransform(1, 0, 0, 1, 0, 0) - drawWatermark(ctx, node.width, node.height, wm) - files.push({ name: '图纸 + 用豆清单', path: await this.capture('png') }) - } - await this.step('保存到相册…') - const saved = await this.saveImages(files.map((f) => f.path)) - this.setData({ exportResult: { kind: 'png', saved, files } }) - if (saved) wx.showToast({ title: `${files.length} 张图片已保存`, icon: 'success' }) - }, - - saveImages(paths) { - return paths.reduce((chain, path) => chain.then((ok) => (ok ? new Promise((resolve) => { - wx.saveImageToPhotosAlbum({ - filePath: path, - success: () => resolve(true), - fail: (err) => { - if (err && /auth|deny/i.test(err.errMsg || '')) { - wx.showModal({ - title: '需要相册权限', - content: '请允许保存到相册,才能把图纸存到手机里', - confirmText: '去设置', - success: (r) => { if (r.confirm) wx.openSetting() }, - }) - } - resolve(false) - }, - }) - }) : false)), Promise.resolve(true)) - }, - - previewResult() { - const result = this.data.exportResult - if (!result) return - if (result.kind === 'png') { - wx.previewImage({ urls: result.files.map((f) => f.path), showmenu: true }) - } else { - this.openPdf(result.files[0].path) - } - }, - - openResultFile(e) { - const file = this.data.exportResult.files[Number(e.currentTarget.dataset.index)] - if (!file) return - if (this.data.exportResult.kind === 'png') wx.previewImage({ urls: [file.path], showmenu: true }) - else this.openPdf(file.path) - }, - - shareResultFile(e) { - const file = this.data.exportResult.files[Number(e.currentTarget.dataset.index)] - if (!file || !wx.shareFileMessage) { - wx.showToast({ title: '当前微信版本不支持,请先打开再转发', icon: 'none' }) - return - } - wx.shareFileMessage({ filePath: file.path, fileName: file.fileName || `${file.name}.pdf` }) - }, - - openPdf(path) { - wx.openDocument({ filePath: path, fileType: 'pdf', showMenu: true }) - }, - - cleanOldFiles() { - try { - const fs = wx.getFileSystemManager() - fs.readdirSync(wx.env.USER_DATA_PATH) - .filter((name) => name.indexOf(FILE_PREFIX) === 0) - .forEach((name) => { try { fs.unlinkSync(`${wx.env.USER_DATA_PATH}/${name}`) } catch (e) { /* 忽略 */ } }) - } catch (e) { /* 忽略 */ } - }, - - async canvasJpeg(width, height, draw, quality = 0.9) { - const node = this._nodes.work - node.width = width - node.height = height - const ctx = node.getContext('2d') - draw(ctx) - const path = await this.capture('jpg', quality) - return wx.getFileSystemManager().readFileSync(path) - }, - - // 页眉(中文)渲染为小图,矢量页面上方使用 - async renderHeader(title, sub) { - const w = 1540 - const h = 80 - const data = await this.canvasJpeg(w, h, (ctx) => { - ctx.fillStyle = '#ffffff' - ctx.fillRect(0, 0, w, h) - ctx.fillStyle = '#16211f' - ctx.font = 'bold 34px sans-serif' - ctx.textBaseline = 'middle' - ctx.fillText(title, 0, 30) - ctx.fillStyle = '#6f7a76' - ctx.font = '22px sans-serif' - ctx.fillText(sub, 0, 64) - ctx.fillStyle = '#0f766e' - ctx.font = 'bold 24px sans-serif' - ctx.textAlign = 'right' - ctx.fillText('豆维 · 可扫码的拼豆', w, 30) - }, 0.92) - return { data, width: w, height: h } - }, - - // 水印转为 1-bit 蒙版,矢量页上以半透明平铺 - watermarkMask(doc, text) { - const value = String(text || '').trim() - if (!value) return null - const node = this._nodes.work - const fontSize = 64 - node.width = 16 - node.height = 16 - let ctx = node.getContext('2d') - ctx.font = `bold ${fontSize}px sans-serif` - const width = Math.min(2000, Math.ceil(ctx.measureText(value).width) + 16) - const height = Math.round(fontSize * 1.4) - node.width = width - node.height = height - ctx = node.getContext('2d') - ctx.clearRect(0, 0, width, height) - ctx.fillStyle = '#000000' - ctx.font = `bold ${fontSize}px sans-serif` - ctx.textBaseline = 'middle' - ctx.fillText(value, 8, height / 2) - const pixels = ctx.getImageData(0, 0, width, height).data - const rowBytes = Math.ceil(width / 8) - const bits = new Uint8Array(rowBytes * height).fill(0xff) // 1 = 不绘制 - for (let y = 0; y < height; y += 1) { - for (let x = 0; x < width; x += 1) { - if (pixels[(y * width + x) * 4 + 3] > 110) bits[y * rowBytes + (x >> 3)] &= ~(0x80 >> (x & 7)) - } - } - return { id: doc.addImageMask(bits, width, height), width, height } - }, - - async buildPatternPdf(doc) { - const art = this._artwork - const plan = planPatternPages(art.size) - const headers = [] - for (let i = 0; i < plan.pages.length; i += 1) { - const page = plan.pages[i] - await this.step(`绘制图纸页 ${i + 1}/${plan.pages.length}…`) - const title = page.kind === 'overview' - ? (plan.tiled ? `图纸总览 · 共分 ${plan.pages.length - 1} 块` : '拼豆图纸') - : `第 ${page.tile} 块 · 列 ${page.region.col + 1}–${page.region.col + page.region.cols} · 行 ${page.region.row + 1}–${page.region.row + page.region.rows}` - const sub = page.kind === 'overview' - ? `${art.size}×${art.size} 格 · ${formatNumber(art.beads)} 颗 · ${art.usage.length} 色 · ${plan.tiled ? '红框编号对应后续分块页,按块拼更轻松' : '格内为 MARD 色号'}` - : `第 ${i + 1} / ${plan.pages.length} 页 · 格内为 MARD 色号 · 坐标为整张作品的行列号` - const header = await this.renderHeader(title, sub) - headers.push({ id: doc.addJpeg(header.data, header.width, header.height), width: header.width, height: header.height }) - } - const watermark = this.watermarkMask(doc, this.data.watermark) - addPatternPages(doc, art, { plan, headers, style: this.data.beadStyle, grid: this.gridSettings(), watermark }) - }, - - async buildInfoPdf(doc) { - const info = buildInfoPages(this.infoParams(1654)) - for (let p = 0; p < info.pageCount; p += 1) { - await this.step(`绘制用豆清单 ${p + 1}/${info.pageCount}…`) - const data = await this.canvasJpeg(info.width, info.height, (ctx) => { - info.draw(ctx, p) - drawWatermark(ctx, info.width, info.height, this.data.watermark) - }, 0.9) - addImagePage(doc, doc.addJpeg(data, info.width, info.height), info.width, info.height) - } - }, - - async exportPdf() { - const fs = wx.getFileSystemManager() - const stamp = Date.now().toString(36) - const art = this._artwork - const base = `${FILE_PREFIX}${art.size}x${art.size}-${stamp}` - const files = [] - const write = (doc, suffix, name) => { - const fileName = `豆维-${name}.pdf` - const path = `${wx.env.USER_DATA_PATH}/${base}-${suffix}.pdf` - fs.writeFileSync(path, doc.build(), 'binary') - files.push({ name, fileName, path }) - } - if (this.data.exportLayout === 'separate') { - const patternDoc = new PdfDoc() - await this.buildPatternPdf(patternDoc) - write(patternDoc, 'pattern', '拼豆图纸') - const infoDoc = new PdfDoc() - await this.buildInfoPdf(infoDoc) - write(infoDoc, 'info', '用豆清单') - } else { - const doc = new PdfDoc() - await this.buildPatternPdf(doc) - await this.buildInfoPdf(doc) - write(doc, 'all', '图纸与用豆清单') - } - this.setData({ exportResult: { kind: 'pdf', saved: true, files } }) - }, -}) diff --git a/BeadQR/miniprogram/pages/index/index.json b/BeadQR/miniprogram/pages/index/index.json deleted file mode 100644 index 3a164a5de25f9e3973d37a973c87fb807ef30625..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/pages/index/index.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "navigationStyle": "custom", - "navigationBarTextStyle": "white", - "backgroundColorTop": "#0f3d3a", - "usingComponents": {} -} diff --git a/BeadQR/miniprogram/pages/index/index.wxml b/BeadQR/miniprogram/pages/index/index.wxml deleted file mode 100644 index 7234757764c44cf005156ba6875b418f5495f838..0000000000000000000000000000000000000000 --- a/BeadQR/miniprogram/pages/index/index.wxml +++ /dev/null @@ -1,403 +0,0 @@ - - - - - 豆维 · 可扫码的拼豆 - - - - - - - MARD 拼豆图纸生成器 - 把一句话藏进拼豆里 - 拼好的作品用微信一扫,就能读出你写的文字或链接 - - 1写一句话 - › - 2选张照片 - › - 3导出图纸 - - - - - - - - 示例二维码 · 写下内容后换成你的 - - - - - - - 上面是演示:写下一句话,这里就会变成你专属的可扫码图纸 - - - 221 色MARD 标准色号 - A4打印图纸 - 扫一扫微信直接读出 - - - 开始制作 · 写下第一句话 - - - - - - - 写下内容后,自动检查能否扫出 - 正在检查能否扫出… - ✓ 自检通过,可以扫出内容 - ! 自检未扫出,建议优化 - 生成后自动检查能否扫码 - - 一键优化 - - - - {{gridText}}格子 - {{beadsText}}颗豆子 - {{colorsText}}种颜色 - - - - 写下内容,生成你的二维码 - 用微信扫一扫验证 - - - - - - - - 1 - - 扫码后要看到什么? - 一句祝福、一个网址,或者你的名字 - - - -