@robot-admin/naive-ui-components 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -0
- package/dist/C_ActionBar-DWN-woTc.css.map +1 -0
- package/dist/C_ActionBar.cjs +5 -0
- package/dist/C_ActionBar.d.cts +2 -0
- package/dist/C_ActionBar.d.ts +2 -0
- package/dist/C_ActionBar.js +4 -0
- package/dist/C_ActionBar2.js +196 -0
- package/dist/C_ActionBar2.js.map +1 -0
- package/dist/C_AntV-AFKyK6hH.css.map +1 -0
- package/dist/C_AntV.cjs +8 -0
- package/dist/C_AntV.d.cts +2 -0
- package/dist/C_AntV.d.ts +2 -0
- package/dist/C_AntV.js +4 -0
- package/dist/C_AntV2.js +3150 -0
- package/dist/C_AntV2.js.map +1 -0
- package/dist/C_Barcode-P_EFj8dC.css.map +1 -0
- package/dist/C_Barcode.cjs +4 -0
- package/dist/C_Barcode.d.cts +2 -0
- package/dist/C_Barcode.d.ts +2 -0
- package/dist/C_Barcode.js +3 -0
- package/dist/C_Barcode2.js +68 -0
- package/dist/C_Barcode2.js.map +1 -0
- package/dist/C_Captcha-C-ef41xw.css.map +1 -0
- package/dist/C_Captcha.cjs +4 -0
- package/dist/C_Captcha.d.cts +2 -0
- package/dist/C_Captcha.d.ts +2 -0
- package/dist/C_Captcha.js +3 -0
- package/dist/C_Captcha2.js +155 -0
- package/dist/C_Captcha2.js.map +1 -0
- package/dist/C_Cascade-D9kNsjsV.css.map +1 -0
- package/dist/C_Cascade.cjs +4 -0
- package/dist/C_Cascade.d.cts +2 -0
- package/dist/C_Cascade.d.ts +2 -0
- package/dist/C_Cascade.js +3 -0
- package/dist/C_Cascade2.js +103 -0
- package/dist/C_Cascade2.js.map +1 -0
- package/dist/C_City-BCQ4ipiK.css.map +1 -0
- package/dist/C_City.cjs +4 -0
- package/dist/C_City.d.cts +2 -0
- package/dist/C_City.d.ts +2 -0
- package/dist/C_City.js +3 -0
- package/dist/C_City2.js +841 -0
- package/dist/C_City2.js.map +1 -0
- package/dist/C_Code-C9kvvEmO.css.map +1 -0
- package/dist/C_Code.cjs +5 -0
- package/dist/C_Code.d.cts +2 -0
- package/dist/C_Code.d.ts +2 -0
- package/dist/C_Code.js +4 -0
- package/dist/C_Code2.js +346 -0
- package/dist/C_Code2.js.map +1 -0
- package/dist/C_CollapsePanel-BUJHuYcU.css.map +1 -0
- package/dist/C_CollapsePanel.cjs +6 -0
- package/dist/C_CollapsePanel.d.cts +2 -0
- package/dist/C_CollapsePanel.d.ts +2 -0
- package/dist/C_CollapsePanel.js +4 -0
- package/dist/C_CollapsePanel2.js +319 -0
- package/dist/C_CollapsePanel2.js.map +1 -0
- package/dist/C_Cron-yx2Ob4Jl.css.map +1 -0
- package/dist/C_Cron.cjs +15 -0
- package/dist/C_Cron.d.cts +2 -0
- package/dist/C_Cron.d.ts +2 -0
- package/dist/C_Cron.js +4 -0
- package/dist/C_Cron2.js +1209 -0
- package/dist/C_Cron2.js.map +1 -0
- package/dist/C_Date.cjs +4 -0
- package/dist/C_Date.d.cts +2 -0
- package/dist/C_Date.d.ts +2 -0
- package/dist/C_Date.js +3 -0
- package/dist/C_Date2.js +219 -0
- package/dist/C_Date2.js.map +1 -0
- package/dist/C_Draggable-C483syRC.css.map +1 -0
- package/dist/C_Draggable.cjs +5 -0
- package/dist/C_Draggable.d.cts +2 -0
- package/dist/C_Draggable.d.ts +2 -0
- package/dist/C_Draggable.js +3 -0
- package/dist/C_Draggable2.js +295 -0
- package/dist/C_Draggable2.js.map +1 -0
- package/dist/C_Editor-Bp0SyIEw.css.map +1 -0
- package/dist/C_Editor.cjs +4 -0
- package/dist/C_Editor.d.cts +2 -0
- package/dist/C_Editor.d.ts +2 -0
- package/dist/C_Editor.js +3 -0
- package/dist/C_Editor2.js +160 -0
- package/dist/C_Editor2.js.map +1 -0
- package/dist/C_FilePreview-CPqvhoCy.css.map +1 -0
- package/dist/C_FilePreview.cjs +6 -0
- package/dist/C_FilePreview.d.cts +2 -0
- package/dist/C_FilePreview.d.ts +2 -0
- package/dist/C_FilePreview.js +3 -0
- package/dist/C_FilePreview2.js +1031 -0
- package/dist/C_FilePreview2.js.map +1 -0
- package/dist/C_Form-Jx7PY3sT.css.map +1 -0
- package/dist/C_Form.cjs +15 -0
- package/dist/C_Form.d.cts +2 -0
- package/dist/C_Form.d.ts +2 -0
- package/dist/C_Form.js +4 -0
- package/dist/C_Form2.js +2510 -0
- package/dist/C_Form2.js.map +1 -0
- package/dist/C_FormSearch-DvRgxlRn.css.map +1 -0
- package/dist/C_FormSearch.cjs +6 -0
- package/dist/C_FormSearch.d.cts +2 -0
- package/dist/C_FormSearch.d.ts +2 -0
- package/dist/C_FormSearch.js +3 -0
- package/dist/C_FormSearch2.js +356 -0
- package/dist/C_FormSearch2.js.map +1 -0
- package/dist/C_FormulaEditor-DtGkt4T_.css.map +1 -0
- package/dist/C_FormulaEditor.cjs +13 -0
- package/dist/C_FormulaEditor.d.cts +2 -0
- package/dist/C_FormulaEditor.d.ts +2 -0
- package/dist/C_FormulaEditor.js +4 -0
- package/dist/C_FormulaEditor2.js +1433 -0
- package/dist/C_FormulaEditor2.js.map +1 -0
- package/dist/C_FullCalendar-BF7H0YIx.css.map +1 -0
- package/dist/C_FullCalendar.cjs +9 -0
- package/dist/C_FullCalendar.d.cts +2 -0
- package/dist/C_FullCalendar.d.ts +2 -0
- package/dist/C_FullCalendar.js +3 -0
- package/dist/C_FullCalendar2.js +377 -0
- package/dist/C_FullCalendar2.js.map +1 -0
- package/dist/C_Guide.cjs +4 -0
- package/dist/C_Guide.d.cts +2 -0
- package/dist/C_Guide.d.ts +2 -0
- package/dist/C_Guide.js +3 -0
- package/dist/C_Guide2.js +58 -0
- package/dist/C_Guide2.js.map +1 -0
- package/dist/C_Icon.cjs +4 -0
- package/dist/C_Icon.d.cts +2 -0
- package/dist/C_Icon.d.ts +2 -0
- package/dist/C_Icon.js +3 -0
- package/dist/C_Icon2.js +286 -0
- package/dist/C_Icon2.js.map +1 -0
- package/dist/C_ImageCropper-BVJfUufl.css.map +1 -0
- package/dist/C_ImageCropper.cjs +6 -0
- package/dist/C_ImageCropper.d.cts +2 -0
- package/dist/C_ImageCropper.d.ts +2 -0
- package/dist/C_ImageCropper.js +4 -0
- package/dist/C_ImageCropper2.js +723 -0
- package/dist/C_ImageCropper2.js.map +1 -0
- package/dist/C_Language.cjs +4 -0
- package/dist/C_Language.d.cts +2 -0
- package/dist/C_Language.d.ts +2 -0
- package/dist/C_Language.js +3 -0
- package/dist/C_Language2.js +72 -0
- package/dist/C_Language2.js.map +1 -0
- package/dist/C_Map-DpzeuWdX.css.map +1 -0
- package/dist/C_Map.cjs +7 -0
- package/dist/C_Map.d.cts +2 -0
- package/dist/C_Map.d.ts +2 -0
- package/dist/C_Map.js +3 -0
- package/dist/C_Map2.js +199 -0
- package/dist/C_Map2.js.map +1 -0
- package/dist/C_Markdown-BEjxknqd.css.map +1 -0
- package/dist/C_Markdown.cjs +4 -0
- package/dist/C_Markdown.d.cts +2 -0
- package/dist/C_Markdown.d.ts +2 -0
- package/dist/C_Markdown.js +3 -0
- package/dist/C_Markdown2.js +186 -0
- package/dist/C_Markdown2.js.map +1 -0
- package/dist/C_NotificationCenter-0l3TY2Gn.css.map +1 -0
- package/dist/C_NotificationCenter.cjs +20 -0
- package/dist/C_NotificationCenter.d.cts +2 -0
- package/dist/C_NotificationCenter.d.ts +2 -0
- package/dist/C_NotificationCenter.js +4 -0
- package/dist/C_NotificationCenter2.js +1383 -0
- package/dist/C_NotificationCenter2.js.map +1 -0
- package/dist/C_Progress.cjs +4 -0
- package/dist/C_Progress.d.cts +2 -0
- package/dist/C_Progress.d.ts +2 -0
- package/dist/C_Progress.js +3 -0
- package/dist/C_Progress2.js +103 -0
- package/dist/C_Progress2.js.map +1 -0
- package/dist/C_QRCode-DbdiAIPg.css.map +1 -0
- package/dist/C_QRCode.cjs +5 -0
- package/dist/C_QRCode.d.cts +2 -0
- package/dist/C_QRCode.d.ts +2 -0
- package/dist/C_QRCode.js +3 -0
- package/dist/C_QRCode2.js +218 -0
- package/dist/C_QRCode2.js.map +1 -0
- package/dist/C_Signature-zhHCbra9.css.map +1 -0
- package/dist/C_Signature.cjs +8 -0
- package/dist/C_Signature.d.cts +2 -0
- package/dist/C_Signature.d.ts +2 -0
- package/dist/C_Signature.js +4 -0
- package/dist/C_Signature2.js +618 -0
- package/dist/C_Signature2.js.map +1 -0
- package/dist/C_SplitPane-C6sBsfKY.css.map +1 -0
- package/dist/C_SplitPane.cjs +6 -0
- package/dist/C_SplitPane.d.cts +2 -0
- package/dist/C_SplitPane.d.ts +2 -0
- package/dist/C_SplitPane.js +4 -0
- package/dist/C_SplitPane2.js +356 -0
- package/dist/C_SplitPane2.js.map +1 -0
- package/dist/C_Steps-CODHN5Hs.css.map +1 -0
- package/dist/C_Steps.cjs +4 -0
- package/dist/C_Steps.d.cts +2 -0
- package/dist/C_Steps.d.ts +2 -0
- package/dist/C_Steps.js +3 -0
- package/dist/C_Steps2.js +82 -0
- package/dist/C_Steps2.js.map +1 -0
- package/dist/C_Table-DSNsntmT.css.map +1 -0
- package/dist/C_Table.cjs +19 -0
- package/dist/C_Table.d.cts +2 -0
- package/dist/C_Table.d.ts +2 -0
- package/dist/C_Table.js +5 -0
- package/dist/C_Table2.js +3009 -0
- package/dist/C_Table2.js.map +1 -0
- package/dist/C_Theme.cjs +4 -0
- package/dist/C_Theme.d.cts +2 -0
- package/dist/C_Theme.d.ts +2 -0
- package/dist/C_Theme.js +3 -0
- package/dist/C_Theme2.js +60 -0
- package/dist/C_Theme2.js.map +1 -0
- package/dist/C_Time-BvZLYraL.css.map +1 -0
- package/dist/C_Time.cjs +5 -0
- package/dist/C_Time.d.cts +2 -0
- package/dist/C_Time.d.ts +2 -0
- package/dist/C_Time.js +3 -0
- package/dist/C_Time2.js +199 -0
- package/dist/C_Time2.js.map +1 -0
- package/dist/C_Tree-0GDv--jX.css.map +1 -0
- package/dist/C_Tree.cjs +7 -0
- package/dist/C_Tree.d.cts +2 -0
- package/dist/C_Tree.d.ts +2 -0
- package/dist/C_Tree.js +4 -0
- package/dist/C_Tree2.js +441 -0
- package/dist/C_Tree2.js.map +1 -0
- package/dist/C_Upload-BXd3YYLx.css.map +1 -0
- package/dist/C_Upload.cjs +12 -0
- package/dist/C_Upload.d.cts +2 -0
- package/dist/C_Upload.d.ts +2 -0
- package/dist/C_Upload.js +4 -0
- package/dist/C_Upload2.js +1388 -0
- package/dist/C_Upload2.js.map +1 -0
- package/dist/C_VideoPlayer-DYG3RL0Q.css.map +1 -0
- package/dist/C_VideoPlayer.cjs +23 -0
- package/dist/C_VideoPlayer.d.cts +2 -0
- package/dist/C_VideoPlayer.d.ts +2 -0
- package/dist/C_VideoPlayer.js +3 -0
- package/dist/C_VideoPlayer2.js +1932 -0
- package/dist/C_VideoPlayer2.js.map +1 -0
- package/dist/C_VtableGantt-fhItIiHE.css.map +1 -0
- package/dist/C_VtableGantt.cjs +6 -0
- package/dist/C_VtableGantt.d.cts +2 -0
- package/dist/C_VtableGantt.d.ts +2 -0
- package/dist/C_VtableGantt.js +4 -0
- package/dist/C_VtableGantt2.js +873 -0
- package/dist/C_VtableGantt2.js.map +1 -0
- package/dist/C_WaterFall-8sQDFXKg.css.map +1 -0
- package/dist/C_WaterFall.cjs +13 -0
- package/dist/C_WaterFall.d.cts +2 -0
- package/dist/C_WaterFall.d.ts +2 -0
- package/dist/C_WaterFall.js +3 -0
- package/dist/C_WaterFall2.js +365 -0
- package/dist/C_WaterFall2.js.map +1 -0
- package/dist/C_WorkFlow-J-dyIuh9.css.map +1 -0
- package/dist/C_WorkFlow.cjs +8 -0
- package/dist/C_WorkFlow.d.cts +2 -0
- package/dist/C_WorkFlow.d.ts +2 -0
- package/dist/C_WorkFlow.js +4 -0
- package/dist/C_WorkFlow2.js +1984 -0
- package/dist/C_WorkFlow2.js.map +1 -0
- package/dist/chunk.js +22 -0
- package/dist/city.js +4817 -0
- package/dist/city.js.map +1 -0
- package/dist/constants.d.ts +273 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants2.d.ts +178 -0
- package/dist/constants2.d.ts.map +1 -0
- package/dist/constants3.d.ts +475 -0
- package/dist/constants3.d.ts.map +1 -0
- package/dist/constants4.d.ts +430 -0
- package/dist/constants4.d.ts.map +1 -0
- package/dist/constants5.d.ts +4283 -0
- package/dist/constants5.d.ts.map +1 -0
- package/dist/data.d.ts +67 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/export-helper.js +9 -0
- package/dist/index.cjs +409 -0
- package/dist/index.d.cts +96 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/dist/index.vue.d.ts +80 -0
- package/dist/index.vue.d.ts.map +1 -0
- package/dist/index10.vue.d.ts +72 -0
- package/dist/index10.vue.d.ts.map +1 -0
- package/dist/index11.vue.d.ts +26 -0
- package/dist/index11.vue.d.ts.map +1 -0
- package/dist/index12.vue.d.ts +81 -0
- package/dist/index12.vue.d.ts.map +1 -0
- package/dist/index13.vue.d.ts +55 -0
- package/dist/index13.vue.d.ts.map +1 -0
- package/dist/index14.vue.d.ts +33 -0
- package/dist/index14.vue.d.ts.map +1 -0
- package/dist/index15.vue.d.ts +18 -0
- package/dist/index15.vue.d.ts.map +1 -0
- package/dist/index16.vue.d.ts +662 -0
- package/dist/index16.vue.d.ts.map +1 -0
- package/dist/index2.vue.d.ts +38 -0
- package/dist/index2.vue.d.ts.map +1 -0
- package/dist/index3.vue.d.ts +45 -0
- package/dist/index3.vue.d.ts.map +1 -0
- package/dist/index4.vue.d.ts +31 -0
- package/dist/index4.vue.d.ts.map +1 -0
- package/dist/index5.vue.d.ts +35 -0
- package/dist/index5.vue.d.ts.map +1 -0
- package/dist/index6.vue.d.ts +48 -0
- package/dist/index6.vue.d.ts.map +1 -0
- package/dist/index7.vue.d.ts +56 -0
- package/dist/index7.vue.d.ts.map +1 -0
- package/dist/index8.vue.d.ts +41 -0
- package/dist/index8.vue.d.ts.map +1 -0
- package/dist/index9.vue.d.ts +30 -0
- package/dist/index9.vue.d.ts.map +1 -0
- package/dist/storage.js +31 -0
- package/dist/storage.js.map +1 -0
- package/dist/style.css +7725 -0
- package/dist/useCalendarEvents.d.ts +148 -0
- package/dist/useCalendarEvents.d.ts.map +1 -0
- package/dist/useCollapsePanel.d.ts +132 -0
- package/dist/useCollapsePanel.d.ts.map +1 -0
- package/dist/useCropperCore.d.ts +102 -0
- package/dist/useCropperCore.d.ts.map +1 -0
- package/dist/useDraggableLayout.d.ts +194 -0
- package/dist/useDraggableLayout.d.ts.map +1 -0
- package/dist/useDynamicFormState.d.ts +4248 -0
- package/dist/useDynamicFormState.d.ts.map +1 -0
- package/dist/useEdgeInteraction.d.ts +7614 -0
- package/dist/useEdgeInteraction.d.ts.map +1 -0
- package/dist/useFullscreen.d.ts +166 -0
- package/dist/useFullscreen.d.ts.map +1 -0
- package/dist/useInfiniteScroll.d.ts +169 -0
- package/dist/useInfiniteScroll.d.ts.map +1 -0
- package/dist/useModalEdit.d.ts +960 -0
- package/dist/useModalEdit.d.ts.map +1 -0
- package/dist/useQRCode.d.ts +87 -0
- package/dist/useQRCode.d.ts.map +1 -0
- package/dist/useSearchState.d.ts +180 -0
- package/dist/useSearchState.d.ts.map +1 -0
- package/dist/useSignatureHistory.d.ts +189 -0
- package/dist/useSignatureHistory.d.ts.map +1 -0
- package/dist/useSplitResize.d.ts +158 -0
- package/dist/useSplitResize.d.ts.map +1 -0
- package/dist/useTimeSelection.d.ts +105 -0
- package/dist/useTimeSelection.d.ts.map +1 -0
- package/dist/useTreeOperations.d.ts +183 -0
- package/dist/useTreeOperations.d.ts.map +1 -0
- package/dist/useWorkflowValidation.d.ts +1052 -0
- package/dist/useWorkflowValidation.d.ts.map +1 -0
- package/package.json +342 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_NotificationCenter2.js","names":["$emit","count","hasUrgent","message","$emit","activeCategory","$emit","messages","scrollHeight","loading","hasMore","$emit","message","scrollHeight"],"sources":["../src/components/C_NotificationCenter/constants.ts","../src/components/C_NotificationCenter/composables/useNotificationWS.ts","../src/components/C_NotificationCenter/composables/useNotificationCore.ts","../src/components/C_NotificationCenter/components/NotificationBadge.vue","../src/components/C_NotificationCenter/components/NotificationBadge.vue","../src/components/C_NotificationCenter/components/NotificationBadge.vue","../src/components/C_NotificationCenter/composables/useNotificationFormat.ts","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationDetail.vue","../src/components/C_NotificationCenter/components/NotificationDetail.vue","../src/components/C_NotificationCenter/components/NotificationDetail.vue","../src/components/C_NotificationCenter/index.vue","../src/components/C_NotificationCenter/index.vue","../src/components/C_NotificationCenter/index.vue"],"sourcesContent":["/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心常量配置\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport type { NotificationCategory, NotificationPriority } from \"./types\";\r\n\r\n/* ─── 默认配置 ─────────────────────────────────── */\r\n\r\n/** 默认角标最大显示数 */\r\nexport const DEFAULT_MAX_BADGE_COUNT = 99;\r\n\r\n/** 默认每页条数 */\r\nexport const DEFAULT_PAGE_SIZE = 20;\r\n\r\n/** 默认轮询间隔(ms):60 秒 */\r\nexport const DEFAULT_POLLING_INTERVAL = 60_000;\r\n\r\n/** 默认缓存 key */\r\nexport const DEFAULT_STORAGE_KEY = \"notification_center\";\r\n\r\n/** WebSocket 默认重连间隔(ms) */\r\nexport const DEFAULT_WS_RECONNECT_INTERVAL = 3_000;\r\n\r\n/** WebSocket 默认最大重连次数 */\r\nexport const DEFAULT_WS_MAX_RECONNECT = 5;\r\n\r\n/** WebSocket 默认心跳间隔(ms) */\r\nexport const DEFAULT_WS_HEARTBEAT_INTERVAL = 30_000;\r\n\r\n/** WebSocket 默认心跳消息 */\r\nexport const DEFAULT_WS_HEARTBEAT_MESSAGE = \"ping\";\r\n\r\n/* ─── 分类配置 ─────────────────────────────────── */\r\n\r\n/** 分类标签映射 */\r\nexport const CATEGORY_MAP: Record<\r\n NotificationCategory,\r\n {\r\n label: string;\r\n icon: string;\r\n color: string;\r\n }\r\n> = {\r\n system: {\r\n label: \"系统通知\",\r\n icon: \"mdi:cog-outline\",\r\n color: \"info\",\r\n },\r\n business: {\r\n label: \"业务消息\",\r\n icon: \"mdi:briefcase-outline\",\r\n color: \"success\",\r\n },\r\n alarm: {\r\n label: \"告警预警\",\r\n icon: \"mdi:alert-outline\",\r\n color: \"warning\",\r\n },\r\n};\r\n\r\n/** 分类 Tab 列表 */\r\nexport const CATEGORY_TABS: {\r\n key: NotificationCategory | \"all\";\r\n label: string;\r\n}[] = [\r\n { key: \"all\", label: \"全部\" },\r\n { key: \"system\", label: \"系统通知\" },\r\n { key: \"business\", label: \"业务消息\" },\r\n { key: \"alarm\", label: \"告警预警\" },\r\n];\r\n\r\n/* ─── 优先级配置 ───────────────────────────────── */\r\n\r\n/** 优先级标签映射 */\r\nexport const PRIORITY_MAP: Record<\r\n NotificationPriority,\r\n {\r\n label: string;\r\n type: \"default\" | \"info\" | \"success\" | \"warning\" | \"error\";\r\n }\r\n> = {\r\n low: { label: \"低\", type: \"default\" },\r\n normal: { label: \"普通\", type: \"info\" },\r\n high: { label: \"重要\", type: \"warning\" },\r\n urgent: { label: \"紧急\", type: \"error\" },\r\n};\r\n\r\n/* ─── 模拟数据(开发环境使用) ─────────────────── */\r\n\r\n/** 模拟消息列表 */\r\nexport const MOCK_MESSAGES = [\r\n {\r\n id: \"msg-001\",\r\n title: \"系统维护通知\",\r\n summary:\r\n \"系统将于今晚 22:00-23:00 进行例行维护升级,届时部分功能可能暂时不可用。\",\r\n content:\r\n \"<p>尊敬的用户:</p><p>为了提升系统性能和稳定性,我们计划于 <strong>2026年2月27日 22:00-23:00</strong> 对系统进行例行维护升级。</p><p>维护期间,以下功能可能暂时不可用:</p><ul><li>报表导出</li><li>数据同步</li><li>定时任务</li></ul><p>请提前保存好未完成的工作,感谢您的理解与支持!</p>\",\r\n category: \"system\" as const,\r\n priority: \"high\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 15 * 60_000).toISOString(),\r\n sender: { name: \"系统管理员\", avatar: \"\" },\r\n },\r\n {\r\n id: \"msg-002\",\r\n title: \"审批流程待处理\",\r\n summary: \"张三提交的《Q1 季度预算申请》需要您审批,请尽快处理。\",\r\n category: \"business\" as const,\r\n priority: \"urgent\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 30 * 60_000).toISOString(),\r\n sender: {\r\n name: \"张三\",\r\n avatar:\r\n \"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png\",\r\n },\r\n actionUrl: \"/workflow/approval\",\r\n actionText: \"去审批\",\r\n },\r\n {\r\n id: \"msg-003\",\r\n title: \"服务器 CPU 使用率告警\",\r\n summary:\r\n \"生产环境节点 prod-node-03 CPU 使用率持续超过 90%,已触发告警阈值。\",\r\n category: \"alarm\" as const,\r\n priority: \"urgent\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 2 * 60_000).toISOString(),\r\n sender: { name: \"监控中心\" },\r\n actionUrl: \"/monitor/server\",\r\n actionText: \"查看详情\",\r\n },\r\n {\r\n id: \"msg-004\",\r\n title: \"新版本发布 v1.14.0\",\r\n summary: \"系统已升级至 v1.14.0,新增通知中心、工作流增强等多项功能。\",\r\n category: \"system\" as const,\r\n priority: \"normal\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 3 * 3_600_000).toISOString(),\r\n sender: { name: \"系统管理员\" },\r\n },\r\n {\r\n id: \"msg-005\",\r\n title: \"项目交付提醒\",\r\n summary: \"「Robot Admin 二期」项目交付截止日期为 3月15日,当前进度 78%。\",\r\n category: \"business\" as const,\r\n priority: \"high\" as const,\r\n status: \"read\" as const,\r\n timestamp: new Date(Date.now() - 8 * 3_600_000).toISOString(),\r\n sender: { name: \"项目管理系统\" },\r\n },\r\n {\r\n id: \"msg-006\",\r\n title: \"数据库连接池告警\",\r\n summary: \"数据库连接池使用率达到 85%,建议关注并适时扩容。\",\r\n category: \"alarm\" as const,\r\n priority: \"high\" as const,\r\n status: \"read\" as const,\r\n timestamp: new Date(Date.now() - 12 * 3_600_000).toISOString(),\r\n sender: { name: \"监控中心\" },\r\n },\r\n {\r\n id: \"msg-007\",\r\n title: \"周报提交提醒\",\r\n summary: \"本周周报提交截止时间为周五 18:00,请及时提交。\",\r\n category: \"business\" as const,\r\n priority: \"normal\" as const,\r\n status: \"read\" as const,\r\n timestamp: new Date(Date.now() - 24 * 3_600_000).toISOString(),\r\n sender: { name: \"OA 系统\" },\r\n },\r\n {\r\n id: \"msg-008\",\r\n title: \"账号安全提醒\",\r\n summary: \"检测到您的账号在新设备上登录,如非本人操作请及时修改密码。\",\r\n category: \"system\" as const,\r\n priority: \"high\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 45 * 60_000).toISOString(),\r\n sender: { name: \"安全中心\" },\r\n },\r\n];\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: WebSocket 连接管理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { ref, readonly, onBeforeUnmount } from \"vue\";\r\nimport type {\r\n NotificationWSConfig,\r\n WSConnectionStatus,\r\n WSNotificationPayload,\r\n} from \"../types\";\r\nimport {\r\n DEFAULT_WS_RECONNECT_INTERVAL,\r\n DEFAULT_WS_MAX_RECONNECT,\r\n DEFAULT_WS_HEARTBEAT_INTERVAL,\r\n DEFAULT_WS_HEARTBEAT_MESSAGE,\r\n} from \"../constants\";\r\n\r\n/**\r\n * WebSocket 连接管理\r\n *\r\n * 管理 WebSocket 的建连、重连、心跳保活、消息解析。\r\n * 支持自动重连、鉴权 token、心跳检测。\r\n */\r\nexport function useNotificationWS(\r\n onMessage: (payload: WSNotificationPayload) => void,\r\n onStatusChange?: (status: WSConnectionStatus) => void,\r\n) {\r\n /** 连接状态 */\r\n const status = ref<WSConnectionStatus>(\"disconnected\");\r\n\r\n /** WebSocket 实例 */\r\n let ws: WebSocket | null = null;\r\n /** 重连计数器 */\r\n let reconnectCount = 0;\r\n /** 重连定时器 */\r\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n /** 心跳定时器 */\r\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n /** 当前配置 */\r\n let currentConfig: NotificationWSConfig | null = null;\r\n\r\n /**\r\n * 更新连接状态\r\n */\r\n function setStatus(s: WSConnectionStatus) {\r\n status.value = s;\r\n onStatusChange?.(s);\r\n }\r\n\r\n /**\r\n * 建立连接\r\n */\r\n function connect(config: NotificationWSConfig) {\r\n currentConfig = config;\r\n reconnectCount = 0;\r\n createConnection(config);\r\n }\r\n\r\n /**\r\n * 创建 WebSocket 连接\r\n */\r\n function createConnection(config: NotificationWSConfig) {\r\n cleanup();\r\n\r\n const url = buildUrl(config);\r\n setStatus(\"connecting\");\r\n\r\n try {\r\n ws = new WebSocket(url);\r\n } catch {\r\n setStatus(\"disconnected\");\r\n scheduleReconnect(config);\r\n return;\r\n }\r\n\r\n ws.addEventListener(\"open\", () => {\r\n setStatus(\"connected\");\r\n reconnectCount = 0;\r\n startHeartbeat(config);\r\n });\r\n\r\n ws.addEventListener(\"message\", (event) => {\r\n handleMessage(event.data);\r\n });\r\n\r\n ws.addEventListener(\"close\", () => {\r\n stopHeartbeat();\r\n setStatus(\"disconnected\");\r\n\r\n if (config.autoReconnect !== false) {\r\n scheduleReconnect(config);\r\n }\r\n });\r\n\r\n ws.addEventListener(\"error\", () => {\r\n /* error 事件后通常紧接 close 事件,交由 close 处理重连 */\r\n });\r\n }\r\n\r\n /**\r\n * 构建带 token 的 WebSocket URL\r\n */\r\n function buildUrl(config: NotificationWSConfig): string {\r\n const token = config.getToken?.();\r\n if (token) {\r\n const separator = config.url.includes(\"?\") ? \"&\" : \"?\";\r\n return `${config.url}${separator}token=${encodeURIComponent(token)}`;\r\n }\r\n return config.url;\r\n }\r\n\r\n /**\r\n * 处理接收到的消息\r\n */\r\n function handleMessage(raw: string | ArrayBuffer | Blob) {\r\n if (typeof raw !== \"string\") return;\r\n\r\n /* 过滤心跳响应 */\r\n if (raw === \"pong\") return;\r\n\r\n try {\r\n const payload = JSON.parse(raw) as WSNotificationPayload;\r\n onMessage(payload);\r\n } catch {\r\n /* 非标准 JSON 消息,忽略 */\r\n }\r\n }\r\n\r\n /**\r\n * 调度重连\r\n */\r\n function scheduleReconnect(config: NotificationWSConfig) {\r\n const maxAttempts = config.maxReconnectAttempts ?? DEFAULT_WS_MAX_RECONNECT;\r\n if (reconnectCount >= maxAttempts) return;\r\n\r\n reconnectCount++;\r\n setStatus(\"reconnecting\");\r\n\r\n const interval = config.reconnectInterval ?? DEFAULT_WS_RECONNECT_INTERVAL;\r\n reconnectTimer = setTimeout(() => {\r\n createConnection(config);\r\n }, interval);\r\n }\r\n\r\n /**\r\n * 启动心跳\r\n */\r\n function startHeartbeat(config: NotificationWSConfig) {\r\n const interval = config.heartbeatInterval ?? DEFAULT_WS_HEARTBEAT_INTERVAL;\r\n if (interval <= 0) return;\r\n\r\n const message = config.heartbeatMessage ?? DEFAULT_WS_HEARTBEAT_MESSAGE;\r\n heartbeatTimer = setInterval(() => {\r\n if (ws?.readyState === WebSocket.OPEN) {\r\n ws.send(message);\r\n }\r\n }, interval);\r\n }\r\n\r\n /**\r\n * 停止心跳\r\n */\r\n function stopHeartbeat() {\r\n if (heartbeatTimer) {\r\n clearInterval(heartbeatTimer);\r\n heartbeatTimer = null;\r\n }\r\n }\r\n\r\n /**\r\n * 清理资源\r\n */\r\n function cleanup() {\r\n stopHeartbeat();\r\n if (reconnectTimer) {\r\n clearTimeout(reconnectTimer);\r\n reconnectTimer = null;\r\n }\r\n if (ws) {\r\n ws.close();\r\n ws = null;\r\n }\r\n }\r\n\r\n /**\r\n * 断开连接\r\n */\r\n function disconnect() {\r\n currentConfig = null;\r\n reconnectCount = Infinity;\r\n cleanup();\r\n setStatus(\"disconnected\");\r\n }\r\n\r\n /**\r\n * 手动重新连接\r\n */\r\n function reconnect() {\r\n if (currentConfig) {\r\n reconnectCount = 0;\r\n createConnection(currentConfig);\r\n }\r\n }\r\n\r\n /* 组件卸载时自动清理 */\r\n onBeforeUnmount(cleanup);\r\n\r\n return {\r\n /** WebSocket 连接状态 */\r\n status: readonly(status),\r\n connect,\r\n disconnect,\r\n reconnect,\r\n };\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心核心状态管理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport type {\r\n NotificationMessage,\r\n NotificationCategory,\r\n NotificationCenterProps,\r\n WSNotificationPayload,\r\n WSConnectionStatus,\r\n} from \"../types\";\r\nimport {\r\n DEFAULT_PAGE_SIZE,\r\n DEFAULT_POLLING_INTERVAL,\r\n DEFAULT_STORAGE_KEY,\r\n MOCK_MESSAGES,\r\n} from \"../constants\";\r\nimport { useNotificationWS } from \"./useNotificationWS\";\r\nimport { setItem, getItem } from \"../../../utils/storage\";\r\n\r\n/** 缓存数据结构 */\r\ninterface CachedState {\r\n unreadIds: string[];\r\n lastFetchTime: number;\r\n}\r\n\r\n/** 防抖持久化延迟(ms) */\r\nconst PERSIST_DEBOUNCE = 300;\r\n\r\n/**\r\n * 通知中心核心状态管理\r\n *\r\n * 统一管理消息列表、未读数、分类过滤、已读标记、\r\n * API 交互、WebSocket 桥接、轮询调度和本地缓存。\r\n */\r\nexport function useNotificationCore(props: NotificationCenterProps) {\r\n /* ─── 响应式状态 ─────────────────────────────── */\r\n\r\n /** 消息列表 */\r\n const messages = ref<NotificationMessage[]>([]);\r\n /** 当前选中分类 */\r\n const activeCategory = ref<NotificationCategory | \"all\">(\"all\");\r\n /** 是否正在加载 */\r\n const loading = ref(false);\r\n /** 总条数 */\r\n const total = ref(0);\r\n /** 当前页码 */\r\n const page = ref(1);\r\n /** 选中的消息(详情展示) */\r\n const selectedMessage = ref<NotificationMessage | null>(null);\r\n /** Popover 展开状态 */\r\n const popoverVisible = ref(false);\r\n /** WebSocket 连接状态 */\r\n const wsStatus = ref<WSConnectionStatus>(\"disconnected\");\r\n\r\n /* ─── 计算属性 ───────────────────────────────── */\r\n\r\n /** 未读消息总数 */\r\n const unreadCount = computed(\r\n () => messages.value.filter((m) => m.status === \"unread\").length,\r\n );\r\n\r\n /** 各分类未读数 */\r\n const unreadByCategory = computed(() => {\r\n const counts: Record<string, number> = { system: 0, business: 0, alarm: 0 };\r\n for (const m of messages.value) {\r\n if (m.status === \"unread\" && m.category in counts) {\r\n counts[m.category]++;\r\n }\r\n }\r\n return counts;\r\n });\r\n\r\n /** 当前分类下的消息列表 */\r\n const filteredMessages = computed(() => {\r\n if (activeCategory.value === \"all\") return messages.value;\r\n return messages.value.filter((m) => m.category === activeCategory.value);\r\n });\r\n\r\n /** 是否有更多消息可加载 */\r\n const hasMore = computed(() => messages.value.length < total.value);\r\n\r\n /* ─── 缓存管理 ───────────────────────────────── */\r\n\r\n /** 缓存 key */\r\n const storageKey = computed(() => props.storageKey ?? DEFAULT_STORAGE_KEY);\r\n\r\n /**\r\n * 从缓存恢复未读状态\r\n */\r\n function restoreFromCache() {\r\n const cached = getItem<CachedState>(storageKey.value);\r\n if (cached?.unreadIds) {\r\n const idSet = new Set(cached.unreadIds);\r\n for (const msg of messages.value) {\r\n if (idSet.has(msg.id)) {\r\n msg.status = \"unread\";\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** 防抖持久化定时器 */\r\n let persistTimer: ReturnType<typeof setTimeout> | null = null;\r\n\r\n /**\r\n * 持久化未读状态到本地缓存(防抖)\r\n */\r\n function persistToCache() {\r\n if (persistTimer) clearTimeout(persistTimer);\r\n persistTimer = setTimeout(() => {\r\n const unreadIds = messages.value\r\n .filter((m) => m.status === \"unread\")\r\n .map((m) => m.id);\r\n setItem<CachedState>(storageKey.value, {\r\n unreadIds,\r\n lastFetchTime: Date.now(),\r\n });\r\n persistTimer = null;\r\n }, PERSIST_DEBOUNCE);\r\n }\r\n\r\n /* ─── API 交互 ───────────────────────────────── */\r\n\r\n /**\r\n * 拉取消息列表\r\n */\r\n async function fetchMessages(reset = false) {\r\n if (reset) {\r\n page.value = 1;\r\n }\r\n\r\n loading.value = true;\r\n try {\r\n if (props.fetchNotifications) {\r\n const categoryParam =\r\n activeCategory.value === \"all\" ? undefined : activeCategory.value;\r\n const pageSize = props.pageSize ?? DEFAULT_PAGE_SIZE;\r\n const result = await props.fetchNotifications({\r\n category: categoryParam,\r\n page: page.value,\r\n pageSize,\r\n });\r\n if (reset) {\r\n messages.value = result.list;\r\n } else {\r\n messages.value.push(...result.list);\r\n }\r\n total.value = result.total;\r\n } else {\r\n /* 未配置 API 接口 → 使用模拟数据(全量加载,客户端过滤) */\r\n loadMockData();\r\n }\r\n\r\n restoreFromCache();\r\n } finally {\r\n loading.value = false;\r\n }\r\n }\r\n\r\n /**\r\n * 加载模拟数据(全量加载,filteredMessages 负责客户端过滤)\r\n */\r\n function loadMockData() {\r\n messages.value = MOCK_MESSAGES.map((m) => ({ ...m }));\r\n total.value = MOCK_MESSAGES.length;\r\n }\r\n\r\n /**\r\n * 加载更多\r\n */\r\n async function loadMore() {\r\n if (!hasMore.value || loading.value) return;\r\n page.value++;\r\n await fetchMessages();\r\n }\r\n\r\n /**\r\n * 标记指定消息为已读\r\n */\r\n async function markAsRead(ids: string[]) {\r\n const idSet = new Set(ids);\r\n\r\n /* 乐观更新 */\r\n for (const msg of messages.value) {\r\n if (idSet.has(msg.id)) {\r\n msg.status = \"read\";\r\n }\r\n }\r\n persistToCache();\r\n\r\n /* 如果配置了 API → 同步到服务端 */\r\n if (props.markAsRead) {\r\n try {\r\n await props.markAsRead(ids);\r\n } catch {\r\n /* 回滚 */\r\n for (const msg of messages.value) {\r\n if (idSet.has(msg.id)) {\r\n msg.status = \"unread\";\r\n }\r\n }\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 标记全部已读\r\n */\r\n async function markAllAsRead(category?: NotificationCategory) {\r\n const targetMessages = category\r\n ? messages.value.filter(\r\n (m) => m.category === category && m.status === \"unread\",\r\n )\r\n : messages.value.filter((m) => m.status === \"unread\");\r\n\r\n /* 乐观更新 */\r\n for (const msg of targetMessages) {\r\n msg.status = \"read\";\r\n }\r\n persistToCache();\r\n\r\n if (props.markAllRead) {\r\n try {\r\n await props.markAllRead(category);\r\n } catch {\r\n /* 回滚 */\r\n for (const msg of targetMessages) {\r\n msg.status = \"unread\";\r\n }\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 删除消息\r\n */\r\n async function deleteMessages(ids: string[]) {\r\n const idSet = new Set(ids);\r\n const backup = [...messages.value];\r\n\r\n /* 乐观更新 */\r\n messages.value = messages.value.filter((m) => !idSet.has(m.id));\r\n total.value = Math.max(0, total.value - ids.length);\r\n persistToCache();\r\n\r\n if (props.deleteNotification) {\r\n try {\r\n await props.deleteNotification(ids);\r\n } catch {\r\n messages.value = backup;\r\n total.value += ids.length;\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 清空消息\r\n */\r\n async function clearMessages(category?: NotificationCategory) {\r\n const backup = [...messages.value];\r\n const backupTotal = total.value;\r\n\r\n if (category) {\r\n messages.value = messages.value.filter((m) => m.category !== category);\r\n } else {\r\n messages.value = [];\r\n }\r\n total.value = messages.value.length;\r\n persistToCache();\r\n\r\n if (props.clearNotifications) {\r\n try {\r\n await props.clearNotifications(category);\r\n } catch {\r\n messages.value = backup;\r\n total.value = backupTotal;\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /* ─── WebSocket 桥接 ─────────────────────────── */\r\n\r\n /**\r\n * 处理 WebSocket 推送消息\r\n */\r\n function handleWSMessage(payload: WSNotificationPayload) {\r\n switch (payload.type) {\r\n case \"new_message\": {\r\n const msg = payload.data as NotificationMessage;\r\n /* 去重后插入到列表头部 */\r\n if (!messages.value.some((m) => m.id === msg.id)) {\r\n messages.value.unshift(msg);\r\n total.value++;\r\n persistToCache();\r\n showDesktopNotification(msg);\r\n }\r\n break;\r\n }\r\n case \"read_sync\": {\r\n const syncMessages = payload.data as NotificationMessage[];\r\n const readIds = new Set(syncMessages.map((m) => m.id));\r\n for (const msg of messages.value) {\r\n if (readIds.has(msg.id)) msg.status = \"read\";\r\n }\r\n persistToCache();\r\n break;\r\n }\r\n case \"count_update\": {\r\n /* 服务端推送的未读数可用于校准 */\r\n break;\r\n }\r\n }\r\n }\r\n\r\n /** WebSocket 连接状态变更回调 */\r\n function handleWSStatusChange(s: WSConnectionStatus) {\r\n wsStatus.value = s;\r\n }\r\n\r\n const { connect: wsConnect, disconnect: wsDisconnect } = useNotificationWS(\r\n handleWSMessage,\r\n handleWSStatusChange,\r\n );\r\n\r\n /* ─── 桌面通知 ──────────────────────────────── */\r\n\r\n /**\r\n * 发送桌面通知\r\n */\r\n function showDesktopNotification(msg: NotificationMessage) {\r\n if (!props.desktopNotification) return;\r\n if (!(\"Notification\" in window)) return;\r\n\r\n if (Notification.permission === \"granted\") {\r\n createDesktopNotification(msg);\r\n } else if (Notification.permission !== \"denied\") {\r\n Notification.requestPermission().then((permission) => {\r\n if (permission === \"granted\") {\r\n createDesktopNotification(msg);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * 创建桌面通知实例\r\n */\r\n function createDesktopNotification(msg: NotificationMessage) {\r\n const n = new Notification(msg.title, {\r\n body: msg.summary,\r\n icon: msg.sender?.avatar || \"/robot-avatar.png\",\r\n tag: msg.id,\r\n });\r\n\r\n n.addEventListener(\"click\", () => {\r\n window.focus();\r\n selectMessage(msg);\r\n n.close();\r\n });\r\n\r\n /* 5 秒后自动关闭 */\r\n setTimeout(() => n.close(), 5000);\r\n }\r\n\r\n /* ─── 轮询 ──────────────────────────────────── */\r\n\r\n let pollingTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n /**\r\n * 启动轮询\r\n */\r\n function startPolling() {\r\n stopPolling();\r\n const interval = props.pollingInterval ?? DEFAULT_POLLING_INTERVAL;\r\n if (interval <= 0) return;\r\n\r\n pollingTimer = setInterval(() => {\r\n fetchMessages(true);\r\n }, interval);\r\n }\r\n\r\n /**\r\n * 停止轮询\r\n */\r\n function stopPolling() {\r\n if (pollingTimer) {\r\n clearInterval(pollingTimer);\r\n pollingTimer = null;\r\n }\r\n }\r\n\r\n /* ─── 交互 ──────────────────────────────────── */\r\n\r\n /**\r\n * 选中/查看消息\r\n */\r\n function selectMessage(msg: NotificationMessage) {\r\n selectedMessage.value = msg;\r\n /* 自动标记为已读 */\r\n if (msg.status === \"unread\") {\r\n markAsRead([msg.id]);\r\n }\r\n }\r\n\r\n /**\r\n * 返回列表\r\n */\r\n function clearSelection() {\r\n selectedMessage.value = null;\r\n }\r\n\r\n /**\r\n * 切换分类\r\n *\r\n * 纯客户端过滤(Mock / 已加载数据)直接切换,\r\n * API 模式则重新拉取对应分类数据。\r\n */\r\n function switchCategory(category: NotificationCategory | \"all\") {\r\n activeCategory.value = category;\r\n selectedMessage.value = null;\r\n\r\n /* 仅 API 模式需要重新拉取 */\r\n if (props.fetchNotifications) {\r\n fetchMessages(true);\r\n }\r\n }\r\n\r\n /* ─── 初始化 & 清理 ─────────────────────────── */\r\n\r\n /**\r\n * 初始化\r\n */\r\n function init() {\r\n fetchMessages(true);\r\n startPolling();\r\n\r\n /* 如果配置了 WebSocket → 建连 */\r\n if (props.wsConfig) wsConnect(props.wsConfig);\r\n }\r\n\r\n /**\r\n * 销毁\r\n */\r\n function destroy() {\r\n stopPolling();\r\n wsDisconnect();\r\n if (persistTimer) {\r\n clearTimeout(persistTimer);\r\n persistTimer = null;\r\n }\r\n }\r\n\r\n onMounted(init);\r\n onBeforeUnmount(destroy);\r\n\r\n return {\r\n /* 状态 */\r\n messages,\r\n activeCategory,\r\n loading,\r\n total,\r\n page,\r\n selectedMessage,\r\n popoverVisible,\r\n wsStatus,\r\n\r\n /* 计算 */\r\n unreadCount,\r\n unreadByCategory,\r\n filteredMessages,\r\n hasMore,\r\n\r\n /* 操作 */\r\n fetchMessages,\r\n loadMore,\r\n markAsRead,\r\n markAllAsRead,\r\n deleteMessages,\r\n clearMessages,\r\n selectMessage,\r\n clearSelection,\r\n switchCategory,\r\n\r\n /* WebSocket */\r\n connectWS: wsConnect,\r\n disconnectWS: wsDisconnect,\r\n };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知角标\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-badge\">\r\n <NTooltip trigger=\"hover\" placement=\"bottom\">\r\n <template #trigger>\r\n <div class=\"notification-badge__trigger\" @click=\"$emit('click')\">\r\n <C_Icon\r\n name=\"mdi:bell-outline\"\r\n :size=\"18\"\r\n class=\"notification-badge__icon\"\r\n />\r\n <!-- 未读角标 -->\r\n <Transition name=\"badge-bounce\">\r\n <span v-if=\"count > 0\" class=\"notification-badge__count\">\r\n {{ displayCount }}\r\n </span>\r\n </Transition>\r\n <!-- 脉冲动画(有紧急消息时) -->\r\n <span v-if=\"hasUrgent\" class=\"notification-badge__pulse\" />\r\n </div>\r\n </template>\r\n 消息通知{{ count > 0 ? `(${count} 条未读)` : \"\" }}\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport { DEFAULT_MAX_BADGE_COUNT } from \"../constants\";\r\n\r\ninterface Props {\r\n /** 未读数量 */\r\n count?: number;\r\n /** 最大显示数 */\r\n maxCount?: number;\r\n /** 是否有紧急消息 */\r\n hasUrgent?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n count: 0,\r\n maxCount: DEFAULT_MAX_BADGE_COUNT,\r\n hasUrgent: false,\r\n});\r\n\r\ndefineEmits<{\r\n click: [];\r\n}>();\r\n\r\n/** 显示文本 */\r\nconst displayCount = computed(() =>\r\n props.count > props.maxCount ? `${props.maxCount}+` : String(props.count),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-badge {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n\r\n &__trigger {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n padding: 4px;\r\n border-radius: 6px;\r\n transition: all var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n background-color: var(--c-border);\r\n\r\n .notification-badge__icon {\r\n color: var(--c-primary);\r\n }\r\n }\r\n }\r\n\r\n &__icon {\r\n color: var(--c-text-2);\r\n transition: color var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &__count {\r\n position: absolute;\r\n top: -2px;\r\n right: -4px;\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n background: #f5222d;\r\n color: #fff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n box-shadow: 0 0 0 2px var(--c-bg);\r\n }\r\n\r\n &__pulse {\r\n position: absolute;\r\n top: -2px;\r\n right: -4px;\r\n width: 16px;\r\n height: 16px;\r\n border-radius: 50%;\r\n background: rgba(245, 34, 45, 0.4);\r\n animation: pulse-ring 1.5s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n@keyframes pulse-ring {\r\n 0% {\r\n transform: scale(0.8);\r\n opacity: 1;\r\n }\r\n\r\n 100% {\r\n transform: scale(2.2);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.badge-bounce-enter-active {\r\n animation: badge-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);\r\n}\r\n\r\n.badge-bounce-leave-active {\r\n animation: badge-in 0.2s ease reverse;\r\n}\r\n\r\n@keyframes badge-in {\r\n 0% {\r\n transform: scale(0);\r\n opacity: 0;\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n opacity: 1;\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知角标\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-badge\">\r\n <NTooltip trigger=\"hover\" placement=\"bottom\">\r\n <template #trigger>\r\n <div class=\"notification-badge__trigger\" @click=\"$emit('click')\">\r\n <C_Icon\r\n name=\"mdi:bell-outline\"\r\n :size=\"18\"\r\n class=\"notification-badge__icon\"\r\n />\r\n <!-- 未读角标 -->\r\n <Transition name=\"badge-bounce\">\r\n <span v-if=\"count > 0\" class=\"notification-badge__count\">\r\n {{ displayCount }}\r\n </span>\r\n </Transition>\r\n <!-- 脉冲动画(有紧急消息时) -->\r\n <span v-if=\"hasUrgent\" class=\"notification-badge__pulse\" />\r\n </div>\r\n </template>\r\n 消息通知{{ count > 0 ? `(${count} 条未读)` : \"\" }}\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport { DEFAULT_MAX_BADGE_COUNT } from \"../constants\";\r\n\r\ninterface Props {\r\n /** 未读数量 */\r\n count?: number;\r\n /** 最大显示数 */\r\n maxCount?: number;\r\n /** 是否有紧急消息 */\r\n hasUrgent?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n count: 0,\r\n maxCount: DEFAULT_MAX_BADGE_COUNT,\r\n hasUrgent: false,\r\n});\r\n\r\ndefineEmits<{\r\n click: [];\r\n}>();\r\n\r\n/** 显示文本 */\r\nconst displayCount = computed(() =>\r\n props.count > props.maxCount ? `${props.maxCount}+` : String(props.count),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-badge {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n\r\n &__trigger {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n padding: 4px;\r\n border-radius: 6px;\r\n transition: all var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n background-color: var(--c-border);\r\n\r\n .notification-badge__icon {\r\n color: var(--c-primary);\r\n }\r\n }\r\n }\r\n\r\n &__icon {\r\n color: var(--c-text-2);\r\n transition: color var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &__count {\r\n position: absolute;\r\n top: -2px;\r\n right: -4px;\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n background: #f5222d;\r\n color: #fff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n box-shadow: 0 0 0 2px var(--c-bg);\r\n }\r\n\r\n &__pulse {\r\n position: absolute;\r\n top: -2px;\r\n right: -4px;\r\n width: 16px;\r\n height: 16px;\r\n border-radius: 50%;\r\n background: rgba(245, 34, 45, 0.4);\r\n animation: pulse-ring 1.5s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n@keyframes pulse-ring {\r\n 0% {\r\n transform: scale(0.8);\r\n opacity: 1;\r\n }\r\n\r\n 100% {\r\n transform: scale(2.2);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.badge-bounce-enter-active {\r\n animation: badge-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);\r\n}\r\n\r\n.badge-bounce-leave-active {\r\n animation: badge-in 0.2s ease reverse;\r\n}\r\n\r\n@keyframes badge-in {\r\n 0% {\r\n transform: scale(0);\r\n opacity: 0;\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n opacity: 1;\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知角标\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-badge\">\r\n <NTooltip trigger=\"hover\" placement=\"bottom\">\r\n <template #trigger>\r\n <div class=\"notification-badge__trigger\" @click=\"$emit('click')\">\r\n <C_Icon\r\n name=\"mdi:bell-outline\"\r\n :size=\"18\"\r\n class=\"notification-badge__icon\"\r\n />\r\n <!-- 未读角标 -->\r\n <Transition name=\"badge-bounce\">\r\n <span v-if=\"count > 0\" class=\"notification-badge__count\">\r\n {{ displayCount }}\r\n </span>\r\n </Transition>\r\n <!-- 脉冲动画(有紧急消息时) -->\r\n <span v-if=\"hasUrgent\" class=\"notification-badge__pulse\" />\r\n </div>\r\n </template>\r\n 消息通知{{ count > 0 ? `(${count} 条未读)` : \"\" }}\r\n </NTooltip>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport { DEFAULT_MAX_BADGE_COUNT } from \"../constants\";\r\n\r\ninterface Props {\r\n /** 未读数量 */\r\n count?: number;\r\n /** 最大显示数 */\r\n maxCount?: number;\r\n /** 是否有紧急消息 */\r\n hasUrgent?: boolean;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n count: 0,\r\n maxCount: DEFAULT_MAX_BADGE_COUNT,\r\n hasUrgent: false,\r\n});\r\n\r\ndefineEmits<{\r\n click: [];\r\n}>();\r\n\r\n/** 显示文本 */\r\nconst displayCount = computed(() =>\r\n props.count > props.maxCount ? `${props.maxCount}+` : String(props.count),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-badge {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n\r\n &__trigger {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n cursor: pointer;\r\n padding: 4px;\r\n border-radius: 6px;\r\n transition: all var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n background-color: var(--c-border);\r\n\r\n .notification-badge__icon {\r\n color: var(--c-primary);\r\n }\r\n }\r\n }\r\n\r\n &__icon {\r\n color: var(--c-text-2);\r\n transition: color var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &__count {\r\n position: absolute;\r\n top: -2px;\r\n right: -4px;\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n background: #f5222d;\r\n color: #fff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n box-shadow: 0 0 0 2px var(--c-bg);\r\n }\r\n\r\n &__pulse {\r\n position: absolute;\r\n top: -2px;\r\n right: -4px;\r\n width: 16px;\r\n height: 16px;\r\n border-radius: 50%;\r\n background: rgba(245, 34, 45, 0.4);\r\n animation: pulse-ring 1.5s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n@keyframes pulse-ring {\r\n 0% {\r\n transform: scale(0.8);\r\n opacity: 1;\r\n }\r\n\r\n 100% {\r\n transform: scale(2.2);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.badge-bounce-enter-active {\r\n animation: badge-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);\r\n}\r\n\r\n.badge-bounce-leave-active {\r\n animation: badge-in 0.2s ease reverse;\r\n}\r\n\r\n@keyframes badge-in {\r\n 0% {\r\n transform: scale(0);\r\n opacity: 0;\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n opacity: 1;\r\n }\r\n}\r\n</style>\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知时间格式化\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\n/** 时间单位阈值 */\r\nconst MINUTE = 60_000;\r\nconst HOUR = 3_600_000;\r\nconst DAY = 86_400_000;\r\nconst WEEK = 7 * DAY;\r\nconst MONTH = 30 * DAY;\r\nconst YEAR = 365 * DAY;\r\n\r\n/** 单例缓存 */\r\nlet _instance: ReturnType<typeof createNotificationFormat> | null = null;\r\n\r\n/**\r\n * 通知时间格式化(单例)\r\n *\r\n * 将 ISO 时间戳格式化为人类友好的相对时间描述,\r\n * 如 \"刚刚\"、\"5分钟前\"、\"昨天 14:30\" 等。\r\n */\r\nexport function useNotificationFormat() {\r\n if (!_instance) _instance = createNotificationFormat();\r\n return _instance;\r\n}\r\n\r\n/**\r\n * 创建格式化工具实例\r\n */\r\nfunction createNotificationFormat() {\r\n /**\r\n * 格式化相对时间\r\n * @param timestamp ISO 8601 时间字符串\r\n */\r\n function formatRelativeTime(timestamp: string): string {\r\n const date = new Date(timestamp);\r\n const now = Date.now();\r\n const diff = now - date.getTime();\r\n\r\n if (diff < 0) return \"刚刚\";\r\n if (diff < MINUTE) return \"刚刚\";\r\n if (diff < HOUR) return `${Math.floor(diff / MINUTE)}分钟前`;\r\n if (diff < DAY) return `${Math.floor(diff / HOUR)}小时前`;\r\n\r\n /* 判断是否是昨天 */\r\n const today = new Date();\r\n today.setHours(0, 0, 0, 0);\r\n const yesterday = new Date(today.getTime() - DAY);\r\n\r\n if (date >= yesterday && date < today) {\r\n return `昨天 ${padTime(date.getHours())}:${padTime(date.getMinutes())}`;\r\n }\r\n\r\n if (diff < WEEK) return `${Math.floor(diff / DAY)}天前`;\r\n if (diff < MONTH) return `${Math.floor(diff / WEEK)}周前`;\r\n if (diff < YEAR) return `${Math.floor(diff / MONTH)}个月前`;\r\n\r\n return formatFullDate(date);\r\n }\r\n\r\n /**\r\n * 格式化完整日期时间\r\n * @param date Date 对象\r\n */\r\n function formatFullDate(date: Date): string {\r\n const y = date.getFullYear();\r\n const m = padTime(date.getMonth() + 1);\r\n const d = padTime(date.getDate());\r\n const h = padTime(date.getHours());\r\n const min = padTime(date.getMinutes());\r\n return `${y}-${m}-${d} ${h}:${min}`;\r\n }\r\n\r\n /**\r\n * 补零\r\n * @param n 数字\r\n */\r\n function padTime(n: number): string {\r\n return n < 10 ? `0${n}` : String(n);\r\n }\r\n\r\n return {\r\n formatRelativeTime,\r\n formatFullDate,\r\n };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 单条消息\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div\r\n class=\"notification-item\"\r\n :class=\"{\r\n 'notification-item--unread': message.status === 'unread',\r\n 'notification-item--urgent': message.priority === 'urgent',\r\n }\"\r\n @click=\"$emit('click', message)\"\r\n >\r\n <!-- 未读指示点 -->\r\n <div class=\"notification-item__indicator\">\r\n <span\r\n v-if=\"message.status === 'unread'\"\r\n class=\"notification-item__dot\"\r\n :class=\"`notification-item__dot--${message.priority}`\"\r\n />\r\n </div>\r\n\r\n <!-- 头像 -->\r\n <div class=\"notification-item__avatar\">\r\n <NAvatar\r\n v-if=\"message.sender?.avatar\"\r\n :src=\"message.sender.avatar\"\r\n :size=\"36\"\r\n round\r\n />\r\n <div\r\n v-else\r\n class=\"notification-item__avatar-placeholder\"\r\n :class=\"`notification-item__avatar-placeholder--${message.category}`\"\r\n >\r\n <C_Icon :name=\"categoryIcon\" :size=\"16\" />\r\n </div>\r\n </div>\r\n\r\n <!-- 内容 -->\r\n <div class=\"notification-item__body\">\r\n <div class=\"notification-item__header\">\r\n <span class=\"notification-item__title\">{{ message.title }}</span>\r\n <NTag\r\n v-if=\"message.priority === 'urgent' || message.priority === 'high'\"\r\n :type=\"priorityConfig.type\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n round\r\n >\r\n {{ priorityConfig.label }}\r\n </NTag>\r\n </div>\r\n <div class=\"notification-item__summary\">\r\n {{ message.summary }}\r\n </div>\r\n <div class=\"notification-item__meta\">\r\n <span class=\"notification-item__time\">{{ formattedTime }}</span>\r\n <span v-if=\"message.sender?.name\" class=\"notification-item__sender\">\r\n {{ message.sender.name }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作区 -->\r\n <div class=\"notification-item__actions\" @click.stop>\r\n <NTooltip\r\n v-if=\"message.status === 'unread'\"\r\n trigger=\"hover\"\r\n placement=\"top\"\r\n >\r\n <template #trigger>\r\n <span\r\n class=\"notification-item__action\"\r\n @click=\"$emit('read', message.id)\"\r\n >\r\n <C_Icon name=\"mdi:check\" :size=\"16\" />\r\n </span>\r\n </template>\r\n 标记已读\r\n </NTooltip>\r\n <NTooltip trigger=\"hover\" placement=\"top\">\r\n <template #trigger>\r\n <span\r\n class=\"notification-item__action\"\r\n @click=\"$emit('delete', message.id)\"\r\n >\r\n <C_Icon name=\"mdi:delete-outline\" :size=\"16\" />\r\n </span>\r\n </template>\r\n 删除\r\n </NTooltip>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage } from \"../types\";\r\nimport { CATEGORY_MAP, PRIORITY_MAP } from \"../constants\";\r\nimport { useNotificationFormat } from \"../composables/useNotificationFormat\";\r\n\r\ninterface Props {\r\n /** 消息数据 */\r\n message: NotificationMessage;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\ndefineEmits<{\r\n click: [message: NotificationMessage];\r\n read: [id: string];\r\n delete: [id: string];\r\n}>();\r\n\r\nconst { formatRelativeTime } = useNotificationFormat();\r\n\r\n/** 分类图标 */\r\nconst categoryIcon = computed(\r\n () => CATEGORY_MAP[props.message.category]?.icon ?? \"mdi:bell-outline\",\r\n);\r\n\r\n/** 优先级配置 */\r\nconst priorityConfig = computed(\r\n () => PRIORITY_MAP[props.message.priority] ?? PRIORITY_MAP.normal,\r\n);\r\n\r\n/** 格式化时间 */\r\nconst formattedTime = computed(() =>\r\n formatRelativeTime(props.message.timestamp),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-item {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n cursor: pointer;\r\n transition: background-color var(--c-transition, 0.2s ease);\r\n border-bottom: 1px solid var(--c-border);\r\n\r\n &:last-child {\r\n border-bottom: none;\r\n }\r\n\r\n &:hover {\r\n background-color: var(--c-bg-card);\r\n\r\n .notification-item__actions {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n &--unread {\r\n background-color: color-mix(in srgb, var(--c-primary) 4%, transparent);\r\n\r\n &:hover {\r\n background-color: color-mix(in srgb, var(--c-primary) 8%, transparent);\r\n }\r\n }\r\n\r\n &--urgent {\r\n border-left: 3px solid #f5222d;\r\n padding-left: 13px;\r\n }\r\n\r\n /* ─── 子元素 ──────────────────────────── */\r\n\r\n &__indicator {\r\n flex-shrink: 0;\r\n width: 8px;\r\n display: flex;\r\n align-items: center;\r\n padding-top: 14px;\r\n }\r\n\r\n &__dot {\r\n width: 6px;\r\n height: 6px;\r\n border-radius: 50%;\r\n background: var(--c-primary);\r\n\r\n &--urgent {\r\n background: #f5222d;\r\n box-shadow: 0 0 4px rgba(245, 34, 45, 0.5);\r\n }\r\n\r\n &--high {\r\n background: #fa8c16;\r\n }\r\n\r\n &--normal {\r\n background: var(--c-primary);\r\n }\r\n\r\n &--low {\r\n background: var(--c-text-4);\r\n }\r\n }\r\n\r\n &__avatar {\r\n flex-shrink: 0;\r\n }\r\n\r\n &__avatar-placeholder {\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 16px;\r\n color: #fff;\r\n\r\n &--system {\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n }\r\n\r\n &--business {\r\n background: linear-gradient(135deg, #43e97b, #38f9d7);\r\n color: #0d1425;\r\n }\r\n\r\n &--alarm {\r\n background: linear-gradient(135deg, #f093fb, #f5576c);\r\n }\r\n }\r\n\r\n &__body {\r\n flex: 1;\r\n min-width: 0;\r\n }\r\n\r\n &__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n &__title {\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--c-text-1);\r\n line-height: 1.4;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n &__summary {\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n line-height: 1.5;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n margin-top: 6px;\r\n }\r\n\r\n &__time,\r\n &__sender {\r\n font-size: 11px;\r\n color: var(--c-text-4);\r\n }\r\n\r\n &__sender::before {\r\n content: \"·\";\r\n margin-right: 8px;\r\n }\r\n\r\n &__actions {\r\n flex-shrink: 0;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n opacity: 0;\r\n transition: opacity var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &__action {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 2px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n color: var(--c-text-4);\r\n transition: all var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n color: var(--c-primary);\r\n background-color: var(--c-border);\r\n }\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 单条消息\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div\r\n class=\"notification-item\"\r\n :class=\"{\r\n 'notification-item--unread': message.status === 'unread',\r\n 'notification-item--urgent': message.priority === 'urgent',\r\n }\"\r\n @click=\"$emit('click', message)\"\r\n >\r\n <!-- 未读指示点 -->\r\n <div class=\"notification-item__indicator\">\r\n <span\r\n v-if=\"message.status === 'unread'\"\r\n class=\"notification-item__dot\"\r\n :class=\"`notification-item__dot--${message.priority}`\"\r\n />\r\n </div>\r\n\r\n <!-- 头像 -->\r\n <div class=\"notification-item__avatar\">\r\n <NAvatar\r\n v-if=\"message.sender?.avatar\"\r\n :src=\"message.sender.avatar\"\r\n :size=\"36\"\r\n round\r\n />\r\n <div\r\n v-else\r\n class=\"notification-item__avatar-placeholder\"\r\n :class=\"`notification-item__avatar-placeholder--${message.category}`\"\r\n >\r\n <C_Icon :name=\"categoryIcon\" :size=\"16\" />\r\n </div>\r\n </div>\r\n\r\n <!-- 内容 -->\r\n <div class=\"notification-item__body\">\r\n <div class=\"notification-item__header\">\r\n <span class=\"notification-item__title\">{{ message.title }}</span>\r\n <NTag\r\n v-if=\"message.priority === 'urgent' || message.priority === 'high'\"\r\n :type=\"priorityConfig.type\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n round\r\n >\r\n {{ priorityConfig.label }}\r\n </NTag>\r\n </div>\r\n <div class=\"notification-item__summary\">\r\n {{ message.summary }}\r\n </div>\r\n <div class=\"notification-item__meta\">\r\n <span class=\"notification-item__time\">{{ formattedTime }}</span>\r\n <span v-if=\"message.sender?.name\" class=\"notification-item__sender\">\r\n {{ message.sender.name }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作区 -->\r\n <div class=\"notification-item__actions\" @click.stop>\r\n <NTooltip\r\n v-if=\"message.status === 'unread'\"\r\n trigger=\"hover\"\r\n placement=\"top\"\r\n >\r\n <template #trigger>\r\n <span\r\n class=\"notification-item__action\"\r\n @click=\"$emit('read', message.id)\"\r\n >\r\n <C_Icon name=\"mdi:check\" :size=\"16\" />\r\n </span>\r\n </template>\r\n 标记已读\r\n </NTooltip>\r\n <NTooltip trigger=\"hover\" placement=\"top\">\r\n <template #trigger>\r\n <span\r\n class=\"notification-item__action\"\r\n @click=\"$emit('delete', message.id)\"\r\n >\r\n <C_Icon name=\"mdi:delete-outline\" :size=\"16\" />\r\n </span>\r\n </template>\r\n 删除\r\n </NTooltip>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage } from \"../types\";\r\nimport { CATEGORY_MAP, PRIORITY_MAP } from \"../constants\";\r\nimport { useNotificationFormat } from \"../composables/useNotificationFormat\";\r\n\r\ninterface Props {\r\n /** 消息数据 */\r\n message: NotificationMessage;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\ndefineEmits<{\r\n click: [message: NotificationMessage];\r\n read: [id: string];\r\n delete: [id: string];\r\n}>();\r\n\r\nconst { formatRelativeTime } = useNotificationFormat();\r\n\r\n/** 分类图标 */\r\nconst categoryIcon = computed(\r\n () => CATEGORY_MAP[props.message.category]?.icon ?? \"mdi:bell-outline\",\r\n);\r\n\r\n/** 优先级配置 */\r\nconst priorityConfig = computed(\r\n () => PRIORITY_MAP[props.message.priority] ?? PRIORITY_MAP.normal,\r\n);\r\n\r\n/** 格式化时间 */\r\nconst formattedTime = computed(() =>\r\n formatRelativeTime(props.message.timestamp),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-item {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n cursor: pointer;\r\n transition: background-color var(--c-transition, 0.2s ease);\r\n border-bottom: 1px solid var(--c-border);\r\n\r\n &:last-child {\r\n border-bottom: none;\r\n }\r\n\r\n &:hover {\r\n background-color: var(--c-bg-card);\r\n\r\n .notification-item__actions {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n &--unread {\r\n background-color: color-mix(in srgb, var(--c-primary) 4%, transparent);\r\n\r\n &:hover {\r\n background-color: color-mix(in srgb, var(--c-primary) 8%, transparent);\r\n }\r\n }\r\n\r\n &--urgent {\r\n border-left: 3px solid #f5222d;\r\n padding-left: 13px;\r\n }\r\n\r\n /* ─── 子元素 ──────────────────────────── */\r\n\r\n &__indicator {\r\n flex-shrink: 0;\r\n width: 8px;\r\n display: flex;\r\n align-items: center;\r\n padding-top: 14px;\r\n }\r\n\r\n &__dot {\r\n width: 6px;\r\n height: 6px;\r\n border-radius: 50%;\r\n background: var(--c-primary);\r\n\r\n &--urgent {\r\n background: #f5222d;\r\n box-shadow: 0 0 4px rgba(245, 34, 45, 0.5);\r\n }\r\n\r\n &--high {\r\n background: #fa8c16;\r\n }\r\n\r\n &--normal {\r\n background: var(--c-primary);\r\n }\r\n\r\n &--low {\r\n background: var(--c-text-4);\r\n }\r\n }\r\n\r\n &__avatar {\r\n flex-shrink: 0;\r\n }\r\n\r\n &__avatar-placeholder {\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 16px;\r\n color: #fff;\r\n\r\n &--system {\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n }\r\n\r\n &--business {\r\n background: linear-gradient(135deg, #43e97b, #38f9d7);\r\n color: #0d1425;\r\n }\r\n\r\n &--alarm {\r\n background: linear-gradient(135deg, #f093fb, #f5576c);\r\n }\r\n }\r\n\r\n &__body {\r\n flex: 1;\r\n min-width: 0;\r\n }\r\n\r\n &__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n &__title {\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--c-text-1);\r\n line-height: 1.4;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n &__summary {\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n line-height: 1.5;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n margin-top: 6px;\r\n }\r\n\r\n &__time,\r\n &__sender {\r\n font-size: 11px;\r\n color: var(--c-text-4);\r\n }\r\n\r\n &__sender::before {\r\n content: \"·\";\r\n margin-right: 8px;\r\n }\r\n\r\n &__actions {\r\n flex-shrink: 0;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n opacity: 0;\r\n transition: opacity var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &__action {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 2px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n color: var(--c-text-4);\r\n transition: all var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n color: var(--c-primary);\r\n background-color: var(--c-border);\r\n }\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 单条消息\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div\r\n class=\"notification-item\"\r\n :class=\"{\r\n 'notification-item--unread': message.status === 'unread',\r\n 'notification-item--urgent': message.priority === 'urgent',\r\n }\"\r\n @click=\"$emit('click', message)\"\r\n >\r\n <!-- 未读指示点 -->\r\n <div class=\"notification-item__indicator\">\r\n <span\r\n v-if=\"message.status === 'unread'\"\r\n class=\"notification-item__dot\"\r\n :class=\"`notification-item__dot--${message.priority}`\"\r\n />\r\n </div>\r\n\r\n <!-- 头像 -->\r\n <div class=\"notification-item__avatar\">\r\n <NAvatar\r\n v-if=\"message.sender?.avatar\"\r\n :src=\"message.sender.avatar\"\r\n :size=\"36\"\r\n round\r\n />\r\n <div\r\n v-else\r\n class=\"notification-item__avatar-placeholder\"\r\n :class=\"`notification-item__avatar-placeholder--${message.category}`\"\r\n >\r\n <C_Icon :name=\"categoryIcon\" :size=\"16\" />\r\n </div>\r\n </div>\r\n\r\n <!-- 内容 -->\r\n <div class=\"notification-item__body\">\r\n <div class=\"notification-item__header\">\r\n <span class=\"notification-item__title\">{{ message.title }}</span>\r\n <NTag\r\n v-if=\"message.priority === 'urgent' || message.priority === 'high'\"\r\n :type=\"priorityConfig.type\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n round\r\n >\r\n {{ priorityConfig.label }}\r\n </NTag>\r\n </div>\r\n <div class=\"notification-item__summary\">\r\n {{ message.summary }}\r\n </div>\r\n <div class=\"notification-item__meta\">\r\n <span class=\"notification-item__time\">{{ formattedTime }}</span>\r\n <span v-if=\"message.sender?.name\" class=\"notification-item__sender\">\r\n {{ message.sender.name }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作区 -->\r\n <div class=\"notification-item__actions\" @click.stop>\r\n <NTooltip\r\n v-if=\"message.status === 'unread'\"\r\n trigger=\"hover\"\r\n placement=\"top\"\r\n >\r\n <template #trigger>\r\n <span\r\n class=\"notification-item__action\"\r\n @click=\"$emit('read', message.id)\"\r\n >\r\n <C_Icon name=\"mdi:check\" :size=\"16\" />\r\n </span>\r\n </template>\r\n 标记已读\r\n </NTooltip>\r\n <NTooltip trigger=\"hover\" placement=\"top\">\r\n <template #trigger>\r\n <span\r\n class=\"notification-item__action\"\r\n @click=\"$emit('delete', message.id)\"\r\n >\r\n <C_Icon name=\"mdi:delete-outline\" :size=\"16\" />\r\n </span>\r\n </template>\r\n 删除\r\n </NTooltip>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage } from \"../types\";\r\nimport { CATEGORY_MAP, PRIORITY_MAP } from \"../constants\";\r\nimport { useNotificationFormat } from \"../composables/useNotificationFormat\";\r\n\r\ninterface Props {\r\n /** 消息数据 */\r\n message: NotificationMessage;\r\n}\r\n\r\nconst props = defineProps<Props>();\r\n\r\ndefineEmits<{\r\n click: [message: NotificationMessage];\r\n read: [id: string];\r\n delete: [id: string];\r\n}>();\r\n\r\nconst { formatRelativeTime } = useNotificationFormat();\r\n\r\n/** 分类图标 */\r\nconst categoryIcon = computed(\r\n () => CATEGORY_MAP[props.message.category]?.icon ?? \"mdi:bell-outline\",\r\n);\r\n\r\n/** 优先级配置 */\r\nconst priorityConfig = computed(\r\n () => PRIORITY_MAP[props.message.priority] ?? PRIORITY_MAP.normal,\r\n);\r\n\r\n/** 格式化时间 */\r\nconst formattedTime = computed(() =>\r\n formatRelativeTime(props.message.timestamp),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-item {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 10px;\r\n padding: 12px 16px;\r\n cursor: pointer;\r\n transition: background-color var(--c-transition, 0.2s ease);\r\n border-bottom: 1px solid var(--c-border);\r\n\r\n &:last-child {\r\n border-bottom: none;\r\n }\r\n\r\n &:hover {\r\n background-color: var(--c-bg-card);\r\n\r\n .notification-item__actions {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n &--unread {\r\n background-color: color-mix(in srgb, var(--c-primary) 4%, transparent);\r\n\r\n &:hover {\r\n background-color: color-mix(in srgb, var(--c-primary) 8%, transparent);\r\n }\r\n }\r\n\r\n &--urgent {\r\n border-left: 3px solid #f5222d;\r\n padding-left: 13px;\r\n }\r\n\r\n /* ─── 子元素 ──────────────────────────── */\r\n\r\n &__indicator {\r\n flex-shrink: 0;\r\n width: 8px;\r\n display: flex;\r\n align-items: center;\r\n padding-top: 14px;\r\n }\r\n\r\n &__dot {\r\n width: 6px;\r\n height: 6px;\r\n border-radius: 50%;\r\n background: var(--c-primary);\r\n\r\n &--urgent {\r\n background: #f5222d;\r\n box-shadow: 0 0 4px rgba(245, 34, 45, 0.5);\r\n }\r\n\r\n &--high {\r\n background: #fa8c16;\r\n }\r\n\r\n &--normal {\r\n background: var(--c-primary);\r\n }\r\n\r\n &--low {\r\n background: var(--c-text-4);\r\n }\r\n }\r\n\r\n &__avatar {\r\n flex-shrink: 0;\r\n }\r\n\r\n &__avatar-placeholder {\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 50%;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 16px;\r\n color: #fff;\r\n\r\n &--system {\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n }\r\n\r\n &--business {\r\n background: linear-gradient(135deg, #43e97b, #38f9d7);\r\n color: #0d1425;\r\n }\r\n\r\n &--alarm {\r\n background: linear-gradient(135deg, #f093fb, #f5576c);\r\n }\r\n }\r\n\r\n &__body {\r\n flex: 1;\r\n min-width: 0;\r\n }\r\n\r\n &__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n &__title {\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--c-text-1);\r\n line-height: 1.4;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n &__summary {\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n line-height: 1.5;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n margin-top: 6px;\r\n }\r\n\r\n &__time,\r\n &__sender {\r\n font-size: 11px;\r\n color: var(--c-text-4);\r\n }\r\n\r\n &__sender::before {\r\n content: \"·\";\r\n margin-right: 8px;\r\n }\r\n\r\n &__actions {\r\n flex-shrink: 0;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n opacity: 0;\r\n transition: opacity var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &__action {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 24px;\r\n padding: 2px;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n color: var(--c-text-4);\r\n transition: all var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n color: var(--c-primary);\r\n background-color: var(--c-border);\r\n }\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 消息列表\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-list\">\r\n <!-- 分类 Tab -->\r\n <div class=\"notification-list__tabs\">\r\n <div\r\n v-for=\"tab in CATEGORY_TABS\"\r\n :key=\"tab.key\"\r\n class=\"notification-list__tab\"\r\n :class=\"{\r\n 'notification-list__tab--active': activeCategory === tab.key,\r\n }\"\r\n @click=\"$emit('switchCategory', tab.key)\"\r\n >\r\n <span>{{ tab.label }}</span>\r\n <span\r\n v-if=\"getTabCount(tab.key) > 0\"\r\n class=\"notification-list__tab-count\"\r\n >\r\n {{ getTabCount(tab.key) }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作栏 -->\r\n <div v-if=\"messages.length > 0\" class=\"notification-list__toolbar\">\r\n <NButton\r\n text\r\n size=\"tiny\"\r\n :disabled=\"currentUnread === 0\"\r\n @click=\"$emit('markAllRead')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:check-all\" :size=\"14\" />\r\n </template>\r\n 全部已读\r\n </NButton>\r\n <NButton text size=\"tiny\" @click=\"$emit('clear')\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-sweep-outline\" :size=\"14\" />\r\n </template>\r\n 清空\r\n </NButton>\r\n </div>\r\n\r\n <!-- 消息列表 -->\r\n <NScrollbar\r\n class=\"notification-list__scroll\"\r\n :style=\"{ maxHeight: `${scrollHeight}px` }\"\r\n >\r\n <template v-if=\"loading && messages.length === 0\">\r\n <!-- 骨架屏 -->\r\n <div v-for=\"i in 4\" :key=\"i\" class=\"notification-list__skeleton\">\r\n <NSkeleton circle :width=\"36\" :height=\"36\" />\r\n <div class=\"notification-list__skeleton-text\">\r\n <NSkeleton text :width=\"'60%'\" :height=\"14\" />\r\n <NSkeleton text :width=\"'90%'\" :height=\"12\" />\r\n <NSkeleton text :width=\"'40%'\" :height=\"10\" />\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <template v-else-if=\"messages.length > 0\">\r\n <div class=\"notification-list__items\">\r\n <NotificationItem\r\n v-for=\"msg in messages\"\r\n :key=\"msg.id\"\r\n :message=\"msg\"\r\n @click=\"$emit('itemClick', $event)\"\r\n @read=\"$emit('read', $event)\"\r\n @delete=\"$emit('delete', $event)\"\r\n />\r\n </div>\r\n\r\n <!-- 加载更多 -->\r\n <div\r\n v-if=\"hasMore\"\r\n class=\"notification-list__load-more\"\r\n @click=\"$emit('loadMore')\"\r\n >\r\n <NSpin v-if=\"loading\" :size=\"14\" />\r\n <span>{{ loading ? \"加载中...\" : \"加载更多\" }}</span>\r\n </div>\r\n </template>\r\n\r\n <!-- 空状态 -->\r\n <div v-else class=\"notification-list__empty\">\r\n <C_Icon\r\n name=\"mdi:bell-off-outline\"\r\n :size=\"48\"\r\n class=\"notification-list__empty-icon\"\r\n />\r\n <span class=\"notification-list__empty-text\">暂无消息</span>\r\n </div>\r\n </NScrollbar>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage, NotificationCategory } from \"../types\";\r\nimport { CATEGORY_TABS } from \"../constants\";\r\nimport NotificationItem from \"./NotificationItem.vue\";\r\n\r\ninterface Props {\r\n /** 消息列表 */\r\n messages: NotificationMessage[];\r\n /** 当前分类 */\r\n activeCategory: NotificationCategory | \"all\";\r\n /** 各分类未读数 */\r\n unreadByCategory: Record<string, number>;\r\n /** 总未读数 */\r\n unreadCount: number;\r\n /** 是否加载中 */\r\n loading: boolean;\r\n /** 是否有更多 */\r\n hasMore: boolean;\r\n /** 滚动区域高度 */\r\n scrollHeight?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n scrollHeight: 420,\r\n});\r\n\r\ndefineEmits<{\r\n switchCategory: [key: NotificationCategory | \"all\"];\r\n itemClick: [message: NotificationMessage];\r\n read: [id: string];\r\n delete: [id: string];\r\n markAllRead: [];\r\n clear: [];\r\n loadMore: [];\r\n}>();\r\n\r\n/** 当前分类下的未读数 */\r\nconst currentUnread = computed(() => {\r\n if (props.activeCategory === \"all\") return props.unreadCount;\r\n return props.unreadByCategory[props.activeCategory] ?? 0;\r\n});\r\n\r\n/** 获取 Tab 的未读数 */\r\nfunction getTabCount(key: NotificationCategory | \"all\"): number {\r\n if (key === \"all\") return props.unreadCount;\r\n return props.unreadByCategory[key] ?? 0;\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-list {\r\n &__tabs {\r\n display: flex;\r\n border-bottom: 1px solid var(--c-border);\r\n padding: 0 4px;\r\n }\r\n\r\n &__tab {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 10px 12px;\r\n font-size: 13px;\r\n color: var(--c-text-2);\r\n cursor: pointer;\r\n transition: all var(--c-transition, 0.2s ease);\r\n user-select: none;\r\n\r\n &::after {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 12px;\r\n right: 12px;\r\n height: 2px;\r\n background: transparent;\r\n border-radius: 1px;\r\n transition: background var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &:hover {\r\n color: var(--c-text-1);\r\n }\r\n\r\n &--active {\r\n color: var(--c-primary);\r\n font-weight: 500;\r\n\r\n &::after {\r\n background: var(--c-primary);\r\n }\r\n }\r\n }\r\n\r\n &__tab-count {\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n background: #f5222d;\r\n color: #fff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n }\r\n\r\n &__toolbar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: 12px;\r\n padding: 6px 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__scroll {\r\n flex: 1;\r\n }\r\n\r\n &__skeleton {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 14px 16px;\r\n }\r\n\r\n &__skeleton-text {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n }\r\n\r\n &__load-more {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 6px;\r\n padding: 12px;\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n cursor: pointer;\r\n transition: color var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n color: var(--c-primary);\r\n }\r\n }\r\n\r\n &__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px 0;\r\n gap: 12px;\r\n }\r\n\r\n &__empty-icon {\r\n color: var(--c-text-4);\r\n opacity: 0.5;\r\n }\r\n\r\n &__empty-text {\r\n font-size: 13px;\r\n color: var(--c-text-4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 消息列表\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-list\">\r\n <!-- 分类 Tab -->\r\n <div class=\"notification-list__tabs\">\r\n <div\r\n v-for=\"tab in CATEGORY_TABS\"\r\n :key=\"tab.key\"\r\n class=\"notification-list__tab\"\r\n :class=\"{\r\n 'notification-list__tab--active': activeCategory === tab.key,\r\n }\"\r\n @click=\"$emit('switchCategory', tab.key)\"\r\n >\r\n <span>{{ tab.label }}</span>\r\n <span\r\n v-if=\"getTabCount(tab.key) > 0\"\r\n class=\"notification-list__tab-count\"\r\n >\r\n {{ getTabCount(tab.key) }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作栏 -->\r\n <div v-if=\"messages.length > 0\" class=\"notification-list__toolbar\">\r\n <NButton\r\n text\r\n size=\"tiny\"\r\n :disabled=\"currentUnread === 0\"\r\n @click=\"$emit('markAllRead')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:check-all\" :size=\"14\" />\r\n </template>\r\n 全部已读\r\n </NButton>\r\n <NButton text size=\"tiny\" @click=\"$emit('clear')\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-sweep-outline\" :size=\"14\" />\r\n </template>\r\n 清空\r\n </NButton>\r\n </div>\r\n\r\n <!-- 消息列表 -->\r\n <NScrollbar\r\n class=\"notification-list__scroll\"\r\n :style=\"{ maxHeight: `${scrollHeight}px` }\"\r\n >\r\n <template v-if=\"loading && messages.length === 0\">\r\n <!-- 骨架屏 -->\r\n <div v-for=\"i in 4\" :key=\"i\" class=\"notification-list__skeleton\">\r\n <NSkeleton circle :width=\"36\" :height=\"36\" />\r\n <div class=\"notification-list__skeleton-text\">\r\n <NSkeleton text :width=\"'60%'\" :height=\"14\" />\r\n <NSkeleton text :width=\"'90%'\" :height=\"12\" />\r\n <NSkeleton text :width=\"'40%'\" :height=\"10\" />\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <template v-else-if=\"messages.length > 0\">\r\n <div class=\"notification-list__items\">\r\n <NotificationItem\r\n v-for=\"msg in messages\"\r\n :key=\"msg.id\"\r\n :message=\"msg\"\r\n @click=\"$emit('itemClick', $event)\"\r\n @read=\"$emit('read', $event)\"\r\n @delete=\"$emit('delete', $event)\"\r\n />\r\n </div>\r\n\r\n <!-- 加载更多 -->\r\n <div\r\n v-if=\"hasMore\"\r\n class=\"notification-list__load-more\"\r\n @click=\"$emit('loadMore')\"\r\n >\r\n <NSpin v-if=\"loading\" :size=\"14\" />\r\n <span>{{ loading ? \"加载中...\" : \"加载更多\" }}</span>\r\n </div>\r\n </template>\r\n\r\n <!-- 空状态 -->\r\n <div v-else class=\"notification-list__empty\">\r\n <C_Icon\r\n name=\"mdi:bell-off-outline\"\r\n :size=\"48\"\r\n class=\"notification-list__empty-icon\"\r\n />\r\n <span class=\"notification-list__empty-text\">暂无消息</span>\r\n </div>\r\n </NScrollbar>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage, NotificationCategory } from \"../types\";\r\nimport { CATEGORY_TABS } from \"../constants\";\r\nimport NotificationItem from \"./NotificationItem.vue\";\r\n\r\ninterface Props {\r\n /** 消息列表 */\r\n messages: NotificationMessage[];\r\n /** 当前分类 */\r\n activeCategory: NotificationCategory | \"all\";\r\n /** 各分类未读数 */\r\n unreadByCategory: Record<string, number>;\r\n /** 总未读数 */\r\n unreadCount: number;\r\n /** 是否加载中 */\r\n loading: boolean;\r\n /** 是否有更多 */\r\n hasMore: boolean;\r\n /** 滚动区域高度 */\r\n scrollHeight?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n scrollHeight: 420,\r\n});\r\n\r\ndefineEmits<{\r\n switchCategory: [key: NotificationCategory | \"all\"];\r\n itemClick: [message: NotificationMessage];\r\n read: [id: string];\r\n delete: [id: string];\r\n markAllRead: [];\r\n clear: [];\r\n loadMore: [];\r\n}>();\r\n\r\n/** 当前分类下的未读数 */\r\nconst currentUnread = computed(() => {\r\n if (props.activeCategory === \"all\") return props.unreadCount;\r\n return props.unreadByCategory[props.activeCategory] ?? 0;\r\n});\r\n\r\n/** 获取 Tab 的未读数 */\r\nfunction getTabCount(key: NotificationCategory | \"all\"): number {\r\n if (key === \"all\") return props.unreadCount;\r\n return props.unreadByCategory[key] ?? 0;\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-list {\r\n &__tabs {\r\n display: flex;\r\n border-bottom: 1px solid var(--c-border);\r\n padding: 0 4px;\r\n }\r\n\r\n &__tab {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 10px 12px;\r\n font-size: 13px;\r\n color: var(--c-text-2);\r\n cursor: pointer;\r\n transition: all var(--c-transition, 0.2s ease);\r\n user-select: none;\r\n\r\n &::after {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 12px;\r\n right: 12px;\r\n height: 2px;\r\n background: transparent;\r\n border-radius: 1px;\r\n transition: background var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &:hover {\r\n color: var(--c-text-1);\r\n }\r\n\r\n &--active {\r\n color: var(--c-primary);\r\n font-weight: 500;\r\n\r\n &::after {\r\n background: var(--c-primary);\r\n }\r\n }\r\n }\r\n\r\n &__tab-count {\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n background: #f5222d;\r\n color: #fff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n }\r\n\r\n &__toolbar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: 12px;\r\n padding: 6px 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__scroll {\r\n flex: 1;\r\n }\r\n\r\n &__skeleton {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 14px 16px;\r\n }\r\n\r\n &__skeleton-text {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n }\r\n\r\n &__load-more {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 6px;\r\n padding: 12px;\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n cursor: pointer;\r\n transition: color var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n color: var(--c-primary);\r\n }\r\n }\r\n\r\n &__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px 0;\r\n gap: 12px;\r\n }\r\n\r\n &__empty-icon {\r\n color: var(--c-text-4);\r\n opacity: 0.5;\r\n }\r\n\r\n &__empty-text {\r\n font-size: 13px;\r\n color: var(--c-text-4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 消息列表\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-list\">\r\n <!-- 分类 Tab -->\r\n <div class=\"notification-list__tabs\">\r\n <div\r\n v-for=\"tab in CATEGORY_TABS\"\r\n :key=\"tab.key\"\r\n class=\"notification-list__tab\"\r\n :class=\"{\r\n 'notification-list__tab--active': activeCategory === tab.key,\r\n }\"\r\n @click=\"$emit('switchCategory', tab.key)\"\r\n >\r\n <span>{{ tab.label }}</span>\r\n <span\r\n v-if=\"getTabCount(tab.key) > 0\"\r\n class=\"notification-list__tab-count\"\r\n >\r\n {{ getTabCount(tab.key) }}\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- 操作栏 -->\r\n <div v-if=\"messages.length > 0\" class=\"notification-list__toolbar\">\r\n <NButton\r\n text\r\n size=\"tiny\"\r\n :disabled=\"currentUnread === 0\"\r\n @click=\"$emit('markAllRead')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:check-all\" :size=\"14\" />\r\n </template>\r\n 全部已读\r\n </NButton>\r\n <NButton text size=\"tiny\" @click=\"$emit('clear')\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:delete-sweep-outline\" :size=\"14\" />\r\n </template>\r\n 清空\r\n </NButton>\r\n </div>\r\n\r\n <!-- 消息列表 -->\r\n <NScrollbar\r\n class=\"notification-list__scroll\"\r\n :style=\"{ maxHeight: `${scrollHeight}px` }\"\r\n >\r\n <template v-if=\"loading && messages.length === 0\">\r\n <!-- 骨架屏 -->\r\n <div v-for=\"i in 4\" :key=\"i\" class=\"notification-list__skeleton\">\r\n <NSkeleton circle :width=\"36\" :height=\"36\" />\r\n <div class=\"notification-list__skeleton-text\">\r\n <NSkeleton text :width=\"'60%'\" :height=\"14\" />\r\n <NSkeleton text :width=\"'90%'\" :height=\"12\" />\r\n <NSkeleton text :width=\"'40%'\" :height=\"10\" />\r\n </div>\r\n </div>\r\n </template>\r\n\r\n <template v-else-if=\"messages.length > 0\">\r\n <div class=\"notification-list__items\">\r\n <NotificationItem\r\n v-for=\"msg in messages\"\r\n :key=\"msg.id\"\r\n :message=\"msg\"\r\n @click=\"$emit('itemClick', $event)\"\r\n @read=\"$emit('read', $event)\"\r\n @delete=\"$emit('delete', $event)\"\r\n />\r\n </div>\r\n\r\n <!-- 加载更多 -->\r\n <div\r\n v-if=\"hasMore\"\r\n class=\"notification-list__load-more\"\r\n @click=\"$emit('loadMore')\"\r\n >\r\n <NSpin v-if=\"loading\" :size=\"14\" />\r\n <span>{{ loading ? \"加载中...\" : \"加载更多\" }}</span>\r\n </div>\r\n </template>\r\n\r\n <!-- 空状态 -->\r\n <div v-else class=\"notification-list__empty\">\r\n <C_Icon\r\n name=\"mdi:bell-off-outline\"\r\n :size=\"48\"\r\n class=\"notification-list__empty-icon\"\r\n />\r\n <span class=\"notification-list__empty-text\">暂无消息</span>\r\n </div>\r\n </NScrollbar>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage, NotificationCategory } from \"../types\";\r\nimport { CATEGORY_TABS } from \"../constants\";\r\nimport NotificationItem from \"./NotificationItem.vue\";\r\n\r\ninterface Props {\r\n /** 消息列表 */\r\n messages: NotificationMessage[];\r\n /** 当前分类 */\r\n activeCategory: NotificationCategory | \"all\";\r\n /** 各分类未读数 */\r\n unreadByCategory: Record<string, number>;\r\n /** 总未读数 */\r\n unreadCount: number;\r\n /** 是否加载中 */\r\n loading: boolean;\r\n /** 是否有更多 */\r\n hasMore: boolean;\r\n /** 滚动区域高度 */\r\n scrollHeight?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n scrollHeight: 420,\r\n});\r\n\r\ndefineEmits<{\r\n switchCategory: [key: NotificationCategory | \"all\"];\r\n itemClick: [message: NotificationMessage];\r\n read: [id: string];\r\n delete: [id: string];\r\n markAllRead: [];\r\n clear: [];\r\n loadMore: [];\r\n}>();\r\n\r\n/** 当前分类下的未读数 */\r\nconst currentUnread = computed(() => {\r\n if (props.activeCategory === \"all\") return props.unreadCount;\r\n return props.unreadByCategory[props.activeCategory] ?? 0;\r\n});\r\n\r\n/** 获取 Tab 的未读数 */\r\nfunction getTabCount(key: NotificationCategory | \"all\"): number {\r\n if (key === \"all\") return props.unreadCount;\r\n return props.unreadByCategory[key] ?? 0;\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-list {\r\n &__tabs {\r\n display: flex;\r\n border-bottom: 1px solid var(--c-border);\r\n padding: 0 4px;\r\n }\r\n\r\n &__tab {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 10px 12px;\r\n font-size: 13px;\r\n color: var(--c-text-2);\r\n cursor: pointer;\r\n transition: all var(--c-transition, 0.2s ease);\r\n user-select: none;\r\n\r\n &::after {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 12px;\r\n right: 12px;\r\n height: 2px;\r\n background: transparent;\r\n border-radius: 1px;\r\n transition: background var(--c-transition, 0.2s ease);\r\n }\r\n\r\n &:hover {\r\n color: var(--c-text-1);\r\n }\r\n\r\n &--active {\r\n color: var(--c-primary);\r\n font-weight: 500;\r\n\r\n &::after {\r\n background: var(--c-primary);\r\n }\r\n }\r\n }\r\n\r\n &__tab-count {\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n background: #f5222d;\r\n color: #fff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n }\r\n\r\n &__toolbar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: 12px;\r\n padding: 6px 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__scroll {\r\n flex: 1;\r\n }\r\n\r\n &__skeleton {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 14px 16px;\r\n }\r\n\r\n &__skeleton-text {\r\n flex: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n }\r\n\r\n &__load-more {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 6px;\r\n padding: 12px;\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n cursor: pointer;\r\n transition: color var(--c-transition, 0.2s ease);\r\n\r\n &:hover {\r\n color: var(--c-primary);\r\n }\r\n }\r\n\r\n &__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px 0;\r\n gap: 12px;\r\n }\r\n\r\n &__empty-icon {\r\n color: var(--c-text-4);\r\n opacity: 0.5;\r\n }\r\n\r\n &__empty-text {\r\n font-size: 13px;\r\n color: var(--c-text-4);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 消息详情\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-detail\">\r\n <!-- 返回栏 -->\r\n <div class=\"notification-detail__back\">\r\n <NButton text size=\"small\" @click=\"$emit('back')\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:arrow-left\" :size=\"16\" />\r\n </template>\r\n 返回列表\r\n </NButton>\r\n </div>\r\n\r\n <!-- 消息头部 -->\r\n <div class=\"notification-detail__header\">\r\n <h3 class=\"notification-detail__title\">{{ message.title }}</h3>\r\n <div class=\"notification-detail__meta\">\r\n <NTag :type=\"categoryTag.color\" size=\"tiny\" :bordered=\"false\" round>\r\n <template #icon>\r\n <C_Icon :name=\"categoryTag.icon\" :size=\"12\" />\r\n </template>\r\n {{ categoryTag.label }}\r\n </NTag>\r\n <NTag\r\n v-if=\"message.priority === 'urgent' || message.priority === 'high'\"\r\n :type=\"priorityTag.type\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n round\r\n >\r\n {{ priorityTag.label }}\r\n </NTag>\r\n <span class=\"notification-detail__time\">{{ formattedTime }}</span>\r\n </div>\r\n <div v-if=\"message.sender\" class=\"notification-detail__sender\">\r\n <NAvatar\r\n v-if=\"message.sender.avatar\"\r\n :src=\"message.sender.avatar\"\r\n :size=\"20\"\r\n round\r\n />\r\n <span>{{ message.sender.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 消息内容 -->\r\n <NScrollbar\r\n class=\"notification-detail__content\"\r\n :style=\"{ maxHeight: `${scrollHeight}px` }\"\r\n >\r\n <div\r\n v-if=\"message.content\"\r\n class=\"notification-detail__body\"\r\n v-html=\"message.content\"\r\n />\r\n <div v-else class=\"notification-detail__body\">\r\n {{ message.summary }}\r\n </div>\r\n </NScrollbar>\r\n\r\n <!-- 操作区 -->\r\n <div v-if=\"message.actionUrl\" class=\"notification-detail__footer\">\r\n <NButton type=\"primary\" size=\"small\" @click=\"handleAction\">\r\n {{ message.actionText || \"查看详情\" }}\r\n <template #icon>\r\n <C_Icon name=\"mdi:arrow-right\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage } from \"../types\";\r\nimport { CATEGORY_MAP, PRIORITY_MAP } from \"../constants\";\r\nimport { useNotificationFormat } from \"../composables/useNotificationFormat\";\r\n\r\ninterface Props {\r\n /** 消息数据 */\r\n message: NotificationMessage;\r\n /** 内容滚动区高度 */\r\n scrollHeight?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n scrollHeight: 320,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n back: [];\r\n action: [url: string];\r\n navigate: [url: string];\r\n}>();\r\nconst { formatRelativeTime } = useNotificationFormat();\r\n\r\n/** 分类标签 */\r\nconst categoryTag = computed(\r\n () => CATEGORY_MAP[props.message.category] ?? CATEGORY_MAP.system,\r\n);\r\n\r\n/** 优先级标签 */\r\nconst priorityTag = computed(\r\n () => PRIORITY_MAP[props.message.priority] ?? PRIORITY_MAP.normal,\r\n);\r\n\r\n/** 格式化时间 */\r\nconst formattedTime = computed(() =>\r\n formatRelativeTime(props.message.timestamp),\r\n);\r\n\r\n/** 处理操作按钮点击 */\r\nfunction handleAction() {\r\n const url = props.message.actionUrl;\r\n if (!url) return;\r\n\r\n emit(\"action\", url);\r\n\r\n if (url.startsWith(\"http\")) {\r\n window.open(url, \"_blank\");\r\n } else {\r\n emit(\"navigate\", url);\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-detail {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n\r\n &__back {\r\n padding: 8px 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__header {\r\n padding: 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n color: var(--c-text-1);\r\n line-height: 1.5;\r\n margin: 0 0 8px;\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n }\r\n\r\n &__time {\r\n font-size: 11px;\r\n color: var(--c-text-4);\r\n }\r\n\r\n &__sender {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-top: 8px;\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n }\r\n\r\n &__content {\r\n flex: 1;\r\n min-height: 0;\r\n }\r\n\r\n &__body {\r\n padding: 16px;\r\n font-size: 13px;\r\n line-height: 1.7;\r\n color: var(--c-text-1);\r\n word-break: break-word;\r\n\r\n :deep(p) {\r\n margin: 0 0 8px;\r\n }\r\n\r\n :deep(ul),\r\n :deep(ol) {\r\n padding-left: 20px;\r\n margin: 8px 0;\r\n }\r\n\r\n :deep(li) {\r\n margin: 4px 0;\r\n }\r\n\r\n :deep(strong) {\r\n font-weight: 600;\r\n color: var(--c-primary);\r\n }\r\n }\r\n\r\n &__footer {\r\n padding: 12px 16px;\r\n border-top: 1px solid var(--c-border);\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 消息详情\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-detail\">\r\n <!-- 返回栏 -->\r\n <div class=\"notification-detail__back\">\r\n <NButton text size=\"small\" @click=\"$emit('back')\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:arrow-left\" :size=\"16\" />\r\n </template>\r\n 返回列表\r\n </NButton>\r\n </div>\r\n\r\n <!-- 消息头部 -->\r\n <div class=\"notification-detail__header\">\r\n <h3 class=\"notification-detail__title\">{{ message.title }}</h3>\r\n <div class=\"notification-detail__meta\">\r\n <NTag :type=\"categoryTag.color\" size=\"tiny\" :bordered=\"false\" round>\r\n <template #icon>\r\n <C_Icon :name=\"categoryTag.icon\" :size=\"12\" />\r\n </template>\r\n {{ categoryTag.label }}\r\n </NTag>\r\n <NTag\r\n v-if=\"message.priority === 'urgent' || message.priority === 'high'\"\r\n :type=\"priorityTag.type\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n round\r\n >\r\n {{ priorityTag.label }}\r\n </NTag>\r\n <span class=\"notification-detail__time\">{{ formattedTime }}</span>\r\n </div>\r\n <div v-if=\"message.sender\" class=\"notification-detail__sender\">\r\n <NAvatar\r\n v-if=\"message.sender.avatar\"\r\n :src=\"message.sender.avatar\"\r\n :size=\"20\"\r\n round\r\n />\r\n <span>{{ message.sender.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 消息内容 -->\r\n <NScrollbar\r\n class=\"notification-detail__content\"\r\n :style=\"{ maxHeight: `${scrollHeight}px` }\"\r\n >\r\n <div\r\n v-if=\"message.content\"\r\n class=\"notification-detail__body\"\r\n v-html=\"message.content\"\r\n />\r\n <div v-else class=\"notification-detail__body\">\r\n {{ message.summary }}\r\n </div>\r\n </NScrollbar>\r\n\r\n <!-- 操作区 -->\r\n <div v-if=\"message.actionUrl\" class=\"notification-detail__footer\">\r\n <NButton type=\"primary\" size=\"small\" @click=\"handleAction\">\r\n {{ message.actionText || \"查看详情\" }}\r\n <template #icon>\r\n <C_Icon name=\"mdi:arrow-right\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage } from \"../types\";\r\nimport { CATEGORY_MAP, PRIORITY_MAP } from \"../constants\";\r\nimport { useNotificationFormat } from \"../composables/useNotificationFormat\";\r\n\r\ninterface Props {\r\n /** 消息数据 */\r\n message: NotificationMessage;\r\n /** 内容滚动区高度 */\r\n scrollHeight?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n scrollHeight: 320,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n back: [];\r\n action: [url: string];\r\n navigate: [url: string];\r\n}>();\r\nconst { formatRelativeTime } = useNotificationFormat();\r\n\r\n/** 分类标签 */\r\nconst categoryTag = computed(\r\n () => CATEGORY_MAP[props.message.category] ?? CATEGORY_MAP.system,\r\n);\r\n\r\n/** 优先级标签 */\r\nconst priorityTag = computed(\r\n () => PRIORITY_MAP[props.message.priority] ?? PRIORITY_MAP.normal,\r\n);\r\n\r\n/** 格式化时间 */\r\nconst formattedTime = computed(() =>\r\n formatRelativeTime(props.message.timestamp),\r\n);\r\n\r\n/** 处理操作按钮点击 */\r\nfunction handleAction() {\r\n const url = props.message.actionUrl;\r\n if (!url) return;\r\n\r\n emit(\"action\", url);\r\n\r\n if (url.startsWith(\"http\")) {\r\n window.open(url, \"_blank\");\r\n } else {\r\n emit(\"navigate\", url);\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-detail {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n\r\n &__back {\r\n padding: 8px 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__header {\r\n padding: 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n color: var(--c-text-1);\r\n line-height: 1.5;\r\n margin: 0 0 8px;\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n }\r\n\r\n &__time {\r\n font-size: 11px;\r\n color: var(--c-text-4);\r\n }\r\n\r\n &__sender {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-top: 8px;\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n }\r\n\r\n &__content {\r\n flex: 1;\r\n min-height: 0;\r\n }\r\n\r\n &__body {\r\n padding: 16px;\r\n font-size: 13px;\r\n line-height: 1.7;\r\n color: var(--c-text-1);\r\n word-break: break-word;\r\n\r\n :deep(p) {\r\n margin: 0 0 8px;\r\n }\r\n\r\n :deep(ul),\r\n :deep(ol) {\r\n padding-left: 20px;\r\n margin: 8px 0;\r\n }\r\n\r\n :deep(li) {\r\n margin: 4px 0;\r\n }\r\n\r\n :deep(strong) {\r\n font-weight: 600;\r\n color: var(--c-primary);\r\n }\r\n }\r\n\r\n &__footer {\r\n padding: 12px 16px;\r\n border-top: 1px solid var(--c-border);\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 消息详情\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"notification-detail\">\r\n <!-- 返回栏 -->\r\n <div class=\"notification-detail__back\">\r\n <NButton text size=\"small\" @click=\"$emit('back')\">\r\n <template #icon>\r\n <C_Icon name=\"mdi:arrow-left\" :size=\"16\" />\r\n </template>\r\n 返回列表\r\n </NButton>\r\n </div>\r\n\r\n <!-- 消息头部 -->\r\n <div class=\"notification-detail__header\">\r\n <h3 class=\"notification-detail__title\">{{ message.title }}</h3>\r\n <div class=\"notification-detail__meta\">\r\n <NTag :type=\"categoryTag.color\" size=\"tiny\" :bordered=\"false\" round>\r\n <template #icon>\r\n <C_Icon :name=\"categoryTag.icon\" :size=\"12\" />\r\n </template>\r\n {{ categoryTag.label }}\r\n </NTag>\r\n <NTag\r\n v-if=\"message.priority === 'urgent' || message.priority === 'high'\"\r\n :type=\"priorityTag.type\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n round\r\n >\r\n {{ priorityTag.label }}\r\n </NTag>\r\n <span class=\"notification-detail__time\">{{ formattedTime }}</span>\r\n </div>\r\n <div v-if=\"message.sender\" class=\"notification-detail__sender\">\r\n <NAvatar\r\n v-if=\"message.sender.avatar\"\r\n :src=\"message.sender.avatar\"\r\n :size=\"20\"\r\n round\r\n />\r\n <span>{{ message.sender.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 消息内容 -->\r\n <NScrollbar\r\n class=\"notification-detail__content\"\r\n :style=\"{ maxHeight: `${scrollHeight}px` }\"\r\n >\r\n <div\r\n v-if=\"message.content\"\r\n class=\"notification-detail__body\"\r\n v-html=\"message.content\"\r\n />\r\n <div v-else class=\"notification-detail__body\">\r\n {{ message.summary }}\r\n </div>\r\n </NScrollbar>\r\n\r\n <!-- 操作区 -->\r\n <div v-if=\"message.actionUrl\" class=\"notification-detail__footer\">\r\n <NButton type=\"primary\" size=\"small\" @click=\"handleAction\">\r\n {{ message.actionText || \"查看详情\" }}\r\n <template #icon>\r\n <C_Icon name=\"mdi:arrow-right\" :size=\"16\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { NotificationMessage } from \"../types\";\r\nimport { CATEGORY_MAP, PRIORITY_MAP } from \"../constants\";\r\nimport { useNotificationFormat } from \"../composables/useNotificationFormat\";\r\n\r\ninterface Props {\r\n /** 消息数据 */\r\n message: NotificationMessage;\r\n /** 内容滚动区高度 */\r\n scrollHeight?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n scrollHeight: 320,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n back: [];\r\n action: [url: string];\r\n navigate: [url: string];\r\n}>();\r\nconst { formatRelativeTime } = useNotificationFormat();\r\n\r\n/** 分类标签 */\r\nconst categoryTag = computed(\r\n () => CATEGORY_MAP[props.message.category] ?? CATEGORY_MAP.system,\r\n);\r\n\r\n/** 优先级标签 */\r\nconst priorityTag = computed(\r\n () => PRIORITY_MAP[props.message.priority] ?? PRIORITY_MAP.normal,\r\n);\r\n\r\n/** 格式化时间 */\r\nconst formattedTime = computed(() =>\r\n formatRelativeTime(props.message.timestamp),\r\n);\r\n\r\n/** 处理操作按钮点击 */\r\nfunction handleAction() {\r\n const url = props.message.actionUrl;\r\n if (!url) return;\r\n\r\n emit(\"action\", url);\r\n\r\n if (url.startsWith(\"http\")) {\r\n window.open(url, \"_blank\");\r\n } else {\r\n emit(\"navigate\", url);\r\n }\r\n}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.notification-detail {\r\n display: flex;\r\n flex-direction: column;\r\n height: 100%;\r\n\r\n &__back {\r\n padding: 8px 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__header {\r\n padding: 16px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n color: var(--c-text-1);\r\n line-height: 1.5;\r\n margin: 0 0 8px;\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n }\r\n\r\n &__time {\r\n font-size: 11px;\r\n color: var(--c-text-4);\r\n }\r\n\r\n &__sender {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-top: 8px;\r\n font-size: 12px;\r\n color: var(--c-text-2);\r\n }\r\n\r\n &__content {\r\n flex: 1;\r\n min-height: 0;\r\n }\r\n\r\n &__body {\r\n padding: 16px;\r\n font-size: 13px;\r\n line-height: 1.7;\r\n color: var(--c-text-1);\r\n word-break: break-word;\r\n\r\n :deep(p) {\r\n margin: 0 0 8px;\r\n }\r\n\r\n :deep(ul),\r\n :deep(ol) {\r\n padding-left: 20px;\r\n margin: 8px 0;\r\n }\r\n\r\n :deep(li) {\r\n margin: 4px 0;\r\n }\r\n\r\n :deep(strong) {\r\n font-weight: 600;\r\n color: var(--c-primary);\r\n }\r\n }\r\n\r\n &__footer {\r\n padding: 12px 16px;\r\n border-top: 1px solid var(--c-border);\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-notification-center\">\r\n <NPopover\r\n v-model:show=\"core.popoverVisible.value\"\r\n :width=\"420\"\r\n trigger=\"click\"\r\n placement=\"bottom-end\"\r\n :show-arrow=\"false\"\r\n content-class=\"c-notification-center__popover\"\r\n @after-leave=\"handlePopoverClose\"\r\n >\r\n <!-- 触发器:角标铃铛 -->\r\n <template #trigger>\r\n <NotificationBadge\r\n :count=\"core.unreadCount.value\"\r\n :max-count=\"props.maxBadgeCount\"\r\n :has-urgent=\"hasUrgentMessage\"\r\n @click=\"handleBadgeClick\"\r\n />\r\n </template>\r\n\r\n <!-- 面板内容 -->\r\n <div class=\"c-notification-center__panel\">\r\n <!-- 头部标题 -->\r\n <div class=\"c-notification-center__header\">\r\n <span class=\"c-notification-center__title\">消息中心</span>\r\n <div class=\"c-notification-center__header-extra\">\r\n <!-- WebSocket 连接状态 -->\r\n <NTooltip v-if=\"props.wsConfig\" trigger=\"hover\" placement=\"top\">\r\n <template #trigger>\r\n <span\r\n class=\"c-notification-center__ws-dot\"\r\n :class=\"`c-notification-center__ws-dot--${core.wsStatus.value}`\"\r\n />\r\n </template>\r\n {{ wsStatusText }}\r\n </NTooltip>\r\n </div>\r\n </div>\r\n\r\n <!-- 列表 / 详情切换 -->\r\n <Transition name=\"slide-fade\" mode=\"out-in\">\r\n <!-- 消息详情 -->\r\n <NotificationDetail\r\n v-if=\"core.selectedMessage.value\"\r\n :key=\"core.selectedMessage.value.id\"\r\n :message=\"core.selectedMessage.value\"\r\n @back=\"core.clearSelection\"\r\n @action=\"handleAction\"\r\n @navigate=\"(url) => emit('navigate', url)\"\r\n />\r\n\r\n <!-- 消息列表 -->\r\n <NotificationList\r\n v-else\r\n :messages=\"core.filteredMessages.value\"\r\n :active-category=\"core.activeCategory.value\"\r\n :unread-by-category=\"core.unreadByCategory.value\"\r\n :unread-count=\"core.unreadCount.value\"\r\n :loading=\"core.loading.value\"\r\n :has-more=\"core.hasMore.value\"\r\n @switch-category=\"core.switchCategory\"\r\n @item-click=\"handleItemClick\"\r\n @read=\"handleRead\"\r\n @delete=\"handleDelete\"\r\n @mark-all-read=\"handleMarkAllRead\"\r\n @clear=\"handleClear\"\r\n @load-more=\"core.loadMore\"\r\n />\r\n </Transition>\r\n </div>\r\n </NPopover>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, watch } from \"vue\";\r\nimport type {\r\n NotificationCenterProps,\r\n NotificationCenterExpose,\r\n NotificationMessage,\r\n NotificationCategory,\r\n} from \"./types\";\r\nimport { useNotificationCore } from \"./composables/useNotificationCore\";\r\nimport NotificationBadge from \"./components/NotificationBadge.vue\";\r\nimport NotificationList from \"./components/NotificationList.vue\";\r\nimport NotificationDetail from \"./components/NotificationDetail.vue\";\r\n\r\ndefineOptions({ name: \"C_NotificationCenter\" });\r\n\r\nconst props = withDefaults(defineProps<NotificationCenterProps>(), {\r\n desktopNotification: false,\r\n maxBadgeCount: 99,\r\n pollingInterval: 60_000,\r\n pageSize: 20,\r\n storageKey: \"notification_center\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n itemClick: [message: NotificationMessage];\r\n read: [ids: string[]];\r\n allRead: [category?: NotificationCategory];\r\n delete: [ids: string[]];\r\n unreadChange: [count: number];\r\n wsStatusChange: [status: string];\r\n newMessage: [message: NotificationMessage];\r\n navigate: [url: string];\r\n}>();\r\n\r\nconst core = useNotificationCore(props);\r\n\r\n/* ─── 计算属性 ───────────────────────────────── */\r\n\r\n/** 是否有紧急未读消息 */\r\nconst hasUrgentMessage = computed(() =>\r\n core.messages.value.some(\r\n (m) => m.status === \"unread\" && m.priority === \"urgent\",\r\n ),\r\n);\r\n\r\n/** WebSocket 状态文本 */\r\nconst wsStatusText = computed(() => {\r\n const map: Record<string, string> = {\r\n connected: \"已连接\",\r\n connecting: \"连接中...\",\r\n disconnected: \"未连接\",\r\n reconnecting: \"重连中...\",\r\n };\r\n return map[core.wsStatus.value] ?? \"未知\";\r\n});\r\n\r\n/* ─── 监听未读数变化 ─────────────────────────── */\r\n\r\nwatch(\r\n () => core.unreadCount.value,\r\n (count) => {\r\n emit(\"unreadChange\", count);\r\n },\r\n);\r\n\r\n/* ─── 事件处理 ───────────────────────────────── */\r\n\r\n/** 角标点击 */\r\nfunction handleBadgeClick() {\r\n core.popoverVisible.value = !core.popoverVisible.value;\r\n}\r\n\r\n/** 点击消息 */\r\nfunction handleItemClick(message: NotificationMessage) {\r\n core.selectMessage(message);\r\n emit(\"itemClick\", message);\r\n}\r\n\r\n/** 标记已读 */\r\nfunction handleRead(id: string) {\r\n core.markAsRead([id]);\r\n emit(\"read\", [id]);\r\n}\r\n\r\n/** 删除消息 */\r\nfunction handleDelete(id: string) {\r\n core.deleteMessages([id]);\r\n emit(\"delete\", [id]);\r\n}\r\n\r\n/** 全部已读 */\r\nfunction handleMarkAllRead() {\r\n const category =\r\n core.activeCategory.value === \"all\" ? undefined : core.activeCategory.value;\r\n core.markAllAsRead(category);\r\n emit(\"allRead\", category);\r\n}\r\n\r\n/** 清空 */\r\nfunction handleClear() {\r\n const category =\r\n core.activeCategory.value === \"all\" ? undefined : core.activeCategory.value;\r\n core.clearMessages(category);\r\n}\r\n\r\n/** 操作链接跳转 */\r\nfunction handleAction() {\r\n core.popoverVisible.value = false;\r\n}\r\n\r\n/** Popover 关闭后重置详情 */\r\nfunction handlePopoverClose() {\r\n core.clearSelection();\r\n}\r\n\r\n/* ─── Expose ──────────────────────────────────── */\r\n\r\ndefineExpose<NotificationCenterExpose>({\r\n refresh: () => core.fetchMessages(true),\r\n connectWS: () => {\r\n if (props.wsConfig) core.connectWS(props.wsConfig);\r\n },\r\n disconnectWS: core.disconnectWS,\r\n getUnreadCount: () => core.unreadCount.value,\r\n getMessages: () => core.messages.value,\r\n markRead: (ids: string[]) => core.markAsRead(ids),\r\n markAllAsRead: (category?: NotificationCategory) =>\r\n core.markAllAsRead(category),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.c-notification-center {\r\n display: inline-flex;\r\n align-items: center;\r\n\r\n &__panel {\r\n display: flex;\r\n flex-direction: column;\r\n max-height: 560px;\r\n }\r\n\r\n &__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 14px 16px 10px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n color: var(--c-text-1);\r\n }\r\n\r\n &__header-extra {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n }\r\n\r\n &__ws-dot {\r\n width: 8px;\r\n height: 8px;\r\n border-radius: 50%;\r\n transition: background-color var(--c-transition, 0.2s ease);\r\n\r\n &--connected {\r\n background: #52c41a;\r\n box-shadow: 0 0 4px rgba(82, 196, 26, 0.5);\r\n }\r\n\r\n &--connecting,\r\n &--reconnecting {\r\n background: #faad14;\r\n animation: status-blink 1s ease-in-out infinite;\r\n }\r\n\r\n &--disconnected {\r\n background: var(--c-text-4);\r\n }\r\n }\r\n}\r\n\r\n@keyframes status-blink {\r\n 0%,\r\n 100% {\r\n opacity: 1;\r\n }\r\n\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n}\r\n\r\n/* ─── 切换过渡 ───────────────────────────────── */\r\n\r\n.slide-fade-enter-active {\r\n transition: all 0.25s ease-out;\r\n}\r\n\r\n.slide-fade-leave-active {\r\n transition: all 0.2s ease-in;\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateY(8px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateY(-8px);\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-notification-center\">\r\n <NPopover\r\n v-model:show=\"core.popoverVisible.value\"\r\n :width=\"420\"\r\n trigger=\"click\"\r\n placement=\"bottom-end\"\r\n :show-arrow=\"false\"\r\n content-class=\"c-notification-center__popover\"\r\n @after-leave=\"handlePopoverClose\"\r\n >\r\n <!-- 触发器:角标铃铛 -->\r\n <template #trigger>\r\n <NotificationBadge\r\n :count=\"core.unreadCount.value\"\r\n :max-count=\"props.maxBadgeCount\"\r\n :has-urgent=\"hasUrgentMessage\"\r\n @click=\"handleBadgeClick\"\r\n />\r\n </template>\r\n\r\n <!-- 面板内容 -->\r\n <div class=\"c-notification-center__panel\">\r\n <!-- 头部标题 -->\r\n <div class=\"c-notification-center__header\">\r\n <span class=\"c-notification-center__title\">消息中心</span>\r\n <div class=\"c-notification-center__header-extra\">\r\n <!-- WebSocket 连接状态 -->\r\n <NTooltip v-if=\"props.wsConfig\" trigger=\"hover\" placement=\"top\">\r\n <template #trigger>\r\n <span\r\n class=\"c-notification-center__ws-dot\"\r\n :class=\"`c-notification-center__ws-dot--${core.wsStatus.value}`\"\r\n />\r\n </template>\r\n {{ wsStatusText }}\r\n </NTooltip>\r\n </div>\r\n </div>\r\n\r\n <!-- 列表 / 详情切换 -->\r\n <Transition name=\"slide-fade\" mode=\"out-in\">\r\n <!-- 消息详情 -->\r\n <NotificationDetail\r\n v-if=\"core.selectedMessage.value\"\r\n :key=\"core.selectedMessage.value.id\"\r\n :message=\"core.selectedMessage.value\"\r\n @back=\"core.clearSelection\"\r\n @action=\"handleAction\"\r\n @navigate=\"(url) => emit('navigate', url)\"\r\n />\r\n\r\n <!-- 消息列表 -->\r\n <NotificationList\r\n v-else\r\n :messages=\"core.filteredMessages.value\"\r\n :active-category=\"core.activeCategory.value\"\r\n :unread-by-category=\"core.unreadByCategory.value\"\r\n :unread-count=\"core.unreadCount.value\"\r\n :loading=\"core.loading.value\"\r\n :has-more=\"core.hasMore.value\"\r\n @switch-category=\"core.switchCategory\"\r\n @item-click=\"handleItemClick\"\r\n @read=\"handleRead\"\r\n @delete=\"handleDelete\"\r\n @mark-all-read=\"handleMarkAllRead\"\r\n @clear=\"handleClear\"\r\n @load-more=\"core.loadMore\"\r\n />\r\n </Transition>\r\n </div>\r\n </NPopover>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, watch } from \"vue\";\r\nimport type {\r\n NotificationCenterProps,\r\n NotificationCenterExpose,\r\n NotificationMessage,\r\n NotificationCategory,\r\n} from \"./types\";\r\nimport { useNotificationCore } from \"./composables/useNotificationCore\";\r\nimport NotificationBadge from \"./components/NotificationBadge.vue\";\r\nimport NotificationList from \"./components/NotificationList.vue\";\r\nimport NotificationDetail from \"./components/NotificationDetail.vue\";\r\n\r\ndefineOptions({ name: \"C_NotificationCenter\" });\r\n\r\nconst props = withDefaults(defineProps<NotificationCenterProps>(), {\r\n desktopNotification: false,\r\n maxBadgeCount: 99,\r\n pollingInterval: 60_000,\r\n pageSize: 20,\r\n storageKey: \"notification_center\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n itemClick: [message: NotificationMessage];\r\n read: [ids: string[]];\r\n allRead: [category?: NotificationCategory];\r\n delete: [ids: string[]];\r\n unreadChange: [count: number];\r\n wsStatusChange: [status: string];\r\n newMessage: [message: NotificationMessage];\r\n navigate: [url: string];\r\n}>();\r\n\r\nconst core = useNotificationCore(props);\r\n\r\n/* ─── 计算属性 ───────────────────────────────── */\r\n\r\n/** 是否有紧急未读消息 */\r\nconst hasUrgentMessage = computed(() =>\r\n core.messages.value.some(\r\n (m) => m.status === \"unread\" && m.priority === \"urgent\",\r\n ),\r\n);\r\n\r\n/** WebSocket 状态文本 */\r\nconst wsStatusText = computed(() => {\r\n const map: Record<string, string> = {\r\n connected: \"已连接\",\r\n connecting: \"连接中...\",\r\n disconnected: \"未连接\",\r\n reconnecting: \"重连中...\",\r\n };\r\n return map[core.wsStatus.value] ?? \"未知\";\r\n});\r\n\r\n/* ─── 监听未读数变化 ─────────────────────────── */\r\n\r\nwatch(\r\n () => core.unreadCount.value,\r\n (count) => {\r\n emit(\"unreadChange\", count);\r\n },\r\n);\r\n\r\n/* ─── 事件处理 ───────────────────────────────── */\r\n\r\n/** 角标点击 */\r\nfunction handleBadgeClick() {\r\n core.popoverVisible.value = !core.popoverVisible.value;\r\n}\r\n\r\n/** 点击消息 */\r\nfunction handleItemClick(message: NotificationMessage) {\r\n core.selectMessage(message);\r\n emit(\"itemClick\", message);\r\n}\r\n\r\n/** 标记已读 */\r\nfunction handleRead(id: string) {\r\n core.markAsRead([id]);\r\n emit(\"read\", [id]);\r\n}\r\n\r\n/** 删除消息 */\r\nfunction handleDelete(id: string) {\r\n core.deleteMessages([id]);\r\n emit(\"delete\", [id]);\r\n}\r\n\r\n/** 全部已读 */\r\nfunction handleMarkAllRead() {\r\n const category =\r\n core.activeCategory.value === \"all\" ? undefined : core.activeCategory.value;\r\n core.markAllAsRead(category);\r\n emit(\"allRead\", category);\r\n}\r\n\r\n/** 清空 */\r\nfunction handleClear() {\r\n const category =\r\n core.activeCategory.value === \"all\" ? undefined : core.activeCategory.value;\r\n core.clearMessages(category);\r\n}\r\n\r\n/** 操作链接跳转 */\r\nfunction handleAction() {\r\n core.popoverVisible.value = false;\r\n}\r\n\r\n/** Popover 关闭后重置详情 */\r\nfunction handlePopoverClose() {\r\n core.clearSelection();\r\n}\r\n\r\n/* ─── Expose ──────────────────────────────────── */\r\n\r\ndefineExpose<NotificationCenterExpose>({\r\n refresh: () => core.fetchMessages(true),\r\n connectWS: () => {\r\n if (props.wsConfig) core.connectWS(props.wsConfig);\r\n },\r\n disconnectWS: core.disconnectWS,\r\n getUnreadCount: () => core.unreadCount.value,\r\n getMessages: () => core.messages.value,\r\n markRead: (ids: string[]) => core.markAsRead(ids),\r\n markAllAsRead: (category?: NotificationCategory) =>\r\n core.markAllAsRead(category),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.c-notification-center {\r\n display: inline-flex;\r\n align-items: center;\r\n\r\n &__panel {\r\n display: flex;\r\n flex-direction: column;\r\n max-height: 560px;\r\n }\r\n\r\n &__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 14px 16px 10px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n color: var(--c-text-1);\r\n }\r\n\r\n &__header-extra {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n }\r\n\r\n &__ws-dot {\r\n width: 8px;\r\n height: 8px;\r\n border-radius: 50%;\r\n transition: background-color var(--c-transition, 0.2s ease);\r\n\r\n &--connected {\r\n background: #52c41a;\r\n box-shadow: 0 0 4px rgba(82, 196, 26, 0.5);\r\n }\r\n\r\n &--connecting,\r\n &--reconnecting {\r\n background: #faad14;\r\n animation: status-blink 1s ease-in-out infinite;\r\n }\r\n\r\n &--disconnected {\r\n background: var(--c-text-4);\r\n }\r\n }\r\n}\r\n\r\n@keyframes status-blink {\r\n 0%,\r\n 100% {\r\n opacity: 1;\r\n }\r\n\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n}\r\n\r\n/* ─── 切换过渡 ───────────────────────────────── */\r\n\r\n.slide-fade-enter-active {\r\n transition: all 0.25s ease-out;\r\n}\r\n\r\n.slide-fade-leave-active {\r\n transition: all 0.2s ease-in;\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateY(8px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateY(-8px);\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-notification-center\">\r\n <NPopover\r\n v-model:show=\"core.popoverVisible.value\"\r\n :width=\"420\"\r\n trigger=\"click\"\r\n placement=\"bottom-end\"\r\n :show-arrow=\"false\"\r\n content-class=\"c-notification-center__popover\"\r\n @after-leave=\"handlePopoverClose\"\r\n >\r\n <!-- 触发器:角标铃铛 -->\r\n <template #trigger>\r\n <NotificationBadge\r\n :count=\"core.unreadCount.value\"\r\n :max-count=\"props.maxBadgeCount\"\r\n :has-urgent=\"hasUrgentMessage\"\r\n @click=\"handleBadgeClick\"\r\n />\r\n </template>\r\n\r\n <!-- 面板内容 -->\r\n <div class=\"c-notification-center__panel\">\r\n <!-- 头部标题 -->\r\n <div class=\"c-notification-center__header\">\r\n <span class=\"c-notification-center__title\">消息中心</span>\r\n <div class=\"c-notification-center__header-extra\">\r\n <!-- WebSocket 连接状态 -->\r\n <NTooltip v-if=\"props.wsConfig\" trigger=\"hover\" placement=\"top\">\r\n <template #trigger>\r\n <span\r\n class=\"c-notification-center__ws-dot\"\r\n :class=\"`c-notification-center__ws-dot--${core.wsStatus.value}`\"\r\n />\r\n </template>\r\n {{ wsStatusText }}\r\n </NTooltip>\r\n </div>\r\n </div>\r\n\r\n <!-- 列表 / 详情切换 -->\r\n <Transition name=\"slide-fade\" mode=\"out-in\">\r\n <!-- 消息详情 -->\r\n <NotificationDetail\r\n v-if=\"core.selectedMessage.value\"\r\n :key=\"core.selectedMessage.value.id\"\r\n :message=\"core.selectedMessage.value\"\r\n @back=\"core.clearSelection\"\r\n @action=\"handleAction\"\r\n @navigate=\"(url) => emit('navigate', url)\"\r\n />\r\n\r\n <!-- 消息列表 -->\r\n <NotificationList\r\n v-else\r\n :messages=\"core.filteredMessages.value\"\r\n :active-category=\"core.activeCategory.value\"\r\n :unread-by-category=\"core.unreadByCategory.value\"\r\n :unread-count=\"core.unreadCount.value\"\r\n :loading=\"core.loading.value\"\r\n :has-more=\"core.hasMore.value\"\r\n @switch-category=\"core.switchCategory\"\r\n @item-click=\"handleItemClick\"\r\n @read=\"handleRead\"\r\n @delete=\"handleDelete\"\r\n @mark-all-read=\"handleMarkAllRead\"\r\n @clear=\"handleClear\"\r\n @load-more=\"core.loadMore\"\r\n />\r\n </Transition>\r\n </div>\r\n </NPopover>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, watch } from \"vue\";\r\nimport type {\r\n NotificationCenterProps,\r\n NotificationCenterExpose,\r\n NotificationMessage,\r\n NotificationCategory,\r\n} from \"./types\";\r\nimport { useNotificationCore } from \"./composables/useNotificationCore\";\r\nimport NotificationBadge from \"./components/NotificationBadge.vue\";\r\nimport NotificationList from \"./components/NotificationList.vue\";\r\nimport NotificationDetail from \"./components/NotificationDetail.vue\";\r\n\r\ndefineOptions({ name: \"C_NotificationCenter\" });\r\n\r\nconst props = withDefaults(defineProps<NotificationCenterProps>(), {\r\n desktopNotification: false,\r\n maxBadgeCount: 99,\r\n pollingInterval: 60_000,\r\n pageSize: 20,\r\n storageKey: \"notification_center\",\r\n});\r\n\r\nconst emit = defineEmits<{\r\n itemClick: [message: NotificationMessage];\r\n read: [ids: string[]];\r\n allRead: [category?: NotificationCategory];\r\n delete: [ids: string[]];\r\n unreadChange: [count: number];\r\n wsStatusChange: [status: string];\r\n newMessage: [message: NotificationMessage];\r\n navigate: [url: string];\r\n}>();\r\n\r\nconst core = useNotificationCore(props);\r\n\r\n/* ─── 计算属性 ───────────────────────────────── */\r\n\r\n/** 是否有紧急未读消息 */\r\nconst hasUrgentMessage = computed(() =>\r\n core.messages.value.some(\r\n (m) => m.status === \"unread\" && m.priority === \"urgent\",\r\n ),\r\n);\r\n\r\n/** WebSocket 状态文本 */\r\nconst wsStatusText = computed(() => {\r\n const map: Record<string, string> = {\r\n connected: \"已连接\",\r\n connecting: \"连接中...\",\r\n disconnected: \"未连接\",\r\n reconnecting: \"重连中...\",\r\n };\r\n return map[core.wsStatus.value] ?? \"未知\";\r\n});\r\n\r\n/* ─── 监听未读数变化 ─────────────────────────── */\r\n\r\nwatch(\r\n () => core.unreadCount.value,\r\n (count) => {\r\n emit(\"unreadChange\", count);\r\n },\r\n);\r\n\r\n/* ─── 事件处理 ───────────────────────────────── */\r\n\r\n/** 角标点击 */\r\nfunction handleBadgeClick() {\r\n core.popoverVisible.value = !core.popoverVisible.value;\r\n}\r\n\r\n/** 点击消息 */\r\nfunction handleItemClick(message: NotificationMessage) {\r\n core.selectMessage(message);\r\n emit(\"itemClick\", message);\r\n}\r\n\r\n/** 标记已读 */\r\nfunction handleRead(id: string) {\r\n core.markAsRead([id]);\r\n emit(\"read\", [id]);\r\n}\r\n\r\n/** 删除消息 */\r\nfunction handleDelete(id: string) {\r\n core.deleteMessages([id]);\r\n emit(\"delete\", [id]);\r\n}\r\n\r\n/** 全部已读 */\r\nfunction handleMarkAllRead() {\r\n const category =\r\n core.activeCategory.value === \"all\" ? undefined : core.activeCategory.value;\r\n core.markAllAsRead(category);\r\n emit(\"allRead\", category);\r\n}\r\n\r\n/** 清空 */\r\nfunction handleClear() {\r\n const category =\r\n core.activeCategory.value === \"all\" ? undefined : core.activeCategory.value;\r\n core.clearMessages(category);\r\n}\r\n\r\n/** 操作链接跳转 */\r\nfunction handleAction() {\r\n core.popoverVisible.value = false;\r\n}\r\n\r\n/** Popover 关闭后重置详情 */\r\nfunction handlePopoverClose() {\r\n core.clearSelection();\r\n}\r\n\r\n/* ─── Expose ──────────────────────────────────── */\r\n\r\ndefineExpose<NotificationCenterExpose>({\r\n refresh: () => core.fetchMessages(true),\r\n connectWS: () => {\r\n if (props.wsConfig) core.connectWS(props.wsConfig);\r\n },\r\n disconnectWS: core.disconnectWS,\r\n getUnreadCount: () => core.unreadCount.value,\r\n getMessages: () => core.messages.value,\r\n markRead: (ids: string[]) => core.markAsRead(ids),\r\n markAllAsRead: (category?: NotificationCategory) =>\r\n core.markAllAsRead(category),\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.c-notification-center {\r\n display: inline-flex;\r\n align-items: center;\r\n\r\n &__panel {\r\n display: flex;\r\n flex-direction: column;\r\n max-height: 560px;\r\n }\r\n\r\n &__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 14px 16px 10px;\r\n border-bottom: 1px solid var(--c-border);\r\n }\r\n\r\n &__title {\r\n font-size: 15px;\r\n font-weight: 600;\r\n color: var(--c-text-1);\r\n }\r\n\r\n &__header-extra {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n }\r\n\r\n &__ws-dot {\r\n width: 8px;\r\n height: 8px;\r\n border-radius: 50%;\r\n transition: background-color var(--c-transition, 0.2s ease);\r\n\r\n &--connected {\r\n background: #52c41a;\r\n box-shadow: 0 0 4px rgba(82, 196, 26, 0.5);\r\n }\r\n\r\n &--connecting,\r\n &--reconnecting {\r\n background: #faad14;\r\n animation: status-blink 1s ease-in-out infinite;\r\n }\r\n\r\n &--disconnected {\r\n background: var(--c-text-4);\r\n }\r\n }\r\n}\r\n\r\n@keyframes status-blink {\r\n 0%,\r\n 100% {\r\n opacity: 1;\r\n }\r\n\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n}\r\n\r\n/* ─── 切换过渡 ───────────────────────────────── */\r\n\r\n.slide-fade-enter-active {\r\n transition: all 0.25s ease-out;\r\n}\r\n\r\n.slide-fade-leave-active {\r\n transition: all 0.2s ease-in;\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateY(8px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateY(-8px);\r\n}\r\n</style>\r\n"],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { computed, createBlock, createSlots, defineComponent, mergeProps, onMounted, openBlock, ref, renderSlot, unref, useSlots, watch, withCtx } from "vue";
|
|
2
|
+
import { NProgress } from "naive-ui";
|
|
3
|
+
|
|
4
|
+
//#region src/components/C_Progress/index.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
+
var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
6
|
+
name: "C_Progress",
|
|
7
|
+
__name: "index",
|
|
8
|
+
props: {
|
|
9
|
+
percentage: { default: 0 },
|
|
10
|
+
isAnimation: {
|
|
11
|
+
type: Boolean,
|
|
12
|
+
default: false
|
|
13
|
+
},
|
|
14
|
+
time: { default: 3e3 },
|
|
15
|
+
type: {},
|
|
16
|
+
borderRadius: {},
|
|
17
|
+
circleGap: {},
|
|
18
|
+
color: {},
|
|
19
|
+
fillBorderRadius: {},
|
|
20
|
+
gapDegree: {},
|
|
21
|
+
gapOffsetDegree: {},
|
|
22
|
+
height: {},
|
|
23
|
+
indicatorPlacement: { default: "outside" },
|
|
24
|
+
indicatorTextColor: {},
|
|
25
|
+
offsetDegree: {},
|
|
26
|
+
railColor: {},
|
|
27
|
+
railStyle: {},
|
|
28
|
+
showIndicator: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: true
|
|
31
|
+
},
|
|
32
|
+
status: { default: "default" },
|
|
33
|
+
strokeWidth: { default: 7 },
|
|
34
|
+
unit: { default: "%" }
|
|
35
|
+
},
|
|
36
|
+
setup(__props) {
|
|
37
|
+
const props = __props;
|
|
38
|
+
const slots = useSlots();
|
|
39
|
+
const p = ref(Array.isArray(props.percentage) ? [...props.percentage] : 0);
|
|
40
|
+
const hasIndicatorSlot = computed(() => !!slots.indicator);
|
|
41
|
+
const processedPercentage = computed(() => {
|
|
42
|
+
return props.type === "multiple-circle" ? Array.isArray(p.value) ? p.value : [p.value] : Array.isArray(p.value) ? p.value[0] : p.value;
|
|
43
|
+
});
|
|
44
|
+
const progressProps = computed(() => ({
|
|
45
|
+
type: props.type,
|
|
46
|
+
borderRadius: props.borderRadius,
|
|
47
|
+
circleGap: props.circleGap,
|
|
48
|
+
color: props.color,
|
|
49
|
+
fillBorderRadius: props.fillBorderRadius,
|
|
50
|
+
gapDegree: props.gapDegree,
|
|
51
|
+
gapOffsetDegree: props.gapOffsetDegree,
|
|
52
|
+
height: props.height,
|
|
53
|
+
indicatorPlacement: props.indicatorPlacement,
|
|
54
|
+
indicatorTextColor: props.indicatorTextColor,
|
|
55
|
+
offsetDegree: props.offsetDegree,
|
|
56
|
+
railColor: props.railColor,
|
|
57
|
+
railStyle: props.railStyle,
|
|
58
|
+
status: props.status,
|
|
59
|
+
strokeWidth: props.strokeWidth,
|
|
60
|
+
unit: props.unit
|
|
61
|
+
}));
|
|
62
|
+
watch(() => props.percentage, (newVal) => {
|
|
63
|
+
if (!props.isAnimation) p.value = newVal;
|
|
64
|
+
else if (props.type === "multiple-circle" && Array.isArray(newVal)) p.value = [...newVal];
|
|
65
|
+
}, {
|
|
66
|
+
immediate: true,
|
|
67
|
+
deep: true
|
|
68
|
+
});
|
|
69
|
+
onMounted(() => {
|
|
70
|
+
if (props.isAnimation && props.type !== "multiple-circle") {
|
|
71
|
+
const targetValue = Array.isArray(props.percentage) ? props.percentage[0] : props.percentage;
|
|
72
|
+
if (targetValue > 0) {
|
|
73
|
+
const startTime = performance.now();
|
|
74
|
+
const animate = (now) => {
|
|
75
|
+
const elapsed = now - startTime;
|
|
76
|
+
const progress = Math.min(elapsed / props.time, 1);
|
|
77
|
+
p.value = Math.round(progress * targetValue);
|
|
78
|
+
if (progress < 1) requestAnimationFrame(animate);
|
|
79
|
+
};
|
|
80
|
+
requestAnimationFrame(animate);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return (_ctx, _cache) => {
|
|
85
|
+
return openBlock(), createBlock(unref(NProgress), mergeProps(progressProps.value, {
|
|
86
|
+
percentage: processedPercentage.value,
|
|
87
|
+
processing: _ctx.isAnimation
|
|
88
|
+
}), createSlots({ _: 2 }, [_ctx.showIndicator && hasIndicatorSlot.value ? {
|
|
89
|
+
name: "default",
|
|
90
|
+
fn: withCtx(() => [renderSlot(_ctx.$slots, "indicator")]),
|
|
91
|
+
key: "0"
|
|
92
|
+
} : void 0]), 1040, ["percentage", "processing"]);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/components/C_Progress/index.vue
|
|
99
|
+
var C_Progress_default = index_vue_vue_type_script_setup_true_lang_default;
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { C_Progress_default as t };
|
|
103
|
+
//# sourceMappingURL=C_Progress2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_Progress2.js","names":["isAnimation","showIndicator"],"sources":["../src/components/C_Progress/index.vue","../src/components/C_Progress/index.vue","../src/components/C_Progress/index.vue"],"sourcesContent":["<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 进度条组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NProgress\r\n v-bind=\"progressProps\"\r\n :percentage=\"processedPercentage\"\r\n :processing=\"isAnimation\"\r\n >\r\n <template v-if=\"showIndicator && hasIndicatorSlot\" #default>\r\n <slot name=\"indicator\" />\r\n </template>\r\n </NProgress>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, useSlots, onMounted } from \"vue\";\r\nimport { NProgress } from \"naive-ui\";\r\nimport type { CSSProperties } from \"vue\";\r\n\r\ndefineOptions({ name: \"C_Progress\" });\r\n\r\ntype CSS = CSSProperties | string;\r\n\r\ninterface Props {\r\n percentage: number | number[];\r\n isAnimation?: boolean;\r\n time?: number;\r\n type?: \"line\" | \"circle\" | \"multiple-circle\" | \"dashboard\";\r\n borderRadius?: number | string;\r\n circleGap?: number;\r\n color?: string | string[] | { stops: string[] } | Array<{ stops: string[] }>;\r\n fillBorderRadius?: number | string;\r\n gapDegree?: number;\r\n gapOffsetDegree?: number;\r\n height?: number;\r\n indicatorPlacement?: \"inside\" | \"outside\";\r\n indicatorTextColor?: string;\r\n offsetDegree?: number;\r\n railColor?: string | string[];\r\n railStyle?: string | CSS | Array<string | CSS>;\r\n showIndicator?: boolean;\r\n status?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\";\r\n strokeWidth?: number;\r\n unit?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n percentage: 0,\r\n isAnimation: false,\r\n time: 3000,\r\n indicatorPlacement: \"outside\",\r\n showIndicator: true,\r\n status: \"default\",\r\n strokeWidth: 7,\r\n unit: \"%\",\r\n});\r\n\r\nconst slots = useSlots();\r\nconst p = ref<number | number[]>(\r\n Array.isArray(props.percentage) ? [...props.percentage] : 0,\r\n);\r\n\r\nconst hasIndicatorSlot = computed(() => !!slots.indicator);\r\n\r\nconst processedPercentage = computed(() => {\r\n return props.type === \"multiple-circle\"\r\n ? Array.isArray(p.value)\r\n ? p.value\r\n : [p.value]\r\n : Array.isArray(p.value)\r\n ? p.value[0]\r\n : p.value;\r\n});\r\n\r\nconst progressProps = computed(() => ({\r\n type: props.type,\r\n borderRadius: props.borderRadius,\r\n circleGap: props.circleGap,\r\n color: props.color,\r\n fillBorderRadius: props.fillBorderRadius,\r\n gapDegree: props.gapDegree,\r\n gapOffsetDegree: props.gapOffsetDegree,\r\n height: props.height,\r\n indicatorPlacement: props.indicatorPlacement,\r\n indicatorTextColor: props.indicatorTextColor,\r\n offsetDegree: props.offsetDegree,\r\n railColor: props.railColor,\r\n railStyle: props.railStyle,\r\n status: props.status,\r\n strokeWidth: props.strokeWidth,\r\n unit: props.unit,\r\n}));\r\n\r\nwatch(\r\n () => props.percentage,\r\n (newVal) => {\r\n if (!props.isAnimation) {\r\n p.value = newVal;\r\n } else if (props.type === \"multiple-circle\" && Array.isArray(newVal)) {\r\n p.value = [...newVal];\r\n }\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\nonMounted(() => {\r\n if (props.isAnimation && props.type !== \"multiple-circle\") {\r\n const targetValue = Array.isArray(props.percentage)\r\n ? props.percentage[0]\r\n : props.percentage;\r\n\r\n if (targetValue > 0) {\r\n const startTime = performance.now();\r\n\r\n const animate = (now: number) => {\r\n const elapsed = now - startTime;\r\n const progress = Math.min(elapsed / props.time, 1);\r\n p.value = Math.round(progress * targetValue);\r\n\r\n if (progress < 1) {\r\n requestAnimationFrame(animate);\r\n }\r\n };\r\n\r\n requestAnimationFrame(animate);\r\n }\r\n }\r\n});\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 进度条组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NProgress\r\n v-bind=\"progressProps\"\r\n :percentage=\"processedPercentage\"\r\n :processing=\"isAnimation\"\r\n >\r\n <template v-if=\"showIndicator && hasIndicatorSlot\" #default>\r\n <slot name=\"indicator\" />\r\n </template>\r\n </NProgress>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, useSlots, onMounted } from \"vue\";\r\nimport { NProgress } from \"naive-ui\";\r\nimport type { CSSProperties } from \"vue\";\r\n\r\ndefineOptions({ name: \"C_Progress\" });\r\n\r\ntype CSS = CSSProperties | string;\r\n\r\ninterface Props {\r\n percentage: number | number[];\r\n isAnimation?: boolean;\r\n time?: number;\r\n type?: \"line\" | \"circle\" | \"multiple-circle\" | \"dashboard\";\r\n borderRadius?: number | string;\r\n circleGap?: number;\r\n color?: string | string[] | { stops: string[] } | Array<{ stops: string[] }>;\r\n fillBorderRadius?: number | string;\r\n gapDegree?: number;\r\n gapOffsetDegree?: number;\r\n height?: number;\r\n indicatorPlacement?: \"inside\" | \"outside\";\r\n indicatorTextColor?: string;\r\n offsetDegree?: number;\r\n railColor?: string | string[];\r\n railStyle?: string | CSS | Array<string | CSS>;\r\n showIndicator?: boolean;\r\n status?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\";\r\n strokeWidth?: number;\r\n unit?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n percentage: 0,\r\n isAnimation: false,\r\n time: 3000,\r\n indicatorPlacement: \"outside\",\r\n showIndicator: true,\r\n status: \"default\",\r\n strokeWidth: 7,\r\n unit: \"%\",\r\n});\r\n\r\nconst slots = useSlots();\r\nconst p = ref<number | number[]>(\r\n Array.isArray(props.percentage) ? [...props.percentage] : 0,\r\n);\r\n\r\nconst hasIndicatorSlot = computed(() => !!slots.indicator);\r\n\r\nconst processedPercentage = computed(() => {\r\n return props.type === \"multiple-circle\"\r\n ? Array.isArray(p.value)\r\n ? p.value\r\n : [p.value]\r\n : Array.isArray(p.value)\r\n ? p.value[0]\r\n : p.value;\r\n});\r\n\r\nconst progressProps = computed(() => ({\r\n type: props.type,\r\n borderRadius: props.borderRadius,\r\n circleGap: props.circleGap,\r\n color: props.color,\r\n fillBorderRadius: props.fillBorderRadius,\r\n gapDegree: props.gapDegree,\r\n gapOffsetDegree: props.gapOffsetDegree,\r\n height: props.height,\r\n indicatorPlacement: props.indicatorPlacement,\r\n indicatorTextColor: props.indicatorTextColor,\r\n offsetDegree: props.offsetDegree,\r\n railColor: props.railColor,\r\n railStyle: props.railStyle,\r\n status: props.status,\r\n strokeWidth: props.strokeWidth,\r\n unit: props.unit,\r\n}));\r\n\r\nwatch(\r\n () => props.percentage,\r\n (newVal) => {\r\n if (!props.isAnimation) {\r\n p.value = newVal;\r\n } else if (props.type === \"multiple-circle\" && Array.isArray(newVal)) {\r\n p.value = [...newVal];\r\n }\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\nonMounted(() => {\r\n if (props.isAnimation && props.type !== \"multiple-circle\") {\r\n const targetValue = Array.isArray(props.percentage)\r\n ? props.percentage[0]\r\n : props.percentage;\r\n\r\n if (targetValue > 0) {\r\n const startTime = performance.now();\r\n\r\n const animate = (now: number) => {\r\n const elapsed = now - startTime;\r\n const progress = Math.min(elapsed / props.time, 1);\r\n p.value = Math.round(progress * targetValue);\r\n\r\n if (progress < 1) {\r\n requestAnimationFrame(animate);\r\n }\r\n };\r\n\r\n requestAnimationFrame(animate);\r\n }\r\n }\r\n});\r\n</script>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-06-01\r\n * @Description: 进度条组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <NProgress\r\n v-bind=\"progressProps\"\r\n :percentage=\"processedPercentage\"\r\n :processing=\"isAnimation\"\r\n >\r\n <template v-if=\"showIndicator && hasIndicatorSlot\" #default>\r\n <slot name=\"indicator\" />\r\n </template>\r\n </NProgress>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, useSlots, onMounted } from \"vue\";\r\nimport { NProgress } from \"naive-ui\";\r\nimport type { CSSProperties } from \"vue\";\r\n\r\ndefineOptions({ name: \"C_Progress\" });\r\n\r\ntype CSS = CSSProperties | string;\r\n\r\ninterface Props {\r\n percentage: number | number[];\r\n isAnimation?: boolean;\r\n time?: number;\r\n type?: \"line\" | \"circle\" | \"multiple-circle\" | \"dashboard\";\r\n borderRadius?: number | string;\r\n circleGap?: number;\r\n color?: string | string[] | { stops: string[] } | Array<{ stops: string[] }>;\r\n fillBorderRadius?: number | string;\r\n gapDegree?: number;\r\n gapOffsetDegree?: number;\r\n height?: number;\r\n indicatorPlacement?: \"inside\" | \"outside\";\r\n indicatorTextColor?: string;\r\n offsetDegree?: number;\r\n railColor?: string | string[];\r\n railStyle?: string | CSS | Array<string | CSS>;\r\n showIndicator?: boolean;\r\n status?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\";\r\n strokeWidth?: number;\r\n unit?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n percentage: 0,\r\n isAnimation: false,\r\n time: 3000,\r\n indicatorPlacement: \"outside\",\r\n showIndicator: true,\r\n status: \"default\",\r\n strokeWidth: 7,\r\n unit: \"%\",\r\n});\r\n\r\nconst slots = useSlots();\r\nconst p = ref<number | number[]>(\r\n Array.isArray(props.percentage) ? [...props.percentage] : 0,\r\n);\r\n\r\nconst hasIndicatorSlot = computed(() => !!slots.indicator);\r\n\r\nconst processedPercentage = computed(() => {\r\n return props.type === \"multiple-circle\"\r\n ? Array.isArray(p.value)\r\n ? p.value\r\n : [p.value]\r\n : Array.isArray(p.value)\r\n ? p.value[0]\r\n : p.value;\r\n});\r\n\r\nconst progressProps = computed(() => ({\r\n type: props.type,\r\n borderRadius: props.borderRadius,\r\n circleGap: props.circleGap,\r\n color: props.color,\r\n fillBorderRadius: props.fillBorderRadius,\r\n gapDegree: props.gapDegree,\r\n gapOffsetDegree: props.gapOffsetDegree,\r\n height: props.height,\r\n indicatorPlacement: props.indicatorPlacement,\r\n indicatorTextColor: props.indicatorTextColor,\r\n offsetDegree: props.offsetDegree,\r\n railColor: props.railColor,\r\n railStyle: props.railStyle,\r\n status: props.status,\r\n strokeWidth: props.strokeWidth,\r\n unit: props.unit,\r\n}));\r\n\r\nwatch(\r\n () => props.percentage,\r\n (newVal) => {\r\n if (!props.isAnimation) {\r\n p.value = newVal;\r\n } else if (props.type === \"multiple-circle\" && Array.isArray(newVal)) {\r\n p.value = [...newVal];\r\n }\r\n },\r\n { immediate: true, deep: true },\r\n);\r\n\r\nonMounted(() => {\r\n if (props.isAnimation && props.type !== \"multiple-circle\") {\r\n const targetValue = Array.isArray(props.percentage)\r\n ? props.percentage[0]\r\n : props.percentage;\r\n\r\n if (targetValue > 0) {\r\n const startTime = performance.now();\r\n\r\n const animate = (now: number) => {\r\n const elapsed = now - startTime;\r\n const progress = Math.min(elapsed / props.time, 1);\r\n p.value = Math.round(progress * targetValue);\r\n\r\n if (progress < 1) {\r\n requestAnimationFrame(animate);\r\n }\r\n };\r\n\r\n requestAnimationFrame(animate);\r\n }\r\n }\r\n});\r\n</script>\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECmDA,MAAM,QAAQ;EAWd,MAAM,QAAQ,UAAU;EACxB,MAAM,IAAI,IACR,MAAM,QAAQ,MAAM,WAAW,GAAG,CAAC,GAAG,MAAM,WAAW,GAAG,EAC3D;EAED,MAAM,mBAAmB,eAAe,CAAC,CAAC,MAAM,UAAU;EAE1D,MAAM,sBAAsB,eAAe;AACzC,UAAO,MAAM,SAAS,oBAClB,MAAM,QAAQ,EAAE,MAAM,GACpB,EAAE,QACF,CAAC,EAAE,MAAM,GACX,MAAM,QAAQ,EAAE,MAAM,GACpB,EAAE,MAAM,KACR,EAAE;IACR;EAEF,MAAM,gBAAgB,gBAAgB;GACpC,MAAM,MAAM;GACZ,cAAc,MAAM;GACpB,WAAW,MAAM;GACjB,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB,WAAW,MAAM;GACjB,iBAAiB,MAAM;GACvB,QAAQ,MAAM;GACd,oBAAoB,MAAM;GAC1B,oBAAoB,MAAM;GAC1B,cAAc,MAAM;GACpB,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,QAAQ,MAAM;GACd,aAAa,MAAM;GACnB,MAAM,MAAM;GACb,EAAE;AAEH,cACQ,MAAM,aACX,WAAW;AACV,OAAI,CAAC,MAAM,YACT,GAAE,QAAQ;YACD,MAAM,SAAS,qBAAqB,MAAM,QAAQ,OAAO,CAClE,GAAE,QAAQ,CAAC,GAAG,OAAO;KAGzB;GAAE,WAAW;GAAM,MAAM;GAAM,CAChC;AAED,kBAAgB;AACd,OAAI,MAAM,eAAe,MAAM,SAAS,mBAAmB;IACzD,MAAM,cAAc,MAAM,QAAQ,MAAM,WAAW,GAC/C,MAAM,WAAW,KACjB,MAAM;AAEV,QAAI,cAAc,GAAG;KACnB,MAAM,YAAY,YAAY,KAAK;KAEnC,MAAM,WAAW,QAAgB;MAC/B,MAAM,UAAU,MAAM;MACtB,MAAM,WAAW,KAAK,IAAI,UAAU,MAAM,MAAM,EAAE;AAClD,QAAE,QAAQ,KAAK,MAAM,WAAW,YAAY;AAE5C,UAAI,WAAW,EACb,uBAAsB,QAAQ;;AAIlC,2BAAsB,QAAQ;;;IAGlC;;uBA5HA,YAQY,MAAA,UAAA,EARZ,WACU,cAOE,OAPW;IACpB,YAAY,oBAAA;IACZ,YAAYA,KAAAA;8BAEGC,KAAAA,iBAAiB,iBAAA;UAAmB;sBACzB,CAAzB,WAAyB,KAAA,QAAA,YAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_QRCode-DbdiAIPg.css","names":[],"sources":["../src/components/C_QRCode/index.vue?vue&type=style&index=0&scoped=09a8468f&lang.scss"],"sourcesContent":["/* C_QRCode styles */\n.c-qrcode[data-v-09a8468f] {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper[data-v-09a8468f] {\n display: inline-block;\n padding: 8px;\n background: var(--c-bg-content, #fff);\n border-radius: 6px;\n transition: all 0.3s ease;\n overflow: hidden;\n max-width: 100%;\n line-height: 0;\n}\n.c-qrcode .qrcode-wrapper.with-border[data-v-09a8468f] {\n border: 1px dashed var(--c-border-default, #e0e0e6);\n}\n.c-qrcode .qrcode-wrapper[data-v-09a8468f]:hover {\n border-color: var(--primary-color, #409eff);\n box-shadow: var(--c-shadow-sm, 0 1px 4px rgba(0, 0, 0, 0.08));\n}\n.c-qrcode .qrcode-wrapper canvas[data-v-09a8468f] {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-09a8468f] {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n}\n.c-qrcode .qrcode-wrapper .qrcode-svg[data-v-09a8468f] svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n.c-qrcode .qrcode-label[data-v-09a8468f] {\n font-size: 14px;\n color: var(--c-text-secondary, #999);\n text-align: center;\n font-weight: 500;\n word-break: break-all;\n max-width: 100%;\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as LogoOptions, c as RenderMode, i as ExportType, n as _default, o as QRCodeExpose, r as ErrorCorrectionLevel, s as QRCodeProps, t as useQRCode } from "./useQRCode.js";
|
|
2
|
+
export { _default as C_QRCode, type ErrorCorrectionLevel, type ExportType, type LogoOptions, type QRCodeExpose, type QRCodeProps, type RenderMode, useQRCode };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as LogoOptions, c as RenderMode, i as ExportType, n as _default, o as QRCodeExpose, r as ErrorCorrectionLevel, s as QRCodeProps, t as useQRCode } from "./useQRCode.js";
|
|
2
|
+
export { _default as C_QRCode, type ErrorCorrectionLevel, type ExportType, type LogoOptions, type QRCodeExpose, type QRCodeProps, type RenderMode, useQRCode };
|
package/dist/C_QRCode.js
ADDED