292 lines
6.5 KiB
Plaintext
292 lines
6.5 KiB
Plaintext
<template>
|
||
<view class="container">
|
||
<view class="header">
|
||
<text class="title">第三方物流 API 模拟发送器</text>
|
||
<text class="subtitle">模拟外部物流平台向后端推送 Webhook 轨迹数据</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">1. 选择目标订单 (已发货)</text>
|
||
<scroll-view class="order-list" direction="horizontal">
|
||
<view v-for="(item, index) in shippedOrders" :key="index"
|
||
:class="['order-card', selectedOrderIndex == index ? 'active' : '']"
|
||
@click="selectOrder(index)">
|
||
<text class="order-no">{{ item.order_no }}</text>
|
||
<text class="tracking-no">{{ item.carrier }}: {{ item.tracking_no }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">2. 构造回调数据 (JSON Payload)</text>
|
||
<view class="form-group">
|
||
<text class="label">运单号:</text>
|
||
<input class="input" v-model="form.tracking_no" placeholder="请输入运单号" />
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="label">快递公司:</text>
|
||
<input class="input" v-model="form.carrier" placeholder="请输入快递公司" />
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="label">物流状态:</text>
|
||
<picker :range="statusOptions" range-key="label" @change="onStatusChange">
|
||
<view class="picker-val">{{ currentStatusLabel }}</view>
|
||
</picker>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="label">轨迹描述文字:</text>
|
||
<textarea class="textarea" v-model="form.event_text" placeholder="描述当前的物流位置或状态..." />
|
||
</view>
|
||
</view>
|
||
|
||
<view class="payload-preview">
|
||
<text class="preview-title">接口发送原始数据预览:</text>
|
||
<view class="code-block">
|
||
<text class="code-text">{{ jsonString }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<button class="btn-send" type="primary" @click="sendWebhook">立即推送 API 数据</button>
|
||
|
||
<view class="footer-links">
|
||
<text class="link" @click="goToLogs">查看接收日志 (Webhook Logs)</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { mockService, MockOrder } from './mock-service.uts'
|
||
|
||
const shippedOrders = computed((): MockOrder[] => {
|
||
return mockService.getMockOrders().filter((o: MockOrder): boolean => o.status !== 'PENDING' && o.tracking_no !== '')
|
||
})
|
||
|
||
const selectedOrderIndex = ref(-1)
|
||
|
||
const form = reactive({
|
||
tracking_no: '',
|
||
carrier: '',
|
||
status_code: 'IN_TRANSIT',
|
||
event_text: '快件已到达【XX转运中心】,准备发往下一站'
|
||
})
|
||
|
||
const statusOptions = [
|
||
{ label: '在途中 (IN_TRANSIT)', value: 'IN_TRANSIT' },
|
||
{ label: '派送中 (OUT_FOR_DELIVERY)', value: 'OUT_FOR_DELIVERY' },
|
||
{ label: '已签收 (DELIVERED)', value: 'DELIVERED' },
|
||
{ label: '异常 (EXCEPTION)', value: 'EXCEPTION' }
|
||
]
|
||
|
||
const currentStatusLabel = computed(() => {
|
||
const opt = statusOptions.find(o => o.value === form.status_code)
|
||
return opt ? opt.label : '请选择'
|
||
})
|
||
|
||
const jsonString = computed(() => {
|
||
return JSON.stringify(form, null, 2)
|
||
})
|
||
|
||
function selectOrder(index: number) {
|
||
selectedOrderIndex.value = index
|
||
const order = shippedOrders.value[index]
|
||
form.tracking_no = order.tracking_no
|
||
form.carrier = order.carrier
|
||
|
||
// 根据订单当前状态智能预设
|
||
if (order.status === 'SHIPPED') {
|
||
form.status_code = 'IN_TRANSIT'
|
||
form.event_text = '快件已揽收,正发往城市中心'
|
||
} else if (order.status === 'IN_TRANSIT') {
|
||
form.status_code = 'OUT_FOR_DELIVERY'
|
||
form.event_text = '派送员王师傅(13700008888)正在派件'
|
||
}
|
||
}
|
||
|
||
function onStatusChange(e: UniPickerChangeEvent) {
|
||
const idx = e.detail.value as number
|
||
form.status_code = statusOptions[idx].value as string
|
||
}
|
||
|
||
function sendWebhook() {
|
||
if (!form.tracking_no) {
|
||
uni.showToast({ title: '请先填写运单号', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
// 执行模拟推送 (转换为普通对象以兼容 UTS)
|
||
const payload = {
|
||
tracking_no: form.tracking_no,
|
||
carrier: form.carrier,
|
||
status_code: form.status_code,
|
||
event_text: form.event_text
|
||
} as UTSJSONObject
|
||
|
||
const success = mockService.pushWebhookData(payload)
|
||
|
||
if (success) {
|
||
uni.showToast({ title: 'API 发送成功!', icon: 'success' })
|
||
// 可选:跳转到详情或日志预览
|
||
} else {
|
||
uni.showModal({
|
||
title: '发送失败',
|
||
content: '系统未找到该运单号,后端拒绝接收该数据。',
|
||
showCancel: false
|
||
})
|
||
}
|
||
}
|
||
|
||
function goToLogs() {
|
||
uni.navigateTo({ url: '/pages/mall/delivery/test/webhook-logs' })
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
padding: 20px;
|
||
background-color: #f8f9fa;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.header {
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 25px;
|
||
background: #fff;
|
||
padding: 15px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #444;
|
||
margin-bottom: 12px;
|
||
display: block;
|
||
}
|
||
|
||
.order-list {
|
||
white-space: nowrap;
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.order-card {
|
||
display: inline-block;
|
||
width: 160px;
|
||
padding: 12px;
|
||
background: #f0f4f8;
|
||
border: 1.5px solid transparent;
|
||
border-radius: 8px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.order-card.active {
|
||
border-color: #007aff;
|
||
background: #eef6ff;
|
||
}
|
||
|
||
.order-no {
|
||
font-size: 13px;
|
||
font-weight: bold;
|
||
display: block;
|
||
}
|
||
|
||
.tracking-no {
|
||
font-size: 11px;
|
||
color: #888;
|
||
margin-top: 4px;
|
||
display: block;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.label {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-bottom: 5px;
|
||
display: block;
|
||
}
|
||
|
||
.input {
|
||
font-size: 14px;
|
||
color: #333;
|
||
height: 35px;
|
||
}
|
||
|
||
.picker-val {
|
||
font-size: 14px;
|
||
color: #007aff;
|
||
padding: 5px 0;
|
||
}
|
||
|
||
.textarea {
|
||
font-size: 14px;
|
||
width: 100%;
|
||
height: 80px;
|
||
background: #fafafa;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.payload-preview {
|
||
background: #2d2d2d;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.preview-title {
|
||
color: #aaa;
|
||
font-size: 12px;
|
||
margin-bottom: 10px;
|
||
display: block;
|
||
}
|
||
|
||
.code-block {
|
||
font-family: monospace;
|
||
}
|
||
|
||
.code-text {
|
||
color: #69f0ae;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.btn-send {
|
||
margin-top: 10px;
|
||
border-radius: 25px;
|
||
}
|
||
|
||
.footer-links {
|
||
text-align: center;
|
||
margin-top: 25px;
|
||
}
|
||
|
||
.link {
|
||
color: #007aff;
|
||
font-size: 14px;
|
||
text-decoration: underline;
|
||
}
|
||
</style>
|