新增消息页面组件,整合消息API,支持获取消息列表、未读消息数量、标记已读和删除消息功能,提升用户消息管理体验。
This commit is contained in:
@@ -4,7 +4,8 @@ import 'dayjs/locale/zh-cn';
|
||||
import type {
|
||||
User, FileLibrary, FileCategory, ThemeSettings,
|
||||
SystemSetting, SystemSettingGroupData,
|
||||
LoginLocation, LoginLocationDetail
|
||||
LoginLocation, LoginLocationDetail,
|
||||
Message, MessageType, MessageStatus
|
||||
} from '../share/types.ts';
|
||||
|
||||
|
||||
@@ -575,6 +576,7 @@ export const SystemAPI = {
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// 更新系统设置
|
||||
@@ -598,3 +600,96 @@ export const SystemAPI = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 消息API响应类型
|
||||
export interface MessageResponse {
|
||||
message: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export interface MessagesResponse {
|
||||
data: Message[];
|
||||
pagination: {
|
||||
total: number;
|
||||
current: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UnreadCountResponse {
|
||||
count: number;
|
||||
}
|
||||
|
||||
// 消息API
|
||||
export const MessageAPI = {
|
||||
// 获取消息列表
|
||||
getMessages: async (params?: {
|
||||
page?: number,
|
||||
pageSize?: number,
|
||||
type?: MessageType,
|
||||
status?: MessageStatus
|
||||
}): Promise<MessagesResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/messages`, { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取消息详情
|
||||
getMessage: async (id: number): Promise<MessageResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/messages/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage: async (data: {
|
||||
title: string,
|
||||
content: string,
|
||||
type: MessageType,
|
||||
receiver_ids: number[]
|
||||
}): Promise<MessageResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/messages`, data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 删除消息(软删除)
|
||||
deleteMessage: async (id: number): Promise<MessageResponse> => {
|
||||
try {
|
||||
const response = await axios.delete(`${API_BASE_URL}/messages/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取未读消息数量
|
||||
getUnreadCount: async (): Promise<UnreadCountResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/messages/unread-count`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 标记消息为已读
|
||||
markAsRead: async (id: number): Promise<MessageResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/messages/${id}/read`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -18,7 +18,7 @@ import HomePage from './pages_index.tsx';
|
||||
import LoginPage from './pages_login.tsx';
|
||||
import { GlobalConfig } from "../share/types.ts";
|
||||
import { ExclamationTriangleIcon, HomeIcon, BellIcon, UserIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
import { NotificationsPage } from './pages_messages.tsx';
|
||||
// 设置中文语言
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
@@ -258,25 +258,6 @@ const ProfilePage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 添加通知页面组件
|
||||
const NotificationsPage = () => (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">通知</h1>
|
||||
<div className="bg-white rounded-lg shadow divide-y">
|
||||
<div className="p-4">
|
||||
<h3 className="font-medium">系统通知</h3>
|
||||
<p className="text-gray-500 text-sm mt-1">欢迎使用移动应用!</p>
|
||||
<p className="text-xs text-gray-400 mt-2">今天 10:00</p>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="font-medium">活动提醒</h3>
|
||||
<p className="text-gray-500 text-sm mt-1">您有一个新的活动邀请</p>
|
||||
<p className="text-xs text-gray-400 mt-2">昨天 14:30</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// 移动端布局组件 - 包含底部导航
|
||||
const MobileLayout = () => {
|
||||
const location = useLocation();
|
||||
|
||||
107
client/mobile/pages_messages.tsx
Normal file
107
client/mobile/pages_messages.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import { BellIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
|
||||
// 添加通知页面组件
|
||||
import { MessageAPI } from './api.ts';
|
||||
|
||||
export const NotificationsPage = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// 获取消息列表
|
||||
const { data: messages, isLoading } = useQuery({
|
||||
queryKey: ['messages'],
|
||||
queryFn: () => MessageAPI.getMessages(),
|
||||
});
|
||||
|
||||
// 获取未读消息数量
|
||||
const { data: unreadCount } = useQuery({
|
||||
queryKey: ['unreadCount'],
|
||||
queryFn: () => MessageAPI.getUnreadCount(),
|
||||
});
|
||||
|
||||
// 标记消息为已读
|
||||
const markAsReadMutation = useMutation({
|
||||
mutationFn: (id: number) => MessageAPI.markAsRead(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['messages'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['unreadCount'] });
|
||||
},
|
||||
});
|
||||
|
||||
// 删除消息
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: number) => MessageAPI.deleteMessage(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['messages'] });
|
||||
},
|
||||
});
|
||||
|
||||
const handleMarkAsRead = (id: number) => {
|
||||
markAsReadMutation.mutate(id);
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
if (confirm('确定要删除这条消息吗?')) {
|
||||
deleteMutation.mutate(id);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">通知</h1>
|
||||
<div className="flex justify-center items-center h-40">
|
||||
<div className="w-8 h-8 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-2xl font-bold">通知</h1>
|
||||
{unreadCount && unreadCount.count > 0 ? (
|
||||
<div className="flex items-center">
|
||||
<BellIcon className="h-5 w-5 text-red-500 mr-1" />
|
||||
<span className="text-sm text-red-500">{unreadCount.count}条未读</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow divide-y">
|
||||
{messages?.data.map((message) => (
|
||||
<div key={message.id} className="p-4">
|
||||
<div className="flex justify-between items-start">
|
||||
<h3 className="font-medium">{message.title}</h3>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMarkAsRead(message.id)}
|
||||
className="text-xs text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
标记已读
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDelete(message.id)}
|
||||
className="text-xs text-red-600 hover:text-red-800"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm mt-1">{message.content}</p>
|
||||
<p className="text-xs text-gray-400 mt-2">
|
||||
{dayjs(message.created_at).format('YYYY-MM-DD HH:mm')}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user