diff --git a/docs/seed_verification.md b/docs/seed_verification.md index b205e57..4b60856 100644 --- a/docs/seed_verification.md +++ b/docs/seed_verification.md @@ -1,58 +1,97 @@ -# Seed Verification & UI Test Guide (Staging) +# UI Page Testing Checklist (By Page → Actions → Data Validation) -## Service Startup (Local) -- Backend: `go run ./backend/main.go serve` (default `http://localhost:8080`). -- Portal: `npm -C frontend/portal install && npm -C frontend/portal run dev` (default `http://localhost:5174`, remote `http://10.1.1.104:5174`). -- Superadmin: `npm -C frontend/superadmin install && npm -C frontend/superadmin run dev` (default `http://localhost:5173`, remote `http://10.1.1.104:5173`). -- Proxies: portal `/v1` → backend `8080`; superadmin `/super/v1` & `/v1` → backend `8080`. +## Prereqs +- Seed: `go run ./backend/main.go seed`(会 TRUNCATE 并重建种子数据)。 +- tenantCode: `SELECT code FROM tenants ORDER BY id DESC LIMIT 1;`(示例:`meipai_768`)。 +- Portal base: `http://10.1.1.104:5174`,Superadmin base: `http://10.1.1.104:5173`。 +- Login creds: Portal OTP 固定 `1234`(手机号 `13800138000`),Superadmin `superadmin/superadmin123`。 -## Chrome DevTools MCP -- Remote Chrome 已启动;连接 `http://10.1.1.104:9222`。 -- 仅页面操作,无需 API 直接调用。 +## Portal(/t/:tenantCode/...) +- **Login** `/auth/login` + - 操作:填手机号 `13800138000` + 勾条款 → 获取验证码 → 输入 `1234` → 登录。 + - 数据验证:登录态写入 token;后续接口 200;用户信息显示 “戏迷小张(ID:2)”。 + - DB 验证:`users` 中 phone 唯一;登录命中最新用户(ID 最大)。 -## AI Auto-Handling Prereqs -- Seed 检查:`SELECT COUNT(*) FROM tenants;` 为 0 则 `go run ./backend/main.go seed`。 -- tenantCode:`SELECT code FROM tenants ORDER BY id DESC LIMIT 1;` → `/t/:tenantCode`。 -- 清理存储:`localStorage.clear(); sessionStorage.clear();` + 清除 cookies。 +- **Home** `/t/:tenantCode` + - 操作:页面加载、点击内容卡片进入详情。 + - 数据验证:内容列表非空;跳转到对应 contentId。 + - DB 验证:`contents` 至少 10 条,tenant_id 为当前租户。 -## Portal Test Checklist (tenantCode from DB) -1) **Login** `/auth/login` → 手机 `13800138000` + OTP `1234`,勾选条款;断言“登录成功”。 -2) **Home** `/t/:tenantCode` → 有内容/导航无报错。 -3) **Content Detail** `/t/:tenantCode/contents/1` → 封面/正文/评论可见。 -4) **Orders** `/t/:tenantCode/me/orders` → 列表非空。 -5) **Library** `/t/:tenantCode/me/library` → 列表非空。 -6) **Favorites** `/t/:tenantCode/me/favorites` → 列表非空,可见“取消收藏”。 -7) **Likes** `/t/:tenantCode/me/likes` → 列表非空,可见“取消点赞”。 -8) **Notifications** `/t/:tenantCode/me/notifications` → 页面无错误。 -9) **Creator entry** `/t/:tenantCode/creator` → 页面可加载。 -- 每页:调用 `chrome-devtools_list_console_messages` 无 `error`。 +- **Content Detail** `/t/:tenantCode/contents/:id` + - 操作:查看封面/正文,点赞/收藏按钮可用。 + - 数据验证:点赞/收藏后,列表页/我的点赞/收藏同步变化。 + - DB 验证:`content_assets` 绑定封面/主资产;`user_content_actions` 写入 like/favorite。 -## Superadmin Test Checklist -1) **Login** `/super/auth/login` → `superadmin/superadmin123`,成功进入 Dashboard。 -2) **Orders** `/super/superadmin/orders` → 有标记/对账数据。 -3) **Finance** `/super/superadmin/finance` → 提现审核/流水/异常标签有数据。 -4) **Users** `/super/superadmin/users` → 列表可见。 -5) **Tenants** `/super/superadmin/tenants` → 列表可见。 -6) **Notifications** `/super/superadmin/notifications` → 模板列表可见。 -7) **System Configs** `/super/superadmin/system-configs` → `site_name/support_email` 可见。 -8) **Audit Logs** `/super/superadmin/audit-logs` → 列表可见。 -- 每页:检查 console 无 `error`;必要时截图。 +- **Orders** `/t/:tenantCode/me/orders` + - 操作:查看订单列表,点击“查看详情”。 + - 数据验证:列表非空;详情金额/状态与列表一致。 + - DB 验证:`orders`、`order_items`、`content_access` 状态/金额匹配。 -## Manual-Only Coverage -- 上传链路(init/part/complete)需真实存储;seed 仅提供素材记录。 +- **Order Detail** `/t/:tenantCode/me/orders/:id` + - 操作:查看订单状态、商品信息,复制订单号。 + - 数据验证:金额/状态显示正确;“已支付/已退款”等标签与订单一致。 + - DB 验证:`orders.status`、`order_items.amount_paid` 与页面一致。 -## Seed Notes -- 执行 `go run ./backend/main.go seed` 会 TRUNCATE 全部业务表后重建。 -- 用户:`creator(13800000001)`, `test(13800138000)`, `superadmin(13800009999)`, `negative(13800009998)`。 -- 租户:`meipai_`(DB 查询获取)。 -- Orders/likes/favorites/notifications 已预置示例数据。 +- **Checkout** `/t/:tenantCode/checkout?contentId=:id`(半成品) + - 操作:加载内容 → 点击“提交订单”→ 跳转 Payment。 + - 数据验证:显示商品标题/价格(取 `price_amount` 等);创建订单成功跳转。 + - DB 验证:`orders` 新增一条(tenant_id、user_id、content_id 匹配)。 -## MCP Steps (Example) -- `chrome-devtools_new_page http://10.1.1.104:5174/auth/login` -- 填手机号+勾条款 → 获取验证码 → 填 `1234` → 登录 -- 导航并断言: - - `/t/:tenantCode/me/orders` 有行 - - `/t/:tenantCode/me/library` 有行 - - `/t/:tenantCode/me/favorites` 有行 - - `/t/:tenantCode/me/likes` 有行 -- Superadmin:打开登录 → 填账号密码 → 断言 Dashboard → 进入 Orders/Finance/Notifications/System Configs/Audit Logs;每页检查 console `error`。 +- **Payment** `/t/:tenantCode/payment/:orderId`(半成品) + - 操作:轮询 `/status`,点击“立即支付”(调用 `/pay`),或“模拟支付成功”。 + - 数据验证:状态变为 paid/completed 后跳转订单详情;金额显示来自订单。 + - DB 验证:`orders.status` 更新;若 pay 未实现,可用模拟成功代替。 + +- **Library** `/t/:tenantCode/me/library` + - 操作:查看已购列表。 + - 数据验证:非空;点击内容可播放/查看。 + - DB 验证:`content_access` 记录存在且 status=active。 + +- **Favorites / Likes** `/t/:tenantCode/me/favorites` `/likes` + - 操作:列表展示,取消收藏/点赞。 + - 数据验证:取消后列表减少;详情页同步。 + - DB 验证:`user_content_actions` 删除/新增对应记录。 + +- **Coupons** `/t/:tenantCode/me/coupons` + - 操作:查看可用/已用/已过期;新人券应可见。 + - 数据验证:金额/门槛显示正确。 + - DB 验证:`user_coupons.status`、`coupons` 配置匹配。 + +- **Wallet** `/t/:tenantCode/me/wallet` + - 操作:查看余额、交易明细;(如有)充值入口。 + - 数据验证:余额 50 元(seed);交易明细含购买/充值记录。 + - DB 验证:`users.balance`、`tenant_ledgers`/交易记录一致。 + +- **Notifications** `/t/:tenantCode/me/notifications` + - 操作:查看列表。 + - 数据验证:种子“欢迎注册”可见;群发暂未落库(已知缺口)。 + - DB 验证:`notifications` 当前仅种子数据;群发功能待修复。 + +- **Creator** `/t/:tenantCode/creator/*` + - 操作:Dashboard/内容/订单/成员/设置页面可打开。 + - 数据验证:种子内容/订单/成员可见。 + - DB 验证:`contents`、`tenant_users`、`orders` 等与租户匹配。 + +## Superadmin +- **Login** `/super/auth/login` + - 操作:账号 `superadmin/superadmin123` 登录。 + - 数据验证:进入 Dashboard;后续接口 200。 + +- **Dashboard/Orders/Finance/Users/Tenants/Reports/Health/Contents/Assets/System Configs/Audit Logs** + - 操作:打开页面,查看列表/统计。 + - 数据验证:列表有种子数据;console 无 error。 + - DB 验证:对应表数据与显示一致(如 `orders`、`tenant_ledgers`、`contents`、`media_assets` 等)。 + +- **Notifications (模板管理)** `/super/superadmin/notifications` + - 操作:编辑模板并保存。 + - 数据验证:保存成功提示;模板列表更新。 + - DB 验证:`notification_templates` 记录更新。 + +- **Broadcast (已知缺口)** `/super/v1/notifications/broadcast` + - API 返回 200 但 DB 无新增通知,说明 Notification provider 未落库;群发暂不可用。 + +## 已知缺口 / 注意事项 +- 群发通知未写入 DB(待修复 provider)。 +- 支付流半成品:需要后端 `/orders`、`/pay`、`/status` 字段/逻辑配合;前端已添加提交/轮询/支付按钮及模拟成功。 +- 上传链路未验证(需真实存储)。 +- 使用最新 seed 后再测,避免重复用户/数据污染。 diff --git a/frontend/portal/src/components/TopNavbar.vue b/frontend/portal/src/components/TopNavbar.vue index 09ca3e6..f66209b 100644 --- a/frontend/portal/src/components/TopNavbar.vue +++ b/frontend/portal/src/components/TopNavbar.vue @@ -28,7 +28,6 @@ onMounted(() => { checkAuth(); }); - const logout = () => { localStorage.removeItem("token"); localStorage.removeItem("user"); @@ -95,10 +94,10 @@ const logout = () => {
diff --git a/frontend/portal/src/utils/request.js b/frontend/portal/src/utils/request.js index c8bf68d..4bfd199 100644 --- a/frontend/portal/src/utils/request.js +++ b/frontend/portal/src/utils/request.js @@ -4,7 +4,11 @@ import { getTenantCode } from "./tenant"; export async function request(endpoint, options = {}) { const tenantCode = getTenantCode(); const isAuthRequest = endpoint.startsWith("/auth/"); - const baseUrl = isAuthRequest ? "/v1" : tenantCode ? `/v1/t/${tenantCode}` : "/v1"; + const baseUrl = isAuthRequest + ? "/v1" + : tenantCode + ? `/v1/t/${tenantCode}` + : "/v1"; if (!tenantCode && !isAuthRequest && !endpoint.startsWith("/tenants")) { // 无租户时仍允许访问公共入口,避免全局页面 404 diff --git a/frontend/portal/src/views/HomeView.vue b/frontend/portal/src/views/HomeView.vue index 78e8217..c4c4ad3 100644 --- a/frontend/portal/src/views/HomeView.vue +++ b/frontend/portal/src/views/HomeView.vue @@ -248,7 +248,7 @@ onMounted(fetchData); v-for="creator in matchedCreators" :key="creator.id" class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 transition-colors cursor-pointer border border-transparent hover:border-slate-200" - @click="$router.push(`/t/${creator.id}`)" + @click="$router.push(`/t/${creator.id}`)" >
{{ creator.name }}
diff --git a/frontend/portal/src/views/order/CheckoutView.vue b/frontend/portal/src/views/order/CheckoutView.vue index 7826564..2652478 100644 --- a/frontend/portal/src/views/order/CheckoutView.vue +++ b/frontend/portal/src/views/order/CheckoutView.vue @@ -1,6 +1,94 @@ + + diff --git a/frontend/portal/src/views/order/PaymentView.vue b/frontend/portal/src/views/order/PaymentView.vue index d60b34d..882795d 100644 --- a/frontend/portal/src/views/order/PaymentView.vue +++ b/frontend/portal/src/views/order/PaymentView.vue @@ -8,9 +8,10 @@ const route = useRoute(); const router = useRouter(); const tenantRoute = (path) => tenantPath(path, route); -const orderId = route.params.id || "82934712"; -const amount = "9.90"; // Should fetch order details first -const productName = "《霸王别姬》全本实录珍藏版"; +const orderId = route.params.id; +const amount = ref("0.00"); +const productName = ref(""); +const orderStatus = ref(""); const paymentMethod = ref("alipay"); const timeLeft = ref(900); // 15 minutes @@ -29,6 +30,38 @@ const formatTime = (seconds) => { return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; }; +const loadOrder = async () => { + try { + const res = await orderApi.status(orderId); + orderStatus.value = res?.status || ""; + if (res?.amount_paid !== undefined) { + amount.value = (res.amount_paid / 100).toFixed(2); + } else if (res?.amount_original !== undefined) { + amount.value = (res.amount_original / 100).toFixed(2); + } + if (res?.content_title) { + productName.value = res.content_title; + } + if (orderStatus.value === "paid" || orderStatus.value === "completed") { + isSuccess.value = true; + isScanning.value = false; + } + } catch (e) { + console.error("Load order failed", e); + } +}; + +const payOrder = async () => { + try { + isScanning.value = true; + await orderApi.pay(orderId, { method: paymentMethod.value }); + // 支付接口若未立即更新状态,将继续依赖轮询 + } catch (e) { + console.error("Pay order failed", e); + isScanning.value = false; + } +}; + const simulateSuccess = () => { isScanning.value = true; setTimeout(() => { @@ -45,11 +78,20 @@ onMounted(() => { if (timeLeft.value > 0) timeLeft.value--; }, 1000); + loadOrder(); + // Poll Status pollTimer = setInterval(async () => { try { const res = await orderApi.status(orderId); - if (res.status === "paid" || res.status === "completed") { + orderStatus.value = res?.status || ""; + if (res?.amount_paid !== undefined) { + amount.value = (res.amount_paid / 100).toFixed(2); + } + if (res?.content_title) { + productName.value = res.content_title; + } + if (orderStatus.value === "paid" || orderStatus.value === "completed") { isScanning.value = false; isSuccess.value = true; clearInterval(pollTimer); @@ -105,7 +147,10 @@ onUnmounted(() => {

订单提交成功,请尽快支付

¥ {{ amount }}
- 商品:{{ productName }} + 商品:{{ productName || "加载中..." }} +
+
+ 状态:{{ orderStatus || "pending" }}
@@ -193,12 +238,20 @@ onUnmounted(() => { - +
+ + +