From 83a7ca7a1e130cd33d42993a16c0ca6a4e5331e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B9=B4=E7=B3=95=E5=B4=BD=E5=B4=BD?= Date: Thu, 17 Sep 2026 14:39:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 15 +- eslint.config.js | 7 +- package.json | 4 +- pnpm-lock.yaml | 130 ++- pnpm-workspace.yaml | 10 + src/App.vue | 252 ++++- src/api/request.ts | 86 ++ src/api/user.ts | 99 ++ src/components/AppFooter.vue | 34 - src/components/AppLogos.vue | 59 -- src/components/Confetti.vue | 83 ++ src/components/CountUp.vue | 44 + src/components/EmptyState.vue | 96 ++ src/components/InputEntry.vue | 36 - src/components/Sticker.vue | 62 ++ src/components/TabBar.vue | 193 ++++ src/components/TabIcon.vue | 121 +++ src/components/home/PaneLibrary.vue | 365 ++++++++ src/components/home/PaneMine.vue | 471 ++++++++++ src/components/home/PaneWorkbench.vue | 423 +++++++++ src/config/env.ts | 10 + src/config/tools.ts | 128 +++ src/main.ts | 2 +- src/manifest.json | 2 +- src/pages.json | 77 +- src/pages/account/login.vue | 297 ++++++ src/pages/index.vue | 20 - src/pages/index/index.vue | 172 ++++ src/pages/mine/mine.vue | 14 - src/pages/mine/profile.vue | 253 +++++ src/pages/tools/image/annotate.vue | 577 ++++++++++++ src/pages/tools/image/avatarfx.vue | 423 +++++++++ src/pages/tools/image/collage.vue | 363 ++++++++ src/pages/tools/image/compress.vue | 313 +++++++ src/pages/tools/image/convert.vue | 241 +++++ src/pages/tools/image/crop.vue | 328 +++++++ src/pages/tools/image/grid9.vue | 207 +++++ src/pages/tools/image/index.vue | 115 +++ src/pages/tools/image/matting.vue | 244 +++++ src/pages/tools/image/qrcode.vue | 246 +++++ src/pages/tools/image/resize.vue | 229 +++++ src/pages/tools/image/watermark.vue | 240 +++++ src/pages/workbench/edit.vue | 278 ++++++ src/stores/counter.ts | 10 - src/stores/tools.ts | 51 ++ src/stores/user.ts | 149 +++ src/types/vendor.d.ts | 10 + src/uni.scss | 20 + src/utils/avatar.ts | 15 + src/utils/image/avatarfx.ts | 275 ++++++ src/utils/image/collage.ts | 99 ++ src/utils/image/compress.ts | 115 +++ src/utils/image/convert.ts | 54 ++ src/utils/image/core.ts | 303 ++++++ src/utils/image/matting.ts | 213 +++++ src/utils/image/qrcode.ts | 21 + src/utils/image/resize.ts | 52 ++ src/utils/image/task.ts | 33 + src/utils/image/transform.ts | 41 + src/utils/image/types.ts | 59 ++ src/utils/image/watermark.ts | 56 ++ src/utils/palette.ts | 36 + src/utils/safearea.ts | 36 + src/utils/vendor/qrcode-core.js | 1220 +++++++++++++++++++++++++ tsconfig.json | 27 +- vite.config.ts | 26 +- 66 files changed, 10067 insertions(+), 223 deletions(-) create mode 100644 pnpm-workspace.yaml create mode 100644 src/api/request.ts create mode 100644 src/api/user.ts delete mode 100644 src/components/AppFooter.vue delete mode 100644 src/components/AppLogos.vue create mode 100644 src/components/Confetti.vue create mode 100644 src/components/CountUp.vue create mode 100644 src/components/EmptyState.vue delete mode 100644 src/components/InputEntry.vue create mode 100644 src/components/Sticker.vue create mode 100644 src/components/TabBar.vue create mode 100644 src/components/TabIcon.vue create mode 100644 src/components/home/PaneLibrary.vue create mode 100644 src/components/home/PaneMine.vue create mode 100644 src/components/home/PaneWorkbench.vue create mode 100644 src/config/env.ts create mode 100644 src/config/tools.ts create mode 100644 src/pages/account/login.vue delete mode 100644 src/pages/index.vue create mode 100644 src/pages/index/index.vue delete mode 100644 src/pages/mine/mine.vue create mode 100644 src/pages/mine/profile.vue create mode 100644 src/pages/tools/image/annotate.vue create mode 100644 src/pages/tools/image/avatarfx.vue create mode 100644 src/pages/tools/image/collage.vue create mode 100644 src/pages/tools/image/compress.vue create mode 100644 src/pages/tools/image/convert.vue create mode 100644 src/pages/tools/image/crop.vue create mode 100644 src/pages/tools/image/grid9.vue create mode 100644 src/pages/tools/image/index.vue create mode 100644 src/pages/tools/image/matting.vue create mode 100644 src/pages/tools/image/qrcode.vue create mode 100644 src/pages/tools/image/resize.vue create mode 100644 src/pages/tools/image/watermark.vue create mode 100644 src/pages/workbench/edit.vue delete mode 100644 src/stores/counter.ts create mode 100644 src/stores/tools.ts create mode 100644 src/stores/user.ts create mode 100644 src/types/vendor.d.ts create mode 100644 src/utils/avatar.ts create mode 100644 src/utils/image/avatarfx.ts create mode 100644 src/utils/image/collage.ts create mode 100644 src/utils/image/compress.ts create mode 100644 src/utils/image/convert.ts create mode 100644 src/utils/image/core.ts create mode 100644 src/utils/image/matting.ts create mode 100644 src/utils/image/qrcode.ts create mode 100644 src/utils/image/resize.ts create mode 100644 src/utils/image/task.ts create mode 100644 src/utils/image/transform.ts create mode 100644 src/utils/image/types.ts create mode 100644 src/utils/image/watermark.ts create mode 100644 src/utils/palette.ts create mode 100644 src/utils/safearea.ts create mode 100644 src/utils/vendor/qrcode-core.js diff --git a/components.d.ts b/components.d.ts index 0dea9b2..37b874e 100644 --- a/components.d.ts +++ b/components.d.ts @@ -7,10 +7,19 @@ export {} declare module 'vue' { export interface GlobalComponents { - AppFooter: typeof import('./src/components/AppFooter.vue')['default'] - AppLogos: typeof import('./src/components/AppLogos.vue')['default'] - InputEntry: typeof import('./src/components/InputEntry.vue')['default'] + Confetti: typeof import('./src/components/Confetti.vue')['default'] + CountUp: typeof import('./src/components/CountUp.vue')['default'] + EmptyState: typeof import('./src/components/EmptyState.vue')['default'] + PaneLibrary: typeof import('./src/components/home/PaneLibrary.vue')['default'] + PaneMine: typeof import('./src/components/home/PaneMine.vue')['default'] + PaneWorkbench: typeof import('./src/components/home/PaneWorkbench.vue')['default'] + Sticker: typeof import('./src/components/Sticker.vue')['default'] + TabBar: typeof import('./src/components/TabBar.vue')['default'] + TabIcon: typeof import('./src/components/TabIcon.vue')['default'] WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default'] + WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default'] + WdInput: typeof import('wot-design-uni/components/wd-input/wd-input.vue')['default'] WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default'] + WdTextarea: typeof import('wot-design-uni/components/wd-textarea/wd-textarea.vue')['default'] } } diff --git a/eslint.config.js b/eslint.config.js index 9bdf2e1..bb2a420 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,8 @@ import uniHelper from '@uni-helper/eslint-config' -export default uniHelper() +export default uniHelper({ + ignores: [ + // QR 编码核心:vendored 第三方实现(qrcode-terminal MIT),不参与 lint + 'src/utils/vendor/**', + ], +}) diff --git a/package.json b/package.json index cb215e0..3564f36 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,8 @@ "@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001", "@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001", "@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001", + "heic2any": "^0.0.4", + "onnxruntime-web": "^1.29.0", "pinia": "2.2.4", "vue": "3.4.21", "vue-i18n": "9.6.2", @@ -88,4 +90,4 @@ "vite": "5.2.8", "vue-tsc": "^3.0.7" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca19c69..a10091a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,12 @@ importers: '@dcloudio/uni-quickapp-webview': specifier: 3.0.0-4070620250821001 version: 3.0.0-4070620250821001(postcss@8.5.6)(rollup@4.52.2)(vue@3.4.21(typescript@5.9.2)) + heic2any: + specifier: ^0.0.4 + version: 0.0.4 + onnxruntime-web: + specifier: ^1.29.0 + version: 1.29.0 pinia: specifier: 2.2.4 version: 2.2.4(typescript@5.9.2)(vue@3.4.21(typescript@5.9.2)) @@ -1451,6 +1457,33 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -1494,56 +1527,67 @@ packages: resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.2': resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.2': resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.2': resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.2': resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.2': resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.2': resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.2': resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.2': resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.2': resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.2': resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.2': resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==} @@ -2607,6 +2651,7 @@ packages: eslint@9.36.0: resolution: {integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2738,6 +2783,9 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -2827,7 +2875,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global@4.4.0: resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} @@ -2857,6 +2905,9 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2880,6 +2931,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + heic2any@0.0.4: + resolution: {integrity: sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==} + html-encoding-sniffer@2.0.1: resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} engines: {node: '>=10'} @@ -3325,6 +3379,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -3622,6 +3679,12 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onnxruntime-common@1.29.0: + resolution: {integrity: sha512-/F63/e2VJoaVXGGNu6S5QH7jivBThGO95OzAVXXQ8hTta/b1QxI8udHa6cI3+3mAb5WWIIaMMwfZw01oivjJ1g==} + + onnxruntime-web@1.29.0: + resolution: {integrity: sha512-LuQlpX6MFLJZu756erwUeb1mNfoJGbs1kzDwJGNlf5RvfYMdqhcY3vNpDPK40CUV2HoWTkIj+uS0o36GFHjeYw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3727,6 +3790,7 @@ packages: phin@3.7.1: resolution: {integrity: sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==} engines: {node: '>= 8'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3773,6 +3837,9 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3862,6 +3929,10 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4425,6 +4496,7 @@ packages: vue-i18n@9.6.2: resolution: {integrity: sha512-J43grTQjPR8LCUxvx3mkoM+11xhTnej1Al4lvJCEeKmQqf8eqbuYPQb54HXnEg/UzZyaxLBAwPAUTbrZ8V7hcg==} engines: {node: '>= 16'} + deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html peerDependencies: vue: ^3.0.0 @@ -4471,6 +4543,7 @@ packages: whatwg-encoding@1.0.5: resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@2.3.0: resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} @@ -6763,6 +6836,26 @@ snapshots: '@pkgr/core@0.2.9': {} + '@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': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + '@rollup/pluginutils@5.3.0(rollup@4.52.2)': dependencies: '@types/estree': 1.0.8 @@ -8211,6 +8304,8 @@ snapshots: flatted: 3.3.3 keyv: 4.5.4 + flatbuffers@25.9.23: {} + flatted@3.3.3: {} follow-redirects@1.15.11: {} @@ -8316,6 +8411,8 @@ snapshots: graphemer@1.4.0: {} + guid-typescript@1.0.9: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -8332,6 +8429,8 @@ snapshots: he@1.2.0: {} + heic2any@0.0.4: {} + html-encoding-sniffer@2.0.1: dependencies: whatwg-encoding: 1.0.5 @@ -8993,6 +9092,8 @@ snapshots: lodash@4.17.21: {} + long@5.3.2: {} + longest-streak@3.1.0: {} lru-cache@5.1.1: @@ -9442,6 +9543,17 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onnxruntime-common@1.29.0: {} + + onnxruntime-web@1.29.0: + dependencies: + flatbuffers: 25.9.23 + guid-typescript: 1.0.9 + long: 5.3.2 + onnxruntime-common: 1.29.0 + platform: 1.3.6 + protobufjs: 7.6.6 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -9581,6 +9693,8 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 + platform@1.3.6: {} + pluralize@8.0.0: {} pngjs@3.4.0: {} @@ -9669,6 +9783,20 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + protobufjs@7.6.6: + 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.2 + '@types/node': 24.5.2 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..e9a2e06 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,10 @@ +allowBuilds: + core-js: true + esbuild: true + protobufjs: true + vue-demi: true +onlyBuiltDependencies: + - core-js + - esbuild + - protobufjs + - vue-demi diff --git a/src/App.vue b/src/App.vue index 297d679..c79c8fb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,255 @@ - + diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100644 index 0000000..2d3e1b0 --- /dev/null +++ b/src/api/request.ts @@ -0,0 +1,86 @@ +/** 统一响应约定:{code, message, result},code=0 成功,401 登录失效 */ +import { BASE_URL } from '@/config/env' + +export interface ApiBody { + code: number + message: string + result: T +} + +const TOKEN_KEY = 'token' + +export function getToken(): string { + return (uni.getStorageSync(TOKEN_KEY) as string) || '' +} + +export function setToken(token: string) { + uni.setStorageSync(TOKEN_KEY, token) +} + +export function clearToken() { + uni.removeStorageSync(TOKEN_KEY) +} + +/** H5:401 时引导去登录页(登录页自身不跳,避免循环) */ +export function gotoH5Login() { + // #ifdef H5 + const pages = getCurrentPages() + const current = pages[pages.length - 1] + const route = (current && current.route) || '' + if (route.includes('account/login')) + return + const redirect = route ? `?redirect=/${route}` : '' + uni.redirectTo({ url: `/pages/account/login${redirect}` }) + // #endif +} + +function request(url: string, method: 'GET' | 'POST' | 'PUT', data?: unknown): Promise { + const header: Record = {} + const token = getToken() + if (token) + header.Authorization = `Bearer ${token}` + + return new Promise((resolve, reject) => { + uni.request({ + url: BASE_URL + url, + method, + data: data as Record | undefined, + header, + timeout: 15000, + success: (res) => { + const body = res.data as ApiBody + if (body && body.code === 0) { + resolve(body.result) + return + } + if (body && body.code === 401) { + clearToken() + gotoH5Login() + reject(new Error(body.message || 'unauthorized')) + return + } + // 4001 等业务错误码也走统一提示 + uni.showToast({ title: (body && body.message) || '请求失败', icon: 'none' }) + const err = new Error((body && body.message) || 'request failed') + ;(err as Error & { code?: number }).code = body && body.code + reject(err) + }, + fail: (err) => { + uni.showToast({ title: '网络异常,请检查服务是否启动', icon: 'none' }) + reject(new Error(err.errMsg || 'network error')) + }, + }) + }) +} + +export function get(url: string, params?: Record): Promise { + return request(url, 'GET', params) +} + +export function post(url: string, data?: unknown): Promise { + return request(url, 'POST', data) +} + +export function put(url: string, data?: unknown): Promise { + return request(url, 'PUT', data) +} diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..6c84a3c --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,99 @@ +import { get, post, put } from '@/api/request' + +export interface UserInfo { + id: number + nickname: string + avatar_url: string +} + +export interface LevelInfo { + level_key: string + name: string + modules: string[] +} + +export interface LoginResult { + token: string + user: UserInfo + level: LevelInfo +} + +export interface ModuleOut { + module_key: string + name: string + icon: string + locked: boolean + enabled_tools_count: number +} + +export interface ToolOut { + tool_key: string + module_key: string + name: string + icon: string + description: string + is_hot: boolean + locked: boolean +} + +export interface ToolsListResult { + modules: ModuleOut[] + list: ToolOut[] +} + +export function apiWxLogin(code: string) { + return post('/user/wx-login', { code }) +} + +export function apiDevLogin() { + return post('/user/dev-login') +} + +/** H5 账号注册(注册即登录) */ +export function apiRegister(username: string, password: string, nickname = '') { + return post('/user/register', { username, password, nickname }) +} + +/** H5 账号密码登录 */ +export function apiAccountLogin(username: string, password: string) { + return post('/user/login', { username, password }) +} + +/** 把 H5 独立账号合并进当前登录账号(工作台随迁) */ +export function apiBindAccount(username: string, password: string) { + return post<{ username: string }>('/user/bind-account', { username, password }) +} + +/** 更新个人信息(昵称 / 头像) */ +export function apiUpdateProfile(nickname: string, avatarUrl: string) { + return put<{ nickname: string, avatar_url: string }>('/user/profile', { nickname, avatar_url: avatarUrl }) +} + +export function apiGetLevel() { + return get('/user/level') +} + +/** 当前登录用户信息(token 校验后恢复 user) */ +export function apiGetUserInfo() { + return get('/user/info') +} + +export function apiToolsList() { + return get('/tools/list') +} + +export function apiToolsUsed(toolKey: string) { + return post('/tools/used', { tool_key: toolKey }) +} + +export function apiWorkbenchGet() { + return get<{ tool_keys: string[] }>('/workbench/get') +} + +export function apiWorkbenchSave(toolKeys: string[]) { + return post('/workbench/save', { tool_keys: toolKeys }) +} + +export function apiFeedbackSubmit(content: string, contact = '') { + return post('/feedback/submit', { content, contact }) +} diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue deleted file mode 100644 index 61049b6..0000000 --- a/src/components/AppFooter.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/src/components/AppLogos.vue b/src/components/AppLogos.vue deleted file mode 100644 index 8779ca6..0000000 --- a/src/components/AppLogos.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/src/components/Confetti.vue b/src/components/Confetti.vue new file mode 100644 index 0000000..6b09adb --- /dev/null +++ b/src/components/Confetti.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue new file mode 100644 index 0000000..4f0a72e --- /dev/null +++ b/src/components/CountUp.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/components/EmptyState.vue b/src/components/EmptyState.vue new file mode 100644 index 0000000..ba30247 --- /dev/null +++ b/src/components/EmptyState.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/components/InputEntry.vue b/src/components/InputEntry.vue deleted file mode 100644 index 10bc891..0000000 --- a/src/components/InputEntry.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/src/components/Sticker.vue b/src/components/Sticker.vue new file mode 100644 index 0000000..a9984f1 --- /dev/null +++ b/src/components/Sticker.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/components/TabBar.vue b/src/components/TabBar.vue new file mode 100644 index 0000000..503d428 --- /dev/null +++ b/src/components/TabBar.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/components/TabIcon.vue b/src/components/TabIcon.vue new file mode 100644 index 0000000..cda1106 --- /dev/null +++ b/src/components/TabIcon.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/components/home/PaneLibrary.vue b/src/components/home/PaneLibrary.vue new file mode 100644 index 0000000..3427b70 --- /dev/null +++ b/src/components/home/PaneLibrary.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/src/components/home/PaneMine.vue b/src/components/home/PaneMine.vue new file mode 100644 index 0000000..6e0e606 --- /dev/null +++ b/src/components/home/PaneMine.vue @@ -0,0 +1,471 @@ + + + + + diff --git a/src/components/home/PaneWorkbench.vue b/src/components/home/PaneWorkbench.vue new file mode 100644 index 0000000..44139f1 --- /dev/null +++ b/src/components/home/PaneWorkbench.vue @@ -0,0 +1,423 @@ + + + + + diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000..4edabb6 --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,10 @@ +/** + * 环境配置 + * 微信开发者工具联调需勾选「不校验合法域名」;生产改为 https 域名(需在小程序后台配置 request 合法域名) + */ +let baseUrl = 'http://127.0.0.1:8000' +// #ifdef H5 +baseUrl = '/api' +// #endif + +export const BASE_URL = baseUrl diff --git a/src/config/tools.ts b/src/config/tools.ts new file mode 100644 index 0000000..2bc0ee9 --- /dev/null +++ b/src/config/tools.ts @@ -0,0 +1,128 @@ +/** + * 工具注册表(前端路由与展示的唯一来源) + * + * 新增工具 SOP(见 docs/design-levels.md §7): + * 1. 在 pages.json 注册工具页面 + * 2. 在此登记:key 必须与后台工具目录 tool_key 一致 + * 3. 后台「工具管理」新增目录记录并挂到模块 + */ +export interface ToolDef { + key: string + name: string + icon: string // wot 图标名 + desc: string + path: string + module: string + keywords: string[] // 工具库搜索关键词 +} + +export const TOOL_REGISTRY: ToolDef[] = [ + { + key: 'img-compress', + name: '图片压缩', + icon: 'download', + desc: '批量压缩体积,画质几乎无损', + path: '/pages/tools/image/compress', + module: 'image', + keywords: ['压缩', '体积', '变小', 'compress'], + }, + { + key: 'img-convert', + name: '格式转换', + icon: 'swap', + desc: 'PNG / JPG / WebP 互转', + path: '/pages/tools/image/convert', + module: 'image', + keywords: ['转换', 'png', 'jpg', 'jpeg', 'webp', 'convert'], + }, + { + key: 'img-resize', + name: '修改尺寸', + icon: 'zoom-in', + desc: '按宽高、比例或最长边缩放', + path: '/pages/tools/image/resize', + module: 'image', + keywords: ['尺寸', '缩放', '大小', 'resize'], + }, + { + key: 'img-crop', + name: '裁剪旋转', + icon: 'scan', + desc: '自由裁剪、旋转与镜像', + path: '/pages/tools/image/crop', + module: 'image', + keywords: ['裁剪', '旋转', '镜像', 'crop'], + }, + { + key: 'img-watermark', + name: '文字水印', + icon: 'edit-outline', + desc: '平铺或居中的文字水印', + path: '/pages/tools/image/watermark', + module: 'image', + keywords: ['水印', '签名', 'watermark'], + }, + { + key: 'img-matting', + name: '去除背景', + icon: 'image', + desc: '一键抠除纯色背景', + path: '/pages/tools/image/matting', + module: 'image', + keywords: ['抠图', '去背景', '透明', 'matting'], + }, + { + key: 'img-avatarfx', + name: '头像特效', + icon: 'camera', + desc: '国庆红旗圈、节日边框等头像特效', + path: '/pages/tools/image/avatarfx', + module: 'image', + keywords: ['头像', '国旗', '红旗', '国庆', '边框', '特效', '节日'], + }, + { + key: 'img-grid9', + name: '九宫格切图', + icon: 'dashboard', + desc: '照片切成 9 张发朋友圈', + path: '/pages/tools/image/grid9', + module: 'image', + keywords: ['九宫格', '切图', '朋友圈', '拼接', 'grid'], + }, + { + key: 'img-collage', + name: '图片拼图', + icon: 'copy', + desc: '2-4 张照片拼成一张', + path: '/pages/tools/image/collage', + module: 'image', + keywords: ['拼图', '合并', '拼接', 'collage'], + }, + { + key: 'img-qrcode', + name: '二维码生成', + icon: 'qrcode', + desc: '文本链接生成二维码图片', + path: '/pages/tools/image/qrcode', + module: 'image', + keywords: ['二维码', 'qr', '链接', '扫码'], + }, + { + key: 'img-annotate', + name: '图片标注', + icon: 'edit', + desc: '涂鸦、文字、马赛克标注', + path: '/pages/tools/image/annotate', + module: 'image', + keywords: ['标注', '涂鸦', '马赛克', '箭头', '打码'], + }, +] + +export function searchTools(keyword: string): ToolDef[] { + const kw = keyword.trim().toLowerCase() + if (!kw) + return TOOL_REGISTRY + return TOOL_REGISTRY.filter( + t => t.name.toLowerCase().includes(kw) || t.desc.toLowerCase().includes(kw) || t.keywords.some(k => k.toLowerCase().includes(kw)), + ) +} diff --git a/src/main.ts b/src/main.ts index 4294d8e..34b158c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ -import { createSSRApp } from 'vue' import * as Pinia from 'pinia' +import { createSSRApp } from 'vue' import App from './App.vue' export function createApp() { diff --git a/src/manifest.json b/src/manifest.json index 3458c59..41d8e9d 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -49,7 +49,7 @@ }, "quickapp": {}, "mp-weixin": { - "appid": "wx67e73fb3c9882dfc", + "appid": "wx8f3891eaad380949", "setting": { "urlCheck": false }, diff --git a/src/pages.json b/src/pages.json index 4a4de3e..cb446fe 100644 --- a/src/pages.json +++ b/src/pages.json @@ -1,26 +1,69 @@ { "pages": [ { - "path": "pages/index", - "type": "home" + "path": "pages/index/index", + "type": "home", + "style": { + "enablePullDownRefresh": true, + "backgroundTextStyle": "dark", + "backgroundColor": "#FFF9F2" + } }, { - "path": "pages/mine/mine", - "style": { - "navigationBarTitleText": "我的", - "enablePullDownRefresh": false - } + "path": "pages/workbench/edit" + }, + { + "path": "pages/mine/profile" + }, + { + "path": "pages/tools/image/index" + }, + { + "path": "pages/tools/image/compress" + }, + { + "path": "pages/tools/image/convert" + }, + { + "path": "pages/tools/image/resize" + }, + { + "path": "pages/tools/image/crop" + }, + { + "path": "pages/tools/image/watermark" + }, + { + "path": "pages/tools/image/matting" + }, + { + "path": "pages/tools/image/avatarfx" + }, + { + "path": "pages/tools/image/grid9" + }, + { + "path": "pages/tools/image/collage" + }, + { + "path": "pages/tools/image/qrcode" + }, + { + "path": "pages/tools/image/annotate" + }, + // #ifdef H5 + { + "path": "pages/account/login" } + // #endif ], "globalStyle": { - "backgroundColor": "@bgColor", - "backgroundColorBottom": "@bgColorBottom", - "backgroundColorTop": "@bgColorTop", - "backgroundTextStyle": "@bgTxtStyle", - "navigationBarBackgroundColor": "#000000", - "navigationBarTextStyle": "@navTxtStyle", - "navigationBarTitleText": "Uni Creator", - "navigationStyle": "custom" - }, - "subPackages": [] + "navigationStyle": "custom", + "navigationBarTitleText": "工具百宝箱", + "navigationBarBackgroundColor": "#FFF9F2", + "navigationBarTextStyle": "black", + "backgroundColor": "#FFF9F2", + "backgroundColorTop": "#FFF9F2", + "backgroundColorBottom": "#FFF9F2" + } } diff --git a/src/pages/account/login.vue b/src/pages/account/login.vue new file mode 100644 index 0000000..af6032b --- /dev/null +++ b/src/pages/account/login.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/src/pages/index.vue b/src/pages/index.vue deleted file mode 100644 index 553811e..0000000 --- a/src/pages/index.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue new file mode 100644 index 0000000..9208f07 --- /dev/null +++ b/src/pages/index/index.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/src/pages/mine/mine.vue b/src/pages/mine/mine.vue deleted file mode 100644 index 2c75593..0000000 --- a/src/pages/mine/mine.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/src/pages/mine/profile.vue b/src/pages/mine/profile.vue new file mode 100644 index 0000000..7e17ac5 --- /dev/null +++ b/src/pages/mine/profile.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/src/pages/tools/image/annotate.vue b/src/pages/tools/image/annotate.vue new file mode 100644 index 0000000..4d69c90 --- /dev/null +++ b/src/pages/tools/image/annotate.vue @@ -0,0 +1,577 @@ + + + + + diff --git a/src/pages/tools/image/avatarfx.vue b/src/pages/tools/image/avatarfx.vue new file mode 100644 index 0000000..3e3ea50 --- /dev/null +++ b/src/pages/tools/image/avatarfx.vue @@ -0,0 +1,423 @@ + + + + + diff --git a/src/pages/tools/image/collage.vue b/src/pages/tools/image/collage.vue new file mode 100644 index 0000000..e3886f0 --- /dev/null +++ b/src/pages/tools/image/collage.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/src/pages/tools/image/compress.vue b/src/pages/tools/image/compress.vue new file mode 100644 index 0000000..64b24b8 --- /dev/null +++ b/src/pages/tools/image/compress.vue @@ -0,0 +1,313 @@ + + + + + diff --git a/src/pages/tools/image/convert.vue b/src/pages/tools/image/convert.vue new file mode 100644 index 0000000..15f3a39 --- /dev/null +++ b/src/pages/tools/image/convert.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/src/pages/tools/image/crop.vue b/src/pages/tools/image/crop.vue new file mode 100644 index 0000000..c2c2ee2 --- /dev/null +++ b/src/pages/tools/image/crop.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/src/pages/tools/image/grid9.vue b/src/pages/tools/image/grid9.vue new file mode 100644 index 0000000..a6af999 --- /dev/null +++ b/src/pages/tools/image/grid9.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/pages/tools/image/index.vue b/src/pages/tools/image/index.vue new file mode 100644 index 0000000..1948418 --- /dev/null +++ b/src/pages/tools/image/index.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/pages/tools/image/matting.vue b/src/pages/tools/image/matting.vue new file mode 100644 index 0000000..873e88e --- /dev/null +++ b/src/pages/tools/image/matting.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/pages/tools/image/qrcode.vue b/src/pages/tools/image/qrcode.vue new file mode 100644 index 0000000..89a4850 --- /dev/null +++ b/src/pages/tools/image/qrcode.vue @@ -0,0 +1,246 @@ + + +