feat: add kuaidi100 logistics sync
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { adminApi, type AdminExpressCompanyItem, type AdminManualOrderCreatePayload, type AdminManualOrderMeta, type AdminOrderDetail, type AdminOrderListItem, type AdminOrderWarehouseOption } from "../../api/admin";
|
||||
import {
|
||||
adminApi,
|
||||
type AdminExpressCompanyItem,
|
||||
type AdminExpressCompanyRecognitionCandidate,
|
||||
type AdminManualOrderCreatePayload,
|
||||
type AdminManualOrderMeta,
|
||||
type AdminOrderDetail,
|
||||
type AdminOrderListItem,
|
||||
type AdminOrderWarehouseOption,
|
||||
} from "../../api/admin";
|
||||
import OrderStatusTag from "../../components/OrderStatusTag.vue";
|
||||
import { recognizeReturnAddress } from "../../utils/address-recognition";
|
||||
|
||||
@@ -19,6 +28,8 @@ const returnDialogVisible = ref(false);
|
||||
const returnSubmitting = ref(false);
|
||||
const returnExpressCompany = ref("");
|
||||
const returnTrackingNo = ref("");
|
||||
const returnRecognitionLoading = ref(false);
|
||||
const returnRecognitionCandidates = ref<AdminExpressCompanyRecognitionCandidate[]>([]);
|
||||
const expressCompanyLoading = ref(false);
|
||||
const expressCompanyOptions = ref<AdminExpressCompanyItem[]>([]);
|
||||
const defaultExpressCompany = ref("");
|
||||
@@ -139,6 +150,8 @@ const expressCompanySelectOptions = computed(() => {
|
||||
...expressCompanyOptions.value,
|
||||
];
|
||||
});
|
||||
let returnRecognitionTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
function createManualOrderForm(): AdminManualOrderCreatePayload {
|
||||
return {
|
||||
service_provider: "anxinyan",
|
||||
@@ -377,7 +390,53 @@ async function openReturnDialog() {
|
||||
await ensureExpressCompanyOptions();
|
||||
returnExpressCompany.value = detail.value.return_logistics?.express_company || defaultExpressCompany.value || expressCompanyOptions.value[0]?.company_name || "";
|
||||
returnTrackingNo.value = detail.value.return_logistics?.tracking_no || "";
|
||||
returnRecognitionCandidates.value = [];
|
||||
returnDialogVisible.value = true;
|
||||
if (returnTrackingNo.value) {
|
||||
scheduleReturnRecognition();
|
||||
}
|
||||
}
|
||||
|
||||
async function recognizeReturnExpressCompany() {
|
||||
const trackingNo = returnTrackingNo.value.trim();
|
||||
if (!returnDialogVisible.value || !trackingNo) {
|
||||
returnRecognitionCandidates.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
returnRecognitionLoading.value = true;
|
||||
try {
|
||||
const response = await adminApi.recognizeExpressCompany({
|
||||
tracking_no: trackingNo,
|
||||
company_name: returnExpressCompany.value.trim(),
|
||||
});
|
||||
const result = response.data;
|
||||
returnRecognitionCandidates.value = result.candidates || [];
|
||||
if (result.resolved) {
|
||||
returnExpressCompany.value = result.resolved.company_name;
|
||||
} else if (result.candidates.length === 1) {
|
||||
returnExpressCompany.value = result.candidates[0].company_name;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
returnRecognitionCandidates.value = [];
|
||||
} finally {
|
||||
returnRecognitionLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleReturnRecognition() {
|
||||
if (returnRecognitionTimer) {
|
||||
clearTimeout(returnRecognitionTimer);
|
||||
}
|
||||
returnRecognitionTimer = setTimeout(() => {
|
||||
void recognizeReturnExpressCompany();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function chooseReturnRecognitionCandidate(candidate: AdminExpressCompanyRecognitionCandidate) {
|
||||
returnExpressCompany.value = candidate.company_name;
|
||||
returnRecognitionCandidates.value = [candidate];
|
||||
}
|
||||
|
||||
async function submitReturnLogistics() {
|
||||
@@ -409,6 +468,18 @@ async function submitReturnLogistics() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(returnTrackingNo, () => {
|
||||
if (returnDialogVisible.value) {
|
||||
scheduleReturnRecognition();
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (returnRecognitionTimer) {
|
||||
clearTimeout(returnRecognitionTimer);
|
||||
}
|
||||
});
|
||||
|
||||
async function markReturnReceived() {
|
||||
if (!detail.value) return;
|
||||
returnReceiveSubmitting.value = true;
|
||||
@@ -655,10 +726,18 @@ onMounted(fetchOrders);
|
||||
<div class="order-detail-item__label">物流状态</div>
|
||||
<div class="order-detail-item__value">{{ logisticsActionText }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item">
|
||||
<div class="order-detail-item__label">快递100状态</div>
|
||||
<div class="order-detail-item__value">{{ detail.logistics_info.provider_status_text || detail.logistics_info.sync_status_text || "-" }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item order-detail-item--full">
|
||||
<div class="order-detail-item__label">最新节点</div>
|
||||
<div class="order-detail-item__value">{{ detail.logistics_info.latest_desc || "-" }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item order-detail-item--full" v-if="detail.logistics_info.sync_error">
|
||||
<div class="order-detail-item__label">同步异常</div>
|
||||
<div class="order-detail-item__value">{{ detail.logistics_info.sync_error }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item order-detail-item--full" v-if="detail.logistics_info.latest_time">
|
||||
<div class="order-detail-item__label">最新更新时间</div>
|
||||
<div class="order-detail-item__value">{{ detail.logistics_info.latest_time }}</div>
|
||||
@@ -680,10 +759,18 @@ onMounted(fetchOrders);
|
||||
<div class="order-detail-item__label">物流状态</div>
|
||||
<div class="order-detail-item__value">{{ detail.return_logistics.tracking_status_text }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item">
|
||||
<div class="order-detail-item__label">快递100状态</div>
|
||||
<div class="order-detail-item__value">{{ detail.return_logistics.provider_status_text || detail.return_logistics.sync_status_text || "-" }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item order-detail-item--full">
|
||||
<div class="order-detail-item__label">最新节点</div>
|
||||
<div class="order-detail-item__value">{{ detail.return_logistics.latest_desc || "-" }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item order-detail-item--full" v-if="detail.return_logistics.sync_error">
|
||||
<div class="order-detail-item__label">同步异常</div>
|
||||
<div class="order-detail-item__value">{{ detail.return_logistics.sync_error }}</div>
|
||||
</div>
|
||||
<div class="order-detail-item order-detail-item--full" v-if="detail.return_logistics.latest_time">
|
||||
<div class="order-detail-item__label">最新更新时间</div>
|
||||
<div class="order-detail-item__value">{{ detail.return_logistics.latest_time }}</div>
|
||||
@@ -738,6 +825,18 @@ onMounted(fetchOrders);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card" v-if="detail.return_logistics" style="grid-column: 1 / -1">
|
||||
<div class="detail-card__title">回寄物流轨迹</div>
|
||||
<div v-if="detail.return_logistics.nodes.length" class="timeline-list" style="margin-top: 14px">
|
||||
<div v-for="item in detail.return_logistics.nodes" :key="`${item.node_time}-${item.node_desc}`" class="timeline-node">
|
||||
<div class="timeline-node__title">{{ item.node_desc }}</div>
|
||||
<div class="timeline-node__time">{{ item.node_time }}</div>
|
||||
<div class="timeline-node__desc">{{ item.node_location || "-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无回寄物流轨迹" :image-size="64" />
|
||||
</div>
|
||||
|
||||
<div class="detail-card" v-if="detail.supplement_task" style="grid-column: 1 / -1">
|
||||
<div class="detail-card__title">补图任务</div>
|
||||
<div class="detail-card__desc">
|
||||
@@ -810,6 +909,20 @@ onMounted(fetchOrders);
|
||||
<el-form-item label="回寄运单号">
|
||||
<el-input v-model="returnTrackingNo" placeholder="请输入回寄运单号" />
|
||||
</el-form-item>
|
||||
<div v-if="returnRecognitionLoading" style="margin: -8px 0 12px; color: var(--admin-text-subtle); font-size: 12px;">
|
||||
正在识别快递公司...
|
||||
</div>
|
||||
<div v-if="returnRecognitionCandidates.length" style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px;">
|
||||
<el-tag
|
||||
v-for="candidate in returnRecognitionCandidates"
|
||||
:key="`${candidate.company_code}-${candidate.company_name}`"
|
||||
effect="plain"
|
||||
style="cursor: pointer;"
|
||||
@click="chooseReturnRecognitionCandidate(candidate)"
|
||||
>
|
||||
{{ candidate.company_name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="detail?.return_address"
|
||||
type="info"
|
||||
|
||||
Reference in New Issue
Block a user