Files
anxinyan/user-app/src/pages/order/detail.vue
2026-05-26 17:08:33 +08:00

1106 lines
34 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.
<script setup lang="ts">
import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { appApi, type OrderDetailData, type UserAddressItem } from "../../api/app";
import { orderDetailFallback } from "../../mocks/app";
import { resolveErrorMessage, showErrorToast, showInfoToast } from "../../utils/feedback";
import { getPrivacyMode, maskOrderNo } from "../../utils/privacy";
const detail = ref<OrderDetailData>(orderDetailFallback);
const orderId = ref(0);
const privacyMode = ref(getPrivacyMode());
const addressSheetVisible = ref(false);
const addressesLoading = ref(false);
const returnAddressSaving = ref(false);
const addressOptions = ref<UserAddressItem[]>([]);
const loading = ref(false);
const pageReady = ref(false);
const loadError = ref("");
const serviceProviderText = computed(() =>
detail.value.order_info.service_provider === "zhongjian" ? "中检鉴定" : "实物鉴定",
);
const shippingSubmitted = computed(
() => detail.value.order_info.order_status === "pending_shipping" && detail.value.order_info.display_status === "已提交运单",
);
const latestTimelineItem = computed(() => {
const { timeline } = detail.value;
return timeline.length ? timeline[timeline.length - 1] : null;
});
const timelineHistory = computed(() => [...detail.value.timeline].reverse());
const productHighlights = computed(() => {
const values = [
detail.value.product_info.category_name,
detail.value.product_info.brand_name,
detail.value.product_info.color,
detail.value.product_info.size_spec,
].filter(Boolean);
return [...new Set(values)];
});
const productRows = computed(() =>
[
{ label: "品类", value: detail.value.product_info.category_name },
{ label: "品牌", value: detail.value.product_info.brand_name },
{ label: "颜色", value: detail.value.product_info.color },
{ label: "规格", value: detail.value.product_info.size_spec },
{ label: "编码", value: detail.value.product_info.serial_no },
].filter((item) => Boolean(item.value)),
);
const extraInfoRows = computed(() =>
[
{ label: "购买渠道", value: detail.value.extra_info.purchase_channel },
{ label: "购买价格", value: detail.value.extra_info.purchase_price > 0 ? `¥${detail.value.extra_info.purchase_price}` : "" },
{ label: "购买日期", value: detail.value.extra_info.purchase_date },
{ label: "使用情况", value: detail.value.extra_info.usage_status_text || detail.value.extra_info.usage_status },
{ label: "补充备注", value: detail.value.extra_info.remark },
].filter((item) => Boolean(item.value)),
);
const hasExtraInfo = computed(
() => extraInfoRows.value.length > 0 || detail.value.extra_info.accessories.length > 0,
);
const materialItems = computed(() => detail.value.materials || []);
const hasReturnAddress = computed(() => Boolean(detail.value.return_address));
const hasReturnLogistics = computed(() => Boolean(detail.value.return_logistics?.tracking_no));
const returnReceived = computed(() => detail.value.return_logistics?.tracking_status === "received");
const returnLogisticsNodes = computed(() => detail.value.return_logistics?.nodes || []);
const returnLogisticsStatusText = computed(
() => detail.value.return_logistics?.provider_status_text || detail.value.return_logistics?.tracking_status_text || "",
);
const canEditReturnAddress = computed(() => detail.value.order_info.can_edit_return_address);
const heroTagClass = computed(() => {
if (detail.value.order_info.order_status === "pending_supplement") return "tag tag--warning";
if (detail.value.order_info.order_status === "pending_shipping" || detail.value.order_info.order_status === "pending_submission") {
return "tag tag--accent";
}
if (detail.value.order_info.order_status === "report_published" || detail.value.order_info.order_status === "completed") {
return "tag tag--success";
}
return "tag tag--info";
});
const heroTagText = computed(() => {
switch (detail.value.order_info.order_status) {
case "pending_shipping":
return shippingSubmitted.value ? "待签收" : "待寄送";
case "pending_submission":
return "待提交";
case "pending_supplement":
return "待补资料";
case "received":
return "已签收";
case "report_published":
case "completed":
return "已出报告";
default:
return "处理中";
}
});
const focusTitle = computed(() => {
switch (detail.value.order_info.order_status) {
case "pending_shipping":
return shippingSubmitted.value ? "下一步等待鉴定中心签收" : "下一步请先寄送商品";
case "pending_supplement":
return "下一步请优先补充资料";
case "report_published":
return hasReturnAddress.value ? "下一步等待平台寄回物品" : "下一步请先确认寄回地址";
case "completed":
return returnReceived.value ? "本次订单已完成" : hasReturnLogistics.value ? "物品已寄回,请留意签收" : "结果已经可以查看";
default:
return "当前订单正在继续处理";
}
});
const focusDesc = computed(() => {
if (detail.value.supplement_task?.reason) {
return detail.value.supplement_task.reason;
}
switch (detail.value.order_info.order_status) {
case "pending_shipping":
return shippingSubmitted.value
? "运单已登记成功,无需再次寄送商品,后续等待鉴定中心签收后继续处理。"
: "寄出商品后请及时补充运单号,我们会继续同步签收与鉴定进度。";
case "report_published":
return hasReturnAddress.value
? "正式报告已生成,平台会按你确认的寄回地址安排回寄,寄出后会同步物流信息。"
: "正式报告已生成,但还缺少寄回地址,请先确认收件地址以便平台安排回寄。";
case "completed":
return returnReceived.value
? "系统已确认您签收回寄商品,本次鉴定订单已经完成。"
: hasReturnLogistics.value
? "平台已登记回寄运单,可在本页查看回寄物流进度和最新节点。"
: "正式报告已生成,可前往报告页查看结果并完成验真。";
default:
return detail.value.order_info.status_desc;
}
});
const focusMeta = computed(() => {
if (detail.value.supplement_task?.deadline) {
return `补充截止 ${detail.value.supplement_task.deadline}`;
}
if (latestTimelineItem.value?.occurred_at) {
return `最新更新 ${latestTimelineItem.value.occurred_at}`;
}
if (detail.value.order_info.estimated_finish_time) {
return `预计完成 ${detail.value.order_info.estimated_finish_time}`;
}
return "";
});
const progressSectionDesc = computed(() =>
timelineHistory.value.length > 1 ? "最近更新优先展示,帮助你快速判断当前所处环节。" : "订单的关键处理节点会持续同步到本页。",
);
const supportHelpText = computed(() => {
switch (detail.value.order_info.order_status) {
case "pending_supplement":
return "如果暂时无法补图,可先联系客服说明情况,我们会协助判断下一步处理方式。";
case "pending_shipping":
return "如果寄送过程中需要改派、补充信息或说明异常,也可以联系客服协助处理。";
case "report_published":
case "completed":
return returnReceived.value
? "如果对报告结果或售后处理仍有疑问,可以联系客服继续协助说明。"
: "如果对报告结果、寄回安排或签收异常有疑问,可以联系客服继续协助说明。";
default:
return "如果对当前处理进度有疑问,或需要补充说明,都可以联系客服协助跟进。";
}
});
const primaryActionText = computed(() =>
{
if (detail.value.order_info.order_status === "report_published" && !hasReturnAddress.value) {
return "确认寄回地址";
}
if (detail.value.order_info.order_status === "completed" && hasReturnLogistics.value && !returnReceived.value) {
return "查看物流";
}
return detail.value.available_actions.primary_action === "查看进度" ? "查看处理记录" : detail.value.available_actions.primary_action;
},
);
async function fetchDetail() {
if (!orderId.value) return;
loading.value = true;
privacyMode.value = getPrivacyMode();
if (!pageReady.value) {
loadError.value = "";
}
try {
detail.value = await appApi.getOrderDetail(orderId.value);
pageReady.value = true;
} catch (error) {
console.warn("order detail fallback", error);
if (!pageReady.value) {
loadError.value = resolveErrorMessage(error, "订单详情加载失败,请稍后重试。");
} else {
showErrorToast(error, "订单详情刷新失败");
}
} finally {
loading.value = false;
}
}
async function fetchAddressOptions() {
addressesLoading.value = true;
try {
const data = await appApi.getAddresses();
addressOptions.value = data.list;
} catch (error) {
showErrorToast(error, "地址列表加载失败");
} finally {
addressesLoading.value = false;
}
}
function handlePrimaryAction() {
if (detail.value.order_info.order_status === "report_published" && !hasReturnAddress.value) {
openReturnAddressSheet();
return;
}
if (detail.value.order_info.order_status === "completed" && hasReturnLogistics.value && !returnReceived.value) {
uni.pageScrollTo({
selector: "#return-logistics-card",
duration: 260,
});
return;
}
const action = detail.value.available_actions.primary_action;
if (action === "去补资料") {
uni.navigateTo({ url: `/pages/order/supplement?order_id=${detail.value.order_info.order_id}` });
return;
}
if (action === "查看寄送") {
uni.navigateTo({ url: `/pages/order/shipping?order_id=${detail.value.order_info.order_id}` });
return;
}
if (action === "查看报告") {
uni.switchTab({ url: "/pages/report/index" });
return;
}
if (action === "查看进度") {
uni.pageScrollTo({
selector: "#order-progress",
duration: 260,
});
return;
}
uni.showToast({
title: action,
icon: "none",
});
}
function contactService() {
uni.navigateTo({
url: `/pages/support/create?ticket_type=order_issue&order_id=${detail.value.order_info.order_id}&prefill_title=${encodeURIComponent("订单问题咨询")}`,
});
}
function previewMaterialFiles(files: Array<{ file_url: string }>, current: string) {
if (!files.length) return;
uni.previewImage({
urls: files.map((item) => item.file_url),
current,
});
}
async function openReturnAddressSheet() {
await fetchAddressOptions();
if (!addressOptions.value.length) {
uni.showModal({
title: "还没有地址",
content: "请先新增一个常用地址,作为鉴定完成后的寄回地址。",
success: (result) => {
if (!result.confirm) return;
uni.navigateTo({ url: "/pages/address/index" });
},
});
return;
}
addressSheetVisible.value = true;
}
function closeReturnAddressSheet() {
addressSheetVisible.value = false;
}
async function chooseReturnAddress(addressId: number) {
if (returnAddressSaving.value) return;
returnAddressSaving.value = true;
try {
await appApi.saveOrderReturnAddress({
order_id: detail.value.order_info.order_id,
address_id: addressId,
});
showInfoToast("寄回地址已更新");
closeReturnAddressSheet();
await fetchDetail();
} catch (error) {
showErrorToast(error, "寄回地址保存失败");
} finally {
returnAddressSaving.value = false;
}
}
onLoad((options) => {
orderId.value = Number(options?.id || 0);
if (!orderId.value) {
loadError.value = "缺少订单编号,无法查看详情。";
}
});
onShow(fetchDetail);
</script>
<template>
<view class="app-page app-page--tight">
<view v-if="!pageReady && loading" class="section notice-card">
<view class="notice-card__title">正在加载订单详情</view>
<view class="notice-card__desc">请稍候我们正在同步订单状态资料寄回地址和处理记录</view>
</view>
<view v-else-if="!pageReady && loadError" class="section notice-card">
<view class="notice-card__title">订单详情加载失败</view>
<view class="notice-card__desc">{{ loadError }}</view>
<view style="margin-top: 20rpx">
<text class="btn btn--ghost" @click="fetchDetail">重新加载</text>
</view>
</view>
<template v-else>
<view class="section-card status-hero order-detail-hero">
<text :class="heroTagClass">{{ heroTagText }}</text>
<view class="status-hero__title">{{ detail.order_info.display_status }}</view>
<view class="status-hero__desc">{{ detail.order_info.status_desc }}</view>
<view class="status-hero__meta">
<text class="meta-chip">订单号 {{ maskOrderNo(detail.order_info.order_no, privacyMode) }}</text>
<text class="meta-chip">鉴定单号 {{ maskOrderNo(detail.order_info.appraisal_no, privacyMode) }}</text>
<text class="meta-chip">{{ serviceProviderText }}</text>
<text v-if="detail.order_info.estimated_finish_time" class="meta-chip">预计 {{ detail.order_info.estimated_finish_time }}</text>
</view>
<view class="order-focus-card">
<view class="order-focus-card__eyebrow">{{ focusTitle }}</view>
<view class="order-focus-card__desc">{{ focusDesc }}</view>
<view v-if="focusMeta" class="order-focus-card__meta">{{ focusMeta }}</view>
</view>
</view>
<view class="section section-card product-summary-card">
<view class="section__heading">
<view>
<view class="section__title">商品信息</view>
<view class="section__desc">本次送检商品的识别信息与规格摘要</view>
</view>
</view>
<view class="product-summary-card__name">{{ detail.product_info.product_name || "待补充商品名称" }}</view>
<view v-if="productHighlights.length" class="product-summary-card__chips">
<text v-for="item in productHighlights" :key="item" class="product-summary-card__chip">{{ item }}</text>
</view>
<view class="product-summary-card__grid">
<view v-for="item in productRows" :key="item.label" class="product-summary-card__field">
<view class="product-summary-card__label">{{ item.label }}</view>
<view class="product-summary-card__value">{{ item.value }}</view>
</view>
</view>
</view>
<view v-if="hasExtraInfo" class="section section-card">
<view class="section__heading">
<view>
<view class="section__title">下单补充信息</view>
<view class="section__desc">这里展示你在下单时填写的购买信息使用情况与补充说明</view>
</view>
</view>
<view
v-for="item in extraInfoRows"
:key="item.label"
:class="['report-meta__row', item.label === '品相说明' || item.label === '补充备注' ? 'report-meta__row--stacked' : '']"
>
<text class="report-meta__label">{{ item.label }}</text>
<text class="report-meta__value">{{ item.value }}</text>
</view>
<view v-if="detail.extra_info.accessories.length" class="report-meta__row report-meta__row--stacked">
<text class="report-meta__label">附件情况</text>
<view class="detail-chip-list">
<text v-for="item in detail.extra_info.accessories" :key="item" class="detail-chip">{{ item }}</text>
</view>
</view>
</view>
<view v-if="materialItems.length" class="section section-card material-panel">
<view class="section__heading">
<view>
<view class="section__title">提交资料</view>
<view class="section__desc">下单时提交的图片资料以及后续补充资料都会保留在这里供你回看</view>
</view>
</view>
<view class="material-panel__list">
<view v-for="item in materialItems" :key="item.upload_item_id" class="material-card">
<view class="material-card__top">
<view>
<view class="material-card__title">{{ item.item_name }}</view>
<view class="material-card__meta">
<text class="detail-chip">{{ item.source_type_text }}</text>
<text class="detail-chip">{{ item.status_text }}</text>
<text class="detail-chip">{{ item.file_count }} </text>
</view>
</view>
</view>
<view v-if="item.files.length" class="task-files">
<view v-for="file in item.files" :key="file.file_id" class="task-file">
<image
class="task-file__img"
:src="file.thumbnail_url || file.file_url"
mode="aspectFill"
@click="previewMaterialFiles(item.files, file.file_url)"
/>
</view>
</view>
<view v-else class="material-card__empty">当前资料项暂无图片</view>
</view>
</view>
</view>
<view class="section section-card return-panel">
<view class="section__heading">
<view>
<view class="section__title">寄回给您</view>
<view class="section__desc">报告出具并完成处理后平台会按这里的地址将鉴定物品寄回给您</view>
</view>
</view>
<view v-if="hasReturnAddress && detail.return_address" class="return-address-card">
<view class="return-address-card__top">
<view>
<view class="return-address-card__name">{{ detail.return_address.consignee }}</view>
<view class="return-address-card__mobile">{{ detail.return_address.mobile }}</view>
</view>
<text class="detail-chip">已确认地址</text>
</view>
<view class="return-address-card__address">{{ detail.return_address.full_address }}</view>
<view v-if="canEditReturnAddress" class="return-address-card__action" @click="openReturnAddressSheet">更换寄回地址</view>
</view>
<view v-else class="return-address-card return-address-card--empty">
<view class="return-address-card__name">暂未确认寄回地址</view>
<view class="return-address-card__address">请先选择一个地址方便平台在鉴定完成后尽快安排回寄</view>
<view class="return-address-card__action" @click="openReturnAddressSheet">选择寄回地址</view>
</view>
<view v-if="detail.return_logistics" id="return-logistics-card" class="return-logistics-card">
<view class="return-logistics-card__top">
<view class="return-logistics-card__title">回寄物流</view>
<text class="detail-chip">{{ returnLogisticsStatusText || detail.return_logistics.tracking_status_text }}</text>
</view>
<view class="report-meta__row">
<text class="report-meta__label">快递公司</text>
<text class="report-meta__value">{{ detail.return_logistics.express_company || "-" }}</text>
</view>
<view class="report-meta__row">
<text class="report-meta__label">运单号</text>
<text class="report-meta__value">{{ detail.return_logistics.tracking_no || "-" }}</text>
</view>
<view class="report-meta__row report-meta__row--stacked">
<text class="report-meta__label">最新进展</text>
<text class="report-meta__value">{{ detail.return_logistics.latest_desc || "待平台登记回寄运单" }}</text>
</view>
<view v-if="returnLogisticsStatusText" class="report-meta__row">
<text class="report-meta__label">快递状态</text>
<text class="report-meta__value">{{ returnLogisticsStatusText }}</text>
</view>
<view v-if="returnLogisticsNodes.length" class="compact-timeline return-logistics-card__timeline">
<view
v-for="(item, index) in returnLogisticsNodes"
:key="`${item.node_time}-${item.node_desc}`"
:class="['compact-timeline__item', index === 0 ? 'compact-timeline__item--current' : '']"
>
<view class="compact-timeline__rail"></view>
<view class="compact-timeline__dot"></view>
<view class="compact-timeline__content">
<view class="compact-timeline__row">
<text class="compact-timeline__title">{{ item.node_desc }}</text>
<text class="compact-timeline__time">{{ item.node_time }}</text>
</view>
<view v-if="item.node_location" class="compact-timeline__desc">{{ item.node_location }}</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="detail.supplement_task" class="section section-card task-panel">
<view class="section__heading">
<view>
<view class="section__title">待补充资料</view>
<view class="section__desc">请尽快完成以下资料补充以免影响订单继续流转</view>
</view>
<text class="task-panel__deadline">{{ detail.supplement_task.deadline || "请尽快处理" }}</text>
</view>
<view class="task-panel__reason">{{ detail.supplement_task.reason }}</view>
<view class="task-panel__list">
<view v-for="(item, index) in detail.supplement_task.items" :key="item.item_code" class="task-panel__item">
<view class="task-panel__index">{{ index + 1 }}</view>
<view class="task-panel__body">
<view class="task-panel__item-title">{{ item.item_name }}</view>
<view class="task-panel__item-desc">{{ item.guide_text }}</view>
</view>
</view>
</view>
</view>
<view id="order-progress" class="section section-card progress-panel">
<view class="section__heading">
<view>
<view class="section__title">处理记录</view>
<view class="section__desc">{{ progressSectionDesc }}</view>
</view>
</view>
<view v-if="latestTimelineItem" class="progress-highlight">
<text class="progress-highlight__badge">最新进展</text>
<view class="progress-highlight__title">{{ latestTimelineItem.node_text }}</view>
<view class="progress-highlight__time">{{ latestTimelineItem.occurred_at }}</view>
<view v-if="latestTimelineItem.node_desc" class="progress-highlight__desc">{{ latestTimelineItem.node_desc }}</view>
</view>
<view class="compact-timeline">
<view
v-for="(item, index) in timelineHistory"
:key="`${item.node_code}-${item.occurred_at}`"
:class="['compact-timeline__item', index === 0 ? 'compact-timeline__item--current' : '']"
>
<view class="compact-timeline__rail"></view>
<view class="compact-timeline__dot"></view>
<view class="compact-timeline__content">
<view class="compact-timeline__row">
<text class="compact-timeline__title">{{ item.node_text }}</text>
<text class="compact-timeline__time">{{ item.occurred_at }}</text>
</view>
<view v-if="item.node_desc" class="compact-timeline__desc">{{ item.node_desc }}</view>
</view>
</view>
</view>
</view>
<view class="section section-note order-help-note">
<view class="support-banner__title">客服协助</view>
<view class="support-banner__desc">{{ supportHelpText }}</view>
</view>
<view class="fixed-action-bar">
<view class="btn btn--secondary" @click="contactService">联系客服</view>
<view class="btn btn--primary" @click="handlePrimaryAction">{{ primaryActionText }}</view>
</view>
<view v-if="addressSheetVisible" class="return-address-sheet">
<view class="return-address-sheet__mask" @click="closeReturnAddressSheet"></view>
<view class="return-address-sheet__panel">
<view class="return-address-sheet__header">
<view>
<view class="return-address-sheet__title">选择寄回地址</view>
<view class="return-address-sheet__desc">平台回寄商品时会以当前所选地址为准</view>
</view>
<view class="return-address-sheet__close" @click="closeReturnAddressSheet">关闭</view>
</view>
<scroll-view scroll-y class="return-address-sheet__list">
<view
v-for="item in addressOptions"
:key="item.id"
:class="['return-address-sheet__item', detail.return_address?.user_address_id === item.id ? 'return-address-sheet__item--selected' : '']"
@click="chooseReturnAddress(item.id)"
>
<view class="return-address-sheet__item-top">
<view>
<view class="return-address-sheet__item-name">{{ item.consignee }}</view>
<view class="return-address-sheet__item-mobile">{{ item.mobile }}</view>
</view>
<text class="return-address-sheet__item-action">
{{ detail.return_address?.user_address_id === item.id ? "已选地址" : "选择" }}
</text>
</view>
<view class="return-address-sheet__item-address">{{ item.full_address }}</view>
</view>
</scroll-view>
</view>
</view>
</template>
</view>
</template>
<style scoped>
.order-detail-hero {
padding-bottom: 34rpx;
}
.order-focus-card {
position: relative;
margin-top: 28rpx;
padding: 22rpx 24rpx;
border-radius: 26rpx;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(237, 189, 0, 0.18);
}
.order-focus-card__eyebrow {
color: var(--color-heading);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
}
.order-focus-card__desc {
margin-top: 10rpx;
color: var(--color-body);
font-size: var(--font-size-sm);
line-height: 1.8;
}
.order-focus-card__meta {
margin-top: 14rpx;
color: var(--color-accent);
font-size: var(--font-size-xs);
line-height: 1.7;
}
.product-summary-card {
overflow: hidden;
background: #ffffff;
}
.product-summary-card__name {
margin-top: 8rpx;
color: var(--color-heading);
font-size: 46rpx;
font-weight: var(--font-weight-bold);
line-height: 1.18;
}
.product-summary-card__chips {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-top: 22rpx;
}
.product-summary-card__chip {
min-height: 44rpx;
padding: 0 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(225, 216, 198, 0.96);
color: var(--color-body);
font-size: var(--font-size-xs);
line-height: 44rpx;
}
.product-summary-card__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18rpx;
margin-top: 24rpx;
}
.product-summary-card__field {
padding: 20rpx 22rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.72);
border: 1px solid var(--card-border);
}
.product-summary-card__label {
color: var(--color-muted);
font-size: var(--font-size-xs);
line-height: 1.6;
}
.product-summary-card__value {
margin-top: 10rpx;
color: var(--color-heading);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.7;
}
.task-panel {
background:
#ffffff;
}
.detail-chip-list {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-top: 14rpx;
}
.detail-chip {
min-height: 42rpx;
padding: 0 18rpx;
border-radius: 999rpx;
background: rgba(237, 189, 0, 0.1);
color: #c89b00;
font-size: var(--font-size-xs);
line-height: 42rpx;
}
.material-panel__list {
display: grid;
gap: 18rpx;
}
.material-card {
padding: 22rpx 22rpx 24rpx;
border-radius: 26rpx;
background: #ffffff;
border: 1px solid var(--card-border);
}
.material-card__title {
color: var(--color-heading);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.6;
}
.material-card__meta {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
margin-top: 12rpx;
}
.material-card__empty {
margin-top: 16rpx;
color: var(--color-muted);
font-size: var(--font-size-xs);
line-height: 1.7;
}
.return-panel {
background:
#ffffff;
}
.return-address-card,
.return-logistics-card {
margin-top: 8rpx;
padding: 22rpx 22rpx 24rpx;
border-radius: 26rpx;
background: rgba(255, 255, 255, 0.78);
border: 1px solid var(--card-border);
}
.return-address-card--empty {
background: #ffffff;
}
.return-address-card__top,
.return-logistics-card__top,
.return-address-sheet__item-top {
display: flex;
justify-content: space-between;
gap: 16rpx;
align-items: flex-start;
}
.return-address-card__name,
.return-logistics-card__title,
.return-address-sheet__item-name {
color: var(--color-heading);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.6;
}
.return-address-card__mobile,
.return-address-sheet__item-mobile {
margin-top: 8rpx;
color: var(--color-muted);
font-size: var(--font-size-xs);
line-height: 1.6;
}
.return-logistics-card__timeline {
margin-top: 24rpx;
}
.return-address-card__address,
.return-address-sheet__item-address {
margin-top: 14rpx;
color: var(--color-body);
font-size: var(--font-size-sm);
line-height: 1.8;
}
.return-address-card__action {
margin-top: 16rpx;
color: var(--color-accent);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
}
.return-address-sheet {
position: fixed;
inset: 0;
z-index: 180;
}
.return-address-sheet__mask {
position: absolute;
inset: 0;
background: rgba(20, 20, 18, 0.38);
}
.return-address-sheet__panel {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 28rpx 28rpx calc(28rpx + env(safe-area-inset-bottom));
border-radius: 36rpx 36rpx 0 0;
background: #ffffff;
box-shadow: 0 -12rpx 36rpx rgba(0, 0, 0, 0.08);
}
.return-address-sheet__header {
display: flex;
justify-content: space-between;
gap: 20rpx;
align-items: flex-start;
margin-bottom: 20rpx;
}
.return-address-sheet__title {
color: var(--color-heading);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
}
.return-address-sheet__desc {
margin-top: 8rpx;
color: var(--color-body);
font-size: var(--font-size-xs);
line-height: 1.7;
}
.return-address-sheet__close {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
min-width: 104rpx;
padding: 12rpx 20rpx;
border-radius: 999rpx;
background: rgba(237, 189, 0, 0.12);
color: var(--color-accent);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
line-height: 1;
white-space: nowrap;
}
.return-address-sheet__list {
max-height: 58vh;
}
.return-address-sheet__item {
padding: 22rpx;
border-radius: 24rpx;
border: 2rpx solid rgba(219, 206, 183, 0.9);
background: rgba(255, 255, 255, 0.92);
}
.return-address-sheet__item + .return-address-sheet__item {
margin-top: 16rpx;
}
.return-address-sheet__item--selected {
border-color: rgba(237, 189, 0, 0.38);
background: #ffffff;
}
.return-address-sheet__item-action {
flex-shrink: 0;
padding: 10rpx 16rpx;
border-radius: 999rpx;
background: rgba(237, 189, 0, 0.12);
color: var(--color-accent);
font-size: 22rpx;
font-weight: var(--font-weight-semibold);
}
.task-panel__deadline {
padding: 8rpx 18rpx;
border-radius: 999rpx;
background: rgba(237, 189, 0, 0.12);
color: var(--color-accent);
font-size: var(--font-size-xs);
}
.task-panel__reason {
margin-top: 4rpx;
padding: 18rpx 20rpx;
border-radius: 22rpx;
background: rgba(255, 255, 255, 0.72);
color: var(--color-body);
font-size: var(--font-size-sm);
line-height: 1.8;
}
.task-panel__list {
display: grid;
gap: 16rpx;
margin-top: 22rpx;
}
.task-panel__item {
display: flex;
gap: 18rpx;
align-items: flex-start;
padding: 20rpx 22rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.8);
border: 1px solid var(--card-border);
}
.task-panel__index {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
background: linear-gradient(145deg, #171717 0%, #342d20 100%);
color: #f4dfb0;
font-size: 24rpx;
font-weight: var(--font-weight-semibold);
line-height: 44rpx;
text-align: center;
flex-shrink: 0;
}
.task-panel__body {
flex: 1;
}
.task-panel__item-title {
color: var(--color-heading);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.7;
}
.task-panel__item-desc {
margin-top: 8rpx;
color: var(--color-body);
font-size: var(--font-size-xs);
line-height: 1.8;
}
.progress-panel {
background: #ffffff;
}
.progress-highlight {
margin-top: 8rpx;
padding: 24rpx 24rpx 22rpx;
border-radius: 28rpx;
background: linear-gradient(145deg, #181818 0%, #332c20 100%);
box-shadow: 0 16rpx 32rpx rgba(26, 20, 12, 0.16);
}
.progress-highlight__badge {
display: inline-flex;
align-items: center;
min-height: 40rpx;
padding: 0 18rpx;
border-radius: 999rpx;
background: rgba(244, 215, 157, 0.12);
color: #f4d79d;
font-size: var(--font-size-xs);
}
.progress-highlight__title {
margin-top: 18rpx;
color: #fffaf0;
font-size: 42rpx;
font-weight: var(--font-weight-bold);
line-height: 1.16;
}
.progress-highlight__time {
margin-top: 14rpx;
color: rgba(255, 250, 240, 0.68);
font-size: var(--font-size-xs);
line-height: 1.7;
}
.progress-highlight__desc {
margin-top: 12rpx;
color: rgba(255, 250, 240, 0.88);
font-size: var(--font-size-sm);
line-height: 1.8;
}
.compact-timeline {
margin-top: 24rpx;
}
.compact-timeline__item {
position: relative;
padding-left: 34rpx;
padding-bottom: 28rpx;
}
.compact-timeline__item:last-child {
padding-bottom: 0;
}
.compact-timeline__rail {
position: absolute;
left: 10rpx;
top: 18rpx;
bottom: -8rpx;
width: 2rpx;
background: #eeeeef;
}
.compact-timeline__item:last-child .compact-timeline__rail {
display: none;
}
.compact-timeline__dot {
position: absolute;
left: 0;
top: 10rpx;
width: 22rpx;
height: 22rpx;
border-radius: 50%;
border: 4rpx solid var(--color-page-bg);
background: #d4c7b0;
}
.compact-timeline__item--current .compact-timeline__dot {
background: var(--color-accent);
}
.compact-timeline__content {
padding: 18rpx 20rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.74);
border: 1px solid var(--card-border);
}
.compact-timeline__item--current .compact-timeline__content {
background: #ffffff;
}
.compact-timeline__row {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.compact-timeline__title {
color: var(--color-heading);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
line-height: 1.6;
}
.compact-timeline__time {
color: var(--color-muted);
font-size: var(--font-size-xs);
line-height: 1.6;
}
.compact-timeline__desc {
margin-top: 10rpx;
color: var(--color-body);
font-size: var(--font-size-xs);
line-height: 1.8;
}
.order-help-note {
background: rgba(53, 92, 125, 0.05);
}
@media (max-width: 420px) {
.product-summary-card__grid {
grid-template-columns: 1fr;
}
}
</style>