更新云信相关代码

This commit is contained in:
XiuYun CHEN
2025-07-10 08:57:32 +08:00
parent a297e0452f
commit 5954f51701
523 changed files with 61546 additions and 1488 deletions
@@ -0,0 +1,14 @@
// 转发选择页面数据模型
@ObservedV2
export class ConversationSelectModel {
// 会话id
conversationId?: string
// 会话名称
name?: string
// 会话头像
avatar?: ResourceStr
// 是否已选中
@Trace isSelected: boolean = false
// 会话人数,用于展示群人数(单聊默认为 0,群聊为群人数)
memberCount: number = 0
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { ConversationSelectModel } from './ConversationSelectModel';
export class ConversationSelectParam {
filterList?: string[] = [];
selectLimit: number = 200;
onSureButtonClick?: (selectedList: ConversationSelectModel[]) => void = undefined
constructor(filterList: string[] = [],
selectLimit: number = 200,
onSureButtonClick?: (selectedList: ConversationSelectModel[]) => void
) {
this.filterList = filterList
this.selectLimit = selectLimit
this.onSureButtonClick = onSureButtonClick
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { ConversationSelectModel } from './ConversationSelectModel';
export class ConversationSelectedParam {
selectedList?: ConversationSelectModel[] = [];
onRemoveButtonClick?: (item: ConversationSelectModel) => void = undefined // 移除按钮点击事件
constructor(selectedList?: ConversationSelectModel[],
onRemoveButtonClick?: (item: ConversationSelectModel) => void
) {
this.selectedList = selectedList
this.onRemoveButtonClick = onRemoveButtonClick
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
// 自定义消息附件
export interface CustomMessageAttachment {
// 自定义消息类型
type?: string;
}
// 合并消息
export interface MergedMessageAttachment extends CustomMessageAttachment {
// 会话id
sessionId?: string;
// 会话名称
sessionName?: string;
// 合并消息上传NOS后的url
url?: string;
// 合并消息文件的md5
md5?: string;
// 合并消息的深度
depth?: number;
// 合并消息的摘要,用于在消息列表展示,默认三条
abstracts?: MergeMessageAbstract[];
// 合并消息的id,用于标识合并消息
// 通[NIMMessage.uuid]获取
messageId?: string;
}
// 合并转发消息的缩略
export interface MergeMessageAbstract {
// 消息展示的nick,只取fromNick,没有就accId
senderNick: string;
// 内容,不是Text的显示缩略
content: string;
// 发送方的accId
userAccId: string;
}
// 消息上传后的信息
export interface MessageUploadInfo {
url: string;
md5: string;
}
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { V2NIMFriend, V2NIMUser } from '@nimsdk/base';
interface NEUserWithFriendParams {
friend?: V2NIMFriend;
user?: V2NIMUser;
}
@ObservedV2
export class NEUserWithFriend {
@Trace user?: V2NIMUser
@Trace friend?: V2NIMFriend
constructor(params: NEUserWithFriendParams) {
this.friend = params.friend
if (params.user) {
this.user = params.user
} else {
this.user = params.friend?.userProfile
}
}
/// 获取显示名称
/// (备注) > 昵称 > accid
/// - Parameter showAlias: 是否优先显示备注
public showName(showAlias: boolean = true): string {
if (showAlias && this.friend?.alias && this.friend.alias.length > 0) {
return this.friend?.alias
}
if (this.user?.name && this.user.name.length > 0) {
return this.user.name
}
return this.user?.accountId ?? ""
}
/// 获取简称 (尾部截取)
/// - Parameter showAlias: 是否优先显示备注
/// - Parameter count: 尾部截取长度
public shortName(showAlias: boolean = true, count: number = 2): string {
let name = this.showName(showAlias)
if (name) {
const start: number = (name.length - count) > 0 ? name.length - count : 0
const end: number = name.length
return name.substring(start, end)
}
return ""
}
/**
* 获取好友备注
* @returns
*/
public getAlias() {
return this.friend?.alias
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { NEUserWithFriend } from './NEUserWithFriend';
export class PersonSelectParam {
filterList?: string[] = [];
selectLimit: number = 200;
onClickSureButton?: (selectedList: NEUserWithFriend[]) => void = undefined
constructor(onClickSureButton?: (selectedList: NEUserWithFriend[]) => void, filterList: string[] = [],
selectLimit: number = 200) {
this.filterList = filterList
this.selectLimit = selectLimit
this.onClickSureButton = onClickSureButton
}
}
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
export interface RevokeMessageExtension {
revoke_message_local: boolean,
revoke_message_local_time: number,
revoke_message_local_edit: boolean,
revoke_message_local_content: string,
revoke_message_client_id: string,
}
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { V2NIMFriend, V2NIMTeamMember, V2NIMTeamMemberRole, V2NIMUser } from '@nimsdk/base'
/**
* 群成员,好友,用户信息的集合
*/
export class TeamMemberWithUser {
//群成员信息
teamMember: V2NIMTeamMember
//好友信息
friendInfo?: V2NIMFriend
//用户信息
userInfo?: V2NIMUser
constructor(teamMember: V2NIMTeamMember,
friendInfo?: V2NIMFriend, userInfo?: V2NIMUser) {
this.teamMember = teamMember
this.friendInfo = friendInfo
this.userInfo = userInfo
}
/**
* 获取头像
* @returns
*/
getAvatar(): string | undefined {
return this.userInfo?.avatar
}
/**
* 获取头像nick
* @returns
*/
getAvatarName(): string {
let avatarName = ''
if (this.friendInfo && this.friendInfo.alias &&
this.friendInfo.alias.length > 0) {
avatarName = this.friendInfo.alias
} else if (this.userInfo && this.userInfo.name && this.userInfo.name.length > 0) {
avatarName = this.userInfo.name
} else {
avatarName = this.teamMember.accountId
}
return avatarName.length > 2 ? avatarName.substring(avatarName.length - 2, avatarName.length) : avatarName
}
/**
* 获取@的名称,不包含好友备注
*/
getAitName(): string {
//群昵称
if (this.teamMember.teamNick &&
this.teamMember.teamNick.length > 0) {
return this.teamMember.teamNick
}
//用户名
if (this.userInfo && this.userInfo.name && this.userInfo.name.length > 0) {
return this.userInfo.name
}
//最后accId
return this.teamMember.accountId
}
/**
* 是否是群主
* @returns
*/
isOwner(): boolean {
return this.teamMember.memberRole === V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_OWNER
}
/**
* 是否是管理员
* @returns
*/
isManager(): boolean {
return this.teamMember.memberRole === V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER
}
/**
* 获取昵称
* @returns
*/
getNickname(): string {
//优先好友备注
if (this.friendInfo && this.friendInfo.alias &&
this.friendInfo.alias.length > 0) {
return this.friendInfo.alias
}
//其次群昵称
if (this.teamMember.teamNick &&
this.teamMember.teamNick.length > 0) {
return this.teamMember.teamNick
}
//再次用户名
if (this.userInfo && this.userInfo.name && this.userInfo.name.length > 0) {
return this.userInfo.name
}
//最后accId
return this.teamMember.accountId
}
/**
* 获取群成员Id
* @returns
*/
getAccId(): string {
return this.teamMember.accountId
}
}
/**
* 群成员请求返回结果
*/
export interface TeamMemberResult {
//是否结束
finished: boolean
//下次请求token
nextToken: string
//成员列表
memberList: TeamMemberWithUser[]
}
@@ -0,0 +1,4 @@
export interface TeamSettingParam {
teamId: string,
memberIds: string[]
}
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
/**
* @所有人的权限
*/
export interface AitAllPermission {
yxAllowAt: string
lastOpt: string
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { AitSegment } from './AitSegment'
import { Type } from 'class-transformer'
import 'reflect-metadata'
export class AitMessage {
/**
* 账号
*/
accountId: string = ""
//@的文本
text: string
//@的信息存储,一个文案可能被@多次
@Type(() => AitSegment)
segments: AitSegment[] = []
constructor(text: string, accountId: string) {
this.text = text
this.accountId = accountId
}
addSegment(start: number, end: number, broken?: boolean) {
let segment: AitSegment = {
start: start,
end: end,
broken: broken ?? false
}
this.segments.push(...[segment])
}
removeSegment(start: number, end: number) {
let index = this.segments.findIndex(e => e.start === start && e.end === end)
if (index >= 0) {
this.segments.splice(index, 1)
}
}
valid(): boolean {
if (this.segments.length < 0) {
return false
}
for (let segment of this.segments) {
if (!segment.broken) {
return true
}
}
return false
}
segmentToMap(segment: AitSegment): Map<string, number | boolean> {
let map = new Map<string, number | boolean>()
map.set('start', segment.start)
map.set('end', segment.end)
map.set('broken', segment.broken ?? false)
return map
}
}
+271
View File
@@ -0,0 +1,271 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
import { AitMessage } from './AitMessage'
import { plainToClass, Transform, Type } from 'class-transformer'
import 'reflect-metadata'
import { AitSegment } from './AitSegment'
import { JSON } from '@kit.ArkTS'
//@所有人的Account
export const accountAll: string = "ait_all"
//@key
export const aitKey: string = 'yxAitMsg'
/**
* 云信@功能集合
*/
export interface YxAitMsg {
yxAitMsg?: Map<string, AitMessage>
}
export class AitModel {
/**
* key 为AccountID
*/
@Type(() => Map)
@Transform((param) => {
const map = new Map<string, AitMessage>();
if (param.value instanceof Map) {
let params = param.value as Map<string, AitMessage>
params.forEach((
value, key
) => {
map.set(key, value)
})
}
return map;
})
aitBlocks: Map<string, AitMessage> = new Map()
reset() {
this.aitBlocks.clear()
}
/**
* 删除一个@的用户
* @param deletedText 文本
* @param endIndex 删除的文本的光标所在位置
* @param length 删除的文本的长度
* @returns
*/
deleteAitUser(deletedText: string, endIndex: number, length: number): AitMessage | null {
//如果deletedText为空,直接返回
if (deletedText.length <= 0) {
return null
}
let len: number = deletedText.length;
//如果aitBlocks 中有Value值和deletedText不匹配则返回
let removedBlack: AitMessage | null = null;
for (let aitMsg of this.aitBlocks.values()) {
for (let segment of aitMsg.segments) {
if (endIndex < segment.start) {
segment.start -= length;
segment.end -= length;
continue;
}
if (len < segment.end + 1 ||
deletedText.substring(segment.start, segment.end + 1) !== aitMsg.text) {
removedBlack = new AitMessage(aitMsg.text, "");
removedBlack.addSegment(segment.start, segment.end);
}
}
}
if (removedBlack !== null) {
this.removeSegment(removedBlack, length);
}
return removedBlack;
}
/**
* 删除文本
* @param endIndex 删除后光标的位置
* @param length 删除的长度
*/
deleteText(endIndex: number, length: number) {
let removedBlack: AitMessage | null = null;
for (let aitMsg of this.aitBlocks.values()) {
let deletedSegment: AitSegment | null = null
for (let segment of aitMsg.segments) {
if (endIndex < segment.start) {
segment.start -= length;
segment.end -= length;
continue;
} else if (endIndex < segment.end) {
deletedSegment = segment
}
}
if (deletedSegment !== null) {
aitMsg.removeSegment(deletedSegment.start, deletedSegment.end)
}
if (aitMsg.segments.length <= 0) {
removedBlack = aitMsg
}
}
if (removedBlack !== null) {
this.removeSegment(removedBlack, length)
}
}
/**
* 删除aitMsg中的segment,如果aitMsg中的segment为空,则删除aitMsg
* 同时处理其他aitMsg中的segment的位置
* @param removedBlack
* @param deletedLen 删除的文本的长度,如果为正数则其他后面的前移,如果为负数,则其他不变
*/
removeSegment(removedBlack: AitMessage, deletedLen: number): void {
// 删除aitMsg中的segment,如果aitMsg中的segment为空,则删除aitMsg
let aitMsg: AitMessage | undefined = undefined
let removeKey: string = ''
this.aitBlocks.forEach((value, key) => {
if (value.text === removedBlack.text) {
aitMsg = value
removeKey = key
}
})
if (aitMsg) {
//artTs 语法要求,重新拷贝
const aitMessage: AitMessage = aitMsg
const start: number = removedBlack.segments[0].start;
const end: number = removedBlack.segments[0].end;
// 该段文字的长度,加上删除的长度,因为其在前面已经移了deletedLen位
const length = end - start + 1 - deletedLen;
aitMessage.removeSegment(start, end);
if (aitMessage.segments.length === 0) {
this.aitBlocks.delete(removeKey);
}
if (deletedLen > 0) {
for (const aitMsg of this.aitBlocks.values()) {
for (const segment of aitMsg.segments) {
if (end <= segment.start) {
segment.start -= length;
segment.end -= length;
continue;
}
}
}
}
}
}
/**
* 用户是否被@
* @param accId
* @returns
*/
isUserBeenAit(accId: string | null): boolean {
if (accId === null) {
return false;
}
for (let key of this.aitBlocks.keys()) {
if (key === accountAll) {
return true;
}
if (key === accId) {
return true;
}
}
return false;
}
/**
* 拷贝
* @param aitModel
*/
fork(aitModel: AitModel) {
this.aitBlocks.clear()
this.aitBlocks = aitModel.aitBlocks
}
/**
* 根据插入后的Text 文案, segment 移位或者删除。
* @param changedText
* @param endIndex
* @param length
*/
insertText(endIndex: number, length: number): void {
let removedBlack: AitMessage | null = null;
const start: number = endIndex - length;
for (let aitMsg of this.aitBlocks.values()) {
for (let segment of aitMsg.segments) {
if (start <= segment.start) {
segment.start += length;
segment.end += length;
continue;
}
if (endIndex > segment.start && endIndex <= segment.end) {
removedBlack = new AitMessage(aitMsg.text, "");
removedBlack.addSegment(segment.start, segment.end);
continue;
}
}
}
if (removedBlack !== null) {
this.removeSegment(removedBlack, -1);
}
}
/**
* 添加@成员
* @param account
* @param name
* @param start
*/
addAitMember(account: string, name: string, start: number): void {
for (let aitMsg of this.aitBlocks.values()) {
for (let segment of aitMsg.segments) {
if (start <= segment.start) {
segment.start += name.length;
segment.end += name.length;
continue;
}
}
}
let aitBlock: AitMessage | undefined = this.aitBlocks.get(account);
if (aitBlock === undefined) {
aitBlock = new AitMessage(name, account);
this.aitBlocks.set(account, aitBlock);
}
const end: number = start + name.length - 1;
aitBlock.addSegment(start, end);
}
}
/**
* Extension 解析获得AitModel
* @param extension
* @returns
*/
export function getAitModelFromJson(extension?: string): AitModel | undefined {
if (extension) {
try {
const obj: Record<string, Object> = JSON.parse(extension) as Record<string, Object>
for (let key of Object.entries(obj)) {
if (key[0] === aitKey) {
let trans: AitModelTrans = {
aitBlocks: key[1]
}
let aitModel: AitModel = plainToClass(AitModel, trans, { enableImplicitConversion: true })
if (aitModel.aitBlocks.size > 0) {
return aitModel
}
}
}
}catch (e) {
console.error('parse json error', JSON.stringify(e), JSON.stringify(extension))
return undefined
}
}
return undefined
}
interface AitModelTrans {
aitBlocks: object
}
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2022 NetEase, Inc. All rights reserved.
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*
*/
/**
* 记录@消息的最小单位
*/
export class AitSegment {
//@消息的起点
start: number = 0;
//@消息的终点位置
end: number = 0;
//是否已经被破坏
broken: boolean = false
}