@robot-admin/naive-ui-components 0.8.1 → 0.8.2
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 +279 -279
- package/dist/C_ActionBar-DYG0Yf3A.css.map +1 -0
- package/dist/C_ActionBar-MAjRhqcI.css.map +1 -0
- package/dist/C_ActionBar.cjs +1 -1
- package/dist/C_ActionBar.css +47 -47
- package/dist/C_ActionBar.js +1 -1
- package/dist/C_ActionBar.js.map +1 -1
- package/dist/C_ActionBar2.js +1 -1
- package/dist/C_ActionBar2.js.map +1 -1
- package/dist/{C_AntV-A3NXx_sF.css.map → C_AntV-BY8jqqmi.css.map} +1 -1
- package/dist/{C_AntV-D2Zp2qQF.css.map → C_AntV-BbkHc-_a.css.map} +1 -1
- package/dist/C_AntV.cjs +1 -1
- package/dist/C_AntV.css +78 -78
- package/dist/C_AntV.js +1 -1
- package/dist/C_AntV.js.map +1 -1
- package/dist/C_AntV2.js +3 -3
- package/dist/C_AntV2.js.map +1 -1
- package/dist/{C_AudioPlayer-CXl4yP_m.css.map → C_AudioPlayer-DKRIGhi7.css.map} +1 -1
- package/dist/{C_AudioPlayer-CcBtt71m.css.map → C_AudioPlayer-DlB6eNsa.css.map} +1 -1
- package/dist/C_AudioPlayer.cjs +1 -1
- package/dist/C_AudioPlayer.css +47 -47
- package/dist/C_AudioPlayer.js +1 -1
- package/dist/C_AudioPlayer.js.map +1 -1
- package/dist/C_AudioPlayer2.js +1 -1
- package/dist/C_AudioPlayer2.js.map +1 -1
- package/dist/{C_AvatarGroup-CtPhfH9h.css.map → C_AvatarGroup-D02zci-i.css.map} +1 -1
- package/dist/{C_AvatarGroup-M1dNPH3E.css.map → C_AvatarGroup-DatHaUlY.css.map} +1 -1
- package/dist/C_AvatarGroup.css +21 -21
- package/dist/C_AvatarGroup.js.map +1 -1
- package/dist/C_AvatarGroup2.js +1 -1
- package/dist/C_AvatarGroup2.js.map +1 -1
- package/dist/{C_Barcode-DjTmDkbQ.css.map → C_Barcode-DYDRDe2O.css.map} +1 -1
- package/dist/{C_Barcode-Do1Ozsmh.css.map → C_Barcode-muJdc_O4.css.map} +1 -1
- package/dist/C_Barcode.css +7 -7
- package/dist/C_Barcode.js.map +1 -1
- package/dist/C_Barcode2.js +1 -1
- package/dist/C_Barcode2.js.map +1 -1
- package/dist/C_Breadcrumb.cjs +1 -1
- package/dist/C_Breadcrumb.js +1 -1
- package/dist/C_Breadcrumb.js.map +1 -1
- package/dist/C_Breadcrumb2.js +1 -1
- package/dist/C_Breadcrumb2.js.map +1 -1
- package/dist/{C_Captcha-CZmAETly.css.map → C_Captcha-9_hk6VmY.css.map} +1 -1
- package/dist/{C_Captcha-Ccq3DMrR.css.map → C_Captcha-MtASlJ0f.css.map} +1 -1
- package/dist/C_Captcha.css +19 -19
- package/dist/C_Captcha.js.map +1 -1
- package/dist/C_Captcha2.js +1 -1
- package/dist/C_Captcha2.js.map +1 -1
- package/dist/C_Cascade.js.map +1 -1
- package/dist/C_Cascade2.js +1 -1
- package/dist/C_Cascade2.js.map +1 -1
- package/dist/{C_Chat-CJ8KESTM.css.map → C_Chat-Csjz_BFw.css.map} +1 -1
- package/dist/{C_Chat-DzV4AFLd.css.map → C_Chat-DO1ZBA_0.css.map} +1 -1
- package/dist/C_Chat.cjs +1 -1
- package/dist/C_Chat.css +65 -65
- package/dist/C_Chat.js +1 -1
- package/dist/C_Chat.js.map +1 -1
- package/dist/C_Chat2.js +1 -1
- package/dist/C_Chat2.js.map +1 -1
- package/dist/{C_City-Cv5BESaN.css.map → C_City-Bam08Qv3.css.map} +1 -1
- package/dist/{C_City-DNKv4MpH.css.map → C_City-Bb3W-KaN.css.map} +1 -1
- package/dist/C_City.css +29 -29
- package/dist/C_City.js.map +1 -1
- package/dist/C_City2.js +1 -1
- package/dist/C_City2.js.map +1 -1
- package/dist/C_Code-BFi5xxnM.css.map +1 -0
- package/dist/C_Code-CZ7yfnri.css.map +1 -0
- package/dist/C_Code.cjs +1 -1
- package/dist/C_Code.css +3 -3
- package/dist/C_Code.js +1 -1
- package/dist/C_Code.js.map +1 -1
- package/dist/C_Code2.js +1 -1
- package/dist/C_Code2.js.map +1 -1
- package/dist/{C_CollapsePanel-CedsEM0N.css.map → C_CollapsePanel-CWkFCqNQ.css.map} +1 -1
- package/dist/{C_CollapsePanel-BkOivqn3.css.map → C_CollapsePanel-pcVJA_4z.css.map} +1 -1
- package/dist/C_CollapsePanel.cjs +1 -1
- package/dist/C_CollapsePanel.css +35 -35
- package/dist/C_CollapsePanel.js +1 -1
- package/dist/C_CollapsePanel.js.map +1 -1
- package/dist/C_CollapsePanel2.js +1 -1
- package/dist/C_CollapsePanel2.js.map +1 -1
- package/dist/{C_ContextMenu-CCsCeI39.css.map → C_ContextMenu-B3Cgz_wJ.css.map} +1 -1
- package/dist/{C_ContextMenu-DOTBeltf.css.map → C_ContextMenu-u1LixF3_.css.map} +1 -1
- package/dist/C_ContextMenu.cjs +1 -1
- package/dist/C_ContextMenu.css +17 -17
- package/dist/C_ContextMenu.js +1 -1
- package/dist/C_ContextMenu.js.map +1 -1
- package/dist/C_ContextMenu2.js +1 -1
- package/dist/C_ContextMenu2.js.map +1 -1
- package/dist/{C_Cron-C0-8b5af.css.map → C_Cron-Civir5-I.css.map} +1 -1
- package/dist/{C_Cron-DqHQ92LN.css.map → C_Cron-iw3qpMsy.css.map} +1 -1
- package/dist/C_Cron.cjs +1 -1
- package/dist/C_Cron.css +37 -37
- package/dist/C_Cron.js +1 -1
- package/dist/C_Cron.js.map +1 -1
- package/dist/C_Cron2.js +1 -1
- package/dist/C_Cron2.js.map +1 -1
- package/dist/C_Date.js.map +1 -1
- package/dist/C_Date2.js +1 -1
- package/dist/C_Date2.js.map +1 -1
- package/dist/{C_Draggable-Bq6o0qXn.css.map → C_Draggable-BCjIvIwT.css.map} +1 -1
- package/dist/{C_Draggable-r6nhx3yM.css.map → C_Draggable-DwkSlCOS.css.map} +1 -1
- package/dist/C_Draggable.css +46 -46
- package/dist/C_Draggable.js.map +1 -1
- package/dist/C_Draggable2.js +1 -1
- package/dist/C_Draggable2.js.map +1 -1
- package/dist/C_Editor-BfjjYQ9B.css.map +1 -0
- package/dist/C_Editor-Do2StvoJ.css.map +1 -0
- package/dist/C_Editor.css +55 -55
- package/dist/C_Editor.js.map +1 -1
- package/dist/C_Editor2.js +1 -1
- package/dist/C_Editor2.js.map +1 -1
- package/dist/{C_FilePreview-BVarVvnD.css.map → C_FilePreview-BhcbiXsV.css.map} +1 -1
- package/dist/{C_FilePreview-DHGlV4OR.css.map → C_FilePreview-DJudcIbW.css.map} +1 -1
- package/dist/C_FilePreview.cjs +1 -1
- package/dist/C_FilePreview.css +91 -91
- package/dist/C_FilePreview.js +1 -1
- package/dist/C_FilePreview.js.map +1 -1
- package/dist/C_FilePreview2.js +1 -1
- package/dist/C_FilePreview2.js.map +1 -1
- package/dist/{C_Form-BciQUPpS.css.map → C_Form-BtcAPIRi.css.map} +1 -1
- package/dist/{C_Form-CKHvXL3O.css.map → C_Form-D_gwQ7hC.css.map} +1 -1
- package/dist/C_Form.cjs +1 -1
- package/dist/C_Form.css +222 -222
- package/dist/C_Form.js +1 -1
- package/dist/C_Form.js.map +1 -1
- package/dist/C_Form2.js +1 -1
- package/dist/C_Form2.js.map +1 -1
- package/dist/{C_FormSearch-Bmd2CmG6.css.map → C_FormSearch-Bi4XYjjk.css.map} +1 -1
- package/dist/{C_FormSearch-DBNtnjaL.css.map → C_FormSearch-DUGOoX0u.css.map} +1 -1
- package/dist/C_FormSearch.css +45 -45
- package/dist/C_FormSearch.js.map +1 -1
- package/dist/C_FormSearch2.js +1 -1
- package/dist/C_FormSearch2.js.map +1 -1
- package/dist/{C_FormulaEditor-3WG2vtQl.css.map → C_FormulaEditor-CJk3SSUJ.css.map} +1 -1
- package/dist/{C_FormulaEditor-Cm0CokN5.css.map → C_FormulaEditor-Cy7mjfVO.css.map} +1 -1
- package/dist/C_FormulaEditor.cjs +1 -1
- package/dist/C_FormulaEditor.css +78 -78
- package/dist/C_FormulaEditor.js +1 -1
- package/dist/C_FormulaEditor.js.map +1 -1
- package/dist/C_FormulaEditor2.js +1 -1
- package/dist/C_FormulaEditor2.js.map +1 -1
- package/dist/C_FullCalendar-BQA0OjGB.css.map +1 -0
- package/dist/C_FullCalendar-ChJUosAi.css.map +1 -0
- package/dist/C_FullCalendar.css +3 -3
- package/dist/C_FullCalendar.js.map +1 -1
- package/dist/C_FullCalendar2.js +1 -1
- package/dist/C_FullCalendar2.js.map +1 -1
- package/dist/C_GlobalSearch.js.map +1 -1
- package/dist/C_GlobalSearch2.js.map +1 -1
- package/dist/C_Guide.cjs +1 -1
- package/dist/C_Guide.js +1 -1
- package/dist/C_Guide.js.map +1 -1
- package/dist/C_Guide2.js +1 -1
- package/dist/C_Guide2.js.map +1 -1
- package/dist/C_Icon.js.map +1 -1
- package/dist/C_Icon2.js +1 -1
- package/dist/C_Icon2.js.map +1 -1
- package/dist/{C_ImageCropper-D3L2-J_J.css.map → C_ImageCropper-BqZpkZfU.css.map} +1 -1
- package/dist/{C_ImageCropper-DrmUlaLi.css.map → C_ImageCropper-CylwGuzS.css.map} +1 -1
- package/dist/C_ImageCropper.cjs +1 -1
- package/dist/C_ImageCropper.css +10 -10
- package/dist/C_ImageCropper.js +1 -1
- package/dist/C_ImageCropper.js.map +1 -1
- package/dist/C_ImageCropper2.js +1 -1
- package/dist/C_ImageCropper2.js.map +1 -1
- package/dist/C_Language.cjs +1 -1
- package/dist/C_Language.js +1 -1
- package/dist/C_Language.js.map +1 -1
- package/dist/C_Language2.js +1 -1
- package/dist/C_Language2.js.map +1 -1
- package/dist/{C_Login-Cst_W1XC.css.map → C_Login-BkdnkBKX.css.map} +1 -1
- package/dist/{C_Login-DWZ_6K1x.css.map → C_Login-VkMM6OJ2.css.map} +1 -1
- package/dist/C_Login.cjs +1 -1
- package/dist/C_Login.css +72 -72
- package/dist/C_Login.js +1 -1
- package/dist/C_Login.js.map +1 -1
- package/dist/C_Login2.js +1 -1
- package/dist/C_Login2.js.map +1 -1
- package/dist/{C_Map-BCJmDnJT.css.map → C_Map-DNnBcY70.css.map} +1 -1
- package/dist/{C_Map-WUMXSAfy.css.map → C_Map-LEXIx6zM.css.map} +1 -1
- package/dist/C_Map.css +4 -4
- package/dist/C_Map.js.map +1 -1
- package/dist/C_Map2.js +1 -1
- package/dist/C_Map2.js.map +1 -1
- package/dist/{C_Markdown-7J0uPEuk.css.map → C_Markdown-DwmI0_0Q.css.map} +1 -1
- package/dist/{C_Markdown-C7Lg0_9z.css.map → C_Markdown-k0794RW0.css.map} +1 -1
- package/dist/C_Markdown.css +33 -33
- package/dist/C_Markdown.js.map +1 -1
- package/dist/C_Markdown2.js +1 -1
- package/dist/C_Markdown2.js.map +1 -1
- package/dist/C_Menu.js.map +1 -1
- package/dist/C_Menu2.js +1 -1
- package/dist/C_Menu2.js.map +1 -1
- package/dist/C_NotificationCenter.cjs +1 -1
- package/dist/C_NotificationCenter.js +1 -1
- package/dist/C_NotificationCenter.js.map +1 -1
- package/dist/C_NotificationCenter2.js +1 -1
- package/dist/C_NotificationCenter2.js.map +1 -1
- package/dist/C_OrgChart.js.map +1 -1
- package/dist/C_OrgChart2.js +1 -1
- package/dist/C_OrgChart2.js.map +1 -1
- package/dist/C_Progress.js.map +1 -1
- package/dist/C_Progress2.js +1 -1
- package/dist/C_Progress2.js.map +1 -1
- package/dist/{C_QRCode-DMxSyogU.css.map → C_QRCode-CFNTda_1.css.map} +1 -1
- package/dist/{C_QRCode-G7fiAkm4.css.map → C_QRCode-CmWr9lK-.css.map} +1 -1
- package/dist/C_QRCode.css +9 -9
- package/dist/C_QRCode.js.map +1 -1
- package/dist/C_QRCode2.js +1 -1
- package/dist/C_QRCode2.js.map +1 -1
- package/dist/{C_Signature-BJMvVPWT.css.map → C_Signature-BDx4ctsE.css.map} +1 -1
- package/dist/{C_Signature-BJUHx2BF.css.map → C_Signature-Bz_uNbwv.css.map} +1 -1
- package/dist/C_Signature.cjs +1 -1
- package/dist/C_Signature.css +15 -15
- package/dist/C_Signature.js +1 -1
- package/dist/C_Signature.js.map +1 -1
- package/dist/C_Signature2.js +1 -1
- package/dist/C_Signature2.js.map +1 -1
- package/dist/C_Skeleton.js.map +1 -1
- package/dist/C_Skeleton2.js +1 -1
- package/dist/C_Skeleton2.js.map +1 -1
- package/dist/{C_SplitPane-Br2eK8IG.css.map → C_SplitPane-BOwr1C7K.css.map} +1 -1
- package/dist/{C_SplitPane-DuyzUhKB.css.map → C_SplitPane-N-a_D-7F.css.map} +1 -1
- package/dist/C_SplitPane.cjs +1 -1
- package/dist/C_SplitPane.css +31 -31
- package/dist/C_SplitPane.js +1 -1
- package/dist/C_SplitPane.js.map +1 -1
- package/dist/C_SplitPane2.js +1 -1
- package/dist/C_SplitPane2.js.map +1 -1
- package/dist/C_Steps-Bk1krgiR.css.map +1 -0
- package/dist/C_Steps-Dn2CMpy1.css.map +1 -0
- package/dist/C_Steps.css +38 -38
- package/dist/C_Steps.js.map +1 -1
- package/dist/C_Steps2.js +1 -1
- package/dist/C_Steps2.js.map +1 -1
- package/dist/C_Table.cjs +1 -1
- package/dist/C_Table.js +1 -1
- package/dist/C_Table.js.map +1 -1
- package/dist/C_Table2.js +2 -2
- package/dist/C_Table2.js.map +1 -1
- package/dist/{C_TagsView-BsutyC3U.css.map → C_TagsView-DX3LecK1.css.map} +1 -1
- package/dist/{C_TagsView-Crm7yWg9.css.map → C_TagsView-DqOTa8bL.css.map} +1 -1
- package/dist/C_TagsView.cjs +1 -1
- package/dist/C_TagsView.css +36 -36
- package/dist/C_TagsView.js +1 -1
- package/dist/C_TagsView.js.map +1 -1
- package/dist/C_TagsView2.js +1 -1
- package/dist/C_TagsView2.js.map +1 -1
- package/dist/C_Theme.cjs +1 -1
- package/dist/C_Theme.js +1 -1
- package/dist/C_Theme.js.map +1 -1
- package/dist/C_Theme2.js +1 -1
- package/dist/C_Theme2.js.map +1 -1
- package/dist/C_Time-BzyQVvTS.css.map +1 -0
- package/dist/C_Time-DmpKLQHc.css.map +1 -0
- package/dist/C_Time.css +5 -5
- package/dist/C_Time.js.map +1 -1
- package/dist/C_Time2.js +1 -1
- package/dist/C_Time2.js.map +1 -1
- package/dist/{C_Timeline-DG70WtJP.css.map → C_Timeline-BKkimrZx.css.map} +1 -1
- package/dist/{C_Timeline-Dzxmmdjg.css.map → C_Timeline-DTuSCX6p.css.map} +1 -1
- package/dist/C_Timeline.cjs +1 -1
- package/dist/C_Timeline.css +45 -45
- package/dist/C_Timeline.js +1 -1
- package/dist/C_Timeline.js.map +1 -1
- package/dist/C_Timeline2.js +1 -1
- package/dist/C_Timeline2.js.map +1 -1
- package/dist/{C_Transfer-CCEqnC-v.css.map → C_Transfer-BOB6OBqS.css.map} +1 -1
- package/dist/{C_Transfer-CStPm4NP.css.map → C_Transfer-Dpq7ezWC.css.map} +1 -1
- package/dist/C_Transfer.cjs +1 -1
- package/dist/C_Transfer.css +31 -31
- package/dist/C_Transfer.js +1 -1
- package/dist/C_Transfer.js.map +1 -1
- package/dist/C_Transfer2.js +1 -1
- package/dist/C_Transfer2.js.map +1 -1
- package/dist/{C_Tree-B-Le4hjQ.css.map → C_Tree-D98nX9qT.css.map} +1 -1
- package/dist/{C_Tree-DnGc_MPb.css.map → C_Tree-ulfm7b-P.css.map} +1 -1
- package/dist/C_Tree.cjs +1 -1
- package/dist/C_Tree.css +33 -33
- package/dist/C_Tree.js +1 -1
- package/dist/C_Tree.js.map +1 -1
- package/dist/C_Tree2.js +1 -1
- package/dist/C_Tree2.js.map +1 -1
- package/dist/{C_Upload-CFAxVGVz.css.map → C_Upload-BR9fg5r4.css.map} +1 -1
- package/dist/{C_Upload-D8_QeSV0.css.map → C_Upload-CPqf74Ea.css.map} +1 -1
- package/dist/C_Upload.cjs +1 -1
- package/dist/C_Upload.css +4 -4
- package/dist/C_Upload.js +1 -1
- package/dist/C_Upload.js.map +1 -1
- package/dist/C_Upload2.js +2 -2
- package/dist/C_Upload2.js.map +1 -1
- package/dist/{C_VideoPlayer-Cdgv4Zq0.css.map → C_VideoPlayer-4J96tKfK.css.map} +1 -1
- package/dist/{C_VideoPlayer-rm0MODUv.css.map → C_VideoPlayer-D35seHkm.css.map} +1 -1
- package/dist/C_VideoPlayer.css +13 -13
- package/dist/C_VideoPlayer.js.map +1 -1
- package/dist/C_VideoPlayer2.js +3 -3
- package/dist/C_VideoPlayer2.js.map +1 -1
- package/dist/{C_VtableGantt-BpY-Rng3.css.map → C_VtableGantt-C2lxFmWg.css.map} +1 -1
- package/dist/{C_VtableGantt-DB87-jdS.css.map → C_VtableGantt-Dwnkw_WL.css.map} +1 -1
- package/dist/C_VtableGantt.cjs +1 -1
- package/dist/C_VtableGantt.css +15 -15
- package/dist/C_VtableGantt.js +1 -1
- package/dist/C_VtableGantt.js.map +1 -1
- package/dist/C_VtableGantt2.js +1 -1
- package/dist/C_VtableGantt2.js.map +1 -1
- package/dist/C_WaterFall.js.map +1 -1
- package/dist/C_WaterFall2.js +1 -1
- package/dist/C_WaterFall2.js.map +1 -1
- package/dist/{C_WorkFlow-7F6op6qN.css.map → C_WorkFlow-2hJfvvTK.css.map} +1 -1
- package/dist/{C_WorkFlow-Cy3HWA6A.css.map → C_WorkFlow-DHoNjl8-.css.map} +1 -1
- package/dist/C_WorkFlow.cjs +1 -1
- package/dist/C_WorkFlow.css +108 -108
- package/dist/C_WorkFlow.js +1 -1
- package/dist/C_WorkFlow.js.map +1 -1
- package/dist/C_WorkFlow2.js +1 -1
- package/dist/C_WorkFlow2.js.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants2.d.ts +4 -4
- package/dist/constants3.d.ts +2 -2
- package/dist/constants4.d.ts +6 -6
- package/dist/constants5.d.ts +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.vue.d.ts +2 -2
- package/dist/index10.vue.d.ts +1 -1
- package/dist/index12.vue.d.ts +2 -2
- package/dist/index13.vue.d.ts +3 -3
- package/dist/index14.vue.d.ts +3 -3
- package/dist/index15.vue.d.ts +7 -7
- package/dist/index17.vue.d.ts +1 -1
- package/dist/index18.vue.d.ts +5 -5
- package/dist/index2.vue.d.ts +2 -2
- package/dist/index20.vue.d.ts +1 -1
- package/dist/index21.vue.d.ts +3 -3
- package/dist/index22.vue.d.ts +1 -1
- package/dist/index26.vue.d.ts +4 -4
- package/dist/index27.vue.d.ts +2 -2
- package/dist/index28.vue.d.ts +3 -3
- package/dist/index4.vue.d.ts +7 -7
- package/dist/index6.vue.d.ts +3 -3
- package/dist/index8.vue.d.ts +4 -4
- package/dist/menuAdapter.js.map +1 -1
- package/dist/resolver.cjs.map +1 -1
- package/dist/resolver.js.map +1 -1
- package/dist/style.css +1483 -1483
- package/dist/useCalendarEvents.d.ts +1 -1
- package/dist/useCollapsePanel.d.ts +3 -3
- package/dist/useCropperCore.d.ts +5 -5
- package/dist/useDraggableLayout.d.ts +5 -5
- package/dist/useDynamicFormState.d.ts +21 -21
- package/dist/useEdgeInteraction.d.ts +3 -3
- package/dist/useFullscreen.d.ts +2 -2
- package/dist/useInfiniteScroll.d.ts +3 -3
- package/dist/useModalEdit.d.ts +1 -1
- package/dist/useQRCode.d.ts +5 -5
- package/dist/useSearchState.d.ts +2 -2
- package/dist/useSignatureHistory.d.ts +5 -5
- package/dist/useSplitResize.d.ts +4 -4
- package/dist/useTimeSelection.d.ts +2 -2
- package/dist/useTreeOperations.d.ts +3 -3
- package/dist/useWorkflowValidation.d.ts +2 -2
- package/package.json +5 -5
- package/dist/C_ActionBar-jZPsiXtT.css.map +0 -1
- package/dist/C_ActionBar-nnfbZCea.css.map +0 -1
- package/dist/C_Code-DPZlNSxL.css.map +0 -1
- package/dist/C_Code-avZjAnHD.css.map +0 -1
- package/dist/C_Editor-C49qURgb.css.map +0 -1
- package/dist/C_Editor-DuxBD9Qg.css.map +0 -1
- package/dist/C_FullCalendar-1U4VlG7X.css.map +0 -1
- package/dist/C_FullCalendar-DLUdCtqf.css.map +0 -1
- package/dist/C_Steps-BHYNJI1l.css.map +0 -1
- package/dist/C_Steps-DY7vl-4I.css.map +0 -1
- package/dist/C_Time-Bd_e1YDN.css.map +0 -1
- package/dist/C_Time-CKb_fwdm.css.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"C_NotificationCenter.js","names":["getItem","$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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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","/* unplugin-vue-components disabled */<!--\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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"}
|
|
1
|
+
{"version":3,"file":"C_NotificationCenter.js","names":["getItem"],"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/composables/useNotificationFormat.ts","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationDetail.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/** 时间单位阈值 */\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 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=\"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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"}
|