feat: 加入对话请求

This commit is contained in:
CrescentLeaf
2025-10-19 18:23:46 +08:00
parent af55143292
commit ba71d66db8
8 changed files with 500 additions and 147 deletions

View File

@@ -17,7 +17,7 @@ export type CallMethod =
"User.getMyRecentChats" |
"Chat.getInfo" |
"Chat.updateSettings" |
"Chat.createGroup" |
@@ -25,6 +25,10 @@ export type CallMethod =
"Chat.getIdForPrivate" |
"Chat.getAnotherUserIdFromPrivate" |
"Chat.processJoinRequest" |
"Chat.sendJoinRequest" |
"Chat.getJoinRequests" |
"Chat.sendMessage" |
"Chat.getMessageHistory" |

View File

@@ -0,0 +1,8 @@
export default class JoinRequest {
declare user_id: string
declare title: string
declare avatar?: string
declare reason?: string
[key: string]: unknown
}

View File

@@ -27,6 +27,7 @@ import Preference from '../preference/Preference.tsx'
import GroupSettings from "../../api/client_data/GroupSettings.ts"
import PreferenceUpdater from "../preference/PreferenceUpdater.ts"
import SystemMessage from "./SystemMessage.tsx"
import JoinRequestsList from "./JoinRequestsList.tsx";
interface Args extends React.HTMLAttributes<HTMLElement> {
target: string
@@ -86,7 +87,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
setChatInfo(chatInfo)
if (chatInfo.is_member)
await loadMore()
await loadMore()
setTabItemSelected(chatInfo.is_member ? "Chat" : "RequestJoin")
if (re.data!.type == 'group') {
@@ -253,10 +254,13 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
marginRight: '5px',
}}></mdui-button-icon>
}
<mdui-tab value="Chat">{
chatInfo.title
}</mdui-tab>
{chatInfo.type == 'group' && <mdui-tab value="NewMemberRequests"></mdui-tab>}
{
chatInfo.is_member ? <>
<mdui-tab value="Chat">{chatInfo.title}</mdui-tab>
{chatInfo.type == 'group' && chatInfo.is_admin && <mdui-tab value="NewMemberRequests"></mdui-tab>}
</>
: <mdui-tab value="RequestJoin">{chatInfo.title}</mdui-tab>
}
<mdui-tab value="Settings"></mdui-tab>
<mdui-tab value="None" style={{ display: 'none' }}></mdui-tab>
<div style={{
@@ -268,6 +272,29 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
marginRight: '5px',
}}></mdui-button-icon>
<mdui-tab-panel slot="panel" value="RequestJoin" style={{
display: tabItemSelected == "RequestJoin" ? "flex" : "none",
flexDirection: "column",
height: "100%",
justifyContent: 'center',
alignItems: 'center',
}}>
<div>
<mdui-button onClick={async () => {
const re = await Client.invoke("Chat.sendJoinRequest", {
token: data.access_token,
target: target,
})
if (re.code != 200)
return checkApiSuccessOrSncakbar(re, "发送加入请求失败")
snackbar({
message: '发送成功!',
placement: 'top',
})
}}></mdui-button>
</div>
</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="Chat" ref={chatPanelRef} style={{
display: tabItemSelected == "Chat" ? "flex" : "none",
flexDirection: "column",
@@ -457,7 +484,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
flexDirection: "column",
height: "100%",
}}>
{tabItemSelected == "NewMemberRequests" && <JoinRequestsList target={target} />}
</mdui-tab-panel>
}
<mdui-tab-panel slot="panel" value="Settings" style={{

View File

@@ -0,0 +1,104 @@
import { TextField } from "mdui"
import RecentChat from "../../api/client_data/RecentChat.ts"
import useEventListener from "../useEventListener.ts"
import RecentsListItem from "./JoinRequestsListItem.tsx"
import React from "react"
import useAsyncEffect from "../useAsyncEffect.ts"
import Client from "../../api/Client.ts"
import { checkApiSuccessOrSncakbar } from "../snackbar.ts"
import data from "../../Data.ts"
import EventBus from "../../EventBus.ts"
import isMobileUI from "../isMobileUI.ts"
import JoinRequest from "../../api/client_data/JoinRequest.ts"
import JoinRequestsListItem from "./JoinRequestsListItem.tsx";
interface Args extends React.HTMLAttributes<HTMLElement> {
target: string
}
export default function JoinRequestsList({
target,
...props
}: Args) {
const searchRef = React.useRef<HTMLElement>(null)
const [searchText, setSearchText] = React.useState('')
const [updateJoinRequests, setUpdateJoinRequests] = React.useState<JoinRequest[]>([])
useEventListener(searchRef, 'input', (e) => {
setSearchText((e.target as unknown as TextField).value)
})
useAsyncEffect(async () => {
async function updateJoinRequests() {
const re = await Client.invoke("Chat.getJoinRequests", {
token: data.access_token,
target: target,
})
if (re.code != 200)
return checkApiSuccessOrSncakbar(re, "获取加入请求列表失败")
setUpdateJoinRequests(re.data!.join_requests as JoinRequest[])
}
updateJoinRequests()
EventBus.on('JoinRequestsList.updateJoinRequests', () => updateJoinRequests())
setTimeout(() => updateJoinRequests(), 15 * 1000)
})
async function removeJoinRequest(userId: string) {
const re = await Client.invoke("Chat.processJoinRequest", {
token: data.access_token,
chat_id: target,
user_id: userId,
action: 'remove',
})
if (re.code != 200)
return checkApiSuccessOrSncakbar(re, "删除加入请求失败")
EventBus.emit('JoinRequestsList.updateJoinRequests')
}
async function acceptJoinRequest(userId: string) {
const re = await Client.invoke("Chat.processJoinRequest", {
token: data.access_token,
chat_id: target,
user_id: userId,
action: 'accept',
})
if (re.code != 200)
return checkApiSuccessOrSncakbar(re, "通过加入请求失败")
EventBus.emit('JoinRequestsList.updateJoinRequests')
}
return <mdui-list style={{
overflowY: 'auto',
paddingRight: '10px',
paddingLeft: '10px',
height: '100%',
width: '100%',
}} {...props}>
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
marginTop: '5px',
marginBottom: '13px',
}}></mdui-text-field>
<mdui-list-item rounded style={{
width: '100%',
marginBottom: '15px',
}} icon="refresh" onClick={() => EventBus.emit('JoinRequestsList.updateJoinRequests')}></mdui-list-item>
{
updateJoinRequests.filter((joinRequest) =>
searchText == '' ||
joinRequest.title.includes(searchText) ||
joinRequest.reason?.includes(searchText) ||
joinRequest.user_id.includes(searchText)
).map((v) =>
<JoinRequestsListItem
key={v.user_id}
acceptJoinRequest={acceptJoinRequest}
removeJoinRequest={removeJoinRequest}
joinRequest={v} />
)
}
</mdui-list>
}

