Files
ruoyi-vue/src/views/system/jd-instruction/index.vue
雷欧(林平凡) 51f77af121 1
2025-10-11 18:19:45 +08:00

515 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>