This commit is contained in:
wushumin
2026-05-11 15:28:27 +08:00
commit 9aac78b8da
289 changed files with 67193 additions and 0 deletions

View File

@@ -0,0 +1,500 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { appApi, type OrderListItem } from "../../api/app";
import { isLoggedIn, redirectToLogin } from "../../utils/auth";
import { showErrorToast } from "../../utils/feedback";
import { getPrivacyMode, maskOrderNo } from "../../utils/privacy";
const orders = ref<OrderListItem[]>([]);
const privacyMode = ref(getPrivacyMode());
const orderHeroBackground = ref("");
const defaultOrderHeroBackground = "/static/order/order-reference.jpg";
const orderHeroStyle = computed(() => ({
backgroundImage: `url("${orderHeroBackground.value || defaultOrderHeroBackground}")`,
}));
const heroStats = computed(() => {
const stats = {
pending: 0,
processing: 0,
completed: 0,
};
for (const item of orders.value) {
if (["report_published", "completed"].includes(item.order_status)) {
stats.completed += 1;
continue;
}
if (item.order_status === "pending_supplement") {
stats.pending += 1;
continue;
}
stats.processing += 1;
}
return stats;
});
const emptyState = computed(() => ({
title: "还没有鉴定订单",
desc: "发起第一笔鉴定后,订单进度、补资料提醒和报告状态都会集中展示在这里。",
}));
function openOrder(id: number) {
uni.navigateTo({ url: `/pages/order/detail?id=${id}` });
}
function goStartAppraisal() {
uni.navigateTo({ url: "/pages/appraisal/service" });
}
function goHome() {
uni.switchTab({ url: "/pages/home/index" });
}
function goHelp() {
uni.navigateTo({ url: "/pages/help/index" });
}
async function fetchPageVisuals() {
try {
const data = await appApi.getPageVisuals();
orderHeroBackground.value = data.order_background_image_url || "";
} catch (error) {
console.warn("order page visuals fallback", error);
}
}
onShow(async () => {
privacyMode.value = getPrivacyMode();
void fetchPageVisuals();
if (!isLoggedIn()) {
orders.value = [];
redirectToLogin("/pages/order/index");
return;
}
try {
const data = await appApi.getOrders();
orders.value = data.list;
} catch (error) {
orders.value = [];
showErrorToast(error, "订单加载失败");
}
});
</script>
<template>
<view class="order-page">
<view :class="['order-hero', orders.length === 0 ? 'order-hero--empty' : 'order-hero--list']">
<view class="order-hero__bg" :style="orderHeroStyle"></view>
<view class="order-nav">
<view class="order-nav__home" @click="goHome">
<view class="order-nav__home-roof"></view>
<view class="order-nav__home-body"></view>
</view>
<view class="order-nav__title">订单中心</view>
<view class="order-nav__capsule">
<text class="order-nav__dots"></text>
<view class="order-nav__divider"></view>
<view class="order-nav__circle"></view>
</view>
</view>
<view class="order-hero__content">
<view class="order-hero__title">订单中心</view>
<view class="order-hero__desc">查看每一笔鉴定从下单寄送补资料到出报告的完整进度</view>
<view class="order-stat-grid">
<view class="order-stat-card">
<view class="order-stat-card__value">{{ heroStats.pending }}</view>
<view class="order-stat-card__label">待处理</view>
</view>
<view class="order-stat-card">
<view class="order-stat-card__value">{{ heroStats.processing }}</view>
<view class="order-stat-card__label">进行中</view>
</view>
<view class="order-stat-card">
<view class="order-stat-card__value">{{ heroStats.completed }}</view>
<view class="order-stat-card__label">已完成</view>
</view>
</view>
</view>
</view>
<view v-if="orders.length === 0" class="order-empty-state">
<view class="order-empty-state__title">{{ emptyState.title }}</view>
<view class="order-empty-state__desc">{{ emptyState.desc }}</view>
<view class="order-empty-state__actions">
<view class="order-empty-state__button order-empty-state__button--primary" @click="goStartAppraisal">发起鉴定</view>
<view class="order-empty-state__button order-empty-state__button--secondary" @click="goHelp">查看帮助</view>
</view>
</view>
<view v-else class="order-list">
<view
v-for="item in orders"
:key="item.order_id"
class="order-card"
@click="openOrder(item.order_id)"
>
<view class="order-card__top">
<view>
<view class="order-card__title">{{ item.product_name }}</view>
<view class="order-card__no">订单号{{ maskOrderNo(item.order_no, privacyMode) }}</view>
</view>
<text
class="order-card__status"
:class="item.order_status === 'pending_supplement' ? 'order-card__status--warning' : ['report_published', 'completed'].includes(item.order_status) ? 'order-card__status--success' : 'order-card__status--info'"
>
{{ item.display_status }}
</text>
</view>
<view class="order-card__desc">{{ item.status_desc }}</view>
<view v-if="item.order_status === 'report_published'" class="order-card__desc">平台待安排寄回请先确认寄回地址</view>
<view v-if="item.order_status === 'completed' && item.display_status === '物品已寄回'" class="order-card__desc">平台已回寄商品请留意签收物流</view>
<view class="order-card__footer">
<view class="order-card__provider">{{ item.service_provider === "zhongjian" ? "中检鉴定" : "安心验鉴定" }}</view>
<view class="order-card__action">{{ item.primary_action }}</view>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.order-page {
width: 100vw;
min-height: 100vh;
overflow-x: hidden;
padding-bottom: calc(48rpx + env(safe-area-inset-bottom));
background: #f2f2f4;
color: #2d2d2f;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
}
.order-page,
.order-page view,
.order-page text {
box-sizing: border-box;
}
.order-hero {
position: relative;
overflow: hidden;
width: 100vw;
padding: 88rpx 32rpx 0;
}
.order-hero--empty {
height: 900rpx;
}
.order-hero--list {
height: 790rpx;
}
.order-hero__bg {
position: absolute;
inset: -18rpx;
background-size: 100vw auto;
background-position: top center;
background-repeat: no-repeat;
filter: blur(8rpx) saturate(1.08);
transform: scale(1.04);
opacity: 0.82;
pointer-events: none;
}
.order-hero::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(245, 239, 232, 0.08) 0%, rgba(243, 243, 245, 0.22) 42%, #f2f2f4 88%),
linear-gradient(0deg, rgba(242, 242, 244, 0.36), rgba(255, 255, 255, 0.04));
pointer-events: none;
}
.order-nav,
.order-hero__content {
position: relative;
z-index: 1;
}
.order-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 76rpx;
}
.order-nav__title {
position: absolute;
left: 50%;
top: 50%;
color: #272729;
font-size: 34rpx;
line-height: 1;
font-weight: 700;
transform: translate(-50%, -50%);
white-space: nowrap;
}
.order-nav__home {
position: relative;
width: 56rpx;
height: 56rpx;
}
.order-nav__home-roof {
position: absolute;
left: 10rpx;
top: 7rpx;
width: 36rpx;
height: 36rpx;
border-top: 4rpx solid #2a2a2c;
border-left: 4rpx solid #2a2a2c;
transform: rotate(45deg);
}
.order-nav__home-body {
position: absolute;
left: 12rpx;
bottom: 8rpx;
width: 34rpx;
height: 30rpx;
border: 4rpx solid #2a2a2c;
border-top: 0;
border-radius: 0 0 8rpx 8rpx;
background: linear-gradient(135deg, transparent 0 36%, #f0c000 36% 100%);
}
.order-nav__capsule {
display: flex;
align-items: center;
justify-content: space-around;
width: 174rpx;
height: 64rpx;
padding: 0 20rpx;
border-radius: 34rpx;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.05);
backdrop-filter: blur(12rpx);
}
.order-nav__dots {
color: #050505;
font-size: 36rpx;
line-height: 1;
font-weight: 800;
}
.order-nav__divider {
width: 1rpx;
height: 34rpx;
background: rgba(20, 20, 20, 0.68);
}
.order-nav__circle {
width: 38rpx;
height: 38rpx;
border: 7rpx solid #070707;
border-radius: 50%;
}
.order-hero__content {
margin-top: 214rpx;
}
.order-hero__title {
color: #262628;
font-size: 70rpx;
line-height: 1.08;
font-weight: 800;
letter-spacing: 0;
}
.order-hero__desc {
width: 650rpx;
max-width: 100%;
margin-top: 28rpx;
color: #666;
font-size: 29rpx;
line-height: 1.7;
}
.order-stat-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 42rpx;
margin: 26rpx 22rpx 0;
}
.order-stat-card {
height: 176rpx;
border-radius: 10rpx;
background: rgba(255, 255, 255, 0.9);
text-align: center;
box-shadow: 0 10rpx 24rpx rgba(0, 0, 0, 0.02);
}
.order-stat-card__value {
margin-top: 35rpx;
color: #2b2b2d;
font-size: 68rpx;
line-height: 1;
font-weight: 500;
}
.order-stat-card__label {
margin-top: 18rpx;
color: #686868;
font-size: 25rpx;
line-height: 1;
}
.order-empty-state {
margin: 190rpx 32rpx 0;
text-align: center;
}
.order-empty-state__title {
color: #202022;
font-size: 40rpx;
line-height: 1.2;
font-weight: 800;
}
.order-empty-state__desc {
width: 560rpx;
max-width: 100%;
margin: 28rpx auto 0;
color: #818181;
font-size: 29rpx;
line-height: 1.55;
}
.order-empty-state__actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 38rpx;
margin-top: 50rpx;
}
.order-empty-state__button {
height: 68rpx;
border-radius: 36rpx;
font-size: 25rpx;
font-weight: 700;
line-height: 68rpx;
text-align: center;
}
.order-empty-state__button--primary {
background: #edbd00;
color: #fff;
}
.order-empty-state__button--secondary {
background: #fff;
color: #29292b;
}
.order-list {
display: grid;
gap: 22rpx;
margin: -22rpx 32rpx 0;
padding-bottom: 36rpx;
}
.order-card {
padding: 28rpx;
border-radius: 16rpx;
background: #fff;
box-shadow: 0 12rpx 26rpx rgba(0, 0, 0, 0.035);
}
.order-card__top {
display: flex;
justify-content: space-between;
gap: 20rpx;
}
.order-card__title {
color: #242426;
font-size: 31rpx;
line-height: 1.25;
font-weight: 800;
}
.order-card__no,
.order-card__desc {
margin-top: 12rpx;
color: #777;
font-size: 24rpx;
line-height: 1.55;
}
.order-card__status {
flex-shrink: 0;
height: 42rpx;
padding: 0 16rpx;
border-radius: 22rpx;
font-size: 22rpx;
line-height: 42rpx;
}
.order-card__status--warning {
background: #fff4d9;
color: #a97700;
}
.order-card__status--success {
background: #eaf7ef;
color: #2d7652;
}
.order-card__status--info {
background: #edf4ff;
color: #28669f;
}
.order-card__footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 22rpx;
}
.order-card__provider {
color: #9a9a9a;
font-size: 23rpx;
}
.order-card__action {
min-width: 126rpx;
height: 48rpx;
padding: 0 20rpx;
border-radius: 25rpx;
background: #f1bd00;
color: #fff;
font-size: 23rpx;
font-weight: 700;
line-height: 48rpx;
text-align: center;
}
@media (max-width: 360px) {
.order-stat-grid {
gap: 22rpx;
margin-left: 12rpx;
margin-right: 12rpx;
}
.order-empty-state__actions {
gap: 22rpx;
}
}
</style>