From 3a42f65c5aac57ad08a58637084a8c0e283ab323 Mon Sep 17 00:00:00 2001 From: zhangjianjun Date: Tue, 18 Aug 2026 17:18:45 +0800 Subject: [PATCH] feat: native progress query, resubmit and map picker Replace the WebView shell with a native check page that accepts H5 launch params, queries orders, resubmits materials with Qiniu uploads, and picks map locations on a WeChat Map with reverse geocoding. --- .env.development | 5 +- .env.production | 5 +- AGENTS.md | 21 +- package.json | 56 +- pnpm-lock.yaml | 597 ++++++++++++++++++++ src/api/pay.ts | 17 + src/api/user.ts | 76 +++ src/app.config.ts | 20 +- src/assets/images/landing-storefronts.jpg | Bin 123074 -> 0 bytes src/lib/auth.test.ts | 47 ++ src/lib/auth.ts | 25 + src/lib/bootstrap.ts | 23 + src/lib/cdn.ts | 14 + src/lib/contact.test.ts | 34 ++ src/lib/contact.ts | 35 ++ src/lib/crypto.test.ts | 21 + src/lib/crypto.ts | 18 + src/lib/geocode.test.ts | 73 +++ src/lib/geocode.ts | 88 +++ src/lib/launch.test.ts | 103 ++++ src/lib/launch.ts | 83 +++ src/lib/order.test.ts | 95 ++++ src/lib/order.ts | 160 ++++++ src/lib/origin.test.ts | 56 ++ src/lib/origin.ts | 26 + src/lib/phone.test.ts | 18 + src/lib/phone.ts | 5 + src/lib/qiniu.test.ts | 31 ++ src/lib/qiniu.ts | 37 ++ src/lib/request.ts | 144 +++++ src/lib/reverseGeocode.ts | 20 + src/lib/session.ts | 105 ++++ src/lib/sign.test.ts | 50 ++ src/lib/sign.ts | 31 ++ src/lib/status.ts | 23 + src/lib/uuid.ts | 7 + src/pages/index/contact.tsx | 101 ++++ src/pages/index/index.config.ts | 4 +- src/pages/index/index.scss | 616 +++++++++++++++++++++ src/pages/index/index.tsx | 342 +++++++++--- src/pages/index/resubmit.tsx | 331 +++++++++++ src/pages/landing/index.scss | 305 ---------- src/pages/landing/index.tsx | 294 ---------- src/pages/{landing => map}/index.config.ts | 5 +- src/pages/map/index.scss | 112 ++++ src/pages/map/index.tsx | 193 +++++++ src/pages/map/picker.ts | 31 ++ src/pages/pay/index.config.ts | 3 - src/pages/pay/index.scss | 114 ---- src/pages/pay/index.tsx | 208 ------- src/utils/base64.ts | 60 -- types/global.d.ts | 6 + vitest.config.ts | 14 + 53 files changed, 3798 insertions(+), 1110 deletions(-) create mode 100644 src/api/pay.ts create mode 100644 src/api/user.ts delete mode 100644 src/assets/images/landing-storefronts.jpg create mode 100644 src/lib/auth.test.ts create mode 100644 src/lib/auth.ts create mode 100644 src/lib/bootstrap.ts create mode 100644 src/lib/cdn.ts create mode 100644 src/lib/contact.test.ts create mode 100644 src/lib/contact.ts create mode 100644 src/lib/crypto.test.ts create mode 100644 src/lib/crypto.ts create mode 100644 src/lib/geocode.test.ts create mode 100644 src/lib/geocode.ts create mode 100644 src/lib/launch.test.ts create mode 100644 src/lib/launch.ts create mode 100644 src/lib/order.test.ts create mode 100644 src/lib/order.ts create mode 100644 src/lib/origin.test.ts create mode 100644 src/lib/origin.ts create mode 100644 src/lib/phone.test.ts create mode 100644 src/lib/phone.ts create mode 100644 src/lib/qiniu.test.ts create mode 100644 src/lib/qiniu.ts create mode 100644 src/lib/request.ts create mode 100644 src/lib/reverseGeocode.ts create mode 100644 src/lib/session.ts create mode 100644 src/lib/sign.test.ts create mode 100644 src/lib/sign.ts create mode 100644 src/lib/status.ts create mode 100644 src/lib/uuid.ts create mode 100644 src/pages/index/contact.tsx create mode 100644 src/pages/index/resubmit.tsx delete mode 100644 src/pages/landing/index.scss delete mode 100644 src/pages/landing/index.tsx rename src/pages/{landing => map}/index.config.ts (51%) create mode 100644 src/pages/map/index.scss create mode 100644 src/pages/map/index.tsx create mode 100644 src/pages/map/picker.ts delete mode 100644 src/pages/pay/index.config.ts delete mode 100644 src/pages/pay/index.scss delete mode 100644 src/pages/pay/index.tsx delete mode 100644 src/utils/base64.ts create mode 100644 vitest.config.ts diff --git a/.env.development b/.env.development index 1804f79..d231aa4 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,3 @@ -# 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config -TARO_APP_ID="wx6d4f6f29c41aff93" +TARO_APP_API_ORIGIN=https://corp-test.batiao8.com +TARO_APP_API_HOST=nb.batiao8.com +TARO_APP_TIANDITU_KEY=ad8b251afd667ea8b8bc6f33df2ce198 diff --git a/.env.production b/.env.production index c192f7e..c83f994 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,3 @@ -# TARO_APP_ID="生产环境下的小程序 AppID" -TARO_APP_ID="wx6d4f6f29c41aff93" \ No newline at end of file +TARO_APP_API_ORIGIN=https://nb.zuom8.cn +TARO_APP_API_HOST=nb.zuom8.cn +TARO_APP_TIANDITU_KEY=ad8b251afd667ea8b8bc6f33df2ce198 diff --git a/AGENTS.md b/AGENTS.md index 5859b41..63ecd6d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,17 +23,22 @@ Other targets follow `pnpm dev:` / `pnpm build:` (`alipay`, ## Architecture -The app is a thin native shell around a business H5, with a native payment bridge. Two pages, registered in `src/app.config.ts`: +Native mini-program for map-label progress query. Pages in `src/app.config.ts`: -**1. `pages/index/index.tsx` — auth + WebView host.** On load it calls `Taro.login()` to get the WeChat `code`, then renders a full-screen `` pointing at the business H5 (`http://niubsw.com/...?mpCode=`). All business UI lives in that remote H5, not in this repo. +**`pages/index/index` — 进度查询.** Accepts H5 jump query `token` / `host` / `package` / `scene` (see `corp-h5` `weixinMiniProgram.ts`). Queries `GET /api/h5/order/phone`, resubmits with `PUT /api/h5/order`, uploads via `GET /api/presign` + Qiniu. -**2. `pages/pay/index.tsx` — native payment bridge.** The H5 cannot invoke WeChat JSAPI pay while running inside the mini-program WebView, so it hands off to this native page. The handoff contract: - - The H5 detects it's inside the mini-program and calls `wxSdk.miniProgram.navigateTo({ url: '/pages/pay/index?order=&formInfo=' })`. - - Params are **base64-encoded JSON**, produced H5-side by `utf8ToBase64(v) = btoa(unescape(encodeURIComponent(JSON.stringify(v))))`. - - `order` already contains the complete WeChat JSAPI fields (`timeStamp`/`nonceStr`/`package`/`signType`/`paySign`). The page decodes it, auto-fires `Taro.requestPayment`, then `navigateBack()`s to the H5 on success. No backend call is made here. - - `temp.txt` (repo root) holds the H5-side snippet documenting this contract — keep the two sides in sync. +**`pages/map/index` — 微信原生地图选点.** Tap / locate / `chooseLocation` search, returns GCJ-02 `lng,lat`. -**`src/utils/base64.ts`** is the strict inverse of the H5 encoder. It hand-rolls a base64 decoder because **weapp has no global `atob`**, and normalizes `+`→space mangling from URL routing. Reuse `decodeOrderParam(raw)` for any future base64 route params. +### Session and request + +- API origin comes from `TARO_APP_API_ORIGIN`: `.env.development` → `http://corp-test.batiao8.com`, `.env.production` → `https://nb.zuom8.cn`. +- `x-host` prefers the H5 jump `host`, then `TARO_APP_API_HOST`. +- Map reverse geocode uses `TARO_APP_TIANDITU_KEY` against `api.tianditu.gov.cn` (add this request domain in WeChat admin). +- Missing launch query fields fall back to `DEFAULT_LAUNCH_SCHEME` in `src/lib/launch.ts` (`scene=/h/KZ9Q`, `host=nb.batiao8.com`, `package=10044`, `phone=13800138000`). +- No token: `GET /api/user/config` then keep the returned guest token. Then `GET /api/h5/corp` for package/config. +- Shared client: `src/lib/request.ts` (same MD5 sign + AES decrypt as H5). Platform header is `wx-mp`. + +Do not reintroduce a business H5 WebView unless explicitly requested. ## Conventions diff --git a/package.json b/package.json index 757385a..e243176 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "dev:rn": "npm run build:rn -- --watch", "dev:qq": "npm run build:qq -- --watch", "dev:jd": "npm run build:jd -- --watch", - "dev:harmony-hybrid": "npm run build:harmony-hybrid -- --watch" + "dev:harmony-hybrid": "npm run build:harmony-hybrid -- --watch", + "test": "vitest run" }, "browserslist": { "development": [ @@ -47,48 +48,51 @@ "@babel/runtime": "^7.24.4", "@tarojs/components": "4.2.0", "@tarojs/helper": "4.2.0", - "@tarojs/plugin-platform-weapp": "4.2.0", + "@tarojs/plugin-framework-react": "4.2.0", "@tarojs/plugin-platform-alipay": "4.2.0", - "@tarojs/plugin-platform-tt": "4.2.0", - "@tarojs/plugin-platform-swan": "4.2.0", - "@tarojs/plugin-platform-jd": "4.2.0", - "@tarojs/plugin-platform-qq": "4.2.0", "@tarojs/plugin-platform-h5": "4.2.0", "@tarojs/plugin-platform-harmony-hybrid": "4.2.0", + "@tarojs/plugin-platform-jd": "4.2.0", + "@tarojs/plugin-platform-qq": "4.2.0", + "@tarojs/plugin-platform-swan": "4.2.0", + "@tarojs/plugin-platform-tt": "4.2.0", + "@tarojs/plugin-platform-weapp": "4.2.0", + "@tarojs/react": "4.2.0", "@tarojs/runtime": "4.2.0", "@tarojs/shared": "4.2.0", "@tarojs/taro": "4.2.0", - "@tarojs/plugin-framework-react": "4.2.0", - "@tarojs/react": "4.2.0", - "react-dom": "^18.0.0", - "react": "^18.0.0" + "crypto-js": "^4.2.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "devDependencies": { - "@tarojs/plugin-generator": "4.2.0", - "@commitlint/cli": "^19.8.1", - "@commitlint/config-conventional": "^19.8.1", - "lint-staged": "^16.1.2", - "husky": "^9.1.7", - "stylelint-config-standard": "^38.0.0", "@babel/core": "^7.24.4", "@babel/plugin-transform-class-properties": "7.25.9", - "@tarojs/cli": "4.2.0", - "@tarojs/vite-runner": "4.2.0", - "babel-preset-taro": "4.2.0", - "eslint-config-taro": "4.2.0", - "eslint": "^8.57.0", - "stylelint": "^16.4.0", - "terser": "^5.30.4", - "vite": "^4.2.0", "@babel/preset-react": "^7.24.1", + "@commitlint/cli": "^19.8.1", + "@commitlint/config-conventional": "^19.8.1", + "@tarojs/cli": "4.2.0", + "@tarojs/plugin-generator": "4.2.0", + "@tarojs/vite-runner": "4.2.0", + "@types/crypto-js": "^4.2.2", + "@types/minimatch": "^5", "@types/react": "^18.0.0", "@vitejs/plugin-react": "^4.3.0", + "babel-preset-taro": "4.2.0", + "eslint": "^8.57.0", + "eslint-config-taro": "4.2.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.4.0", + "husky": "^9.1.7", + "lint-staged": "^16.1.2", + "postcss": "^8.5.6", "react-refresh": "^0.14.0", "sass": "^1.75.0", + "stylelint": "^16.4.0", + "stylelint-config-standard": "^38.0.0", + "terser": "^5.30.4", "typescript": "^5.4.5", - "postcss": "^8.5.6", - "@types/minimatch": "^5" + "vite": "^4.2.0", + "vitest": "^2.1.9" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32be937..b248af7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@tarojs/taro': specifier: 4.2.0 version: 4.2.0(@tarojs/components@4.2.0(@tarojs/helper@4.2.0)(@types/react@18.3.31)(postcss@8.5.15)(rollup@3.30.0))(@tarojs/helper@4.2.0)(@tarojs/shared@4.2.0)(@types/react@18.3.31)(postcss@8.5.15)(rollup@3.30.0) + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 react: specifier: ^18.0.0 version: 18.3.1 @@ -87,6 +90,9 @@ importers: '@tarojs/vite-runner': specifier: 4.2.0 version: 4.2.0(@tarojs/runtime@4.2.0)(@types/babel__core@7.20.5)(jiti@2.6.1)(postcss@8.5.15)(rollup@3.30.0)(terser@5.48.0)(typescript@5.9.3)(vite@4.5.14(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0)) + '@types/crypto-js': + specifier: ^4.2.2 + version: 4.2.2 '@types/minimatch': specifier: ^5 version: 5.1.2 @@ -141,6 +147,9 @@ importers: vite: specifier: ^4.2.0 version: 4.5.14(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0) + vitest: + specifier: ^2.1.9 + version: 2.1.9(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0) packages: @@ -1253,6 +1262,13 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + '@napi-rs/triples@1.2.0': resolution: {integrity: sha512-HAPjR3bnCsdXBsATpDIP5WCrw0JcACwhhrwIAQhiR46n+jm+a2F8kBsfseAuWtSyQ+H3Yebt2k43B5dy+04yMA==} @@ -1425,6 +1441,144 @@ packages: rollup: optional: true + '@rollup/rollup-android-arm-eabi@4.62.4': + resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.4': + resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.4': + resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.4': + resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.4': + resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.4': + resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.4': + resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.4': + resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.4': + resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.4': + resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.4': + resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.4': + resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.4': + resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.4': + resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.4': + resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.4': + resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.4': + resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.4': + resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.4': + resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.4': + resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.4': + resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1927,6 +2081,9 @@ packages: '@types/conventional-commits-parser@5.0.2': resolution: {integrity: sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==} + '@types/crypto-js@4.2.2': + resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -2056,6 +2213,35 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2171,6 +2357,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -2326,6 +2516,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cacheable-request@2.1.4: resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==} @@ -2368,6 +2562,10 @@ packages: resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==} engines: {node: '>=4'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -2386,6 +2584,10 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2547,6 +2749,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. + css-functions-list@3.3.3: resolution: {integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==} engines: {node: '>=12'} @@ -2636,6 +2842,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -2759,6 +2969,9 @@ packages: resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -2910,6 +3123,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2917,6 +3133,10 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -3748,6 +3968,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lower-case@1.1.4: resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} @@ -4069,6 +4292,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -4408,6 +4638,11 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true + rollup@4.62.4: + resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -4526,6 +4761,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4590,6 +4828,12 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -4753,10 +4997,28 @@ packages: tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.2.4: resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + to-buffer@1.2.2: resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} engines: {node: '>= 0.4'} @@ -4920,6 +5182,11 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-plugin-static-copy@0.17.1: resolution: {integrity: sha512-9h3iaVs0bqnqZOM5YHJXGHqdC5VAVlTZ2ARYsuNpzhEJUHmFqXY7dAK4ZFpjEQ4WLFKcaN8yWbczr81n01U4sQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -4954,6 +5221,62 @@ packages: terser: optional: true + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -4993,6 +5316,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wildcard@2.0.1: resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} @@ -6303,6 +6631,9 @@ snapshots: '@keyv/serialize@1.1.1': {} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + '@napi-rs/triples@1.2.0': {} '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -6435,6 +6766,81 @@ snapshots: optionalDependencies: rollup: 3.30.0 + '@rollup/rollup-android-arm-eabi@4.62.4': + optional: true + + '@rollup/rollup-android-arm64@4.62.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.4': + optional: true + + '@rollup/rollup-darwin-x64@4.62.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.4': + optional: true + '@rtsao/scc@1.1.0': {} '@sideway/address@4.1.5': @@ -7028,6 +7434,8 @@ snapshots: dependencies: '@types/node': 25.9.2 + '@types/crypto-js@4.2.2': {} + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -7201,6 +7609,46 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -7344,6 +7792,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + astral-regex@2.0.0: {} async-function@1.0.0: {} @@ -7529,6 +7979,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + cac@6.7.14: {} + cacheable-request@2.1.4: dependencies: clone-response: 1.0.2 @@ -7601,6 +8053,14 @@ snapshots: tunnel-agent: 0.6.0 url-to-options: 1.0.1 + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -7630,6 +8090,8 @@ snapshots: chardet@2.1.1: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -7801,6 +8263,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crypto-js@4.2.0: {} + css-functions-list@3.3.3: {} css-tree@3.2.1: @@ -7888,6 +8352,8 @@ snapshots: dedent@1.7.2: {} + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -8079,6 +8545,8 @@ snapshots: iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -8368,10 +8836,16 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} eventemitter3@5.0.4: {} + expect-type@1.4.0: {} + ext-list@2.2.2: dependencies: mime-db: 1.54.0 @@ -9216,6 +9690,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lower-case@1.1.4: {} lower-case@2.0.2: @@ -9526,6 +10002,10 @@ snapshots: path-type@4.0.0: {} + pathe@1.1.2: {} + + pathval@2.0.1: {} + pend@1.2.0: {} picocolors@1.1.1: {} @@ -9849,6 +10329,38 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + rollup@4.62.4: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.4 + '@rollup/rollup-android-arm64': 4.62.4 + '@rollup/rollup-darwin-arm64': 4.62.4 + '@rollup/rollup-darwin-x64': 4.62.4 + '@rollup/rollup-freebsd-arm64': 4.62.4 + '@rollup/rollup-freebsd-x64': 4.62.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 + '@rollup/rollup-linux-arm-musleabihf': 4.62.4 + '@rollup/rollup-linux-arm64-gnu': 4.62.4 + '@rollup/rollup-linux-arm64-musl': 4.62.4 + '@rollup/rollup-linux-loong64-gnu': 4.62.4 + '@rollup/rollup-linux-loong64-musl': 4.62.4 + '@rollup/rollup-linux-ppc64-gnu': 4.62.4 + '@rollup/rollup-linux-ppc64-musl': 4.62.4 + '@rollup/rollup-linux-riscv64-gnu': 4.62.4 + '@rollup/rollup-linux-riscv64-musl': 4.62.4 + '@rollup/rollup-linux-s390x-gnu': 4.62.4 + '@rollup/rollup-linux-x64-gnu': 4.62.4 + '@rollup/rollup-linux-x64-musl': 4.62.4 + '@rollup/rollup-openbsd-x64': 4.62.4 + '@rollup/rollup-openharmony-arm64': 4.62.4 + '@rollup/rollup-win32-arm64-msvc': 4.62.4 + '@rollup/rollup-win32-ia32-msvc': 4.62.4 + '@rollup/rollup-win32-x64-gnu': 4.62.4 + '@rollup/rollup-win32-x64-msvc': 4.62.4 + fsevents: 2.3.3 + run-async@2.4.1: {} run-parallel@1.2.0: @@ -10005,6 +10517,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -10065,6 +10579,10 @@ snapshots: split2@4.2.0: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -10290,8 +10808,18 @@ snapshots: tiny-case@1.0.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyexec@1.2.4: {} + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + to-buffer@1.2.2: dependencies: isarray: 2.0.5 @@ -10450,6 +10978,24 @@ snapshots: validate-npm-package-name@5.0.1: {} + vite-node@2.1.9(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-plugin-static-copy@0.17.1(vite@4.5.14(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0)): dependencies: chokidar: 3.6.0 @@ -10469,6 +11015,52 @@ snapshots: sass: 1.100.0 terser: 5.48.0 + vite@5.4.21(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.15 + rollup: 4.62.4 + optionalDependencies: + '@types/node': 25.9.2 + fsevents: 2.3.3 + sass: 1.100.0 + terser: 5.48.0 + + vitest@2.1.9(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0) + vite-node: 2.1.9(@types/node@25.9.2)(sass@1.100.0)(terser@5.48.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.2 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -10532,6 +11124,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wildcard@2.0.1: {} word-wrap@1.2.5: {} diff --git a/src/api/pay.ts b/src/api/pay.ts new file mode 100644 index 0000000..8c379bd --- /dev/null +++ b/src/api/pay.ts @@ -0,0 +1,17 @@ +import { request } from '@/lib/request' + +export function getOrdersByPhone (phone: string) { + return request({ + url: '/api/h5/order/phone', + method: 'GET', + params: { phone }, + }) +} + +export function updateOrderExtra (data: Record) { + return request({ + url: '/api/h5/order', + method: 'PUT', + data, + }) +} diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..d362ee9 --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,76 @@ +import Taro from '@tarojs/taro' +import { request } from '@/lib/request' +import { applyCorpInit } from '@/lib/session' +import { buildFileUrl, getFileExtension, pickPresignResult, QINIU_UPLOAD_URL } from '@/lib/qiniu' + +export function getUserConfig (params: Record = {}) { + return request({ + url: '/api/user/config', + method: 'GET', + params, + }) +} + +export function initCorp (params: Record = {}) { + return request({ + url: '/api/h5/corp', + method: 'GET', + params, + }).then((res) => { + applyCorpInit(res) + return res + }) +} + +export function presign (ext = '') { + return request({ + url: '/api/presign', + method: 'GET', + params: { + ext, + scene: 'process', + }, + }) +} + +export async function uploadImage (filePath: string, onProgress?: (percent: number) => void) { + const ext = getFileExtension(filePath) + onProgress?.(0) + const presignResponse = await presign(ext) + const { id, token, key, domain } = pickPresignResult(presignResponse) + if (!id || !token || !key || !domain) { + throw new Error('获取上传凭证失败') + } + + const uploaded = await new Promise<{ key?: string }>((resolve, reject) => { + const task = Taro.uploadFile({ + url: QINIU_UPLOAD_URL, + filePath, + name: 'file', + formData: { + token, + key, + }, + success (res) { + try { + const body = typeof res.data === 'string' ? JSON.parse(res.data || '{}') : res.data + resolve(body || {}) + } catch { + resolve({}) + } + }, + fail (error) { + reject(error) + }, + }) + task.onProgressUpdate?.((event) => { + onProgress?.(Math.min(100, Math.max(0, event.progress))) + }) + }) + + onProgress?.(100) + return { + id, + url: buildFileUrl(domain, uploaded.key || key), + } +} diff --git a/src/app.config.ts b/src/app.config.ts index 294ede1..1184111 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,13 +1,21 @@ export default defineAppConfig({ pages: [ 'pages/index/index', - 'pages/landing/index', - 'pages/pay/index', + 'pages/map/index', ], window: { backgroundTextStyle: 'light', - navigationBarBackgroundColor: '#fff', - navigationBarTitleText: 'WeChat', - navigationBarTextStyle: 'black' - } + navigationBarBackgroundColor: '#2F4FEE', + navigationBarTitleText: '网上快办平台', + navigationBarTextStyle: 'white' + }, + permission: { + 'scope.userLocation': { + desc: '用于选择门店地图位置' + } + }, + requiredPrivateInfos: [ + 'getLocation', + 'chooseLocation', + ], }) diff --git a/src/assets/images/landing-storefronts.jpg b/src/assets/images/landing-storefronts.jpg deleted file mode 100644 index 33c712970e2774ac39a276b738734b2905ff76cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123074 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%0ATDlJ5xdjl1`0H+S+ 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

