This commit is contained in:
van
2026-04-03 00:42:56 +08:00
parent e3abff037b
commit 0cb49410a3
2 changed files with 149 additions and 2 deletions

View File

@@ -21,3 +21,20 @@ export function backfillShareLinkLogisticsFromTrace() {
method: 'post' method: 'post'
}) })
} }
/** 与订单列表「获取物流」一致:立即查物流并推送 */
export function fetchShareLinkManually(data) {
return request({
url: '/jarvis/wecom/shareLinkLogisticsJob/fetchShareLinkManually',
method: 'post',
data
})
}
/** 手动执行一轮 Redis 待扫描队列(同定时任务) */
export function drainShareLinkPendingQueueOnce() {
return request({
url: '/jarvis/wecom/shareLinkLogisticsJob/drainPendingQueueOnce',
method: 'post'
})
}

View File

@@ -45,6 +45,17 @@
/> />
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-video-play"
size="mini"
:loading="drainQueueLoading"
v-hasPermi="['jarvis:wecom:shareLinkLog:list']"
@click="handleDrainQueueOnce"
>执行待队列一轮</el-button>
</el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="warning" type="warning"
@@ -80,9 +91,10 @@
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="88" align="center" fixed="right"> <el-table-column label="操作" width="168" align="center" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button> <el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button>
<el-button type="text" size="mini" icon="el-icon-truck" @click="handleFetchShareLink(scope.row)">获取物流</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -95,6 +107,51 @@
@pagination="getList" @pagination="getList"
/> />
<el-dialog title="获取物流信息(分享链)" :visible.sync="fetchLogisticsDialogVisible" width="720px" append-to-body @close="fetchLogisticsResult = null">
<div v-loading="fetchLogisticsLoading">
<el-alert
v-if="fetchLogisticsResult"
:title="fetchLogisticsResult.success ? '请求已完成' : '请求失败'"
:type="fetchLogisticsResult.success ? 'success' : 'error'"
:closable="false"
show-icon
class="mb8"
/>
<el-form v-if="fetchLogisticsResult && fetchLogisticsResult.success" label-width="120px" size="small">
<el-form-item label="jobKey"><span>{{ fetchLogisticsResult.jobKey }}</span></el-form-item>
<el-form-item label="终端成功"><span>{{ fetchLogisticsResult.terminalSuccess }}</span>已推送或已去重</el-form-item>
<el-form-item label="已发推送"><span>{{ fetchLogisticsResult.pushSent }}</span></el-form-item>
<el-form-item label="运单号" v-if="fetchLogisticsResult.waybillNo"><span>{{ fetchLogisticsResult.waybillNo }}</span></el-form-item>
<el-form-item label="说明" v-if="fetchLogisticsResult.adhocNote"><span>{{ fetchLogisticsResult.adhocNote }}</span></el-form-item>
<el-form-item label="物流短链"><span style="word-break:break-all">{{ fetchLogisticsResult.logisticsLink }}</span></el-form-item>
<el-form-item label="请求URL" v-if="fetchLogisticsResult.requestUrl">
<el-input type="textarea" :rows="2" readonly :value="fetchLogisticsResult.requestUrl" />
</el-form-item>
<el-form-item label="健康检查" v-if="fetchLogisticsResult.healthOk != null">
<span>{{ fetchLogisticsResult.healthOk }}</span>
<span v-if="fetchLogisticsResult.healthMessage"> {{ fetchLogisticsResult.healthMessage }}</span>
</el-form-item>
<el-form-item label="推送错误" v-if="fetchLogisticsResult.pushError">
<el-input type="textarea" :rows="3" readonly :value="fetchLogisticsResult.pushError" />
</el-form-item>
<el-form-item label="返回(原始)" v-if="fetchLogisticsResult.responseRaw">
<el-input type="textarea" :rows="5" readonly :value="fetchLogisticsResult.responseRaw" />
</el-form-item>
<el-form-item label="返回(解析)" v-if="fetchLogisticsResult.responseData">
<el-input type="textarea" :rows="8" readonly :value="formatJson(fetchLogisticsResult.responseData)" />
</el-form-item>
</el-form>
<el-form v-else-if="fetchLogisticsResult && !fetchLogisticsResult.success" label-width="100px" size="small">
<el-form-item label="错误"><span>{{ fetchLogisticsResult.error }}</span></el-form-item>
</el-form>
<span v-else-if="fetchLogisticsLoading" style="color:#999">正在请求</span>
</div>
<div slot="footer">
<el-button @click="fetchLogisticsDialogVisible = false">关闭</el-button>
<el-button type="primary" v-if="fetchLogisticsResult && fetchLogisticsResult.success" @click="copyFetchLogisticsResult">复制 JSON</el-button>
</div>
</el-dialog>
<el-dialog title="任务详情" :visible.sync="detailOpen" width="720px" append-to-body> <el-dialog title="任务详情" :visible.sync="detailOpen" width="720px" append-to-body>
<el-descriptions v-if="detail" :column="1" border size="small"> <el-descriptions v-if="detail" :column="1" border size="small">
<el-descriptions-item label="jobKey">{{ detail.jobKey }}</el-descriptions-item> <el-descriptions-item label="jobKey">{{ detail.jobKey }}</el-descriptions-item>
@@ -118,13 +175,17 @@
</template> </template>
<script> <script>
import { listWecomShareLinkLogisticsJob, getWecomShareLinkLogisticsJob, backfillShareLinkLogisticsFromTrace } from '@/api/jarvis/wecomShareLinkLogistics' import { listWecomShareLinkLogisticsJob, getWecomShareLinkLogisticsJob, backfillShareLinkLogisticsFromTrace, fetchShareLinkManually, drainShareLinkPendingQueueOnce } from '@/api/jarvis/wecomShareLinkLogistics'
export default { export default {
name: 'WecomShareLinkLogistics', name: 'WecomShareLinkLogistics',
data() { data() {
return { return {
backfillLoading: false, backfillLoading: false,
drainQueueLoading: false,
fetchLogisticsDialogVisible: false,
fetchLogisticsLoading: false,
fetchLogisticsResult: null,
loading: true, loading: true,
total: 0, total: 0,
list: [], list: [],
@@ -172,6 +233,75 @@ export default {
this.detailOpen = true this.detailOpen = true
}) })
}, },
formatJson(v) {
if (v == null) return ''
if (typeof v === 'string') return v
try {
return JSON.stringify(v, null, 2)
} catch (e) {
return String(v)
}
},
async handleFetchShareLink(row) {
if (!row.trackingUrl || !String(row.trackingUrl).trim()) {
this.$message.warning('该任务暂无物流短链')
return
}
this.fetchLogisticsDialogVisible = true
this.fetchLogisticsLoading = true
this.fetchLogisticsResult = null
try {
const res = await fetchShareLinkManually({ jobKey: row.jobKey })
if (res.code === 200) {
this.fetchLogisticsResult = { success: true, ...res.data }
this.$message.success('已请求物流接口,列表状态已更新')
this.getList()
} else {
this.fetchLogisticsResult = { success: false, error: res.msg || '失败' }
this.$message.error(res.msg || '获取失败')
}
} catch (e) {
this.fetchLogisticsResult = { success: false, error: e.message || '请求异常' }
this.$message.error('获取物流失败: ' + (e.message || '未知错误'))
} finally {
this.fetchLogisticsLoading = false
}
},
copyFetchLogisticsResult() {
if (!this.fetchLogisticsResult) return
const t = JSON.stringify(this.fetchLogisticsResult, null, 2)
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(t).then(() => this.$message.success('已复制到剪贴板')).catch(() => this.fallbackCopy(t))
} else {
this.fallbackCopy(t)
}
},
fallbackCopy(text) {
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.position = 'fixed'
textArea.style.left = '-999999px'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
try {
document.execCommand('copy')
this.$message.success('已复制到剪贴板')
} catch (e) {
this.$message.error('复制失败')
}
document.body.removeChild(textArea)
},
handleDrainQueueOnce() {
this.$modal.confirm('立即执行一轮 Redis 待扫描队列(与定时任务末尾逻辑相同,每批条数受后端 adhoc-pending-batch-size 限制)?').then(() => {
this.drainQueueLoading = true
return drainShareLinkPendingQueueOnce()
}).then(res => {
const d = res.data || {}
this.$modal.msgSuccess('已弹出处理 ' + (d.processedFromQueue != null ? d.processedFromQueue : 0) + ' 条')
this.getList()
}).catch(() => {}).finally(() => { this.drainQueueLoading = false })
},
handleBackfill() { handleBackfill() {
this.$modal.confirm('从「企微消息跟踪」补录历史分享链任务(状态 IMPORTED已存在的 jobKey 将跳过。是否继续?').then(() => { this.$modal.confirm('从「企微消息跟踪」补录历史分享链任务(状态 IMPORTED已存在的 jobKey 将跳过。是否继续?').then(() => {
this.backfillLoading = true this.backfillLoading = true