Files
toolbox/resources/js/components/message-sync/MessageDispatch.vue
2025-12-02 10:16:32 +08:00

747 lines
30 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="p-6">
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">消息分发异常查询</h1>
<p class="text-gray-600 mt-2">查询和管理异常的消息分发数据</p>
</div>
<!-- 筛选区域 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">筛选条件</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">消息ID</label>
<textarea
v-model="filters.msgId"
rows="3"
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500"
placeholder="输入消息ID支持多个用空格、换行或逗号分隔"
></textarea>
</div>
<div class="relative">
<label class="block text-sm font-medium text-gray-700 mb-2">国家代码</label>
<div class="relative">
<button
@click="showCountryDropdown = !showCountryDropdown"
type="button"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-left focus:ring-2 focus:ring-blue-500 bg-white flex items-center justify-between"
>
<span class="text-sm text-gray-700">
{{ filters.countryCodes.length > 0 ? `已选 ${filters.countryCodes.length}` : '请选择国家代码' }}
</span>
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
<div
v-if="showCountryDropdown"
class="absolute z-50 mt-1 w-full bg-white border border-gray-300 rounded-lg shadow-lg max-h-80 overflow-y-auto"
>
<div class="sticky top-0 bg-gray-50 border-b border-gray-200 p-2 flex gap-2">
<button
@click="selectAllCountries"
type="button"
class="flex-1 px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700"
>
全选
</button>
<button
@click="clearAllCountries"
type="button"
class="flex-1 px-3 py-1 text-xs bg-gray-600 text-white rounded hover:bg-gray-700"
>
清空
</button>
</div>
<div class="p-2">
<label class="flex items-center px-2 py-2 hover:bg-gray-50 rounded cursor-pointer">
<input
type="checkbox"
value=""
v-model="filters.countryCodes"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="ml-2 text-sm text-gray-500 italic">()</span>
</label>
<label
v-for="code in availableCountryCodes"
:key="code"
class="flex items-center px-2 py-2 hover:bg-gray-50 rounded cursor-pointer"
>
<input
type="checkbox"
:value="code"
v-model="filters.countryCodes"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="ml-2 text-sm text-gray-700">{{ code }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="relative">
<label class="block text-sm font-medium text-gray-700 mb-2">域名</label>
<div class="relative">
<button
@click="showDomainDropdown = !showDomainDropdown"
type="button"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-left focus:ring-2 focus:ring-blue-500 bg-white flex items-center justify-between"
>
<span class="text-sm text-gray-700">
{{ filters.domains.length > 0 ? `已选 ${filters.domains.length}` : '请选择域名' }}
</span>
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
<div
v-if="showDomainDropdown"
class="absolute z-50 mt-1 w-full bg-white border border-gray-300 rounded-lg shadow-lg max-h-80 overflow-y-auto"
>
<div class="sticky top-0 bg-gray-50 border-b border-gray-200 p-2 flex gap-2">
<button
@click="selectAllDomains"
type="button"
class="flex-1 px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700"
>
全选
</button>
<button
@click="clearAllDomains"
type="button"
class="flex-1 px-3 py-1 text-xs bg-gray-600 text-white rounded hover:bg-gray-700"
>
清空
</button>
</div>
<div class="p-2">
<label class="flex items-center px-2 py-2 hover:bg-gray-50 rounded cursor-pointer">
<input
type="checkbox"
value=""
v-model="filters.domains"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="ml-2 text-sm text-gray-500 italic">()</span>
</label>
<label
v-for="domain in availableDomains"
:key="domain"
class="flex items-center px-2 py-2 hover:bg-gray-50 rounded cursor-pointer"
>
<input
type="checkbox"
:value="domain"
v-model="filters.domains"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="ml-2 text-sm text-gray-700">{{ domain }}</span>
</label>
</div>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">请求状态</label>
<select
v-model="filters.requestStatus"
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500"
>
<option :value="null">全部</option>
<option :value="0">待处理</option>
<option :value="1">成功</option>
<option :value="2">失败</option>
<option :value="3">重试中</option>
<option :value="4">超时</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">回调状态</label>
<select
v-model="filters.businessStatus"
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500"
>
<option :value="null">全部</option>
<option :value="0">待处理</option>
<option :value="1">成功</option>
<option :value="2">失败</option>
</select>
</div>
</div>
<div class="mt-4 flex justify-between items-center">
<div class="flex gap-2">
<button
@click="quickFilterCN"
class="px-4 py-2 text-sm font-medium rounded-md bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors"
>
快速筛选 CN
</button>
<button
@click="quickFilterUS"
class="px-4 py-2 text-sm font-medium rounded-md bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors"
>
快速筛选 US
</button>
</div>
<button
@click="loadData"
:disabled="loading"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
查询
</button>
</div>
</div>
<!-- 错误信息 -->
<div v-if="error" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<div class="flex">
<svg class="w-5 h-5 text-red-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<div>
<h3 class="text-sm font-medium text-red-800">错误</h3>
<p class="text-sm text-red-700 mt-1">{{ error }}</p>
</div>
</div>
</div>
<!-- 数据表格 -->
<div v-if="dispatches.length > 0" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-700">异常数据列表 ({{ dispatches.length }})</h2>
<div class="flex gap-2">
<button
@click="copySelectedMsgIds"
:disabled="selectedIds.length === 0"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
复制消息ID ({{ selectedIds.length }})
</button>
<button
@click="showBatchUpdateModal"
:disabled="selectedIds.length === 0"
class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
批量更新 ({{ selectedIds.length }})
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" style="min-width: 1600px;">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-3 text-left sticky left-0 bg-gray-50 z-10">
<input
type="checkbox"
@change="toggleSelectAll"
:checked="selectedIds.length === dispatches.length"
class="rounded"
/>
</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">消息ID</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">国家代码</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">域名</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">事件名称</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">实体代码</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">请求状态</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">回调状态</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">Agent状态</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">重试次数</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">请求错误</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">回调错误</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">创建时间</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">更新时间</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase whitespace-nowrap">操作</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="item in dispatches" :key="item.id" class="hover:bg-gray-50">
<td class="px-3 py-3 sticky left-0 bg-white z-10">
<input
type="checkbox"
:value="item.id"
v-model="selectedIds"
class="rounded"
/>
</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.msg_id }}</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.country_code || '-' }}</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.domain || '-' }}</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.event_name }}</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.entity_code }}</td>
<td class="px-3 py-3 whitespace-nowrap">
<span :class="getStatusClass(item.request_status)">
{{ getStatusText(item.request_status) }}
</span>
</td>
<td class="px-3 py-3 whitespace-nowrap">
<span :class="getBusinessStatusClass(item.business_status)">
{{ getBusinessStatusText(item.business_status) }}
</span>
</td>
<td class="px-3 py-3 whitespace-nowrap">
<span v-if="!isUsDomain(item.domain)" :class="getConsumerStatusClass(item.consumer_status)">
{{ getConsumerStatusText(item.consumer_status) }}
</span>
<span v-else class="text-sm text-gray-400">-</span>
</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.retry_count }}</td>
<td class="px-3 py-3 text-sm text-gray-900 max-w-xs truncate" :title="item.request_error_message">
{{ item.request_error_message || '-' }}
</td>
<td class="px-3 py-3 text-sm text-gray-900 max-w-xs truncate" :title="item.business_error_message">
{{ item.business_error_message || '-' }}
</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.created || '-' }}</td>
<td class="px-3 py-3 text-sm text-gray-900 whitespace-nowrap">{{ item.updated || '-' }}</td>
<td class="px-3 py-3 whitespace-nowrap">
<button
@click="showDetail(item)"
class="text-blue-600 hover:text-blue-800 text-sm"
>
详情
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 详情弹窗 -->
<div v-if="detailItem" class="fixed inset-0 flex items-center justify-center z-50" @click.self="detailItem = null">
<div class="bg-white rounded-lg p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto border border-gray-200 shadow-xl">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold">消息详情</h3>
<button @click="detailItem = null" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="text-sm font-medium text-gray-700">消息ID</label>
<p class="text-sm text-gray-900">{{ detailItem.msg_id }}</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700">服务名称</label>
<p class="text-sm text-gray-900">{{ detailItem.service_name }}</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700">请求错误信息</label>
<p class="text-sm text-red-600">{{ detailItem.request_error_message || '-' }}</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700">业务错误信息</label>
<p class="text-sm text-red-600">{{ detailItem.business_error_message || '-' }}</p>
</div>
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-2 block">消息体</label>
<pre class="bg-gray-50 p-4 rounded-lg text-xs overflow-x-auto">{{ formatJson(detailItem.msg_body) }}</pre>
</div>
</div>
</div>
</div>
<!-- 批量更新弹窗 -->
<div v-if="showBatchUpdate" class="fixed inset-0 flex items-center justify-center z-50" @click.self="showBatchUpdate = false">
<div class="bg-white rounded-lg p-6 max-w-2xl w-full border border-gray-200 shadow-xl">
<h3 class="text-xl font-semibold mb-4">批量更新 ({{ selectedIds.length }} )</h3>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">请求状态</label>
<select v-model="batchUpdate.request_status" class="w-full border border-gray-300 rounded-lg px-3 py-2">
<option :value="null">不修改</option>
<option :value="0">待处理</option>
<option :value="1">成功</option>
<option :value="2">失败</option>
<option :value="3">重试中</option>
<option :value="4">超时</option>
<option :value="5">已取消</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">回调状态</label>
<select v-model="batchUpdate.business_status" class="w-full border border-gray-300 rounded-lg px-3 py-2">
<option :value="null">不修改</option>
<option :value="0">待处理</option>
<option :value="1">成功</option>
<option :value="2">失败</option>
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">重试次数</label>
<input v-model.number="batchUpdate.retry_count" type="number" min="0" class="w-full border border-gray-300 rounded-lg px-3 py-2" placeholder="不修改" />
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">目标服务</label>
<select v-model="batchUpdate.target_service" class="w-full border border-gray-300 rounded-lg px-3 py-2">
<option :value="null">不修改</option>
<option v-for="route in serviceRoutes" :key="route.id" :value="route.id">
{{ route.display_name }}
</option>
</select>
</div>
</div>
<div class="flex justify-end space-x-4 mt-6">
<button @click="showBatchUpdate = false" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">
取消
</button>
<button @click="executeBatchUpdate" :disabled="updating" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50">
确认更新
</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'MessageDispatch',
data() {
return {
loading: false,
updating: false,
error: null,
availableCountryCodes: [],
availableDomains: [],
serviceRoutes: [],
showCountryDropdown: false,
showDomainDropdown: false,
filters: {
msgId: null,
countryCodes: [],
domains: [],
requestStatus: null,
businessStatus: null,
},
dispatches: [],
selectedIds: [],
detailItem: null,
showBatchUpdate: false,
batchUpdate: {
request_status: null,
business_status: null,
retry_count: null,
target_service: null,
},
};
},
mounted() {
this.loadCountryCodes();
this.loadDomains();
this.loadServiceRoutes();
document.addEventListener('click', this.handleClickOutside);
},
beforeUnmount() {
document.removeEventListener('click', this.handleClickOutside);
},
methods: {
async loadCountryCodes() {
try {
const response = await axios.get('/api/message-dispatch/country-codes');
this.availableCountryCodes = response.data.data;
} catch (err) {
console.error('加载国家代码列表失败:', err);
}
},
async loadDomains() {
try {
const response = await axios.get('/api/message-dispatch/domains');
this.availableDomains = response.data.data;
} catch (err) {
console.error('加载域名列表失败:', err);
}
},
async loadServiceRoutes() {
try {
const response = await axios.get('/api/message-dispatch/service-routes');
this.serviceRoutes = response.data.data;
} catch (err) {
console.error('加载服务路由列表失败:', err);
}
},
async loadData() {
this.loading = true;
this.error = null;
try {
const params = {};
// 处理消息ID按空格、换行、逗号分割
if (this.filters.msgId) {
const msgIds = this.filters.msgId
.split(/[\s,]+/)
.map(id => id.trim())
.filter(id => id.length > 0);
if (msgIds.length > 0) {
params.msg_ids = msgIds;
}
}
if (this.filters.countryCodes.length > 0) params.country_codes = this.filters.countryCodes;
if (this.filters.domains.length > 0) params.domains = this.filters.domains;
if (this.filters.requestStatus !== null) params.request_status = this.filters.requestStatus;
if (this.filters.businessStatus !== null) params.business_status = this.filters.businessStatus;
const response = await axios.get('/api/message-dispatch/abnormal', { params });
this.dispatches = response.data.data;
this.selectedIds = [];
} catch (err) {
this.error = err.response?.data?.message || err.message;
} finally {
this.loading = false;
}
},
selectAllCountries() {
this.filters.countryCodes = ['', ...this.availableCountryCodes];
},
clearAllCountries() {
this.filters.countryCodes = [];
},
selectAllDomains() {
this.filters.domains = ['', ...this.availableDomains];
},
clearAllDomains() {
this.filters.domains = [];
},
quickFilterCN() {
// CN筛选选择空和cnsha域名
const cnDomains = this.availableDomains.filter(domain =>
domain && domain.includes('cnsha')
);
this.filters.domains = ['', ...cnDomains];
this.loadData();
},
quickFilterUS() {
// US筛选选择us域名
const usDomains = this.availableDomains.filter(domain =>
domain && (domain.includes('partner-us') || domain.includes('.us.'))
);
this.filters.domains = usDomains;
this.loadData();
},
handleClickOutside(event) {
const dropdown = event.target.closest('.relative');
if (!dropdown || !dropdown.querySelector('button')) {
this.showCountryDropdown = false;
this.showDomainDropdown = false;
}
},
toggleSelectAll(event) {
if (event.target.checked) {
this.selectedIds = this.dispatches.map(item => item.id);
} else {
this.selectedIds = [];
}
},
copySelectedMsgIds() {
const selectedItems = this.dispatches.filter(item => this.selectedIds.includes(item.id));
const msgIds = selectedItems.map(item => `'${item.msg_id}'`).join(',');
// 使用降级方案兼容HTTP环境
const textArea = document.createElement('textarea');
textArea.value = msgIds;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (successful) {
this.showToast(`已复制 ${selectedItems.length} 个消息ID到剪贴板`, 'success');
} else {
this.showToast('复制失败,请重试', 'error');
}
} catch (err) {
document.body.removeChild(textArea);
console.error('复制失败:', err);
this.showToast('复制失败,请重试', 'error');
}
},
showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white z-50 ${
type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
setTimeout(() => {
document.body.removeChild(toast);
}, 300);
}, 2000);
},
showDetail(item) {
this.detailItem = item;
},
showBatchUpdateModal() {
this.showBatchUpdate = true;
this.batchUpdate = {
request_status: null,
business_status: null,
retry_count: null,
target_service: null,
};
},
async executeBatchUpdate() {
this.updating = true;
this.error = null;
try {
const selectedRoute = this.batchUpdate.target_service !== null
? this.serviceRoutes.find(route => String(route.id) === String(this.batchUpdate.target_service))
: null;
const updates = this.selectedIds.map(id => {
const update = { id: String(id) };
if (this.batchUpdate.request_status !== null) update.request_status = String(this.batchUpdate.request_status);
if (this.batchUpdate.business_status !== null) update.business_status = String(this.batchUpdate.business_status);
if (this.batchUpdate.retry_count !== null) update.retry_count = String(this.batchUpdate.retry_count);
if (this.batchUpdate.target_service !== null) {
update.target_service = String(this.batchUpdate.target_service);
if (selectedRoute && selectedRoute.country_code) {
update.country_code = selectedRoute.country_code;
}
}
return update;
});
const response = await axios.post('/api/message-dispatch/batch-update', { updates });
alert(`更新完成!成功: ${response.data.data.summary.success}, 失败: ${response.data.data.summary.failure}`);
this.showBatchUpdate = false;
this.loadData();
} catch (err) {
this.error = err.response?.data?.message || err.message;
} finally {
this.updating = false;
}
},
isUsDomain(domain) {
return domain === 'partner-us.eainc.com';
},
formatJson(json) {
try {
const obj = typeof json === 'string' ? JSON.parse(json) : json;
return JSON.stringify(obj, null, 2);
} catch {
return json;
}
},
getStatusText(status) {
const map = { 0: '待处理', 1: '成功', 2: '失败', 3: '重试中', 4: '超时', 5: '已取消' };
return map[status] || '未知';
},
getStatusClass(status) {
const map = {
0: 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800',
1: 'px-2 py-1 text-xs rounded-full bg-green-100 text-green-800',
2: 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800',
3: 'px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800',
4: 'px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-800',
5: 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800',
};
return map[status] || 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800';
},
getBusinessStatusText(status) {
const map = { 0: '待处理', 1: '成功', 2: '失败' };
return map[status] || '未知';
},
getBusinessStatusClass(status) {
const map = {
0: 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800',
1: 'px-2 py-1 text-xs rounded-full bg-green-100 text-green-800',
2: 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800',
};
return map[status] || 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800';
},
getConsumerStatusText(status) {
const map = {
0: '待处理',
1: '处理成功',
2: '处理中',
3: '处理失败',
4: '等待重试',
5: '处理忽略',
};
return map[status] ?? '未知';
},
getConsumerStatusClass(status) {
const map = {
0: 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800',
1: 'px-2 py-1 text-xs rounded-full bg-green-100 text-green-800',
2: 'px-2 py-1 text-xs rounded-full bg-blue-100 text-blue-800',
3: 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800',
4: 'px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800',
5: 'px-2 py-1 text-xs rounded-full bg-purple-100 text-purple-800',
};
return map[status] ?? 'px-2 py-1 text-xs rounded-full bg-gray-100 text-gray-800';
},
},
};
</script>