Files
ruoyi-vue/src/views/system/jd-instruction/index.vue
2026-03-09 15:25:19 +08:00

1093 lines
32 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">
<el-form :model="form" label-width="45px" label-position="top">
<el-form-item label="输入指令">
<el-input v-model="form.command" type="textarea" :rows="8" placeholder="💰💰💰💰💰💰💰💰" />
</el-form-item>
<div class="button-group button-group-primary">
<el-button type="success" size="medium" @click="run" :loading="loading">执行</el-button>
<el-button type="danger" size="medium" @click="clearAll">清空</el-button>
<el-button size="warning" @click="fillMan">慢单</el-button>
<el-button size="success" @click="fillSheng">通用格式</el-button>
</div>
<div class="button-group button-group-secondary">
<el-button type="primary" size="medium" @click="fillTF">腾峰</el-button>
<el-button type="primary" size="medium" @click="fillFan"></el-button>
<!-- <el-button type="primary" size="medium" @click="fillWen"></el-button> -->
<el-button type="primary" size="medium" @click="fillHong">鸿</el-button>
<el-button type="primary" size="medium" @click="fillPDD">拼多多</el-button>
<!-- <el-button type="primary" size="medium" @click="fillPDDWen">拼多多-纹</el-button> -->
</div>
</el-form>
<el-divider>响应</el-divider>
<div v-if="resultList.length === 0" style="padding: 12px 0;">
<el-empty description="无响应" />
</div>
<div v-else class="response-container">
<!-- 上面完整消息 -->
<div class="response-section response-section-full">
<div class="response-header">
<span>完整消息</span>
<el-button size="mini" type="primary" @click="copyAll">复制全部</el-button>
</div>
<div class="response-content-full">
<el-input :value="fullMessage" type="textarea" :rows="15" readonly />
</div>
</div>
<!-- 下面独立消息列表 -->
<div class="response-section response-section-list">
<div class="response-header">
<span>消息列表 {{ resultList.length }} </span>
</div>
<div class="response-content-list">
<div class="message-list">
<div v-for="(msg, idx) in resultList" :key="idx" class="message-item">
<div class="message-item-header">
<span class="message-index"> {{ idx + 1 }} </span>
<el-button size="mini" type="success" icon="el-icon-document-copy" @click="copyOne(msg)">复制</el-button>
</div>
<div class="message-content">{{ msg }}</div>
</div>
</div>
</div>
</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>
<span class="history-label history-search-label">搜索匹配</span>
<el-input
v-model="historySearchKeyword"
size="small"
placeholder="输入关键词,在全部历史数据中搜索"
clearable
style="width: 240px;"
prefix-icon="el-icon-search"
@input="onHistorySearchInput"
/>
<span v-if="historySearchKeyword.trim()" class="history-search-tip">在全部数据中匹配</span>
</div>
<div class="history-container">
<div class="history-column">
<div class="history-header">
<span>{{ historySearchKeyword.trim() ? `历史请求(关键词匹配 共 ${displayRequestList.length} 条)` : `历史请求(最近 ${historyLimit} 条)` }}</span>
<el-button size="mini" type="primary" icon="el-icon-refresh" @click="loadHistory" :loading="historyLoading">刷新</el-button>
</div>
<div class="history-content" v-loading="displayRequestLoading">
<div v-if="!historySearchKeyword.trim() && requestHistory.length === 0" class="empty-history">
<el-empty description="暂无历史请求" :image-size="80" />
</div>
<div v-else-if="historySearchKeyword.trim() && displayRequestList.length === 0 && !displayRequestLoading" class="empty-history">
<el-empty description="无匹配结果" :image-size="80" />
</div>
<div v-else class="history-list">
<div v-for="(item, idx) in displayRequestList" :key="'req-' + idx" class="history-item">
<div class="history-item-header">
<div class="history-time">{{ extractTime(item) }}</div>
<el-button
size="medium"
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>{{ historySearchKeyword.trim() ? `历史响应(关键词匹配 共 ${displayResponseList.length} 条)` : `历史响应(最近 ${historyLimit} 条)` }}</span>
</div>
<div class="history-content" v-loading="displayResponseLoading">
<div v-if="!historySearchKeyword.trim() && responseHistory.length === 0" class="empty-history">
<el-empty description="暂无历史响应" :image-size="80" />
</div>
<div v-else-if="historySearchKeyword.trim() && displayResponseList.length === 0 && !displayResponseLoading" class="empty-history">
<el-empty description="无匹配结果" :image-size="80" />
</div>
<div v-else class="history-list">
<div v-for="(item, idx) in displayResponseList" :key="'res-' + idx" class="history-item">
<div class="history-item-header">
<div class="history-time">{{ extractTime(item) }}</div>
<el-button
size="medium"
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>
</el-card>
<!-- 地址重复验证码弹窗 -->
<el-dialog
title="地址重复验证"
:visible.sync="verifyDialogVisible"
width="400px"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div style="text-align: center;">
<el-alert
:title="verifyMessage"
type="warning"
:closable="false"
style="margin-bottom: 20px;"
/>
<div style="font-size: 24px; font-weight: bold; color: #409EFF; margin: 20px 0;">
{{ verifyCode }}
</div>
<el-input
v-model="verifyInput"
placeholder="请输入上方四位数字验证码"
maxlength="4"
style="width: 200px;"
@keyup.enter.native="handleVerify"
/>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="verifyDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleVerify" :loading="verifyLoading">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { executeInstruction, getHistory, executeInstructionWithForce } from '@/api/system/instruction'
export default {
name: 'JdInstruction',
data() {
return {
form: { command: '' },
loading: false,
resultList: [],
requestHistory: [],
responseHistory: [],
historyLoading: false,
historyLimit: 50,
historySearchKeyword: '', // 历史记录搜索关键词(有值时请求后端在全部数据中搜索)
searchRequestList: [], // 服务端返回的请求搜索结果
searchResponseList: [], // 服务端返回的响应搜索结果
historySearchLoading: false,
historySearchTimer: null, // 防抖定时器
// 验证码相关
verifyDialogVisible: false,
verifyCode: '',
verifyInput: '',
verifyMessage: '',
verifyLoading: false,
pendingCommand: '' // 待执行的命令(验证通过后执行)
}
},
computed: {
// 当前展示的请求列表:有搜索关键词时用服务端搜索结果,否则用本地已加载的 requestHistory
displayRequestList() {
return this.historySearchKeyword.trim() ? this.searchRequestList : this.requestHistory
},
displayResponseList() {
return this.historySearchKeyword.trim() ? this.searchResponseList : this.responseHistory
},
displayRequestLoading() {
return !!this.historySearchKeyword.trim() && this.historySearchLoading
},
displayResponseLoading() {
return !!this.historySearchKeyword.trim() && this.historySearchLoading
},
// 生成完整消息,用三个空行分隔
fullMessage() {
if (!this.resultList || this.resultList.length === 0) return ''
return this.resultList.join('\n\n\n')
},
// 检测移动端
isMobile() {
if (this.$store?.getters?.device === 'mobile') {
return true
}
if (typeof window !== 'undefined' && window.innerWidth < 768) {
return true
}
return false
}
},
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\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 = []
// 调试:打印返回结果
console.log('返回结果:', this.resultList)
// 检查是否是地址重复或订单编号重复错误
if (this.checkAddressDuplicate(this.resultList)) {
console.log('检测到重复错误,准备显示验证码弹窗')
// 显示验证码弹窗
this.showVerifyDialog(cmd)
return
}
// 检查是否有以[炸弹]开头的消息
this.checkBombAlert(this.resultList)
// 执行成功后刷新历史记录
this.loadHistory()
} else {
this.$modal.msgError(res && res.msg ? res.msg : '执行失败')
}
}).catch(() => {
this.loading = false
this.$modal.msgError('执行失败,请稍后重试')
})
},
// 检查是否是地址重复或订单编号重复错误
checkAddressDuplicate(resultList) {
if (!resultList || resultList.length === 0) {
console.log('结果列表为空')
return false
}
console.log('检查重复错误,结果列表:', resultList)
for (let i = 0; i < resultList.length; i++) {
const result = resultList[i]
console.log(`检查第${i}个结果:`, result, '类型:', typeof result)
if (typeof result === 'string') {
// 检查是否包含地址重复或订单编号重复错误码
const hasAddressDuplicate = result.includes('ERROR_CODE:ADDRESS_DUPLICATE')
const hasOrderNumberDuplicate = result.includes('ERROR_CODE:ORDER_NUMBER_DUPLICATE')
if (hasAddressDuplicate || hasOrderNumberDuplicate) {
console.log('检测到重复错误:', result, '地址重复:', hasAddressDuplicate, '订单编号重复:', hasOrderNumberDuplicate)
return true
}
}
}
console.log('未检测到重复错误')
return false
},
// 显示验证码弹窗
showVerifyDialog(command) {
// 生成四位随机数字验证码
this.verifyCode = String(Math.floor(1000 + Math.random() * 9000))
this.verifyInput = ''
// 根据错误类型设置提示信息
let hasOrderNumberDuplicate = false
let hasAddressDuplicate = false
if (this.resultList && this.resultList.length > 0) {
for (let i = 0; i < this.resultList.length; i++) {
const result = this.resultList[i]
if (typeof result === 'string') {
if (result.includes('ERROR_CODE:ORDER_NUMBER_DUPLICATE')) {
hasOrderNumberDuplicate = true
}
if (result.includes('ERROR_CODE:ADDRESS_DUPLICATE')) {
hasAddressDuplicate = true
}
}
}
}
if (hasOrderNumberDuplicate && hasAddressDuplicate) {
this.verifyMessage = '检测到订单编号和地址重复,请输入验证码以强制生成表单'
} else if (hasOrderNumberDuplicate) {
this.verifyMessage = '检测到订单编号重复,请输入验证码以强制生成表单'
} else {
this.verifyMessage = '检测到地址重复,请输入验证码以强制生成表单'
}
this.pendingCommand = command
this.verifyDialogVisible = true
},
// 处理验证码验证
handleVerify() {
if (!this.verifyInput || this.verifyInput.length !== 4) {
this.$modal.msgError('请输入四位数字验证码')
return
}
if (this.verifyInput !== this.verifyCode) {
this.$modal.msgError('验证码错误,请重新输入')
this.verifyInput = ''
return
}
// 验证通过使用forceGenerate参数重新执行
this.verifyLoading = true
executeInstructionWithForce({ command: this.pendingCommand }).then(res => {
this.verifyLoading = false
this.verifyDialogVisible = 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()
this.$modal.msgSuccess('表单已强制生成')
} else {
this.$modal.msgError(res && res.msg ? res.msg : '执行失败')
}
}).catch(() => {
this.verifyLoading = 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('加载历史记录失败')
})
},
// 搜索框输入:防抖后请求后端在全部历史数据中搜索
onHistorySearchInput() {
if (this.historySearchTimer) clearTimeout(this.historySearchTimer)
const kw = (this.historySearchKeyword || '').trim()
if (!kw) {
this.searchRequestList = []
this.searchResponseList = []
return
}
this.historySearchTimer = setTimeout(() => {
this.loadHistorySearch(kw)
}, 300)
},
// 按关键词在全部数据中搜索(后端接口)
loadHistorySearch(keyword) {
if (!keyword || !keyword.trim()) {
this.searchRequestList = []
this.searchResponseList = []
return
}
this.historySearchLoading = true
const limit = 500
Promise.all([
getHistory('request', limit, keyword),
getHistory('response', limit, keyword)
]).then(([reqRes, respRes]) => {
this.historySearchLoading = false
if (reqRes && reqRes.code === 200) {
this.searchRequestList = reqRes.data || []
} else {
this.searchRequestList = []
}
if (respRes && respRes.code === 200) {
this.searchResponseList = respRes.data || []
} else {
this.searchResponseList = []
}
}).catch(() => {
this.historySearchLoading = 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 = '生'
this.run()
},
fillFan() {
this.form.command = '生\r\nF\r\n型号\r\n1\r\n1\r\n地址'
},
fillWen() {
this.form.command = '生\r\nW'
this.run()
},
fillHong() {
this.form.command = '生\r\nH'
this.run()
},
fillPDD() {
this.form.command = '拼多多\r\n'
this.run()
},
fillPDDWen() {
this.form.command = '拼多多 W\r\n'
this.run()
},
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;
}
/* 移动端卡片优化 */
@media (max-width: 768px) {
.box-card {
margin: 10px;
border-radius: 8px;
}
.box-card ::v-deep .el-card__header {
padding: 12px 16px;
font-size: 16px;
}
.box-card ::v-deep .el-card__body {
padding: 16px;
}
}
/* 响应容器 */
.response-container {
display: flex;
flex-direction: column;
gap: 16px;
margin-top: 8px;
}
.response-section {
border: 1px solid #DCDFE6;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.response-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;
font-size: 14px;
}
/* 完整消息区域 */
.response-content-full {
padding: 0;
background-color: #FFFFFF;
display: flex;
flex-direction: column;
}
.response-content-full ::v-deep .el-textarea {
flex: 1;
display: flex;
flex-direction: column;
}
.response-content-full ::v-deep .el-textarea__inner {
border: none;
border-radius: 0;
resize: none;
min-height: 400px;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
}
/* 消息列表区域 */
.response-content-list {
padding: 16px;
background-color: #FFFFFF;
max-height: 600px;
overflow-y: auto;
}
/* 消息列表样式 */
.message-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.message-item {
padding: 12px;
border-radius: 4px;
background-color: #F9FAFC;
border-left: 3px solid #409EFF;
transition: all 0.3s;
}
.message-item:hover {
background-color: #ECF5FF;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.message-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
flex-wrap: wrap;
gap: 8px;
}
.message-index {
font-size: 12px;
color: #909399;
font-weight: 500;
}
.message-content {
font-size: 13px;
color: #303133;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
font-family: 'Courier New', monospace;
}
/* 滚动条美化 */
.response-content-list::-webkit-scrollbar {
width: 8px;
}
.response-content-list::-webkit-scrollbar-track {
background: #F5F7FA;
}
.response-content-list::-webkit-scrollbar-thumb {
background: #DCDFE6;
border-radius: 4px;
}
.response-content-list::-webkit-scrollbar-thumb:hover {
background: #C0C4CC;
}
/* 移动端响应容器优化 */
@media (max-width: 768px) {
.response-container {
gap: 12px;
}
.response-content-full ::v-deep .el-textarea__inner {
min-height: 250px;
font-size: 12px;
}
.response-content-list {
max-height: 400px;
padding: 12px;
}
.message-item {
padding: 10px;
}
.message-content {
font-size: 12px;
}
.message-item-header {
margin-bottom: 6px;
}
.response-header {
padding: 10px 12px;
font-size: 13px;
}
}
.msg-block { margin-bottom: 12px; }
.msg-header { display: flex; align-items: center; justify-content: space-between; margin: 6px 0; }
/* 按钮组样式 */
.button-group {
margin-bottom: 16px;
display: flex;
flex-wrap: wrap;
gap: 10px;
width: 100%;
}
.button-group .el-button {
margin-right: 0;
padding: 12px 24px;
font-size: 14px;
font-weight: 500;
flex: 1;
min-width: 80px;
}
.button-group .el-button:last-child {
margin-right: 0;
}
/* 移动端按钮优化 - 每行显示4个按钮 */
@media (max-width: 768px) {
.button-group {
gap: 6px;
margin-bottom: 12px;
display: grid !important;
width: 100% !important;
grid-template-columns: repeat(4, 1fr) !important;
}
.button-group .el-button {
padding: 8px 4px;
font-size: 12px;
height: 40px;
line-height: 1.2;
margin: 0 !important;
flex: none !important;
min-width: 0 !important;
max-width: 100% !important;
min-height: 40px;
}
/* 主按钮组:执行、清空、慢单、通用格式 - 一行4个 */
.button-group-primary {
display: grid !important;
grid-template-columns: repeat(4, 1fr) !important;
gap: 6px !important;
width: 100% !important;
}
.button-group-primary .el-button {
width: 100% !important;
max-width: 100% !important;
}
/* 次要按钮组:腾峰、凡、纹、鸿、拼多多、拼多多-纹 - 每行4个 */
.button-group-secondary {
display: grid !important;
grid-template-columns: repeat(4, 1fr) !important;
gap: 6px !important;
width: 100% !important;
}
.button-group-secondary .el-button {
width: 100% !important;
max-width: 100% !important;
padding: 8px 4px;
font-size: 12px;
}
/* 确保按钮组独立于form-item */
.button-group {
margin-left: 0 !important;
margin-right: 0 !important;
}
}
/* 历史记录控制条 */
.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-search-label {
margin-left: 16px;
}
.history-search-tip {
font-size: 12px;
color: #909399;
margin-left: 8px;
}
/* 历史消息容器 */
.history-container {
display: flex;
gap: 20px;
margin-top: 8px;
}
/* 移动端历史记录优化 */
@media (max-width: 768px) {
.history-container {
flex-direction: column;
gap: 12px;
}
.history-column {
width: 100%;
}
.history-content {
height: 300px;
}
.history-controls {
flex-direction: column;
align-items: flex-start;
gap: 10px;
padding: 10px 12px;
}
.history-label {
margin-right: 0;
margin-bottom: 0;
}
}
.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>