feat: add kuaidi100 logistics sync
This commit is contained in:
@@ -207,6 +207,9 @@ export interface OrderDetailData {
|
||||
tracking_no: string;
|
||||
tracking_status: string;
|
||||
tracking_status_text: string;
|
||||
provider_status_text: string;
|
||||
sync_status_text: string;
|
||||
sync_error: string;
|
||||
latest_desc: string;
|
||||
latest_time: string;
|
||||
nodes: Array<{
|
||||
@@ -302,6 +305,9 @@ export interface ShippingDetailData {
|
||||
tracking_no: string;
|
||||
tracking_status: string;
|
||||
tracking_status_text: string;
|
||||
provider_status_text: string;
|
||||
sync_status_text: string;
|
||||
sync_error: string;
|
||||
latest_desc: string;
|
||||
latest_time: string;
|
||||
is_submitted: boolean;
|
||||
@@ -314,6 +320,27 @@ export interface ShippingDetailData {
|
||||
can_submit_tracking: boolean;
|
||||
}
|
||||
|
||||
export interface ExpressCompanyRecognitionCandidate {
|
||||
company_name: string;
|
||||
company_code: string;
|
||||
official_name?: string;
|
||||
display_text: string;
|
||||
length_pre?: number;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface ExpressCompanyRecognitionResult {
|
||||
input: string;
|
||||
tracking_no: string;
|
||||
company_code: string;
|
||||
company_name: string;
|
||||
status: string;
|
||||
status_text: string;
|
||||
error_message?: string;
|
||||
resolved: null | ExpressCompanyRecognitionCandidate;
|
||||
candidates: ExpressCompanyRecognitionCandidate[];
|
||||
}
|
||||
|
||||
export interface UserAddressItem {
|
||||
id: number;
|
||||
consignee: string;
|
||||
@@ -627,6 +654,12 @@ export const appApi = {
|
||||
params: { order_id: orderId },
|
||||
});
|
||||
},
|
||||
recognizeOrderShippingCompany(data: { tracking_no: string; company_name?: string; company_code?: string }) {
|
||||
return request<ExpressCompanyRecognitionResult>("/api/app/order/shipping/recognize", {
|
||||
method: "POST",
|
||||
data,
|
||||
});
|
||||
},
|
||||
saveOrderShipping(payload: {
|
||||
order_id: number;
|
||||
express_company: string;
|
||||
|
||||
@@ -301,6 +301,9 @@ export const shippingDetailFallback: ShippingDetailData = {
|
||||
tracking_no: "",
|
||||
tracking_status: "",
|
||||
tracking_status_text: "待提交",
|
||||
provider_status_text: "",
|
||||
sync_status_text: "未同步",
|
||||
sync_error: "",
|
||||
latest_desc: "",
|
||||
latest_time: "",
|
||||
is_submitted: false,
|
||||
|
||||
@@ -63,6 +63,10 @@ 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(() => {
|
||||
@@ -459,7 +463,7 @@ onShow(fetchDetail);
|
||||
<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>
|
||||
<text class="detail-chip">{{ returnLogisticsStatusText || detail.return_logistics.tracking_status_text }}</text>
|
||||
</view>
|
||||
<view class="report-meta__row">
|
||||
<text class="report-meta__label">快递公司</text>
|
||||
@@ -473,6 +477,27 @@ onShow(fetchDetail);
|
||||
<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>
|
||||
|
||||
@@ -770,6 +795,10 @@ onShow(fetchDetail);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.return-logistics-card__timeline {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.return-address-card__address,
|
||||
.return-address-sheet__item-address {
|
||||
margin-top: 14rpx;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { appApi, type ShippingDetailData } from "../../api/app";
|
||||
import { appApi, type ExpressCompanyRecognitionCandidate, type ShippingDetailData } from "../../api/app";
|
||||
import { shippingDetailFallback } from "../../mocks/app";
|
||||
import { resolveErrorMessage, showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
|
||||
import { getPrivacyMode, maskOrderNo } from "../../utils/privacy";
|
||||
@@ -17,9 +17,18 @@ const warehouseSheetVisible = ref(false);
|
||||
const loading = ref(false);
|
||||
const pageReady = ref(false);
|
||||
const loadError = ref("");
|
||||
const recognitionLoading = ref(false);
|
||||
const recognitionCandidates = ref<ExpressCompanyRecognitionCandidate[]>([]);
|
||||
let recognitionTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const submitted = computed(() => detail.value.logistics_info.is_submitted);
|
||||
const canEditTracking = computed(() => !submitted.value && detail.value.can_submit_tracking);
|
||||
const logisticsStatusText = computed(
|
||||
() => detail.value.logistics_info.provider_status_text || detail.value.logistics_info.tracking_status_text || "待提交",
|
||||
);
|
||||
const logisticsLatestDesc = computed(
|
||||
() => detail.value.logistics_info.latest_desc || detail.value.logistics_info.sync_status_text || "",
|
||||
);
|
||||
const hasWarehouseChoices = computed(
|
||||
() => detail.value.shipping_options.can_select_warehouse && detail.value.shipping_options.list.length > 1,
|
||||
);
|
||||
@@ -102,6 +111,47 @@ function useCompany(name: string) {
|
||||
expressCompany.value = name;
|
||||
}
|
||||
|
||||
async function recognizeExpressCompany() {
|
||||
const trackingValue = trackingNo.value.trim();
|
||||
if (!trackingValue || submitted.value) {
|
||||
recognitionCandidates.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
recognitionLoading.value = true;
|
||||
try {
|
||||
const result = await appApi.recognizeOrderShippingCompany({
|
||||
tracking_no: trackingValue,
|
||||
company_name: expressCompany.value.trim(),
|
||||
});
|
||||
recognitionCandidates.value = result.candidates || [];
|
||||
if (result.resolved) {
|
||||
expressCompany.value = result.resolved.company_name;
|
||||
} else if (result.candidates.length === 1) {
|
||||
expressCompany.value = result.candidates[0].company_name;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
recognitionCandidates.value = [];
|
||||
} finally {
|
||||
recognitionLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRecognition() {
|
||||
if (recognitionTimer) {
|
||||
clearTimeout(recognitionTimer);
|
||||
}
|
||||
recognitionTimer = setTimeout(() => {
|
||||
void recognizeExpressCompany();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function chooseRecognitionCandidate(candidate: ExpressCompanyRecognitionCandidate) {
|
||||
expressCompany.value = candidate.company_name;
|
||||
recognitionCandidates.value = [candidate];
|
||||
}
|
||||
|
||||
async function fetchDetail() {
|
||||
if (!orderId.value) return;
|
||||
loading.value = true;
|
||||
@@ -168,6 +218,16 @@ onLoad((options) => {
|
||||
});
|
||||
|
||||
onShow(fetchDetail);
|
||||
|
||||
watch(trackingNo, () => {
|
||||
scheduleRecognition();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (recognitionTimer) {
|
||||
clearTimeout(recognitionTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -254,7 +314,7 @@ onShow(fetchDetail);
|
||||
<view class="metric-card__label">当前寄送商品,请确保与订单信息一致</view>
|
||||
</view>
|
||||
<view class="metric-card">
|
||||
<view class="metric-card__value">{{ submitted ? detail.logistics_info.tracking_status_text : "待提交" }}</view>
|
||||
<view class="metric-card__value">{{ submitted ? logisticsStatusText : "待提交" }}</view>
|
||||
<view class="metric-card__label">寄送状态,提交运单后我们会继续同步节点</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -288,6 +348,17 @@ onShow(fetchDetail);
|
||||
<input v-model="trackingNo" class="field-input" maxlength="40" placeholder="请输入快递单号" :disabled="submitted" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="recognitionLoading" class="form-group__hint">正在识别快递公司...</view>
|
||||
<view v-if="recognitionCandidates.length" class="chip-list" style="margin-top: 16rpx;">
|
||||
<view
|
||||
v-for="candidate in recognitionCandidates"
|
||||
:key="`${candidate.company_code}-${candidate.company_name}`"
|
||||
class="choice-chip"
|
||||
@click="chooseRecognitionCandidate(candidate)"
|
||||
>
|
||||
{{ candidate.company_name }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-group__hint">
|
||||
{{ submitted ? "如物流信息存在异常,请联系平台客服协助处理。" : "提交后将进入待签收跟踪状态,请确认信息无误后再提交。" }}
|
||||
</view>
|
||||
@@ -295,7 +366,7 @@ onShow(fetchDetail);
|
||||
|
||||
<view v-if="submitted" class="section timeline-panel">
|
||||
<view class="section__title">寄送轨迹</view>
|
||||
<view class="section__desc">{{ detail.logistics_info.latest_desc }}</view>
|
||||
<view class="section__desc">{{ logisticsLatestDesc }}</view>
|
||||
<view class="timeline" style="margin-top: 24rpx">
|
||||
<view
|
||||
v-for="item in detail.logistics_nodes"
|
||||
|
||||
Reference in New Issue
Block a user