feat: add kuaidi100 logistics sync
This commit is contained in:
@@ -188,6 +188,10 @@ onShow(() => {
|
||||
<view class="meta-label">寄送到中心</view>
|
||||
<view class="meta-value">{{ detail.logistics_info ? `${detail.logistics_info.express_company || "-"} / ${detail.logistics_info.tracking_no || "-"}` : "-" }}</view>
|
||||
</view>
|
||||
<view v-if="detail.logistics_info" class="meta-item">
|
||||
<view class="meta-label">寄送状态</view>
|
||||
<view class="meta-value">{{ detail.logistics_info.provider_status_text || detail.logistics_info.sync_status_text || detail.logistics_info.tracking_status_text || "-" }}</view>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<view class="meta-label">寄回地址</view>
|
||||
<view class="meta-value">{{ displayAddress(detail.return_address) }}</view>
|
||||
@@ -196,6 +200,24 @@ onShow(() => {
|
||||
<view class="meta-label">回寄运单</view>
|
||||
<view class="meta-value">{{ detail.return_logistics ? `${detail.return_logistics.express_company || "-"} / ${detail.return_logistics.tracking_no || "-"}` : "-" }}</view>
|
||||
</view>
|
||||
<view v-if="detail.return_logistics" class="meta-item">
|
||||
<view class="meta-label">回寄状态</view>
|
||||
<view class="meta-value">{{ detail.return_logistics.provider_status_text || detail.return_logistics.sync_status_text || detail.return_logistics.tracking_status_text || "-" }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="detail.logistics_info?.nodes?.length" class="logistics-timeline">
|
||||
<view class="card-desc">寄送轨迹</view>
|
||||
<view v-for="item in detail.logistics_info.nodes" :key="`send-${item.node_time}-${item.node_desc}`" class="logistics-timeline__item">
|
||||
<view class="logistics-timeline__title">{{ item.node_desc }}</view>
|
||||
<view class="logistics-timeline__meta">{{ item.node_time }}{{ item.node_location ? ` / ${item.node_location}` : "" }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="detail.return_logistics?.nodes?.length" class="logistics-timeline">
|
||||
<view class="card-desc">回寄轨迹</view>
|
||||
<view v-for="item in detail.return_logistics.nodes" :key="`return-${item.node_time}-${item.node_desc}`" class="logistics-timeline__item">
|
||||
<view class="logistics-timeline__title">{{ item.node_desc }}</view>
|
||||
<view class="logistics-timeline__meta">{{ item.node_time }}{{ item.node_location ? ` / ${item.node_location}` : "" }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -445,6 +467,31 @@ onShow(() => {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.logistics-timeline {
|
||||
margin-top: 22rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1px solid var(--work-border);
|
||||
}
|
||||
|
||||
.logistics-timeline__item {
|
||||
padding: 16rpx 0 16rpx 22rpx;
|
||||
border-left: 4rpx solid var(--work-primary);
|
||||
}
|
||||
|
||||
.logistics-timeline__title {
|
||||
color: var(--work-text);
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.logistics-timeline__meta {
|
||||
margin-top: 6rpx;
|
||||
color: var(--work-text-soft);
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.attachment-play {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { adminApi, type AdminExpressCompanyItem, type AdminFileAsset, type AdminWarehouseWorkbenchContext } from "../../api/admin";
|
||||
import {
|
||||
adminApi,
|
||||
type AdminExpressCompanyItem,
|
||||
type AdminExpressCompanyRecognitionCandidate,
|
||||
type AdminFileAsset,
|
||||
type AdminWarehouseWorkbenchContext,
|
||||
} from "../../api/admin";
|
||||
import { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
|
||||
|
||||
const internalTagNo = ref("");
|
||||
@@ -10,6 +16,8 @@ const trackingNo = ref("");
|
||||
const expressCompanyOptions = ref<AdminExpressCompanyItem[]>([]);
|
||||
const expressCompanyLoading = ref(false);
|
||||
const defaultExpressCompany = ref("");
|
||||
const recognitionLoading = ref(false);
|
||||
const recognitionCandidates = ref<AdminExpressCompanyRecognitionCandidate[]>([]);
|
||||
const context = ref<AdminWarehouseWorkbenchContext | null>(null);
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
@@ -34,6 +42,7 @@ const canSubmit = computed(() =>
|
||||
!uploading.value &&
|
||||
!submitting.value,
|
||||
);
|
||||
let recognitionTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function readQueryString(value: unknown) {
|
||||
const raw = Array.isArray(value) ? value[0] : value;
|
||||
@@ -85,6 +94,47 @@ function onExpressCompanyChange(event: any) {
|
||||
expressCompany.value = expressCompanyNames.value[index] || "";
|
||||
}
|
||||
|
||||
async function recognizeExpressCompany() {
|
||||
const trackingValue = trackingNo.value.trim();
|
||||
if (!trackingValue) {
|
||||
recognitionCandidates.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
recognitionLoading.value = true;
|
||||
try {
|
||||
const result = await adminApi.recognizeExpressCompany({
|
||||
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: AdminExpressCompanyRecognitionCandidate) {
|
||||
expressCompany.value = candidate.company_name;
|
||||
recognitionCandidates.value = [candidate];
|
||||
}
|
||||
|
||||
function scanTrackingNo() {
|
||||
uni.scanCode({
|
||||
scanType: ["barCode", "qrCode"],
|
||||
@@ -224,6 +274,16 @@ onLoad((options) => {
|
||||
void fetchExpressCompanies();
|
||||
void fetchContext();
|
||||
});
|
||||
|
||||
watch(trackingNo, () => {
|
||||
scheduleRecognition();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (recognitionTimer) {
|
||||
clearTimeout(recognitionTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -281,6 +341,17 @@ onLoad((options) => {
|
||||
<text class="picker-field__arrow"></text>
|
||||
</view>
|
||||
</picker>
|
||||
<view v-if="recognitionLoading" class="recognition-tip">正在识别快递公司...</view>
|
||||
<view v-if="recognitionCandidates.length" class="chip-list recognition-chip-list">
|
||||
<text
|
||||
v-for="candidate in recognitionCandidates"
|
||||
:key="`${candidate.company_code}-${candidate.company_name}`"
|
||||
class="choice-chip"
|
||||
@click="chooseRecognitionCandidate(candidate)"
|
||||
>
|
||||
{{ candidate.company_name }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="scan-control">
|
||||
<input v-model="trackingNo" class="field scan-input" placeholder="扫描或输入回寄运单号" />
|
||||
<button class="btn scan-button" @click="scanTrackingNo">扫码</button>
|
||||
@@ -382,6 +453,40 @@ onLoad((options) => {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.recognition-tip {
|
||||
margin-top: 14rpx;
|
||||
color: var(--work-text-soft);
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.recognition-chip-list {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.chip-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.choice-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 44rpx;
|
||||
padding: 0 18rpx;
|
||||
border-radius: var(--work-radius-pill);
|
||||
background: var(--work-card-muted);
|
||||
color: var(--work-text);
|
||||
font-size: 22rpx;
|
||||
line-height: 44rpx;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.choice-chip:active {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.scan-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
Reference in New Issue
Block a user