优化
This commit is contained in:
@@ -1,46 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Download, Eye, Plus, Send } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import ContractPendingNotice from '@/modules/notifications/components/ContractPendingNotice.vue'
|
||||
import MessageComposerDialog from '@/modules/notifications/components/MessageComposerDialog.vue'
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const composerOpen = ref(false)
|
||||
const filters = ref<QueryModel>({ type: '', status: '', keyword: '' })
|
||||
const queryFields: QueryField[] = [
|
||||
{ key: 'type', type: 'select', label: '消息类型', placeholder: '全部类型', options: [{ label: '系统通知', value: '系统通知' }, { label: '业务提醒', value: '业务提醒' }, { label: '异常告警', value: '异常告警' }] },
|
||||
{ key: 'status', type: 'select', label: '消息状态', placeholder: '全部状态', options: [{ label: '已发送', value: '已发送' }, { label: '草稿', value: '草稿' }] },
|
||||
{ key: 'keyword', type: 'input', label: '标题或内容', placeholder: '消息标题/内容' },
|
||||
]
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'id', prop: 'id', label: '消息ID', minWidth: 170 },
|
||||
{ key: 'title', prop: 'title', label: '标题', minWidth: 220 },
|
||||
{ key: 'type', prop: 'type', label: '类型', width: 110 },
|
||||
{ key: 'target', prop: 'target', label: '接收对象', minWidth: 150 },
|
||||
{ key: 'sentAt', prop: 'sentAt', label: '发送时间', width: 170 },
|
||||
{ key: 'readSummary', prop: 'readSummary', label: '已读/总数', width: 110 },
|
||||
{ key: 'status', prop: 'status', label: '状态', width: 100 },
|
||||
]
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { Plus, RotateCcw, XCircle } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'; import DataTable,{type DataTableColumn} from '@/shared/components/DataTable.vue'; import ListPageLayout from '@/shared/components/ListPageLayout.vue'; import QueryToolbar,{type QueryField,type QueryModel} from '@/shared/components/QueryToolbar.vue'; import LoadErrorState from '@/shared/components/LoadErrorState.vue'; import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import MessageComposerDialog from '../components/MessageComposerDialog.vue'; import { cancelTask, listTasks, listTemplates, retryTask } from '../api/notificationCenter'; import type { NotificationTask, NotificationTaskStatus, NotificationTemplate } from '../types'; import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/shared/api/http'; import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
const access=useAccessPreviewStore(),page=ref(1),pageSize=ref(20),loading=ref(false),actionId=ref(''),error=ref<ApiErrorDetails|null>(null),composerOpen=ref(false),templates=ref<NotificationTemplate[]>([]),result=ref({records:[] as NotificationTask[],total:0,pages:0}),filters=ref<QueryModel>({category:'',status:'',keyword:''})
|
||||
const queryFields:QueryField[]=[{key:'category',type:'input',label:'业务分类',placeholder:'业务分类'},{key:'status',type:'select',label:'任务状态',placeholder:'全部状态',options:['CREATED','SCHEDULED','DISPATCHING','RETRYING','SENT','DEAD','CANCELLED'].map(value=>({label:statusLabel(value as NotificationTaskStatus),value}))},{key:'keyword',type:'input',label:'任务号或标题',placeholder:'任务号/消息标题'}]
|
||||
const columns:DataTableColumn[]=[{key:'actions',label:'操作',width:180,fixed:'left',slotName:'actions'},{key:'taskNo',prop:'taskNo',label:'任务号',minWidth:190},{key:'title',prop:'title',label:'标题',minWidth:220},{key:'category',prop:'category',label:'业务分类',width:130},{key:'recipientCount',prop:'recipientCount',label:'接收人数',width:100},{key:'scheduledAt',label:'定时发送',width:170,slotName:'scheduledAt'},{key:'sentAt',label:'发送时间',width:170,slotName:'sentAt'},{key:'status',label:'状态',width:110,slotName:'status'}]
|
||||
function statusLabel(v:NotificationTaskStatus){return {CREATED:'已创建',SCHEDULED:'待定时发送',DISPATCHING:'发送中',RETRYING:'重试中',SENT:'已发送',DEAD:'发送失败',CANCELLED:'已取消'}[v]}
|
||||
function formatDate(v:string|null){return v?new Date(v).toLocaleString('zh-CN',{hour12:false}):'—'}
|
||||
async function load(){loading.value=true;error.value=null;try{const keyword=String(filters.value.keyword||'').trim();const data=await listTasks({page:page.value,size:pageSize.value,category:String(filters.value.category||'').trim()||undefined,status:(filters.value.status||undefined) as NotificationTaskStatus|undefined,...(keyword.startsWith('NT')?{taskNo:keyword}:{title:keyword||undefined})});result.value={records:data.records,total:data.total,pages:data.pages}}catch(e){error.value=getApiErrorDetails(e,'消息任务加载失败')}finally{loading.value=false}}
|
||||
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,'可用模板加载失败'))}}
|
||||
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'?'取消任务':'重试任务',{type:'warning'});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><ListPageLayout aria-label="消息管理列表">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" aria-label="消息筛选" ariaLabel="消息筛选" :fields="queryFields" preference-key="notifications-messages-query" /></template>
|
||||
<template #state><ContractPendingNotice /></template>
|
||||
<section class="surface-panel table-panel">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="[]" :columns="columns" preference-key="notifications-messages-table" :total="0" row-key="id" empty-text="接口接入后显示平台消息">
|
||||
<template #toolbar-actions><el-button type="primary" @click="composerOpen = true"><Plus :size="16" />新建消息</el-button><el-button disabled><Download :size="16" />导出Excel</el-button></template>
|
||||
<template #pagination-left><span>共 0 条消息</span></template>
|
||||
<template #actions><div class="table-actions"><el-button link type="primary" disabled><Eye :size="15" />详情</el-button><el-button link type="primary" disabled><Send :size="15" />发送</el-button></div></template>
|
||||
</DataTable>
|
||||
</section>
|
||||
</ListPageLayout><MessageComposerDialog v-model="composerOpen" /></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>.table-actions { display: flex; min-height: 32px; align-items: center; gap: 8px; }.table-actions :deep(.el-button + .el-button) { margin-left: 0; }</style>
|
||||
<template><AdminPage><ListPageLayout aria-label="消息管理列表"><template #toolbar><QueryToolbar v-model="filters" class="surface-panel" ariaLabel="消息任务筛选" :fields="queryFields" preference-key="notifications-messages-query" @search="search" /></template><template v-if="error" #state><LoadErrorState title="消息任务加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template><section v-if="!error" class="surface-panel table-panel"><DataTable 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-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="table-actions"><el-button v-if="access.can('platform:notification:task:cancel')&&['CREATED','SCHEDULED'].includes((row as NotificationTask).status)" link type="danger" :loading="actionId===(row as NotificationTask).id" @click="action(row as NotificationTask,'cancel')"><XCircle :size="15"/>取消</el-button><el-button v-if="access.can('platform:notification:task:retry')&&['RETRYING','DEAD'].includes((row as NotificationTask).status)" link type="primary" :loading="actionId===(row as NotificationTask).id" @click="action(row as NotificationTask,'retry')"><RotateCcw :size="15"/>重试</el-button></div></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"/></AdminPage></template>
|
||||
<style scoped>.table-actions{display:flex;min-height:32px;align-items:center;gap:8px}.table-actions :deep(.el-button+.el-button){margin-left:0}</style>
|
||||
|
||||
@@ -1,37 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Download, FileUp, Pencil, Plus } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import ContractPendingNotice from '@/modules/notifications/components/ContractPendingNotice.vue'
|
||||
import TemplateEditorDialog from '@/modules/notifications/components/TemplateEditorDialog.vue'
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const editorOpen = ref(false)
|
||||
const filters = ref<QueryModel>({ code: '', content: '' })
|
||||
const queryFields: QueryField[] = [{ key: 'code', type: 'input', label: '模板CODE', placeholder: '模板CODE' }, { key: 'content', type: 'input', label: '模板内容', placeholder: '模板内容' }]
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 110, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'code', prop: 'code', label: '模板CODE', minWidth: 190 },
|
||||
{ key: 'title', prop: 'title', label: '模板标题', minWidth: 180 },
|
||||
{ key: 'content', prop: 'content', label: '模板内容', minWidth: 320 },
|
||||
{ key: 'type', prop: 'type', label: '模板类型', width: 110 },
|
||||
]
|
||||
import { onMounted, ref, watch } from 'vue'; import { Pencil, Plus, Power } from '@lucide/vue'; import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'; import DataTable,{type DataTableColumn} from '@/shared/components/DataTable.vue'; import ListPageLayout from '@/shared/components/ListPageLayout.vue'; import QueryToolbar,{type QueryField,type QueryModel} from '@/shared/components/QueryToolbar.vue'; import LoadErrorState from '@/shared/components/LoadErrorState.vue'; import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'; import TemplateEditorDialog from '../components/TemplateEditorDialog.vue'; import { changeTemplateStatus, listTemplates } from '../api/notificationCenter'; import type { NotificationTemplate, TemplateStatus } from '../types'; import { getApiErrorDetails,getApiErrorMessage,type ApiErrorDetails } from '@/shared/api/http'; import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
const access=useAccessPreviewStore(),page=ref(1),pageSize=ref(20),loading=ref(false),actionId=ref(''),error=ref<ApiErrorDetails|null>(null),editorOpen=ref(false),editing=ref<NotificationTemplate|null>(null),result=ref({records:[] as NotificationTemplate[],total:0,pages:0}),filters=ref<QueryModel>({code:'',category:'',title:'',status:''})
|
||||
const queryFields:QueryField[]=[{key:'code',type:'input',label:'模板CODE',placeholder:'模板CODE'},{key:'category',type:'input',label:'业务分类',placeholder:'业务分类'},{key:'title',type:'input',label:'名称或标题',placeholder:'模板名称/标题'},{key:'status',type:'select',label:'状态',placeholder:'全部状态',options:[{label:'草稿',value:'DRAFT'},{label:'已激活',value:'ACTIVE'},{label:'已禁用',value:'DISABLED'}]}]
|
||||
const columns:DataTableColumn[]=[{key:'actions',label:'操作',width:190,fixed:'left',slotName:'actions'},{key:'templateCode',prop:'templateCode',label:'模板CODE',minWidth:190},{key:'templateName',prop:'templateName',label:'模板名称',minWidth:170},{key:'category',prop:'category',label:'业务分类',width:130},{key:'titleTemplate',prop:'titleTemplate',label:'消息标题模板',minWidth:220},{key:'bodyTemplate',prop:'bodyTemplate',label:'正文模板',minWidth:320,showOverflowTooltip:true},{key:'status',label:'状态',width:100,slotName:'status'},{key:'version',prop:'version',label:'版本',width:80}]
|
||||
function statusLabel(v:TemplateStatus){return {DRAFT:'草稿',ACTIVE:'已激活',DISABLED:'已禁用'}[v]}
|
||||
async function load(){loading.value=true;error.value=null;try{const d=await listTemplates({page:page.value,size:pageSize.value,templateCode:String(filters.value.code||'').trim()||undefined,category:String(filters.value.category||'').trim()||undefined,title:String(filters.value.title||'').trim()||undefined,status:(filters.value.status||undefined) as TemplateStatus|undefined});result.value={records:d.records,total:d.total,pages:d.pages}}catch(e){error.value=getApiErrorDetails(e,'模板加载失败')}finally{loading.value=false}}
|
||||
function search(){page.value===1?void load():page.value=1}function create(){editing.value=null;editorOpen.value=true}function edit(row:NotificationTemplate){editing.value=row;editorOpen.value=true}
|
||||
async function changeStatus(row:NotificationTemplate){const target=row.status==='DRAFT'?'ACTIVE':'DISABLED';try{await ElMessageBox.confirm(target==='ACTIVE'?'激活后模板不可再编辑,确定继续吗?':'禁用后首期不可重新启用,确定继续吗?',target==='ACTIVE'?'激活模板':'禁用模板',{type:'warning'});actionId.value=row.id;await changeTemplateStatus(row.id,target,row.version);ElMessage.success(target==='ACTIVE'?'模板已激活':'模板已禁用');await load()}catch(e){if(e!=='cancel'&&e!=='close')ElMessage.error(getApiErrorMessage(e,'模板状态变更失败'))}finally{actionId.value=''}}
|
||||
watch([page,pageSize],load);onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage><ListPageLayout aria-label="消息模板列表">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" aria-label="消息模板筛选" ariaLabel="消息模板筛选" :fields="queryFields" preference-key="notifications-templates-query" /></template>
|
||||
<template #state><ContractPendingNotice /></template>
|
||||
<section class="surface-panel table-panel">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="[]" :columns="columns" preference-key="notifications-templates-table" :total="0" row-key="code" empty-text="接口接入后显示站内消息模板">
|
||||
<template #toolbar-actions><el-button type="primary" @click="editorOpen = true"><Plus :size="16" />新增模板</el-button><el-button disabled><Download :size="16" />导出</el-button><el-button disabled><FileUp :size="16" />导入</el-button></template>
|
||||
<template #pagination-left><span>共 0 个模板</span></template>
|
||||
<template #actions><el-button link type="primary" disabled><Pencil :size="15" />编辑</el-button></template>
|
||||
</DataTable>
|
||||
</section>
|
||||
</ListPageLayout><TemplateEditorDialog v-model="editorOpen" /></AdminPage>
|
||||
</template>
|
||||
<template><AdminPage><ListPageLayout aria-label="消息模板列表"><template #toolbar><QueryToolbar v-model="filters" class="surface-panel" ariaLabel="消息模板筛选" :fields="queryFields" preference-key="notifications-templates-query" @search="search"/></template><template v-if="error" #state><LoadErrorState title="模板加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error"/></template></LoadErrorState></template><section v-if="!error" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="notifications-templates-table" :loading="loading" :total="result.total" row-key="id" empty-text="暂无消息模板"><template #toolbar-actions><el-button v-if="access.can('platform:notification:template:create')" type="primary" @click="create"><Plus :size="16"/>新增模板</el-button></template><template #pagination-left><span>共 {{result.total}} 个模板</span></template><template #actions="{row}"><div class="table-actions"><el-button v-if="access.can('platform:notification:template:edit')&&(row as NotificationTemplate).status==='DRAFT'" link type="primary" @click="edit(row as NotificationTemplate)"><Pencil :size="15"/>编辑</el-button><el-button v-if="access.can('platform:notification:template:status')&&(row as NotificationTemplate).status!=='DISABLED'" link :type="(row as NotificationTemplate).status==='ACTIVE'?'danger':'primary'" :loading="actionId===(row as NotificationTemplate).id" @click="changeStatus(row as NotificationTemplate)"><Power :size="15"/>{{(row as NotificationTemplate).status==='DRAFT'?'激活':'禁用'}}</el-button></div></template><template #status="{row}"><el-tag>{{statusLabel((row as NotificationTemplate).status)}}</el-tag></template></DataTable></section></ListPageLayout><TemplateEditorDialog v-model="editorOpen" :template="editing" @success="load"/></AdminPage></template>
|
||||
<style scoped>.table-actions{display:flex;min-height:32px;align-items:center;gap:8px}.table-actions :deep(.el-button+.el-button){margin-left:0}</style>
|
||||
|
||||
@@ -1,41 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { CheckCheck, Eye, Trash2 } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import ContractPendingNotice from '@/modules/notifications/components/ContractPendingNotice.vue'
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const filters = ref<QueryModel>({ status: '', type: '' })
|
||||
const queryFields: QueryField[] = [
|
||||
{ key: 'status', type: 'select', label: '消息状态', placeholder: '全部状态', options: [{ label: '未读', value: '未读' }, { label: '已读', value: '已读' }] },
|
||||
{ key: 'type', type: 'select', label: '消息类型', placeholder: '全部类型', options: [{ label: '系统通知', value: '系统通知' }, { label: '业务提醒', value: '业务提醒' }, { label: '任务待办', value: '任务待办' }] },
|
||||
]
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 210, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'title', prop: 'title', label: '消息标题', minWidth: 220 },
|
||||
{ key: 'summary', prop: 'summary', label: '内容摘要', minWidth: 320 },
|
||||
{ key: 'type', prop: 'type', label: '类型', width: 110 },
|
||||
{ key: 'sentAt', prop: 'sentAt', label: '发送时间', width: 170 },
|
||||
{ key: 'status', prop: 'status', label: '状态', width: 100 },
|
||||
]
|
||||
import { onMounted, ref, watch } from 'vue'; import { CheckCheck, Eye } from '@lucide/vue'; import { ElMessage } from 'element-plus'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'; import AppDialog from '@/shared/components/AppDialog.vue'; import DataTable,{type DataTableColumn} from '@/shared/components/DataTable.vue'; import ListPageLayout from '@/shared/components/ListPageLayout.vue'; import QueryToolbar,{type QueryField,type QueryModel} from '@/shared/components/QueryToolbar.vue'; import LoadErrorState from '@/shared/components/LoadErrorState.vue'; import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'; import { getInboxDetail,listInbox,markAllInboxRead,markInboxRead } from '../api/notificationCenter'; import type { InboxDetail,InboxItem,InboxStatus } from '../types'; import { getApiErrorDetails,getApiErrorMessage,type ApiErrorDetails } from '@/shared/api/http'
|
||||
const page=ref(1),pageSize=ref(20),loading=ref(false),actionId=ref(''),error=ref<ApiErrorDetails|null>(null),detailOpen=ref(false),detailLoading=ref(false),detail=ref<InboxDetail|null>(null),result=ref({records:[] as InboxItem[],total:0,pages:0}),filters=ref<QueryModel>({status:'',category:''})
|
||||
const queryFields:QueryField[]=[{key:'status',type:'select',label:'消息状态',placeholder:'全部状态',options:[{label:'未读',value:'UNREAD'},{label:'已读',value:'READ'}]},{key:'category',type:'input',label:'业务分类',placeholder:'业务分类'}]
|
||||
const columns:DataTableColumn[]=[{key:'actions',label:'操作',width:210,fixed:'left',slotName:'actions'},{key:'title',prop:'title',label:'消息标题',minWidth:240},{key:'category',prop:'category',label:'业务分类',width:140},{key:'sentAt',label:'发送时间',width:170,slotName:'sentAt'},{key:'status',label:'状态',width:100,slotName:'status'}]
|
||||
function formatDate(v:string|null){return v?new Date(v).toLocaleString('zh-CN',{hour12:false}):'—'}
|
||||
async function load(){loading.value=true;error.value=null;try{const d=await listInbox({page:page.value,size:pageSize.value,status:(filters.value.status||undefined) as InboxStatus|undefined,category:String(filters.value.category||'').trim()||undefined});result.value={records:d.records,total:d.total,pages:d.pages}}catch(e){error.value=getApiErrorDetails(e,'我的消息加载失败')}finally{loading.value=false}}
|
||||
function search(){page.value===1?void load():page.value=1}
|
||||
async function openDetail(row:InboxItem){detailOpen.value=true;detailLoading.value=true;detail.value=null;try{detail.value=await getInboxDetail(row.id);if(detail.value.status==='UNREAD'){detail.value=await markInboxRead(row.id);await load();window.dispatchEvent(new CustomEvent('cc-notification-unread-changed'))}}catch(e){ElMessage.error(getApiErrorMessage(e,'消息详情加载失败'))}finally{detailLoading.value=false}}
|
||||
async function markRead(row:InboxItem){actionId.value=row.id;try{await markInboxRead(row.id);ElMessage.success('已标为已读');await load();window.dispatchEvent(new CustomEvent('cc-notification-unread-changed'))}catch(e){ElMessage.error(getApiErrorMessage(e,'标记已读失败'))}finally{actionId.value=''}}
|
||||
async function markAll(){try{const count=await markAllInboxRead();ElMessage.success(count?`已将 ${count} 条消息标为已读`:'没有需要更新的未读消息');await load();window.dispatchEvent(new CustomEvent('cc-notification-unread-changed'))}catch(e){ElMessage.error(getApiErrorMessage(e,'全部标为已读失败'))}}
|
||||
watch([page,pageSize],load);onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage><ListPageLayout aria-label="我的消息列表">
|
||||
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" aria-label="我的消息筛选" ariaLabel="我的消息筛选" :fields="queryFields" preference-key="notifications-mine-query" /></template>
|
||||
<template #state><ContractPendingNotice /></template>
|
||||
<section class="surface-panel table-panel">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="[]" :columns="columns" preference-key="notifications-mine-table" :total="0" row-key="id" empty-text="接口接入后显示当前员工的消息">
|
||||
<template #toolbar-actions><el-button disabled><CheckCheck :size="16" />全部标为已读</el-button><el-button disabled><Trash2 :size="16" />清空已读</el-button></template>
|
||||
<template #pagination-left><span>共 0 条消息</span></template>
|
||||
<template #actions><div class="table-actions"><el-button link type="primary" disabled><Eye :size="15" />查看</el-button><el-button link type="primary" disabled><CheckCheck :size="15" />标为已读</el-button></div></template>
|
||||
</DataTable>
|
||||
</section>
|
||||
</ListPageLayout></AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>.table-actions { display: flex; min-height: 32px; align-items: center; gap: 8px; }.table-actions :deep(.el-button + .el-button) { margin-left: 0; }</style>
|
||||
<template><AdminPage><ListPageLayout aria-label="我的消息列表"><template #toolbar><QueryToolbar v-model="filters" class="surface-panel" ariaLabel="我的消息筛选" :fields="queryFields" preference-key="notifications-mine-query" @search="search"/></template><template v-if="error" #state><LoadErrorState title="我的消息加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error"/></template></LoadErrorState></template><section v-if="!error" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="notifications-mine-table" :loading="loading" :total="result.total" row-key="id" empty-text="暂无消息"><template #toolbar-actions><el-button @click="markAll"><CheckCheck :size="16"/>全部标为已读</el-button></template><template #pagination-left><span>共 {{result.total}} 条消息</span></template><template #actions="{row}"><div class="table-actions"><el-button link type="primary" @click="openDetail(row as InboxItem)"><Eye :size="15"/>查看</el-button><el-button v-if="(row as InboxItem).status==='UNREAD'" link type="primary" :loading="actionId===(row as InboxItem).id" @click="markRead(row as InboxItem)"><CheckCheck :size="15"/>标为已读</el-button></div></template><template #sentAt="{row}">{{formatDate((row as InboxItem).sentAt)}}</template><template #status="{row}"><el-tag :type="(row as InboxItem).status==='UNREAD'?'danger':'info'">{{(row as InboxItem).status==='UNREAD'?'未读':'已读'}}</el-tag></template></DataTable></section></ListPageLayout><AppDialog v-model="detailOpen" title="消息详情" width="min(680px, calc(100vw - 28px))" :loading="detailLoading" :show-confirm="false"><article v-if="detail" class="message-detail"><header><div><h3>{{detail.title}}</h3><p>{{detail.category}} · {{formatDate(detail.sentAt)}}</p></div><el-tag :type="detail.status==='UNREAD'?'danger':'info'">{{detail.status==='UNREAD'?'未读':'已读'}}</el-tag></header><div class="message-body">{{detail.body}}</div><dl v-if="detail.sourceType||detail.sourceId"><div><dt>来源类型</dt><dd>{{detail.sourceType||'—'}}</dd></div><div><dt>来源标识</dt><dd>{{detail.sourceId||'—'}}</dd></div></dl></article></AppDialog></AdminPage></template>
|
||||
<style scoped>.table-actions{display:flex;min-height:32px;align-items:center;gap:8px}.table-actions :deep(.el-button+.el-button){margin-left:0}.message-detail header{display:flex;justify-content:space-between;gap:16px;padding-bottom:16px;border-bottom:1px solid var(--cc-border)}.message-detail h3{margin:0;font-size:17px}.message-detail p{margin:6px 0 0;color:var(--cc-muted);font-size:12px}.message-body{padding:20px 0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.8}.message-detail dl{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));margin:0;border-top:1px solid var(--cc-border)}.message-detail dl>div{padding:12px}.message-detail dt{color:var(--cc-muted);font-size:12px}.message-detail dd{margin:5px 0 0;overflow-wrap:anywhere}@media(max-width:480px){.message-detail header{flex-direction:column}.message-detail dl{grid-template-columns:1fr}}</style>
|
||||
|
||||
Reference in New Issue
Block a user