515 lines
15 KiB
Vue
515 lines
15 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span>京东指令台</span>
|
||
</div>
|
||
|
||
<el-form :model="form" label-width="80px" label-position="top">
|
||
<el-form-item label="输入指令">
|
||
<el-input v-model="form.command" type="textarea" :rows="8" placeholder="例如:京今日统计 / 京昨日订单 / 慢搜关键词 / 录单20250101-20250107" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="run" :loading="loading">执行</el-button>
|
||
<el-button @click="fillMan">慢单</el-button>
|
||
<el-button @click="fillTF">TF</el-button>
|
||
<el-button @click="fillSheng">生</el-button>
|
||
<el-button type="danger" @click="clearAll">清空</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-divider>响应</el-divider>
|
||
|
||
<div v-if="resultList.length === 0" style="padding: 12px 0;">
|
||
<el-empty description="无响应" />
|
||
</div>
|
||
<div v-else>
|
||
<div v-for="(msg, idx) in resultList" :key="idx" class="msg-block">
|
||
<div class="msg-header">
|
||
<span>第 {{ idx + 1 }} 段</span>
|
||
<el-button size="mini" type="success" @click="copyOne(msg)">复制此段</el-button>
|
||
</div>
|
||
<el-input :value="msg" type="textarea" :rows="8" readonly />
|
||
</div>
|
||
<div style="margin-top: 8px;">
|
||
<el-button size="mini" type="primary" @click="copyAll">复制全部</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-divider>历史消息记录</el-divider>
|
||
|
||
<div class="history-controls">
|
||
<span class="history-label">显示条数:</span>
|
||
<el-select v-model="historyLimit" size="small" style="width: 120px;" @change="loadHistory">
|
||
<el-option label="10条" :value="10"></el-option>
|
||
<el-option label="20条" :value="20"></el-option>
|
||
<el-option label="50条" :value="50"></el-option>
|
||
<el-option label="100条" :value="100"></el-option>
|
||
<el-option label="200条" :value="200"></el-option>
|
||
<el-option label="500条" :value="500"></el-option>
|
||
<el-option label="1000条" :value="1000"></el-option>
|
||
</el-select>
|
||
</div>
|
||
|
||
<div class="history-container">
|
||
<div class="history-column">
|
||
<div class="history-header">
|
||
<span>历史请求(最近 {{ historyLimit }} 条)</span>
|
||
<el-button size="mini" type="primary" icon="el-icon-refresh" @click="loadHistory" :loading="historyLoading">刷新</el-button>
|
||
</div>
|
||
<div class="history-content">
|
||
<div v-if="requestHistory.length === 0" class="empty-history">
|
||
<el-empty description="暂无历史请求" :image-size="80" />
|
||
</div>
|
||
<div v-else class="history-list">
|
||
<div v-for="(item, idx) in requestHistory" :key="'req-' + idx" class="history-item">
|
||
<div class="history-item-header">
|
||
<div class="history-time">{{ extractTime(item) }}</div>
|
||
<el-button
|
||
size="mini"
|
||
icon="el-icon-document-copy"
|
||
type="text"
|
||
@click="copyHistoryItem(item)"
|
||
title="复制此条消息">
|
||
</el-button>
|
||
</div>
|
||
<div class="history-text">{{ extractMessage(item) }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="history-column">
|
||
<div class="history-header">
|
||
<span>历史响应(最近 {{ historyLimit }} 条)</span>
|
||
<el-button size="mini" type="success" @click="copyHistory('response')">复制全部</el-button>
|
||
</div>
|
||
<div class="history-content">
|
||
<div v-if="responseHistory.length === 0" class="empty-history">
|
||
<el-empty description="暂无历史响应" :image-size="80" />
|
||
</div>
|
||
<div v-else class="history-list">
|
||
<div v-for="(item, idx) in responseHistory" :key="'res-' + idx" class="history-item">
|
||
<div class="history-time">{{ extractTime(item) }}</div>
|
||
<div class="history-text">{{ extractMessage(item) }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { executeInstruction, getHistory } from '@/api/system/instruction'
|
||
|
||
export default {
|
||
name: 'JdInstruction',
|
||
data() {
|
||
return {
|
||
form: { command: '' },
|
||
loading: false,
|
||
resultList: [],
|
||
requestHistory: [],
|
||
responseHistory: [],
|
||
historyLoading: false,
|
||
historyLimit: 50
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadHistory()
|
||
},
|
||
methods: {
|
||
copyOne(text) {
|
||
if (!text) return
|
||
this.doCopy(text)
|
||
},
|
||
copyAll() {
|
||
if (!this.resultList || this.resultList.length === 0) return
|
||
const text = this.resultList.join('\n\n')
|
||
this.doCopy(text)
|
||
},
|
||
copyHistory(type) {
|
||
const list = type === 'response' ? this.responseHistory : this.requestHistory
|
||
if (!list || list.length === 0) {
|
||
this.$modal.msgWarning('暂无历史记录')
|
||
return
|
||
}
|
||
const text = list.map(item => this.extractMessage(item)).join('\n\n')
|
||
this.doCopy(text)
|
||
},
|
||
doCopy(text) {
|
||
if (navigator.clipboard) {
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
this.$modal.msgSuccess('复制成功')
|
||
}).catch(() => {
|
||
this.fallbackCopyText(text)
|
||
})
|
||
} else {
|
||
this.fallbackCopyText(text)
|
||
}
|
||
},
|
||
fallbackCopyText(text) {
|
||
const ta = document.createElement('textarea')
|
||
ta.value = text
|
||
document.body.appendChild(ta)
|
||
ta.focus(); ta.select()
|
||
try { document.execCommand('copy'); this.$modal.msgSuccess('复制成功') } catch (e) { this.$modal.msgError('复制失败') }
|
||
document.body.removeChild(ta)
|
||
},
|
||
run() {
|
||
const cmd = (this.form.command || '').trim()
|
||
if (!cmd) { this.$modal.msgError('请输入指令'); return }
|
||
this.loading = true
|
||
executeInstruction({ command: cmd }).then(res => {
|
||
this.loading = false
|
||
if (res && (res.code === 200 || res.msg === '操作成功')) {
|
||
const data = res.data
|
||
if (Array.isArray(data)) this.resultList = data
|
||
else if (typeof data === 'string') this.resultList = data ? [data] : []
|
||
else this.resultList = []
|
||
|
||
// 检查是否有以[炸弹]开头的消息
|
||
this.checkBombAlert(this.resultList)
|
||
|
||
// 执行成功后刷新历史记录
|
||
this.loadHistory()
|
||
} else {
|
||
this.$modal.msgError(res && res.msg ? res.msg : '执行失败')
|
||
}
|
||
}).catch(() => {
|
||
this.loading = false
|
||
this.$modal.msgError('执行失败,请稍后重试')
|
||
})
|
||
},
|
||
loadHistory() {
|
||
this.historyLoading = true
|
||
Promise.all([
|
||
getHistory('request', this.historyLimit),
|
||
getHistory('response', this.historyLimit)
|
||
]).then(([reqRes, respRes]) => {
|
||
this.historyLoading = false
|
||
if (reqRes && reqRes.code === 200) {
|
||
this.requestHistory = reqRes.data || []
|
||
}
|
||
if (respRes && respRes.code === 200) {
|
||
this.responseHistory = respRes.data || []
|
||
}
|
||
}).catch(() => {
|
||
this.historyLoading = false
|
||
this.$modal.msgError('加载历史记录失败')
|
||
})
|
||
},
|
||
extractTime(item) {
|
||
if (!item) return ''
|
||
const idx = item.indexOf(' | ')
|
||
return idx > 0 ? item.substring(0, idx) : ''
|
||
},
|
||
extractMessage(item) {
|
||
if (!item) return ''
|
||
const idx = item.indexOf(' | ')
|
||
return idx > 0 ? item.substring(idx + 3) : item
|
||
},
|
||
fillTF() {
|
||
this.form.command = 'TF'
|
||
},
|
||
fillSheng() {
|
||
this.form.command = '生'
|
||
},
|
||
async fillMan() {
|
||
// 先尝试查询今天的数据
|
||
this.form.command = '慢单'
|
||
this.loading = true
|
||
|
||
try {
|
||
const res = await executeInstruction({ command: '慢单' })
|
||
this.loading = false
|
||
|
||
if (res && (res.code === 200 || res.msg === '操作成功')) {
|
||
const data = res.data
|
||
let resultData = []
|
||
if (Array.isArray(data)) resultData = data
|
||
else if (typeof data === 'string') resultData = data ? [data] : []
|
||
else resultData = []
|
||
|
||
// 如果今天的数据为空,尝试查询昨天的数据
|
||
if (resultData.length === 0 || (resultData.length === 1 && resultData[0].includes('无数据'))) {
|
||
this.$message.info('今天暂无慢单数据,正在查询昨天的数据...')
|
||
|
||
// 获取昨天的日期
|
||
const yesterday = new Date()
|
||
yesterday.setDate(yesterday.getDate() - 1)
|
||
const yesterdayStr = yesterday.toISOString().split('T')[0].replace(/-/g, '')
|
||
|
||
this.loading = true
|
||
const yesterdayRes = await executeInstruction({ command: `慢单${yesterdayStr}` })
|
||
this.loading = false
|
||
|
||
if (yesterdayRes && (yesterdayRes.code === 200 || yesterdayRes.msg === '操作成功')) {
|
||
const yesterdayData = yesterdayRes.data
|
||
if (Array.isArray(yesterdayData)) this.resultList = yesterdayData
|
||
else if (typeof yesterdayData === 'string') this.resultList = yesterdayData ? [yesterdayData] : []
|
||
else this.resultList = []
|
||
|
||
if (this.resultList.length > 0) {
|
||
this.$message.success(`已查询到昨天(${yesterdayStr})的慢单数据`)
|
||
} else {
|
||
this.$message.warning('昨天也没有慢单数据')
|
||
this.resultList = []
|
||
}
|
||
} else {
|
||
this.$message.error('查询昨天数据失败')
|
||
this.resultList = []
|
||
}
|
||
} else {
|
||
// 今天有数据,直接显示
|
||
this.resultList = resultData
|
||
this.$message.success('已查询到今天的慢单数据')
|
||
}
|
||
|
||
// 检查是否有以[炸弹]开头的消息
|
||
this.checkBombAlert(this.resultList)
|
||
|
||
// 执行成功后刷新历史记录
|
||
this.loadHistory()
|
||
} else {
|
||
this.$message.error(res && res.msg ? res.msg : '查询失败')
|
||
this.resultList = []
|
||
}
|
||
} catch (error) {
|
||
this.loading = false
|
||
this.$message.error('查询失败,请稍后重试')
|
||
this.resultList = []
|
||
}
|
||
},
|
||
clearAll() {
|
||
this.form.command = ''
|
||
this.resultList = []
|
||
},
|
||
checkBombAlert(resultList) {
|
||
if (!resultList || resultList.length === 0) return
|
||
|
||
// 检查是否有以[炸弹]开头的消息
|
||
const bombMessages = resultList
|
||
.filter(msg => {
|
||
return msg && typeof msg === 'string' && msg.trim().startsWith('[炸弹]')
|
||
})
|
||
.map(msg => {
|
||
// 移除所有的[炸弹]标记
|
||
return msg.trim().replace(/\[炸弹\]\s*/g, '').trim()
|
||
})
|
||
|
||
if (bombMessages.length > 0) {
|
||
// 显示全屏警告弹窗
|
||
this.$alert(bombMessages.join('\n\n'), '⚠️ 警告提示', {
|
||
confirmButtonText: '我已知晓',
|
||
type: 'warning',
|
||
center: true,
|
||
customClass: 'bomb-alert-dialog',
|
||
showClose: false,
|
||
closeOnClickModal: false,
|
||
closeOnPressEscape: false,
|
||
dangerouslyUseHTMLString: false
|
||
}).catch(() => {})
|
||
}
|
||
},
|
||
copyHistoryItem(item) {
|
||
const message = this.extractMessage(item)
|
||
if (message) {
|
||
this.doCopy(message)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.box-card { margin: 20px; }
|
||
.msg-block { margin-bottom: 12px; }
|
||
.msg-header { display: flex; align-items: center; justify-content: space-between; margin: 6px 0; }
|
||
|
||
/* 历史记录控制条 */
|
||
.history-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-top: 16px;
|
||
margin-bottom: 12px;
|
||
padding: 12px 16px;
|
||
background-color: #F5F7FA;
|
||
border-radius: 4px;
|
||
border: 1px solid #DCDFE6;
|
||
}
|
||
|
||
.history-label {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
margin-right: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 历史消息容器 */
|
||
.history-container {
|
||
display: flex;
|
||
gap: 20px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.history-column {
|
||
flex: 1;
|
||
border: 1px solid #DCDFE6;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.history-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
background-color: #F5F7FA;
|
||
border-bottom: 1px solid #DCDFE6;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.history-content {
|
||
height: 500px;
|
||
overflow-y: auto;
|
||
background-color: #FFFFFF;
|
||
}
|
||
|
||
.empty-history {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
}
|
||
|
||
.history-list {
|
||
padding: 10px;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 10px 12px;
|
||
margin-bottom: 8px;
|
||
border-radius: 4px;
|
||
background-color: #F9FAFC;
|
||
border-left: 3px solid #409EFF;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.history-item:hover {
|
||
background-color: #ECF5FF;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.history-time {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.history-text {
|
||
font-size: 13px;
|
||
color: #303133;
|
||
line-height: 1.5;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* 滚动条美化 */
|
||
.history-content::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
|
||
.history-content::-webkit-scrollbar-track {
|
||
background: #F5F7FA;
|
||
}
|
||
|
||
.history-content::-webkit-scrollbar-thumb {
|
||
background: #DCDFE6;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.history-content::-webkit-scrollbar-thumb:hover {
|
||
background: #C0C4CC;
|
||
}
|
||
|
||
.history-item-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 炸弹警告弹窗样式 */
|
||
::v-deep .bomb-alert-dialog {
|
||
width: 80% !important;
|
||
max-width: 1920px !important;
|
||
}
|
||
|
||
::v-deep .bomb-alert-dialog .el-message-box__message {
|
||
font-size: 16px !important;
|
||
font-weight: 600 !important;
|
||
color: #E6A23C !important;
|
||
white-space: pre-wrap !important;
|
||
word-break: break-word !important;
|
||
line-height: 1.8 !important;
|
||
max-height: 60vh !important;
|
||
overflow-y: auto !important;
|
||
}
|
||
|
||
::v-deep .bomb-alert-dialog .el-message-box__btns {
|
||
text-align: center !important;
|
||
}
|
||
|
||
::v-deep .bomb-alert-dialog .el-message-box__btns .el-button {
|
||
padding: 12px 40px !important;
|
||
font-size: 16px !important;
|
||
font-weight: 600 !important;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
/* 全局样式:炸弹警告弹窗(不使用scoped,因为弹窗挂载在body下) */
|
||
.bomb-alert-dialog {
|
||
width: 80vw !important;
|
||
max-width: 1920px !important;
|
||
min-width: 500px !important;
|
||
}
|
||
|
||
.bomb-alert-dialog .el-message-box__header {
|
||
padding: 20px 20px 15px !important;
|
||
}
|
||
|
||
.bomb-alert-dialog .el-message-box__title {
|
||
font-size: 20px !important;
|
||
font-weight: 700 !important;
|
||
}
|
||
|
||
.bomb-alert-dialog .el-message-box__message {
|
||
font-size: 16px !important;
|
||
font-weight: 600 !important;
|
||
color: #E6A23C !important;
|
||
white-space: pre-wrap !important;
|
||
word-break: break-word !important;
|
||
line-height: 1.8 !important;
|
||
max-height: 60vh !important;
|
||
overflow-y: auto !important;
|
||
padding: 15px 20px !important;
|
||
}
|
||
|
||
.bomb-alert-dialog .el-message-box__btns {
|
||
text-align: center !important;
|
||
padding: 15px 20px 20px !important;
|
||
}
|
||
|
||
.bomb-alert-dialog .el-message-box__btns .el-button {
|
||
padding: 12px 40px !important;
|
||
font-size: 16px !important;
|
||
font-weight: 600 !important;
|
||
}
|
||
</style>
|
||
|
||
|