feat 商户通知任务列表

This commit is contained in:
DaxPay
2024-08-06 15:34:30 +08:00
parent dcb4fbbb57
commit f1b04219cb
12 changed files with 1172 additions and 31 deletions

View File

@@ -1,6 +1,5 @@
import { reactive, toRefs, unref } from "vue";
import { reactive, toRefs, unref } from 'vue'
import { FormEditType } from '@/enums/formTypeEnum'
import { setIcon } from 'vxe-table'
export default function () {
const model = reactive({

View File

@@ -15,35 +15,41 @@
</template>
</vxe-toolbar>
<div class="h-65vh">
<vxe-table height="auto" ey-field="id" ref="xTable" :data="pagination.records" :loading="loading">
<vxe-column type="seq" width="60" />
<vxe-column field="code" title="字典项编码" />
<vxe-column field="name" title="字典项名称" />
<vxe-column field="enable" title="启用状态">
<template #default="{ row }">
<a-tag v-if="row.enable" color="green">启用</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
</vxe-column>
<vxe-column field="sortNo" title="排序" />
<vxe-column field="remark" title="备注" />
<vxe-column field="createTime" title="创建时间" />
<vxe-column fixed="right" width="150" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
<a-divider type="vertical" />
<span>
<a href="javascript:" @click="edit(row)">编辑</a>
</span>
<a-divider type="vertical" />
<a-popconfirm title="是否删除" @confirm="remove(row)" okText="是" cancelText="否">
<a href="javascript:" style="color: red">删除</a>
</a-popconfirm>
</template>
</vxe-column>
</vxe-table>
<vxe-table
height="auto"
ey-field="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
>
<vxe-column type="seq" width="60" />
<vxe-column field="code" title="字典项编码" />
<vxe-column field="name" title="字典项名称" />
<vxe-column field="enable" title="启用状态">
<template #default="{ row }">
<a-tag v-if="row.enable" color="green">启用</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
</vxe-column>
<vxe-column field="sortNo" title="排序" />
<vxe-column field="remark" title="备注" />
<vxe-column field="createTime" title="创建时间" />
<vxe-column fixed="right" width="150" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
<a-divider type="vertical" />
<span>
<a href="javascript:" @click="edit(row)">编辑</a>
</span>
<a-divider type="vertical" />
<a-popconfirm title="是否删除" @confirm="remove(row)" okText="是" cancelText="否">
<a href="javascript:" style="color: red">删除</a>
</a-popconfirm>
</template>
</vxe-column>
</vxe-table>
</div>
<vxe-pager
size="medium"

View File

@@ -0,0 +1,71 @@
<template>
<basic-modal
title="查看记录"
v-bind="$attrs"
:loading="confirmLoading"
:width="modalWidth"
:visible="visible"
:mask-closable="showable"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-descriptions bordered title="" :column="{ md: 1, sm: 1, xs: 1 }">
<a-descriptions-item label="主键">
{{ info.id }}
</a-descriptions-item>
<a-descriptions-item label="请求次数">
{{ info.reqCount || '空' }}
</a-descriptions-item>
<a-descriptions-item label="发送类型">
<a-tag>{{ dictConvert('SendType', info.sendType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="是否发送成功">
<a-tag v-if="info.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</a-descriptions-item>
<a-descriptions-item label="错误信息">
{{ info.errorMsg }}
</a-descriptions-item>
<a-descriptions-item label="发送时间">
{{ info.createTime }}
</a-descriptions-item>
</a-descriptions>
</a-spin>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
</a-space>
</template>
</basic-modal>
</template>
<script lang="ts" setup>
import { BasicModal } from '@/components/Modal'
import { CallbackRecord, getRecord } from './CallbackTask.api'
import { useDict } from '@/hooks/bootx/useDict'
import useFormEdit from '@/hooks/bootx/useFormEdit'
import { ref } from 'vue'
const { handleCancel, modalWidth, confirmLoading, visible, showable } = useFormEdit()
const { dictConvert } = useDict()
let info = ref<CallbackRecord>({})
/**
* 入口
*/
async function init(record: CallbackRecord) {
info.value = record
visible.value = true
confirmLoading.value = true
await getRecord(record.id).then(({ data }) => {
info.value = data
})
confirmLoading.value = false
}
// 获取信息
defineExpose({
init,
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,114 @@
<template>
<basic-drawer
forceRender
v-bind="$attrs"
title="通知明细列表"
width="60%"
:visible="visible"
@close="visible = false"
>
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
row-id="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="sortChange"
>
<vxe-column type="seq" width="60" />
<vxe-column field="reqCount" title="请求次数">
<template #default="{ row }">
<a-tag color="green">{{ row.reqCount || '空' }}</a-tag>
</template>
</vxe-column>
<vxe-column field="channel" title="是否成功">
<template #default="{ row }">
<a-tag v-if="row.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</template>
</vxe-column>
<vxe-column field="sendType" title="发送类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ClientNoticeSendType', row.sendType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="errorMsg" title="错误信息" max-width="200" />
<vxe-column field="createTime" title="发送时间" />
<vxe-column fixed="right" width="60" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
<CallbackRecordInfo ref="callbackRecordInfo" />
</basic-drawer>
</template>
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import useTablePage from '@/hooks/bootx/useTablePage'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue'
import { useDict } from '@/hooks/bootx/useDict'
import { pageRecord, CallbackRecord } from './CallbackTask.api'
import CallbackRecordInfo from './CallbackRecordInfo.vue'
// 使用hooks
const { handleTableChange, pageQueryResHandel, sortChange, pagination, sortParam, loading } =
useTablePage(queryPage)
const { dictConvert } = useDict()
let visible = ref(false)
let task = ref<CallbackRecord>({})
let callbackRecordInfo = ref<any>()
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
nextTick(() => {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
})
/**
* 入口
*/
function init(record: CallbackRecord) {
visible.value = true
task.value = record
queryPage()
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
pageRecord({
taskId: task.value.id,
...sortParam,
}).then(({ data }) => {
pageQueryResHandel(data)
})
}
/**
* 查看
*/
function show(record) {
callbackRecordInfo.value.init(record)
}
defineExpose({
init,
})
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,100 @@
import { defHttp } from '@/utils/http/axios'
import { PageResult, Result } from '#/axios'
import { MchEntity } from '#/web'
/**
* 重新发送消息通知
*/
export function send(taskId) {
return defHttp.post<Result>({
url: '/merchant/notice/callback/send',
method: 'post',
params: { taskId },
})
}
/**
* 任务分页
*/
export function page(params) {
return defHttp.get<Result<PageResult<CallbackTask[]>>>({
url: '/merchant/notice/callback/task/page',
method: 'get',
params,
})
}
/**
* 获取单条
*/
export function get(id) {
return defHttp.get<Result<CallbackTask>>({
url: '/merchant/notice/callback/task/findById',
params: { id },
})
}
/**
* 记录分页
*/
export function pageRecord(params) {
return defHttp.get<Result<PageResult<CallbackRecord[]>>>({
url: '/merchant/notice/callback/record/page',
method: 'get',
params,
})
}
/**
* 记录单条
*/
export function getRecord(id) {
return defHttp.get<Result<CallbackRecord>>({
url: '/merchant/notice/callback/record/findById',
params: { id },
})
}
/**
* 消息通知任务
*/
export interface CallbackTask extends MchEntity {
// 本地交易ID
tradeId?: string
// 本地交易号
tradeNo?: string
// 交易类型
tradeType?: string
// 消息内容
content?: string
// 是否发送成功
success?: boolean
// 下次发送时间
nextTime?: string
// 发送次数
sendCount?: number
// 延迟重试次数
delayCount?: number
// 发送地址
url?: string
// 最后发送时间
latestTime?: string
}
/**
* 消息通知记录
*/
export interface CallbackRecord extends MchEntity {
// 任务ID
taskId?: string
// 请求次数
reqCount?: number
// 发送类型
sendType?: boolean
// 发送是否成功
success?: boolean
// 错误编码
errorCode?: string
// 错误信息
errorMsg?: string
}

View File

@@ -0,0 +1,84 @@
<template>
<basic-modal
title="查看通知任务"
v-bind="$attrs"
:loading="confirmLoading"
:width="1200"
:visible="visible"
:mask-closable="showable"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-descriptions title="" bordered>
<a-descriptions-item label="本地订单号" :span="2">
<a-tag>{{ task.tradeNo }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="交易类型" :span="2">
<a-tag>{{ dictConvert('tradeType', task.tradeType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="是否发送成功" :span="2">
<a-tag v-if="task.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</a-descriptions-item>
<a-descriptions-item label="发送次数" :span="2">
{{ task.sendCount }}
</a-descriptions-item>
<a-descriptions-item label="下次发送时间" :span="2">
{{ task.nextTime }}
</a-descriptions-item>
<a-descriptions-item label="延迟重试次数" :span="2">
{{ task.delayCount }}
</a-descriptions-item>
<a-descriptions-item label="发送地址" :span="2">
{{ task.url }}
</a-descriptions-item>
<a-descriptions-item label="最后发送时间" :span="2">
{{ task.latestTime }}
</a-descriptions-item>
<a-descriptions-item label="创建时间" :span="2">
{{ task.createTime }}
</a-descriptions-item>
<a-descriptions-item label="消息内容" :span="4">
<json-preview :data="JSON.parse(task.content || '{}')" />
</a-descriptions-item>
</a-descriptions>
</a-spin>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
</a-space>
</template>
</basic-modal>
</template>
<script lang="ts" setup>
import useFormEdit from '@/hooks/bootx/useFormEdit'
import { get, CallbackTask } from './CallbackTask.api'
import { BasicModal } from '@/components/Modal'
import { useDict } from '@/hooks/bootx/useDict'
import JsonPreview from '@/components/CodeEditor/src/json-preview/JsonPreview.vue'
import { ref } from 'vue'
const { handleCancel, confirmLoading, visible, showable } = useFormEdit()
const { dictConvert } = useDict()
let task = ref<CallbackTask>({})
/**
*
*/
async function init(record: CallbackTask) {
task.value = record
visible.value = true
confirmLoading.value = true
await get(record.id).then(({ data }) => {
task.value = data
})
confirmLoading.value = false
}
// 获取信息
defineExpose({
init,
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,199 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query
:query-params="model.queryParam"
:fields="fields"
@query="queryPage"
@reset="resetQueryParams"
/>
</div>
<div class="m-3 p-3 bg-white">
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
row-id="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="sortChange"
>
<vxe-column type="seq" title="序号" width="60" />
<vxe-column field="orderId" title="本地交易号" :min-width="230">
<template #default="{ row }">
<a-link @click="showOrder(row)">
{{ row.tradeNo }}
</a-link>
</template>
</vxe-column>
<vxe-column field="noticeType" title="交易类型" :min-width="80">
<template #default="{ row }">
<a-tag>{{ dictConvert('TradeType', row.tradeType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="success" title="发送成功" sortable :min-width="80">
<template #default="{ row }">
<a-tag v-if="row.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</template>
</vxe-column>
<vxe-column field="sendCount" title="发送次数" sortable :min-width="70" />
<vxe-column field="nextTime" title="下次发送时间" sortable :min-width="170" />
<vxe-column field="delayCount" title="延迟重试次数" sortable :min-width="70" />
<vxe-column field="latestTime" title="最后发送时间" sortable :min-width="170" />
<vxe-column field="createTime" title="创建时间" sortable :min-width="170" />
<vxe-column fixed="right" width="180" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
<a-divider type="vertical" />
<a-link @click="showRecord(row)">记录列表</a-link>
<a-divider type="vertical" />
<a-link @click="resetSend(row)">重发</a-link>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
</div>
<CallbackTaskInfo ref="noticeTaskInfo" />
<PayOrderInfo ref="payOrderInfo" />
<TransferOrderInfo ref="transferOrderInfo" />
<CallbackRecordList ref="noticeRecordList" />
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { CallbackTask, page, send } from './CallbackTask.api'
import useTablePage from '@/hooks/bootx/useTablePage'
import BQuery from '@/components/Bootx/Query/BQuery.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { LIST, QueryField, STRING } from '@/components/Bootx/Query/Query'
import { useDict } from '@/hooks/bootx/useDict'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import ALink from '@/components/Link/Link.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
import CallbackTaskInfo from './CallbackTaskInfo.vue'
import PayOrderInfo from '../../order/pay/PayOrderInfo.vue'
import TransferOrderInfo from '../../order/transfer/TransferOrderInfo.vue'
import CallbackRecordList from './CallbackRecordList.vue'
// 使用hooks
const {
handleTableChange,
pageQueryResHandel,
sortChange,
resetQueryParams,
pagination,
pages,
sortParam,
model,
loading,
} = useTablePage(queryPage)
const { createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
let noticeTypeList = ref<LabeledValue[]>([])
// 查询条件
const fields = computed(() => {
return [
{ field: 'tradeNo', type: STRING, name: '本地交易号', placeholder: '请输入完整本地交易号' },
{
field: 'noticeType',
type: LIST,
name: '消息类型',
placeholder: '请选择消息类型',
selectList: noticeTypeList.value,
},
] as QueryField[]
})
const noticeRecordList = ref<any>()
const noticeTaskInfo = ref<any>()
const payOrderInfo = ref<any>()
const refundOrderInfo = ref<any>()
const transferOrderInfo = ref<any>()
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
onMounted(() => {
vxeBind()
init()
queryPage()
})
function vxeBind() {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
}
/**
* 初始化
*/
async function init() {
noticeTypeList.value = await dictDropDown('ClientNoticeType')
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
...sortParam,
}).then(({ data }) => {
pageQueryResHandel(data)
})
}
/**
* 重新发送消息
*/
function resetSend(record: CallbackTask) {
createConfirm({
iconType: 'warning',
title: '警告',
content: '是否重新发送通知消息?',
onOk: () => {
loading.value = true
send(record.id).then(() => {
createMessage.success('请求已发送')
queryPage()
})
},
})
}
/**
* 查看
*/
function show(record) {
noticeTaskInfo.value.init(record)
}
/**
* 查看记录列表
*/
function showRecord(record) {
noticeRecordList.value.init(record)
}
/**
* 查看订单信息
*/
function showOrder(record: CallbackTask) {
if (record.tradeType === 'pay') {
payOrderInfo.value.init(record.tradeNo)
} else if (record.tradeType === 'refund') {
refundOrderInfo.value.init(record.tradeNo)
} else {
transferOrderInfo.value.init(record.tradeNo)
}
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,71 @@
<template>
<basic-modal
title="查看记录"
v-bind="$attrs"
:loading="confirmLoading"
:width="modalWidth"
:visible="visible"
:mask-closable="showable"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-descriptions bordered title="" :column="{ md: 1, sm: 1, xs: 1 }">
<a-descriptions-item label="主键">
{{ info.id }}
</a-descriptions-item>
<a-descriptions-item label="请求次数">
{{ info.reqCount || '空' }}
</a-descriptions-item>
<a-descriptions-item label="发送类型">
<a-tag>{{ dictConvert('SendType', info.sendType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="是否发送成功">
<a-tag v-if="info.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</a-descriptions-item>
<a-descriptions-item label="错误信息">
{{ info.errorMsg }}
</a-descriptions-item>
<a-descriptions-item label="发送时间">
{{ info.createTime }}
</a-descriptions-item>
</a-descriptions>
</a-spin>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
</a-space>
</template>
</basic-modal>
</template>
<script lang="ts" setup>
import { BasicModal } from '@/components/Modal'
import { NotifyRecord, getRecord } from './NotifyTask.api'
import { useDict } from '@/hooks/bootx/useDict'
import useFormEdit from '@/hooks/bootx/useFormEdit'
import { ref } from 'vue'
const { handleCancel, modalWidth, confirmLoading, visible, showable } = useFormEdit()
const { dictConvert } = useDict()
let info = ref<NotifyRecord>({})
/**
* 入口
*/
async function init(record: NotifyRecord) {
info.value = record
visible.value = true
confirmLoading.value = true
await getRecord(record.id).then(({ data }) => {
info.value = data
})
confirmLoading.value = false
}
// 获取信息
defineExpose({
init,
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,114 @@
<template>
<basic-drawer
forceRender
v-bind="$attrs"
title="通知明细列表"
width="60%"
:visible="visible"
@close="visible = false"
>
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
row-id="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="sortChange"
>
<vxe-column type="seq" width="60" />
<vxe-column field="reqCount" title="请求次数">
<template #default="{ row }">
<a-tag color="green">{{ row.reqCount || '空' }}</a-tag>
</template>
</vxe-column>
<vxe-column field="channel" title="是否成功">
<template #default="{ row }">
<a-tag v-if="row.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</template>
</vxe-column>
<vxe-column field="sendType" title="发送类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ClientNoticeSendType', row.sendType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="errorMsg" title="错误信息" max-width="200" />
<vxe-column field="createTime" title="发送时间" />
<vxe-column fixed="right" width="60" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
<NotifyRecordInfo ref="notifyRecordInfo" />
</basic-drawer>
</template>
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import useTablePage from '@/hooks/bootx/useTablePage'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue'
import { useDict } from '@/hooks/bootx/useDict'
import { pageRecord, NotifyRecord } from './NotifyTask.api'
import NotifyRecordInfo from './NotifyRecordInfo.vue'
// 使用hooks
const { handleTableChange, pageQueryResHandel, sortChange, pagination, sortParam, loading } =
useTablePage(queryPage)
const { dictConvert } = useDict()
let visible = ref(false)
let task = ref<NotifyRecord>({})
let notifyRecordInfo = ref<any>()
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
nextTick(() => {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
})
/**
* 入口
*/
function init(record: NotifyRecord) {
visible.value = true
task.value = record
queryPage()
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
pageRecord({
taskId: task.value.id,
...sortParam,
}).then(({ data }) => {
pageQueryResHandel(data)
})
}
/**
* 查看
*/
function show(record) {
notifyRecordInfo.value.init(record)
}
defineExpose({
init,
})
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,100 @@
import { defHttp } from '@/utils/http/axios'
import { PageResult, Result } from '#/axios'
import { MchEntity } from '#/web'
/**
* 重新发送消息通知
*/
export function send(taskId) {
return defHttp.post<Result>({
url: '/merchant/notice/notify/send',
method: 'post',
params: { taskId },
})
}
/**
* 任务分页
*/
export function page(params) {
return defHttp.get<Result<PageResult<NotifyTask[]>>>({
url: '/merchant/notice/notify/task/page',
method: 'get',
params,
})
}
/**
* 获取单条
*/
export function get(id) {
return defHttp.get<Result<NotifyTask>>({
url: '/merchant/notice/notify/task/findById',
params: { id },
})
}
/**
* 记录分页
*/
export function pageRecord(params) {
return defHttp.get<Result<PageResult<NotifyRecord[]>>>({
url: '/merchant/notice/notify/record/page',
method: 'get',
params,
})
}
/**
* 记录单条
*/
export function getRecord(id) {
return defHttp.get<Result<NotifyRecord>>({
url: '/merchant/notice/notify/record/findById',
params: { id },
})
}
/**
* 消息通知任务
*/
export interface NotifyTask extends MchEntity {
// 本地交易ID
tradeId?: string
// 本地交易号
tradeNo?: string
// 交易类型
tradeType?: string
// 消息内容
content?: string
// 是否发送成功
success?: boolean
// 发送次数
nextTime?: string
// 发送次数
sendCount?: number
// 延迟重试次数
delayCount?: number
// 发送地址
url?: string
// 最后发送时间
latestTime?: string
}
/**
* 消息通知记录
*/
export interface NotifyRecord extends MchEntity {
// 任务ID
taskId?: string
// 请求次数
reqCount?: number
// 发送类型
sendType?: boolean
// 发送是否成功
success?: boolean
// 错误编码
errorCode?: string
// 错误信息
errorMsg?: string
}

View File

@@ -0,0 +1,84 @@
<template>
<basic-modal
title="查看通知任务"
v-bind="$attrs"
:loading="confirmLoading"
:width="1200"
:visible="visible"
:mask-closable="showable"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-descriptions title="" bordered>
<a-descriptions-item label="本地订单号" :span="2">
<a-tag>{{ task.tradeNo }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="交易类型" :span="2">
<a-tag>{{ dictConvert('tradeType', task.tradeType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="是否发送成功" :span="2">
<a-tag v-if="task.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</a-descriptions-item>
<a-descriptions-item label="发送次数" :span="2">
{{ task.sendCount }}
</a-descriptions-item>
<a-descriptions-item label="下次发送时间" :span="2">
{{ task.nextTime }}
</a-descriptions-item>
<a-descriptions-item label="延迟重试次数" :span="2">
{{ task.delayCount }}
</a-descriptions-item>
<a-descriptions-item label="发送地址" :span="2">
{{ task.url }}
</a-descriptions-item>
<a-descriptions-item label="最后发送时间" :span="2">
{{ task.latestTime }}
</a-descriptions-item>
<a-descriptions-item label="创建时间" :span="2">
{{ task.createTime }}
</a-descriptions-item>
<a-descriptions-item label="消息内容" :span="4">
<json-preview :data="JSON.parse(task.content || '{}')" />
</a-descriptions-item>
</a-descriptions>
</a-spin>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
</a-space>
</template>
</basic-modal>
</template>
<script lang="ts" setup>
import useFormEdit from '@/hooks/bootx/useFormEdit'
import { get, NotifyTask } from './NotifyTask.api'
import { BasicModal } from '@/components/Modal'
import { useDict } from '@/hooks/bootx/useDict'
import JsonPreview from '@/components/CodeEditor/src/json-preview/JsonPreview.vue'
import { ref } from 'vue'
const { handleCancel, confirmLoading, visible, showable } = useFormEdit()
const { dictConvert } = useDict()
let task = ref<NotifyTask>({})
/**
*
*/
async function init(record: NotifyTask) {
task.value = record
visible.value = true
confirmLoading.value = true
await get(record.id).then(({ data }) => {
task.value = data
})
confirmLoading.value = false
}
// 获取信息
defineExpose({
init,
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,199 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query
:query-params="model.queryParam"
:fields="fields"
@query="queryPage"
@reset="resetQueryParams"
/>
</div>
<div class="m-3 p-3 bg-white">
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
row-id="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="sortChange"
>
<vxe-column type="seq" title="序号" width="60" />
<vxe-column field="orderId" title="本地交易号" :min-width="230">
<template #default="{ row }">
<a-link @click="showOrder(row)">
{{ row.tradeNo }}
</a-link>
</template>
</vxe-column>
<vxe-column field="noticeType" title="交易类型" :min-width="80">
<template #default="{ row }">
<a-tag>{{ dictConvert('TradeType', row.tradeType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="success" title="发送成功" sortable :min-width="80">
<template #default="{ row }">
<a-tag v-if="row.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</template>
</vxe-column>
<vxe-column field="sendCount" title="发送次数" sortable :min-width="70" />
<vxe-column field="nextTime" title="下次发送时间" sortable :min-width="170" />
<vxe-column field="delayCount" title="延迟重试次数" sortable :min-width="70" />
<vxe-column field="latestTime" title="最后发送时间" sortable :min-width="170" />
<vxe-column field="createTime" title="创建时间" sortable :min-width="170" />
<vxe-column fixed="right" width="180" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
<a-divider type="vertical" />
<a-link @click="showRecord(row)">记录列表</a-link>
<a-divider type="vertical" />
<a-link @click="resetSend(row)">重发</a-link>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
</div>
<NotifyTaskInfo ref="noticeTaskInfo" />
<PayOrderInfo ref="payOrderInfo" />
<TransferOrderInfo ref="transferOrderInfo" />
<NotifyRecordList ref="noticeRecordList" />
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { NotifyTask, page, send } from './NotifyTask.api'
import useTablePage from '@/hooks/bootx/useTablePage'
import BQuery from '@/components/Bootx/Query/BQuery.vue'
import { useMessage } from '@/hooks/web/useMessage'
import { LIST, QueryField, STRING } from '@/components/Bootx/Query/Query'
import { useDict } from '@/hooks/bootx/useDict'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import ALink from '@/components/Link/Link.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
import NotifyTaskInfo from './NotifyTaskInfo.vue'
import PayOrderInfo from '../../order/pay/PayOrderInfo.vue'
import TransferOrderInfo from '../../order/transfer/TransferOrderInfo.vue'
import NotifyRecordList from './NotifyRecordList.vue'
// 使用hooks
const {
handleTableChange,
pageQueryResHandel,
sortChange,
resetQueryParams,
pagination,
pages,
sortParam,
model,
loading,
} = useTablePage(queryPage)
const { createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
let noticeTypeList = ref<LabeledValue[]>([])
// 查询条件
const fields = computed(() => {
return [
{ field: 'tradeNo', type: STRING, name: '本地交易号', placeholder: '请输入完整本地交易号' },
{
field: 'noticeType',
type: LIST,
name: '消息类型',
placeholder: '请选择消息类型',
selectList: noticeTypeList.value,
},
] as QueryField[]
})
const noticeRecordList = ref<any>()
const noticeTaskInfo = ref<any>()
const payOrderInfo = ref<any>()
const refundOrderInfo = ref<any>()
const transferOrderInfo = ref<any>()
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
onMounted(() => {
vxeBind()
init()
queryPage()
})
function vxeBind() {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
}
/**
* 初始化
*/
async function init() {
noticeTypeList.value = await dictDropDown('ClientNoticeType')
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
...sortParam,
}).then(({ data }) => {
pageQueryResHandel(data)
})
}
/**
* 重新发送消息
*/
function resetSend(record: NotifyTask) {
createConfirm({
iconType: 'warning',
title: '警告',
content: '是否重新发送通知消息?',
onOk: () => {
loading.value = true
send(record.id).then(() => {
createMessage.success('请求已发送')
queryPage()
})
},
})
}
/**
* 查看
*/
function show(record) {
noticeTaskInfo.value.init(record)
}
/**
* 查看记录列表
*/
function showRecord(record) {
noticeRecordList.value.init(record)
}
/**
* 查看订单信息
*/
function showOrder(record: NotifyTask) {
if (record.tradeType === 'pay') {
payOrderInfo.value.init(record.tradeNo)
} else if (record.tradeType === 'refund') {
refundOrderInfo.value.init(record.tradeNo)
} else {
transferOrderInfo.value.init(record.tradeNo)
}
}
</script>
<style lang="less" scoped></style>