@robot-admin/naive-ui-components 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/README.md +294 -279
  2. package/dist/C_ActionBar-DYG0Yf3A.css.map +1 -0
  3. package/dist/C_ActionBar-MAjRhqcI.css.map +1 -0
  4. package/dist/C_ActionBar.cjs +1 -1
  5. package/dist/C_ActionBar.css +47 -47
  6. package/dist/C_ActionBar.js +1 -1
  7. package/dist/C_ActionBar.js.map +1 -1
  8. package/dist/C_ActionBar2.js +1 -1
  9. package/dist/C_ActionBar2.js.map +1 -1
  10. package/dist/{C_AntV-A3NXx_sF.css.map → C_AntV-BY8jqqmi.css.map} +1 -1
  11. package/dist/{C_AntV-D2Zp2qQF.css.map → C_AntV-BbkHc-_a.css.map} +1 -1
  12. package/dist/C_AntV.cjs +1 -1
  13. package/dist/C_AntV.css +78 -78
  14. package/dist/C_AntV.js +1 -1
  15. package/dist/C_AntV.js.map +1 -1
  16. package/dist/C_AntV2.js +3 -3
  17. package/dist/C_AntV2.js.map +1 -1
  18. package/dist/{C_AudioPlayer-CXl4yP_m.css.map → C_AudioPlayer-DKRIGhi7.css.map} +1 -1
  19. package/dist/{C_AudioPlayer-CcBtt71m.css.map → C_AudioPlayer-DlB6eNsa.css.map} +1 -1
  20. package/dist/C_AudioPlayer.cjs +1 -1
  21. package/dist/C_AudioPlayer.css +47 -47
  22. package/dist/C_AudioPlayer.js +1 -1
  23. package/dist/C_AudioPlayer.js.map +1 -1
  24. package/dist/C_AudioPlayer2.js +1 -1
  25. package/dist/C_AudioPlayer2.js.map +1 -1
  26. package/dist/{C_AvatarGroup-CtPhfH9h.css.map → C_AvatarGroup-D02zci-i.css.map} +1 -1
  27. package/dist/{C_AvatarGroup-M1dNPH3E.css.map → C_AvatarGroup-DatHaUlY.css.map} +1 -1
  28. package/dist/C_AvatarGroup.css +21 -21
  29. package/dist/C_AvatarGroup.js.map +1 -1
  30. package/dist/C_AvatarGroup2.js +1 -1
  31. package/dist/C_AvatarGroup2.js.map +1 -1
  32. package/dist/{C_Barcode-DjTmDkbQ.css.map → C_Barcode-DYDRDe2O.css.map} +1 -1
  33. package/dist/{C_Barcode-Do1Ozsmh.css.map → C_Barcode-muJdc_O4.css.map} +1 -1
  34. package/dist/C_Barcode.css +7 -7
  35. package/dist/C_Barcode.js.map +1 -1
  36. package/dist/C_Barcode2.js +1 -1
  37. package/dist/C_Barcode2.js.map +1 -1
  38. package/dist/C_Breadcrumb.cjs +1 -1
  39. package/dist/C_Breadcrumb.js +1 -1
  40. package/dist/C_Breadcrumb.js.map +1 -1
  41. package/dist/C_Breadcrumb2.js +1 -1
  42. package/dist/C_Breadcrumb2.js.map +1 -1
  43. package/dist/{C_Captcha-CZmAETly.css.map → C_Captcha-9_hk6VmY.css.map} +1 -1
  44. package/dist/{C_Captcha-Ccq3DMrR.css.map → C_Captcha-MtASlJ0f.css.map} +1 -1
  45. package/dist/C_Captcha.css +19 -19
  46. package/dist/C_Captcha.js.map +1 -1
  47. package/dist/C_Captcha2.js +1 -1
  48. package/dist/C_Captcha2.js.map +1 -1
  49. package/dist/C_Cascade.js.map +1 -1
  50. package/dist/C_Cascade2.js +1 -1
  51. package/dist/C_Cascade2.js.map +1 -1
  52. package/dist/{C_Chat-CJ8KESTM.css.map → C_Chat-Csjz_BFw.css.map} +1 -1
  53. package/dist/{C_Chat-DzV4AFLd.css.map → C_Chat-DO1ZBA_0.css.map} +1 -1
  54. package/dist/C_Chat.cjs +1 -1
  55. package/dist/C_Chat.css +65 -65
  56. package/dist/C_Chat.js +1 -1
  57. package/dist/C_Chat.js.map +1 -1
  58. package/dist/C_Chat2.js +1 -1
  59. package/dist/C_Chat2.js.map +1 -1
  60. package/dist/{C_City-Cv5BESaN.css.map → C_City-Bam08Qv3.css.map} +1 -1
  61. package/dist/{C_City-DNKv4MpH.css.map → C_City-Bb3W-KaN.css.map} +1 -1
  62. package/dist/C_City.css +29 -29
  63. package/dist/C_City.js.map +1 -1
  64. package/dist/C_City2.js +1 -1
  65. package/dist/C_City2.js.map +1 -1
  66. package/dist/C_Code-BFi5xxnM.css.map +1 -0
  67. package/dist/C_Code-CZ7yfnri.css.map +1 -0
  68. package/dist/C_Code.cjs +1 -1
  69. package/dist/C_Code.css +3 -3
  70. package/dist/C_Code.js +1 -1
  71. package/dist/C_Code.js.map +1 -1
  72. package/dist/C_Code2.js +1 -1
  73. package/dist/C_Code2.js.map +1 -1
  74. package/dist/{C_CollapsePanel-CedsEM0N.css.map → C_CollapsePanel-CWkFCqNQ.css.map} +1 -1
  75. package/dist/{C_CollapsePanel-BkOivqn3.css.map → C_CollapsePanel-pcVJA_4z.css.map} +1 -1
  76. package/dist/C_CollapsePanel.cjs +1 -1
  77. package/dist/C_CollapsePanel.css +35 -35
  78. package/dist/C_CollapsePanel.js +1 -1
  79. package/dist/C_CollapsePanel.js.map +1 -1
  80. package/dist/C_CollapsePanel2.js +1 -1
  81. package/dist/C_CollapsePanel2.js.map +1 -1
  82. package/dist/{C_ContextMenu-CCsCeI39.css.map → C_ContextMenu-B3Cgz_wJ.css.map} +1 -1
  83. package/dist/{C_ContextMenu-DOTBeltf.css.map → C_ContextMenu-u1LixF3_.css.map} +1 -1
  84. package/dist/C_ContextMenu.cjs +1 -1
  85. package/dist/C_ContextMenu.css +17 -17
  86. package/dist/C_ContextMenu.js +1 -1
  87. package/dist/C_ContextMenu.js.map +1 -1
  88. package/dist/C_ContextMenu2.js +1 -1
  89. package/dist/C_ContextMenu2.js.map +1 -1
  90. package/dist/{C_Cron-C0-8b5af.css.map → C_Cron-Civir5-I.css.map} +1 -1
  91. package/dist/{C_Cron-DqHQ92LN.css.map → C_Cron-iw3qpMsy.css.map} +1 -1
  92. package/dist/C_Cron.cjs +1 -1
  93. package/dist/C_Cron.css +37 -37
  94. package/dist/C_Cron.js +1 -1
  95. package/dist/C_Cron.js.map +1 -1
  96. package/dist/C_Cron2.js +1 -1
  97. package/dist/C_Cron2.js.map +1 -1
  98. package/dist/C_Date.js.map +1 -1
  99. package/dist/C_Date2.js +1 -1
  100. package/dist/C_Date2.js.map +1 -1
  101. package/dist/{C_Draggable-Bq6o0qXn.css.map → C_Draggable-BCjIvIwT.css.map} +1 -1
  102. package/dist/{C_Draggable-r6nhx3yM.css.map → C_Draggable-DwkSlCOS.css.map} +1 -1
  103. package/dist/C_Draggable.css +46 -46
  104. package/dist/C_Draggable.js.map +1 -1
  105. package/dist/C_Draggable2.js +1 -1
  106. package/dist/C_Draggable2.js.map +1 -1
  107. package/dist/C_Editor-BfjjYQ9B.css.map +1 -0
  108. package/dist/C_Editor-Do2StvoJ.css.map +1 -0
  109. package/dist/C_Editor.css +55 -55
  110. package/dist/C_Editor.js.map +1 -1
  111. package/dist/C_Editor2.js +1 -1
  112. package/dist/C_Editor2.js.map +1 -1
  113. package/dist/{C_FilePreview-BVarVvnD.css.map → C_FilePreview-BhcbiXsV.css.map} +1 -1
  114. package/dist/{C_FilePreview-DHGlV4OR.css.map → C_FilePreview-DJudcIbW.css.map} +1 -1
  115. package/dist/C_FilePreview.cjs +1 -1
  116. package/dist/C_FilePreview.css +91 -91
  117. package/dist/C_FilePreview.js +1 -1
  118. package/dist/C_FilePreview.js.map +1 -1
  119. package/dist/C_FilePreview2.js +1 -1
  120. package/dist/C_FilePreview2.js.map +1 -1
  121. package/dist/{C_Form-BciQUPpS.css.map → C_Form-BtcAPIRi.css.map} +1 -1
  122. package/dist/{C_Form-CKHvXL3O.css.map → C_Form-D_gwQ7hC.css.map} +1 -1
  123. package/dist/C_Form.cjs +1 -1
  124. package/dist/C_Form.css +222 -222
  125. package/dist/C_Form.js +1 -1
  126. package/dist/C_Form.js.map +1 -1
  127. package/dist/C_Form2.js +1 -1
  128. package/dist/C_Form2.js.map +1 -1
  129. package/dist/{C_FormSearch-Bmd2CmG6.css.map → C_FormSearch-Bi4XYjjk.css.map} +1 -1
  130. package/dist/{C_FormSearch-DBNtnjaL.css.map → C_FormSearch-DUGOoX0u.css.map} +1 -1
  131. package/dist/C_FormSearch.css +45 -45
  132. package/dist/C_FormSearch.js.map +1 -1
  133. package/dist/C_FormSearch2.js +1 -1
  134. package/dist/C_FormSearch2.js.map +1 -1
  135. package/dist/{C_FormulaEditor-3WG2vtQl.css.map → C_FormulaEditor-CJk3SSUJ.css.map} +1 -1
  136. package/dist/{C_FormulaEditor-Cm0CokN5.css.map → C_FormulaEditor-Cy7mjfVO.css.map} +1 -1
  137. package/dist/C_FormulaEditor.cjs +1 -1
  138. package/dist/C_FormulaEditor.css +78 -78
  139. package/dist/C_FormulaEditor.js +1 -1
  140. package/dist/C_FormulaEditor.js.map +1 -1
  141. package/dist/C_FormulaEditor2.js +1 -1
  142. package/dist/C_FormulaEditor2.js.map +1 -1
  143. package/dist/C_FullCalendar-BQA0OjGB.css.map +1 -0
  144. package/dist/C_FullCalendar-ChJUosAi.css.map +1 -0
  145. package/dist/C_FullCalendar.css +3 -3
  146. package/dist/C_FullCalendar.js.map +1 -1
  147. package/dist/C_FullCalendar2.js +1 -1
  148. package/dist/C_FullCalendar2.js.map +1 -1
  149. package/dist/C_GlobalSearch.js.map +1 -1
  150. package/dist/C_GlobalSearch2.js.map +1 -1
  151. package/dist/C_Guide.cjs +1 -1
  152. package/dist/C_Guide.js +1 -1
  153. package/dist/C_Guide.js.map +1 -1
  154. package/dist/C_Guide2.js +1 -1
  155. package/dist/C_Guide2.js.map +1 -1
  156. package/dist/C_Icon.js.map +1 -1
  157. package/dist/C_Icon2.js +1 -1
  158. package/dist/C_Icon2.js.map +1 -1
  159. package/dist/{C_ImageCropper-D3L2-J_J.css.map → C_ImageCropper-BqZpkZfU.css.map} +1 -1
  160. package/dist/{C_ImageCropper-DrmUlaLi.css.map → C_ImageCropper-CylwGuzS.css.map} +1 -1
  161. package/dist/C_ImageCropper.cjs +1 -1
  162. package/dist/C_ImageCropper.css +10 -10
  163. package/dist/C_ImageCropper.js +1 -1
  164. package/dist/C_ImageCropper.js.map +1 -1
  165. package/dist/C_ImageCropper2.js +1 -1
  166. package/dist/C_ImageCropper2.js.map +1 -1
  167. package/dist/C_Language.cjs +1 -1
  168. package/dist/C_Language.js +1 -1
  169. package/dist/C_Language.js.map +1 -1
  170. package/dist/C_Language2.js +1 -1
  171. package/dist/C_Language2.js.map +1 -1
  172. package/dist/{C_Login-Cst_W1XC.css.map → C_Login-BkdnkBKX.css.map} +1 -1
  173. package/dist/{C_Login-DWZ_6K1x.css.map → C_Login-VkMM6OJ2.css.map} +1 -1
  174. package/dist/C_Login.cjs +1 -1
  175. package/dist/C_Login.css +72 -72
  176. package/dist/C_Login.js +1 -1
  177. package/dist/C_Login.js.map +1 -1
  178. package/dist/C_Login2.js +1 -1
  179. package/dist/C_Login2.js.map +1 -1
  180. package/dist/{C_Map-BCJmDnJT.css.map → C_Map-DNnBcY70.css.map} +1 -1
  181. package/dist/{C_Map-WUMXSAfy.css.map → C_Map-LEXIx6zM.css.map} +1 -1
  182. package/dist/C_Map.css +4 -4
  183. package/dist/C_Map.js.map +1 -1
  184. package/dist/C_Map2.js +1 -1
  185. package/dist/C_Map2.js.map +1 -1
  186. package/dist/{C_Markdown-7J0uPEuk.css.map → C_Markdown-DwmI0_0Q.css.map} +1 -1
  187. package/dist/{C_Markdown-C7Lg0_9z.css.map → C_Markdown-k0794RW0.css.map} +1 -1
  188. package/dist/C_Markdown.css +33 -33
  189. package/dist/C_Markdown.js.map +1 -1
  190. package/dist/C_Markdown2.js +1 -1
  191. package/dist/C_Markdown2.js.map +1 -1
  192. package/dist/C_Menu.js.map +1 -1
  193. package/dist/C_Menu2.js +1 -1
  194. package/dist/C_Menu2.js.map +1 -1
  195. package/dist/C_NotificationCenter.cjs +1 -1
  196. package/dist/C_NotificationCenter.js +1 -1
  197. package/dist/C_NotificationCenter.js.map +1 -1
  198. package/dist/C_NotificationCenter2.js +1 -1
  199. package/dist/C_NotificationCenter2.js.map +1 -1
  200. package/dist/C_OrgChart.js.map +1 -1
  201. package/dist/C_OrgChart2.js +1 -1
  202. package/dist/C_OrgChart2.js.map +1 -1
  203. package/dist/C_Progress.js.map +1 -1
  204. package/dist/C_Progress2.js +1 -1
  205. package/dist/C_Progress2.js.map +1 -1
  206. package/dist/{C_QRCode-DMxSyogU.css.map → C_QRCode-CFNTda_1.css.map} +1 -1
  207. package/dist/{C_QRCode-G7fiAkm4.css.map → C_QRCode-CmWr9lK-.css.map} +1 -1
  208. package/dist/C_QRCode.css +9 -9
  209. package/dist/C_QRCode.js.map +1 -1
  210. package/dist/C_QRCode2.js +1 -1
  211. package/dist/C_QRCode2.js.map +1 -1
  212. package/dist/{C_Signature-BJMvVPWT.css.map → C_Signature-BDx4ctsE.css.map} +1 -1
  213. package/dist/{C_Signature-BJUHx2BF.css.map → C_Signature-Bz_uNbwv.css.map} +1 -1
  214. package/dist/C_Signature.cjs +1 -1
  215. package/dist/C_Signature.css +15 -15
  216. package/dist/C_Signature.js +1 -1
  217. package/dist/C_Signature.js.map +1 -1
  218. package/dist/C_Signature2.js +1 -1
  219. package/dist/C_Signature2.js.map +1 -1
  220. package/dist/C_Skeleton.js.map +1 -1
  221. package/dist/C_Skeleton2.js +1 -1
  222. package/dist/C_Skeleton2.js.map +1 -1
  223. package/dist/{C_SplitPane-Br2eK8IG.css.map → C_SplitPane-BOwr1C7K.css.map} +1 -1
  224. package/dist/{C_SplitPane-DuyzUhKB.css.map → C_SplitPane-N-a_D-7F.css.map} +1 -1
  225. package/dist/C_SplitPane.cjs +1 -1
  226. package/dist/C_SplitPane.css +31 -31
  227. package/dist/C_SplitPane.js +1 -1
  228. package/dist/C_SplitPane.js.map +1 -1
  229. package/dist/C_SplitPane2.js +1 -1
  230. package/dist/C_SplitPane2.js.map +1 -1
  231. package/dist/C_Steps-Bk1krgiR.css.map +1 -0
  232. package/dist/C_Steps-Dn2CMpy1.css.map +1 -0
  233. package/dist/C_Steps.css +38 -38
  234. package/dist/C_Steps.js.map +1 -1
  235. package/dist/C_Steps2.js +1 -1
  236. package/dist/C_Steps2.js.map +1 -1
  237. package/dist/C_Table.cjs +1 -1
  238. package/dist/C_Table.js +1 -1
  239. package/dist/C_Table.js.map +1 -1
  240. package/dist/C_Table2.js +2 -2
  241. package/dist/C_Table2.js.map +1 -1
  242. package/dist/{C_TagsView-BsutyC3U.css.map → C_TagsView-DX3LecK1.css.map} +1 -1
  243. package/dist/{C_TagsView-Crm7yWg9.css.map → C_TagsView-DqOTa8bL.css.map} +1 -1
  244. package/dist/C_TagsView.cjs +1 -1
  245. package/dist/C_TagsView.css +36 -36
  246. package/dist/C_TagsView.js +1 -1
  247. package/dist/C_TagsView.js.map +1 -1
  248. package/dist/C_TagsView2.js +1 -1
  249. package/dist/C_TagsView2.js.map +1 -1
  250. package/dist/C_Theme.cjs +1 -1
  251. package/dist/C_Theme.js +1 -1
  252. package/dist/C_Theme.js.map +1 -1
  253. package/dist/C_Theme2.js +1 -1
  254. package/dist/C_Theme2.js.map +1 -1
  255. package/dist/C_Time-BzyQVvTS.css.map +1 -0
  256. package/dist/C_Time-DmpKLQHc.css.map +1 -0
  257. package/dist/C_Time.css +5 -5
  258. package/dist/C_Time.js.map +1 -1
  259. package/dist/C_Time2.js +1 -1
  260. package/dist/C_Time2.js.map +1 -1
  261. package/dist/{C_Timeline-DG70WtJP.css.map → C_Timeline-BKkimrZx.css.map} +1 -1
  262. package/dist/{C_Timeline-Dzxmmdjg.css.map → C_Timeline-DTuSCX6p.css.map} +1 -1
  263. package/dist/C_Timeline.cjs +1 -1
  264. package/dist/C_Timeline.css +45 -45
  265. package/dist/C_Timeline.js +1 -1
  266. package/dist/C_Timeline.js.map +1 -1
  267. package/dist/C_Timeline2.js +1 -1
  268. package/dist/C_Timeline2.js.map +1 -1
  269. package/dist/{C_Transfer-CCEqnC-v.css.map → C_Transfer-BOB6OBqS.css.map} +1 -1
  270. package/dist/{C_Transfer-CStPm4NP.css.map → C_Transfer-Dpq7ezWC.css.map} +1 -1
  271. package/dist/C_Transfer.cjs +1 -1
  272. package/dist/C_Transfer.css +31 -31
  273. package/dist/C_Transfer.js +1 -1
  274. package/dist/C_Transfer.js.map +1 -1
  275. package/dist/C_Transfer2.js +1 -1
  276. package/dist/C_Transfer2.js.map +1 -1
  277. package/dist/{C_Tree-B-Le4hjQ.css.map → C_Tree-D98nX9qT.css.map} +1 -1
  278. package/dist/{C_Tree-DnGc_MPb.css.map → C_Tree-ulfm7b-P.css.map} +1 -1
  279. package/dist/C_Tree.cjs +1 -1
  280. package/dist/C_Tree.css +33 -33
  281. package/dist/C_Tree.js +1 -1
  282. package/dist/C_Tree.js.map +1 -1
  283. package/dist/C_Tree2.js +1 -1
  284. package/dist/C_Tree2.js.map +1 -1
  285. package/dist/{C_Upload-CFAxVGVz.css.map → C_Upload-BR9fg5r4.css.map} +1 -1
  286. package/dist/{C_Upload-D8_QeSV0.css.map → C_Upload-CPqf74Ea.css.map} +1 -1
  287. package/dist/C_Upload.cjs +1 -1
  288. package/dist/C_Upload.css +4 -4
  289. package/dist/C_Upload.js +1 -1
  290. package/dist/C_Upload.js.map +1 -1
  291. package/dist/C_Upload2.js +2 -2
  292. package/dist/C_Upload2.js.map +1 -1
  293. package/dist/{C_VideoPlayer-Cdgv4Zq0.css.map → C_VideoPlayer-4J96tKfK.css.map} +1 -1
  294. package/dist/{C_VideoPlayer-rm0MODUv.css.map → C_VideoPlayer-D35seHkm.css.map} +1 -1
  295. package/dist/C_VideoPlayer.css +13 -13
  296. package/dist/C_VideoPlayer.js.map +1 -1
  297. package/dist/C_VideoPlayer2.js +3 -3
  298. package/dist/C_VideoPlayer2.js.map +1 -1
  299. package/dist/{C_VtableGantt-BpY-Rng3.css.map → C_VtableGantt-C2lxFmWg.css.map} +1 -1
  300. package/dist/{C_VtableGantt-DB87-jdS.css.map → C_VtableGantt-Dwnkw_WL.css.map} +1 -1
  301. package/dist/C_VtableGantt.cjs +1 -1
  302. package/dist/C_VtableGantt.css +15 -15
  303. package/dist/C_VtableGantt.js +1 -1
  304. package/dist/C_VtableGantt.js.map +1 -1
  305. package/dist/C_VtableGantt2.js +1 -1
  306. package/dist/C_VtableGantt2.js.map +1 -1
  307. package/dist/C_WaterFall.js.map +1 -1
  308. package/dist/C_WaterFall2.js +1 -1
  309. package/dist/C_WaterFall2.js.map +1 -1
  310. package/dist/{C_WorkFlow-7F6op6qN.css.map → C_WorkFlow-2hJfvvTK.css.map} +1 -1
  311. package/dist/{C_WorkFlow-Cy3HWA6A.css.map → C_WorkFlow-DHoNjl8-.css.map} +1 -1
  312. package/dist/C_WorkFlow.cjs +1 -1
  313. package/dist/C_WorkFlow.css +108 -108
  314. package/dist/C_WorkFlow.js +1 -1
  315. package/dist/C_WorkFlow.js.map +1 -1
  316. package/dist/C_WorkFlow2.js +1 -1
  317. package/dist/C_WorkFlow2.js.map +1 -1
  318. package/dist/constants.d.ts +3 -3
  319. package/dist/constants2.d.ts +4 -4
  320. package/dist/constants3.d.ts +3 -3
  321. package/dist/constants4.d.ts +4 -4
  322. package/dist/constants5.d.ts +2 -2
  323. package/dist/data.d.ts +2 -2
  324. package/dist/index.cjs.map +1 -1
  325. package/dist/index.js.map +1 -1
  326. package/dist/index.vue.d.ts +1 -1
  327. package/dist/index10.vue.d.ts +1 -1
  328. package/dist/index11.vue.d.ts +4 -4
  329. package/dist/index12.vue.d.ts +1 -1
  330. package/dist/index13.vue.d.ts +2 -2
  331. package/dist/index14.vue.d.ts +3 -3
  332. package/dist/index15.vue.d.ts +3 -3
  333. package/dist/index16.vue.d.ts +2 -2
  334. package/dist/index17.vue.d.ts +1 -1
  335. package/dist/index18.vue.d.ts +5 -5
  336. package/dist/index2.vue.d.ts +1 -1
  337. package/dist/index20.vue.d.ts +1 -1
  338. package/dist/index22.vue.d.ts +1 -1
  339. package/dist/index23.vue.d.ts +2 -2
  340. package/dist/index24.vue.d.ts +1 -1
  341. package/dist/index26.vue.d.ts +3 -3
  342. package/dist/index27.vue.d.ts +2 -2
  343. package/dist/index28.vue.d.ts +3 -3
  344. package/dist/index3.vue.d.ts +3 -3
  345. package/dist/index4.vue.d.ts +2 -2
  346. package/dist/index6.vue.d.ts +6 -6
  347. package/dist/index7.vue.d.ts +2 -2
  348. package/dist/index8.vue.d.ts +1 -1
  349. package/dist/index9.vue.d.ts +2 -2
  350. package/dist/menuAdapter.js.map +1 -1
  351. package/dist/resolver.cjs.map +1 -1
  352. package/dist/resolver.js.map +1 -1
  353. package/dist/style.css +1483 -1483
  354. package/dist/themes/glass-morphism.css +1 -0
  355. package/dist/useCalendarEvents.d.ts +1 -1
  356. package/dist/useCollapsePanel.d.ts +1 -1
  357. package/dist/useCropperCore.d.ts +2 -2
  358. package/dist/useDraggableLayout.d.ts +4 -4
  359. package/dist/useDynamicFormState.d.ts +21 -21
  360. package/dist/useEdgeInteraction.d.ts +2 -2
  361. package/dist/useModalEdit.d.ts +1 -1
  362. package/dist/useQRCode.d.ts +3 -3
  363. package/dist/useSearchState.d.ts +2 -2
  364. package/dist/useSignatureHistory.d.ts +4 -4
  365. package/dist/useSplitResize.d.ts +2 -2
  366. package/dist/useTimeSelection.d.ts +2 -2
  367. package/dist/useTreeOperations.d.ts +3 -3
  368. package/dist/useWorkflowValidation.d.ts +6 -6
  369. package/package.json +11 -8
  370. package/dist/C_ActionBar-jZPsiXtT.css.map +0 -1
  371. package/dist/C_ActionBar-nnfbZCea.css.map +0 -1
  372. package/dist/C_Code-DPZlNSxL.css.map +0 -1
  373. package/dist/C_Code-avZjAnHD.css.map +0 -1
  374. package/dist/C_Editor-C49qURgb.css.map +0 -1
  375. package/dist/C_Editor-DuxBD9Qg.css.map +0 -1
  376. package/dist/C_FullCalendar-1U4VlG7X.css.map +0 -1
  377. package/dist/C_FullCalendar-DLUdCtqf.css.map +0 -1
  378. package/dist/C_Steps-BHYNJI1l.css.map +0 -1
  379. package/dist/C_Steps-DY7vl-4I.css.map +0 -1
  380. package/dist/C_Time-Bd_e1YDN.css.map +0 -1
  381. package/dist/C_Time-CKb_fwdm.css.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"C_NotificationCenter2.js","names":["$emit","count","hasUrgent","message","$emit","activeCategory","$emit","messages","scrollHeight","loading","hasMore","$emit","message","scrollHeight"],"sources":["../src/components/C_NotificationCenter/constants.ts","../src/components/C_NotificationCenter/composables/useNotificationWS.ts","../src/components/C_NotificationCenter/composables/useNotificationCore.ts","../src/components/C_NotificationCenter/components/NotificationBadge.vue","../src/components/C_NotificationCenter/components/NotificationBadge.vue","../src/components/C_NotificationCenter/components/NotificationBadge.vue","../src/components/C_NotificationCenter/composables/useNotificationFormat.ts","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationItem.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationList.vue","../src/components/C_NotificationCenter/components/NotificationDetail.vue","../src/components/C_NotificationCenter/components/NotificationDetail.vue","../src/components/C_NotificationCenter/components/NotificationDetail.vue","../src/components/C_NotificationCenter/index.vue","../src/components/C_NotificationCenter/index.vue","../src/components/C_NotificationCenter/index.vue"],"sourcesContent":["/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心常量配置\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport type { NotificationCategory, NotificationPriority } from \"./types\";\r\n\r\n/* ─── 默认配置 ─────────────────────────────────── */\r\n\r\n/** 默认角标最大显示数 */\r\nexport const DEFAULT_MAX_BADGE_COUNT = 99;\r\n\r\n/** 默认每页条数 */\r\nexport const DEFAULT_PAGE_SIZE = 20;\r\n\r\n/** 默认轮询间隔(ms):60 秒 */\r\nexport const DEFAULT_POLLING_INTERVAL = 60_000;\r\n\r\n/** 默认缓存 key */\r\nexport const DEFAULT_STORAGE_KEY = \"notification_center\";\r\n\r\n/** WebSocket 默认重连间隔(ms) */\r\nexport const DEFAULT_WS_RECONNECT_INTERVAL = 3_000;\r\n\r\n/** WebSocket 默认最大重连次数 */\r\nexport const DEFAULT_WS_MAX_RECONNECT = 5;\r\n\r\n/** WebSocket 默认心跳间隔(ms) */\r\nexport const DEFAULT_WS_HEARTBEAT_INTERVAL = 30_000;\r\n\r\n/** WebSocket 默认心跳消息 */\r\nexport const DEFAULT_WS_HEARTBEAT_MESSAGE = \"ping\";\r\n\r\n/* ─── 分类配置 ─────────────────────────────────── */\r\n\r\n/** 分类标签映射 */\r\nexport const CATEGORY_MAP: Record<\r\n NotificationCategory,\r\n {\r\n label: string;\r\n icon: string;\r\n color: string;\r\n }\r\n> = {\r\n system: {\r\n label: \"系统通知\",\r\n icon: \"mdi:cog-outline\",\r\n color: \"info\",\r\n },\r\n business: {\r\n label: \"业务消息\",\r\n icon: \"mdi:briefcase-outline\",\r\n color: \"success\",\r\n },\r\n alarm: {\r\n label: \"告警预警\",\r\n icon: \"mdi:alert-outline\",\r\n color: \"warning\",\r\n },\r\n};\r\n\r\n/** 分类 Tab 列表 */\r\nexport const CATEGORY_TABS: {\r\n key: NotificationCategory | \"all\";\r\n label: string;\r\n}[] = [\r\n { key: \"all\", label: \"全部\" },\r\n { key: \"system\", label: \"系统通知\" },\r\n { key: \"business\", label: \"业务消息\" },\r\n { key: \"alarm\", label: \"告警预警\" },\r\n];\r\n\r\n/* ─── 优先级配置 ───────────────────────────────── */\r\n\r\n/** 优先级标签映射 */\r\nexport const PRIORITY_MAP: Record<\r\n NotificationPriority,\r\n {\r\n label: string;\r\n type: \"default\" | \"info\" | \"success\" | \"warning\" | \"error\";\r\n }\r\n> = {\r\n low: { label: \"低\", type: \"default\" },\r\n normal: { label: \"普通\", type: \"info\" },\r\n high: { label: \"重要\", type: \"warning\" },\r\n urgent: { label: \"紧急\", type: \"error\" },\r\n};\r\n\r\n/* ─── 模拟数据(开发环境使用) ─────────────────── */\r\n\r\n/** 模拟消息列表 */\r\nexport const MOCK_MESSAGES = [\r\n {\r\n id: \"msg-001\",\r\n title: \"系统维护通知\",\r\n summary:\r\n \"系统将于今晚 22:00-23:00 进行例行维护升级,届时部分功能可能暂时不可用。\",\r\n content:\r\n \"<p>尊敬的用户:</p><p>为了提升系统性能和稳定性,我们计划于 <strong>2026年2月27日 22:00-23:00</strong> 对系统进行例行维护升级。</p><p>维护期间,以下功能可能暂时不可用:</p><ul><li>报表导出</li><li>数据同步</li><li>定时任务</li></ul><p>请提前保存好未完成的工作,感谢您的理解与支持!</p>\",\r\n category: \"system\" as const,\r\n priority: \"high\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 15 * 60_000).toISOString(),\r\n sender: { name: \"系统管理员\", avatar: \"\" },\r\n },\r\n {\r\n id: \"msg-002\",\r\n title: \"审批流程待处理\",\r\n summary: \"张三提交的《Q1 季度预算申请》需要您审批,请尽快处理。\",\r\n category: \"business\" as const,\r\n priority: \"urgent\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 30 * 60_000).toISOString(),\r\n sender: {\r\n name: \"张三\",\r\n avatar:\r\n \"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png\",\r\n },\r\n actionUrl: \"/workflow/approval\",\r\n actionText: \"去审批\",\r\n },\r\n {\r\n id: \"msg-003\",\r\n title: \"服务器 CPU 使用率告警\",\r\n summary:\r\n \"生产环境节点 prod-node-03 CPU 使用率持续超过 90%,已触发告警阈值。\",\r\n category: \"alarm\" as const,\r\n priority: \"urgent\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 2 * 60_000).toISOString(),\r\n sender: { name: \"监控中心\" },\r\n actionUrl: \"/monitor/server\",\r\n actionText: \"查看详情\",\r\n },\r\n {\r\n id: \"msg-004\",\r\n title: \"新版本发布 v1.14.0\",\r\n summary: \"系统已升级至 v1.14.0,新增通知中心、工作流增强等多项功能。\",\r\n category: \"system\" as const,\r\n priority: \"normal\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 3 * 3_600_000).toISOString(),\r\n sender: { name: \"系统管理员\" },\r\n },\r\n {\r\n id: \"msg-005\",\r\n title: \"项目交付提醒\",\r\n summary: \"「Robot Admin 二期」项目交付截止日期为 3月15日,当前进度 78%。\",\r\n category: \"business\" as const,\r\n priority: \"high\" as const,\r\n status: \"read\" as const,\r\n timestamp: new Date(Date.now() - 8 * 3_600_000).toISOString(),\r\n sender: { name: \"项目管理系统\" },\r\n },\r\n {\r\n id: \"msg-006\",\r\n title: \"数据库连接池告警\",\r\n summary: \"数据库连接池使用率达到 85%,建议关注并适时扩容。\",\r\n category: \"alarm\" as const,\r\n priority: \"high\" as const,\r\n status: \"read\" as const,\r\n timestamp: new Date(Date.now() - 12 * 3_600_000).toISOString(),\r\n sender: { name: \"监控中心\" },\r\n },\r\n {\r\n id: \"msg-007\",\r\n title: \"周报提交提醒\",\r\n summary: \"本周周报提交截止时间为周五 18:00,请及时提交。\",\r\n category: \"business\" as const,\r\n priority: \"normal\" as const,\r\n status: \"read\" as const,\r\n timestamp: new Date(Date.now() - 24 * 3_600_000).toISOString(),\r\n sender: { name: \"OA 系统\" },\r\n },\r\n {\r\n id: \"msg-008\",\r\n title: \"账号安全提醒\",\r\n summary: \"检测到您的账号在新设备上登录,如非本人操作请及时修改密码。\",\r\n category: \"system\" as const,\r\n priority: \"high\" as const,\r\n status: \"unread\" as const,\r\n timestamp: new Date(Date.now() - 45 * 60_000).toISOString(),\r\n sender: { name: \"安全中心\" },\r\n },\r\n];\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: WebSocket 连接管理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { ref, readonly, onBeforeUnmount } from \"vue\";\r\nimport type {\r\n NotificationWSConfig,\r\n WSConnectionStatus,\r\n WSNotificationPayload,\r\n} from \"../types\";\r\nimport {\r\n DEFAULT_WS_RECONNECT_INTERVAL,\r\n DEFAULT_WS_MAX_RECONNECT,\r\n DEFAULT_WS_HEARTBEAT_INTERVAL,\r\n DEFAULT_WS_HEARTBEAT_MESSAGE,\r\n} from \"../constants\";\r\n\r\n/**\r\n * WebSocket 连接管理\r\n *\r\n * 管理 WebSocket 的建连、重连、心跳保活、消息解析。\r\n * 支持自动重连、鉴权 token、心跳检测。\r\n */\r\nexport function useNotificationWS(\r\n onMessage: (payload: WSNotificationPayload) => void,\r\n onStatusChange?: (status: WSConnectionStatus) => void,\r\n) {\r\n /** 连接状态 */\r\n const status = ref<WSConnectionStatus>(\"disconnected\");\r\n\r\n /** WebSocket 实例 */\r\n let ws: WebSocket | null = null;\r\n /** 重连计数器 */\r\n let reconnectCount = 0;\r\n /** 重连定时器 */\r\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n /** 心跳定时器 */\r\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\r\n /** 当前配置 */\r\n let currentConfig: NotificationWSConfig | null = null;\r\n\r\n /**\r\n * 更新连接状态\r\n */\r\n function setStatus(s: WSConnectionStatus) {\r\n status.value = s;\r\n onStatusChange?.(s);\r\n }\r\n\r\n /**\r\n * 建立连接\r\n */\r\n function connect(config: NotificationWSConfig) {\r\n currentConfig = config;\r\n reconnectCount = 0;\r\n createConnection(config);\r\n }\r\n\r\n /**\r\n * 创建 WebSocket 连接\r\n */\r\n function createConnection(config: NotificationWSConfig) {\r\n cleanup();\r\n\r\n const url = buildUrl(config);\r\n setStatus(\"connecting\");\r\n\r\n try {\r\n ws = new WebSocket(url);\r\n } catch {\r\n setStatus(\"disconnected\");\r\n scheduleReconnect(config);\r\n return;\r\n }\r\n\r\n ws.addEventListener(\"open\", () => {\r\n setStatus(\"connected\");\r\n reconnectCount = 0;\r\n startHeartbeat(config);\r\n });\r\n\r\n ws.addEventListener(\"message\", (event) => {\r\n handleMessage(event.data);\r\n });\r\n\r\n ws.addEventListener(\"close\", () => {\r\n stopHeartbeat();\r\n setStatus(\"disconnected\");\r\n\r\n if (config.autoReconnect !== false) {\r\n scheduleReconnect(config);\r\n }\r\n });\r\n\r\n ws.addEventListener(\"error\", () => {\r\n /* error 事件后通常紧接 close 事件,交由 close 处理重连 */\r\n });\r\n }\r\n\r\n /**\r\n * 构建带 token 的 WebSocket URL\r\n */\r\n function buildUrl(config: NotificationWSConfig): string {\r\n const token = config.getToken?.();\r\n if (token) {\r\n const separator = config.url.includes(\"?\") ? \"&\" : \"?\";\r\n return `${config.url}${separator}token=${encodeURIComponent(token)}`;\r\n }\r\n return config.url;\r\n }\r\n\r\n /**\r\n * 处理接收到的消息\r\n */\r\n function handleMessage(raw: string | ArrayBuffer | Blob) {\r\n if (typeof raw !== \"string\") return;\r\n\r\n /* 过滤心跳响应 */\r\n if (raw === \"pong\") return;\r\n\r\n try {\r\n const payload = JSON.parse(raw) as WSNotificationPayload;\r\n onMessage(payload);\r\n } catch {\r\n /* 非标准 JSON 消息,忽略 */\r\n }\r\n }\r\n\r\n /**\r\n * 调度重连\r\n */\r\n function scheduleReconnect(config: NotificationWSConfig) {\r\n const maxAttempts = config.maxReconnectAttempts ?? DEFAULT_WS_MAX_RECONNECT;\r\n if (reconnectCount >= maxAttempts) return;\r\n\r\n reconnectCount++;\r\n setStatus(\"reconnecting\");\r\n\r\n const interval = config.reconnectInterval ?? DEFAULT_WS_RECONNECT_INTERVAL;\r\n reconnectTimer = setTimeout(() => {\r\n createConnection(config);\r\n }, interval);\r\n }\r\n\r\n /**\r\n * 启动心跳\r\n */\r\n function startHeartbeat(config: NotificationWSConfig) {\r\n const interval = config.heartbeatInterval ?? DEFAULT_WS_HEARTBEAT_INTERVAL;\r\n if (interval <= 0) return;\r\n\r\n const message = config.heartbeatMessage ?? DEFAULT_WS_HEARTBEAT_MESSAGE;\r\n heartbeatTimer = setInterval(() => {\r\n if (ws?.readyState === WebSocket.OPEN) {\r\n ws.send(message);\r\n }\r\n }, interval);\r\n }\r\n\r\n /**\r\n * 停止心跳\r\n */\r\n function stopHeartbeat() {\r\n if (heartbeatTimer) {\r\n clearInterval(heartbeatTimer);\r\n heartbeatTimer = null;\r\n }\r\n }\r\n\r\n /**\r\n * 清理资源\r\n */\r\n function cleanup() {\r\n stopHeartbeat();\r\n if (reconnectTimer) {\r\n clearTimeout(reconnectTimer);\r\n reconnectTimer = null;\r\n }\r\n if (ws) {\r\n ws.close();\r\n ws = null;\r\n }\r\n }\r\n\r\n /**\r\n * 断开连接\r\n */\r\n function disconnect() {\r\n currentConfig = null;\r\n reconnectCount = Infinity;\r\n cleanup();\r\n setStatus(\"disconnected\");\r\n }\r\n\r\n /**\r\n * 手动重新连接\r\n */\r\n function reconnect() {\r\n if (currentConfig) {\r\n reconnectCount = 0;\r\n createConnection(currentConfig);\r\n }\r\n }\r\n\r\n /* 组件卸载时自动清理 */\r\n onBeforeUnmount(cleanup);\r\n\r\n return {\r\n /** WebSocket 连接状态 */\r\n status: readonly(status),\r\n connect,\r\n disconnect,\r\n reconnect,\r\n };\r\n}\r\n","/*\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 通知中心核心状态管理\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n */\r\n\r\nimport { ref, computed, onMounted, onBeforeUnmount } from \"vue\";\r\nimport type {\r\n NotificationMessage,\r\n NotificationCategory,\r\n NotificationCenterProps,\r\n WSNotificationPayload,\r\n WSConnectionStatus,\r\n} from \"../types\";\r\nimport {\r\n DEFAULT_PAGE_SIZE,\r\n DEFAULT_POLLING_INTERVAL,\r\n DEFAULT_STORAGE_KEY,\r\n MOCK_MESSAGES,\r\n} from \"../constants\";\r\nimport { useNotificationWS } from \"./useNotificationWS\";\r\nimport { setItem, getItem } from \"../../../utils/storage\";\r\n\r\n/** 缓存数据结构 */\r\ninterface CachedState {\r\n unreadIds: string[];\r\n lastFetchTime: number;\r\n}\r\n\r\n/** 防抖持久化延迟(ms) */\r\nconst PERSIST_DEBOUNCE = 300;\r\n\r\n/**\r\n * 通知中心核心状态管理\r\n *\r\n * 统一管理消息列表、未读数、分类过滤、已读标记、\r\n * API 交互、WebSocket 桥接、轮询调度和本地缓存。\r\n */\r\nexport function useNotificationCore(props: NotificationCenterProps) {\r\n /* ─── 响应式状态 ─────────────────────────────── */\r\n\r\n /** 消息列表 */\r\n const messages = ref<NotificationMessage[]>([]);\r\n /** 当前选中分类 */\r\n const activeCategory = ref<NotificationCategory | \"all\">(\"all\");\r\n /** 是否正在加载 */\r\n const loading = ref(false);\r\n /** 总条数 */\r\n const total = ref(0);\r\n /** 当前页码 */\r\n const page = ref(1);\r\n /** 选中的消息(详情展示) */\r\n const selectedMessage = ref<NotificationMessage | null>(null);\r\n /** Popover 展开状态 */\r\n const popoverVisible = ref(false);\r\n /** WebSocket 连接状态 */\r\n const wsStatus = ref<WSConnectionStatus>(\"disconnected\");\r\n\r\n /* ─── 计算属性 ───────────────────────────────── */\r\n\r\n /** 未读消息总数 */\r\n const unreadCount = computed(\r\n () => messages.value.filter((m) => m.status === \"unread\").length,\r\n );\r\n\r\n /** 各分类未读数 */\r\n const unreadByCategory = computed(() => {\r\n const counts: Record<string, number> = { system: 0, business: 0, alarm: 0 };\r\n for (const m of messages.value) {\r\n if (m.status === \"unread\" && m.category in counts) {\r\n counts[m.category]++;\r\n }\r\n }\r\n return counts;\r\n });\r\n\r\n /** 当前分类下的消息列表 */\r\n const filteredMessages = computed(() => {\r\n if (activeCategory.value === \"all\") return messages.value;\r\n return messages.value.filter((m) => m.category === activeCategory.value);\r\n });\r\n\r\n /** 是否有更多消息可加载 */\r\n const hasMore = computed(() => messages.value.length < total.value);\r\n\r\n /* ─── 缓存管理 ───────────────────────────────── */\r\n\r\n /** 缓存 key */\r\n const storageKey = computed(() => props.storageKey ?? DEFAULT_STORAGE_KEY);\r\n\r\n /**\r\n * 从缓存恢复未读状态\r\n */\r\n function restoreFromCache() {\r\n const cached = getItem<CachedState>(storageKey.value);\r\n if (cached?.unreadIds) {\r\n const idSet = new Set(cached.unreadIds);\r\n for (const msg of messages.value) {\r\n if (idSet.has(msg.id)) {\r\n msg.status = \"unread\";\r\n }\r\n }\r\n }\r\n }\r\n\r\n /** 防抖持久化定时器 */\r\n let persistTimer: ReturnType<typeof setTimeout> | null = null;\r\n\r\n /**\r\n * 持久化未读状态到本地缓存(防抖)\r\n */\r\n function persistToCache() {\r\n if (persistTimer) clearTimeout(persistTimer);\r\n persistTimer = setTimeout(() => {\r\n const unreadIds = messages.value\r\n .filter((m) => m.status === \"unread\")\r\n .map((m) => m.id);\r\n setItem<CachedState>(storageKey.value, {\r\n unreadIds,\r\n lastFetchTime: Date.now(),\r\n });\r\n persistTimer = null;\r\n }, PERSIST_DEBOUNCE);\r\n }\r\n\r\n /* ─── API 交互 ───────────────────────────────── */\r\n\r\n /**\r\n * 拉取消息列表\r\n */\r\n async function fetchMessages(reset = false) {\r\n if (reset) {\r\n page.value = 1;\r\n }\r\n\r\n loading.value = true;\r\n try {\r\n if (props.fetchNotifications) {\r\n const categoryParam =\r\n activeCategory.value === \"all\" ? undefined : activeCategory.value;\r\n const pageSize = props.pageSize ?? DEFAULT_PAGE_SIZE;\r\n const result = await props.fetchNotifications({\r\n category: categoryParam,\r\n page: page.value,\r\n pageSize,\r\n });\r\n if (reset) {\r\n messages.value = result.list;\r\n } else {\r\n messages.value.push(...result.list);\r\n }\r\n total.value = result.total;\r\n } else {\r\n /* 未配置 API 接口 → 使用模拟数据(全量加载,客户端过滤) */\r\n loadMockData();\r\n }\r\n\r\n restoreFromCache();\r\n } finally {\r\n loading.value = false;\r\n }\r\n }\r\n\r\n /**\r\n * 加载模拟数据(全量加载,filteredMessages 负责客户端过滤)\r\n */\r\n function loadMockData() {\r\n messages.value = MOCK_MESSAGES.map((m) => ({ ...m }));\r\n total.value = MOCK_MESSAGES.length;\r\n }\r\n\r\n /**\r\n * 加载更多\r\n */\r\n async function loadMore() {\r\n if (!hasMore.value || loading.value) return;\r\n page.value++;\r\n await fetchMessages();\r\n }\r\n\r\n /**\r\n * 标记指定消息为已读\r\n */\r\n async function markAsRead(ids: string[]) {\r\n const idSet = new Set(ids);\r\n\r\n /* 乐观更新 */\r\n for (const msg of messages.value) {\r\n if (idSet.has(msg.id)) {\r\n msg.status = \"read\";\r\n }\r\n }\r\n persistToCache();\r\n\r\n /* 如果配置了 API → 同步到服务端 */\r\n if (props.markAsRead) {\r\n try {\r\n await props.markAsRead(ids);\r\n } catch {\r\n /* 回滚 */\r\n for (const msg of messages.value) {\r\n if (idSet.has(msg.id)) {\r\n msg.status = \"unread\";\r\n }\r\n }\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 标记全部已读\r\n */\r\n async function markAllAsRead(category?: NotificationCategory) {\r\n const targetMessages = category\r\n ? messages.value.filter(\r\n (m) => m.category === category && m.status === \"unread\",\r\n )\r\n : messages.value.filter((m) => m.status === \"unread\");\r\n\r\n /* 乐观更新 */\r\n for (const msg of targetMessages) {\r\n msg.status = \"read\";\r\n }\r\n persistToCache();\r\n\r\n if (props.markAllRead) {\r\n try {\r\n await props.markAllRead(category);\r\n } catch {\r\n /* 回滚 */\r\n for (const msg of targetMessages) {\r\n msg.status = \"unread\";\r\n }\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 删除消息\r\n */\r\n async function deleteMessages(ids: string[]) {\r\n const idSet = new Set(ids);\r\n const backup = [...messages.value];\r\n\r\n /* 乐观更新 */\r\n messages.value = messages.value.filter((m) => !idSet.has(m.id));\r\n total.value = Math.max(0, total.value - ids.length);\r\n persistToCache();\r\n\r\n if (props.deleteNotification) {\r\n try {\r\n await props.deleteNotification(ids);\r\n } catch {\r\n messages.value = backup;\r\n total.value += ids.length;\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 清空消息\r\n */\r\n async function clearMessages(category?: NotificationCategory) {\r\n const backup = [...messages.value];\r\n const backupTotal = total.value;\r\n\r\n if (category) {\r\n messages.value = messages.value.filter((m) => m.category !== category);\r\n } else {\r\n messages.value = [];\r\n }\r\n total.value = messages.value.length;\r\n persistToCache();\r\n\r\n if (props.clearNotifications) {\r\n try {\r\n await props.clearNotifications(category);\r\n } catch {\r\n messages.value = backup;\r\n total.value = backupTotal;\r\n persistToCache();\r\n }\r\n }\r\n }\r\n\r\n /* ─── WebSocket 桥接 ─────────────────────────── */\r\n\r\n /**\r\n * 处理 WebSocket 推送消息\r\n */\r\n function handleWSMessage(payload: WSNotificationPayload) {\r\n switch (payload.type) {\r\n case \"new_message\": {\r\n const msg = payload.data as NotificationMessage;\r\n /* 去重后插入到列表头部 */\r\n if (!messages.value.some((m) => m.id === msg.id)) {\r\n messages.value.unshift(msg);\r\n total.value++;\r\n persistToCache();\r\n showDesktopNotification(msg);\r\n }\r\n break;\r\n }\r\n case \"read_sync\": {\r\n const syncMessages = payload.data as NotificationMessage[];\r\n const readIds = new Set(syncMessages.map((m) => m.id));\r\n for (const msg of messages.value) {\r\n if (readIds.has(msg.id)) msg.status = \"read\";\r\n }\r\n persistToCache();\r\n break;\r\n }\r\n case \"count_update\": {\r\n /* 服务端推送的未读数可用于校准 */\r\n break;\r\n }\r\n }\r\n }\r\n\r\n /** WebSocket 连接状态变更回调 */\r\n function handleWSStatusChange(s: WSConnectionStatus) {\r\n wsStatus.value = s;\r\n }\r\n\r\n const { connect: wsConnect, disconnect: wsDisconnect } = useNotificationWS(\r\n handleWSMessage,\r\n handleWSStatusChange,\r\n );\r\n\r\n /* ─── 桌面通知 ──────────────────────────────── */\r\n\r\n /**\r\n * 发送桌面通知\r\n */\r\n function showDesktopNotification(msg: NotificationMessage) {\r\n if (!props.desktopNotification) return;\r\n if (!(\"Notification\" in window)) return;\r\n\r\n if (Notification.permission === \"granted\") {\r\n createDesktopNotification(msg);\r\n } else if (Notification.permission !== \"denied\") {\r\n Notification.requestPermission().then((permission) => {\r\n if (permission === \"granted\") {\r\n createDesktopNotification(msg);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * 创建桌面通知实例\r\n */\r\n function createDesktopNotification(msg: NotificationMessage) {\r\n const n = new Notification(msg.title, {\r\n body: msg.summary,\r\n icon: msg.sender?.avatar || \"/robot-avatar.png\",\r\n tag: msg.id,\r\n });\r\n\r\n n.addEventListener(\"click\", () => {\r\n window.focus();\r\n selectMessage(msg);\r\n n.close();\r\n });\r\n\r\n /* 5 秒后自动关闭 */\r\n setTimeout(() => n.close(), 5000);\r\n }\r\n\r\n /* ─── 轮询 ──────────────────────────────────── */\r\n\r\n let pollingTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n /**\r\n * 启动轮询\r\n */\r\n function startPolling() {\r\n stopPolling();\r\n const interval = props.pollingInterval ?? DEFAULT_POLLING_INTERVAL;\r\n if (interval <= 0) return;\r\n\r\n pollingTimer = setInterval(() => {\r\n fetchMessages(true);\r\n }, interval);\r\n }\r\n\r\n /**\r\n * 停止轮询\r\n */\r\n function stopPolling() {\r\n if (pollingTimer) {\r\n clearInterval(pollingTimer);\r\n pollingTimer = null;\r\n }\r\n }\r\n\r\n /* ─── 交互 ──────────────────────────────────── */\r\n\r\n /**\r\n * 选中/查看消息\r\n */\r\n function selectMessage(msg: NotificationMessage) {\r\n selectedMessage.value = msg;\r\n /* 自动标记为已读 */\r\n if (msg.status === \"unread\") {\r\n markAsRead([msg.id]);\r\n }\r\n }\r\n\r\n /**\r\n * 返回列表\r\n */\r\n function clearSelection() {\r\n selectedMessage.value = null;\r\n }\r\n\r\n /**\r\n * 切换分类\r\n *\r\n * 纯客户端过滤(Mock / 已加载数据)直接切换,\r\n * API 模式则重新拉取对应分类数据。\r\n */\r\n function switchCategory(category: NotificationCategory | \"all\") {\r\n activeCategory.value = category;\r\n selectedMessage.value = null;\r\n\r\n /* 仅 API 模式需要重新拉取 */\r\n if (props.fetchNotifications) {\r\n fetchMessages(true);\r\n }\r\n }\r\n\r\n /* ─── 初始化 & 清理 ─────────────────────────── */\r\n\r\n /**\r\n * 初始化\r\n */\r\n function init() {\r\n fetchMessages(true);\r\n startPolling();\r\n\r\n /* 如果配置了 WebSocket → 建连 */\r\n if (props.wsConfig) wsConnect(props.wsConfig);\r\n }\r\n\r\n /**\r\n * 销毁\r\n */\r\n function destroy() {\r\n stopPolling();\r\n wsDisconnect();\r\n if (persistTimer) {\r\n clearTimeout(persistTimer);\r\n persistTimer = null;\r\n }\r\n }\r\n\r\n onMounted(init);\r\n onBeforeUnmount(destroy);\r\n\r\n return {\r\n /* 状态 */\r\n messages,\r\n activeCategory,\r\n loading,\r\n total,\r\n page,\r\n selectedMessage,\r\n popoverVisible,\r\n wsStatus,\r\n\r\n /* 计算 */\r\n unreadCount,\r\n unreadByCategory,\r\n filteredMessages,\r\n hasMore,\r\n\r\n /* 操作 */\r\n fetchMessages,\r\n loadMore,\r\n markAsRead,\r\n markAllAsRead,\r\n deleteMessages,\r\n clearMessages,\r\n selectMessage,\r\n clearSelection,\r\n switchCategory,\r\n\r\n /* WebSocket */\r\n connectWS: wsConnect,\r\n disconnectWS: wsDisconnect,\r\n };\r\n}\r\n","/* 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_NotificationCenter2.js","names":[],"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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"}