diff --git a/README.md b/README.md index d4fec383..ea04bc79 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,252 @@ -# order-site-workspace +# order_site -统一工作区版本的订单自动兑换系统。 +订单自动兑换与履约管理系统。 + +项目采用前后端分离架构,主要用于接收订单、匹配履约规则、生成领取链接、管理库存、执行自动/半自动兑换,并提供后台页面处理人工复核、平台配置和任务追踪。 + +## 技术栈 + +- 前端:Vue 3、Vite、Element Plus、TypeScript +- 后端:Node.js、Express、PostgreSQL、Playwright +- OCR:独立 Python worker +- 网关:Caddy +- 本地与生产运行:Docker Compose ## 目录结构 ```text apps/ - backend/ Node + Express + Playwright + OCR worker - frontend/ Vue 3 + Vite + Element Plus + backend/ 后端 API、任务编排、平台对接、数据库迁移 + src/ + data/ 本地开发运行数据和平台配置 + subservices/ocr-worker/ OCR 子服务源码 + frontend/ 前端后台、领取页和配置页面 deploy/ - caddy/ Caddy 配置 - docker/ Dockerfile -docs/ 迁移与部署文档 -docker-compose.yml + caddy/ Caddy 反向代理配置 + docker/ 前后端与 OCR 镜像 Dockerfile +docs/ 平台接口、迁移设计和部署资料 +docker-compose.dev.yml 本地 Docker 开发环境 +docker-compose.yml 生产/服务器 Compose ``` ## 本地开发 -前端: - -```bash -cd apps/frontend -npm install -npm run dev -``` - -后端: - -```bash -cp .env.mac-docker.example .env -cd apps/backend/subservices/ocr-worker -uv sync -cd apps/backend -npm install -npm run dev -``` - -## Docker 部署 - -```bash -cp .env.server.example .env -mkdir -p deploy/data/backend -docker compose up -d -``` - -生产版 Compose 现在会把后端运行数据直接挂载到宿主机目录: - -- `deploy/data/backend` - -这样服务器上可以直接查看和备份: - -- `logs/` -- `browser-sessions/` -- `redeem-screenshots/` -- `agiso-shops.json` -- `order-fulfillment-bindings.json` - -如果你本地已经配好了店铺和履约绑定,首次上线前可以把这两份文件先放进去: - -- `deploy/data/backend/agiso-shops.json` -- `deploy/data/backend/order-fulfillment-bindings.json` - -后端启动时会自动执行数据库 migration、初始化默认管理员、同步履约目录;但 `/app/data` 下的业务配置文件仍以宿主机目录内容为准。 - -## Docker 本地开发 +推荐直接使用 Docker,本地不需要额外维护 Node、PostgreSQL、Playwright 浏览器和 OCR 运行环境。 ```bash cp .env.mac-docker.example .env docker compose -f docker-compose.dev.yml up -d --build ``` -开发环境现在不再把 Caddy 绑定到固定域名。 -这意味着: +启动后常用入口: -- 本地直接访问 `http://localhost` 可用 -- 用 ngrok / cloudflared 转发到 `http://localhost:80` 时,不需要因为随机子域名变化而改 Caddy 配置 -- 如果要生成发给用户的公网领取链接,只需要更新 `.env` 里的 `APP_BASE_URL` +- 前端、后台、领取页:`http://localhost` +- 后端健康检查:`http://localhost/health` +- 后端 API 前缀:`http://localhost/api/v1/...` +- OCR worker:`http://127.0.0.1:8100/health` +- noVNC 浏览器画面:`http://127.0.0.1:6080/vnc.html` -开发版 Compose 现在是热更新模式: - -- `postgres` 容器提供 PostgreSQL -- 后端源码挂载到容器里,运行 `npm run dev` -- 前端源码挂载到容器里,运行 `vite` -- Caddy 只负责把 `80/443` 反代到前后端容器 - -通常只有首次启动、改 Dockerfile、改系统依赖时才需要 `--build`。 - -开发版 Compose 会把后端运行产物目录直接挂载到: - -- [apps/backend/data](/Users/yml/codes/order-site-workspace/apps/backend/data) - -这样本地可以直接看到: - -- `data/logs/*.log` -- 浏览器会话产物 -- 截图和证明文件 - -当 `TENCENT_BROWSER_HEADLESS=false` 时,开发版后端容器会自动启动 `Xvfb + x11vnc + noVNC`。 -默认可直接在本机访问: - -- `http://127.0.0.1:6080/vnc.html` - -这样即使整个项目都跑在 Docker 里,也可以直接查看后端 Playwright 浏览器画面。 - -PostgreSQL 数据则保存在 Docker volume 里: - -- `postgres_dev_data` - -当前 Dockerfile 已默认针对国内服务器优化以下下载源: - -- Debian `apt` 使用腾讯云镜像 -- `npm` 使用 `npmmirror` -- Python `pip` 使用腾讯云 PyPI 镜像 -- 后端 Playwright 固定为 `1.42.1`,浏览器下载使用 `npmmirror` - -如果你的服务器网络环境不同,也可以在构建时覆盖: +查看容器状态: ```bash -docker compose build \ - --build-arg DEBIAN_MIRROR=mirrors.tuna.tsinghua.edu.cn \ - --build-arg NPM_REGISTRY=https://registry.npmjs.org \ - --build-arg UV_INDEX_URL=https://pypi.org/simple +docker compose -f docker-compose.dev.yml ps ``` -默认入口: +查看日志: + +```bash +docker compose -f docker-compose.dev.yml logs -f backend +docker compose -f docker-compose.dev.yml logs -f frontend +docker compose -f docker-compose.dev.yml logs -f ocr-worker +``` + +停止环境: + +```bash +docker compose -f docker-compose.dev.yml down +``` + +## 开发验证 + +本项目以容器内验证为准。 + +```bash +docker compose -f docker-compose.dev.yml exec -T backend npm test +docker compose -f docker-compose.dev.yml exec -T backend npm run typecheck +docker compose -f docker-compose.dev.yml exec -T frontend npm run typecheck +docker compose -f docker-compose.dev.yml exec -T frontend npm run lint +``` + +前端生产构建: + +```bash +docker compose -f docker-compose.dev.yml exec -T frontend npm run build +``` + +后端数据库迁移通常会在启动时自动执行;需要手动执行时: + +```bash +docker compose -f docker-compose.dev.yml exec -T backend npm run db:migrate +``` + +## 热更新 + +开发版 Compose 会把源码挂载进容器: + +- 修改 `apps/backend/src`:后端容器内 `node --watch` 自动重启 +- 修改 `apps/frontend/src`:Vite 自动 HMR +- 修改 `apps/backend/subservices/ocr-worker`:下次 OCR 调用会使用挂载后的代码 +- 修改数据库 migration:重启后端会自动执行迁移 + +如果只改业务代码,一般不需要重新 `--build`。改 Dockerfile、系统依赖或基础镜像时再重新构建。 + +## 环境变量 + +本地开发从根目录 `.env` 读取,模板为: + +```bash +cp .env.mac-docker.example .env +``` + +生产部署从 `.env.server.example` 复制: + +```bash +cp .env.server.example .env +``` + +常用变量: + +- `APP_BASE_URL`:应用公网地址,用于推导领取链接 +- `CLAIM_BASE_URL`:领取页地址,优先级高于 `APP_BASE_URL` +- `ADMIN_SESSION_SECRET`:后台登录态签名密钥 +- `ADMIN_DEFAULT_USERS_JSON`:默认后台用户 +- `DATABASE_URL`:PostgreSQL 连接串 +- `TENCENT_BROWSER_HEADLESS`:Playwright 是否无头运行 +- `TENCENT_BROWSER_PREWARM`:启动时是否预热浏览器 +- `OCR_BASE_URL`:OCR worker 地址 + +## 运行数据 + +本地开发数据: + +- 后端配置与运行产物:`apps/backend/data` +- PostgreSQL 数据:Docker volume `postgres_dev_data` +- 后端日志:`apps/backend/data/logs` +- 浏览器会话:`apps/backend/data/browser-sessions` +- 兑换截图:`apps/backend/data/redeem-screenshots` + +生产部署数据: + +- 后端数据挂载到 `deploy/data/backend` +- PostgreSQL 数据保存在 Docker volume `postgres_data` +- Caddy 数据保存在 Docker volume `caddy_data` + +注意:`apps/backend/data/*.json` 中可能包含账号、Cookie、token、平台配置等敏感信息。提交代码前请确认没有把真实生产凭据提交到仓库。 + +## 生产部署 + +在服务器上准备 `.env` 后启动: + +```bash +cp .env.server.example .env +mkdir -p deploy/data/backend +docker compose up -d --build +``` + +生产入口: - 前端与领取页:`https://你的域名/` - 后端健康检查:`https://你的域名/health` -- 后端接口前缀:`https://你的域名/api/v1/...` +- 后端 API:`https://你的域名/api/v1/...` -开发环境下的链接生成规则: +生产 Compose 会自动: -- 优先使用 `CLAIM_BASE_URL` -- 如果未设置 `CLAIM_BASE_URL`,后端会自动根据 `APP_BASE_URL` 推导为 `APP_BASE_URL/#/claim` +- 启动 PostgreSQL +- 启动 OCR worker +- 启动后端并执行 migration +- 初始化默认后台管理员 +- 同步履约目录 +- 通过 Caddy 暴露前端和 API -开发时如果只改业务代码,直接保留 `docker compose -f docker-compose.dev.yml up -d` 即可: +查看生产日志: -- 改后端 `src/`:容器内自动热重启 -- 改前端 `src/`:Vite 自动热更新 -- 改 OCR Python:后端下次调用时直接走挂载后的最新源码 -- 改数据库 schema:后端启动时会自动执行 migration +```bash +docker compose logs -f backend +docker compose logs -f web +``` -详细文档: +## 开发数据脚本 -- [服务器部署教程](/Users/yml/codes/order-site-workspace/docs/%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B.md) -- [首次上线检查清单](/Users/yml/codes/order-site-workspace/docs/%E9%A6%96%E6%AC%A1%E4%B8%8A%E7%BA%BF%E6%A3%80%E6%9F%A5%E6%B8%85%E5%8D%95.md) -- [mac 本地 Docker 调试模板](/Users/yml/codes/order-site-workspace/.env.mac-docker.example) -- [服务器部署模板](/Users/yml/codes/order-site-workspace/.env.server.example) +后端提供开发数据脚本: -## 迁移原则 +```bash +docker compose -f docker-compose.dev.yml exec -T backend npm run seed:dev-data +docker compose -f docker-compose.dev.yml exec -T backend npm run cleanup:dev-data +``` -- 不重写现有业务代码 -- 先统一目录、部署和配置 -- 生产环境优先使用 `.env` 和宿主机数据目录挂载 -- 本机 Docker 调试优先使用 [docker-compose.dev.yml](/Users/yml/codes/order-site-workspace/docker-compose.dev.yml) +脚本默认有预览/确认语义,执行前注意终端输出说明。 + +## 主要业务模块 + +- `apps/backend/src/routes`:API 路由 +- `apps/backend/src/services/order`:订单、webhook、库存和履约匹配 +- `apps/backend/src/services/fulfillment`:快手 Cloud 等履约编排 +- `apps/backend/src/services/session`:腾讯登录、二维码、兑换和截图 +- `apps/backend/src/services/admin`:后台读写、权限、配置管理 +- `apps/backend/src/services/platforms`:外部平台 API 封装 +- `apps/backend/src/repositories`:数据库访问 +- `apps/frontend/src/views/admin`:后台页面 +- `apps/frontend/src/views/claim`:用户领取页 + +## 排障 + +如果页面打不开: + +```bash +docker compose -f docker-compose.dev.yml ps +docker compose -f docker-compose.dev.yml logs -f web +``` + +如果后端未 ready: + +```bash +docker compose -f docker-compose.dev.yml logs -f backend +curl http://localhost/health/ready +``` + +如果浏览器兑换或扫码异常: + +```bash +docker compose -f docker-compose.dev.yml logs -f backend +open http://127.0.0.1:6080/vnc.html +``` + +如果前端类型或 lint 失败: + +```bash +docker compose -f docker-compose.dev.yml exec -T frontend npm run typecheck +docker compose -f docker-compose.dev.yml exec -T frontend npm run lint +``` + +如果后端单测失败: + +```bash +docker compose -f docker-compose.dev.yml exec -T backend npm test +``` + +## 提交前检查清单 + +```bash +docker compose -f docker-compose.dev.yml exec -T backend npm test +docker compose -f docker-compose.dev.yml exec -T backend npm run typecheck +docker compose -f docker-compose.dev.yml exec -T frontend npm run typecheck +docker compose -f docker-compose.dev.yml exec -T frontend npm run lint +``` + +确认项: + +- 没有提交真实账号、Cookie、token、手机号或生产配置 +- `.env` 未被提交 +- 数据库 migration 与代码一起提交 +- 领取链接相关改动已在 Docker 环境验证 +- 涉及 Playwright/OCR 的改动已看过 noVNC 或相关日志 diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js b/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js index 3e95c19b..b3681123 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles.test.js @@ -30,6 +30,7 @@ test('resolveAdminCloudtentaclesCredentialPayload merges payload and saved sourc }, ), { + sourceKey: 'default', baseUrl: 'https://override.example.com', username: 'saved-user', password: 'saved-pass', @@ -60,6 +61,7 @@ test('resolveAdminCloudtentaclesSessionPayload merges payload source and persist }, ), { + sourceKey: 'default', baseUrl: 'https://saved.example.com', token: 'payload-token', deviceId: 'saved-device', @@ -111,6 +113,7 @@ test('buildAdminCloudtentaclesSessionSummary masks token and reports permission }, ), { + sourceKey: 'default', tokenMasked: 'abcdef****567890', permissionCount: 2, permissions: ['asset', 'sku'], @@ -138,6 +141,7 @@ test('buildAdminCloudtentaclesLoginResult assembles login response payload', () }, ), { + sourceKey: 'default', baseUrl: 'https://cloud.example.com', username: 'test-user', phoneMasked: '138****5678', @@ -145,6 +149,7 @@ test('buildAdminCloudtentaclesLoginResult assembles login response payload', () responseMessage: 'success', token: 'abcdef1234567890', session: { + sourceKey: 'default', tokenMasked: 'abcdef****567890', permissionCount: 1, permissions: ['asset'], @@ -170,9 +175,11 @@ test('buildAdminCloudtentaclesValidateResult assembles validate response payload {}, ), { + sourceKey: 'default', baseUrl: 'https://cloud.example.com', loggedInAt: '2026-05-04T12:00:00.000Z', session: { + sourceKey: 'default', tokenMasked: 'abcdef****567890', permissionCount: 2, permissions: ['asset', 'sku'], diff --git a/apps/backend/src/services/admin/platform-config/context.test.js b/apps/backend/src/services/admin/platform-config/context.test.js index 4094d154..16495888 100644 --- a/apps/backend/src/services/admin/platform-config/context.test.js +++ b/apps/backend/src/services/admin/platform-config/context.test.js @@ -88,6 +88,7 @@ test('resolveCloudtentaclesAdminContext merges payload source and persisted sess ) assert.deepEqual(context, { + sourceKey: 'default', baseUrl: 'https://saved.example.com', token: 'token-1', deviceId: 'device-saved', diff --git a/apps/backend/src/services/admin/platform-config/writes.test.js b/apps/backend/src/services/admin/platform-config/writes.test.js index 2a7f2548..cb63cd67 100644 --- a/apps/backend/src/services/admin/platform-config/writes.test.js +++ b/apps/backend/src/services/admin/platform-config/writes.test.js @@ -113,6 +113,8 @@ test('normalizeAdminCloudtentaclesSourceConfigPayload applies defaults', () => { deviceType: '2', }), { + key: 'default', + label: '', enabled: false, baseUrl: 'https://123.207.217.176', username: 'user', diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js index dcee312c..080333a6 100644 --- a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js +++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js @@ -36,6 +36,7 @@ test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => { configId: '', cloudtentacles: { cloudSourceKey: 'default', + cloudSourceKeyFallbacks: [], skuId: 28, skuName: '', vnKey: '1', diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json index 0560b2f5..913dee90 100644 --- a/apps/frontend/package-lock.json +++ b/apps/frontend/package-lock.json @@ -1598,6 +1598,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -1659,13 +1671,14 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", - "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -1829,7 +1842,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2378,9 +2390,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -2560,6 +2572,19 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3089,7 +3114,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/muggle-string": { @@ -3100,9 +3124,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "funding": [ { "type": "github", @@ -3290,9 +3314,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -3309,7 +3333,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/apps/frontend/src/composables/shared/useSessionPolling.ts b/apps/frontend/src/composables/shared/useSessionPolling.ts index c4399c11..ed8ae613 100644 --- a/apps/frontend/src/composables/shared/useSessionPolling.ts +++ b/apps/frontend/src/composables/shared/useSessionPolling.ts @@ -1,19 +1,19 @@ -import { computed, ref, type Ref, type ComputedRef } from 'vue' +import { computed, ref, type ComputedRef } from 'vue' import { POLL_INTERVAL_MS, POLL_FAILURE_LIMIT } from './sessionConstants' -export interface UseSessionPollingOptions { +export interface UseSessionPollingOptions { /** Whether there is an active session – used as a guard inside the poll loop. */ hasSession: ComputedRef /** Called on each poll tick (typically refreshSessionSummary). */ - onPollTick: () => Promise + onPollTick: () => Promise /** Optional: determines whether polling should continue. Defaults to checking hasSession. */ shouldKeepPolling?: ComputedRef /** Context-specific warning message prefix when polling fails repeatedly. */ pollWarningPrefix: string } -export function useSessionPolling(options: UseSessionPollingOptions) { +export function useSessionPolling(options: UseSessionPollingOptions) { const { hasSession, onPollTick, pollWarningPrefix } = options const pollWarningMessage = ref('') diff --git a/apps/frontend/src/composables/shared/useSessionPresentation.ts b/apps/frontend/src/composables/shared/useSessionPresentation.ts index 71b164b3..57cdde8e 100644 --- a/apps/frontend/src/composables/shared/useSessionPresentation.ts +++ b/apps/frontend/src/composables/shared/useSessionPresentation.ts @@ -5,6 +5,8 @@ import type { TencentLoginType } from '@/types/tencent/session' import { DEFAULT_LOGIN_TYPE } from './sessionConstants' +type ReadableRef = Pick, 'value'> + // --------------------------------------------------------------------------- // syncLoginTypeFromDetail // --------------------------------------------------------------------------- @@ -102,7 +104,7 @@ export interface UseSessionPresentationOptions { } export function useSessionRoleFacts(options: UseSessionPresentationOptions) { - const { activityInfo, statusLabel, roleFactDefaults } = options + const { activityInfo, roleFactDefaults } = options const roleFacts = computed(() => [ { @@ -168,17 +170,17 @@ export function useSessionResultFacts(options: UseSessionResultFactsOptions) { // --------------------------------------------------------------------------- export function buildRedeemBlockedReason(options: { - detail: ComputedRef - hasSession: ComputedRef - loginTypeLabel: ComputedRef - redeemLoading: ComputedRef - roleReady: ComputedRef - roleConfirmed: ComputedRef - sessionNotice: ComputedRef + detail: ReadableRef + hasSession: ReadableRef + loginTypeLabel: ReadableRef + redeemLoading: ReadableRef + roleReady: ReadableRef + roleConfirmed: ReadableRef + sessionNotice: ReadableRef context: 'claim' | 'admin' /** Claim-specific fields */ - tokenStatus?: ComputedRef - requiresSupportReview?: ComputedRef + tokenStatus?: ReadableRef + requiresSupportReview?: ReadableRef }): string { const { detail, diff --git a/apps/frontend/src/composables/shared/useSessionQrDisplay.ts b/apps/frontend/src/composables/shared/useSessionQrDisplay.ts index 364fdcad..802d805e 100644 --- a/apps/frontend/src/composables/shared/useSessionQrDisplay.ts +++ b/apps/frontend/src/composables/shared/useSessionQrDisplay.ts @@ -1,4 +1,4 @@ -import { computed, ref, watch, type Ref, type ComputedRef } from 'vue' +import { computed, ref, watch, type ComputedRef } from 'vue' /** * Shared QR code display logic: renders the base64 QR image, tracks its diff --git a/apps/frontend/src/composables/useAdminManualRedeemPage.ts b/apps/frontend/src/composables/useAdminManualRedeemPage.ts index 4c4948a8..2a7a5b80 100644 --- a/apps/frontend/src/composables/useAdminManualRedeemPage.ts +++ b/apps/frontend/src/composables/useAdminManualRedeemPage.ts @@ -122,7 +122,7 @@ export function useAdminManualRedeemPage() { () => `请让客户使用${loginTypeLabel.value}扫码,并在手机上确认登录`, ) - const { roleFacts } = useSessionRoleFacts({ + useSessionRoleFacts({ activityInfo, statusLabel, roleFactDefaults: { nickname: '等待客户登录' }, @@ -230,10 +230,6 @@ export function useAdminManualRedeemPage() { }) // ── Helpers ──────────────────────────────────────────────────────── - function applyLoginTypeFromDetail(nextDetail: AdminManualRedeemDetail) { - loginType.value = syncLoginTypeFromDetail(nextDetail, loginType.value) - } - function restartPollingIfNeeded(nextDetail = detail.value) { resetPolling() diff --git a/apps/frontend/src/composables/useClaimPage.ts b/apps/frontend/src/composables/useClaimPage.ts index e698b826..6f56ab04 100644 --- a/apps/frontend/src/composables/useClaimPage.ts +++ b/apps/frontend/src/composables/useClaimPage.ts @@ -88,7 +88,7 @@ export function useClaimPage(token: string) { () => `请使用${loginTypeLabel.value}扫描二维码,并在手机上确认登录`, ) - const { roleFacts } = useSessionRoleFacts({ + useSessionRoleFacts({ activityInfo, statusLabel, roleFactDefaults: { nickname: '等待扫码登录' }, diff --git a/apps/frontend/src/services/admin/platform-config/cloudtentacles.ts b/apps/frontend/src/services/admin/platform-config/cloudtentacles.ts index 38a5b252..e325e5c5 100644 --- a/apps/frontend/src/services/admin/platform-config/cloudtentacles.ts +++ b/apps/frontend/src/services/admin/platform-config/cloudtentacles.ts @@ -1,8 +1,6 @@ import { apiDelete, apiGet, apiPost } from '@/lib/http' import type { AdminCloudtentaclesLoginTestResult, - AdminCloudtentaclesPersistedSession, - AdminCloudtentaclesSessionsMap, AdminCloudtentaclesSkuListResult, AdminCloudtentaclesSendSmsResult, AdminCloudtentaclesSourceConfigResponse, diff --git a/apps/frontend/src/types/admin/index.ts b/apps/frontend/src/types/admin/index.ts index 54a47593..9656adcf 100644 --- a/apps/frontend/src/types/admin/index.ts +++ b/apps/frontend/src/types/admin/index.ts @@ -81,6 +81,7 @@ export type { AdminCloudtentaclesSessionsMap, AdminCloudtentaclesSourceConfig, AdminCloudtentaclesPersistedSession, + AdminCloudtentaclesSessionItem, AdminCloudtentaclesSendSmsResult, AdminCloudtentaclesSessionSummary, AdminCloudtentaclesLoginTestResult, diff --git a/apps/frontend/src/types/admin/platform-config/cloudtentacles.ts b/apps/frontend/src/types/admin/platform-config/cloudtentacles.ts index 8a30ceee..45c29aa3 100644 --- a/apps/frontend/src/types/admin/platform-config/cloudtentacles.ts +++ b/apps/frontend/src/types/admin/platform-config/cloudtentacles.ts @@ -26,6 +26,8 @@ export interface AdminCloudtentaclesPersistedSession { hasToken: boolean } +export type AdminCloudtentaclesSessionItem = AdminCloudtentaclesPersistedSession + export interface AdminCloudtentaclesSessionsMap { [sourceKey: string]: AdminCloudtentaclesPersistedSession } diff --git a/apps/frontend/src/types/admin/platform-config/index.ts b/apps/frontend/src/types/admin/platform-config/index.ts index d782f11f..634641e7 100644 --- a/apps/frontend/src/types/admin/platform-config/index.ts +++ b/apps/frontend/src/types/admin/platform-config/index.ts @@ -39,6 +39,7 @@ export type { AdminCloudtentaclesSessionsMap, AdminCloudtentaclesSourceConfig, AdminCloudtentaclesPersistedSession, + AdminCloudtentaclesSessionItem, AdminCloudtentaclesSendSmsResult, AdminCloudtentaclesSessionSummary, AdminCloudtentaclesLoginTestResult, diff --git a/apps/frontend/src/views/admin/fulfillment/bindings/composables/types.ts b/apps/frontend/src/views/admin/fulfillment/bindings/composables/types.ts index d197c0f3..bc5c17c0 100644 --- a/apps/frontend/src/views/admin/fulfillment/bindings/composables/types.ts +++ b/apps/frontend/src/views/admin/fulfillment/bindings/composables/types.ts @@ -1,4 +1,4 @@ -import type { AdminFulfillmentBindingConfigItem, AdminObservedProductItem } from '@/types/admin' +import type { AdminObservedProductItem } from '@/types/admin' export type EditableBinding = { id: string diff --git a/apps/frontend/src/views/admin/fulfillment/bindings/composables/useFulfillmentBindingsLookup.ts b/apps/frontend/src/views/admin/fulfillment/bindings/composables/useFulfillmentBindingsLookup.ts index 272a7a6e..6b99cead 100644 --- a/apps/frontend/src/views/admin/fulfillment/bindings/composables/useFulfillmentBindingsLookup.ts +++ b/apps/frontend/src/views/admin/fulfillment/bindings/composables/useFulfillmentBindingsLookup.ts @@ -3,7 +3,6 @@ import { reactive, ref } from 'vue' import { lookupAdminFulfillmentBindingOrder } from '@/services/admin' import type { AdminFulfillmentLookupItem, AdminFulfillmentLookupResult } from '@/types/admin' -import type { ImportableProductCandidate } from './types' import type { useFulfillmentBindings } from './useFulfillmentBindings' export function useFulfillmentBindingsLookup( diff --git a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudNinetyoneSection.vue b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudNinetyoneSection.vue index d7581f9b..e4876f32 100644 --- a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudNinetyoneSection.vue +++ b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudNinetyoneSection.vue @@ -1,7 +1,7 @@ diff --git a/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts b/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts index 9c85aa54..63a2f699 100644 --- a/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts +++ b/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts @@ -6,15 +6,12 @@ import { fetchAdminInventoryItems, fetchAdminInventorySkuSuggestions, importAdminInventoryItems, - invalidateAdminInventoryItem, - releaseAdminInventoryItem, } from '@/services/admin' import type { AdminInventoryItemListItem, AdminInventorySkuSuggestion, AdminPagination, } from '@/types/admin' -import { hasAdminRole } from '@/utils/admin-auth' import { formatAdminDateTime } from '@/utils/admin-time' import { adminInventoryCredentialTypeOptions, @@ -425,7 +422,6 @@ export function useAdminInventory() { formatInventoryStatus, getGroupSuggestion, // utils - hasAdminRole, formatAdminDateTime, adminInventoryCredentialTypeOptions, adminInventoryStatusOptions, diff --git a/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue b/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue index 389d115e..90201ad8 100644 --- a/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue +++ b/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue @@ -184,6 +184,7 @@ const taskDetailLink = computed(() => :review-card-title="reviewCardTitle" :review-ready="reviewReady" :result-ready="resultReady" + :screenshot-empty-title="screenshotEmptyTitle" :screenshot-empty-message="screenshotEmptyMessage" :screenshot-url="screenshotUrl" :screenshot-loading="screenshotLoading" diff --git a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue index 20ba2dfe..c6cbe3d0 100644 --- a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue +++ b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue @@ -3,6 +3,7 @@ type Props = { reviewCardTitle: string reviewReady: boolean resultReady: boolean + screenshotEmptyTitle: string screenshotEmptyMessage: string screenshotUrl: string screenshotLoading: boolean diff --git a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue index 5f2e11ca..2c8701d0 100644 --- a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue +++ b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue @@ -1,7 +1,7 @@