fix:新增审核新进群成员功能

This commit is contained in:
SL 2025-04-11 15:29:31 +08:00
parent a21ddf7382
commit 72661006ed
16 changed files with 346 additions and 152 deletions

View File

@ -28,7 +28,6 @@ import { ConnectStatus } from "wukongimjssdk";
import { WKBaseContext } from "./Components/WKBase";
import StorageService from "./Service/StorageService";
import { ProhibitwordsService } from "./Service/ProhibitwordsService";
import { JSX } from "react";
export enum ThemeMode {
light,
@ -230,7 +229,7 @@ export default class WKApp extends ProviderListener {
new Array<MessageDeleteListener>(); // 消息删除监听
supportFavorites = [MessageContentType.text, MessageContentType.image]; // 注册收藏的消息
supportEdit = [MessageContentType.text]; // 注册编辑的消息
notSupportForward: number[] = []; // 不支持转发的消息
openChannel?: Channel; // 当前打开的会话频道

View File

@ -41,6 +41,15 @@ export default interface ConversationContext {
*/
revokeMessage(message: Message): Promise<void>
/**
*
* @param messageID ID
* @param messageSeq
* @param channelID ID
* @param channelType
* @param content
*/
editMessage(messageID: String, messageSeq: number, channelID: String, channelType: number, content: String): Promise<void>
/**
*
* @param uid
@ -55,8 +64,9 @@ export default interface ConversationContext {
/**
*
* @param message
* @param handlerType 1: 回复消息 2: 编辑消息
*/
reply(message: Message): void
reply(message: Message, handlerType: number): void
/**

View File

@ -1,4 +1,4 @@
import { Channel, ChannelTypeGroup, ChannelTypePerson, ConversationAction, WKSDK, Mention, Message, MessageContent, Reminder, ReminderType, Reply, MessageText } from "wukongimjssdk";
import { Channel, ChannelTypeGroup, ChannelTypePerson, ConversationAction, WKSDK, Mention, Message, MessageContent, Reminder, ReminderType, Reply, MessageText, MessageContentType } from "wukongimjssdk";
import React, { Component, HTMLProps } from "react";
import Provider from "../../Service/Provider";
import ConversationVM from "./vm";
@ -12,7 +12,7 @@ import MessageInput, { MentionModel, MessageInputContext } from "../MessageInput
import ContextMenus, { ContextMenusContext } from "../ContextMenus";
import classNames from "classnames";
import WKAvatar from "../WKAvatar";
import { IconClose } from "@douyinfe/semi-icons";
import { IconClose, IconEdit, IconReply } from "@douyinfe/semi-icons";
import { Toast, Spin } from "@douyinfe/semi-ui";
import { FlameMessageCell } from "../../Messages/Flame";
@ -89,6 +89,9 @@ export class Conversation extends Component<ConversationProps> implements Conver
revokeMessage(message: Message): Promise<void> {
return this.vm.revokeMessage(message)
}
editMessage(messageID: String, messageSeq: number, channelID: String, channelType: number, content: String): Promise<void> {
return this.vm.editMessage(messageID, messageSeq, channelID, channelType, content)
}
onTapAvatar(uid: string, event: React.MouseEvent<Element, MouseEvent>): void {
this.vm.selectUID = uid
@ -130,7 +133,7 @@ export class Conversation extends Component<ConversationProps> implements Conver
}
// 回复消息
reply(message: Message): void {
reply(message: Message, handlerType: number): void {
if (message.fromUID !== WKApp.loginInfo.uid) {
const channelInfo = WKSDK.shared().channelManager.getChannelInfo(new Channel(message.fromUID, ChannelTypePerson))
let name = ""
@ -140,6 +143,11 @@ export class Conversation extends Component<ConversationProps> implements Conver
this._messageInputContext.addMention(message.fromUID, name)
}
if (handlerType === 2) {
let content = message.remoteExtra?.isEdit ? message.remoteExtra?.contentEdit?.conversationDigest : message.content.conversationDigest
this.insertText(content)
}
this.vm.currentHandlerType = handlerType
this.vm.currentReplyMessage = message
}
@ -548,7 +556,7 @@ export class Conversation extends Component<ConversationProps> implements Conver
</div>
<div className="wk-conversation-topview">
{
vm.currentReplyMessage ? <ReplyView message={vm.currentReplyMessage} onClose={() => {
vm.currentReplyMessage ? <ReplyView message={vm.currentReplyMessage} vm={vm} onClose={() => {
vm.currentReplyMessage = undefined
}}></ReplyView> : undefined
}
@ -596,7 +604,7 @@ export class Conversation extends Component<ConversationProps> implements Conver
<MessageInput members={this.vm.subscribers.filter((s) => s.uid !== WKApp.loginInfo.uid)} onContext={(ctx) => {
this._messageInputContext = ctx
}} toolbar={this.chatToolbarUI()} context={this} onSend={(text: string, mention?: MentionModel) => {
}} toolbar={this.chatToolbarUI()} context={this} onSend={async (text: string, mention?: MentionModel) => {
const content = new MessageText(text)
if (mention) {
const mn = new Mention()
@ -605,6 +613,14 @@ export class Conversation extends Component<ConversationProps> implements Conver
content.mention = mn
}
if (vm.currentReplyMessage) {
if (vm.currentHandlerType === 2) {
// 编辑消息
let json = content.encodeJSON()
json['type'] = MessageContentType.text
await vm.editMessage(vm.currentReplyMessage.messageID, vm.currentReplyMessage.messageSeq, vm.currentReplyMessage.channel.channelID, vm.currentReplyMessage.channel.channelType, JSON.stringify(json))
vm.currentReplyMessage = undefined
return
}
const reply = new Reply()
reply.messageID = vm.currentReplyMessage.messageID
reply.messageSeq = vm.currentReplyMessage.messageSeq
@ -796,19 +812,18 @@ class ConversationPositionView extends Component<ConversationPositionViewProps,
interface ReplyViewProps {
message: Message
vm: ConversationVM
onClose?: () => void
}
class ReplyView extends Component<ReplyViewProps> {
render(): React.ReactNode {
const { message, onClose } = this.props
const { message, onClose, vm } = this.props
const fromChannelInfo = WKSDK.shared().channelManager.getChannelInfo(new Channel(message.fromUID, ChannelTypePerson))
return <div className="wk-replyview">
<div className="wk-replyview-close" onClick={() => {
if (onClose) {
onClose()
<div className="wk-replyview-close">
{
vm.currentHandlerType === 1 ? <IconReply className="wk-replyview-close-icon" /> : <IconEdit className="wk-replyview-close-icon" />
}
}}>
<IconClose className="wk-replyview-close-icon" />
</div>
<div className="wk-replyview-content">
<div className="wk-replyview-content-first">
@ -825,10 +840,19 @@ class ReplyView extends Component<ReplyViewProps> {
</div>
<div className="wk-replyview-content-second">
<div className="wk-replyview-content-msg">
{message.content.conversationDigest}
{
message.remoteExtra?.isEdit ? message.remoteExtra?.contentEdit?.conversationDigest : message.content.conversationDigest
}
</div>
</div>
</div>
<div className="wk-replyview-close" onClick={() => {
if (onClose) {
onClose()
}
}}>
<IconClose className="wk-replyview-close-icon" />
</div>
</div>
}
}
@ -859,8 +883,7 @@ class MultiplePanel extends Component<MultiplePanelProps> {
}
}}>
<div className="wk-multiplepanel-content-item-icon">
<svg className="wk-multiplepanel-content-item-icon-svg" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M362.666667 704h554.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H362.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM106.666667 874.666667h810.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H106.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z m427.093333-661.034667V57.152c0-3.84 1.6-7.530667 4.416-10.24a15.36 15.36 0 0 1 21.184 0L846.72 326.122667a21.205333 21.205333 0 0 1 0 30.698666L559.36 635.754667a15.253333 15.253333 0 0 1-10.602667 4.245333 14.72 14.72 0 0 1-14.976-14.485333v-155.733334H503.893333c-116.053333 0-203.946667 22.762667-257.301333 89.792-4.416 5.546667-9.216 11.264-16.256 20.096a8.106667 8.106667 0 0 1-5.248 3.264c-3.989333 0.512-7.125333-1.536-8.128-6.144-2.730667-14.421333-3.626667-29.866667-3.626667-40.746666 0-175.210667 143.466667-322.410667 320.426667-322.410667z m85.333333 85.333333h-85.333333c-80.277333 0-151.914667 41.984-194.453333 104.981334 47.722667-13.44 102.421333-19.52 164.586666-19.52h115.2v74.410666l120.96-117.397333-120.96-117.504v75.029333z"></path></svg>
</div>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 14 4 9 9 4"></polyline><path d="M20 20v-7a4 4 0 0 0-4-4H4"></path></svg> </div>
<div className="wk-multiplepanel-content-item-title">
</div>
@ -883,8 +906,7 @@ class MultiplePanel extends Component<MultiplePanelProps> {
}
}}>
<div className="wk-multiplepanel-content-item-icon">
<svg className="wk-multiplepanel-content-item-icon-svg" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M362.666667 704h554.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H362.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM106.666667 874.666667h810.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H106.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z m427.093333-661.034667V57.152c0-3.84 1.6-7.530667 4.416-10.24a15.36 15.36 0 0 1 21.184 0L846.72 326.122667a21.205333 21.205333 0 0 1 0 30.698666L559.36 635.754667a15.253333 15.253333 0 0 1-10.602667 4.245333 14.72 14.72 0 0 1-14.976-14.485333v-155.733334H503.893333c-116.053333 0-203.946667 22.762667-257.301333 89.792-4.416 5.546667-9.216 11.264-16.256 20.096a8.106667 8.106667 0 0 1-5.248 3.264c-3.989333 0.512-7.125333-1.536-8.128-6.144-2.730667-14.421333-3.626667-29.866667-3.626667-40.746666 0-175.210667 143.466667-322.410667 320.426667-322.410667z m85.333333 85.333333h-85.333333c-80.277333 0-151.914667 41.984-194.453333 104.981334 47.722667-13.44 102.421333-19.52 164.586666-19.52h115.2v74.410666l120.96-117.397333-120.96-117.504v75.029333z"></path></svg>
</div>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg> </div>
<div className="wk-multiplepanel-content-item-title">
</div>

View File

@ -54,7 +54,7 @@ export default class ConversationVM extends ProviderListener {
selectUID?: string // 点击头像的用户uid
private _currentReplyMessage?: Message // 当前回复的消息
private _currentHandlerType: number = 0 // 当前处理类型
onFirstMessagesLoaded?: Function // 第一屏消息已加载完成
constructor(channel: Channel, initLocateMessageSeq?: number) {
@ -66,7 +66,13 @@ export default class ConversationVM extends ProviderListener {
this.initLocateMessageSeq = initLocateMessageSeq
}
}
get currentHandlerType(): number {
return this._currentHandlerType
}
set currentHandlerType(v: number) {
this._currentHandlerType = v
this.notifyListener()
}
get currentReplyMessage() {
return this._currentReplyMessage
}
@ -190,6 +196,11 @@ export default class ConversationVM extends ProviderListener {
}
// 编辑消息
async editMessage(messageID: String, messageSeq: number, channelID: String, channelType: number, content: String): Promise<void> {
return WKApp.conversationProvider.editMessage(messageID, messageSeq, channelID, channelType, content)
}
// 仅仅删除本地消息
async deleteMessagesFromLocal(deletedMessages: Message[]): Promise<void> {
@ -575,15 +586,33 @@ export default class ConversationVM extends ProviderListener {
return
}
for (const messageExtra of messageExtras) {
this.updateReplyMessageContent(messageExtra)
const message = this.findMessageWithMessageID(messageExtra.messageID)
if (message) {
message.message.remoteExtra = messageExtra
message.resetParts()
}
}
this.notifyListener()
}
// 修改被回复的消息体
updateReplyMessageContent(extra: MessageExtra) {
if (!this.messages || this.messages.length <= 0) {
return
}
for (let i = this.messages.length - 1; i >= 0; i--) {
const message = this.messages[i]
if(message.content.reply === undefined){
continue
}
if (message.content.reply.messageID && message.content.reply.messageID === extra.messageID) {
message.content.reply.content = extra.contentEdit
}
}
this.notifyListener()
}
// 通过clientSeq获取消息对象
findMessageWithClientSeq(clientSeq: number): MessageWrap | undefined {
if (!this.messages || this.messages.length <= 0) {

View File

@ -85,6 +85,7 @@ export default class TabAll extends Component<TabAllProps> {
{
this.props.searchResult?.messages.map((item: any) => {
let digest = "[未知消息]"
console.log("item.content--->",item.content)
if(item.content) {
digest = item.content.conversationDigest
}else {

View File

@ -0,0 +1,18 @@
.wk-message-system {
padding: 10px 20px;
width: 85%;
margin: 0 auto;
text-align: center;
margin-top: 15px;
font-size: 12px;
color: rgba(9,30,66,.87);
white-space: pre-line;
}
.wk-message-approve {
margin-left: 10px;
color: #f65835;
}
body[theme-mode=dark] .wk-message-system {
color: #999;
}

View File

@ -0,0 +1,29 @@
import { SystemContent } from "wukongimjssdk"
import React from "react"
import { MessageCell } from "../MessageCell"
import './index.css'
import { MessageWrap } from "../../Service/Model"
import WKApp from "../../App"
export class ApproveGroupMemberCell extends MessageCell {
render() {
const { message } = this.props
const content = message.content as SystemContent
return <div className="wk-message-system">{content.displayText}<a href="#" onClick={() => this.goApproval(message)} className="wk-message-approve"></a></div>
}
async goApproval(message: MessageWrap) {
let inviteNo = message.content["content"]["invite_no"]
const resp = await WKApp.apiClient.get(`groups/${message.channel.channelID}/member/h5confirm`, {
param: { invite_no: inviteNo || '' },
});
if (resp) {
let url = resp["url"]
if (url) {
window.open(url, '_blank');
}
}
}
}

View File

@ -33,6 +33,7 @@ export default class MessageTrail extends Component<MessageTrailProps> {
render() {
const { message,timeStyle,statusStyle } = this.props
return <span className="messageMeta">
{message.remoteExtra?.isEdit?<span className="messageTime"></span>:null}
<span className="messageTime" style={timeStyle}> {moment(message.timestamp * 1000).format('HH:mm')}</span>
{message.send?<span className="messageStatus" style={statusStyle}> {this.getMessageStatusIcon()}</span>:null}
</span>

View File

@ -112,7 +112,7 @@ export class ImageCell extends MessageCell<any, ImageCellState> {
visible={showPreview}
noImgDetails={true}
downloadable={true}
rotatable={true}
rotatable={false}
changeable={false}
showTotal={false}
onMaskClick={() => { this.setState({ showPreview: false }); }}

View File

@ -26,6 +26,10 @@ export class ChannelSettingManager {
return this._onSetting({ "invite": v ? 1 : 0 }, channel)
}
remark(remark: string, channel: Channel): Promise<void> {
return this._onSetting({ "remark": remark }, channel)
}
// 消息回执
receipt(v: boolean, channel: Channel): Promise<void> {
return this._onSetting({ "receipt": v ? 1 : 0 }, channel)
@ -40,6 +44,15 @@ export class ChannelSettingManager {
return this._onSetting({ "forbidden_add_friend": v ? 1 : 0 }, channel)
}
// 允许新成员查看历史消息
allowViewHistoryMsg(v: boolean, channel: Channel): Promise<void> {
return this._onSetting({ "allow_view_history_msg": v ? 1 : 0 }, channel)
}
// 允许群成员置顶消息
allowMemberPinnedMessage(v: boolean, channel: Channel): Promise<void> {
return this._onSetting({ "allow_member_pinned_message": v ? 1 : 0 }, channel)
}
_onSetting(setting: any, channel: Channel): Promise<void> {
return WKApp.dataSource.channelDataSource.updateSetting(setting, channel).catch((err) => {

View File

@ -58,8 +58,9 @@ export class MessageContentTypeConst {
static addMembers: number = 1002 // 添加群成员
static removeMembers: number = 1003 // 删除群成员
static channelUpdate: number = 1005 // 频道更新
static newGroupOwner: number = 1008 // 成为新群主
static screenshot:number = 1014 // 截屏消息
static newGroupOwner: number = 1008 // 新的管理员
static approveGroupMember: number = 1009 // 审批群成员
static screenshot:number = 20 // 截屏消息
// 音频通话消息号段 9900 - 9999
static rtcResult:number = 9989 // 音视频通话结果

View File

@ -36,6 +36,16 @@ export interface IConversationProvider {
*/
revokeMessage(message: Message): Promise<void>
/**
*
* @param messageID ID
* @param messageSeq
* @param channelID ID
* @param channelType
* @param content
*/
editMessage(messageID:String,messageSeq:number,channelID:String,channelType:number,content:String):Promise<void>
/**
*
* @param channel

View File

@ -150,7 +150,7 @@ export class MessageWrap {
this.message = message
this.order = message.messageSeq * OrderFactor
}
private _parts!: Array<Part>
private _parts?: Array<Part>
preMessage?: MessageWrap
nextMessage?: MessageWrap
@ -258,6 +258,11 @@ export class MessageWrap {
return this.message.contentType
}
public resetParts() {
this._parts = undefined
this._parts = this.parts
}
public get parts(): Array<Part> {
if (!this._parts) {
this._parts = this.parseMention()
@ -309,7 +314,10 @@ export class MessageWrap {
if (this.content.contentType !== MessageContentType.text) {
return new Array<Part>()
}
const textContent = this.content as MessageText
let textContent = this.content as MessageText
if (this.message.remoteExtra.isEdit && this.message.remoteExtra.contentEdit !== undefined) {
textContent = this.message.remoteExtra.contentEdit as MessageText
}
let text = textContent.text || ''
const mention = this.content.mention
if (!mention?.uids || mention.uids.length <= 0) {

View File

@ -1,4 +1,4 @@
import React, { JSX } from "react";
import React from "react";
import WKApp from "../App";
import { EndpointCategory, EndpointID } from "./Const";
import { EndpointManager } from "./Module";

View File

@ -82,6 +82,7 @@ import { ProhibitwordsService } from "./Service/ProhibitwordsService";
import { SubscriberList } from "./Components/Subscribers/list";
import GlobalSearch from "./Components/GlobalSearch";
import { handleGlobalSearchClick } from "./Pages/Chat/vm";
import { ApproveGroupMemberCell } from "./Messages/ApproveGroupMember";
export default class BaseModule implements IModule {
messageTone?: Howl;
@ -136,6 +137,8 @@ export default class BaseModule implements IModule {
case MessageContentTypeConst.screenshot:
return ScreenshotCell;
case MessageContentType.signalMessage: // 端对端加密错误消息
case MessageContentTypeConst.approveGroupMember: // 审批群成员
return ApproveGroupMemberCell;
case 98:
return SignalMessageCell;
default:
@ -610,7 +613,7 @@ export default class BaseModule implements IModule {
return {
title: "回复",
onClick: () => {
context.reply(message);
context.reply(message, 1);
},
};
}
@ -1205,7 +1208,29 @@ export default class BaseModule implements IModule {
},
})
);
rows.push(
new Row({
cell: ListItem,
properties: {
title: "备注",
subTitle: channelInfo?.orgData?.remark,
onClick: () => {
this.inputEditPush(
context,
channelInfo?.orgData?.remark || "",
(value: string) => {
return ChannelSettingManager.shared.remark(value, channel).then(() => {
data.refresh()
})
},
"群聊的备注仅自己可见",
15,
true
);
},
},
})
);
return new Section({
rows: rows,
});

View File

@ -0,0 +1,28 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
react@*:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"