docs: add pagewise ui test checklist

This commit is contained in:
2026-02-03 12:37:24 +08:00
parent 1b10315170
commit 5a125c0f51
6 changed files with 273 additions and 90 deletions

View File

@@ -28,7 +28,6 @@ onMounted(() => {
checkAuth();
});
const logout = () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
@@ -95,10 +94,10 @@ const logout = () => {
<div class="flex items-center gap-4">
<template v-if="isLoggedIn">
<!-- Notification -->
<router-link
:to="tenantRoute('/me/notifications')"
class="relative w-10 h-10 flex items-center justify-center rounded-full hover:bg-slate-50 text-slate-600"
>
<router-link
:to="tenantRoute('/me/notifications')"
class="relative w-10 h-10 flex items-center justify-center rounded-full hover:bg-slate-50 text-slate-600"
>
<i class="pi pi-bell text-xl"></i>
<span
class="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full border border-white"
@@ -106,10 +105,10 @@ const logout = () => {
</router-link>
<!-- Creator Entry -->
<router-link
:to="tenantRoute('/creator/apply')"
class="hidden sm:flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-50 rounded-lg border border-slate-200"
>
<router-link
:to="tenantRoute('/creator/apply')"
class="hidden sm:flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-50 rounded-lg border border-slate-200"
>
<i class="pi pi-pencil"></i>
<span>创作</span>
</router-link>
@@ -143,16 +142,16 @@ const logout = () => {
{{ user.phone }}
</p>
</div>
<router-link
:to="tenantRoute('/me')"
class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50"
>个人中心</router-link
>
<router-link
:to="tenantRoute('/creator')"
class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50"
>创作者中心</router-link
>
<router-link
:to="tenantRoute('/me')"
class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50"
>个人中心</router-link
>
<router-link
:to="tenantRoute('/creator')"
class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50"
>创作者中心</router-link
>
<div class="border-t border-slate-50 mt-1"></div>
<button
@click="logout"
@@ -166,11 +165,11 @@ const logout = () => {
</template>
<template v-else>
<router-link
:to="'/auth/login'"
class="bg-primary-600 text-white px-6 py-2 rounded-full font-medium hover:bg-primary-700 transition-all shadow-sm shadow-primary-100 active:scale-95"
>登录 / 注册</router-link
>
<router-link
:to="'/auth/login'"
class="bg-primary-600 text-white px-6 py-2 rounded-full font-medium hover:bg-primary-700 transition-all shadow-sm shadow-primary-100 active:scale-95"
>登录 / 注册</router-link
>
</template>
<!-- Mobile Menu Button -->

View File

@@ -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

View File

@@ -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}`)"
>
<img
:src="
@@ -402,7 +402,7 @@ onMounted(fetchData);
<div class="flex-1 min-w-0">
<div
class="font-bold text-slate-900 text-sm truncate hover:text-primary-600 cursor-pointer"
@click="$router.push(`/t/${creator.id}`)"
@click="$router.push(`/t/${creator.id}`)"
>
{{ creator.name }}
</div>

View File

@@ -1,6 +1,94 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { contentApi } from "../../api/content";
import { orderApi } from "../../api/order";
import { tenantPath } from "../../utils/tenant";
const route = useRoute();
const router = useRouter();
const tenantRoute = (path) => tenantPath(path, route);
const contentId = route.query.contentId || route.query.content_id;
const loading = ref(false);
const errorMsg = ref("");
const content = ref(null);
const price = ref("0.00");
const loadContent = async () => {
if (!contentId) {
errorMsg.value = "缺少 contentId";
return;
}
try {
loading.value = true;
const res = await contentApi.get(contentId);
content.value = res;
if (res?.prices?.length) {
price.value = (res.prices[0].price_amount / 100).toFixed(2);
} else if (res?.price_amount !== undefined) {
price.value = (res.price_amount / 100).toFixed(2);
} else if (res?.price !== undefined) {
price.value = Number(res.price).toFixed(2);
}
} catch (e) {
errorMsg.value = "加载内容失败";
console.error(e);
} finally {
loading.value = false;
}
};
const createOrder = async () => {
if (!contentId) return;
try {
loading.value = true;
const res = await orderApi.create({ content_id: Number(contentId) });
const orderID = res?.id || res?.order_id || res?.orderID;
if (!orderID) {
throw new Error("未返回订单ID");
}
router.push(tenantRoute(`/payment/${orderID}`));
} catch (e) {
errorMsg.value = "创建订单失败";
console.error(e);
} finally {
loading.value = false;
}
};
onMounted(loadContent);
</script>
<template>
<div class="mx-auto max-w-screen-xl my-8 p-8 bg-white rounded-xl shadow-sm">
<h1 class="text-2xl font-bold mb-4">Checkout</h1>
<p class="text-slate-400">(Checkout flow)</p>
<h1 class="text-2xl font-bold mb-4">结算</h1>
<p v-if="loading" class="text-slate-400">加载中...</p>
<p v-else-if="errorMsg" class="text-red-500 text-sm">{{ errorMsg }}</p>
<div v-else class="space-y-6">
<div class="border rounded-lg p-4 bg-slate-50">
<div class="text-lg font-semibold text-slate-900">
{{ content?.title }}
</div>
<div class="text-slate-500 text-sm">{{ content?.description }}</div>
<div class="text-2xl font-bold text-primary-600 mt-2">
¥ {{ price }}
</div>
</div>
<div class="flex gap-3">
<button
class="px-5 py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700"
@click="createOrder"
>
提交订单
</button>
<button
class="px-4 py-2 rounded-lg border border-slate-200 text-slate-600"
@click="router.back()"
>
取消
</button>
</div>
</div>
</div>
</template>

View File

@@ -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(() => {
<p class="text-slate-500 mb-2">订单提交成功请尽快支付</p>
<div class="text-4xl font-bold text-slate-900">¥ {{ amount }}</div>
<div class="text-sm text-slate-500 mt-2">
商品{{ productName }}
商品{{ productName || "加载中..." }}
</div>
<div class="text-xs text-slate-400 mt-1">
状态{{ orderStatus || "pending" }}
</div>
</div>
@@ -193,12 +238,20 @@ onUnmounted(() => {
</div>
<!-- Dev Tool -->
<button
@click="simulateSuccess"
class="mt-8 text-xs text-slate-300 hover:text-slate-500 underline"
>
[开发调试] 模拟支付成功
</button>
<div class="flex flex-col items-center gap-3 mt-6">
<button
@click="payOrder"
class="px-4 py-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700"
>
立即支付
</button>
<button
@click="simulateSuccess"
class="text-xs text-slate-300 hover:text-slate-500 underline"
>
[开发调试] 模拟支付成功
</button>
</div>
</div>
</div>
</div>