1
This commit is contained in:
@@ -176,3 +176,56 @@ export function getRecentLogs(params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 批量推送记录相关 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取批量推送记录列表
|
||||||
|
*/
|
||||||
|
export function getBatchPushRecords(params) {
|
||||||
|
return request({
|
||||||
|
url: '/jarvis/tendoc/batchPushRecords',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取批量推送记录详情
|
||||||
|
*/
|
||||||
|
export function getBatchPushRecordDetail(batchId) {
|
||||||
|
return request({
|
||||||
|
url: `/jarvis/tendoc/batchPushRecord/${batchId}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取推送状态和倒计时信息
|
||||||
|
*/
|
||||||
|
export function getPushStatus() {
|
||||||
|
return request({
|
||||||
|
url: '/jarvis/tendoc/pushStatus',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发立即推送
|
||||||
|
*/
|
||||||
|
export function triggerPushNow() {
|
||||||
|
return request({
|
||||||
|
url: '/jarvis/tendoc/triggerPushNow',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消待推送任务
|
||||||
|
*/
|
||||||
|
export function cancelPendingPush() {
|
||||||
|
return request({
|
||||||
|
url: '/jarvis/tendoc/cancelPendingPush',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,188 +1,224 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
title="H-TF订单自动写入配置"
|
|
||||||
:visible.sync="visible"
|
:visible.sync="visible"
|
||||||
width="600px"
|
width="900px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
|
top="5vh"
|
||||||
>
|
>
|
||||||
<!-- 配置状态 -->
|
<!-- 自定义标题 -->
|
||||||
<el-alert
|
<div slot="title" class="dialog-title">
|
||||||
v-if="config.isConfigured"
|
<i class="el-icon-setting"></i>
|
||||||
title="配置已完成"
|
<span>H-TF订单自动写入配置</span>
|
||||||
type="success"
|
<el-tag v-if="config.isConfigured" type="success" size="mini" style="margin-left: 10px;">
|
||||||
:description="config.hint"
|
<i class="el-icon-success"></i> 已配置
|
||||||
show-icon
|
</el-tag>
|
||||||
:closable="false"
|
<el-tag v-else type="warning" size="mini" style="margin-left: 10px;">
|
||||||
style="margin-bottom: 20px;"
|
<i class="el-icon-warning"></i> 未配置
|
||||||
/>
|
</el-tag>
|
||||||
<el-alert
|
</div>
|
||||||
v-else
|
|
||||||
title="配置未完成"
|
|
||||||
type="warning"
|
|
||||||
:description="config.hint"
|
|
||||||
show-icon
|
|
||||||
:closable="false"
|
|
||||||
style="margin-bottom: 20px;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 授权状态 -->
|
<div class="config-container">
|
||||||
<el-card shadow="never" style="margin-bottom: 20px;">
|
<!-- 左侧:配置表单 -->
|
||||||
<div slot="header">
|
<div class="config-left">
|
||||||
<span>授权状态</span>
|
<!-- 授权状态 -->
|
||||||
</div>
|
<div class="config-section">
|
||||||
<el-form label-width="120px">
|
<div class="section-header">
|
||||||
<el-form-item label="授权状态:">
|
<i class="el-icon-key"></i>
|
||||||
<el-tag v-if="config.hasAccessToken" type="success">{{ config.accessTokenStatus }}</el-tag>
|
<span>授权状态</span>
|
||||||
<el-tag v-else type="danger">{{ config.accessTokenStatus }}</el-tag>
|
</div>
|
||||||
<el-button
|
<div class="auth-status">
|
||||||
v-if="!config.hasAccessToken"
|
<el-tag v-if="config.hasAccessToken" type="success" size="medium">
|
||||||
type="primary"
|
<i class="el-icon-circle-check"></i> {{ config.accessTokenStatus }}
|
||||||
size="mini"
|
</el-tag>
|
||||||
style="margin-left: 10px;"
|
<el-tag v-else type="danger" size="medium">
|
||||||
@click="handleAuth"
|
<i class="el-icon-circle-close"></i> {{ config.accessTokenStatus }}
|
||||||
>
|
</el-tag>
|
||||||
立即授权
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<!-- 文档配置 -->
|
|
||||||
<el-card shadow="never">
|
|
||||||
<div slot="header">
|
|
||||||
<span>目标文档配置</span>
|
|
||||||
</div>
|
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
|
|
||||||
<el-form-item label="文件ID:" prop="fileId">
|
|
||||||
<el-input
|
|
||||||
v-model="form.fileId"
|
|
||||||
placeholder="请输入腾讯文档的文件ID"
|
|
||||||
clearable
|
|
||||||
>
|
|
||||||
<el-button
|
<el-button
|
||||||
slot="append"
|
v-if="!config.hasAccessToken"
|
||||||
icon="el-icon-search"
|
type="primary"
|
||||||
@click="handleFetchSheets"
|
size="small"
|
||||||
:disabled="!form.fileId"
|
icon="el-icon-unlock"
|
||||||
|
@click="handleAuth"
|
||||||
>
|
>
|
||||||
获取工作表
|
立即授权
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-input>
|
|
||||||
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
|
|
||||||
例如:DUW50RUprWXh2TGJK(从腾讯文档URL中获取)
|
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</div>
|
||||||
|
|
||||||
<el-form-item label="工作表ID:" prop="sheetId">
|
<!-- 文档配置表单 -->
|
||||||
<el-select
|
<div class="config-section">
|
||||||
v-if="sheetList.length > 0"
|
<div class="section-header">
|
||||||
v-model="form.sheetId"
|
<i class="el-icon-document"></i>
|
||||||
placeholder="请选择工作表"
|
<span>目标文档</span>
|
||||||
style="width: 100%;"
|
|
||||||
clearable
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="sheet in sheetList"
|
|
||||||
:key="sheet.sheetId"
|
|
||||||
:label="sheet.title"
|
|
||||||
:value="sheet.sheetId"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-input
|
|
||||||
v-else
|
|
||||||
v-model="form.sheetId"
|
|
||||||
placeholder="请输入工作表ID或点击'获取工作表'按钮选择"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
|
|
||||||
例如:BB08J2
|
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px" size="small">
|
||||||
|
<el-form-item label="文件ID" prop="fileId">
|
||||||
|
<el-input
|
||||||
|
v-model="form.fileId"
|
||||||
|
placeholder="例如:DUW50RUprWXh2TGJK"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
slot="append"
|
||||||
|
icon="el-icon-search"
|
||||||
|
@click="handleFetchSheets"
|
||||||
|
:disabled="!form.fileId"
|
||||||
|
>
|
||||||
|
获取工作表
|
||||||
|
</el-button>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="表头行号:" prop="headerRow">
|
<el-form-item label="工作表ID" prop="sheetId">
|
||||||
<el-input-number
|
<el-select
|
||||||
v-model="form.headerRow"
|
v-if="sheetList.length > 0"
|
||||||
:min="1"
|
v-model="form.sheetId"
|
||||||
placeholder="表头所在的行号"
|
placeholder="请选择工作表"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
/>
|
clearable
|
||||||
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
|
>
|
||||||
<i class="el-icon-info"></i>
|
<el-option
|
||||||
表头所在的行(包含"单号"、"物流单号"等列名),默认为2
|
v-for="sheet in sheetList"
|
||||||
</div>
|
:key="sheet.sheetId"
|
||||||
</el-form-item>
|
:label="sheet.title"
|
||||||
|
:value="sheet.sheetId"
|
||||||
|
>
|
||||||
|
<span style="float: left">{{ sheet.title }}</span>
|
||||||
|
<span style="float: right; color: #8492a6; font-size: 12px;">{{ sheet.sheetId }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-else
|
||||||
|
v-model="form.sheetId"
|
||||||
|
placeholder="例如:BB08J2"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="数据起始行:" prop="startRow">
|
<el-row :gutter="10">
|
||||||
<el-input-number
|
<el-col :span="12">
|
||||||
v-model="form.startRow"
|
<el-form-item label="表头行号" prop="headerRow">
|
||||||
:min="1"
|
<el-input-number
|
||||||
placeholder="数据从第几行开始"
|
v-model="form.headerRow"
|
||||||
style="width: 100%;"
|
:min="1"
|
||||||
/>
|
controls-position="right"
|
||||||
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
|
style="width: 100%;"
|
||||||
<i class="el-icon-info"></i>
|
/>
|
||||||
数据从第几行开始(默认为3,表示第3行是第一条数据)
|
</el-form-item>
|
||||||
</div>
|
</el-col>
|
||||||
</el-form-item>
|
<el-col :span="12">
|
||||||
</el-form>
|
<el-form-item label="起始行号" prop="startRow">
|
||||||
</el-card>
|
<el-input-number
|
||||||
|
v-model="form.startRow"
|
||||||
<!-- 同步进度显示 -->
|
:min="1"
|
||||||
<el-card v-if="config.progressHint" shadow="never" style="margin-top: 15px;">
|
controls-position="right"
|
||||||
<div slot="header" style="display: flex; align-items: center;">
|
style="width: 100%;"
|
||||||
<i class="el-icon-data-line" style="margin-right: 8px;"></i>
|
/>
|
||||||
<span>同步进度</span>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-alert
|
|
||||||
:title="config.progressHint"
|
<!-- 右侧:状态信息 -->
|
||||||
:type="config.currentProgress ? 'success' : 'info'"
|
<div class="config-right">
|
||||||
:closable="false"
|
<!-- 配置状态提示 -->
|
||||||
show-icon
|
<div class="status-card">
|
||||||
>
|
<div class="status-icon" :class="config.isConfigured ? 'success' : 'warning'">
|
||||||
<template v-if="config.currentProgress">
|
<i :class="config.isConfigured ? 'el-icon-success' : 'el-icon-warning'"></i>
|
||||||
<div style="margin-top: 10px; font-size: 13px;">
|
|
||||||
<p style="margin: 5px 0;">
|
|
||||||
<i class="el-icon-circle-check" style="color: #67c23a;"></i>
|
|
||||||
当前进度:已处理到第 <strong>{{ config.currentProgress }}</strong> 行
|
|
||||||
</p>
|
|
||||||
<p style="margin: 5px 0;">
|
|
||||||
<i class="el-icon-right"></i>
|
|
||||||
下次同步:
|
|
||||||
<span v-if="config.currentProgress <= (form.startRow + 49)">
|
|
||||||
将从第 <strong>{{ form.startRow }}</strong> 行重新开始(进度较小)
|
|
||||||
</span>
|
|
||||||
<span v-else-if="config.currentProgress > (form.startRow + 100)">
|
|
||||||
将从第 <strong>{{ config.currentProgress - 100 }}</strong> 行开始(回溯100行,防止遗漏)
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
将从第 <strong>{{ form.startRow }}</strong> 行重新开始
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p style="margin: 5px 0; color: #909399; font-size: 12px;">
|
|
||||||
<i class="el-icon-info"></i>
|
|
||||||
系统会自动回溯检查,确保不遗漏数据。如需完全重置,请点击"清除配置"
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="status-text">
|
||||||
</el-alert>
|
<div class="status-title">{{ config.isConfigured ? '配置完成' : '配置未完成' }}</div>
|
||||||
</el-card>
|
<div class="status-desc">{{ config.hint }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 同步进度 -->
|
||||||
|
<div v-if="config.progressHint" class="progress-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="el-icon-data-line"></i>
|
||||||
|
<span>同步进度</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-content">
|
||||||
|
<div v-if="config.currentProgress" class="progress-detail">
|
||||||
|
<div class="progress-item">
|
||||||
|
<span class="label">当前进度</span>
|
||||||
|
<span class="value">第 {{ config.currentProgress }} 行</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-item">
|
||||||
|
<span class="label">下次同步</span>
|
||||||
|
<span class="value">
|
||||||
|
<template v-if="config.currentProgress <= (form.startRow + 49)">
|
||||||
|
第 {{ form.startRow }} 行
|
||||||
|
</template>
|
||||||
|
<template v-else-if="config.currentProgress > (form.startRow + 100)">
|
||||||
|
第 {{ config.currentProgress - 100 }} 行
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
第 {{ form.startRow }} 行
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-hint">
|
||||||
|
<i class="el-icon-info"></i>
|
||||||
|
系统自动回溯检查,防止遗漏
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-progress">
|
||||||
|
{{ config.progressHint }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 快速帮助 -->
|
||||||
|
<div class="help-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="el-icon-question"></i>
|
||||||
|
<span>配置说明</span>
|
||||||
|
</div>
|
||||||
|
<div class="help-content">
|
||||||
|
<div class="help-item">
|
||||||
|
<i class="el-icon-check"></i>
|
||||||
|
<span>文件ID从腾讯文档URL中获取</span>
|
||||||
|
</div>
|
||||||
|
<div class="help-item">
|
||||||
|
<i class="el-icon-check"></i>
|
||||||
|
<span>点击"获取工作表"自动加载</span>
|
||||||
|
</div>
|
||||||
|
<div class="help-item">
|
||||||
|
<i class="el-icon-check"></i>
|
||||||
|
<span>表头行号默认为第2行</span>
|
||||||
|
</div>
|
||||||
|
<div class="help-item">
|
||||||
|
<i class="el-icon-check"></i>
|
||||||
|
<span>数据起始行默认为第3行</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div slot="footer">
|
<div slot="footer" class="footer-buttons">
|
||||||
<el-button @click="showOperationLogs = true" icon="el-icon-document" type="info" plain>
|
<div class="footer-left">
|
||||||
查看操作日志
|
<el-button @click="showOperationLogs = true" icon="el-icon-document" size="small">
|
||||||
</el-button>
|
操作日志
|
||||||
<el-button @click="handleTest" :loading="testLoading" icon="el-icon-setting">
|
</el-button>
|
||||||
测试配置
|
<el-button @click="handleTest" :loading="testLoading" icon="el-icon-setting" size="small">
|
||||||
</el-button>
|
测试配置
|
||||||
<el-button @click="handleClear" :loading="clearLoading" type="danger" plain>
|
</el-button>
|
||||||
清除配置
|
</div>
|
||||||
</el-button>
|
<div class="footer-right">
|
||||||
<el-button @click="handleClose">取消</el-button>
|
<el-button @click="handleClear" :loading="clearLoading" type="danger" plain size="small">
|
||||||
<el-button type="primary" @click="handleSave" :loading="saveLoading">
|
清除配置
|
||||||
保存配置
|
</el-button>
|
||||||
</el-button>
|
<el-button @click="handleClose" size="small">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSave" :loading="saveLoading" size="small">
|
||||||
|
<i class="el-icon-check"></i> 保存配置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作日志查看对话框 -->
|
<!-- 操作日志查看对话框 -->
|
||||||
@@ -450,8 +486,264 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.el-card {
|
/* 标题样式 */
|
||||||
border: 1px solid #EBEEF5;
|
.dialog-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title i {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 容器布局 */
|
||||||
|
.config-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-left {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-right {
|
||||||
|
width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置区块 */
|
||||||
|
.config-section {
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header i {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 授权状态 */
|
||||||
|
.auth-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态卡片 */
|
||||||
|
.status-card {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
box-shadow: 0 2px 12px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.warning {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon.success {
|
||||||
|
background: rgba(103, 194, 58, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon.warning {
|
||||||
|
background: rgba(230, 162, 60, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度卡片 */
|
||||||
|
.progress-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 12px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header i {
|
||||||
|
margin-right: 6px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item .label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item .value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #fef0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #f56c6c;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-progress {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 帮助卡片 */
|
||||||
|
.help-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-content {
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-item i {
|
||||||
|
color: #67c23a;
|
||||||
|
margin-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部按钮 */
|
||||||
|
.footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-left,
|
||||||
|
.footer-right {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式调整 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.config-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-right {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Element UI 覆盖样式 */
|
||||||
|
.config-section >>> .el-form-item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section >>> .el-form-item__label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section >>> .el-input-number {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
678
src/views/system/jdorder/components/TencentDocPushMonitor.vue
Normal file
678
src/views/system/jdorder/components/TencentDocPushMonitor.vue
Normal file
@@ -0,0 +1,678 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="腾讯文档推送监控"
|
||||||
|
:visible.sync="visible"
|
||||||
|
width="1200px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleClose"
|
||||||
|
top="5vh"
|
||||||
|
>
|
||||||
|
<div class="push-monitor">
|
||||||
|
<!-- 倒计时和状态卡片 -->
|
||||||
|
<el-card class="countdown-card" shadow="hover">
|
||||||
|
<div class="countdown-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<i class="el-icon-timer"></i>
|
||||||
|
<span class="title">自动推送倒计时</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<el-tag v-if="pushStatus.isScheduled" type="warning" size="medium">
|
||||||
|
<i class="el-icon-loading"></i> 等待推送中
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-else type="info" size="medium">
|
||||||
|
<i class="el-icon-circle-check"></i> 无待推送任务
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="countdown-content">
|
||||||
|
<div class="countdown-display" :class="{active: pushStatus.isScheduled}">
|
||||||
|
<div class="time-box">
|
||||||
|
<span class="time-value">{{ countdownDisplay.minutes }}</span>
|
||||||
|
<span class="time-label">分</span>
|
||||||
|
</div>
|
||||||
|
<span class="time-separator">:</span>
|
||||||
|
<div class="time-box">
|
||||||
|
<span class="time-value">{{ countdownDisplay.seconds }}</span>
|
||||||
|
<span class="time-label">秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="countdown-info">
|
||||||
|
<div v-if="pushStatus.scheduledTime" class="info-item">
|
||||||
|
<i class="el-icon-time"></i>
|
||||||
|
<span>预计推送时间:{{ formatDateTime(pushStatus.scheduledTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="pushStatus.lastSuccessRecord" class="info-item">
|
||||||
|
<i class="el-icon-success"></i>
|
||||||
|
<span>上次推送:{{ formatDateTime(pushStatus.lastSuccessRecord.endTime) }}</span>
|
||||||
|
<el-tag size="mini" type="success" style="margin-left: 10px;">
|
||||||
|
成功 {{ pushStatus.lastSuccessRecord.successCount }} 条
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="countdown-actions">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
icon="el-icon-upload2"
|
||||||
|
:loading="pushing"
|
||||||
|
@click="handleTriggerPushNow"
|
||||||
|
>
|
||||||
|
立即推送
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
icon="el-icon-close"
|
||||||
|
:disabled="!pushStatus.isScheduled"
|
||||||
|
@click="handleCancelPush"
|
||||||
|
>
|
||||||
|
取消推送
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
icon="el-icon-refresh"
|
||||||
|
@click="loadPushStatus"
|
||||||
|
>
|
||||||
|
刷新状态
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 推送记录列表 -->
|
||||||
|
<el-card class="records-card" shadow="hover">
|
||||||
|
<div slot="header" class="records-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<i class="el-icon-document-copy"></i>
|
||||||
|
<span class="title">推送记录</span>
|
||||||
|
<el-tag size="mini" type="info" style="margin-left: 10px;">
|
||||||
|
共 {{ batchRecords.length }} 条
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<el-button type="text" icon="el-icon-refresh" @click="loadBatchRecords">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-timeline v-if="batchRecords.length > 0">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="record in batchRecords"
|
||||||
|
:key="record.batchId"
|
||||||
|
:timestamp="formatDateTime(record.createTime)"
|
||||||
|
placement="top"
|
||||||
|
:type="getRecordType(record.status)"
|
||||||
|
:icon="getRecordIcon(record.status)"
|
||||||
|
>
|
||||||
|
<el-card class="record-item" shadow="hover">
|
||||||
|
<div class="record-summary" @click="toggleRecordDetail(record.batchId)">
|
||||||
|
<div class="summary-left">
|
||||||
|
<el-tag :type="getStatusTagType(record.status)" size="small">
|
||||||
|
{{ getStatusText(record.status) }}
|
||||||
|
</el-tag>
|
||||||
|
<span class="trigger-source">
|
||||||
|
{{ getTriggerSourceText(record.triggerSource) }}
|
||||||
|
</span>
|
||||||
|
<span class="record-stats">
|
||||||
|
<i class="el-icon-check" style="color: #67c23a;"></i> {{ record.successCount }}
|
||||||
|
<i class="el-icon-remove-outline" style="color: #e6a23c; margin-left: 10px;"></i> {{ record.skipCount }}
|
||||||
|
<i class="el-icon-close" style="color: #f56c6c; margin-left: 10px;"></i> {{ record.errorCount }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-right">
|
||||||
|
<span class="record-range">行 {{ record.startRow }} - {{ record.endRow }}</span>
|
||||||
|
<span v-if="record.durationMs" class="record-duration">
|
||||||
|
耗时 {{ formatDuration(record.durationMs) }}
|
||||||
|
</span>
|
||||||
|
<i :class="expandedRecords.includes(record.batchId) ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情展开区域 -->
|
||||||
|
<el-collapse-transition>
|
||||||
|
<div v-if="expandedRecords.includes(record.batchId)" class="record-detail">
|
||||||
|
<el-divider></el-divider>
|
||||||
|
|
||||||
|
<div v-if="record.resultMessage" class="detail-message">
|
||||||
|
<div class="message-label">结果消息:</div>
|
||||||
|
<div class="message-content">{{ record.resultMessage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="record.errorMessage" class="detail-error">
|
||||||
|
<div class="error-label">错误信息:</div>
|
||||||
|
<div class="error-content">{{ record.errorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作日志列表 -->
|
||||||
|
<div v-if="record.operationLogs && record.operationLogs.length > 0" class="operation-logs">
|
||||||
|
<div class="logs-header">
|
||||||
|
<i class="el-icon-document"></i>
|
||||||
|
<span>操作日志({{ record.operationLogs.length }} 条)</span>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
:data="record.operationLogs"
|
||||||
|
size="mini"
|
||||||
|
max-height="300"
|
||||||
|
stripe
|
||||||
|
>
|
||||||
|
<el-table-column prop="orderNo" label="订单号" width="150" />
|
||||||
|
<el-table-column prop="operationType" label="操作类型" width="100" />
|
||||||
|
<el-table-column prop="targetRow" label="目标行" width="80" />
|
||||||
|
<el-table-column prop="logisticsLink" label="物流链接" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="operationStatus" label="状态" width="80">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="scope.row.operationStatus === 'SUCCESS' ? 'success' : 'danger'" size="mini">
|
||||||
|
{{ scope.row.operationStatus }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="errorMessage" label="错误信息" min-width="150" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-collapse-transition>
|
||||||
|
</el-card>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
|
||||||
|
<el-empty v-else description="暂无推送记录"></el-empty>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getPushStatus,
|
||||||
|
getBatchPushRecords,
|
||||||
|
triggerPushNow,
|
||||||
|
cancelPendingPush
|
||||||
|
} from '@/api/jarvis/tendoc'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TencentDocPushMonitor',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
pushing: false,
|
||||||
|
pushStatus: {
|
||||||
|
isScheduled: false,
|
||||||
|
scheduledTime: null,
|
||||||
|
remainingSeconds: 0,
|
||||||
|
remainingMs: 0,
|
||||||
|
countdownText: '无定时任务',
|
||||||
|
lastSuccessRecord: null
|
||||||
|
},
|
||||||
|
countdownDisplay: {
|
||||||
|
minutes: '00',
|
||||||
|
seconds: '00'
|
||||||
|
},
|
||||||
|
batchRecords: [],
|
||||||
|
expandedRecords: [],
|
||||||
|
countdownTimer: null,
|
||||||
|
refreshTimer: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.visible = val
|
||||||
|
if (val) {
|
||||||
|
this.init()
|
||||||
|
} else {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visible(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async init() {
|
||||||
|
await this.loadPushStatus()
|
||||||
|
await this.loadBatchRecords()
|
||||||
|
this.startCountdown()
|
||||||
|
this.startAutoRefresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stopCountdown()
|
||||||
|
this.stopAutoRefresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadPushStatus() {
|
||||||
|
try {
|
||||||
|
const res = await getPushStatus()
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.pushStatus = res.data
|
||||||
|
this.updateCountdownDisplay()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载推送状态失败', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadBatchRecords() {
|
||||||
|
try {
|
||||||
|
const res = await getBatchPushRecords({ limit: 20 })
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.batchRecords = res.data || []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载推送记录失败', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleTriggerPushNow() {
|
||||||
|
try {
|
||||||
|
await this.$confirm('确定要立即执行推送吗?', '提示', {
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.pushing = true
|
||||||
|
const res = await triggerPushNow()
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.$message.success('推送已触发')
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadPushStatus()
|
||||||
|
this.loadBatchRecords()
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg || '触发推送失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') {
|
||||||
|
this.$message.error('触发推送失败: ' + (e.message || '未知错误'))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.pushing = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleCancelPush() {
|
||||||
|
try {
|
||||||
|
await this.$confirm('确定要取消待推送任务吗?', '提示', {
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await cancelPendingPush()
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.$message.success('已取消待推送任务')
|
||||||
|
this.loadPushStatus()
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg || '取消失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') {
|
||||||
|
this.$message.error('取消失败: ' + (e.message || '未知错误'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleRecordDetail(batchId) {
|
||||||
|
const index = this.expandedRecords.indexOf(batchId)
|
||||||
|
if (index > -1) {
|
||||||
|
this.expandedRecords.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
this.expandedRecords.push(batchId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startCountdown() {
|
||||||
|
this.stopCountdown()
|
||||||
|
this.countdownTimer = setInterval(() => {
|
||||||
|
if (this.pushStatus.remainingSeconds > 0) {
|
||||||
|
this.pushStatus.remainingSeconds--
|
||||||
|
this.pushStatus.remainingMs = this.pushStatus.remainingSeconds * 1000
|
||||||
|
this.updateCountdownDisplay()
|
||||||
|
} else if (this.pushStatus.isScheduled) {
|
||||||
|
// 倒计时结束,刷新状态
|
||||||
|
this.loadPushStatus()
|
||||||
|
this.loadBatchRecords()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
|
||||||
|
stopCountdown() {
|
||||||
|
if (this.countdownTimer) {
|
||||||
|
clearInterval(this.countdownTimer)
|
||||||
|
this.countdownTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startAutoRefresh() {
|
||||||
|
this.stopAutoRefresh()
|
||||||
|
// 每30秒自动刷新一次状态
|
||||||
|
this.refreshTimer = setInterval(() => {
|
||||||
|
this.loadPushStatus()
|
||||||
|
this.loadBatchRecords()
|
||||||
|
}, 30000)
|
||||||
|
},
|
||||||
|
|
||||||
|
stopAutoRefresh() {
|
||||||
|
if (this.refreshTimer) {
|
||||||
|
clearInterval(this.refreshTimer)
|
||||||
|
this.refreshTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCountdownDisplay() {
|
||||||
|
const seconds = this.pushStatus.remainingSeconds || 0
|
||||||
|
const minutes = Math.floor(seconds / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
this.countdownDisplay.minutes = String(minutes).padStart(2, '0')
|
||||||
|
this.countdownDisplay.seconds = String(secs).padStart(2, '0')
|
||||||
|
},
|
||||||
|
|
||||||
|
formatDateTime(dateTime) {
|
||||||
|
if (!dateTime) return '-'
|
||||||
|
return this.$moment(dateTime).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
},
|
||||||
|
|
||||||
|
formatDuration(ms) {
|
||||||
|
if (!ms) return '-'
|
||||||
|
const seconds = Math.floor(ms / 1000)
|
||||||
|
const minutes = Math.floor(seconds / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
return minutes > 0 ? `${minutes}分${secs}秒` : `${secs}秒`
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatusText(status) {
|
||||||
|
const statusMap = {
|
||||||
|
'RUNNING': '执行中',
|
||||||
|
'SUCCESS': '成功',
|
||||||
|
'PARTIAL': '部分成功',
|
||||||
|
'FAILED': '失败'
|
||||||
|
}
|
||||||
|
return statusMap[status] || status
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatusTagType(status) {
|
||||||
|
const typeMap = {
|
||||||
|
'RUNNING': 'warning',
|
||||||
|
'SUCCESS': 'success',
|
||||||
|
'PARTIAL': 'warning',
|
||||||
|
'FAILED': 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecordType(status) {
|
||||||
|
const typeMap = {
|
||||||
|
'SUCCESS': 'success',
|
||||||
|
'PARTIAL': 'warning',
|
||||||
|
'FAILED': 'danger',
|
||||||
|
'RUNNING': 'primary'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecordIcon(status) {
|
||||||
|
const iconMap = {
|
||||||
|
'SUCCESS': 'el-icon-success',
|
||||||
|
'PARTIAL': 'el-icon-warning',
|
||||||
|
'FAILED': 'el-icon-error',
|
||||||
|
'RUNNING': 'el-icon-loading'
|
||||||
|
}
|
||||||
|
return iconMap[status] || 'el-icon-info'
|
||||||
|
},
|
||||||
|
|
||||||
|
getTriggerSourceText(source) {
|
||||||
|
const sourceMap = {
|
||||||
|
'DELAYED_TIMER': '延迟定时器',
|
||||||
|
'USER': '用户手动',
|
||||||
|
'SYSTEM': '系统'
|
||||||
|
}
|
||||||
|
return sourceMap[source] || source
|
||||||
|
},
|
||||||
|
|
||||||
|
handleClose() {
|
||||||
|
this.visible = false
|
||||||
|
this.expandedRecords = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.push-monitor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 倒计时卡片 */
|
||||||
|
.countdown-card {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-card >>> .el-card__body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-header .header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-header .header-left i {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-content {
|
||||||
|
padding: 30px 20px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-display.active {
|
||||||
|
opacity: 1;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 80px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-value {
|
||||||
|
font-size: 48px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-label {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-separator {
|
||||||
|
font-size: 36px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 8px 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推送记录卡片 */
|
||||||
|
.records-card {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-header .header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.records-header .header-left i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-summary {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-left,
|
||||||
|
.summary-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-source {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-stats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-range,
|
||||||
|
.record-duration {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-detail {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-message,
|
||||||
|
.detail-error {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-label,
|
||||||
|
.error-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
padding: 10px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border-left: 3px solid #409eff;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
padding: 10px;
|
||||||
|
background: #fef0f0;
|
||||||
|
border-left: 3px solid #f56c6c;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-logs {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-header i {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||||||
<el-button type="warning" size="small" icon="el-icon-download" @click="handleExport" v-hasPermi="['system:jdorder:export']">导出</el-button>
|
<el-button type="warning" size="small" icon="el-icon-download" @click="handleExport" v-hasPermi="['system:jdorder:export']">导出</el-button>
|
||||||
<el-button type="success" size="small" icon="el-icon-setting" @click="showAutoWriteConfig = true" title="配置H-TF订单自动写入腾讯文档">腾峰文档配置</el-button>
|
<el-button type="success" size="small" icon="el-icon-setting" @click="showAutoWriteConfig = true" title="配置H-TF订单自动写入腾讯文档">腾峰文档配置</el-button>
|
||||||
|
<el-button type="info" size="small" icon="el-icon-monitor" @click="showPushMonitor = true" title="查看推送监控和历史记录">推送监控</el-button>
|
||||||
<el-button type="primary" size="small" icon="el-icon-refresh-right" @click="handleBatchSyncLogistics" :loading="batchSyncLoading" title="批量同步物流链接到腾讯文档">
|
<el-button type="primary" size="small" icon="el-icon-refresh-right" @click="handleBatchSyncLogistics" :loading="batchSyncLoading" title="批量同步物流链接到腾讯文档">
|
||||||
<i v-if="!batchSyncLoading"></i>
|
<i v-if="!batchSyncLoading"></i>
|
||||||
一键发货到腾峰
|
一键发货到腾峰
|
||||||
@@ -351,6 +352,9 @@
|
|||||||
|
|
||||||
<!-- H-TF订单自动写入配置 -->
|
<!-- H-TF订单自动写入配置 -->
|
||||||
<tencent-doc-auto-write-config v-model="showAutoWriteConfig" @config-updated="handleAutoConfigUpdated" />
|
<tencent-doc-auto-write-config v-model="showAutoWriteConfig" @config-updated="handleAutoConfigUpdated" />
|
||||||
|
|
||||||
|
<!-- 腾讯文档推送监控 -->
|
||||||
|
<tencent-doc-push-monitor v-model="showPushMonitor" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -359,12 +363,14 @@ import { listJDOrders, updateJDOrder, delJDOrder, fetchLogisticsManually } from
|
|||||||
import { fillLogisticsByOrderNo, getTokenStatus, getTencentDocAuthUrl, testUserInfo, getAutoWriteConfig } from '@/api/jarvis/tendoc'
|
import { fillLogisticsByOrderNo, getTokenStatus, getTencentDocAuthUrl, testUserInfo, getAutoWriteConfig } from '@/api/jarvis/tendoc'
|
||||||
import ListLayout from '@/components/ListLayout'
|
import ListLayout from '@/components/ListLayout'
|
||||||
import TencentDocAutoWriteConfig from './components/TencentDocAutoWriteConfig'
|
import TencentDocAutoWriteConfig from './components/TencentDocAutoWriteConfig'
|
||||||
|
import TencentDocPushMonitor from './components/TencentDocPushMonitor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'JDOrderList',
|
name: 'JDOrderList',
|
||||||
components: {
|
components: {
|
||||||
ListLayout,
|
ListLayout,
|
||||||
TencentDocAutoWriteConfig
|
TencentDocAutoWriteConfig,
|
||||||
|
TencentDocPushMonitor
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -404,6 +410,7 @@ export default {
|
|||||||
tokenStatusChecked: false,
|
tokenStatusChecked: false,
|
||||||
// H-TF订单自动写入配置
|
// H-TF订单自动写入配置
|
||||||
showAutoWriteConfig: false,
|
showAutoWriteConfig: false,
|
||||||
|
showPushMonitor: false,
|
||||||
// 批量同步loading状态
|
// 批量同步loading状态
|
||||||
batchSyncLoading: false,
|
batchSyncLoading: false,
|
||||||
// 获取物流信息对话框
|
// 获取物流信息对话框
|
||||||
|
|||||||
Reference in New Issue
Block a user