LAOP-web/src/views/wxMessage/index.vue
2026-09-18 16:50:27 +08:00

351 lines
11 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="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>