UP*gP;<8AoMezYSG@RY^(<^PaL{UVK-Uq>(#oh3 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 { + it('reads token from the common config payload', () => { + expect(pickToken({ data: { token: 'tmp-token' } })).toBe('tmp-token') + }) + + it('reads a nested data.token', () => { + expect(pickToken({ data: { data: { token: 'nested-token' } } })).toBe('nested-token') + }) + + it('returns empty when missing', () => { + expect(pickToken({ data: { config: {} } })).toBe('') + }) +}) + +describe('ensureSessionToken', () => { + it('keeps an existing jump token', async () => { + const token = await ensureSessionToken({ + token: 'h5-token', + fetchUserConfig: async () => { + throw new Error('should not request config when token exists') + }, + }) + + expect(token).toBe('h5-token') + }) + + it('loads a temporary token from /api/user/config when missing', async () => { + const token = await ensureSessionToken({ + token: '', + fetchUserConfig: async () => ({ data: { token: 'guest-token' } }), + }) + + expect(token).toBe('guest-token') + }) + + it('fails when config does not return a token', async () => { + await expect( + ensureSessionToken({ + token: '', + fetchUserConfig: async () => ({ data: {} }), + }) + ).rejects.toThrow('获取临时登录态失败') + }) +}) diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..0361dfc --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,25 @@ +export function pickToken (payload: any): string { + const first = payload?.data ?? payload + const nested = first?.data && typeof first.data === 'object' && ('token' in first.data || !first.token) + ? first.data + : first + const token = nested?.token ?? first?.token ?? payload?.token + return typeof token === 'string' ? token.trim() : '' +} + +export async function ensureSessionToken (input: { + token?: string + fetchUserConfig: () => Promise +}) { + const existing = String(input.token || '').trim() + if (existing) { + return existing + } + + const token = pickToken(await input.fetchUserConfig()) + if (!token) { + throw new Error('获取临时登录态失败') + } + + return token +} diff --git a/src/lib/bootstrap.ts b/src/lib/bootstrap.ts new file mode 100644 index 0000000..a0e3d49 --- /dev/null +++ b/src/lib/bootstrap.ts @@ -0,0 +1,23 @@ +import { getUserConfig, initCorp } from '@/api/user' +import { ensureSessionToken } from './auth' +import { applyLaunchParams, applyServiceConfig, getSession, setSession } from './session' + +export async function bootstrapSession (raw: Record = {}) { + applyLaunchParams(raw) + try { + applyServiceConfig(await getUserConfig()) + } catch { + // 配置拉取失败时仍可继续查询 + } + const token = await ensureSessionToken({ + token: getSession().token, + fetchUserConfig: () => getUserConfig(), + }) + setSession({ token }) + try { + await initCorp() + } catch { + // 已有临时 token 时,初始化失败不阻断查询 + } + return getSession() +} diff --git a/src/lib/cdn.ts b/src/lib/cdn.ts new file mode 100644 index 0000000..b505865 --- /dev/null +++ b/src/lib/cdn.ts @@ -0,0 +1,14 @@ +export const STATIC_CDN = 'https://cdn.u8t.cn/frontend-static/corp-h5/static' +export const CORP_ASSET_BASE = `${STATIC_CDN}/package-v3` +export const MAP_ASSET_BASE = `${STATIC_CDN}/package-v4` +export const HOME_ICON_BASE = `${MAP_ASSET_BASE}/images-pc/home` +export const MOBILE_ASSET_BASE = `${MAP_ASSET_BASE}/images-mobile` +export const BANNER_BG = `${MOBILE_ASSET_BASE}/bg-banner.png` +export const CARD_HEADER = `${MOBILE_ASSET_BASE}/card_header.png` +export const PAGE_TITLE = '网上快办平台' + +export const ENTITY_LABELS = { + entityName: '门店名称', + entityAddress: '门店地址', + entityMapLocation: '门店定位', +} diff --git a/src/lib/contact.test.ts b/src/lib/contact.test.ts new file mode 100644 index 0000000..a4bcac8 --- /dev/null +++ b/src/lib/contact.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest' +import { hasServiceContact, pickServiceContact } from './contact' + +describe('pickServiceContact', () => { + it('reads mobile online phone and weixin from client config', () => { + expect(pickServiceContact({ + 'client.service.online.mobile': 'https://chat.example.com', + 'client.service.phone': '4001166311', + 'client.service.weixin': 'https://work.weixin.qq.com/kfid/abc', + })).toEqual({ + online: 'https://chat.example.com', + phone: '4001166311', + weixin: 'https://work.weixin.qq.com/kfid/abc', + }) + }) + + it('falls back to flattened service fields', () => { + expect(pickServiceContact({ + service_phone: '4000000000', + service_weixin: 'https://u.wechat.com/xyz', + })).toEqual({ + online: '', + phone: '4000000000', + weixin: 'https://u.wechat.com/xyz', + }) + }) +}) + +describe('hasServiceContact', () => { + it('is true when any contact channel exists', () => { + expect(hasServiceContact({ 'client.service.phone': '4001166311' })).toBe(true) + expect(hasServiceContact({})).toBe(false) + }) +}) diff --git a/src/lib/contact.ts b/src/lib/contact.ts new file mode 100644 index 0000000..6aa2863 --- /dev/null +++ b/src/lib/contact.ts @@ -0,0 +1,35 @@ +export type ServiceContact = { + online: string + phone: string + weixin: string +} + +function asText (value: unknown) { + return typeof value === 'string' ? value.trim() : '' +} + +export function pickServiceContact (config?: Record | null): ServiceContact { + return { + online: asText(config?.['client.service.online.mobile'] || config?.service_online), + phone: asText(config?.['client.service.phone'] || config?.service_phone), + weixin: asText(config?.['client.service.weixin'] || config?.service_weixin), + } +} + +export function hasServiceContact (config?: Record | null) { + const contact = pickServiceContact(config) + return Boolean(contact.online || contact.phone || contact.weixin) +} + +export function mergeServiceConfig (current: Record | undefined, payload: any) { + const data = payload?.data ?? payload + const incoming = data?.config && typeof data.config === 'object' ? data.config : {} + const merged = { + ...(current || {}), + ...incoming, + } + if (merged['client.service.phone']) merged.service_phone = merged['client.service.phone'] + if (merged['client.service.weixin']) merged.service_weixin = merged['client.service.weixin'] + if (merged['client.service.online.mobile']) merged.service_online = merged['client.service.online.mobile'] + return merged +} diff --git a/src/lib/crypto.test.ts b/src/lib/crypto.test.ts new file mode 100644 index 0000000..658a1ae --- /dev/null +++ b/src/lib/crypto.test.ts @@ -0,0 +1,21 @@ +import CryptoJS from 'crypto-js' +import Utf8 from 'crypto-js/enc-utf8' +import { describe, expect, it } from 'vitest' +import { decryptResponse } from './crypto' + +describe('decryptResponse', () => { + it('decrypts the AES payload used by H5', () => { + const key = Utf8.parse('JphN7MRfPSsaydwMeYk6YhBquRz5Ck5K') + const iv = key.clone() + iv.sigBytes = 16 + iv.clamp() + const payload = { code: 0, data: { token: 'tmp-token' } } + const encrypted = CryptoJS.AES.encrypt(JSON.stringify(payload), key, { + iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }).toString() + + expect(decryptResponse(encrypted)).toEqual(payload) + }) +}) diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts new file mode 100644 index 0000000..08440ec --- /dev/null +++ b/src/lib/crypto.ts @@ -0,0 +1,18 @@ +import CryptoJS from 'crypto-js' +import Utf8 from 'crypto-js/enc-utf8' + +const AES_KEY = 'JphN7MRfPSsaydwMeYk6YhBquRz5Ck5K' + +export function decryptResponse (encrypted: string) { + const key = Utf8.parse(AES_KEY) + const iv = key.clone() + iv.sigBytes = 16 + iv.clamp() + const decrypted = CryptoJS.AES.decrypt(encrypted, key, { + iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }).toString(Utf8) + + return JSON.parse(decrypted) +} diff --git a/src/lib/geocode.test.ts b/src/lib/geocode.test.ts new file mode 100644 index 0000000..ea41e51 --- /dev/null +++ b/src/lib/geocode.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest' +import { + gcj02ToWgs84, + isRegionChangeEnd, + isUserRegionChange, + parseGeocoderResult, + pickMapCenter, +} from './geocode' + +describe('pickMapCenter', () => { + it('reads nested WeChat regionchange centerLocation', () => { + expect(pickMapCenter({ + detail: { + type: 'end', + causedBy: 'drag', + detail: { + centerLocation: { longitude: 121.4737, latitude: 31.2304 }, + }, + }, + })).toEqual({ lng: 121.4737, lat: 31.2304 }) + }) + + it('reads a flat centerLocation', () => { + expect(pickMapCenter({ + detail: { centerLocation: { longitude: 116.4, latitude: 39.9 } }, + })).toEqual({ lng: 116.4, lat: 39.9 }) + }) +}) + +describe('region change helpers', () => { + it('detects a finished user drag', () => { + const event = { detail: { type: 'end', causedBy: 'drag' } } + expect(isRegionChangeEnd(event)).toBe(true) + expect(isUserRegionChange(event)).toBe(true) + }) + + it('ignores programmatic updates and begin events', () => { + expect(isRegionChangeEnd({ detail: { type: 'begin', causedBy: 'gesture' } })).toBe(false) + expect(isUserRegionChange({ detail: { type: 'end', causedBy: 'update' } })).toBe(false) + expect(isUserRegionChange({ detail: { type: 'end' } })).toBe(true) + }) +}) + +describe('parseGeocoderResult', () => { + it('prefers formatted address and poi name', () => { + expect(parseGeocoderResult({ + result: { + formatted_address: '上海市黄浦区南京东路1号', + addressComponent: { poi: '外滩' }, + }, + })).toEqual({ + address: '上海市黄浦区南京东路1号', + name: '外滩', + }) + }) + + it('falls back to the formatted address when poi is missing', () => { + expect(parseGeocoderResult({ + result: { formatted_address: '北京市东城区' }, + })).toEqual({ + address: '北京市东城区', + name: '北京市东城区', + }) + }) +}) + +describe('gcj02ToWgs84', () => { + it('matches the H5 GCJ-02 to Tianditu conversion for Shanghai', () => { + const [lng, lat] = gcj02ToWgs84(121.4737, 31.2304) + expect(lng).toBeCloseTo(121.469162, 6) + expect(lat).toBeCloseTo(31.232329, 6) + }) +}) diff --git a/src/lib/geocode.ts b/src/lib/geocode.ts new file mode 100644 index 0000000..67eb4a8 --- /dev/null +++ b/src/lib/geocode.ts @@ -0,0 +1,88 @@ +const GCJ_PI = Math.PI +const GCJ_A = 6378245 +const GCJ_EE = 0.006693421622965943 +export const TIANDITU_GEOCODER_URL = 'https://api.tianditu.gov.cn/geocoder' + +export type GeoPoint = { lng: number, lat: number } +export type GeoPlace = { address: string, name: string } + +function isOutsideChina (lng: number, lat: number) { + return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271 +} + +function transformLatitude (lng: number, lat: number) { + let result = -100 + 2 * lng + 3 * lat + 0.2 * lat * lat + 0.1 * lng * lat + result += 0.2 * Math.sqrt(Math.abs(lng)) + result += (20 * Math.sin(6 * lng * GCJ_PI) + 20 * Math.sin(2 * lng * GCJ_PI)) * 2 / 3 + result += (20 * Math.sin(lat * GCJ_PI) + 40 * Math.sin(lat / 3 * GCJ_PI)) * 2 / 3 + result += (160 * Math.sin(lat / 12 * GCJ_PI) + 320 * Math.sin(lat * GCJ_PI / 30)) * 2 / 3 + return result +} + +function transformLongitude (lng: number, lat: number) { + let result = 300 + lng + 2 * lat + 0.1 * lng * lng + 0.1 * lng * lat + result += 0.1 * Math.sqrt(Math.abs(lng)) + result += (20 * Math.sin(6 * lng * GCJ_PI) + 20 * Math.sin(2 * lng * GCJ_PI)) * 2 / 3 + result += (20 * Math.sin(lng * GCJ_PI) + 40 * Math.sin(lng / 3 * GCJ_PI)) * 2 / 3 + result += (150 * Math.sin(lng / 12 * GCJ_PI) + 300 * Math.sin(lng / 30 * GCJ_PI)) * 2 / 3 + return result +} + +function getGcjOffset (lng: number, lat: number): GeoPoint { + const latitudeRadians = lat / 180 * GCJ_PI + const magicBase = 1 - GCJ_EE * Math.sin(latitudeRadians) ** 2 + const magicRoot = Math.sqrt(magicBase) + const latitudeOffset = + transformLatitude(lng - 105, lat - 35) * 180 / + ((GCJ_A * (1 - GCJ_EE) / (magicBase * magicRoot)) * GCJ_PI) + const longitudeOffset = + transformLongitude(lng - 105, lat - 35) * 180 / + ((GCJ_A / magicRoot * Math.cos(latitudeRadians)) * GCJ_PI) + return { lng: longitudeOffset, lat: latitudeOffset } +} + +export function gcj02ToWgs84 (lng: number, lat: number): [number, number] { + if (isOutsideChina(lng, lat)) return [lng, lat] + let estimateLng = lng + let estimateLat = lat + for (let index = 0; index < 3; index += 1) { + const offset = getGcjOffset(estimateLng, estimateLat) + estimateLng = estimateLng + lng - (estimateLng + offset.lng) + estimateLat = estimateLat + lat - (estimateLat + offset.lat) + } + return [estimateLng, estimateLat] +} + +function asRecord (value: unknown): Record | null { + return value && typeof value === 'object' ? value as Record : null +} + +export function pickMapCenter (event: any): GeoPoint | null { + const detail = asRecord(event?.detail) || asRecord(event) + const inner = asRecord(detail?.detail) || detail + const center = asRecord(inner?.centerLocation) || asRecord(detail?.centerLocation) + const lng = Number(center?.longitude) + const lat = Number(center?.latitude) + if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null + return { lng, lat } +} + +export function isRegionChangeEnd (event: any) { + const detail = asRecord(event?.detail) || asRecord(event) + const type = detail?.type || asRecord(detail?.detail)?.type || event?.type + return type === 'end' +} + +export function isUserRegionChange (event: any) { + const detail = asRecord(event?.detail) || asRecord(event) + const causedBy = detail?.causedBy || asRecord(detail?.detail)?.causedBy + return causedBy !== 'update' +} + +export function parseGeocoderResult (payload: any): GeoPlace { + const result = asRecord(payload?.result) || asRecord(payload) || {} + const address = typeof result.formatted_address === 'string' ? result.formatted_address : '' + const poi = asRecord(result.addressComponent)?.poi + const name = typeof poi === 'string' && poi ? poi : address + return { address, name } +} diff --git a/src/lib/launch.test.ts b/src/lib/launch.test.ts new file mode 100644 index 0000000..e4bfb5b --- /dev/null +++ b/src/lib/launch.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, it } from 'vitest' +import { + DEFAULT_LAUNCH_SCHEME, + parseLaunchQuery, + parseMiniProgramScheme, + resolveLaunchQuery, +} from './launch' + +describe('parseMiniProgramScheme', () => { + it('reads the default weixin business scheme query', () => { + expect(parseMiniProgramScheme(DEFAULT_LAUNCH_SCHEME)).toEqual({ + appId: 'wx6d4f6f29c41aff93', + path: 'pages/index/index', + scene: '/h/KZ9Q', + token: '6224a1fe-d88c-443c-8972-e6a0942d17fd', + host: 'nb.batiao8.com', + package: '10044', + phone: '13800138000', + }) + }) +}) + +describe('parseLaunchQuery', () => { + it('keeps token host and package from the H5 jump', () => { + expect( + parseLaunchQuery({ + token: 'h5-token', + host: 'h5test.batiao8.com', + package: '10044', + scene: '%2Fh%2FKZ9Q', + }) + ).toEqual({ + token: 'h5-token', + host: 'h5test.batiao8.com', + packageId: '10044', + scene: '/h/KZ9Q', + linkId: 'KZ9Q', + shareId: '', + prevId: '', + phone: '', + }) + }) + + it('reads share and preview short links', () => { + expect(parseLaunchQuery({ scene: '/s/SHARE1' }).shareId).toBe('SHARE1') + expect(parseLaunchQuery({ scene: '/p/PREV9' }).prevId).toBe('PREV9') + }) + + it('ignores empty values', () => { + expect(parseLaunchQuery({})).toEqual({ + token: '', + host: '', + packageId: '', + scene: '', + linkId: '', + shareId: '', + prevId: '', + phone: '', + }) + + expect(parseLaunchQuery({ phone: '13900001111' }).phone).toBe('13900001111') + }) +}) + +describe('resolveLaunchQuery', () => { + it('fills missing fields from the default scheme', () => { + expect(resolveLaunchQuery({})).toEqual({ + token: '6224a1fe-d88c-443c-8972-e6a0942d17fd', + host: 'nb.batiao8.com', + packageId: '10044', + scene: '/h/KZ9Q', + linkId: 'KZ9Q', + shareId: '', + prevId: '', + phone: '13800138000', + }) + }) + + it('uses the launched phone when present', () => { + expect(resolveLaunchQuery({ phone: '13900001111' }).phone).toBe('13900001111') + }) + + it('keeps explicit jump params over defaults', () => { + expect( + resolveLaunchQuery({ + token: 'h5-token', + host: 'h5test.batiao8.com', + package: '20001', + scene: '/s/SHARE1', + }) + ).toEqual({ + token: 'h5-token', + host: 'h5test.batiao8.com', + packageId: '20001', + scene: '/s/SHARE1', + linkId: '', + shareId: 'SHARE1', + prevId: '', + phone: '13800138000', + }) + }) +}) + diff --git a/src/lib/launch.ts b/src/lib/launch.ts new file mode 100644 index 0000000..921cc2c --- /dev/null +++ b/src/lib/launch.ts @@ -0,0 +1,83 @@ +export type LaunchQuery = { + token: string + host: string + packageId: string + scene: string + linkId: string + shareId: string + prevId: string + phone: string +} + +export const DEFAULT_LAUNCH_SCHEME = + 'weixin://dl/business/?appid=wx6d4f6f29c41aff93&path=pages/index/index&query=scene%3D%2Fh%2FKZ9Q%26token%3D6224a1fe-d88c-443c-8972-e6a0942d17fd%26host%3Dnb.batiao8.com%26package%3D10044%26phone%3D13800138000' + +export function parseMiniProgramScheme (scheme: string) { + const search = scheme.split('?')[1] || '' + const params = new URLSearchParams(search) + const inner = new URLSearchParams(params.get('query') || '') + return { + appId: params.get('appid') || '', + path: params.get('path') || '', + scene: inner.get('scene') || '', + token: inner.get('token') || '', + host: inner.get('host') || '', + package: inner.get('package') || '', + phone: inner.get('phone') || '', + } +} + +function asText (value?: string) { + return String(value ?? '').trim() +} + +function safeDecode (value: string) { + try { + return decodeURIComponent(value) + } catch { + return value + } +} + +function parseSceneIds (scene: string) { + const decoded = safeDecode(scene).trim() + const matched = decoded.match(/\/(h|s|p)\/([^/?#]+)/) + if (!matched) { + return { linkId: '', shareId: '', prevId: '' } + } + + const id = matched[2] + if (matched[1] === 'h') return { linkId: id, shareId: '', prevId: '' } + if (matched[1] === 's') return { linkId: '', shareId: id, prevId: '' } + return { linkId: '', shareId: '', prevId: id } +} + +export function parseLaunchQuery (raw: Record = {}): LaunchQuery { + const scene = safeDecode(asText(raw.scene)) + return { + token: asText(raw.token), + host: asText(raw.host), + packageId: asText(raw.package), + scene, + phone: asText(raw.phone), + ...parseSceneIds(scene), + } +} + +export function resolveLaunchQuery (raw: Record = {}): LaunchQuery { + const parsed = parseLaunchQuery(raw) + const defaults = parseLaunchQuery(parseMiniProgramScheme(DEFAULT_LAUNCH_SCHEME)) + const scene = parsed.scene || defaults.scene + const sceneIds = parsed.scene + ? { linkId: parsed.linkId, shareId: parsed.shareId, prevId: parsed.prevId } + : { linkId: defaults.linkId, shareId: defaults.shareId, prevId: defaults.prevId } + + return { + token: parsed.token || defaults.token, + host: parsed.host || defaults.host, + packageId: parsed.packageId || defaults.packageId, + scene, + phone: parsed.phone || defaults.phone, + ...sceneIds, + } +} diff --git a/src/lib/order.test.ts b/src/lib/order.test.ts new file mode 100644 index 0000000..fb4cdd8 --- /dev/null +++ b/src/lib/order.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from 'vitest' +import { + buildResubmitPayload, + filterMapLabelOrders, + formatMapCoordinate, + getOrderAddressName, + getOrderExtraImages, + getOrderMapAddress, + joinUploadedImageIds, + parseCoordinate, +} from './order' + +const mapOrder = { + order_id: '4', + goods_type: 'map_label', + entity_name: '老店', + entity_phone: '13800138000', + entity_extra: { keep: 1 }, + extra: { + entity_address_name: '南京路1号', + entity_address: '121.473700,31.230400', + entity_storefront_image: '11,12', + entity_storefront_image_url: 'https://cdn.example/a.jpg,https://cdn.example/b.jpg', + origin_price: '99', + goods_param: '{"client.goods.icon":["https://cdn.example/i.png"]}', + }, +} + +describe('filterMapLabelOrders', () => { + it('keeps only map_label orders and parses goods_param', () => { + const orders = filterMapLabelOrders([ + mapOrder, + { ...mapOrder, order_id: '5', goods_type: 'license_year' }, + ] as any) + + expect(orders).toHaveLength(1) + expect(orders[0].extra.goods_param).toEqual({ + 'client.goods.icon': ['https://cdn.example/i.png'], + }) + }) +}) + +describe('order address helpers', () => { + it('splits named address and coordinate address', () => { + expect(getOrderAddressName(mapOrder as any)).toBe('南京路1号') + expect(getOrderMapAddress(mapOrder as any)).toBe('121.473700,31.230400') + expect(parseCoordinate('121.4737, 31.2304')).toEqual({ lng: 121.4737, lat: 31.2304 }) + expect(formatMapCoordinate({ lng: 121.4737, lat: 31.2304 })).toBe('121.473700,31.230400') + }) +}) + +describe('order images', () => { + it('reads extra image urls and joins uploaded ids', () => { + expect(getOrderExtraImages(mapOrder as any, ['entity_storefront_image'])).toEqual([ + { id: '11', url: 'https://cdn.example/a.jpg', preview: 'https://cdn.example/a.jpg' }, + { id: '12', url: 'https://cdn.example/b.jpg', preview: 'https://cdn.example/b.jpg' }, + ]) + expect(joinUploadedImageIds([ + { id: '11', url: 'https://cdn.example/a.jpg', preview: 'https://cdn.example/a.jpg' }, + { id: ' ', url: 'x', preview: 'x' }, + ])).toBe('11') + }) +}) + +describe('buildResubmitPayload', () => { + it('updates extra while dropping upload-only keys', () => { + expect( + buildResubmitPayload({ + order: mapOrder as any, + values: { + entity_name: '新店', + entity_address_name: '淮海路2号', + entity_address: '121.480000,31.240000', + entity_phone: '13900139000', + entity_phone2: '13700137000', + }, + storefrontImages: [{ id: '21', url: 'https://cdn.example/c.jpg', preview: 'p' }], + licenseImages: [{ id: '31', url: 'https://cdn.example/d.jpg', preview: 'p' }], + }) + ).toEqual({ + id: '4', + corp_id: 0, + entity_name: '新店', + entity_phone: '13900139000', + entity_extra: { keep: 1 }, + extra: { + entity_address_name: '淮海路2号', + entity_address: '121.480000,31.240000', + entity_phone2: '13700137000', + entity_storefront_image: '21', + entity_business_license_image: '31', + }, + }) + }) +}) diff --git a/src/lib/order.ts b/src/lib/order.ts new file mode 100644 index 0000000..c7006bd --- /dev/null +++ b/src/lib/order.ts @@ -0,0 +1,160 @@ +export type UploadedImage = { + id: string + url: string + preview: string +} + +export type OrderLike = { + order_id?: string + goods_type?: string + entity_name?: string + entity_phone?: string + entity_extra?: Record + extra?: Record + [key: string]: any +} + +export type ResubmitValues = { + entity_name?: string + entity_address_name?: string + entity_address?: string + entity_phone?: string + entity_phone2?: string +} + +const OMIT_EXTRA_KEYS = new Set([ + 'origin_price', + 'qrCodeImgUrl', + 'remoteIp', + 'weixinAppId', + 'entity_storefront_image', + 'entity_business_license_image', + 'entity_storefront_image_url', + 'entity_business_license_image_url', + 'goods_param', +]) + +function splitValues (value: unknown) { + if (value === undefined || value === null) return [] + return String(value) + .split(',') + .map((item) => item.trim()) + .filter(Boolean) +} + +export function isCoordinateAddress (address?: string) { + if (!address) return false + const parts = address.split(',').map((part) => part.trim()) + if (parts.length !== 2) return false + return parts.every((part) => Number.isFinite(Number(part))) +} + +export function parseCoordinate (address?: string) { + if (!isCoordinateAddress(address)) return null + const [lng, lat] = String(address).split(',').map((part) => Number(part.trim())) + return { lng, lat } +} + +export function formatMapCoordinate (point: { lng: number, lat: number }) { + return `${point.lng.toFixed(6)},${point.lat.toFixed(6)}` +} + +export function getOrderAddressName (order: OrderLike) { + const extra = order.extra || {} + return extra.entity_address_name || (!isCoordinateAddress(extra.entity_address) ? extra.entity_address : '') || '' +} + +export function getOrderMapAddress (order: OrderLike) { + const extra = order.extra || {} + return isCoordinateAddress(extra.entity_address) ? extra.entity_address : '' +} + +export function getInitialPhone2 (order: OrderLike) { + const extra = order.extra || {} + return String(extra.entity_phone2 || extra.entity_phone_2 || extra.phone2 || '') +} + +export function getGoodsIcons (order: OrderLike) { + const icons = order.extra?.goods_param?.['client.goods.icon'] + return Array.isArray(icons) ? icons.filter((item) => typeof item === 'string') : [] +} + +export function filterMapLabelOrders (orders: T[] = []) { + return orders + .filter((item) => item.goods_type === 'map_label') + .map((item) => { + const nextItem = { ...item, extra: { ...(item.extra || {}) } } + const goodsParam = nextItem.extra.goods_param + if (typeof goodsParam === 'string') { + try { + nextItem.extra.goods_param = JSON.parse(goodsParam) + } catch { + nextItem.extra.goods_param = null + } + } + return nextItem + }) +} + +export function getOrderExtraImages (order: OrderLike | null | undefined, keys: string[]): UploadedImage[] { + const extra = order?.extra || {} + + for (const key of keys) { + const values = splitValues(extra[key]) + const explicitIds = splitValues(extra[`${key}_id`]) + const explicitUrls = splitValues(extra[`${key}_url`]) + const urls = explicitUrls.length > 0 + ? explicitUrls + : values.filter((value) => /^https?:\/\//i.test(value)) + + if (urls.length > 0) { + return urls.map((url, index) => { + const valueId = values[index] && values[index] !== url ? values[index] : '' + return { + id: valueId || explicitIds[index] || '', + url, + preview: url, + } + }) + } + } + + return [] +} + +export function joinUploadedImageIds (images: UploadedImage[]) { + return images + .map((image) => image.id.trim()) + .filter(Boolean) + .join(',') +} + +export function buildResubmitPayload (input: { + order: OrderLike + values: ResubmitValues + storefrontImages: UploadedImage[] + licenseImages: UploadedImage[] +}) { + const existingExtra = Object.entries(input.order.extra || {}).reduce>((result, [key, value]) => { + if (!OMIT_EXTRA_KEYS.has(key)) { + result[key] = value + } + return result + }, {}) + + return { + id: String(input.order.order_id || ''), + corp_id: 0, + entity_name: input.values.entity_name || input.order.entity_name || '', + entity_phone: input.values.entity_phone || '', + entity_extra: input.order.entity_extra || {}, + extra: { + ...existingExtra, + entity_address_name: input.values.entity_address_name || '', + entity_address: input.values.entity_address || '', + entity_phone2: input.values.entity_phone2 || '', + entity_storefront_image: joinUploadedImageIds(input.storefrontImages), + entity_business_license_image: joinUploadedImageIds(input.licenseImages), + }, + } +} diff --git a/src/lib/origin.test.ts b/src/lib/origin.test.ts new file mode 100644 index 0000000..c9cc73a --- /dev/null +++ b/src/lib/origin.test.ts @@ -0,0 +1,56 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { hostFromOrigin, resolveApiOrigin, resolveRequestHost } from './origin' + +describe('resolveApiOrigin', () => { + const original = process.env.TARO_APP_API_ORIGIN + + afterEach(() => { + process.env.TARO_APP_API_ORIGIN = original + }) + + it('uses the configured local test origin', () => { + expect(resolveApiOrigin('http://corp-test.batiao8.com')).toBe('http://corp-test.batiao8.com') + }) + + it('uses the configured production origin', () => { + expect(resolveApiOrigin('https://nb.zuom8.cn')).toBe('https://nb.zuom8.cn') + }) + + it('strips a trailing slash', () => { + expect(resolveApiOrigin('https://nb.zuom8.cn/')).toBe('https://nb.zuom8.cn') + }) + + it('reads TARO_APP_API_ORIGIN when no argument is passed', () => { + process.env.TARO_APP_API_ORIGIN = 'http://corp-test.batiao8.com' + expect(resolveApiOrigin()).toBe('http://corp-test.batiao8.com') + }) + + it('throws when the env origin is missing', () => { + delete process.env.TARO_APP_API_ORIGIN + expect(() => resolveApiOrigin()).toThrow('缺少接口域名') + }) +}) + +describe('hostFromOrigin', () => { + it('extracts the hostname from a local test origin', () => { + expect(hostFromOrigin('http://corp-test.batiao8.com')).toBe('corp-test.batiao8.com') + }) + + it('extracts the hostname from the official origin', () => { + expect(hostFromOrigin('https://nb.zuom8.cn')).toBe('nb.zuom8.cn') + }) +}) + +describe('resolveRequestHost', () => { + it('prefers the launched host for x-host', () => { + expect(resolveRequestHost({ + host: 'h5test.batiao8.com', + fallbackHost: 'nb.zuom8.cn', + })).toBe('h5test.batiao8.com') + }) + + it('falls back to the env host when no host is launched', () => { + expect(resolveRequestHost({ fallbackHost: 'nb.zuom8.cn' })).toBe('nb.zuom8.cn') + }) +}) + diff --git a/src/lib/origin.ts b/src/lib/origin.ts new file mode 100644 index 0000000..61c6e90 --- /dev/null +++ b/src/lib/origin.ts @@ -0,0 +1,26 @@ +export function normalizeHost (host?: string) { + return String(host || '') + .trim() + .replace(/^https?:\/\//i, '') + .replace(/\/+$/, '') +} + +export function normalizeOrigin (origin?: string) { + return String(origin || '').trim().replace(/\/+$/, '') +} + +export function hostFromOrigin (origin?: string) { + return normalizeHost(normalizeOrigin(origin).replace(/^https?:\/\//i, '')) +} + +export function resolveApiOrigin (origin = process.env.TARO_APP_API_ORIGIN) { + const value = normalizeOrigin(origin) + if (!value) { + throw new Error('缺少接口域名') + } + return value +} + +export function resolveRequestHost (input: { host?: string, fallbackHost?: string }) { + return normalizeHost(input.host) || normalizeHost(input.fallbackHost) +} diff --git a/src/lib/phone.test.ts b/src/lib/phone.test.ts new file mode 100644 index 0000000..715959e --- /dev/null +++ b/src/lib/phone.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' +import { isValidPhone } from './phone' + +describe('isValidPhone', () => { + it('accepts mainland mobile numbers', () => { + expect(isValidPhone('13800138000')).toBe(true) + }) + + it('accepts landline numbers', () => { + expect(isValidPhone('010-12345678')).toBe(true) + }) + + it('rejects empty or invalid values', () => { + expect(isValidPhone('')).toBe(false) + expect(isValidPhone('12345')).toBe(false) + expect(isValidPhone('23800138000')).toBe(false) + }) +}) diff --git a/src/lib/phone.ts b/src/lib/phone.ts new file mode 100644 index 0000000..d9571c1 --- /dev/null +++ b/src/lib/phone.ts @@ -0,0 +1,5 @@ +const PHONE_PATTERN = /^(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})$/ + +export function isValidPhone (phone: string) { + return PHONE_PATTERN.test(String(phone || '').trim()) +} diff --git a/src/lib/qiniu.test.ts b/src/lib/qiniu.test.ts new file mode 100644 index 0000000..13d2f4c --- /dev/null +++ b/src/lib/qiniu.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' +import { buildFileUrl, pickPresignResult } from './qiniu' + +describe('pickPresignResult', () => { + it('normalizes the presign payload used by H5', () => { + expect( + pickPresignResult({ + code: 0, + data: { + id: 33013, + token: 'qiniu-upload-token', + key: 'upload/10034/a.jpg', + domain: 'https://cdn2.batiao8.com', + }, + }) + ).toEqual({ + id: '33013', + token: 'qiniu-upload-token', + key: 'upload/10034/a.jpg', + domain: 'https://cdn2.batiao8.com', + }) + }) +}) + +describe('buildFileUrl', () => { + it('joins domain and key without duplicate slashes', () => { + expect(buildFileUrl('https://cdn2.batiao8.com/', '/upload/a.jpg')).toBe( + 'https://cdn2.batiao8.com/upload/a.jpg' + ) + }) +}) diff --git a/src/lib/qiniu.ts b/src/lib/qiniu.ts new file mode 100644 index 0000000..5e72a44 --- /dev/null +++ b/src/lib/qiniu.ts @@ -0,0 +1,37 @@ +export const QINIU_UPLOAD_URL = 'https://up-cn-east-2.qiniup.com' + +export type PresignResult = { + id: string + token: string + key: string + domain: string +} + +export function pickPresignResult (response: any): PresignResult { + const payload = response?.data ?? response + const data = payload?.data && typeof payload.data === 'object' ? payload.data : payload + return { + id: data?.id === undefined || data?.id === null ? '' : String(data.id), + token: typeof data?.token === 'string' ? data.token : '', + key: typeof data?.key === 'string' ? data.key : '', + domain: typeof data?.domain === 'string' ? data.domain : '', + } +} + +export function buildFileUrl (domain: string, key: string) { + if (!domain) return key + const normalizedDomain = domain.endsWith('/') ? domain.slice(0, -1) : domain + const normalizedKey = key.startsWith('/') ? key.slice(1) : key + return `${normalizedDomain}/${normalizedKey}` +} + +export function getFileExtension (filePath: string) { + const name = filePath.split('?')[0] + const slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')) + const fileName = slash >= 0 ? name.slice(slash + 1) : name + const dotIndex = fileName.lastIndexOf('.') + if (dotIndex >= 0 && dotIndex < fileName.length - 1) { + return fileName.slice(dotIndex + 1).toLowerCase() + } + return 'jpg' +} diff --git a/src/lib/request.ts b/src/lib/request.ts new file mode 100644 index 0000000..70fd1ae --- /dev/null +++ b/src/lib/request.ts @@ -0,0 +1,144 @@ +import Taro from '@tarojs/taro' +import { decryptResponse } from './crypto' +import { resolveApiOrigin, resolveRequestHost } from './origin' +import { signRequest } from './sign' +import { getSession, getSessionHost, setSession } from './session' + +export const REQUEST_TIMEOUT = 60000 +export const APP_VERSION = '3.0.0' +export const CORP_ID = '10006' + +type RequestOptions = { + url: string + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' + params?: Record + data?: any + silent?: boolean +} + +export type BusinessResponse = { + code: number + data: T + message?: string + msg?: string + encrypt?: boolean +} + +let waitGroup = 0 + +function showLoading () { + waitGroup += 1 + if (waitGroup === 1) { + Taro.showLoading({ title: '加载中...', mask: true }) + } +} + +function hideLoading () { + waitGroup = Math.max(0, waitGroup - 1) + if (waitGroup === 0) { + Taro.hideLoading() + } +} + +function getResponseMessage (data: any) { + const message = data?.message ?? data?.msg ?? '请求失败' + return String(message).split(',')[0] || '请求失败' +} + +function parseResponseBody (data: any) { + if (typeof data !== 'string') return data + const text = data.trim() + if (!text) return data + try { + return JSON.parse(text) + } catch { + return data + } +} + +function toQuery (params: Record) { + return Object.keys(params) + .map((key) => `${key}=${encodeURIComponent(params[key] ?? '')}`) + .join('&') +} + +function buildHeaders () { + const session = getSession() + const headers: Record = { + 'content-type': 'application/json', + 'x-device-id': session.deviceId, + 'x-platform': 'wx-mp', + 'x-channel': 'wx-mp', + 'x-version': APP_VERSION, + 'x-host': resolveRequestHost({ + host: getSessionHost(), + fallbackHost: process.env.TARO_APP_API_HOST, + }), + 'b-corp-id': CORP_ID, + } + if (session.linkId) headers['b-link-id'] = session.linkId + if (session.shareId) headers['b-share-id'] = session.shareId + if (session.prevId) headers['b-prev-id'] = session.prevId + if (session.token) headers['x-token'] = session.token + if (session.packageId) headers['x-package'] = session.packageId + return headers +} + +export async function request (options: RequestOptions): Promise> { + const method = options.method || 'GET' + const timestamp = Math.floor(Date.now() / 1000) + const nonce = `${timestamp}-${Math.random().toString(16).slice(2)}` + const signed = signRequest({ + method, + params: { + ...(options.params || {}), + timestamp, + nonce, + }, + data: options.data, + }) + const origin = resolveApiOrigin() + const url = `${origin}${options.url}?${toQuery(signed.params)}` + + if (!options.silent) showLoading() + + try { + const response = await Taro.request({ + url, + method, + data: method === 'GET' || method === 'DELETE' ? undefined : signed.data, + header: buildHeaders(), + timeout: REQUEST_TIMEOUT, + }) + + let payload = parseResponseBody(response.data) as BusinessResponse + if (payload && typeof payload === 'object' && payload.encrypt) { + payload = decryptResponse((payload as any).data) + } + payload = parseResponseBody(payload) + + if (!payload || payload.code !== 0) { + const message = getResponseMessage(payload) + if (!options.silent) { + Taro.showToast({ title: message, icon: 'none' }) + } + if (payload?.code === 11022 || payload?.code === 1001003 || payload?.code === 1001004) { + setSession({ token: '' }) + } + throw new Error(message) + } + + return payload + } catch (error) { + if (error instanceof Error && error.message && error.message !== 'request:fail') { + throw error + } + const message = '当前无法连接服务,请稍后再试' + if (!options.silent) { + Taro.showToast({ title: message, icon: 'none' }) + } + throw new Error(message) + } finally { + if (!options.silent) hideLoading() + } +} diff --git a/src/lib/reverseGeocode.ts b/src/lib/reverseGeocode.ts new file mode 100644 index 0000000..59d3925 --- /dev/null +++ b/src/lib/reverseGeocode.ts @@ -0,0 +1,20 @@ +import Taro from '@tarojs/taro' +import { gcj02ToWgs84, parseGeocoderResult, TIANDITU_GEOCODER_URL, type GeoPlace } from './geocode' + +export async function reverseGeocode (lng: number, lat: number): Promise { + const key = process.env.TARO_APP_TIANDITU_KEY + if (!key) return { address: '', name: '' } + + const [wgsLng, wgsLat] = gcj02ToWgs84(lng, lat) + const response = await Taro.request({ + url: TIANDITU_GEOCODER_URL, + method: 'GET', + data: { + postStr: JSON.stringify({ lon: wgsLng, lat: wgsLat, ver: 1 }), + type: 'geocode', + tk: key, + }, + }) + + return parseGeocoderResult(response.data) +} \ No newline at end of file diff --git a/src/lib/session.ts b/src/lib/session.ts new file mode 100644 index 0000000..836ccad --- /dev/null +++ b/src/lib/session.ts @@ -0,0 +1,105 @@ +import Taro from '@tarojs/taro' +import { mergeServiceConfig } from './contact' +import { resolveLaunchQuery, type LaunchQuery } from './launch' +import { hostFromOrigin, resolveApiOrigin } from './origin' +import { createDeviceId } from './uuid' + +const SESSION_KEY = 'corpMpSession' + +export type SessionState = LaunchQuery & { + deviceId: string + config: Record +} + +const EMPTY_SESSION: SessionState = { + token: '', + host: '', + packageId: '', + scene: '', + linkId: '', + shareId: '', + prevId: '', + phone: '', + deviceId: '', + config: {}, +} + +function readStorage (): Partial { + try { + const value = Taro.getStorageSync(SESSION_KEY) + return value && typeof value === 'object' ? value : {} + } catch { + return {} + } +} + +function writeStorage (session: SessionState) { + try { + Taro.setStorageSync(SESSION_KEY, session) + } catch { + // ignore quota / private mode + } +} + +let memorySession: SessionState | null = null + +export function getSession (): SessionState { + if (!memorySession) { + const stored = readStorage() + memorySession = { + ...EMPTY_SESSION, + ...stored, + deviceId: stored.deviceId || createDeviceId(), + } + writeStorage(memorySession) + } + return memorySession +} + +export function setSession (patch: Partial) { + memorySession = { + ...getSession(), + ...patch, + } + writeStorage(memorySession) + return memorySession +} + +export function applyLaunchParams (raw: Record = {}) { + const launch = resolveLaunchQuery(raw) + const current = getSession() + return setSession({ + token: launch.token || current.token, + host: launch.host || current.host, + packageId: launch.packageId || current.packageId, + scene: launch.scene || current.scene, + linkId: launch.linkId || current.linkId, + shareId: launch.shareId || current.shareId, + prevId: launch.prevId || current.prevId, + phone: launch.phone || current.phone, + }) +} + +export function getSessionHost () { + const session = getSession() + if (session.host) return session.host + return process.env.TARO_APP_API_HOST || hostFromOrigin(resolveApiOrigin()) +} + +export function applyServiceConfig (payload: any) { + return setSession({ + config: mergeServiceConfig(getSession().config, payload), + }) +} + +export function applyCorpInit (payload: any) { + const data = payload?.data ?? payload + const token = typeof data?.token === 'string' ? data.token.trim() : '' + const packageId = data?.package === undefined || data?.package === null ? '' : String(data.package) + const patch: Partial = { + config: mergeServiceConfig(getSession().config, payload), + } + if (token) patch.token = token + if (packageId) patch.packageId = packageId + return setSession(patch) +} diff --git a/src/lib/sign.test.ts b/src/lib/sign.test.ts new file mode 100644 index 0000000..f2ad392 --- /dev/null +++ b/src/lib/sign.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' +import { signRequest } from './sign' + +describe('signRequest', () => { + it('signs GET query params the same way as H5', () => { + const signed = signRequest({ + method: 'GET', + params: { + phone: '13800138000', + timestamp: 1700000000, + nonce: 'abc-nonce', + }, + }) + + expect(signed.params.signature).toBe('170c9063021f3863d00c32b5ddffeb01') + }) + + it('signs POST with query params plus JSON body', () => { + const signed = signRequest({ + method: 'POST', + params: { + phone: '13800138000', + timestamp: 1700000000, + nonce: 'abc-nonce', + }, + data: { + id: '12', + entity_name: '店', + }, + }) + + expect(signed.params.signature).toBe('0b7bcf9a71df45452fda85a3a45b9a8a') + }) + + it('treats PUT like POST', () => { + const signed = signRequest({ + method: 'PUT', + params: { + timestamp: 1700000000, + nonce: 'abc-nonce', + }, + data: { + id: '12', + }, + }) + + expect(typeof signed.params.signature).toBe('string') + expect(signed.params.signature).toHaveLength(32) + }) +}) diff --git a/src/lib/sign.ts b/src/lib/sign.ts new file mode 100644 index 0000000..e9fbe61 --- /dev/null +++ b/src/lib/sign.ts @@ -0,0 +1,31 @@ +import MD5 from 'crypto-js/md5' + +export const SIGN_KEY = 'MsvqoWCMxJsozicF5K4EFVSoVf8rEHfn' + +type SignInput = { + method?: string + params?: Record + data?: any +} + +export function signRequest (input: SignInput) { + const method = (input.method || 'GET').toUpperCase() + const params = { ...(input.params || {}) } + const data = input.data && typeof input.data === 'object' ? input.data : {} + const keys = Object.keys(params).sort() + const query = keys + .map((key) => `${key}=${encodeURIComponent(params[key])}`) + .join('&') + const secret = MD5(SIGN_KEY).toString() + const source = method === 'POST' || method === 'PUT' + ? `${query}&${JSON.stringify(data)}&${secret}` + : `${query}&${secret}` + + return { + params: { + ...params, + signature: MD5(source).toString(), + }, + data, + } +} diff --git a/src/lib/status.ts b/src/lib/status.ts new file mode 100644 index 0000000..46d42bb --- /dev/null +++ b/src/lib/status.ts @@ -0,0 +1,23 @@ +export const STATUS_CONFIG: Record = { + '4': { color: '#FF8E13', bg: '#FFF2E4', result: '处理中' }, + '1': { color: '#FF431D', bg: '#fff1e6', result: '待办理' }, + '3': { color: '#FF431D', bg: '#fff1e6', result: '待办理' }, + '2': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' }, + '5': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' }, + '7': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' }, + '8': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' }, + '6': { color: '#FF8E13', bg: '#FFF2E4', result: '待提交' }, +} + +export const PAY_ICON: Record = { + alipay: 'https://cdn.u8t.cn/frontend-static/corp-h5/static/images/alipay.png', + weixin: 'https://cdn.u8t.cn/frontend-static/corp-h5/static/images/weixin.png', +} + +export function getStatusMeta (status?: string, name?: string) { + const config = STATUS_CONFIG[String(status || '')] || { color: '#666666', bg: '#F5F5F5', result: '未知' } + return { + ...config, + result: name || config.result, + } +} diff --git a/src/lib/uuid.ts b/src/lib/uuid.ts new file mode 100644 index 0000000..5f01178 --- /dev/null +++ b/src/lib/uuid.ts @@ -0,0 +1,7 @@ +export function createDeviceId () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => { + const random = Math.floor(Math.random() * 16) + const value = char === 'x' ? random : (random & 0x3) | 0x8 + return value.toString(16) + }) +} diff --git a/src/pages/index/contact.tsx b/src/pages/index/contact.tsx new file mode 100644 index 0000000..b3939cf --- /dev/null +++ b/src/pages/index/contact.tsx @@ -0,0 +1,101 @@ +import { Image, Text, View } from '@tarojs/components' +import Taro from '@tarojs/taro' +import { CORP_ASSET_BASE, STATIC_CDN } from '@/lib/cdn' +import { pickServiceContact, type ServiceContact } from '@/lib/contact' + +type Props = { + open: boolean + contact: ServiceContact + onCancel: () => void +} + +function ContactItem ({ + icon, + title, + desc, + onClick, +}: { + icon: string + title: string + desc: string + onClick: () => void +}) { + return ( + + + + {title} + {desc} + + + ) +} + +export default function ContactModal ({ open, contact, onCancel }: Props) { + if (!open) return null + + const copyLink = async (url: string, title: string) => { + await Taro.setClipboardData({ data: url }) + Taro.showToast({ title: `${title}链接已复制`, icon: 'none' }) + } + + const handlePhone = () => { + if (!contact.phone) return + Taro.makePhoneCall({ phoneNumber: contact.phone.replace(/-/g, '') }) + } + + const hasAny = Boolean(contact.online || contact.phone || contact.weixin) + + return ( + + event.stopPropagation()}> + 联系客服 + 工作时间段:法定工作日 9:00-18:00 + + {contact.online ? ( + void copyLink(contact.online, '在线客服')} + /> + ) : null} + + {contact.weixin ? ( + void copyLink(contact.weixin, '微信客服')} + /> + ) : null} + + {contact.phone ? ( + + ) : null} + + {!hasAny ? ( + 暂无客服信息,请稍后再试 + ) : null} + + + 关闭 + + + + ) +} + +export function ContactEntry ({ onClick }: { onClick: () => void }) { + return ( + + + 联系我们 + + ) +} diff --git a/src/pages/index/index.config.ts b/src/pages/index/index.config.ts index 12abc5f..282742d 100644 --- a/src/pages/index/index.config.ts +++ b/src/pages/index/index.config.ts @@ -1,3 +1,5 @@ export default definePageConfig({ - navigationBarTitleText: '首页' + navigationBarTitleText: '网上快办平台', + navigationBarBackgroundColor: '#2F4FEE', + navigationBarTextStyle: 'white', }) diff --git a/src/pages/index/index.scss b/src/pages/index/index.scss index e69de29..f886be2 100644 --- a/src/pages/index/index.scss +++ b/src/pages/index/index.scss @@ -0,0 +1,616 @@ +.check-page { + min-height: 100vh; + box-sizing: border-box; + padding-bottom: 48px; + background: #2f4fee; +} + +.check-hero { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 420px; +} + +.check-hero__bg { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; +} + +.check-hero__title { + position: relative; + z-index: 1; + color: #fff; + font-size: 72px; + font-weight: 700; + letter-spacing: 6px; + line-height: 96px; + text-align: center; + text-shadow: 0 4px 8px #1840ff; +} + +.check-card { + position: relative; + z-index: 1; + margin: -80px 24px 24px; +} + +.check-card__header { + position: relative; + height: 154px; +} + +.check-card__header-bg { + display: block; + width: 100%; + height: 154px; +} + +.check-card__header-title { + position: absolute; + right: 0; + bottom: 28px; + left: 16px; + color: #fff; + font-size: 50px; + font-weight: 700; + line-height: 60px; + text-align: center; + text-shadow: 0 8px 0 #0026b3; +} + +.check-card__shell { + padding: 0 16px 16px; + border-radius: 0 0 28px 28px; + background: #cbdeff; +} + +.check-card__body, +.check-card__list { + padding: 40px 24px 40px; + border-radius: 0 0 28px 28px; + background: #fff; +} + +.check-card__note { + display: block; + margin-bottom: 24px; + color: #ff2626; + font-size: 24px; + line-height: 32px; +} + +.check-card__label { + display: flex; + align-items: center; + margin-bottom: 16px; + color: #666; + font-size: 28px; + line-height: 40px; +} + +.check-card__label-icon { + width: 40px; + height: 40px; + margin-right: 8px; +} + +.check-card__required { + margin-left: 4px; + color: #ff2f2f; +} + +.check-card__input { + box-sizing: border-box; + height: 88px; + padding: 0 24px; + border: 1px solid #ddd; + border-radius: 8px; + color: #222; + font-size: 28px; +} + +.check-card__button { + margin-top: 32px; + height: 88px; + border: 0; + border-radius: 8px; + background: #0261fc; + color: #fff; + font-size: 30px; + font-weight: 700; + line-height: 88px; +} + +.check-card__button::after { + border: 0; +} + +.check-card__empty { + min-height: 200px; + display: flex; + align-items: center; + justify-content: center; + color: #999; + font-size: 28px; +} + +.order-card { + padding: 24px 0 32px; + border-top: 1px solid #eee; +} + +.order-card--first { + padding-top: 0; + border-top: 0; +} + +.order-card__head { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 20px; + margin-bottom: 20px; + border-bottom: 1px solid #eee; +} + +.order-card__goods { + display: flex; + flex: 1; + min-width: 0; + align-items: center; +} + +.order-card__goods-icon { + width: 40px; + height: 40px; + margin-right: -8px; + border-radius: 20px; +} + +.order-card__goods-name { + margin-left: 16px; + color: #222; + font-size: 28px; + font-weight: 500; +} + +.order-card__status { + flex-shrink: 0; + min-width: 102px; + height: 44px; + padding: 0 12px; + border-radius: 8px; + font-size: 24px; + line-height: 44px; + text-align: center; +} + +.order-card__reject { + margin-bottom: 16px; + padding: 16px; + border-radius: 8px; + background: #fff3f3; + color: #ff0707; + font-size: 24px; + line-height: 36px; +} + +.order-card__row { + display: flex; + align-items: flex-start; + margin-top: 16px; + font-size: 28px; + line-height: 36px; +} + +.order-card__label { + flex-shrink: 0; + color: #666; +} + +.order-card__value { + flex: 1; + min-width: 0; + color: #222; + word-break: break-all; +} + +.order-card__value--id { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.order-card__copy { + width: 32px; + height: 32px; + margin-left: 12px; +} + +.order-card__pay-icon { + width: 40px; + height: 40px; + margin-right: 8px; +} + +.order-card__fee { + color: #ff2f2f; + font-size: 30px; + font-weight: 700; +} + +.order-card__actions { + display: flex; + justify-content: flex-end; + margin-top: 24px; +} + +.order-card__action { + min-width: 160px; + height: 64px; + margin-left: 16px; + border-radius: 12px; + background: #eaf3ff; + color: #0261fc; + font-size: 28px; + line-height: 64px; + text-align: center; +} + +.detail-mask, +.resubmit-mask { + position: fixed; + inset: 0; + z-index: 20; + background: rgba(0, 0, 0, 0.8); +} + +.detail-dialog { + position: absolute; + top: 50%; + right: 32px; + left: 32px; + padding: 32px 24px 24px; + border-radius: 16px; + background: #fff; + transform: translateY(-50%); +} + +.detail-dialog__title { + display: block; + margin-bottom: 24px; + color: #222; + font-size: 32px; + font-weight: 600; + text-align: center; +} + +.detail-dialog__close { + margin-top: 32px; + height: 80px; + border: 0; + border-radius: 8px; + background: #0261fc; + color: #fff; + font-size: 30px; + line-height: 80px; +} + +.detail-dialog__close::after { + border: 0; +} + +.resubmit-mask { + display: flex; + align-items: flex-end; +} + +.resubmit { + position: relative; + box-sizing: border-box; + width: 100%; + max-height: 84vh; + padding: 32px 24px 40px; + overflow-y: auto; + border-radius: 24px 24px 0 0; + background: linear-gradient(180deg, #c5eeff 0, #fff 136px) #fff; +} + +.resubmit__close { + position: absolute; + top: 24px; + right: 24px; + width: 40px; + height: 40px; +} + +.resubmit__close-text { + color: #b3b3b3; + font-size: 48px; + line-height: 40px; +} + +.resubmit__title { + display: block; + margin-bottom: 24px; + color: #222; + font-size: 34px; + font-weight: 500; + text-align: center; +} + +.resubmit__reject { + margin-bottom: 16px; + padding: 20px 24px; + border-radius: 12px; + background: #fff1f1; + color: #ff0707; + font-size: 24px; + line-height: 36px; +} + +.resubmit__field { + margin-bottom: 24px; +} + +.resubmit__label { + display: flex; + align-items: center; + margin-bottom: 12px; + color: #3d4044; + font-size: 28px; +} + +.resubmit__label-icon { + width: 40px; + height: 40px; + margin-right: 8px; +} + +.resubmit__required { + margin-left: 4px; + color: #ff2f2f; +} + +.resubmit__input, +.resubmit__map-input { + box-sizing: border-box; + height: 88px; + padding: 0 18px; + border: 1px solid #ddd; + border-radius: 12px; + color: #222; + font-size: 28px; +} + +.resubmit__map-input { + display: flex; + align-items: center; + justify-content: space-between; +} + +.resubmit__map-value { + flex: 1; + color: #222; +} + +.resubmit__map-placeholder { + flex: 1; + color: #b3b3b3; +} + +.resubmit__map-arrow { + width: 40px; + height: 40px; +} + +.resubmit__tip { + display: block; + margin-bottom: 24px; + color: #999; + font-size: 24px; + line-height: 36px; +} + +.resubmit__upload-list { + display: flex; + flex-wrap: wrap; +} + +.resubmit__upload-item, +.resubmit__upload-slot { + position: relative; + width: 200px; + height: 200px; + margin: 0 16px 16px 0; + overflow: hidden; + border-radius: 12px; + background: #f7f8fa; +} + +.resubmit__upload-image { + width: 100%; + height: 100%; +} + +.resubmit__upload-remove { + position: absolute; + top: 8px; + right: 8px; + width: 36px; + height: 36px; + border-radius: 18px; + background: rgba(0, 0, 0, 0.5); + color: #fff; + font-size: 28px; + line-height: 36px; + text-align: center; +} + +.resubmit__upload-slot { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border: 1px dashed #cfd6de; +} + +.resubmit__upload-plus { + width: 56px; + height: 56px; +} + +.resubmit__upload-text { + margin-top: 12px; + color: #999; + font-size: 22px; +} + +.resubmit__error { + display: block; + margin-bottom: 16px; + color: #ff2f2f; + font-size: 24px; +} + +.resubmit__submit { + height: 88px; + border: 0; + border-radius: 12px; + background: #0261fc; + color: #fff; + font-size: 32px; + font-weight: 700; + line-height: 88px; +} + +.resubmit__submit::after { + border: 0; +} + +.contact-entry { + position: fixed; + top: 24px; + right: 0; + z-index: 15; + box-sizing: border-box; + display: flex; + align-items: center; + width: 204px; + height: 60px; + padding-left: 20px; + border-radius: 268px 0 0 268px; + background: rgba(0, 0, 0, 0.5); +} + +.contact-entry__icon { + width: 40px; + height: 40px; + margin-right: 4px; + flex-shrink: 0; +} + +.contact-entry__text { + color: #fff; + font-size: 30px; + line-height: 44px; +} + +.contact-mask { + position: fixed; + inset: 0; + z-index: 30; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.65); +} + +.contact-dialog { + box-sizing: border-box; + width: 576px; + padding: 32px 28px 28px; + border-radius: 32px; + background: #fff; +} + +.contact-dialog__title { + display: block; + color: #222; + font-size: 34px; + font-weight: 600; + line-height: 48px; + text-align: center; +} + +.contact-dialog__hours { + display: block; + margin: 16px 0 28px; + color: #999; + font-size: 24px; + line-height: 32px; + text-align: center; +} + +.contact-dialog__empty { + display: block; + padding: 24px 0; + color: #999; + font-size: 26px; + text-align: center; +} + +.contact-item { + display: flex; + align-items: center; + height: 124px; + margin-bottom: 20px; + padding: 0 24px; + border-radius: 16px; + background: #fff; + box-shadow: 0 0 20px rgba(147, 153, 161, 0.12); +} + +.contact-item__icon { + width: 76px; + height: 76px; + margin-right: 24px; + flex-shrink: 0; +} + +.contact-item__copy { + display: flex; + flex-direction: column; +} + +.contact-item__title { + color: #222; + font-size: 28px; + font-weight: 500; + line-height: 40px; +} + +.contact-item__desc { + margin-top: 4px; + color: #999; + font-size: 24px; + line-height: 32px; +} + +.contact-dialog__close { + margin-top: 12px; + height: 80px; + border-radius: 16px; + background: #f5f6f8; + color: #666; + font-size: 30px; + line-height: 80px; + text-align: center; +} diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 3f2881f..134d8db 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -1,97 +1,283 @@ -import { View, WebView } from '@tarojs/components' -import Taro, { useLoad } from '@tarojs/taro' +import { Button, Image, Input, Text, View } from '@tarojs/components' +import Taro, { useDidShow, useLoad } from '@tarojs/taro' import { useState } from 'react' +import { getOrdersByPhone } from '@/api/pay' +import { bootstrapSession } from '@/lib/bootstrap' +import { BANNER_BG, CARD_HEADER, CORP_ASSET_BASE, ENTITY_LABELS, HOME_ICON_BASE, PAGE_TITLE } from '@/lib/cdn' +import { pickServiceContact, type ServiceContact } from '@/lib/contact' +import { + filterMapLabelOrders, + getGoodsIcons, + getOrderAddressName, + parseCoordinate, + type OrderLike, + type ResubmitValues, +} from '@/lib/order' +import { isValidPhone } from '@/lib/phone' +import { getStatusMeta, PAY_ICON } from '@/lib/status' +import { consumeMapPickerResult, type MapPickerResult } from '@/pages/map/picker' +import ContactModal, { ContactEntry } from './contact' +import ResubmitDrawer from './resubmit' import './index.scss' -// xunmeng2,devcon -const LINK_PREFIX = '/p/' -const LANDING_PAGE_URL = '/pages/landing/index' -const SCENE_STORAGE_KEY = 'qrcodeScene' +const COPY_ICON = `${CORP_ASSET_BASE}/images-mobile/icon-copy.png` -function isValidLinkCode (code: string) { - return Boolean(code) && !/[/?#&=]/.test(code) -} +export default function Index () { + const [ready, setReady] = useState(false) + const [inputPhone, setInputPhone] = useState('') + const [checking, setChecking] = useState(false) + const [searched, setSearched] = useState(false) + const [orders, setOrders] = useState([]) + const [detailOrder, setDetailOrder] = useState(null) + const [resubmitOrder, setResubmitOrder] = useState(null) + const [mapResult, setMapResult] = useState(null) + const [showContact, setShowContact] = useState(false) + const [contact, setContact] = useState({ online: '', phone: '', weixin: '' }) -function normalizeQrLinkCode (scene?: string) { - if (!scene) return '' - - try { - const decodedScene = decodeURIComponent(scene) - const linkParam = new URLSearchParams(decodedScene).get('link') - const rawCode = (linkParam || decodedScene).trim() - const code = rawCode.startsWith(LINK_PREFIX) - ? rawCode.slice(LINK_PREFIX.length) - : rawCode - - return isValidLinkCode(code) ? code : '' - } catch (error) { - console.warn('Invalid qrcode scene.', error) - return '' - } -} - -function buildWebViewUrl (linkCode: string, mpCode: string, options: Record) { - const queryParams = new URLSearchParams() - - Object.entries(options).forEach(([key, value]) => { - if (typeof value === 'string') { - queryParams.set(key, value) + useLoad(async (options) => { + try { + const session = await bootstrapSession(options || {}) + setInputPhone(session.phone) + setContact(pickServiceContact(session.config)) + setReady(true) + } catch (error) { + Taro.showToast({ + title: error instanceof Error ? error.message : '初始化失败', + icon: 'none', + }) } }) - queryParams.set('mpCode', mpCode) - - return `https://nb.zuom8.cn${LINK_PREFIX}${encodeURIComponent(linkCode)}?${queryParams.toString()}` -} - -export default function Index () { - const [url, setUrl] = useState('') - - useLoad(async (options) => { - const currentScene = typeof options?.scene === 'string' ? options.scene.trim() : '' - let scene = currentScene - - if (currentScene) { - try { - Taro.setStorageSync(SCENE_STORAGE_KEY, currentScene) - } catch (error) { - console.warn('Failed to save qrcode scene.', error) - } - } else { - try { - const storedScene = Taro.getStorageSync(SCENE_STORAGE_KEY) - scene = typeof storedScene === 'string' ? storedScene.trim() : '' - } catch (error) { - console.warn('Failed to read stored qrcode scene.', error) - } + useDidShow(() => { + const result = consumeMapPickerResult() + if (result) { + setMapResult(result) } + }) - const linkCode = normalizeQrLinkCode(scene) - - if (!linkCode) { - try { - await Taro.redirectTo({ url: LANDING_PAGE_URL }) - } catch (error) { - console.error('Failed to open landing page.', error) - Taro.showToast({ - title: '页面加载失败', - icon: 'none' - }) - } - + const queryOrders = async (phone: string) => { + if (!phone) { + Taro.showToast({ title: '请输入联系电话', icon: 'none' }) + return + } + if (!isValidPhone(phone)) { + Taro.showToast({ title: '请填写正确联系电话', icon: 'none' }) return } - const wxCode = await Taro.login() + setChecking(true) + try { + const res = await getOrdersByPhone(phone) + const nextOrders = filterMapLabelOrders(res.data || []) + setOrders(nextOrders) + setSearched(true) + if (!nextOrders.length) { + Taro.showToast({ title: '无记录', icon: 'none' }) + } + } catch { + Taro.showToast({ title: '当前无法查询,请稍后再试', icon: 'none' }) + } finally { + setChecking(false) + } + } - setUrl(buildWebViewUrl(linkCode, wxCode.code, options)) - }) + const handleChooseMap = (draft: ResubmitValues) => { + const point = parseCoordinate(draft.entity_address) + const query = [ + point ? `lng=${point.lng}` : '', + point ? `lat=${point.lat}` : '', + draft.entity_address_name ? `name=${encodeURIComponent(draft.entity_address_name)}` : '', + ].filter(Boolean).join('&') + + Taro.navigateTo({ + url: query ? `/pages/map/index?${query}` : '/pages/map/index', + }) + } return ( - - { - url && - } + + + + {PAGE_TITLE} + + setShowContact(true)} /> + + + + + 进度查询 + + + + 注:无法查询订单请联系客服 + + + 联系电话 + * + + setInputPhone(event.detail.value.replace(/\D/g, ''))} + /> + + + + + + {searched ? ( + + + + 进度查询 + + + + {orders.length ? orders.map((order, index) => ( + { + if (!order.out_trade_no) return + Taro.setClipboardData({ data: String(order.out_trade_no) }) + }} + onViewDetail={() => setDetailOrder(order)} + onResubmit={() => { + setMapResult(null) + setResubmitOrder(order) + }} + /> + )) : ( + + 暂无记录 + + )} + + + + ) : null} + + {detailOrder ? ( + setDetailOrder(null)}> + event.stopPropagation()}> + 订单详情 + + + + + + + + + ) : null} + + setShowContact(false)} + /> + + { + setResubmitOrder(null) + setMapResult(null) + }} + onChooseMap={handleChooseMap} + onSuccess={() => { + setResubmitOrder(null) + setMapResult(null) + void queryOrders(inputPhone) + }} + /> + + ) +} + +function OrderCard ({ + order, + first, + onCopy, + onViewDetail, + onResubmit, +}: { + order: OrderLike + first: boolean + onCopy: () => void + onViewDetail: () => void + onResubmit: () => void +}) { + const status = getStatusMeta(order.process_status, order.process_status_name) + const goodsIcons = getGoodsIcons(order) + const showDetail = order.process_status === '2' + const payIcon = PAY_ICON[order.pay_type] + + return ( + + + + {goodsIcons.map((icon: string, index: number) => ( + + ))} + {order.goods_name || '-'} + + + {status.result} + + + + {order.reject_reason ? ( + 驳回理由:{order.reject_reason} + ) : null} + + + + + + + + + 订单ID: + {order.out_trade_no || '-'} + {order.out_trade_no ? ( + + ) : null} + + + + 支付方式: + {payIcon ? : null} + ¥{order.total_fee || '-'} + + + + {showDetail ? ( + 查看详情 + ) : null} + 补充资料 + + + ) +} + +function InfoLine ({ label, value }: { label: string, value: string }) { + return ( + + {label} + {value} ) } diff --git a/src/pages/index/resubmit.tsx b/src/pages/index/resubmit.tsx new file mode 100644 index 0000000..f22affe --- /dev/null +++ b/src/pages/index/resubmit.tsx @@ -0,0 +1,331 @@ +import { Button, Image, Input, Text, View } from '@tarojs/components' +import Taro from '@tarojs/taro' +import { useEffect, useState } from 'react' +import { uploadImage } from '@/api/user' +import { updateOrderExtra } from '@/api/pay' +import { ENTITY_LABELS, HOME_ICON_BASE, MAP_ASSET_BASE } from '@/lib/cdn' +import { isValidPhone } from '@/lib/phone' +import { + buildResubmitPayload, + formatMapCoordinate, + getInitialPhone2, + getOrderAddressName, + getOrderExtraImages, + getOrderMapAddress, + parseCoordinate, + type OrderLike, + type ResubmitValues, + type UploadedImage, +} from '@/lib/order' +import type { MapPickerResult } from '@/pages/map/picker' + +type Props = { + open: boolean + order: OrderLike | null + mapResult?: MapPickerResult | null + onCancel: () => void + onChooseMap: (draft: ResubmitValues) => void + onSuccess: () => void +} + +function FieldLabel ({ icon, text, required }: { icon: string, text: string, required?: boolean }) { + return ( + + + {text} + {required ? * : null} + + ) +} + +function UploadField ({ + label, + values, + multiple = false, + uploading, + progress, + onChange, + onRemove, +}: { + label: string + values: UploadedImage[] + multiple?: boolean + uploading: boolean + progress: number + onChange: (filePaths: string[]) => void + onRemove: (index: number) => void +}) { + const showUpload = multiple || values.length === 0 + + const handleChoose = async () => { + const res = await Taro.chooseImage({ + count: multiple ? 6 : 1, + sizeType: ['compressed'], + sourceType: ['album', 'camera'], + }) + if (res.tempFilePaths?.length) { + onChange(res.tempFilePaths) + } + } + + return ( + + {values.map((value, index) => ( + + + onRemove(index)}> + × + + + ))} + {showUpload ? ( + void handleChoose()}> + + + {uploading ? `上传中 ${Math.round(progress)}%` : (multiple && values.length > 0 ? '继续上传' : `请上传${label}`)} + + + ) : null} + + ) +} + +export default function ResubmitDrawer ({ + open, + order, + mapResult, + onCancel, + onChooseMap, + onSuccess, +}: Props) { + const [values, setValues] = useState({}) + const [storefrontImages, setStorefrontImages] = useState([]) + const [licenseImages, setLicenseImages] = useState([]) + const [uploadingField, setUploadingField] = useState<'storefront' | 'license' | null>(null) + const [uploadProgress, setUploadProgress] = useState(0) + const [formError, setFormError] = useState('') + const [submitting, setSubmitting] = useState(false) + + useEffect(() => { + if (!order || !open) return + + const mapAddress = mapResult ? formatMapCoordinate(mapResult) : '' + const mapAddressName = mapResult?.address || mapResult?.name || '' + setValues({ + entity_name: values.entity_name || order.entity_name || mapResult?.name || '', + entity_address_name: mapResult ? mapAddressName || getOrderAddressName(order) : values.entity_address_name || getOrderAddressName(order), + entity_address: mapResult ? mapAddress : values.entity_address || getOrderMapAddress(order), + entity_phone: values.entity_phone || order.entity_phone || '', + entity_phone2: values.entity_phone2 || getInitialPhone2(order), + }) + if (storefrontImages.length === 0) { + setStorefrontImages(getOrderExtraImages(order, ['entity_storefront_image', 'storefront_image'])) + } + if (licenseImages.length === 0) { + setLicenseImages(getOrderExtraImages(order, ['entity_business_license_image', 'business_license_image']).slice(0, 1)) + } + setFormError('') + // Only seed from the current order / returned map point. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, order, mapResult]) + + const updateField = (field: K, value: ResubmitValues[K]) => { + setValues((current) => ({ ...current, [field]: value })) + } + + const handleUpload = async (field: 'storefront' | 'license', filePaths: string[]) => { + setUploadingField(field) + setUploadProgress(0) + setFormError('') + try { + const images: UploadedImage[] = [] + for (const [index, filePath] of filePaths.entries()) { + const uploaded = await uploadImage(filePath, (percent) => { + setUploadProgress(((index + percent / 100) / filePaths.length) * 100) + }) + images.push({ + id: uploaded.id, + url: uploaded.url, + preview: filePath, + }) + } + if (field === 'storefront') { + setStorefrontImages((current) => [...current, ...images]) + } else { + setLicenseImages(images.slice(0, 1)) + } + } catch (error) { + setFormError(error instanceof Error ? error.message : '图片上传失败,请稍后重试') + } finally { + setUploadingField(null) + setUploadProgress(0) + } + } + + const handleSubmit = async () => { + if (!order || submitting) return + if (uploadingField) { + setFormError('图片正在上传,请稍候') + return + } + if (!values.entity_name?.trim()) { + Taro.showToast({ title: `请输入${ENTITY_LABELS.entityName}`, icon: 'none' }) + return + } + if (!values.entity_address_name?.trim()) { + Taro.showToast({ title: `请输入${ENTITY_LABELS.entityAddress}`, icon: 'none' }) + return + } + if (!values.entity_address?.trim()) { + Taro.showToast({ title: `请选择${ENTITY_LABELS.entityMapLocation}`, icon: 'none' }) + return + } + if (!isValidPhone(values.entity_phone || '')) { + Taro.showToast({ title: '请填写正确的联系电话1', icon: 'none' }) + return + } + if (!isValidPhone(values.entity_phone2 || '')) { + Taro.showToast({ title: '请填写正确的联系电话2', icon: 'none' }) + return + } + if (storefrontImages.length === 0) { + Taro.showToast({ title: '请上传门头照片', icon: 'none' }) + return + } + + setSubmitting(true) + setFormError('') + try { + await updateOrderExtra(buildResubmitPayload({ + order, + values, + storefrontImages, + licenseImages, + })) + Taro.showToast({ title: '重新提交成功', icon: 'success' }) + onSuccess() + } catch { + Taro.showToast({ title: '重新提交失败,请稍后再试', icon: 'none' }) + } finally { + setSubmitting(false) + } + } + + if (!open || !order) return null + + const mapPoint = parseCoordinate(values.entity_address) + + return ( + + + + × + + 重新提交 + {order.reject_reason ? ( + 驳回理由:{order.reject_reason} + ) : null} + + + + + updateField('entity_name', event.detail.value)} + /> + + + + + updateField('entity_address_name', event.detail.value)} + /> + + + + + onChooseMap({ + ...values, + entity_address: mapPoint ? formatMapCoordinate(mapPoint) : values.entity_address, + })} + > + + {values.entity_address || '请选择地图位置'} + + + + + + + + updateField('entity_phone', event.detail.value.replace(/\D/g, ''))} + /> + + + + + updateField('entity_phone2', event.detail.value.replace(/\D/g, ''))} + /> + + + 温馨提示:请确保您的电话畅通,工作人员会在1-7个工作日联系您 + + + + void handleUpload('storefront', paths)} + onRemove={(index) => setStorefrontImages((current) => current.filter((_, itemIndex) => itemIndex !== index))} + /> + + + + + void handleUpload('license', paths)} + onRemove={() => setLicenseImages([])} + /> + + + {formError ? {formError} : null} + + + + + + ) +} diff --git a/src/pages/landing/index.scss b/src/pages/landing/index.scss deleted file mode 100644 index 23d6314..0000000 --- a/src/pages/landing/index.scss +++ /dev/null @@ -1,305 +0,0 @@ -/* stylelint-disable selector-class-pattern -- The project requires BEM class names. */ - -.landing-page { - --landing-green: #07c160; - --landing-green-dark: #06ae56; - --landing-canvas: #f2f7fb; - --landing-surface: #fff; - --landing-ink: #17181a; - --landing-muted: #606772; - --landing-placeholder: #959ba3; - --landing-border: #c7ccd2; - --landing-error: #d94242; - - min-height: 100vh; - padding: 54px 30px calc(38px + env(safe-area-inset-bottom)); - overflow-x: hidden; - box-sizing: border-box; - color: var(--landing-ink); - font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; - background: var(--landing-canvas); -} - -.landing-page__intro { - padding: 0 10px; -} - -.landing-page__title { - display: block; - font-size: 48px; - font-weight: 700; - line-height: 1.35; - letter-spacing: -1px; -} - -.landing-page__description { - display: block; - margin-top: 18px; - color: var(--landing-muted); - font-size: 28px; - line-height: 1.65; -} - -.landing-page__notice { - display: block; - margin-top: 14px; - color: var(--landing-muted); - font-size: 24px; - line-height: 1.5; -} - -.landing-page__form { - margin-top: 30px; - padding: 28px; - border: 1px solid #d9dde1; - border-radius: 14px; - box-sizing: border-box; - background: var(--landing-surface); - box-shadow: 0 8px 28px rgb(40 65 86 / 5%); -} - -.landing-page__field + .landing-page__field { - margin-top: 26px; -} - -.landing-page__label { - display: block; - margin-bottom: 12px; - font-size: 28px; - font-weight: 600; - line-height: 1.4; -} - -.landing-page__service-types { - display: flex; - gap: 12px; -} - -.landing-page__service-type { - display: flex; - height: 72px; - flex: 1; - align-items: center; - justify-content: center; - border: 1px solid var(--landing-border); - border-radius: 10px; - box-sizing: border-box; - font-size: 28px; - background: var(--landing-surface); -} - -.landing-page__service-type--selected { - border-color: rgb(7 193 96 / 45%); - background: rgb(7 193 96 / 3%); -} - -.landing-page__radio { - display: flex; - align-items: center; - gap: 12px; -} - -.landing-page__input, -.landing-page__textarea { - width: 100%; - border: 1px solid var(--landing-border); - border-radius: 10px; - box-sizing: border-box; - color: var(--landing-ink); - font-size: 28px; - background: var(--landing-surface); - transition: border-color 160ms ease, box-shadow 160ms ease; -} - -.landing-page__input { - height: 72px; - padding: 0 22px; -} - -.landing-page__textarea { - height: 116px; - padding: 18px 22px; - line-height: 1.5; -} - -.landing-page__input:focus, -.landing-page__textarea:focus { - border-color: var(--landing-green); - box-shadow: 0 0 0 3px rgb(7 193 96 / 10%); -} - -.landing-page__input--error { - border-color: var(--landing-error); -} - -.landing-page__placeholder { - color: var(--landing-placeholder); -} - -.landing-page__error, -.landing-page__agreement-error { - display: block; - margin-top: 8px; - color: var(--landing-error); - font-size: 22px; - line-height: 1.4; -} - -.landing-page__agreement { - display: flex; - align-items: center; - margin: 26px 18px 0; - font-size: 24px; - line-height: 1.5; -} - -.landing-page__checkbox { - margin-right: 10px; - transform: scale(0.88); - transform-origin: left center; -} - -.landing-page__agreement-text { - color: var(--landing-ink); -} - -.landing-page__link { - color: var(--landing-green); -} - -.landing-page__agreement-error { - margin-right: 18px; - margin-left: 18px; -} - -.landing-page__submit { - height: 84px; - margin: 24px 10px 0; - padding: 0; - border: 0; - border-radius: 12px; - color: #fff; - font-size: 32px; - font-weight: 700; - line-height: 84px; - background: var(--landing-green); - box-shadow: none; -} - -.landing-page__submit::after { - border: 0; -} - -.landing-page__submit--pressed { - background: var(--landing-green-dark); - transform: scale(0.99); -} - -.landing-page__footer { - display: flex; - align-items: center; - margin-top: 28px; - flex-direction: column; - color: var(--landing-muted); - font-size: 22px; - line-height: 1.75; -} - -.landing-page__document-mask { - position: fixed; - z-index: 40; - inset: 0; - display: flex; - align-items: flex-end; - padding-top: 80px; - box-sizing: border-box; - background: rgb(0 0 0 / 48%); -} - -.landing-page__document { - display: flex; - width: 100%; - max-height: 88vh; - padding-bottom: env(safe-area-inset-bottom); - overflow: hidden; - border-radius: 28px 28px 0 0; - box-sizing: border-box; - flex-direction: column; - background: var(--landing-surface); -} - -.landing-page__document-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 28px 30px 22px; - border-bottom: 1px solid #e5e7e9; -} - -.landing-page__document-title, -.landing-page__document-date, -.landing-page__document-heading, -.landing-page__document-copy { - display: block; -} - -.landing-page__document-title { - font-size: 34px; - font-weight: 700; - line-height: 1.4; -} - -.landing-page__document-date { - margin-top: 4px; - color: var(--landing-muted); - font-size: 20px; - line-height: 1.4; -} - -.landing-page__document-close { - height: 60px; - margin: 0; - padding: 0 24px; - border: 0; - border-radius: 10px; - color: var(--landing-green-dark); - font-size: 24px; - line-height: 60px; - background: rgb(7 193 96 / 8%); -} - -.landing-page__document-close::after { - border: 0; -} - -.landing-page__document-body { - height: 860px; - padding: 4px 30px 36px; - box-sizing: border-box; -} - -.landing-page__document-section { - padding-top: 26px; -} - -.landing-page__document-heading { - font-size: 27px; - font-weight: 600; - line-height: 1.5; -} - -.landing-page__document-copy { - margin-top: 10px; - color: var(--landing-muted); - font-size: 24px; - line-height: 1.75; - text-align: justify; -} - -@media (prefers-reduced-motion: reduce) { - .landing-page__input, - .landing-page__textarea, - .landing-page__submit { - transition: none; - } -} diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx deleted file mode 100644 index a321748..0000000 --- a/src/pages/landing/index.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import { Button, Checkbox, CheckboxGroup, Input, Radio, RadioGroup, ScrollView, Text, Textarea, View } from '@tarojs/components' -import Taro from '@tarojs/taro' -import { useState } from 'react' -import './index.scss' - -type ConsultationType = 'operation' | 'brand' -type DocumentType = 'privacy' | 'service' - -type ConsultationForm = { - consultationType: ConsultationType - businessName: string - businessCategory: string - contactName: string - contactPhone: string - description: string -} - -type FormErrors = Partial> - -type DocumentSection = { - heading: string - content: string -} - -const CONTACT_PHONE = '4001166311' - -const INITIAL_FORM: ConsultationForm = { - consultationType: 'operation', - businessName: '', - businessCategory: '', - contactName: '', - contactPhone: '', - description: '', -} - -const DOCUMENTS: Record = { - privacy: { - title: '隐私政策', - updatedAt: '更新日期:2026年7月20日', - sections: [ - { - heading: '一、我们处理的信息', - content: '当您使用咨询信息填写功能时,页面会处理您主动填写的企业或门店名称、经营类别、联系人姓名、联系电话和咨询内容。我们不会要求您提供身份证件、精确位置、通讯录、相册或其他与咨询无关的信息。', - }, - { - heading: '二、处理目的和方式', - content: '上述信息仅用于在当前页面核对咨询资料是否填写完整。当前版本没有服务器提交接口,不会将表单内容上传、保存或提供给第三方。关闭或刷新页面后,已填写内容不会由本小程序留存。', - }, - { - heading: '三、权限与第三方共享', - content: '本页面不会申请位置、相机、相册、麦克风或通讯录权限,也不会通过本页面向任何第三方共享、转让或公开披露您填写的信息。', - }, - { - heading: '四、您的权利', - content: '您可以随时修改或清空尚未提交的内容,也可以不填写表单并直接退出页面。若您对个人信息保护有疑问,可在工作时间拨打页面底部客服电话联系我们。', - }, - { - heading: '五、政策更新', - content: '如后续开通在线提交或存储功能,我们会在收集信息前更新本政策,明确告知收集范围、使用目的、保存期限和保护措施,并重新取得您的同意。', - }, - ], - }, - service: { - title: '用户服务协议', - updatedAt: '生效日期:2026年7月20日', - sections: [ - { - heading: '一、服务内容', - content: '本页面提供门店经营信息梳理、品牌展示建议和宣传资料制作等企业咨询信息填写服务。本服务不提供任何平台的官方认证、官方代办或结果承诺。', - }, - { - heading: '二、使用方式', - content: '您应根据实际需求填写企业或门店信息。页面校验完成后,您可自行选择是否拨打客服电话进行正式咨询。当前版本不会在线提交或保存表单内容。', - }, - { - heading: '三、用户责任', - content: '您应保证提供的信息真实、合法且不侵犯他人权益,不得利用本服务提交违法、虚假、侵权或与企业咨询无关的内容。', - }, - { - heading: '四、服务费用', - content: '信息填写和电话咨询不收取费用。如后续服务产生费用,工作人员会在服务开始前说明具体项目、价格和退款规则,由您自主决定是否购买。', - }, - { - heading: '五、免责声明', - content: '咨询建议仅供经营决策参考,实际执行效果会受到经营环境、市场变化和用户自身情况影响。我们不会承诺特定经营结果。', - }, - { - heading: '六、联系我们', - content: '如您对服务内容或本协议有疑问,可在工作日9:00-18:00拨打客服电话400-116-6311。', - }, - ], - }, -} - -const PHONE_PATTERN = /^1[3-9]\d{9}$/ - -const validateForm = (form: ConsultationForm, agreed: boolean) => { - const errors: FormErrors = {} - - if (!form.businessName.trim()) errors.businessName = '请输入企业或门店名称' - if (!form.businessCategory.trim()) errors.businessCategory = '请输入经营类别' - if (!form.contactName.trim()) errors.contactName = '请输入联系人姓名' - - if (!form.contactPhone.trim()) { - errors.contactPhone = '请输入联系电话' - } else if (!PHONE_PATTERN.test(form.contactPhone.trim())) { - errors.contactPhone = '请输入正确的手机号码' - } - - if (!form.description.trim()) errors.description = '请描述您的咨询需求' - if (!agreed) errors.agreement = '请阅读并同意隐私政策和用户服务协议' - - return errors -} - -export default function Landing () { - const [form, setForm] = useState(INITIAL_FORM) - const [agreed, setAgreed] = useState(false) - const [errors, setErrors] = useState({}) - const [activeDocument, setActiveDocument] = useState(null) - - const updateField = (field: K, value: ConsultationForm[K]) => { - setForm((current) => ({ ...current, [field]: value })) - setErrors((current) => ({ ...current, [field]: undefined })) - } - - const handleSubmit = async () => { - const validationErrors = validateForm(form, agreed) - const firstError = Object.values(validationErrors)[0] - - setErrors(validationErrors) - - if (firstError) { - Taro.showToast({ title: firstError, icon: 'none' }) - return - } - - const result = await Taro.showModal({ - title: '信息填写完成', - content: '当前版本不会上传或保存表单信息。如需正式咨询,请拨打客服电话400-116-6311。', - cancelText: '稍后联系', - confirmText: '拨打客服', - confirmColor: '#07c160', - }) - - if (result.confirm) { - await Taro.makePhoneCall({ phoneNumber: CONTACT_PHONE }) - } - } - - const document = activeDocument ? DOCUMENTS[activeDocument] : null - - return ( - - - 门店经营咨询,在线填写 - - 提供经营信息梳理、品牌展示建议和宣传资料制作咨询,帮助线下门店明确实际需求。 - - 企业咨询服务,不提供任何平台官方代办服务 - - - - - 咨询类型 - updateField('consultationType', event.detail.value as ConsultationType)} - > - - 经营信息咨询 - - - 品牌展示咨询 - - - - - - 企业或门店名称 - updateField('businessName', event.detail.value)} - /> - {errors.businessName && {errors.businessName}} - - - - 经营类别 - updateField('businessCategory', event.detail.value)} - /> - {errors.businessCategory && {errors.businessCategory}} - - - - 联系人 - updateField('contactName', event.detail.value)} - /> - {errors.contactName && {errors.contactName}} - - - - 联系电话 - updateField('contactPhone', event.detail.value)} - /> - {errors.contactPhone && {errors.contactPhone}} - - - - 咨询需求 -