Files
hfb_sys/frontend/src/features/chats/api/chats.ts
T
yml2213andClaude Opus 4.8 e75a39b03a 新增消息已读状态
复用 chat_participants.last_read_at 推导已读,无需新表/迁移。

- 后端:MessageDTO 增加 is_read,toMessageDTOs 按「全部已读」语义计算
  (所有其他参与者均已读才算已读,1对1 即对方已读);MarkRead 成功后
  推送 conversation_read 事件(含 reader 与 read_at),NotifyConversation
  与 NotifyAllAdmins 双通道通知,使发送方/客服实时刷新已读
- 前端:ChatMessage 增加 is_read,ChatView/AdminChatsView/MobileChatView
  在自己的消息下渲染「已读/未读」;收到 conversation_read 且本端仍有
  未获回执的自己消息时才重载,避免无谓请求

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:22:05 +08:00

237 lines
6.2 KiB
TypeScript

import { apiClient } from '@/shared/api/client'
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
export interface ChatParticipant {
id: number
conversation_id: number
participant_type: 'user' | 'admin'
participant_id: number
role: 'renter' | 'owner' | 'support' | 'customer' | 'admin'
remark: string
display_name: string
avatar_url: string
last_read_at?: string
joined_at: string
}
export interface ChatConversation {
id: number
order_id: number | null
type: string
title: string
status: string
role: 'renter' | 'owner' | 'support' | 'customer' | 'admin'
participants?: ChatParticipant[]
last_message_id?: number
last_message_preview: string
last_message_at?: string
unread_count: number
created_at: string
updated_at: string
}
export interface ChatMessage {
id: number
conversation_id: number
sender_type: 'user' | 'admin' | 'system'
sender_id: number
sender_role: 'renter' | 'owner' | 'support' | 'customer' | 'system' | 'admin'
sender_name: string
sender_avatar: string
is_self: boolean
is_read: boolean
content_type: 'text' | 'system'
content: string
attachment_urls: string[]
created_at: string
}
export async function fetchChats(page = 1, pageSize = 20) {
const { data } = await apiClient.get<ApiResponse<PaginatedResult<ChatConversation>>>('/chats', {
params: { page, page_size: pageSize },
})
return data.data
}
export async function fetchUnreadChatCount() {
const { data } = await apiClient.get<ApiResponse<{ unread_count: number }>>(
'/chats/unread-count',
{
silent: true,
}
)
return data.data.unread_count
}
export async function fetchChat(id: number) {
const { data } = await apiClient.get<ApiResponse<ChatConversation>>(`/chats/${id}`)
return data.data
}
export async function fetchOrderChat(orderId: number) {
const { data } = await apiClient.get<ApiResponse<ChatConversation>>(`/orders/${orderId}/chat`)
return data.data
}
export async function ensureSupportChat() {
const { data } = await apiClient.post<ApiResponse<ChatConversation>>('/chats/support')
return data.data
}
export async function fetchChatMessages(id: number, page = 1, pageSize = 100) {
const { data } = await apiClient.get<ApiResponse<PaginatedResult<ChatMessage>>>(
`/chats/${id}/messages`,
{
params: { page, page_size: pageSize },
}
)
return data.data
}
export async function sendChatMessage(id: number, content: string, attachmentUrls: string[] = []) {
const { data } = await apiClient.post<ApiResponse<ChatMessage>>(`/chats/${id}/messages`, {
content,
attachment_urls: attachmentUrls,
})
return data.data
}
export async function markChatRead(id: number) {
const { data } = await apiClient.post<ApiResponse<{ read: boolean }>>(`/chats/${id}/read`)
return data.data
}
export async function fetchAdminChats(page = 1, pageSize = 50, filter = 'all') {
const { data } = await apiClient.get<ApiResponse<PaginatedResult<ChatConversation>>>(
'/admin/chats',
{
params: { page, page_size: pageSize, filter },
}
)
return data.data
}
export async function fetchAdminChat(id: number) {
const { data } = await apiClient.get<ApiResponse<ChatConversation>>(`/admin/chats/${id}`)
return data.data
}
export async function fetchAdminChatMessages(id: number, page = 1, pageSize = 100) {
const { data } = await apiClient.get<ApiResponse<PaginatedResult<ChatMessage>>>(
`/admin/chats/${id}/messages`,
{
params: { page, page_size: pageSize },
}
)
return data.data
}
export async function sendAdminChatMessage(
id: number,
content: string,
attachmentUrls: string[] = []
) {
const { data } = await apiClient.post<ApiResponse<ChatMessage>>(`/admin/chats/${id}/messages`, {
content,
attachment_urls: attachmentUrls,
})
return data.data
}
export async function markAdminChatRead(id: number) {
const { data } = await apiClient.post<ApiResponse<{ read: boolean }>>(`/admin/chats/${id}/read`)
return data.data
}
export interface SupportAdmin {
id: number
nickname: string
support_status: 'online' | 'offline' | 'busy'
chat_count: number
}
export async function fetchSupportAdmins() {
const { data } = await apiClient.get<ApiResponse<SupportAdmin[]>>('/admin/chats/support-admins')
return data.data
}
export async function transferChat(id: number, toAdminId: number) {
const { data } = await apiClient.post<ApiResponse<{ transferred: boolean }>>(
`/admin/chats/${id}/transfer`,
{
to_admin_id: toAdminId,
}
)
return data.data
}
export async function updateChatRemark(id: number, remark: string) {
const { data } = await apiClient.put<ApiResponse<{ updated: boolean }>>(
`/admin/chats/${id}/remark`,
{ remark }
)
return data.data
}
export interface QuickReply {
id: number
admin_user_id: number
title: string
content: string
sort_order: number
is_global: boolean
}
export async function fetchQuickReplies() {
const { data } = await apiClient.get<ApiResponse<QuickReply[]>>('/admin/chats/quick-replies')
return data.data
}
export async function createQuickReply(
title: string,
content: string,
sortOrder = 0,
isGlobal = false
) {
const { data } = await apiClient.post<ApiResponse<QuickReply>>('/admin/chats/quick-replies', {
title,
content,
sort_order: sortOrder,
is_global: isGlobal,
})
return data.data
}
export async function updateQuickReply(
id: number,
updates: { title?: string; content?: string; sort_order?: number }
) {
const { data } = await apiClient.put<ApiResponse<{ updated: boolean }>>(
`/admin/chats/quick-replies/${id}`,
updates
)
return data.data
}
export async function deleteQuickReply(id: number) {
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(
`/admin/chats/quick-replies/${id}`
)
return data.data
}
export async function fetchAutoWelcomeMessage() {
const { data } = await apiClient.get<ApiResponse<{ message: string }>>(
'/admin/chats/auto-welcome'
)
return data.data.message
}
export async function updateAutoWelcomeMessage(message: string) {
const { data } = await apiClient.put<ApiResponse<{ updated: boolean }>>(
'/admin/chats/auto-welcome',
{ message }
)
return data.data
}