View File

@@ -0,0 +1,41 @@
import { $ } from "mdui/jq"
import RecentChat from "../../api/client_data/RecentChat.ts"
import Avatar from "../Avatar.tsx"
import React from 'react'
import JoinRequest from "../../api/client_data/JoinRequest.ts"
interface Args extends React.HTMLAttributes<HTMLElement> {
joinRequest: JoinRequest
acceptJoinRequest: (userId: string) => any
removeJoinRequest: (userId: string) => any
}
export default function JoinRequestsListItem({ joinRequest, acceptJoinRequest, removeJoinRequest }: Args) {
const { user_id, title, avatar, reason } = joinRequest
const itemRef = React.useRef<HTMLElement>(null)
React.useEffect(() => {
$(itemRef.current!.shadowRoot).find('.headline').css('margin-top', '3px')
})
return (
<mdui-list-item rounded style={{
marginTop: '3px',
marginBottom: '3px',
}} ref={itemRef}>
{title}
<Avatar src={avatar} text={title} slot="icon" />
<span slot="description"
style={{
width: "100%",
display: "inline-block",
whiteSpace: "nowrap", /* 禁止换行 */
overflow: "hidden", /* 隐藏溢出内容 */
textOverflow: "ellipsis", /* 显示省略号 */
}}>: {reason || "无"}</span>
<div slot="end-icon">
<mdui-button-icon icon="check" onClick={() => acceptJoinRequest(user_id)}></mdui-button-icon>
<mdui-button-icon icon="delete" onClick={() => removeJoinRequest(user_id)}></mdui-button-icon>
</div>
</mdui-list-item>
)
}