MessageModal.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import React, { useState, useRef, useEffect } from "react";
  2. import { Modal, Button, Tabs, Empty, message } from "antd";
  3. import type { TabsProps } from "antd";
  4. import axios from "axios";
  5. import { MESSAGE_STATUS, MESSAGE_TAB } from "@/constants";
  6. import { useNotificationStore } from "@/store/NotificationStore";
  7. import { Eraser, FileText } from "lucide-react";
  8. import dayjs from "dayjs";
  9. import { renderNoticeLength } from "@/utils/utils";
  10. interface MessageModalProps {
  11. visible: boolean;
  12. onClose: () => void;
  13. fetchNoticeList: (id?: number, isRead?: number) => Promise<any[]>;
  14. }
  15. const MessageModal: React.FC<MessageModalProps> = ({
  16. visible,
  17. onClose,
  18. fetchNoticeList,
  19. }) => {
  20. const [activeKey, setActiveKey] = useState(MESSAGE_TAB.UNREAD);
  21. const { notifications, changeNotification } = useNotificationStore();
  22. const [loading, setLoading] = useState(false);
  23. const [hasMore, setHasMore] = useState(true); // 新增状态,用于标记是否还有更多数据
  24. const observerRef = useRef<HTMLDivElement>(null);
  25. const unreadNotifications = notifications.filter(
  26. (notification) => notification.is_read === MESSAGE_STATUS.UNREAD
  27. );
  28. // 根据activeKey获取对应的isRead值
  29. const getIsReadStatus = (tabKey: string) => {
  30. switch (tabKey) {
  31. case MESSAGE_TAB.UNREAD:
  32. return MESSAGE_STATUS.UNREAD;
  33. case MESSAGE_TAB.HAS_READ:
  34. return MESSAGE_STATUS.HAS_READ;
  35. default:
  36. return undefined; // 全部消息时不传isRead参数
  37. }
  38. };
  39. useEffect(() => {
  40. const observer = new IntersectionObserver(
  41. async (entries) => {
  42. const target = entries[0];
  43. if (target.isIntersecting && !loading && hasMore) {
  44. const filteredNotifications = getFilteredNotifications();
  45. if (filteredNotifications.length > 0) {
  46. setLoading(true);
  47. const lastId =
  48. filteredNotifications[filteredNotifications.length - 1].id;
  49. try {
  50. const isRead = getIsReadStatus(activeKey);
  51. const newMessages = await fetchNoticeList(lastId, isRead);
  52. if (newMessages.length === 0) {
  53. setHasMore(false);
  54. setLoading(false);
  55. return;
  56. }
  57. } catch (error) {
  58. console.error("Failed to fetch more messages:", error);
  59. }
  60. setLoading(false);
  61. }
  62. }
  63. },
  64. {
  65. threshold: 0.1,
  66. }
  67. );
  68. if (observerRef.current) {
  69. observer.observe(observerRef.current);
  70. }
  71. return () => observer.disconnect();
  72. }, [loading, fetchNoticeList, activeKey, hasMore]); // 添加hasMore作为依赖
  73. // 获取过滤后的通知列表
  74. const getFilteredNotifications = () => {
  75. let filteredNotifications = notifications;
  76. if (activeKey === MESSAGE_TAB.UNREAD) {
  77. filteredNotifications = notifications.filter(
  78. (notification) => notification.is_read === MESSAGE_STATUS.UNREAD
  79. );
  80. } else if (activeKey === MESSAGE_TAB.HAS_READ) {
  81. filteredNotifications = notifications.filter(
  82. (notification) => notification.is_read === MESSAGE_STATUS.HAS_READ
  83. );
  84. }
  85. return filteredNotifications;
  86. };
  87. const tabItems: TabsProps["items"] = [
  88. {
  89. key: MESSAGE_TAB.ALL,
  90. label: <span className="flex">全部</span>,
  91. },
  92. {
  93. key: MESSAGE_TAB.UNREAD,
  94. label: (
  95. <span className="flex">
  96. 未读
  97. {!!unreadNotifications.length && (
  98. <span className="ml-1 text-xs text-white bg-red-500 rounded-full w-5 h-5 flex justify-center items-center">
  99. {renderNoticeLength(unreadNotifications)}
  100. </span>
  101. )}
  102. </span>
  103. ),
  104. },
  105. {
  106. key: MESSAGE_TAB.HAS_READ,
  107. label: <span className="flex">已读</span>,
  108. },
  109. ];
  110. // 简化 Tab 切换处理函数
  111. const handleTabChange = (key: string) => {
  112. setActiveKey(key);
  113. setHasMore(true); // 切换tab时重置hasMore状态
  114. };
  115. return (
  116. <Modal
  117. title={
  118. <div className="flex items-center">
  119. <span>
  120. 消息中心
  121. {!!renderNoticeLength(unreadNotifications) &&
  122. `(${renderNoticeLength(unreadNotifications)})`}
  123. </span>
  124. <div
  125. className="cursor-pointer hover:bg-gray-100 rounded-md px-2 py-1 ml-2 text-xs flex justify-center items-center gap-1"
  126. onClick={async () => {
  127. const baseUrl =
  128. import.meta.env.MODE === "development"
  129. ? "/notification"
  130. : import.meta.env.VITE_NOTIFICATION_URL;
  131. const res = await axios.post(`${baseUrl}/bazb/clearMsg`);
  132. if (res.data.code === 200) {
  133. changeNotification({ type: "clear" });
  134. }
  135. }}
  136. >
  137. <Eraser size={12} />
  138. 清除消息
  139. </div>
  140. </div>
  141. }
  142. open={visible}
  143. onCancel={onClose}
  144. footer={null}
  145. style={{
  146. top: 30,
  147. maxWidth: "90%",
  148. maxHeight: "80%",
  149. }}
  150. >
  151. <div className="flex flex-col h-[80vh]">
  152. <Tabs
  153. activeKey={activeKey}
  154. items={tabItems}
  155. className="mb-4"
  156. onChange={handleTabChange}
  157. />
  158. <div className="space-y-4 max-h-[100%] overflow-auto">
  159. {(() => {
  160. const filteredNotifications = getFilteredNotifications();
  161. return (
  162. <>
  163. {filteredNotifications.length === 0 ? (
  164. <div className="flex justify-center items-center h-full">
  165. <Empty description="暂无消息" />
  166. </div>
  167. ) : (
  168. <>
  169. {filteredNotifications.map((message, index) => (
  170. <div
  171. key={index}
  172. className="p-4 bg-gray-50 rounded-lg shadow-sm flex flex-col gap-4"
  173. >
  174. <div className="flex justify-between items-center">
  175. <div className="flex gap-1 items-center">
  176. <div className="p-1 bg-blue-500 rounded-full w-6 h-6 text-white flex justify-center items-center">
  177. <FileText size={14} />
  178. </div>
  179. <div className="font-bold">病例时效提醒</div>
  180. </div>
  181. <div className="text-gray-500 text-xs">
  182. {dayjs(message.push_time).format(
  183. "YYYY-MM-DD HH:mm:ss"
  184. )}
  185. </div>
  186. </div>
  187. <div className="flex items-start space-x-4">
  188. <div className="flex-1">
  189. <div className="flex">
  190. <h4 className="text-sm font-semibold">
  191. {message.title}
  192. </h4>
  193. </div>
  194. <p className="text-gray-700 mt-1 indent-[1.75rem]">
  195. {message.content}
  196. </p>
  197. <p className="text-gray-700 mt-1 indent-[1.75rem]">
  198. {message.footer || "祝您工作顺利!"}
  199. </p>
  200. <div className="mt-2 flex space-x-2 justify-end">
  201. <Button
  202. type="primary"
  203. size="middle"
  204. onClick={() => {
  205. axios.post("/notification/bazb/clearMsg", {
  206. msgId: message.id,
  207. });
  208. changeNotification({
  209. type: "clear",
  210. notification: [message.id],
  211. });
  212. }}
  213. >
  214. 知道了
  215. </Button>
  216. <Button size="middle">去查看</Button>
  217. </div>
  218. </div>
  219. </div>
  220. </div>
  221. ))}
  222. {loading && (
  223. <div className="flex justify-center items-center py-4">
  224. <div className="text-gray-500">加载中...</div>
  225. </div>
  226. )}
  227. {hasMore && (
  228. <div ref={observerRef} style={{ height: "20px" }} />
  229. )}
  230. </>
  231. )}
  232. </>
  233. );
  234. })()}
  235. </div>
  236. </div>
  237. </Modal>
  238. );
  239. };
  240. export default MessageModal;