From 60ac59c628e9b59a22674f0ac8f0904ed4574b1f Mon Sep 17 00:00:00 2001 From: sunlei Date: Thu, 11 Jun 2026 11:18:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=AB=99=E5=86=85?= =?UTF-8?q?=E4=BF=A1=E9=80=9A=E7=9F=A5=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + apps/web-antdv-next/src/api/core/menu.ts | 1 - apps/web-antdv-next/src/api/system/notice.ts | 37 ++-- .../src/locales/langs/en-US/system.json | 33 +++- .../src/locales/langs/zh-CN/system.json | 33 +++- .../src/views/system/notice/data.ts | 150 ++++----------- .../src/views/system/notice/list.vue | 181 ++++++++++-------- .../src/views/system/notice/modules/form.vue | 87 --------- 8 files changed, 203 insertions(+), 323 deletions(-) delete mode 100644 apps/web-antdv-next/src/views/system/notice/modules/form.vue diff --git a/README.md b/README.md index 9e04106..89f9707 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ pnpm run build:antdv-next 真实环境变量不提交,示例配置以 `.env.example` 为准。 +## 业务页面 + +- 系统管理 / 站内信是日志级通知列表,只展示 API 错误、QQBot 下线、NapCat 离线等后端自动捕获事件;页面提供筛选、处理/重新打开、置顶和删除,不提供人工新增或编辑。 + ## 部署说明 Jenkins 使用 `Jenkinsfile` 执行: diff --git a/apps/web-antdv-next/src/api/core/menu.ts b/apps/web-antdv-next/src/api/core/menu.ts index 4d8c738..2e89f65 100644 --- a/apps/web-antdv-next/src/api/core/menu.ts +++ b/apps/web-antdv-next/src/api/core/menu.ts @@ -70,7 +70,6 @@ const SUPPORTED_ADMIN_MENU_NAMES = new Set([ 'SystemMenuDelete', 'SystemMenuEdit', 'SystemNotice', - 'SystemNoticeCreate', 'SystemNoticeDelete', 'SystemNoticeEdit', 'SystemRole', diff --git a/apps/web-antdv-next/src/api/system/notice.ts b/apps/web-antdv-next/src/api/system/notice.ts index 818b742..3a43a9f 100644 --- a/apps/web-antdv-next/src/api/system/notice.ts +++ b/apps/web-antdv-next/src/api/system/notice.ts @@ -8,37 +8,39 @@ export namespace SystemNoticeApi { content: string; createTime?: string; createdBy?: string; + dedupeKey?: string; + eventType?: string; + firstSeenAt?: string; id: string; isDeleted: boolean; isTop: boolean; + lastSeenAt?: string; level: number; + metadata?: Recordable | string; notifyUsers?: string; + notifyRoleCode?: string; + occurrenceCount?: number; + severity?: 'error' | 'fatal' | 'info' | 'warn'; + source?: string; status: 0 | 1; summary?: string; title: string; updateTime?: string; } - export interface NoticeInput { - content: string; - id?: string; - isTop?: boolean; - level?: number; - notifyUsers?: string; - status?: 0 | 1; - summary?: string; - title: string; - } - export interface NoticeQuery { [key: string]: any; + eventType?: string; isTop?: boolean | number | string; keyword?: string; level?: number | string; notifyUsers?: string; + notifyRoleCode?: string; page?: number; pageNo?: number; pageSize?: number; + severity?: string; + source?: string; status?: 0 | 1 | number | string; } @@ -60,17 +62,6 @@ async function getNoticeDetail(id: string) { ); } -async function createNotice(data: SystemNoticeApi.NoticeInput) { - return requestClient.post('/system/notice/save', data); -} - -async function updateNotice(id: string, data: SystemNoticeApi.NoticeInput) { - return requestClient.post('/system/notice/update', { - ...data, - id, - }); -} - async function deleteNotice(id: string) { return requestClient.delete(`/system/notice/${id}`); } @@ -97,11 +88,9 @@ async function toggleNoticeTop(id: string, isTop: boolean) { } export { - createNotice, deleteNotice, getNoticeDetail, getNoticeList, toggleNoticeStatus, toggleNoticeTop, - updateNotice, }; diff --git a/apps/web-antdv-next/src/locales/langs/en-US/system.json b/apps/web-antdv-next/src/locales/langs/en-US/system.json index b0fd710..9dd74a2 100644 --- a/apps/web-antdv-next/src/locales/langs/en-US/system.json +++ b/apps/web-antdv-next/src/locales/langs/en-US/system.json @@ -112,28 +112,45 @@ "unconfigured": "Loki Not Configured" }, "notice": { + "cancelTop": "Unpin", + "cancelTopSuccess": "Unpinned", "content": "Content", "createTime": "Create Time", "deleteConfirm": "Confirm deleting \"{0}\"?", - "disableSuccess": "Notice disabled", - "enableSuccess": "Notice enabled", + "eventTitle": "Event Title", + "eventType": "Event Type", + "firstSeenAt": "First Seen", + "handleConfirm": "Mark \"{0}\" as handled?", + "handleSuccess": "Marked as handled", "isTop": "Top", "keyword": "Keyword", + "lastSeenAt": "Last Seen", "level": "Level", - "levelText": "Level {level}", + "markHandled": "Handle", + "markTop": "Pin", "name": "Notice", "notifyUsers": "Notify Users", + "occurrenceCount": "Count", + "reopen": "Reopen", + "reopenConfirm": "Reopen \"{0}\"?", + "reopenSuccess": "Reopened", + "severity": "Severity", + "severityError": "Error", + "severityFatal": "Fatal", + "severityInfo": "Info", + "severityWarn": "Warning", + "source": "Source", + "sourceApi": "API", + "sourceQqbot": "QQBot", "status": "Status", + "statusHandled": "Handled", + "statusUnhandled": "Unhandled", "summary": "Summary", "title": "Notices", "top": "Pinned", + "topSuccess": "Pinned", "topNo": "No", "topYes": "Yes", - "topSuccess": "Pinned", - "cancelTopSuccess": "Unpinned", - "toggle": "Toggle", - "toggleStatusConfirm": "Confirm toggling \"{0}\" status?", - "topToggle": "Pin Toggle", "toggleTopConfirm": "Confirm toggling \"{0}\" pin state?" }, "role": { diff --git a/apps/web-antdv-next/src/locales/langs/zh-CN/system.json b/apps/web-antdv-next/src/locales/langs/zh-CN/system.json index 6674f61..d719ba7 100644 --- a/apps/web-antdv-next/src/locales/langs/zh-CN/system.json +++ b/apps/web-antdv-next/src/locales/langs/zh-CN/system.json @@ -114,28 +114,45 @@ "unconfigured": "Loki 未配置" }, "notice": { + "cancelTop": "取消置顶", + "cancelTopSuccess": "取消置顶成功", "content": "内容", "createTime": "创建时间", "deleteConfirm": "确认删除「{0}」吗?", - "disableSuccess": "已停用站内信", - "enableSuccess": "已启用站内信", + "eventTitle": "事件标题", + "eventType": "事件类型", + "firstSeenAt": "首次发生", + "handleConfirm": "确认将「{0}」标记为已处理吗?", + "handleSuccess": "已标记为已处理", "isTop": "置顶", "keyword": "关键字", + "lastSeenAt": "最后发生", "level": "等级", - "levelText": "等级 {level}", + "markHandled": "标记处理", + "markTop": "置顶", "name": "站内信", "notifyUsers": "通知用户", + "occurrenceCount": "次数", + "reopen": "重新打开", + "reopenConfirm": "确认重新打开「{0}」吗?", + "reopenSuccess": "已重新打开", + "severity": "级别", + "severityError": "错误", + "severityFatal": "致命", + "severityInfo": "信息", + "severityWarn": "警告", + "source": "来源", + "sourceApi": "接口", + "sourceQqbot": "QQBot", "status": "状态", + "statusHandled": "已处理", + "statusUnhandled": "未处理", "summary": "摘要", "title": "站内信管理", "top": "置顶", + "topSuccess": "置顶成功", "topNo": "否", "topYes": "是", - "topSuccess": "置顶成功", - "cancelTopSuccess": "取消置顶成功", - "toggle": "启停", - "toggleStatusConfirm": "确认切换「{0}」启停状态吗?", - "topToggle": "置顶切换", "toggleTopConfirm": "确认切换「{0}」置顶状态吗?" }, "role": { diff --git a/apps/web-antdv-next/src/views/system/notice/data.ts b/apps/web-antdv-next/src/views/system/notice/data.ts index ce18075..3a41065 100644 --- a/apps/web-antdv-next/src/views/system/notice/data.ts +++ b/apps/web-antdv-next/src/views/system/notice/data.ts @@ -1,19 +1,32 @@ import type { VbenFormSchema } from '#/adapter/form'; -import { z } from '#/adapter/form'; import { $t } from '#/locales'; -export const NOTICE_LEVEL_OPTIONS = [ - { label: '普通', value: 1 }, - { label: '重要', value: 2 }, - { label: '紧急', value: 3 }, +export const NOTICE_SEVERITY_OPTIONS = [ + { color: 'blue', label: $t('system.notice.severityInfo'), value: 'info' }, + { color: 'orange', label: $t('system.notice.severityWarn'), value: 'warn' }, + { color: 'red', label: $t('system.notice.severityError'), value: 'error' }, + { color: 'purple', label: $t('system.notice.severityFatal'), value: 'fatal' }, +]; + +export const NOTICE_SOURCE_OPTIONS = [ + { color: 'geekblue', label: $t('system.notice.sourceApi'), value: 'api' }, + { color: 'cyan', label: $t('system.notice.sourceQqbot'), value: 'qqbot' }, ]; export const NOTICE_STATUS_OPTIONS = [ - { color: 'success', label: $t('common.enabled'), value: 1 }, - { color: 'default', label: $t('common.disabled'), value: 0 }, + { color: 'error', label: $t('system.notice.statusUnhandled'), value: 1 }, + { color: 'default', label: $t('system.notice.statusHandled'), value: 0 }, ]; +export function getNoticeSeverityOptions() { + return NOTICE_SEVERITY_OPTIONS; +} + +export function getNoticeSourceOptions() { + return NOTICE_SOURCE_OPTIONS; +} + export function getNoticeStatusOptions() { return NOTICE_STATUS_OPTIONS; } @@ -32,10 +45,10 @@ export function useSearchSchema(): VbenFormSchema[] { component: 'Select', componentProps: { allowClear: true, - options: NOTICE_LEVEL_OPTIONS, + options: NOTICE_SEVERITY_OPTIONS, }, - fieldName: 'level', - label: $t('system.notice.level'), + fieldName: 'severity', + label: $t('system.notice.severity'), }, { component: 'Select', @@ -50,113 +63,18 @@ export function useSearchSchema(): VbenFormSchema[] { component: 'Select', componentProps: { allowClear: true, - options: [ - { label: $t('common.no'), value: 0 }, - { label: $t('common.yes'), value: 1 }, - ], + options: NOTICE_SOURCE_OPTIONS, }, - fieldName: 'isTop', - label: $t('system.notice.top'), - }, - ]; -} - -export function useFormSchema(): VbenFormSchema[] { - return [ - { - component: 'Input', - componentProps: { - allowClear: true, - placeholder: '如 系统公告', - }, - fieldName: 'title', - label: $t('system.notice.title'), - rules: z - .string() - .min(1, $t('ui.formRules.required', [$t('system.notice.title')])) - .max( - 120, - $t('ui.formRules.maxLength', [$t('system.notice.title'), 120]), - ), - }, - { - component: 'Input', - componentProps: { - allowClear: true, - placeholder: '如 站内信内容概要', - }, - fieldName: 'summary', - label: $t('system.notice.summary'), - rules: z - .string() - .max( - 200, - $t('ui.formRules.maxLength', [$t('system.notice.summary'), 200]), - ), - }, - { - component: 'TextArea', - componentProps: { - autoSize: { - maxRows: 10, - minRows: 4, - }, - allowClear: true, - placeholder: '请输入完整内容', - }, - fieldName: 'content', - label: $t('system.notice.content'), - rules: z - .string() - .min(1, $t('ui.formRules.required', [$t('system.notice.content')])), - }, - { - component: 'Select', - componentProps: { - allowClear: true, - options: NOTICE_LEVEL_OPTIONS, - }, - fieldName: 'level', - label: $t('system.notice.level'), - defaultValue: 1, - }, - { - component: 'Input', - componentProps: { - allowClear: true, - placeholder: '逗号分隔,如:10001,10002,留空表示全部用户可见', - }, - fieldName: 'notifyUsers', - label: $t('system.notice.notifyUsers'), - }, - { - component: 'RadioGroup', - componentProps: { - buttonStyle: 'solid', - options: [ - { - label: $t('system.notice.topNo'), - value: false, - }, - { - label: $t('system.notice.topYes'), - value: true, - }, - ], - }, - defaultValue: false, - fieldName: 'isTop', - label: $t('system.notice.top'), - }, - { - component: 'RadioGroup', - componentProps: { - buttonStyle: 'solid', - options: NOTICE_STATUS_OPTIONS, - }, - defaultValue: 1, - fieldName: 'status', - label: $t('system.notice.status'), + fieldName: 'source', + label: $t('system.notice.source'), + }, + { + component: 'Input', + componentProps: { + allowClear: true, + }, + fieldName: 'eventType', + label: $t('system.notice.eventType'), }, ]; } diff --git a/apps/web-antdv-next/src/views/system/notice/list.vue b/apps/web-antdv-next/src/views/system/notice/list.vue index a77fb46..32a2064 100644 --- a/apps/web-antdv-next/src/views/system/notice/list.vue +++ b/apps/web-antdv-next/src/views/system/notice/list.vue @@ -4,15 +4,11 @@ import type { TableColumnType } from 'antdv-next'; import type { SystemNoticeApi } from '#/api/system/notice'; import type { KtTableApi, - KtTableButton, KtTableContext, KtTableRowAction, } from '#/components/ktTable'; -import { h } from 'vue'; - -import { Page, useVbenModal } from '@vben/common-ui'; -import { Plus } from '@vben/icons'; +import { Page } from '@vben/common-ui'; import { message, Tag } from 'antdv-next'; @@ -25,14 +21,15 @@ import { import { KtTable, useKtTable } from '#/components/ktTable'; import { $t } from '#/locales'; -import { getNoticeStatusOptions, useSearchSchema } from './data'; -import Form from './modules/form.vue'; - -const [FormModal, formModalApi] = useVbenModal({ - connectedComponent: Form, - destroyOnClose: true, -}); +import { + getNoticeSeverityOptions, + getNoticeSourceOptions, + getNoticeStatusOptions, + useSearchSchema, +} from './data'; +const noticeSeverityOptions = getNoticeSeverityOptions(); +const noticeSourceOptions = getNoticeSourceOptions(); const noticeStatusOptions = getNoticeStatusOptions(); const columns: Array> = [ @@ -41,37 +38,49 @@ const columns: Array> = [ fixed: 'left', key: 'isTop', title: $t('system.notice.top'), - width: 90, + width: 82, }, { - dataIndex: 'title', + dataIndex: 'severity', fixed: 'left', - key: 'title', - title: $t('system.notice.title'), - width: 260, - }, - { - dataIndex: 'level', - key: 'level', - title: $t('system.notice.level'), - width: 100, + key: 'severity', + title: $t('system.notice.severity'), + width: 96, }, { dataIndex: 'status', key: 'status', title: $t('system.notice.status'), - width: 100, + width: 96, }, { - dataIndex: 'notifyUsers', - key: 'notifyUsers', - title: $t('system.notice.notifyUsers'), + dataIndex: 'title', + key: 'title', + title: $t('system.notice.eventTitle'), + width: 260, + }, + { + dataIndex: 'source', + key: 'source', + title: $t('system.notice.source'), + width: 110, + }, + { + dataIndex: 'eventType', + key: 'eventType', + title: $t('system.notice.eventType'), width: 180, }, { - dataIndex: 'createTime', - key: 'createTime', - title: $t('system.notice.createTime'), + dataIndex: 'occurrenceCount', + key: 'occurrenceCount', + title: $t('system.notice.occurrenceCount'), + width: 110, + }, + { + dataIndex: 'lastSeenAt', + key: 'lastSeenAt', + title: $t('system.notice.lastSeenAt'), width: 190, }, { @@ -79,7 +88,7 @@ const columns: Array> = [ dataIndex: 'summary', key: 'summary', title: $t('system.notice.summary'), - width: 280, + width: 320, }, ]; @@ -87,37 +96,38 @@ const api: KtTableApi = { list: async (params) => await getNoticeList(params), }; -const buttons: Array> = [ - { - icon: () => h(Plus, { class: 'kt-table__button-icon' }), - key: 'create', - label: $t('ui.actionTitle.create', [$t('system.notice.name')]), - onClick: onCreate, - permissionCodes: ['System:Notice:Create'], - type: 'primary', - }, -]; - const rowActions: Array> = [ { - key: 'edit', - label: $t('common.edit'), - onClick: onEdit, - permissionCodes: ['System:Notice:Edit'], - }, - { - confirm: (row) => $t('system.notice.toggleStatusConfirm', [row.title]), - key: 'toggle', - label: $t('system.notice.toggle'), + confirm: (row) => $t('system.notice.handleConfirm', [row.title]), + key: 'handle', + label: $t('system.notice.markHandled'), onClick: onToggleStatus, permissionCodes: ['System:Notice:Edit'], + rowVisible: (row) => row.status === 1, + }, + { + confirm: (row) => $t('system.notice.reopenConfirm', [row.title]), + key: 'reopen', + label: $t('system.notice.reopen'), + onClick: onToggleStatus, + permissionCodes: ['System:Notice:Edit'], + rowVisible: (row) => row.status !== 1, }, { confirm: (row) => $t('system.notice.toggleTopConfirm', [row.title]), - key: 'top', - label: $t('system.notice.topToggle'), + key: 'markTop', + label: $t('system.notice.markTop'), onClick: onToggleTop, permissionCodes: ['System:Notice:Edit'], + rowVisible: (row) => !row.isTop, + }, + { + confirm: (row) => $t('system.notice.toggleTopConfirm', [row.title]), + key: 'cancelTop', + label: $t('system.notice.cancelTop'), + onClick: onToggleTop, + permissionCodes: ['System:Notice:Edit'], + rowVisible: (row) => row.isTop, }, { confirm: (row) => $t('system.notice.deleteConfirm', [row.title]), @@ -129,19 +139,22 @@ const rowActions: Array> = [ }, ]; +function getNoticeSeverityOption( + severity: SystemNoticeApi.NoticeItem['severity'], +) { + return noticeSeverityOptions.find((item) => item.value === severity); +} + +function getNoticeSourceOption(source?: string) { + return noticeSourceOptions.find((item) => item.value === source); +} + function getNoticeStatusOption(status: SystemNoticeApi.NoticeItem['status']) { return noticeStatusOptions.find((item) => item.value === status); } -function getNoticeLevelColor(level: SystemNoticeApi.NoticeItem['level']) { - if (level === 3) return 'red'; - if (level === 2) return 'orange'; - return 'blue'; -} - const [registerTable, tableApi] = useKtTable({ api, - buttons, columns, formOptions: { schema: useSearchSchema(), @@ -152,14 +165,6 @@ const [registerTable, tableApi] = useKtTable({ tableTitle: $t('system.notice.title'), }); -function onCreate() { - formModalApi.setData(undefined).open(); -} - -function onEdit(row: SystemNoticeApi.NoticeItem) { - formModalApi.setData(row).open(); -} - async function onToggleStatus( row: SystemNoticeApi.NoticeItem, context: KtTableContext, @@ -167,9 +172,9 @@ async function onToggleStatus( const nextStatus = row.status === 1 ? 0 : 1; await toggleNoticeStatus(row.id, nextStatus); message.success( - nextStatus === 1 - ? $t('system.notice.enableSuccess') - : $t('system.notice.disableSuccess'), + nextStatus === 0 + ? $t('system.notice.handleSuccess') + : $t('system.notice.reopenSuccess'), ); await context.reload(); } @@ -208,15 +213,10 @@ async function onDelete( hideLoading(); } } - -function onRefresh() { - tableApi.reload(); -}