feat: update appraisal return address and test packaging assets
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
VITE_API_BASE_URL=https://test.api.anxinjianyan.com
|
||||
VITE_APP_ENV=test
|
||||
VITE_API_BASE_URL=http://127.0.0.1:8788
|
||||
VITE_APP_ENV=development
|
||||
VITE_APP_TITLE=安心验
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface CategoryOption {
|
||||
category_id: number;
|
||||
category_name: string;
|
||||
category_code: string;
|
||||
image_url?: string;
|
||||
}
|
||||
|
||||
export interface UploadItem {
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
{
|
||||
"path": "pages/order/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情"
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { appraisalApi } from "../../api/appraisal";
|
||||
import { appraisalApi, type AppraisalServicePackage, type PreviewData } from "../../api/appraisal";
|
||||
import { appApi, type UserAddressItem } from "../../api/app";
|
||||
import { useAppraisalStore } from "../../stores/appraisal";
|
||||
import { isMissingDraftError, rebuildDraftFromStore } from "../../utils/appraisal-flow";
|
||||
@@ -15,18 +15,24 @@ const submitting = ref(false);
|
||||
const addressSheetVisible = ref(false);
|
||||
const addressesLoading = ref(false);
|
||||
const addressOptions = ref<UserAddressItem[]>([]);
|
||||
const packageOptions = ref<AppraisalServicePackage[]>([]);
|
||||
const packageOptionsLoading = ref(false);
|
||||
const packageOptionsError = ref("");
|
||||
const packageUpdating = ref(false);
|
||||
const selectedReturnAddress = computed(() => store.returnAddress);
|
||||
const recentCreatedAddressStorageKey = "anxinyan_recent_created_address_id";
|
||||
|
||||
const serviceProviderText = computed(() => preview.value?.service_summary.service_provider_text || "安心验鉴定");
|
||||
const currentServiceProvider = computed(() => preview.value?.service_summary.service_provider || store.serviceProvider || "anxinyan");
|
||||
const packageNameText = computed(() => preview.value?.service_summary.price_package_name || store.pricePackageName || "");
|
||||
const selectedPackageId = computed(() => Number(preview.value?.service_summary.price_package_id || store.pricePackageId || 0));
|
||||
const categoryText = computed(() => preview.value?.product_summary.category_name || store.product.categoryName || "-");
|
||||
const brandText = computed(() => preview.value?.product_summary.brand_name || store.product.brandName || "未填写");
|
||||
const productNameText = computed(() => preview.value?.product_summary.product_name || `${categoryText.value} ${brandText.value === "未填写" ? "" : brandText.value}`.trim());
|
||||
const serviceFeeText = computed(() => formatMoney(preview.value?.fee_detail.service_fee || 0));
|
||||
const discountFeeText = computed(() => formatMoney(preview.value?.fee_detail.discount_fee || 0));
|
||||
const payAmountText = computed(() => formatMoney(preview.value?.fee_detail.pay_amount || 0));
|
||||
const canSubmit = computed(() => Boolean(store.draftId && store.returnAddress.id && !loading.value && !submitting.value));
|
||||
const canSubmit = computed(() => Boolean(store.draftId && store.returnAddress.id && !loading.value && !submitting.value && !packageUpdating.value));
|
||||
|
||||
function formatMoney(value: number | string) {
|
||||
const amount = Number(value || 0);
|
||||
@@ -34,6 +40,20 @@ function formatMoney(value: number | string) {
|
||||
return amount % 1 === 0 ? String(amount) : amount.toFixed(2);
|
||||
}
|
||||
|
||||
function normalizeProvider(value = "") {
|
||||
return value === "zhongjian" ? "zhongjian" : "anxinyan";
|
||||
}
|
||||
|
||||
function syncPackageFromPreview(data: PreviewData) {
|
||||
store.setServiceProvider(data.service_summary.service_provider || store.serviceProvider);
|
||||
store.setPricePackage({
|
||||
id: Number(data.service_summary.price_package_id || 0),
|
||||
packageName: data.service_summary.price_package_name || "",
|
||||
packageCode: data.service_summary.price_package_code || "",
|
||||
price: Number(data.fee_detail.service_fee || 0),
|
||||
});
|
||||
}
|
||||
|
||||
function applySelectedAddress(item: UserAddressItem) {
|
||||
store.setReturnAddress({
|
||||
id: item.id,
|
||||
@@ -125,11 +145,70 @@ async function loadPreview() {
|
||||
showInfoToast("草稿已自动恢复,请确认订单信息后继续提交。");
|
||||
}
|
||||
store.setPreview(data);
|
||||
syncPackageFromPreview(data);
|
||||
} catch (error) {
|
||||
showErrorToast(error, "订单预览加载失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPackageOptions() {
|
||||
if (packageOptionsLoading.value) return;
|
||||
packageOptionsLoading.value = true;
|
||||
packageOptionsError.value = "";
|
||||
try {
|
||||
const data = await appraisalApi.getServiceConfigs();
|
||||
const provider = normalizeProvider(currentServiceProvider.value);
|
||||
const config = data.list.find((item) => normalizeProvider(item.service_provider) === provider);
|
||||
packageOptions.value = config?.packages || [];
|
||||
} catch (error) {
|
||||
packageOptions.value = [];
|
||||
packageOptionsError.value = "套餐列表加载失败,当前订单仍可按已选套餐继续支付。";
|
||||
showErrorToast(error, "套餐列表加载失败");
|
||||
} finally {
|
||||
packageOptionsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function selectPricePackage(item: AppraisalServicePackage) {
|
||||
if (packageUpdating.value || submitting.value || loading.value) return;
|
||||
if (!item.is_enabled) {
|
||||
showInfoToast("所选价格套餐已停用,请选择其他套餐");
|
||||
return;
|
||||
}
|
||||
if (item.id === selectedPackageId.value) return;
|
||||
if (!store.draftId) {
|
||||
showInfoToast("订单信息未准备完成,请返回上一步检查");
|
||||
return;
|
||||
}
|
||||
|
||||
packageUpdating.value = true;
|
||||
try {
|
||||
await withLoading("正在更新套餐", async () => {
|
||||
const serviceProvider = normalizeProvider(currentServiceProvider.value);
|
||||
await appraisalApi.saveDraft({
|
||||
draft_id: store.draftId,
|
||||
current_step: store.currentStep || 4,
|
||||
service_provider: serviceProvider,
|
||||
price_package_id: item.id,
|
||||
price_package_code: item.package_code,
|
||||
});
|
||||
store.setServiceProvider(serviceProvider);
|
||||
store.setPricePackage({
|
||||
id: item.id,
|
||||
packageName: item.package_name,
|
||||
packageCode: item.package_code,
|
||||
price: item.price,
|
||||
});
|
||||
store.setPreview(null);
|
||||
await loadPreview();
|
||||
});
|
||||
} catch (error) {
|
||||
showErrorToast(error, "套餐更新失败");
|
||||
} finally {
|
||||
packageUpdating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function goSuccess() {
|
||||
if (submitting.value) return;
|
||||
if (!store.draftId) {
|
||||
@@ -187,6 +266,7 @@ onLoad(async () => {
|
||||
store.hydrate();
|
||||
await fetchAddresses();
|
||||
await loadPreview();
|
||||
await loadPackageOptions();
|
||||
});
|
||||
|
||||
onShow(fetchAddresses);
|
||||
@@ -195,16 +275,8 @@ onShow(fetchAddresses);
|
||||
<template>
|
||||
<view class="confirm-page">
|
||||
<view class="confirm-nav">
|
||||
<view class="confirm-nav__home" @click="goBack">
|
||||
<view class="confirm-nav__home-roof"></view>
|
||||
<view class="confirm-nav__home-body"></view>
|
||||
</view>
|
||||
<view class="confirm-nav__back" @click="goBack"></view>
|
||||
<view class="confirm-nav__title">确认订单</view>
|
||||
<view class="confirm-nav__capsule">
|
||||
<text class="confirm-nav__dots">•••</text>
|
||||
<view class="confirm-nav__divider"></view>
|
||||
<view class="confirm-nav__circle"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="confirm-state">
|
||||
@@ -234,16 +306,51 @@ onShow(fetchAddresses);
|
||||
<view class="product-summary__thumb">
|
||||
<view class="product-summary__mark">{{ brandText === "未填写" ? "AXY" : brandText.slice(0, 2) }}</view>
|
||||
</view>
|
||||
<view class="product-summary__info">
|
||||
<view class="product-summary__row">服务:{{ serviceProviderText }}</view>
|
||||
<view v-if="packageNameText" class="product-summary__row">套餐:{{ packageNameText }}</view>
|
||||
<view class="product-summary__row">品类:{{ categoryText }}</view>
|
||||
<view class="product-summary__row">品牌:{{ brandText }}</view>
|
||||
<view class="product-summary__info">
|
||||
<view class="product-summary__row">服务:{{ serviceProviderText }}</view>
|
||||
<view class="product-summary__row">品类:{{ categoryText }}</view>
|
||||
<view class="product-summary__row">品牌:{{ brandText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="confirm-card__muted confirm-card__muted--top">{{ productNameText }}</view>
|
||||
</view>
|
||||
|
||||
<view class="confirm-card package-section">
|
||||
<view class="package-section__head">
|
||||
<view>
|
||||
<view class="confirm-card__title">价格套餐</view>
|
||||
<view class="package-section__desc">选择本次鉴定服务使用的价格套餐。</view>
|
||||
</view>
|
||||
<view v-if="packageUpdating" class="package-section__status">更新中...</view>
|
||||
</view>
|
||||
|
||||
<view v-if="packageOptionsLoading" class="package-empty">套餐加载中...</view>
|
||||
<view v-else-if="packageOptionsError" class="package-empty">{{ packageOptionsError }}</view>
|
||||
<view v-else-if="packageOptions.length" class="package-list">
|
||||
<view
|
||||
v-for="item in packageOptions"
|
||||
:key="item.id"
|
||||
:class="[
|
||||
'package-card',
|
||||
selectedPackageId === item.id ? 'package-card--selected' : '',
|
||||
packageUpdating ? 'package-card--disabled' : '',
|
||||
]"
|
||||
@click="selectPricePackage(item)"
|
||||
>
|
||||
<view class="package-card__main">
|
||||
<view class="package-card__name">{{ item.package_name }}</view>
|
||||
<view v-if="item.description" class="package-card__desc">{{ item.description }}</view>
|
||||
</view>
|
||||
<view class="package-card__side">
|
||||
<view class="package-card__price">¥{{ formatMoney(item.price) }}</view>
|
||||
<view v-if="selectedPackageId === item.id" class="package-card__tag">已选</view>
|
||||
<view v-else-if="item.is_default" class="package-card__tag package-card__tag--muted">默认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="package-empty">当前服务暂无可切换套餐</view>
|
||||
</view>
|
||||
|
||||
<view class="confirm-card">
|
||||
<view class="fee-head">
|
||||
<view class="confirm-card__title">费用明细</view>
|
||||
@@ -340,51 +447,34 @@ onShow(fetchAddresses);
|
||||
}
|
||||
|
||||
.confirm-nav {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
min-height: 72rpx;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.confirm-nav__home {
|
||||
position: relative;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
|
||||
.confirm-nav__home-roof {
|
||||
.confirm-nav__back {
|
||||
position: absolute;
|
||||
left: 10rpx;
|
||||
top: 4rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-left: 5rpx solid #252527;
|
||||
border-top: 5rpx solid #252527;
|
||||
transform: rotate(45deg);
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.confirm-nav__home-body {
|
||||
position: absolute;
|
||||
left: 12rpx;
|
||||
bottom: 6rpx;
|
||||
width: 30rpx;
|
||||
height: 28rpx;
|
||||
border: 5rpx solid #252527;
|
||||
border-top: 0;
|
||||
border-radius: 4rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.confirm-nav__home-body::after {
|
||||
.confirm-nav__back::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 2rpx;
|
||||
bottom: 2rpx;
|
||||
width: 18rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 12rpx 12rpx 12rpx 4rpx;
|
||||
background: #edbd00;
|
||||
left: 20rpx;
|
||||
top: 17rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
border-left: 5rpx solid #252527;
|
||||
border-bottom: 5rpx solid #252527;
|
||||
border-radius: 2rpx;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.confirm-nav__title {
|
||||
@@ -394,38 +484,6 @@ onShow(fetchAddresses);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.confirm-nav__capsule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 150rpx;
|
||||
height: 64rpx;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.confirm-nav__dots {
|
||||
color: #111111;
|
||||
font-size: 36rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.confirm-nav__divider {
|
||||
width: 1px;
|
||||
height: 36rpx;
|
||||
margin: 0 18rpx;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.confirm-nav__circle {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border: 7rpx solid #111111;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.confirm-state,
|
||||
.confirm-card,
|
||||
.agreement-item {
|
||||
@@ -595,6 +653,116 @@ onShow(fetchAddresses);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.package-section__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.package-section__desc {
|
||||
margin-top: 6rpx;
|
||||
color: #a0a0a4;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.package-section__status {
|
||||
flex-shrink: 0;
|
||||
color: #a0a0a4;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.package-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
min-height: 128rpx;
|
||||
padding: 22rpx 24rpx;
|
||||
border: 2rpx solid #e6e6e8;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.package-card--selected {
|
||||
border-color: #edbd00;
|
||||
background: #fffaf0;
|
||||
}
|
||||
|
||||
.package-card--disabled {
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.package-card__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.package-card__name {
|
||||
color: #252527;
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.package-card__desc {
|
||||
margin-top: 8rpx;
|
||||
color: #8c8c90;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.package-card__side {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.package-card__price {
|
||||
color: #edbd00;
|
||||
font-size: 34rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.package-card__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 64rpx;
|
||||
height: 34rpx;
|
||||
margin-top: 8rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(237, 189, 0, 0.13);
|
||||
color: #9a7700;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.package-card__tag--muted {
|
||||
background: #f0f0f2;
|
||||
color: #8c8c90;
|
||||
}
|
||||
|
||||
.package-empty {
|
||||
margin-top: 20rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f7f7f8;
|
||||
color: #9a9a9d;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fee-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -175,7 +175,7 @@ async function saveProductAndGoConfirm(payload: { brandId: number; brandName: st
|
||||
function selectBrand(item: BrandOption) {
|
||||
void saveProductAndGoConfirm({
|
||||
brandId: item.id,
|
||||
brandName: item.name || item.enName || item.displayName,
|
||||
brandName: item.displayName,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { appraisalApi, type AppraisalServiceConfig, type AppraisalServicePackage, type CategoryOption } from "../../api/appraisal";
|
||||
import { appraisalApi, type CategoryOption } from "../../api/appraisal";
|
||||
import { useAppraisalStore } from "../../stores/appraisal";
|
||||
import { isLoggedIn, redirectToLogin } from "../../utils/auth";
|
||||
import { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
|
||||
@@ -12,6 +12,7 @@ type CategoryPickerItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
imageUrl: string;
|
||||
visual: string;
|
||||
};
|
||||
|
||||
@@ -19,7 +20,6 @@ const providerIntro: Record<ServiceProvider, {
|
||||
navTitle: string;
|
||||
logoText: string;
|
||||
intro: string;
|
||||
priceText: string;
|
||||
highlights: string[];
|
||||
steps: Array<{
|
||||
title: string;
|
||||
@@ -31,7 +31,6 @@ const providerIntro: Record<ServiceProvider, {
|
||||
navTitle: "安心验鉴定",
|
||||
logoText: "安心验 鉴定",
|
||||
intro: "安心验(深圳)商品检验鉴定有限责任公司立足深圳核心产业服务区,是一家专业从事商品检验、鉴定、测试及技术咨询的第三方服务机构。公司依托粤港澳大湾区雄厚的产业基础与国际贸易枢纽优势,致力于为 C 端消费者及 B 端电商平台、商家提供网购商品真伪鉴定、成色评级、价值评估及争议仲裁等一站式解决方案。",
|
||||
priceText: "¥99 起",
|
||||
highlights: ["独立第三方", "报告可验真", "流程可追踪"],
|
||||
steps: [
|
||||
{ title: "下单付款", desc: "商品寄至鉴定中心,录像验收。", visual: "pay" },
|
||||
@@ -44,7 +43,6 @@ const providerIntro: Record<ServiceProvider, {
|
||||
navTitle: "中检鉴定",
|
||||
logoText: "中检 鉴定",
|
||||
intro: "中检鉴定服务面向更高规格出具需求,沿用安心验标准化下单、寄送与进度追踪流程,由合作机构完成对应服务交付,适用于对报告出具方有明确要求的鉴定场景。",
|
||||
priceText: "¥199 起",
|
||||
highlights: ["合作机构", "报告出具方不同", "流程一致"],
|
||||
steps: [
|
||||
{ title: "选择中检服务", desc: "首页选定中检鉴定后,确认品类、品牌和费用。", visual: "pay" },
|
||||
@@ -75,24 +73,8 @@ const categorySheetVisible = ref(false);
|
||||
const submitting = ref(false);
|
||||
const loadError = ref("");
|
||||
const selectedCategoryId = ref(0);
|
||||
const selectedPackageId = ref(0);
|
||||
const serviceConfigsLoading = ref(false);
|
||||
const serviceConfigs = ref<Record<ServiceProvider, AppraisalServiceConfig | undefined>>({
|
||||
anxinyan: undefined,
|
||||
zhongjian: undefined,
|
||||
});
|
||||
|
||||
const currentIntro = computed(() => providerIntro[providerCode.value]);
|
||||
const currentPackages = computed<AppraisalServicePackage[]>(() => serviceConfigs.value[providerCode.value]?.packages || []);
|
||||
const currentPackage = computed(() => currentPackages.value.find((item) => item.id === selectedPackageId.value) || currentPackages.value.find((item) => item.is_default) || currentPackages.value[0]);
|
||||
const currentPriceText = computed(() => {
|
||||
if (currentPackage.value) {
|
||||
return `¥${formatPrice(Number(currentPackage.value.price))}`;
|
||||
}
|
||||
const price = serviceConfigs.value[providerCode.value]?.price;
|
||||
return Number(price || 0) > 0 ? `¥${formatPrice(Number(price))} 起` : currentIntro.value.priceText;
|
||||
});
|
||||
const currentPriceDesc = computed(() => currentPackage.value?.package_name || "请选择价格套餐");
|
||||
const providerThemeClass = computed(() => `service-intro--${providerCode.value}`);
|
||||
|
||||
function normalizeProvider(value?: string): ServiceProvider {
|
||||
@@ -116,44 +98,6 @@ function resolveCategoryVisual(item: CategoryOption) {
|
||||
return matched?.visual || "default";
|
||||
}
|
||||
|
||||
function formatPrice(price: number) {
|
||||
return Number.isInteger(price) ? String(price) : price.toFixed(2);
|
||||
}
|
||||
|
||||
async function loadServiceConfigs() {
|
||||
if (serviceConfigsLoading.value) return;
|
||||
serviceConfigsLoading.value = true;
|
||||
try {
|
||||
const data = await appraisalApi.getServiceConfigs();
|
||||
const nextConfigs: Record<ServiceProvider, AppraisalServiceConfig | undefined> = {
|
||||
anxinyan: undefined,
|
||||
zhongjian: undefined,
|
||||
};
|
||||
data.list.forEach((item) => {
|
||||
const provider = normalizeProvider(item.service_provider);
|
||||
nextConfigs[provider] = item;
|
||||
});
|
||||
serviceConfigs.value = nextConfigs;
|
||||
applyDefaultPackage();
|
||||
} catch (error) {
|
||||
console.warn("service config fallback", error);
|
||||
} finally {
|
||||
serviceConfigsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function applyDefaultPackage() {
|
||||
const packages = currentPackages.value;
|
||||
if (packages.some((item) => item.id === selectedPackageId.value)) return;
|
||||
const target = packages.find((item) => item.is_default) || packages[0];
|
||||
selectedPackageId.value = target?.id || 0;
|
||||
}
|
||||
|
||||
function selectPackage(item: AppraisalServicePackage) {
|
||||
if (!item.is_enabled) return;
|
||||
selectedPackageId.value = item.id;
|
||||
}
|
||||
|
||||
function buildServicePageUrl(options: ServicePageOptions = {}) {
|
||||
const params: string[] = [];
|
||||
const provider = options.provider || providerCode.value;
|
||||
@@ -189,6 +133,7 @@ async function loadCategories() {
|
||||
id: item.category_id,
|
||||
name: item.category_name,
|
||||
code: item.category_code,
|
||||
imageUrl: item.image_url || "",
|
||||
visual: resolveCategoryVisual(item),
|
||||
}));
|
||||
categoriesLoaded.value = true;
|
||||
@@ -209,14 +154,6 @@ async function openCategorySheet() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPackage.value) {
|
||||
await loadServiceConfigs();
|
||||
}
|
||||
if (!currentPackage.value) {
|
||||
showInfoToast("当前服务暂无可用价格套餐");
|
||||
return;
|
||||
}
|
||||
|
||||
categorySheetVisible.value = true;
|
||||
await loadCategories();
|
||||
}
|
||||
@@ -228,11 +165,6 @@ function closeCategorySheet() {
|
||||
|
||||
async function selectCategory(item: CategoryPickerItem) {
|
||||
if (submitting.value) return;
|
||||
const selectedPackage = currentPackage.value;
|
||||
if (!selectedPackage) {
|
||||
showInfoToast("请先选择价格套餐");
|
||||
return;
|
||||
}
|
||||
selectedCategoryId.value = item.id;
|
||||
submitting.value = true;
|
||||
try {
|
||||
@@ -241,13 +173,7 @@ async function selectCategory(item: CategoryPickerItem) {
|
||||
store.resetForNewFlow();
|
||||
store.clearLegacyExtraDefaults();
|
||||
store.setServiceProvider(providerCode.value);
|
||||
store.setPricePackage({
|
||||
id: selectedPackage.id,
|
||||
packageName: selectedPackage.package_name,
|
||||
packageCode: selectedPackage.package_code,
|
||||
price: selectedPackage.price,
|
||||
});
|
||||
const draft = await appraisalApi.createDraft(providerCode.value, selectedPackage.id, selectedPackage.package_code);
|
||||
const draft = await appraisalApi.createDraft(providerCode.value);
|
||||
draftId = draft.draft_id;
|
||||
store.setDraft(draftId);
|
||||
store.setPricePackage({
|
||||
@@ -293,21 +219,18 @@ function categoryKey(item: CategoryPickerItem) {
|
||||
return `${item.id}-${item.name}`;
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
function goHome() {
|
||||
uni.switchTab({ url: "/pages/home/index" });
|
||||
}
|
||||
|
||||
function applyProviderFromOptions(options: ServicePageOptions = {}) {
|
||||
providerCode.value = normalizeProvider(options.provider);
|
||||
selectedPackageId.value = 0;
|
||||
applyDefaultPackage();
|
||||
uni.setNavigationBarTitle({ title: currentIntro.value.navTitle });
|
||||
}
|
||||
|
||||
onLoad((options: ServicePageOptions = {}) => {
|
||||
const resolvedOptions = resolveServicePageOptions(options);
|
||||
applyProviderFromOptions(resolvedOptions);
|
||||
void loadServiceConfigs();
|
||||
if (resolvedOptions.start === "1" && isLoggedIn()) {
|
||||
setTimeout(() => {
|
||||
void openCategorySheet();
|
||||
@@ -322,16 +245,11 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
<view :class="['service-intro', providerThemeClass]">
|
||||
<view class="service-intro__hero">
|
||||
<view class="service-intro__nav">
|
||||
<view class="service-intro__home" @click="goBack">
|
||||
<view class="service-intro__home" @click="goHome">
|
||||
<view class="service-intro__home-roof"></view>
|
||||
<view class="service-intro__home-body"></view>
|
||||
</view>
|
||||
<view class="service-intro__title">鉴定服务</view>
|
||||
<view class="service-intro__capsule">
|
||||
<text class="service-intro__dots">•••</text>
|
||||
<view class="service-intro__divider"></view>
|
||||
<view class="service-intro__circle"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="service-intro__brand">
|
||||
@@ -363,32 +281,9 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="service-intro__package-title">价格套餐</view>
|
||||
<view v-if="currentPackages.length" class="package-list">
|
||||
<view
|
||||
v-for="item in currentPackages"
|
||||
:key="item.id"
|
||||
:class="['package-card', selectedPackageId === item.id ? 'package-card--selected' : '']"
|
||||
@click="selectPackage(item)"
|
||||
>
|
||||
<view class="package-card__main">
|
||||
<view class="package-card__name">{{ item.package_name }}</view>
|
||||
<view v-if="item.description" class="package-card__desc">{{ item.description }}</view>
|
||||
</view>
|
||||
<view class="package-card__side">
|
||||
<view class="package-card__price">¥{{ formatPrice(item.price) }}</view>
|
||||
<view v-if="item.is_default" class="package-card__tag">默认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="package-empty">{{ serviceConfigsLoading ? "套餐加载中..." : "当前服务暂无可用套餐" }}</view>
|
||||
</view>
|
||||
|
||||
<view class="service-intro__action-bar">
|
||||
<view>
|
||||
<view class="service-intro__price">{{ currentPriceText }}</view>
|
||||
<view class="service-intro__price-desc">{{ currentPriceDesc }}</view>
|
||||
</view>
|
||||
<view :class="['service-intro__primary', submitting ? 'service-intro__primary--disabled' : '']" @click="openCategorySheet">
|
||||
{{ submitting ? "处理中..." : "发起鉴定" }}
|
||||
</view>
|
||||
@@ -412,7 +307,13 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
@click="selectCategory(item)"
|
||||
>
|
||||
<view class="category-card__name">{{ item.name }}</view>
|
||||
<view :class="['category-card__visual', `category-card__visual--${item.visual}`]"></view>
|
||||
<image
|
||||
v-if="item.imageUrl"
|
||||
class="category-card__image"
|
||||
:src="item.imageUrl"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view v-else :class="['category-card__visual', `category-card__visual--${item.visual}`]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -495,22 +396,25 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
min-height: 64rpx;
|
||||
}
|
||||
|
||||
.service-intro__home {
|
||||
position: relative;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.service-intro__home-roof {
|
||||
position: absolute;
|
||||
left: 10rpx;
|
||||
top: 4rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
left: 15rpx;
|
||||
top: 9rpx;
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-left: 5rpx solid #252527;
|
||||
border-top: 5rpx solid #252527;
|
||||
transform: rotate(45deg);
|
||||
@@ -518,10 +422,10 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
|
||||
.service-intro__home-body {
|
||||
position: absolute;
|
||||
left: 12rpx;
|
||||
bottom: 6rpx;
|
||||
width: 30rpx;
|
||||
height: 28rpx;
|
||||
left: 17rpx;
|
||||
bottom: 10rpx;
|
||||
width: 31rpx;
|
||||
height: 29rpx;
|
||||
border: 5rpx solid #252527;
|
||||
border-top: 0;
|
||||
border-radius: 4rpx;
|
||||
@@ -546,38 +450,6 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.service-intro__capsule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 146rpx;
|
||||
height: 62rpx;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.service-intro__dots {
|
||||
color: #111111;
|
||||
font-size: 36rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.service-intro__divider {
|
||||
width: 1px;
|
||||
height: 36rpx;
|
||||
margin: 0 18rpx;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.service-intro__circle {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border: 7rpx solid #111111;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.service-intro__brand {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@@ -839,109 +711,6 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
width: 232rpx;
|
||||
}
|
||||
|
||||
.service-intro__package-title {
|
||||
margin-top: 6rpx;
|
||||
color: #252527;
|
||||
font-size: 32rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.package-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
min-height: 128rpx;
|
||||
padding: 22rpx 24rpx;
|
||||
border: 2rpx solid #e6e6e8;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.package-card--selected {
|
||||
border-color: #edbd00;
|
||||
background: #fffaf0;
|
||||
}
|
||||
|
||||
.service-intro--zhongjian .package-card--selected {
|
||||
border-color: #416f9e;
|
||||
background: #f3f7fb;
|
||||
}
|
||||
|
||||
.package-card__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.package-card__name {
|
||||
color: #252527;
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.package-card__desc {
|
||||
margin-top: 8rpx;
|
||||
color: #8c8c90;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.package-card__side {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.package-card__price {
|
||||
color: #edbd00;
|
||||
font-size: 34rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.service-intro--zhongjian .package-card__price {
|
||||
color: #416f9e;
|
||||
}
|
||||
|
||||
.package-card__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 64rpx;
|
||||
height: 34rpx;
|
||||
margin-top: 8rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(237, 189, 0, 0.13);
|
||||
color: #9a7700;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.service-intro--zhongjian .package-card__tag {
|
||||
background: rgba(65, 111, 158, 0.12);
|
||||
color: #416f9e;
|
||||
}
|
||||
|
||||
.package-empty {
|
||||
margin-top: 20rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
color: #9a9a9d;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.service-intro__action-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
@@ -950,33 +719,18 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
z-index: 90;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 28rpx;
|
||||
justify-content: center;
|
||||
padding: 22rpx 32rpx calc(22rpx + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #e9e9eb;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
}
|
||||
|
||||
.service-intro__price {
|
||||
color: #edbd00;
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.service-intro__price-desc {
|
||||
margin-top: 4rpx;
|
||||
color: #9a9a9d;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.service-intro__primary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
max-width: 360rpx;
|
||||
max-width: 640rpx;
|
||||
height: 86rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #edbd00;
|
||||
@@ -1095,6 +849,14 @@ onLoad((options: ServicePageOptions = {}) => {
|
||||
background: #f7f7f8;
|
||||
}
|
||||
|
||||
.category-card__image {
|
||||
position: absolute;
|
||||
right: 18rpx;
|
||||
bottom: 18rpx;
|
||||
width: 128rpx;
|
||||
height: 104rpx;
|
||||
}
|
||||
|
||||
.category-card__visual::before,
|
||||
.category-card__visual::after {
|
||||
content: "";
|
||||
|
||||
@@ -12,7 +12,7 @@ const pageLoading = ref(false);
|
||||
const pageReady = ref(false);
|
||||
const categoryDataLoaded = ref(false);
|
||||
const loadError = ref("");
|
||||
const defaultHeroBackground = "/static/home/home-reference.jpg";
|
||||
const defaultHeroBackground = "/static/home/home-hero-bg.png";
|
||||
|
||||
const categoryFallbackVisuals = [
|
||||
{ visual: "bag", keys: ["luxury_bag", "奢侈品箱包", "箱包"] },
|
||||
@@ -239,7 +239,7 @@ onShow(fetchHome);
|
||||
height: 470rpx;
|
||||
overflow: hidden;
|
||||
background-size: cover;
|
||||
background-position: center top;
|
||||
background-position: center 44%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
||||
@@ -209,6 +209,10 @@ const secondaryActionText = computed(() =>
|
||||
isPendingPayment.value ? (cancelSubmitting.value ? "取消中..." : "取消订单") : detail.value.available_actions.secondary_action,
|
||||
);
|
||||
|
||||
function goOrderList() {
|
||||
uni.switchTab({ url: "/pages/order/index" });
|
||||
}
|
||||
|
||||
async function fetchDetail() {
|
||||
if (!orderId.value) return;
|
||||
loading.value = true;
|
||||
@@ -424,6 +428,11 @@ onShow(async () => {
|
||||
|
||||
<template>
|
||||
<view class="app-page app-page--tight">
|
||||
<view class="order-detail-nav">
|
||||
<view class="order-detail-nav__back" @click="goOrderList"></view>
|
||||
<view class="order-detail-nav__title">订单详情</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!pageReady && loading" class="section notice-card">
|
||||
<view class="notice-card__title">正在加载订单详情</view>
|
||||
<view class="notice-card__desc">请稍候,我们正在同步订单状态、资料、寄回地址和处理记录。</view>
|
||||
@@ -710,6 +719,44 @@ onShow(async () => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.order-detail-nav {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 72rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.order-detail-nav__back {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.order-detail-nav__back::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
top: 17rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
border-left: 5rpx solid #252527;
|
||||
border-bottom: 5rpx solid #252527;
|
||||
border-radius: 2rpx;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.order-detail-nav__title {
|
||||
color: var(--color-heading);
|
||||
font-size: 38rpx;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.order-detail-hero {
|
||||
padding-bottom: 34rpx;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ onShow(async () => {
|
||||
{{ item.service_provider === "zhongjian" ? "中检鉴定" : "安心验鉴定" }}
|
||||
<text v-if="item.price_package_name"> / {{ item.price_package_name }}</text>
|
||||
</view>
|
||||
<view class="order-card__action">{{ item.primary_action }}</view>
|
||||
<view v-if="item.primary_action" class="order-card__action">{{ item.primary_action }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
BIN
user-app/src/static/home/home-hero-bg.png
Executable file
BIN
user-app/src/static/home/home-hero-bg.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Reference in New Issue
Block a user