1077 lines
33 KiB
Vue
1077 lines
33 KiB
Vue
<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 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">{{ 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>
|
||
</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-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>
|