docs: add pagewise ui test checklist
This commit is contained in:
@@ -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 -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user