message.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. import { defineStore } from 'pinia';
  2. import { MessageApi, MessageMockData } from '/@/utils/messageApi';
  3. // 消息类型
  4. export interface MessageItem {
  5. id: number;
  6. title: string;
  7. content: string;
  8. type: number; // 1-系统通知,2-站内信,3-待办事项,4-公告通知
  9. senderId: number;
  10. senderName: string;
  11. receiverId: number;
  12. receiverName: string;
  13. status: number; // 0-未读,1-已读,2-已删除
  14. priority: number; // 0-普通,1-重要,2-紧急
  15. bizType?: string;
  16. bizId?: number;
  17. readTime?: string;
  18. createTime: string;
  19. }
  20. // 消息类型枚举
  21. export const MessageType = {
  22. SYSTEM: 1, // 系统通知
  23. LETTER: 2, // 站内信
  24. TODO: 3, // 待办事项
  25. NOTICE: 4 // 公告通知
  26. };
  27. // 消息类型标签(从字典读取)
  28. import Session from '/@/utils/session';
  29. const getDictLabel = (code: string, value: number): string => {
  30. const dicts = Session.get("dicts");
  31. if (!dicts) return '';
  32. const item = dicts[code]?.find((k: any) => k.value == value);
  33. return item?.name || '';
  34. };
  35. const getDictColor = (code: string, value: number): string => {
  36. const dicts = Session.get("dicts");
  37. if (!dicts) return '';
  38. const item = dicts[code]?.find((k: any) => k.value == value);
  39. return item?.color || '';
  40. };
  41. export const getMessageTypeLabel = (type: number) => getDictLabel('message_type', type);
  42. export const getPriorityLabel = (priority: number) => getDictLabel('priority', priority);
  43. export const getPriorityColor = (priority: number) => getDictColor('priority', priority);
  44. // 是否使用Mock数据
  45. const useMock = true;
  46. /**
  47. * 消息通知 Store
  48. */
  49. export const useMessageStore = defineStore('message', {
  50. state: () => ({
  51. // 未读消息数量
  52. unreadCount: 0,
  53. // 各类型未读数量
  54. unreadCountByType: {
  55. 1: 0, // 系统通知
  56. 2: 0, // 站内信
  57. 3: 0, // 待办事项
  58. 4: 0 // 公告通知
  59. } as Record<number, number>,
  60. // 最新未读消息(用于下拉预览)
  61. latestMessages: [] as MessageItem[],
  62. // 消息列表(完整分页列表)
  63. messageList: [] as MessageItem[],
  64. // 分页信息
  65. pagination: {
  66. pageNum: 1,
  67. pageSize: 10,
  68. total: 0
  69. },
  70. // 当前选中的消息类型筛选
  71. currentType: null as number | null,
  72. // 轮询定时器
  73. pollingTimer: null as any,
  74. // 是否正在加载
  75. loading: false
  76. }),
  77. getters: {
  78. // 是否有未读消息
  79. hasUnread: (state) => state.unreadCount > 0,
  80. // 获取某类型的未读数量
  81. getUnreadByType: (state) => (type: number) => state.unreadCountByType[type] || 0
  82. },
  83. actions: {
  84. /**
  85. * 获取未读消息数量
  86. */
  87. async fetchUnreadCount() {
  88. try {
  89. if (useMock) {
  90. this.unreadCount = MessageMockData.unreadCount;
  91. this.unreadCountByType = { ...MessageMockData.unreadCountByType };
  92. return;
  93. }
  94. const count = await MessageApi.getUnreadCount() as number;
  95. this.unreadCount = count;
  96. const countByType = await MessageApi.getUnreadCountByType() as Record<number, number>;
  97. this.unreadCountByType = countByType;
  98. } catch (error) {
  99. console.error('获取未读消息数量失败:', error);
  100. // 使用Mock数据
  101. this.unreadCount = MessageMockData.unreadCount;
  102. this.unreadCountByType = { ...MessageMockData.unreadCountByType };
  103. }
  104. },
  105. /**
  106. * 获取最新未读消息(用于下拉预览)
  107. */
  108. async fetchLatestMessages(limit: number = 5) {
  109. try {
  110. if (useMock) {
  111. this.latestMessages = MessageMockData.list
  112. .filter(m => m.status === 0)
  113. .slice(0, limit);
  114. return;
  115. }
  116. const messages = await MessageApi.getLatestUnread(limit) as MessageItem[];
  117. this.latestMessages = messages;
  118. } catch (error) {
  119. console.error('获取最新消息失败:', error);
  120. this.latestMessages = MessageMockData.list
  121. .filter(m => m.status === 0)
  122. .slice(0, limit);
  123. }
  124. },
  125. /**
  126. * 获取消息列表(分页)
  127. */
  128. async fetchMessageList(params?: {
  129. pageNum?: number;
  130. pageSize?: number;
  131. type?: number;
  132. status?: number;
  133. title?: string;
  134. }) {
  135. this.loading = true;
  136. try {
  137. const queryParams = {
  138. pageNum: params?.pageNum || this.pagination.pageNum,
  139. pageSize: params?.pageSize || this.pagination.pageSize,
  140. type: params?.type ?? this.currentType ?? undefined,
  141. status: params?.status,
  142. title: params?.title
  143. };
  144. if (useMock) {
  145. let filteredList = [...MessageMockData.list];
  146. if (queryParams.type) {
  147. filteredList = filteredList.filter(m => m.type === queryParams.type);
  148. }
  149. if (queryParams.status !== undefined) {
  150. filteredList = filteredList.filter(m => m.status === queryParams.status);
  151. }
  152. this.messageList = filteredList;
  153. this.pagination.total = filteredList.length;
  154. return;
  155. }
  156. const result = await MessageApi.getMessageList(queryParams) as any;
  157. this.messageList = result.list || [];
  158. this.pagination.total = result.total || 0;
  159. this.pagination.pageNum = queryParams.pageNum;
  160. this.pagination.pageSize = queryParams.pageSize;
  161. } catch (error) {
  162. console.error('获取消息列表失败:', error);
  163. let filteredList = [...MessageMockData.list];
  164. if (params?.type) {
  165. filteredList = filteredList.filter(m => m.type === params.type);
  166. }
  167. this.messageList = filteredList;
  168. this.pagination.total = filteredList.length;
  169. } finally {
  170. this.loading = false;
  171. }
  172. },
  173. /**
  174. * 标记单条消息已读
  175. */
  176. async markAsRead(messageId: number) {
  177. try {
  178. if (!useMock) {
  179. await MessageApi.markAsRead(messageId);
  180. }
  181. // 更新本地状态
  182. const message = this.messageList.find(m => m.id === messageId);
  183. if (message && message.status === 0) {
  184. message.status = 1;
  185. message.readTime = new Date().toLocaleString();
  186. this.unreadCount = Math.max(0, this.unreadCount - 1);
  187. if (this.unreadCountByType[message.type]) {
  188. this.unreadCountByType[message.type]--;
  189. }
  190. }
  191. // 更新最新消息列表
  192. this.latestMessages = this.latestMessages.filter(m => m.id !== messageId);
  193. } catch (error) {
  194. console.error('标记已读失败:', error);
  195. }
  196. },
  197. /**
  198. * 批量标记已读
  199. */
  200. async batchMarkAsRead(messageIds: number[]) {
  201. try {
  202. if (!useMock) {
  203. await MessageApi.batchMarkAsRead(messageIds);
  204. }
  205. // 更新本地状态
  206. messageIds.forEach(id => {
  207. const message = this.messageList.find(m => m.id === id);
  208. if (message && message.status === 0) {
  209. message.status = 1;
  210. message.readTime = new Date().toLocaleString();
  211. this.unreadCount = Math.max(0, this.unreadCount - 1);
  212. if (this.unreadCountByType[message.type]) {
  213. this.unreadCountByType[message.type]--;
  214. }
  215. }
  216. });
  217. // 更新最新消息列表
  218. this.latestMessages = this.latestMessages.filter(m => !messageIds.includes(m.id));
  219. } catch (error) {
  220. console.error('批量标记已读失败:', error);
  221. }
  222. },
  223. /**
  224. * 全部标记已读
  225. */
  226. async markAllAsRead(type?: number) {
  227. try {
  228. if (!useMock) {
  229. await MessageApi.markAllAsRead(type);
  230. }
  231. // 更新本地状态
  232. this.messageList.forEach(message => {
  233. if (message.status === 0 && (!type || message.type === type)) {
  234. message.status = 1;
  235. message.readTime = new Date().toLocaleString();
  236. }
  237. });
  238. if (type) {
  239. this.unreadCount = Math.max(0, this.unreadCount - (this.unreadCountByType[type] || 0));
  240. this.unreadCountByType[type] = 0;
  241. } else {
  242. this.unreadCount = 0;
  243. Object.keys(this.unreadCountByType).forEach(key => {
  244. this.unreadCountByType[Number(key)] = 0;
  245. });
  246. }
  247. // 清空最新消息列表
  248. if (!type) {
  249. this.latestMessages = [];
  250. } else {
  251. this.latestMessages = this.latestMessages.filter(m => m.type !== type);
  252. }
  253. } catch (error) {
  254. console.error('全部标记已读失败:', error);
  255. }
  256. },
  257. /**
  258. * 删除消息
  259. */
  260. async deleteMessage(messageId: number) {
  261. try {
  262. if (!useMock) {
  263. await MessageApi.deleteMessage(messageId);
  264. }
  265. // 从列表中移除
  266. const index = this.messageList.findIndex(m => m.id === messageId);
  267. if (index > -1) {
  268. const message = this.messageList[index];
  269. if (message.status === 0) {
  270. this.unreadCount = Math.max(0, this.unreadCount - 1);
  271. if (this.unreadCountByType[message.type]) {
  272. this.unreadCountByType[message.type]--;
  273. }
  274. }
  275. this.messageList.splice(index, 1);
  276. this.pagination.total--;
  277. }
  278. // 从最新消息列表中移除
  279. this.latestMessages = this.latestMessages.filter(m => m.id !== messageId);
  280. } catch (error) {
  281. console.error('删除消息失败:', error);
  282. }
  283. },
  284. /**
  285. * 批量删除消息
  286. */
  287. async batchDeleteMessage(messageIds: number[]) {
  288. try {
  289. if (!useMock) {
  290. await MessageApi.batchDeleteMessage(messageIds);
  291. }
  292. // 从列表中移除
  293. messageIds.forEach(id => {
  294. const index = this.messageList.findIndex(m => m.id === id);
  295. if (index > -1) {
  296. const message = this.messageList[index];
  297. if (message.status === 0) {
  298. this.unreadCount = Math.max(0, this.unreadCount - 1);
  299. if (this.unreadCountByType[message.type]) {
  300. this.unreadCountByType[message.type]--;
  301. }
  302. }
  303. this.messageList.splice(index, 1);
  304. this.pagination.total--;
  305. }
  306. });
  307. // 从最新消息列表中移除
  308. this.latestMessages = this.latestMessages.filter(m => !messageIds.includes(m.id));
  309. } catch (error) {
  310. console.error('批量删除消息失败:', error);
  311. }
  312. },
  313. /**
  314. * 设置当前筛选类型
  315. */
  316. setCurrentType(type: number | null) {
  317. this.currentType = type;
  318. },
  319. /**
  320. * 启动轮询(定时获取未读消息数量)
  321. * 注意:Mock模式下不启动轮询
  322. */
  323. startPolling(interval: number = 60000) {
  324. if (useMock) {
  325. // Mock模式下不需要轮询
  326. return;
  327. }
  328. this.stopPolling();
  329. this.fetchUnreadCount();
  330. this.pollingTimer = setInterval(() => {
  331. this.fetchUnreadCount();
  332. }, interval);
  333. },
  334. /**
  335. * 停止轮询
  336. */
  337. stopPolling() {
  338. if (this.pollingTimer) {
  339. clearInterval(this.pollingTimer);
  340. this.pollingTimer = null;
  341. }
  342. },
  343. /**
  344. * 初始化(登录后调用)
  345. */
  346. async init() {
  347. await this.fetchUnreadCount();
  348. await this.fetchLatestMessages();
  349. this.startPolling();
  350. },
  351. /**
  352. * 清理(登出时调用)
  353. */
  354. cleanup() {
  355. this.stopPolling();
  356. this.unreadCount = 0;
  357. this.unreadCountByType = { 1: 0, 2: 0, 3: 0, 4: 0 };
  358. this.latestMessages = [];
  359. this.messageList = [];
  360. }
  361. }
  362. });