Files
jiapuapp/pages/profile/m09-vip-orders.vue
T
2026-07-27 17:30:01 +08:00

247 lines
6.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="orders-page">
<ModulePageBackground module="profile" />
<view class="page-layer"
><PageHeader title="VIP 与订单" custom-back @back="requestBack"
/></view>
<view class="page-content page-layer">
<view class="service-card"
><text>服务套餐</text
><text>套餐和订单均从服务端读取本页不发起支付或创建订单</text></view
>
<view v-if="state === 'loading'" class="state-card"
><AppLoading text="正在读取服务套餐"
/></view>
<view v-else-if="state === 'error'" class="state-card"
><text>暂时无法读取套餐或订单</text
><AppButton block type="secondary" label="重新加载" @click="loadVip"
/></view>
<template v-else>
<view v-if="packages.length" class="package-list"
><view v-for="item in packages" :key="item.id" class="package-card"
><view class="package-heading"
><text>{{ item.name }}</text
><text v-if="item.price">¥{{ item.price }}</text></view
><text v-if="item.description">{{ item.description }}</text
><text v-if="item.duration">{{ item.duration }}</text></view
></view
>
<view v-else class="state-card"
><text>当前没有可展示的服务套餐</text></view
>
<view class="section-heading"
><text>订单记录</text><text>{{ orders.length }} </text></view
>
<view v-if="orders.length" class="order-list"
><view v-for="item in orders" :key="item.id" class="order-card"
><text>{{ item.title }}</text
><text>{{ item.status }}</text></view
></view
>
<view v-else class="order-empty"><text>暂无订单记录</text></view>
</template>
</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { onBackPress, onLoad, onShow, onUnload } from "@dcloudio/uni-app";
import AppButton from "@/components/AppButton.vue";
import AppLoading from "@/components/AppLoading.vue";
import ModulePageBackground from "@/components/ModulePageBackground.vue";
import PageHeader from "@/components/PageHeader.vue";
import {
appApi,
createRequestController,
isRequestCancelled,
} from "@/utils/api.js";
import { handleBackPress, runBackGuard } from "@/utils/navigation.js";
const packages = ref([]);
const orders = ref([]);
const state = ref("loading");
const controller = createRequestController();
let active = true;
const formatPrice = (value) =>
Number.isFinite(Number(value)) ? Number(value).toFixed(2) : "";
const durationUnits = Object.freeze({ year: "年", month: "个月", day: "天" });
const formatDuration = (value, unit) => {
if (value === undefined || value === null || value === "") return "";
if (Number.isFinite(Number(value)) && Number(value) <= 0) return "永久有效";
const normalizedUnit = String(unit || "")
.trim()
.toLowerCase();
return durationUnits[normalizedUnit]
? `${value} ${durationUnits[normalizedUnit]}`
: String(value);
};
const loadVip = async () => {
controller.abort();
state.value = "loading";
try {
const packageRows = await appApi.getVipPackages({
requestController: controller,
});
const orderRows = await appApi.getVipOrders({
requestController: controller,
});
if (!active) return;
packages.value = packageRows.map((item) => ({
id: String(item.packageId),
name: String(item.packageName || "未命名套餐"),
description: String(item.packageDesc || ""),
price: formatPrice(item.price),
duration: formatDuration(item.durationValue, item.durationUnit),
}));
orders.value = orderRows
.map((item) => ({
id: String(item.orderId || item.id || ""),
title: String(item.packageName || item.orderNo || "VIP 订单"),
status: String(item.orderStatus || item.status || ""),
}))
.filter((item) => item.id);
state.value = "ready";
} catch (error) {
if (!active || isRequestCancelled(error)) return;
state.value = "error";
}
};
const requestBack = () => runBackGuard({});
onLoad(loadVip);
onShow(() => {
if (state.value !== "loading") loadVip();
});
onUnload(() => {
active = false;
controller.abort();
});
onBackPress((event) => handleBackPress(event, requestBack));
</script>
<style scoped lang="scss">
@use "../../styles/adaptive-frame-profiles.scss" as adaptive;
.orders-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $paper;
}
.page-layer {
z-index: 1;
}
.page-content {
flex: 1;
padding: 28rpx 30rpx 72rpx;
}
.service-card,
.state-card,
.package-card,
.order-card,
.order-empty {
@include adaptive.adaptive-profile-content;
}
.service-card {
min-height: 170rpx;
padding: 38rpx 44rpx;
text-align: center;
}
.service-card text {
display: block;
}
.service-card text:first-child {
color: $ink;
font-family: STKaiti, KaiTi, serif;
font-size: 34rpx;
font-weight: 700;
}
.service-card text:last-child {
margin-top: 12rpx;
color: $ink-muted;
font-size: 21rpx;
line-height: 1.55;
}
.state-card {
display: flex;
min-height: 190rpx;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 18rpx;
padding: 36rpx;
text-align: center;
color: $ink;
font-size: 26rpx;
}
.state-card .app-button {
width: 100%;
margin-top: 22rpx;
}
.package-list,
.order-list {
display: flex;
flex-direction: column;
gap: 14rpx;
margin-top: 18rpx;
}
.package-card {
padding: 28rpx 32rpx;
}
.package-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
color: $ink;
font-size: 27rpx;
font-weight: 700;
}
.package-heading text:last-child {
flex: 0 0 auto;
color: $brand-red;
}
.package-card > text {
display: block;
margin-top: 10rpx;
color: $ink-muted;
font-size: 21rpx;
line-height: 1.5;
}
.section-heading {
display: flex;
justify-content: space-between;
margin: 28rpx 6rpx 12rpx;
color: $ink;
font-size: 27rpx;
font-weight: 700;
}
.section-heading text:last-child {
color: $ink-muted;
font-size: 21rpx;
font-weight: 400;
}
.order-card {
display: flex;
justify-content: space-between;
gap: 18rpx;
padding: 26rpx 32rpx;
color: $ink;
font-size: 24rpx;
}
.order-card text:last-child {
color: $ink-muted;
}
.order-empty {
padding: 38rpx;
text-align: center;
color: $ink-muted;
font-size: 23rpx;
}
@media (max-width: 340px) {
.page-content {
padding-right: 22rpx;
padding-left: 22rpx;
}
}
</style>