This commit is contained in:
2026-10-20 23:34:11 +08:00
parent f155976f52
commit 13e4aa56ec
321 changed files with 6756 additions and 2110 deletions
@@ -1,4 +1,5 @@
<script setup lang="ts">
import '@/styles/adminWorkbench.css'
import '@/styles/adminDensity.css'
import { onMounted, ref, watch } from 'vue'
import { Plus } from '@lucide/vue'
@@ -18,18 +19,18 @@ async function load(){loading.value=true;error.value=null;try{const keyword=Stri
async function loadTemplates(){if(!access.can('platform:notification:create'))return;try{const data=await listTemplates({page:1,size:200,status:'ACTIVE'});templates.value=data.records}catch(e){ElMessage.error(getApiErrorMessage(e,'可用模板加载失败'))}}
async function openDetail(row:NotificationTask){const request=++detailRequest;detailRow.value=row;detailOpen.value=true;detailLoading.value=true;detailError.value=null;detail.value=null;try{const result=await getTask(row.id);if(request===detailRequest&&detailOpen.value)detail.value=result}catch(e){if(request===detailRequest&&detailOpen.value)detailError.value=getApiErrorDetails(e,'消息任务详情加载失败')}finally{if(request===detailRequest)detailLoading.value=false}}
function search(){page.value===1?void load():page.value=1}
async function action(row:NotificationTask,type:'cancel'|'retry'){try{await ElMessageBox.confirm(type==='cancel'?'确定取消该消息任务吗?':'确定重试该消息任务吗?',`${type==='cancel'?'取消任务':'重试任务'} · ${row.taskNo || '—'}`,{type:'warning',customClass:'admin-density-confirm'});actionId.value=row.id;if(type==='cancel')await cancelTask(row.id);else await retryTask(row.id);ElMessage.success(type==='cancel'?'任务已取消':'任务已提交重试');await load()}catch(e){if(e!=='cancel'&&e!=='close')ElMessage.error(getApiErrorMessage(e,type==='cancel'?'取消失败':'重试失败'))}finally{actionId.value=''}}
async function action(row:NotificationTask,type:'cancel'|'retry'){try{await ElMessageBox.confirm(type==='cancel'?'确定取消该消息任务吗?':'确定重试该消息任务吗?',`${type==='cancel'?'取消任务':'重试任务'} · ${row.taskNo || '—'}`,{type:'warning',customClass: 'admin-density-confirm admin-workbench'});actionId.value=row.id;if(type==='cancel')await cancelTask(row.id);else await retryTask(row.id);ElMessage.success(type==='cancel'?'任务已取消':'任务已提交重试');await load()}catch(e){if(e!=='cancel'&&e!=='close')ElMessage.error(getApiErrorMessage(e,type==='cancel'?'取消失败':'重试失败'))}finally{actionId.value=''}}
watch([page,pageSize],load);onMounted(()=>{void load();void loadTemplates()})
</script>
<template><AdminPage class="admin-density"><ListPageLayout aria-label="消息管理列表"><section><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="!error" v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="notifications-messages-table" :loading="loading" :total="result.total" row-key="id" empty-text="暂无消息任务">
<template><AdminPage class="admin-density admin-workbench"><ListPageLayout aria-label="消息管理列表"><section class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="!error" v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="notifications-messages-table" :loading="loading" :total="result.total" row-key="id" empty-text="暂无消息任务">
<template #toolbar-start><h1>消息管理</h1></template>
<template #filters><QueryToolbar v-model="filters" ariaLabel="消息任务筛选" :fields="queryFields" preference-key="notifications-messages-query" @search="search" /></template>
<template #notices><template v-if="error"><LoadErrorState title="消息任务加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template></template>
<template #toolbar-actions><el-button v-if="access.can('platform:notification:create')" type="primary" :disabled="!templates.length" @click="composerOpen=true"><Plus :size="16"/>新建消息</el-button></template><template #pagination-left><span>共 {{ result.total }} 个任务</span></template><template #actions="{row}"><div class="density-actions row-actions admin-text-actions"><el-button class="density-action" data-tone="neutral" @click="openDetail(row as NotificationTask)">查看</el-button><el-button v-if="access.can('platform:notification:task:cancel')&&['CREATED','SCHEDULED'].includes((row as NotificationTask).status)" class="density-action" data-tone="danger" :loading="actionId===(row as NotificationTask).id" @click="action(row as NotificationTask,'cancel')">取消</el-button><el-button v-if="access.can('platform:notification:task:retry')&&['RETRYING','DEAD'].includes((row as NotificationTask).status)" class="density-action" data-tone="neutral" :loading="actionId===(row as NotificationTask).id" @click="action(row as NotificationTask,'retry')">重试</el-button></div></template><template #category="{row}">{{notificationCategoryLabel((row as NotificationTask).category)}}</template><template #scheduledAt="{row}">{{formatDate((row as NotificationTask).scheduledAt)}}</template><template #sentAt="{row}">{{formatDate((row as NotificationTask).sentAt)}}</template><template #status="{row}"><el-tag>{{statusLabel((row as NotificationTask).status)}}</el-tag></template></DataTable></section></ListPageLayout><MessageComposerDialog v-model="composerOpen" :templates="templates" @success="load"/><AppDrawer class="admin-density admin-density-drawer admin-density-drawer--detail" v-model="detailOpen" :title="`消息任务详情 · ${detail?.taskNo || '—'}`" size="min(820px, 94vw)" :loading="detailLoading" :show-footer="false" destroy-on-close>
<template #toolbar-actions><el-button v-if="access.can('platform:notification:create')" type="primary" :disabled="!templates.length" @click="composerOpen=true"><Plus :size="16"/>新建消息</el-button></template><template #pagination-left><span>共 {{ result.total }} 个任务</span></template><template #actions="{row}"><div class="density-actions row-actions admin-text-actions"><el-button class="density-action" data-tone="neutral" @click="openDetail(row as NotificationTask)">查看</el-button><el-button v-if="access.can('platform:notification:task:cancel')&&['CREATED','SCHEDULED'].includes((row as NotificationTask).status)" class="density-action" data-tone="danger" :loading="actionId===(row as NotificationTask).id" @click="action(row as NotificationTask,'cancel')">取消</el-button><el-button v-if="access.can('platform:notification:task:retry')&&['RETRYING','DEAD'].includes((row as NotificationTask).status)" class="density-action" data-tone="neutral" :loading="actionId===(row as NotificationTask).id" @click="action(row as NotificationTask,'retry')">重试</el-button></div></template><template #category="{row}">{{notificationCategoryLabel((row as NotificationTask).category)}}</template><template #scheduledAt="{row}">{{formatDate((row as NotificationTask).scheduledAt)}}</template><template #sentAt="{row}">{{formatDate((row as NotificationTask).sentAt)}}</template><template #status="{row}"><el-tag>{{statusLabel((row as NotificationTask).status)}}</el-tag></template></DataTable></section></ListPageLayout><MessageComposerDialog v-model="composerOpen" :templates="templates" @success="load"/><AppDrawer class="admin-density admin-density-drawer admin-density-drawer--detail admin-workbench is-drawer" v-model="detailOpen" :title="`消息任务详情 · ${detail?.taskNo || '—'}`" size="min(820px, 94vw)" :loading="detailLoading" :show-footer="false" destroy-on-close>
<LoadErrorState v-if="detailError" title="消息任务详情加载失败" :message="detailError.message" :loading="detailLoading" @retry="detailRow && openDetail(detailRow)"><template #details><ApiErrorMeta :details="detailError" /></template></LoadErrorState>
<div v-else-if="detail" class="density-dialog-sections">
<section class="density-dialog-section" data-tone="summary" aria-label="任务资料">
<section class="density-dialog-section workbench-evidence" data-tone="summary" aria-label="任务资料">
<h3 class="density-dialog-section__title">{{ detail.title || '消息任务' }}</h3>
<table class="density-table task-facts"><tbody>
<tr><th scope="row">任务号</th><td>{{ detail.taskNo || '—' }}</td><th scope="row">状态</th><td>{{ statusLabel(detail.status) }}</td></tr>
@@ -40,13 +41,13 @@ watch([page,pageSize],load);onMounted(()=>{void load();void loadTemplates()})
<el-alert v-if="detail.lastError" title="任务失败原因" type="error" :closable="false" show-icon><div class="message-body">{{ detail.lastError }}</div></el-alert>
<section class="density-dialog-section" data-tone="source" aria-label="收件人投递明细">
<h3 class="density-dialog-section__title">收件人投递明细</h3>
<el-table :data="detail.recipients" row-key="id" empty-text="暂无收件人明细" size="small">
<div class="workbench-table"><el-table :data="detail.recipients" row-key="id" empty-text="暂无收件人明细" size="small">
<el-table-column prop="displayName" label="收件人" min-width="150" show-overflow-tooltip />
<el-table-column label="应用端" width="110"><template #default="{row}">{{notificationAppLabel(row.appCode)}}</template></el-table-column>
<el-table-column label="投递状态" width="120"><template #default="{row}">{{notificationRecipientStatusLabel(row.status)}}</template></el-table-column>
<el-table-column label="送达时间" width="180"><template #default="{row}">{{formatDate(row.deliveredAt)}}</template></el-table-column>
<el-table-column prop="failureReason" label="失败原因" min-width="220"><template #default="{row}"><span class="message-body">{{row.failureReason||'—'}}</span></template></el-table-column>
</el-table>
</el-table></div>
</section>
</div>
</AppDrawer></AdminPage></template>