351 lines
11 KiB
Vue
351 lines
11 KiB
Vue
<template>
|
||
<div class="wx-message-page">
|
||
<!-- 筛选栏 -->
|
||
<el-card shadow="never" class="filter-card">
|
||
<div class="filter-bar">
|
||
<el-input
|
||
v-model="query.userId"
|
||
placeholder="接收人用户 ID"
|
||
clearable
|
||
class="filter-input"
|
||
@keyup.enter="handleSearch"
|
||
@clear="handleSearch"
|
||
/>
|
||
<el-select
|
||
v-model="query.bizType"
|
||
placeholder="业务类型"
|
||
clearable
|
||
class="filter-select"
|
||
@change="handleSearch"
|
||
>
|
||
<el-option
|
||
v-for="item in BIZ_TYPE_OPTIONS"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
<el-select
|
||
v-model="query.sendStatus"
|
||
placeholder="发送状态"
|
||
clearable
|
||
class="filter-select"
|
||
@change="handleSearch"
|
||
>
|
||
<el-option
|
||
v-for="item in SEND_STATUS_OPTIONS"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
<el-input
|
||
v-model="query.openId"
|
||
placeholder="接收人 openid"
|
||
clearable
|
||
class="filter-input filter-input-openid"
|
||
@keyup.enter="handleSearch"
|
||
@clear="handleSearch"
|
||
/>
|
||
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
||
<el-button :icon="Refresh" @click="handleReset">重置</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 列表 -->
|
||
<el-card shadow="never" class="table-card">
|
||
<el-table v-loading="loading" :data="list" row-key="id" stripe>
|
||
<el-table-column type="expand">
|
||
<template #default="{ row }">
|
||
<div class="expand-body">
|
||
<div class="expand-item">
|
||
<span class="expand-label">模板 ID:</span>
|
||
<span>{{ row.templateId }}</span>
|
||
</div>
|
||
<div v-if="row.skipReason" class="expand-item">
|
||
<span class="expand-label">跳过原因:</span>
|
||
<span>{{ row.skipReason }}</span>
|
||
</div>
|
||
<div v-if="row.errCode || row.errMsg" class="expand-item">
|
||
<span class="expand-label">错误信息:</span>
|
||
<span>{{ row.errCode || '-' }} {{ row.errMsg || '' }}</span>
|
||
</div>
|
||
<div v-if="row.resultStatus" class="expand-item">
|
||
<span class="expand-label">微信送达结果:</span>
|
||
<span>{{ row.resultStatus }}</span>
|
||
</div>
|
||
<div class="expand-item">
|
||
<span class="expand-label">模板参数:</span>
|
||
<pre class="expand-content">{{ formatContent(row.content) }}</pre>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="ID" prop="id" width="80" />
|
||
<el-table-column label="接收用户" prop="userId" width="100" align="center" />
|
||
<el-table-column label="业务类型" width="120" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag effect="plain" size="small">{{ row.bizTypeName }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="业务单据" prop="bizId" width="100" align="center" />
|
||
<el-table-column label="发送状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="sendStatusMeta(row.sendStatus).tag" effect="light" size="small">
|
||
{{ row.sendStatusName }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="接收 openid" prop="openId" min-width="220" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.openId || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="微信 MsgID" prop="msgId" width="160" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.msgId ?? '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="提交时间" width="170">
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.sendTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="送达回调时间" width="170">
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.resultTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="入库时间" prop="createdAt" width="170">
|
||
<template #default="{ row }">
|
||
{{ formatDateTime(row.createdAt) }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination-bar">
|
||
<el-pagination
|
||
v-model:current-page="query.page"
|
||
v-model:page-size="query.pageSize"
|
||
:total="total"
|
||
:page-sizes="[10, 20, 50]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handleSizeChange"
|
||
@current-change="fetchList"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { Search, Refresh } from '@element-plus/icons-vue'
|
||
import { pageWxMessageLog } from '@/api/wxMessage'
|
||
|
||
// ==================== 业务常量(与后端 WxMessageConstants 对齐) ====================
|
||
|
||
/** 发送状态编码 */
|
||
const SEND_STATUS = {
|
||
SKIPPED: 0,
|
||
SUBMITTED: 1,
|
||
DELIVERED: 2,
|
||
FAILED: 3
|
||
}
|
||
|
||
/** 业务类型选项 */
|
||
const BIZ_TYPE_OPTIONS = [
|
||
{ value: 1, label: '任务申请通知' }
|
||
]
|
||
|
||
/** 发送状态选项 */
|
||
const SEND_STATUS_OPTIONS = [
|
||
{ value: SEND_STATUS.SKIPPED, label: '已跳过' },
|
||
{ value: SEND_STATUS.SUBMITTED, label: '已提交' },
|
||
{ value: SEND_STATUS.DELIVERED, label: '送达成功' },
|
||
{ value: SEND_STATUS.FAILED, label: '送达失败' }
|
||
]
|
||
|
||
/** 发送状态 → 标签映射(成功绿 / 失败红 / 等待蓝 / 跳过灰) */
|
||
const SEND_STATUS_META_MAP = {
|
||
[SEND_STATUS.SKIPPED]: { label: '已跳过', tag: 'info' },
|
||
[SEND_STATUS.SUBMITTED]: { label: '已提交', tag: 'primary' },
|
||
[SEND_STATUS.DELIVERED]: { label: '送达成功', tag: 'success' },
|
||
[SEND_STATUS.FAILED]: { label: '送达失败', tag: 'danger' }
|
||
}
|
||
|
||
// ==================== 列表状态 ====================
|
||
|
||
const loading = ref(false)
|
||
const list = ref([])
|
||
const total = ref(0)
|
||
const query = reactive({
|
||
userId: '',
|
||
bizType: null,
|
||
sendStatus: null,
|
||
openId: '',
|
||
page: 1,
|
||
pageSize: 10
|
||
})
|
||
|
||
/**
|
||
* 拉取消息日志分页列表
|
||
*/
|
||
async function fetchList() {
|
||
loading.value = true
|
||
try {
|
||
const res = await pageWxMessageLog({
|
||
userId: query.userId || undefined,
|
||
bizType: query.bizType ?? undefined,
|
||
sendStatus: query.sendStatus ?? undefined,
|
||
openId: query.openId || undefined,
|
||
pageNo: query.page - 1,
|
||
pageSize: query.pageSize
|
||
})
|
||
list.value = res.data.list || []
|
||
total.value = Number(res.data.totalElements || 0)
|
||
} catch {
|
||
// 失败提示已由 axios 拦截器统一处理
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/** 查询:重置到第 1 页再拉取 */
|
||
function handleSearch() {
|
||
query.page = 1
|
||
fetchList()
|
||
}
|
||
|
||
/** 重置筛选条件 */
|
||
function handleReset() {
|
||
query.userId = ''
|
||
query.bizType = null
|
||
query.sendStatus = null
|
||
query.openId = ''
|
||
query.page = 1
|
||
fetchList()
|
||
}
|
||
|
||
/** 页大小变化:回到第 1 页 */
|
||
function handleSizeChange() {
|
||
query.page = 1
|
||
fetchList()
|
||
}
|
||
|
||
// ==================== 展示辅助 ====================
|
||
|
||
/**
|
||
* 发送状态标签元信息
|
||
*/
|
||
function sendStatusMeta(sendStatus) {
|
||
return SEND_STATUS_META_MAP[sendStatus] || { label: '未知', tag: 'info' }
|
||
}
|
||
|
||
/**
|
||
* 时间格式化:ISO 字串 → yyyy-MM-dd HH:mm
|
||
*/
|
||
function formatDateTime(dateTime) {
|
||
if (!dateTime) {
|
||
return '-'
|
||
}
|
||
const str = String(dateTime).replace('T', ' ')
|
||
return str.length > 16 ? str.slice(0, 16) : str
|
||
}
|
||
|
||
/**
|
||
* 模板参数 JSON 美化输出,非法 JSON 原样返回
|
||
*/
|
||
function formatContent(content) {
|
||
if (!content) {
|
||
return '-'
|
||
}
|
||
try {
|
||
return JSON.stringify(JSON.parse(content), null, 2)
|
||
} catch {
|
||
return content
|
||
}
|
||
}
|
||
|
||
// 初始拉取列表
|
||
onMounted(() => {
|
||
fetchList()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.wx-message-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
// ==================== 筛选栏 ====================
|
||
|
||
.filter-card {
|
||
:deep(.el-card__body) {
|
||
padding: 16px 20px;
|
||
}
|
||
}
|
||
|
||
.filter-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.filter-input {
|
||
width: 170px;
|
||
}
|
||
|
||
.filter-input-openid {
|
||
width: 240px;
|
||
}
|
||
|
||
.filter-select {
|
||
width: 130px;
|
||
}
|
||
}
|
||
|
||
// ==================== 列表 ====================
|
||
|
||
.pagination-bar {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
// ==================== 行展开详情 ====================
|
||
|
||
.expand-body {
|
||
padding: 4px 16px 12px 56px;
|
||
}
|
||
|
||
.expand-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 8px;
|
||
font-size: 13px;
|
||
color: #303133;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.expand-label {
|
||
flex-shrink: 0;
|
||
color: #909399;
|
||
}
|
||
|
||
.expand-content {
|
||
margin: 0;
|
||
padding: 10px 14px;
|
||
background-color: #f5f7fa;
|
||
border-radius: 6px;
|
||
font-family: Menlo, Consolas, monospace;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
color: #303133;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
</style>
|