mirror of
https://github.com/TangSengDaoDao/TangSengDaoDaoWeb
synced 2025-06-05 00:28:47 +00:00
feat: add GlobalSearch components and styles for improved search functionality
This commit is contained in:
parent
559812ec8b
commit
16fc0b6221
@ -480,12 +480,12 @@ export class Conversation extends Component<ConversationProps> implements Conver
|
||||
}
|
||||
|
||||
render() {
|
||||
const { chatBg, channel } = this.props
|
||||
const { chatBg, channel,initLocateMessageSeq } = this.props
|
||||
|
||||
const channelInfo = WKSDK.shared().channelManager.getChannelInfo(channel)
|
||||
|
||||
return <Provider create={() => {
|
||||
this.vm = new ConversationVM(channel)
|
||||
this.vm = new ConversationVM(channel,initLocateMessageSeq)
|
||||
return this.vm
|
||||
}} render={(vm: ConversationVM) => {
|
||||
return <>
|
||||
|
@ -24,7 +24,7 @@ export default class ConversationVM extends ProviderListener {
|
||||
currentConversation?: Conversation // 当前最近会话
|
||||
messagesOfOrigin: MessageWrap[] = [] // 原始消息集合(不包含时间消息等本地消息)
|
||||
browseToMessageSeq: number = 0 // 已经预览到的最新的messageSeq
|
||||
initLocateMessageSeq: number = 0 // 初始定位的消息messageSeq 0为不定位
|
||||
initLocateMessageSeq?: number = 0 // 初始定位的消息messageSeq 0为不定位
|
||||
shouldShowHistorySplit: boolean = false // 是否应该显示历史消息分割线
|
||||
private _editOn: boolean = false // 是否开启编辑模式
|
||||
orgUnreadCount: number = 0 // 原未读数量
|
||||
@ -57,10 +57,14 @@ export default class ConversationVM extends ProviderListener {
|
||||
|
||||
onFirstMessagesLoaded?: Function // 第一屏消息已加载完成
|
||||
|
||||
constructor(channel: Channel) {
|
||||
constructor(channel: Channel, initLocateMessageSeq?: number) {
|
||||
super()
|
||||
this.channel = channel
|
||||
// this.initLocateMessageSeq = initLocateMessageSeq
|
||||
if(initLocateMessageSeq==0) {
|
||||
this.initLocateMessageSeq = undefined
|
||||
}else {
|
||||
this.initLocateMessageSeq = initLocateMessageSeq
|
||||
}
|
||||
}
|
||||
|
||||
get currentReplyMessage() {
|
||||
@ -131,7 +135,7 @@ export default class ConversationVM extends ProviderListener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 选中消息
|
||||
checkedMessage(message: Message, checked: boolean): void {
|
||||
let messageWrap = this.findMessageWithClientMsgNo(message.clientMsgNo)
|
||||
@ -255,11 +259,11 @@ export default class ConversationVM extends ProviderListener {
|
||||
didMount(): void {
|
||||
|
||||
this.conversationListener = (conversation: Conversation, action: ConversationAction) => {
|
||||
if(!conversation.channel.isEqual(this.channel)) {
|
||||
if (!conversation.channel.isEqual(this.channel)) {
|
||||
return
|
||||
}
|
||||
if(action == ConversationAction.update) {
|
||||
console.log("update-2--->",conversation.unread)
|
||||
if (action == ConversationAction.update) {
|
||||
console.log("update-2--->", conversation.unread)
|
||||
this.unreadCount = conversation.unread
|
||||
}
|
||||
}
|
||||
@ -315,8 +319,8 @@ export default class ConversationVM extends ProviderListener {
|
||||
|
||||
WKApp.endpointManager.setMethod(EndpointID.clearChannelMessages, (channel: Channel) => {
|
||||
if (channel.isEqual(this.channel)) {
|
||||
if(this.messagesOfOrigin.length > 0) {
|
||||
this.browseToMessageSeq = this.messagesOfOrigin[this.messagesOfOrigin.length-1].messageSeq
|
||||
if (this.messagesOfOrigin.length > 0) {
|
||||
this.browseToMessageSeq = this.messagesOfOrigin[this.messagesOfOrigin.length - 1].messageSeq
|
||||
}
|
||||
this.messagesOfOrigin = []
|
||||
this.messages = []
|
||||
@ -364,7 +368,7 @@ export default class ConversationVM extends ProviderListener {
|
||||
this.orgUnreadCount = unread
|
||||
this.unreadCount = unread
|
||||
this.currentConversation = conversation
|
||||
|
||||
|
||||
|
||||
this.shouldShowHistorySplit = unread > 0
|
||||
if (unread > 0) {
|
||||
@ -383,7 +387,7 @@ export default class ConversationVM extends ProviderListener {
|
||||
WKSDK.shared().conversationManager.openConversation = conversation
|
||||
}
|
||||
|
||||
this.requestMessagesOfFirstPage(undefined, () => {
|
||||
this.requestMessagesOfFirstPage(this.initLocateMessageSeq, () => {
|
||||
if (this.onFirstMessagesLoaded) {
|
||||
this.onFirstMessagesLoaded()
|
||||
}
|
||||
@ -404,8 +408,8 @@ export default class ConversationVM extends ProviderListener {
|
||||
}
|
||||
|
||||
// 加载频道信息完成
|
||||
async loadChannelInfoFinished() {
|
||||
if(this.channel.channelType !== ChannelTypeGroup) {
|
||||
async loadChannelInfoFinished() {
|
||||
if (this.channel.channelType !== ChannelTypeGroup) {
|
||||
return
|
||||
}
|
||||
this.reloadSubscribers()
|
||||
@ -416,25 +420,25 @@ export default class ConversationVM extends ProviderListener {
|
||||
this.reloadSubscribers()
|
||||
})
|
||||
|
||||
if(this.channelInfo?.orgData?.group_type == SuperGroup) {
|
||||
if (this.channelInfo?.orgData?.group_type == SuperGroup) {
|
||||
// 如果是超级群则只获取第一页成员
|
||||
this.subscribers = await this.getFirstPageMembers()
|
||||
WKSDK.shared().channelManager.subscribeCacheMap.set(this.channel.getChannelKey(), this.subscribers)
|
||||
WKSDK.shared().channelManager.notifySubscribeChangeListeners(this.channel)
|
||||
this.notifyListener()
|
||||
}else {
|
||||
this.subscribers = await this.getFirstPageMembers()
|
||||
WKSDK.shared().channelManager.subscribeCacheMap.set(this.channel.getChannelKey(), this.subscribers)
|
||||
WKSDK.shared().channelManager.notifySubscribeChangeListeners(this.channel)
|
||||
this.notifyListener()
|
||||
} else {
|
||||
WKSDK.shared().channelManager.syncSubscribes(this.channel)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 获取第一页成员列表(超大群)
|
||||
getFirstPageMembers() {
|
||||
return WKApp.dataSource.channelDataSource.subscribers(this.channel,{
|
||||
limit: 100,
|
||||
page: 1
|
||||
})
|
||||
return WKApp.dataSource.channelDataSource.subscribers(this.channel, {
|
||||
limit: 100,
|
||||
page: 1
|
||||
})
|
||||
}
|
||||
|
||||
// 标记提醒已完成
|
||||
@ -690,7 +694,7 @@ export default class ConversationVM extends ProviderListener {
|
||||
|
||||
// 刷新新消息数量
|
||||
refreshNewMsgCount() {
|
||||
|
||||
|
||||
const oldUnreadCount = this.unreadCount
|
||||
if (this.browseToMessageSeq == 0) {
|
||||
this.unreadCount = 0
|
||||
@ -1023,7 +1027,7 @@ export default class ConversationVM extends ProviderListener {
|
||||
}
|
||||
}
|
||||
newMessages.push(message)
|
||||
if (shouldShowHistorySplit && this.initLocateMessageSeq > 0 && message.messageSeq === this.initLocateMessageSeq) {
|
||||
if (shouldShowHistorySplit && this.initLocateMessageSeq && this.initLocateMessageSeq > 0 && message.messageSeq === this.initLocateMessageSeq) {
|
||||
newMessages.push(new MessageWrap(this.getHistorySplit()))
|
||||
}
|
||||
}
|
||||
|
126
packages/tsdaodaobase/src/Components/GlobalSearch/index.tsx
Normal file
126
packages/tsdaodaobase/src/Components/GlobalSearch/index.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { Component, ReactNode } from "react";
|
||||
import React from "react";
|
||||
import { Input } from '@douyinfe/semi-ui';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { Tabs } from '@douyinfe/semi-ui';
|
||||
import Provider from "../../Service/Provider";
|
||||
import GlobalSearchVM from "./vm";
|
||||
import TabAll from "./tab-all";
|
||||
import TabContacts from "./tab-contacts";
|
||||
import TabGroup from "./tab-group";
|
||||
import TabFile from "./tab-file";
|
||||
import { Channel } from "wukongimjssdk";
|
||||
|
||||
interface GlobalSearchProps {
|
||||
channel?: Channel; // 查询指定频道的聊天记录
|
||||
// item点击事件,传递item和type,type为contacts、group、message,file
|
||||
onClick?: (item: any, type: string) => void;
|
||||
}
|
||||
|
||||
export default class GlobalSearch extends Component<GlobalSearchProps> {
|
||||
vm!: GlobalSearchVM
|
||||
|
||||
|
||||
tabPanel(key: string) {
|
||||
|
||||
// message
|
||||
if (key === 'all') {
|
||||
return <TabAll
|
||||
searchResult={this.vm.searchResult}
|
||||
keyword={this.vm.keyword}
|
||||
loadMore={() => {
|
||||
this.vm.loadMore()
|
||||
}}
|
||||
onClick={(item, type) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, type)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
// contacts
|
||||
if (key === 'contacts') {
|
||||
return <TabContacts
|
||||
friends={this.vm.searchResult?.friends}
|
||||
keyword={this.vm.keyword}
|
||||
onClick={(item) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, "contacts")
|
||||
}
|
||||
}}
|
||||
></TabContacts>
|
||||
}
|
||||
|
||||
// groups
|
||||
if (key === 'groups') {
|
||||
return <TabGroup
|
||||
groups={this.vm.searchResult?.groups}
|
||||
keyword={this.vm.keyword}
|
||||
onClick={(item) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, "group")
|
||||
}
|
||||
}}
|
||||
></TabGroup>
|
||||
}
|
||||
|
||||
// files
|
||||
if (key === 'files') {
|
||||
return <TabFile
|
||||
files={this.vm.searchResult?.messages}
|
||||
keyword={this.vm.keyword}
|
||||
loadMore={() => {
|
||||
this.vm.loadMore()
|
||||
}}
|
||||
onClick={(item) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, "file")
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
const { channel } = this.props;
|
||||
return <Provider
|
||||
create={() => {
|
||||
this.vm = new GlobalSearchVM()
|
||||
this.vm.channel = channel
|
||||
return this.vm
|
||||
}}
|
||||
render={(vm: GlobalSearchVM) => {
|
||||
|
||||
return <div>
|
||||
{
|
||||
vm.searchInChannel ? <div style={{ fontSize: "14px", fontWeight: "500",width:"100%",textAlign:"center",marginBottom: "10px" }}>{vm.searchTitle}</div> : undefined
|
||||
}
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
showClear
|
||||
style={{ height: "40px" }}
|
||||
onCompositionStart={() => { vm.isComposing = true; }}
|
||||
onCompositionEnd={(e: any) => {
|
||||
vm.isComposing = false;
|
||||
vm.handleInputChange(e.target.value);
|
||||
}}
|
||||
onChange={(value) => {
|
||||
vm.handleInputChange(value);
|
||||
}}></Input>
|
||||
<div className="wk-search-tabs">
|
||||
<Tabs
|
||||
tabList={vm.tabList}
|
||||
onChange={key => {
|
||||
vm.onTabClick(key);
|
||||
}}
|
||||
>
|
||||
{this.tabPanel(vm.selectedTabKey)}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
}}>
|
||||
|
||||
</Provider>
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
|
||||
.wk-item-contacts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.wk-item-contacts:hover {
|
||||
background-color: #f0f0f0; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
body[theme-mode=dark] .wk-item-contacts:hover {
|
||||
background-color: #333333; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
.wk-item-contacts-name {
|
||||
margin-left: 15px;
|
||||
color: var(--wk-text-item);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
body[theme-mode=dark] .wk-item-contacts-name {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
mark {
|
||||
background-color: transparent; /* 移除默认背景色 */
|
||||
color: var(--wk-color-theme); /* 设置文本颜色为红色,可以根据需要更改 */
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { Component, ReactNode } from "react";
|
||||
import WKAvatar from "../WKAvatar";
|
||||
import "./item-contacts.css"
|
||||
interface ItemContactsProps {
|
||||
avatar: string;
|
||||
name: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default class ItemContacts extends Component<ItemContactsProps> {
|
||||
|
||||
render(): ReactNode {
|
||||
return <div className="wk-item-contacts" onClick={()=>{
|
||||
if(this.props.onClick){
|
||||
this.props.onClick()
|
||||
}
|
||||
}}>
|
||||
<WKAvatar src={this.props.avatar} style={{width:"40px",height:"40px"}}></WKAvatar>
|
||||
<div className="wk-item-contacts-name" dangerouslySetInnerHTML={{ __html: this.props.name }}></div>
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
|
||||
|
||||
.wk-item-file-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wk-item-file {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto auto; /* 两行 */
|
||||
gap: 0px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.wk-item-file:hover {
|
||||
background-color: #f0f0f0; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
body[theme-mode=dark] .wk-item-file:hover {
|
||||
background-color: #333333; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
|
||||
.wk-item-file-icon {
|
||||
grid-column: 1 / 2; /* 第一列 */
|
||||
grid-row: 1 / 3; /* 第一行 */
|
||||
}
|
||||
|
||||
.wk-item-file-name {
|
||||
grid-column: 2 / 3; /* 第二列 */
|
||||
grid-row: 1 / 2; /* 第一行 */
|
||||
margin-left: 15px;
|
||||
color: var(--wk-text-item);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wk-item-file-desc {
|
||||
grid-row: 2 / 3; /* 第二行 */
|
||||
grid-column: 2 / 3; /* 第二列 */
|
||||
display: flex;
|
||||
margin-left: 15px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wk-item-file-line {
|
||||
width: 1px;
|
||||
height: 8px;
|
||||
background-color: #666;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin: 4px;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { Component, ReactNode } from "react";
|
||||
import "./item-file.css"
|
||||
import FileHelper from "../../Utils/filehelper";
|
||||
import { getTimeStringAutoShort2 } from "../../Utils/time";
|
||||
interface ItemFileProps {
|
||||
message: any;
|
||||
sender?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default class ItemFile extends Component<ItemFileProps> {
|
||||
|
||||
render(): ReactNode {
|
||||
const file = this.props.message.payload;
|
||||
const channel = this.props.message.channel;
|
||||
const realName = file.name?.replaceAll("<mark>", "").replaceAll("</mark>", "");
|
||||
const fileIconInfo = FileHelper.getFileIconInfo(realName);
|
||||
return <div className="wk-item-file" onClick={() => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick()
|
||||
}
|
||||
}}>
|
||||
<div className="wk-item-file-icon" style={{ backgroundColor: fileIconInfo?.color, borderRadius: "4px" }}>
|
||||
<img alt="" src={fileIconInfo?.icon} style={{ width: '32px', height: '32px' }} />
|
||||
</div>
|
||||
<div className="wk-item-file-name" dangerouslySetInnerHTML={{ __html: file.name }}></div>
|
||||
<div className="wk-item-file-desc">
|
||||
<div className="wk-item-file-sender">{this.props.sender}</div><div className="wk-item-file-line" />
|
||||
<div className="wk-item-file-recv">{channel.channel_name}</div><div className="wk-item-file-line" />
|
||||
<div className="wk-item-file-size">{FileHelper.getFileSizeFormat(file.size || 0)}</div><div className="wk-item-file-line" />
|
||||
<div className="wk-item-file-time">{getTimeStringAutoShort2(this.props.message.timestamp * 1000, true)}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
|
||||
.wk-item-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
body[theme-mode=dark] .wk-item-group:hover {
|
||||
background-color: #333333; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
.wk-item-group:hover {
|
||||
background-color: #f0f0f0; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
.wk-item-group-name {
|
||||
margin-left: 15px;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
body[theme-mode=dark] .wk-item-group-name {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { Component, ReactNode } from "react";
|
||||
import WKAvatar from "../WKAvatar";
|
||||
import "./item-group.css"
|
||||
interface ItemGroupProps {
|
||||
avatar: string;
|
||||
name: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default class ItemGroup extends Component<ItemGroupProps> {
|
||||
|
||||
render(): ReactNode {
|
||||
return <div className="wk-item-group" onClick={()=>{
|
||||
if(this.props.onClick){
|
||||
this.props.onClick()
|
||||
}
|
||||
}}>
|
||||
<WKAvatar src={this.props.avatar} style={{width:"40px",height:"40px"}}></WKAvatar>
|
||||
<div className="wk-item-group-name" dangerouslySetInnerHTML={{ __html: this.props.name }}></div>
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
|
||||
.wk-item-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.wk-item-message:hover {
|
||||
background-color: #f0f0f0; /* Change this color to your desired hover color */
|
||||
}
|
||||
body[theme-mode=dark] .wk-item-message:hover {
|
||||
background-color: #333333; /* Change this color to your desired hover color */
|
||||
}
|
||||
|
||||
.wk-item-message-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr; /* 两列,左边自适应,右边自动宽度 */
|
||||
grid-template-rows: auto auto; /* 两行 */
|
||||
gap: 0px;
|
||||
min-width: 200px;
|
||||
max-width: 420px;
|
||||
|
||||
}
|
||||
|
||||
.wk-item-message-name {
|
||||
margin-left: 15px;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
grid-column: 1 / 2; /* 第一列 */
|
||||
grid-row: 1 / 2; /* 第一行 */
|
||||
}
|
||||
|
||||
body[theme-mode=dark] .wk-item-message-name {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.wk-item-message-digest {
|
||||
margin-left: 15px;
|
||||
color: #999;
|
||||
white-space: nowrap; /* 不换行 */
|
||||
overflow: hidden; /* 超出部分隐藏 */
|
||||
text-overflow: ellipsis; /* 超出部分以省略号显示 */
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: transparent; /* 移除默认背景色 */
|
||||
color: var(--wk-color-theme); /* 设置文本颜色为红色,可以根据需要更改 */
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { Component, ReactNode } from "react";
|
||||
import WKAvatar from "../WKAvatar";
|
||||
import "./item-message.css"
|
||||
interface ItemMessageProps {
|
||||
avatar: string; // 会话头像
|
||||
name: string; // 会话名字
|
||||
digest: string; // 消息摘要
|
||||
sender?: string; // 发送者
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export default class ItemMessage extends Component<ItemMessageProps> {
|
||||
|
||||
render(): ReactNode {
|
||||
|
||||
let digest = this.props?.digest
|
||||
if(this.props.sender && this.props.sender !== ""){
|
||||
digest = this.props.sender + ": " + digest
|
||||
}
|
||||
|
||||
return <div className="wk-item-message" onClick={() => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick()
|
||||
}
|
||||
} }>
|
||||
<WKAvatar src={this.props.avatar} style={{ width: "40px", height: "40px" }}></WKAvatar>
|
||||
<div className="wk-item-message-content">
|
||||
<div className="wk-item-message-name">{this.props.name}</div>
|
||||
{/* <div className="wk-item-message-time">{this.props.time}</div> */}
|
||||
<div className="wk-item-message-digest" dangerouslySetInnerHTML={{ __html: digest}}></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { Component, ReactNode } from "react";
|
||||
|
||||
|
||||
interface SectionProps {
|
||||
title: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export default class Section extends Component<SectionProps> {
|
||||
render(): ReactNode {
|
||||
return <div>
|
||||
<div style={{color:"#666",marginLeft:"10px",marginTop:"10px"}}>{this.props.title}</div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
.wk-tab-all {
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
}
|
131
packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.tsx
Normal file
131
packages/tsdaodaobase/src/Components/GlobalSearch/tab-all.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import React, { Component } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import Section from "./section";
|
||||
import ItemContacts from "./item-contacts";
|
||||
import ItemGroup from "./item-group";
|
||||
import ItemMessage from "./item-message";
|
||||
import WKApp from "../../App";
|
||||
import "./tab-all.css"
|
||||
import WKSDK, { Channel, ChannelTypePerson, MessageContentType } from "wukongimjssdk";
|
||||
import { MessageContentTypeConst } from "../../Service/Const";
|
||||
|
||||
|
||||
interface TabAllProps {
|
||||
keyword?: string;
|
||||
searchResult?: any;
|
||||
loadMore?: () => void; // 添加加载更多的回调函数
|
||||
// item点击事件,传递item和type,type为contacts、group、message
|
||||
onClick?: (item: any, type: string) => void;
|
||||
}
|
||||
|
||||
export default class TabAll extends Component<TabAllProps> {
|
||||
|
||||
handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
||||
if (scrollTop + clientHeight >= scrollHeight) {
|
||||
if (this.props.loadMore) {
|
||||
this.props.loadMore();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render(): ReactNode {
|
||||
|
||||
let existFriends = this.props.searchResult?.friends.length > 0
|
||||
let existGroups = this.props.searchResult?.groups.length > 0
|
||||
let existMessages = this.props.searchResult?.messages.length > 0
|
||||
|
||||
return <div className="wk-tab-all" onScroll={this.handleScroll}>
|
||||
|
||||
{
|
||||
existFriends ? (<Section title="联系人">
|
||||
{
|
||||
this.props.searchResult.friends.map((item: any) => {
|
||||
return <ItemContacts
|
||||
key={item.channel_id}
|
||||
name={item.channel_name}
|
||||
avatar={WKApp.shared.avatarUser(item.channel_id)}
|
||||
onClick={() => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, "contacts")
|
||||
}
|
||||
}}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</Section>) : null
|
||||
}
|
||||
{
|
||||
existGroups ? (
|
||||
<Section title="群组">
|
||||
{
|
||||
this.props.searchResult?.groups.map((item: any) => {
|
||||
if (this.props.keyword && item.channel_name.indexOf(this.props.keyword) !== -1) {
|
||||
item.channel_name = item.channel_name.replace(this.props.keyword, `<mark>${this.props.keyword}</mark>`)
|
||||
}
|
||||
return <ItemGroup
|
||||
key={item.channel_id}
|
||||
name={item.channel_name}
|
||||
avatar={WKApp.shared.avatarGroup(item.channel_id)}
|
||||
onClick={() => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, "group")
|
||||
}
|
||||
}}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</Section>
|
||||
) : null
|
||||
}
|
||||
|
||||
{
|
||||
existMessages ? (
|
||||
<Section title="消息">
|
||||
{
|
||||
this.props.searchResult?.messages.map((item: any) => {
|
||||
let digest = "[未知消息]"
|
||||
if(item.content) {
|
||||
digest = item.content.conversationDigest
|
||||
}else {
|
||||
if (item.payload.type === MessageContentType.text) {
|
||||
digest = item.payload.content
|
||||
} else if (item.payload.type === MessageContentTypeConst.file) {
|
||||
digest = `[${item.payload.name}]`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let sender;
|
||||
if (item.channel.channel_type !== ChannelTypePerson && item.from_uid && item.from_uid !== "") {
|
||||
const senderChannel = new Channel(item.from_uid, ChannelTypePerson)
|
||||
const channelInfo = WKSDK.shared().channelManager.getChannelInfo(senderChannel)
|
||||
if (channelInfo) {
|
||||
sender = channelInfo.title
|
||||
} else {
|
||||
WKSDK.shared().channelManager.fetchChannelInfo(senderChannel)
|
||||
}
|
||||
}
|
||||
|
||||
return <ItemMessage
|
||||
key={item.message_idstr}
|
||||
sender={sender}
|
||||
digest={digest}
|
||||
name={item.channel.channel_name}
|
||||
avatar={WKApp.shared.avatarChannel(new Channel(item.channel.channel_id, item.channel.channel_type))}
|
||||
onClick={() => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(item, "message")
|
||||
}
|
||||
}}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</Section>
|
||||
) : null
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
|
||||
.wk-tab-contacts {
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import React, { Component } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import ItemContacts from "./item-contacts";
|
||||
import WKApp from "../../App";
|
||||
import "./tab-contacts.css"
|
||||
|
||||
interface TabContactsProps {
|
||||
keyword?: string;
|
||||
friends?: any[];
|
||||
onClick?: (item: any) => void;
|
||||
}
|
||||
|
||||
export default class TabContacts extends Component<TabContactsProps> {
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
return <div className="wk-tab-contacts">
|
||||
{
|
||||
this.props.friends?.map((item: any) => {
|
||||
if (this.props.keyword && item.channel_name.indexOf(this.props.keyword) !== -1) {
|
||||
item.channel_name = item.channel_name.replace(this.props.keyword, `<mark>${this.props.keyword}</mark>`)
|
||||
}
|
||||
return <ItemContacts
|
||||
key={item.channel_id}
|
||||
name={item.channel_name}
|
||||
avatar={WKApp.shared.avatarUser(item.channel_id)}
|
||||
onClick={()=>{
|
||||
if(this.props.onClick) {
|
||||
this.props.onClick(item)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
.wk-tab-file {
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import React, { Component } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import ItemFile from "./item-file";
|
||||
import WKApp from "../../App";
|
||||
import "./tab-file.css"
|
||||
import WKSDK, { Channel, ChannelTypePerson } from "wukongimjssdk";
|
||||
|
||||
interface TabFileProps {
|
||||
keyword?: string;
|
||||
files?: any[];
|
||||
loadMore?: () => void; // 添加加载更多的回调函数
|
||||
onClick?: (item: any) => void;
|
||||
}
|
||||
|
||||
export default class TabFile extends Component<TabFileProps> {
|
||||
|
||||
handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
||||
if (scrollTop + clientHeight >= scrollHeight) {
|
||||
if (this.props.loadMore) {
|
||||
this.props.loadMore();
|
||||
}
|
||||
}
|
||||
};
|
||||
render(): ReactNode {
|
||||
return <div className="wk-tab-file" onScroll={this.handleScroll}>
|
||||
{
|
||||
this.props.files?.map((item: any) => {
|
||||
let sender;
|
||||
const senderChannel = new Channel(item.from_uid, ChannelTypePerson)
|
||||
const channelInfo = WKSDK.shared().channelManager.getChannelInfo(senderChannel)
|
||||
if (channelInfo) {
|
||||
sender = channelInfo.title
|
||||
} else {
|
||||
WKSDK.shared().channelManager.fetchChannelInfo(senderChannel)
|
||||
}
|
||||
|
||||
return <ItemFile
|
||||
key={item.message_idstr}
|
||||
sender={sender}
|
||||
message={item}
|
||||
onClick={()=>{
|
||||
if(this.props.onClick) {
|
||||
this.props.onClick(item)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
.wk-tab-group {
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import React, { Component } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import ItemGroup from "./item-group";
|
||||
import WKApp from "../../App";
|
||||
import "./tab-group.css"
|
||||
|
||||
interface TabGroupProps {
|
||||
keyword?: string;
|
||||
groups?: any[];
|
||||
onClick?: (item: any) => void;
|
||||
}
|
||||
|
||||
export default class TabGroup extends Component<TabGroupProps> {
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
return <div className="wk-tab-group">
|
||||
{
|
||||
this.props.groups?.map((item: any) => {
|
||||
if (this.props.keyword && item.channel_name.indexOf(this.props.keyword) !== -1) {
|
||||
item.channel_name = item.channel_name.replace(this.props.keyword, `<mark>${this.props.keyword}</mark>`)
|
||||
}
|
||||
return <ItemGroup
|
||||
key={item.channel_id}
|
||||
name={item.channel_name}
|
||||
avatar={WKApp.shared.avatarGroup(item.channel_id)}
|
||||
onClick={()=>{
|
||||
if(this.props.onClick) {
|
||||
this.props.onClick(item)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
219
packages/tsdaodaobase/src/Components/GlobalSearch/vm.ts
Normal file
219
packages/tsdaodaobase/src/Components/GlobalSearch/vm.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import WKSDK, { Channel, ChannelInfo, ChannelInfoListener, ChannelTypePerson, MessageContentManager, SystemContent } from "wukongimjssdk";
|
||||
import APIClient from "../../Service/APIClient";
|
||||
import { MessageContentTypeConst } from "../../Service/Const";
|
||||
import { ProviderListener } from "../../Service/Provider";
|
||||
|
||||
export default class GlobalSearchVM extends ProviderListener {
|
||||
// 选中的tab组件
|
||||
private _selectedTabKey = "all";
|
||||
|
||||
public page = 1 // 当前页码
|
||||
public limit = 20 // 每页条数
|
||||
public keyword = "" // 搜索关键字
|
||||
public searchResult: any
|
||||
public isComposing: boolean = false; // 是否正在输入(防止中文输入法干扰)
|
||||
public loadMoreing = false; // 是否正在加载更多中
|
||||
public loadFinish = false; // 是否加载完成
|
||||
public contentTypes = new Array<number>() // 内容类型
|
||||
private channelInfoListener!: ChannelInfoListener;
|
||||
public channel?: Channel // 查询指定频道的消息
|
||||
// tab数据列表
|
||||
public get tabList() {
|
||||
if (this.searchInChannel) {
|
||||
return [
|
||||
{ tab: '聊天', itemKey: 'all' },
|
||||
{ tab: '文件', itemKey: 'files' },
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ tab: '聊天', itemKey: 'all' },
|
||||
{ tab: '联系人', itemKey: 'contacts' },
|
||||
{ tab: '群组', itemKey: 'groups' },
|
||||
{ tab: '文件', itemKey: 'files' },
|
||||
];
|
||||
}
|
||||
|
||||
public get selectedTabKey() {
|
||||
return this._selectedTabKey;
|
||||
}
|
||||
|
||||
public set selectedTabKey(value: string) {
|
||||
this._selectedTabKey = value;
|
||||
this.notifyListener()
|
||||
}
|
||||
|
||||
// 是否在频道内搜索
|
||||
public get searchInChannel(): boolean {
|
||||
return this.channel !== undefined
|
||||
}
|
||||
// 搜索标题
|
||||
public get searchTitle() {
|
||||
if (this.searchInChannel) {
|
||||
const channelInfo = WKSDK.shared().channelManager.getChannelInfo(this.channel!)
|
||||
if(channelInfo) {
|
||||
return `与“${channelInfo.title}”的聊天记录`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// tab选中事件
|
||||
public onTabClick(key: string) {
|
||||
if (key === "files") {
|
||||
this.contentTypes = [MessageContentTypeConst.file]
|
||||
this.initLoad()
|
||||
this.requestSearch()
|
||||
} else {
|
||||
this.contentTypes = []
|
||||
this.initLoad()
|
||||
this.requestSearch()
|
||||
}
|
||||
this.selectedTabKey = key;
|
||||
}
|
||||
|
||||
didMount(): void {
|
||||
this.requestSearch()
|
||||
|
||||
this.channelInfoListener = (channelInfo: ChannelInfo) => {
|
||||
if (channelInfo.channel.channelType !== ChannelTypePerson) {
|
||||
return
|
||||
}
|
||||
if (this.searchResult?.messages && this.searchResult.messages.length > 0) {
|
||||
this.searchResult.messages.forEach((item: any) => {
|
||||
if (item.from_uid === channelInfo.channel.channelID) {
|
||||
this.notifyListener()
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
WKSDK.shared().channelManager.addListener(this.channelInfoListener)
|
||||
}
|
||||
|
||||
didUnMount(): void {
|
||||
WKSDK.shared().channelManager.removeListener(this.channelInfoListener)
|
||||
}
|
||||
|
||||
// 输入框输入事件
|
||||
public handleInputChange = (value: string) => {
|
||||
if (!this.isComposing) {
|
||||
this.keyword = value;
|
||||
console.log(this.keyword);
|
||||
this.initLoad()
|
||||
this.requestSearch();
|
||||
}
|
||||
};
|
||||
|
||||
public initLoad() {
|
||||
this.page = 1
|
||||
this.loadFinish = false
|
||||
this.loadMoreing = false
|
||||
this.searchResult = null
|
||||
this.notifyListener()
|
||||
}
|
||||
|
||||
// 请求搜索
|
||||
public requestSearch() {
|
||||
|
||||
const param: any = {
|
||||
keyword: this.keyword || "",
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
content_type: this.contentTypes
|
||||
}
|
||||
|
||||
|
||||
if (this.channel) {
|
||||
param.channel_id = this.channel.channelID
|
||||
param.channel_type = this.channel.channelType
|
||||
param.only_message = 1
|
||||
}
|
||||
|
||||
console.log("requestSearch", param);
|
||||
|
||||
|
||||
APIClient.shared.post("/search/global", param).then(res => {
|
||||
|
||||
if (res.messages.length < this.limit) {
|
||||
this.loadFinish = true
|
||||
}
|
||||
if (this.loadMoreing) {
|
||||
if (this.searchResult) {
|
||||
this.searchResult.messages = this.searchResult.messages?.concat(res.messages)
|
||||
} else {
|
||||
this.searchResult = res
|
||||
}
|
||||
|
||||
} else {
|
||||
this.searchResult = res
|
||||
}
|
||||
|
||||
// 替换备注如果有备注的话
|
||||
this.searchResult.friends?.forEach((v: any) => {
|
||||
if (v.channel_remark && v.channel_remark !== "") {
|
||||
v.channel_name = v.channel_remark
|
||||
}
|
||||
})
|
||||
this.searchResult.groups?.forEach((v: any) => {
|
||||
if (v.channel_remark && v.channel_remark !== "") {
|
||||
v.channel_name = v.channel_remark
|
||||
}
|
||||
})
|
||||
this.searchResult.messages?.forEach((v: any) => {
|
||||
if (v.channel.channel_remark && v.channel.channel_remark !== "") {
|
||||
v.channel.channel_name = v.channel.channel_remark
|
||||
}
|
||||
|
||||
// 解析消息内容
|
||||
if(v.payload) {
|
||||
const contentType = v.payload.type
|
||||
|
||||
const messageContent = MessageContentManager.shared().getMessageContent(contentType)
|
||||
if (messageContent) {
|
||||
messageContent.decode(this.jsonToUint8Array(v.payload))
|
||||
}
|
||||
|
||||
if(messageContent instanceof SystemContent) {
|
||||
messageContent.content["content"] = "[系统消息]"
|
||||
}
|
||||
|
||||
v.content = messageContent
|
||||
}
|
||||
|
||||
})
|
||||
}).finally(() => {
|
||||
this.loadMoreing = false
|
||||
this.notifyListener()
|
||||
})
|
||||
}
|
||||
|
||||
jsonToUint8Array(json: any): Uint8Array {
|
||||
// 将 JSON 对象转换为字符串
|
||||
const jsonString = JSON.stringify(json);
|
||||
|
||||
return this.stringToUint8Array(jsonString)
|
||||
}
|
||||
|
||||
stringToUint8Array(str: string): Uint8Array {
|
||||
const newStr = unescape(encodeURIComponent(str))
|
||||
const arr = new Array<number>();
|
||||
for (let i = 0, j = newStr.length; i < j; ++i) {
|
||||
arr.push(newStr.charCodeAt(i));
|
||||
}
|
||||
const tmpUint8Array = new Uint8Array(arr);
|
||||
return tmpUint8Array
|
||||
}
|
||||
|
||||
// 加载更多消息
|
||||
loadMore() {
|
||||
if (this.loadMoreing) {
|
||||
return
|
||||
}
|
||||
this.loadMoreing = true
|
||||
this.page++
|
||||
this.requestSearch()
|
||||
console.log("加载更多");
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ export class GlobalModalOptions {
|
||||
footer?: ReactNode;
|
||||
className?: string;
|
||||
closable?: boolean;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
export interface WKBaseProps {
|
||||
@ -248,6 +249,7 @@ export default class WKBase
|
||||
visible={this.state.showGlobalModal}
|
||||
width={this.state.globalModalOptions?.width}
|
||||
footer={this.state.globalModalOptions?.footer}
|
||||
onCancel={this.state.globalModalOptions?.onCancel}
|
||||
>
|
||||
{this.state.globalModalOptions?.body}
|
||||
</Modal>
|
||||
|
@ -11,6 +11,11 @@ export class MessageContextMenus {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export class ShowConversationOptions {
|
||||
// 聊天消息定位的messageSeq
|
||||
initLocateMessageSeq?: number;
|
||||
}
|
||||
|
||||
export class EndpointCommon {
|
||||
private _onLogins: VoidFunction[] = []; // 登录成功
|
||||
|
||||
@ -32,10 +37,11 @@ export class EndpointCommon {
|
||||
}
|
||||
}
|
||||
|
||||
showConversation(channel: Channel) {
|
||||
showConversation(channel: Channel, opts?: ShowConversationOptions) {
|
||||
WKApp.shared.openChannel = channel;
|
||||
EndpointManager.shared.invoke(EndpointID.showConversation, {
|
||||
channel: channel,
|
||||
opts: opts,
|
||||
});
|
||||
WKApp.shared.notifyListener();
|
||||
}
|
||||
@ -65,21 +71,38 @@ export class EndpointCommon {
|
||||
EndpointID.showConversation,
|
||||
(param: any) => {
|
||||
const channel = param.channel as Channel;
|
||||
const conversation =
|
||||
WKSDK.shared().conversationManager.findConversation(channel);
|
||||
let initLocateMessageSeq = 0;
|
||||
if (
|
||||
conversation &&
|
||||
conversation.lastMessage &&
|
||||
conversation.unread > 0 &&
|
||||
conversation.lastMessage.messageSeq > conversation.unread
|
||||
) {
|
||||
initLocateMessageSeq =
|
||||
conversation.lastMessage.messageSeq - conversation.unread;
|
||||
let opts: ShowConversationOptions = {}
|
||||
if (param.opts) {
|
||||
opts = param.opts
|
||||
}
|
||||
|
||||
let initLocateMessageSeq = 0;
|
||||
if (opts && opts.initLocateMessageSeq && opts.initLocateMessageSeq > 0) {
|
||||
initLocateMessageSeq = opts.initLocateMessageSeq;
|
||||
}
|
||||
|
||||
if (initLocateMessageSeq <= 0) {
|
||||
const conversation =
|
||||
WKSDK.shared().conversationManager.findConversation(channel);
|
||||
if (
|
||||
conversation &&
|
||||
conversation.lastMessage &&
|
||||
conversation.unread > 0 &&
|
||||
conversation.lastMessage.messageSeq > conversation.unread
|
||||
) {
|
||||
initLocateMessageSeq =
|
||||
conversation.lastMessage.messageSeq - conversation.unread;
|
||||
}
|
||||
}
|
||||
|
||||
let key = channel.getChannelKey()
|
||||
if (initLocateMessageSeq > 0) {
|
||||
key = `${key}-${initLocateMessageSeq}`
|
||||
}
|
||||
|
||||
WKApp.routeRight.replaceToRoot(
|
||||
<ChatContentPage
|
||||
key={channel.getChannelKey()}
|
||||
key={key}
|
||||
channel={channel}
|
||||
initLocateMessageSeq={initLocateMessageSeq}
|
||||
></ChatContentPage>
|
||||
|
@ -3,9 +3,9 @@ import { Conversation } from "../../Components/Conversation";
|
||||
import ConversationList from "../../Components/ConversationList";
|
||||
import Provider from "../../Service/Provider";
|
||||
|
||||
import { Spin, Button, Popover } from "@douyinfe/semi-ui";
|
||||
import { IconPlus } from "@douyinfe/semi-icons";
|
||||
import { ChatVM } from "./vm";
|
||||
import { Spin, Modal, Popover } from "@douyinfe/semi-ui";
|
||||
import { IconPlus, IconSearch } from "@douyinfe/semi-icons";
|
||||
import { ChatVM, handleGlobalSearchClick } from "./vm";
|
||||
import "./index.css";
|
||||
import { ConversationWrap } from "../../Service/Model";
|
||||
import WKApp, { ThemeMode } from "../../App";
|
||||
@ -15,11 +15,12 @@ import { Channel, ChannelInfo, WKSDK } from "wukongimjssdk";
|
||||
import { ChannelInfoListener } from "wukongimjssdk";
|
||||
import { ChatMenus } from "../../App";
|
||||
import ConversationContext from "../../Components/Conversation/context";
|
||||
import { EndpointID } from "../../Service/Const";
|
||||
import GlobalSearch from "../../Components/GlobalSearch";
|
||||
import { ShowConversationOptions } from "../../EndpointCommon";
|
||||
|
||||
export interface ChatContentPageProps {
|
||||
channel: Channel;
|
||||
initLocateMessageSeq?: number;
|
||||
initLocateMessageSeq?: number; // 打开时定位到某条消息
|
||||
}
|
||||
|
||||
export interface ChatContentPageState {
|
||||
@ -180,7 +181,7 @@ export default class ChatPage extends Component<any> {
|
||||
|
||||
componentWillUnmount() { }
|
||||
|
||||
|
||||
|
||||
|
||||
render(): ReactNode {
|
||||
return (
|
||||
@ -201,6 +202,14 @@ export default class ChatPage extends Component<any> {
|
||||
<div className="wk-chat-content-left">
|
||||
<div className="wk-chat-search">
|
||||
<div className="wk-chat-title">{vm.connectTitle}</div>
|
||||
<div
|
||||
style={{ marginRight: '20px', alignItems: 'center', display: 'flex', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
vm.showGlobalSearch = true;
|
||||
}}
|
||||
>
|
||||
<IconSearch size="large" />
|
||||
</div>
|
||||
<Popover
|
||||
onClickOutSide={() => {
|
||||
vm.showAddPopover = false;
|
||||
@ -220,6 +229,7 @@ export default class ChatPage extends Component<any> {
|
||||
>
|
||||
<div
|
||||
className="wk-chat-search-add"
|
||||
style={{ alignItems: 'center', display: 'flex' }}
|
||||
onClick={() => {
|
||||
vm.showAddPopover = !vm.showAddPopover;
|
||||
}}
|
||||
@ -253,6 +263,23 @@ export default class ChatPage extends Component<any> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
visible={vm.showGlobalSearch}
|
||||
closeOnEsc={true}
|
||||
onCancel={() => {
|
||||
vm.showGlobalSearch = false
|
||||
}}
|
||||
footer={null}
|
||||
width="80%"
|
||||
>
|
||||
<div style={{ marginTop: '30px' }}>
|
||||
<GlobalSearch onClick={(item,type:string)=>{
|
||||
handleGlobalSearchClick(item,type,()=>{
|
||||
vm.showGlobalSearch = false
|
||||
})
|
||||
}}/>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
@ -9,6 +9,7 @@ import { ProviderListener } from "../../Service/Provider";
|
||||
import { animateScroll, scroller } from 'react-scroll';
|
||||
import { ProhibitwordsService } from "../../Service/ProhibitwordsService";
|
||||
import { EndpointID } from "../../Service/Const";
|
||||
import { ShowConversationOptions } from "../../EndpointCommon";
|
||||
|
||||
export class ChatVM extends ProviderListener {
|
||||
conversations: ConversationWrap[] = new Array()
|
||||
@ -22,6 +23,7 @@ export class ChatVM extends ProviderListener {
|
||||
private channelListener!: ChannelInfoListener
|
||||
private messageDeleteListener!: MessageDeleteListener
|
||||
private conversationListID = "wk-conversationlist"
|
||||
private _showGlobalSearch = false // 是否显示全局搜索
|
||||
|
||||
|
||||
set showAddPopover(v: boolean) {
|
||||
@ -33,6 +35,15 @@ export class ChatVM extends ProviderListener {
|
||||
return this._showAddPopover
|
||||
}
|
||||
|
||||
set showGlobalSearch(v: boolean) {
|
||||
this._showGlobalSearch = v
|
||||
this.notifyListener()
|
||||
}
|
||||
|
||||
get showGlobalSearch() {
|
||||
return this._showGlobalSearch
|
||||
}
|
||||
|
||||
set selectedConversation(v: ConversationWrap | undefined) {
|
||||
this._selectedConversation = v
|
||||
this.notifyListener()
|
||||
@ -64,7 +75,7 @@ export class ChatVM extends ProviderListener {
|
||||
// 根据连接状态设置标题
|
||||
this.setConnectTitleWithConnectStatus(WKSDK.shared().connectManager.status)
|
||||
|
||||
if(WKSDK.shared().connectManager.status == ConnectStatus.Connected){ // 如果已经连接则直接加载
|
||||
if (WKSDK.shared().connectManager.status == ConnectStatus.Connected) { // 如果已经连接则直接加载
|
||||
this.reloadRequestConversationList()
|
||||
}
|
||||
|
||||
@ -87,7 +98,7 @@ export class ChatVM extends ProviderListener {
|
||||
}
|
||||
if (action === ConversationAction.add) {
|
||||
console.log("ConversationAction-----add")
|
||||
if(conversation.lastMessage?.content && conversation.lastMessage?.contentType === MessageContentType.text) {
|
||||
if (conversation.lastMessage?.content && conversation.lastMessage?.contentType === MessageContentType.text) {
|
||||
conversation.lastMessage.content.text = ProhibitwordsService.shared.filter(conversation.lastMessage?.content.text)
|
||||
}
|
||||
this.conversations = [new ConversationWrap(conversation), ...this.conversations]
|
||||
@ -97,7 +108,7 @@ export class ChatVM extends ProviderListener {
|
||||
const existConversation = this.findConversation(conversation.channel)
|
||||
if (existConversation) {
|
||||
existConversation.conversation = conversation
|
||||
if(existConversation.lastMessage?.content && existConversation.lastMessage?.contentType === MessageContentType.text) {
|
||||
if (existConversation.lastMessage?.content && existConversation.lastMessage?.contentType === MessageContentType.text) {
|
||||
existConversation.lastMessage.content.text = ProhibitwordsService.shared.filter(existConversation.lastMessage?.content.text)
|
||||
}
|
||||
}
|
||||
@ -183,11 +194,11 @@ export class ChatVM extends ProviderListener {
|
||||
}
|
||||
}
|
||||
|
||||
async clearMessages(channel: Channel) {
|
||||
|
||||
async clearMessages(channel: Channel) {
|
||||
|
||||
const conversationWrap = this.findConversation(channel)
|
||||
if (!conversationWrap) {
|
||||
return
|
||||
return
|
||||
}
|
||||
await WKApp.conversationProvider.clearConversationMessages(conversationWrap.conversation)
|
||||
conversationWrap.conversation.lastMessage = undefined
|
||||
@ -195,7 +206,7 @@ export class ChatVM extends ProviderListener {
|
||||
WKApp.endpointManager.invoke(EndpointID.clearChannelMessages, channel)
|
||||
this.sortConversations()
|
||||
this.notifyListener()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setConnectTitleWithConnectStatus(connectStatus: ConnectStatus) {
|
||||
@ -255,7 +266,7 @@ export class ChatVM extends ProviderListener {
|
||||
const conversations = await WKSDK.shared().conversationManager.sync({})
|
||||
if (conversations && conversations.length > 0) {
|
||||
for (const conversation of conversations) {
|
||||
if(conversation.lastMessage?.content && conversation.lastMessage?.contentType == MessageContentType.text) {
|
||||
if (conversation.lastMessage?.content && conversation.lastMessage?.contentType == MessageContentType.text) {
|
||||
conversation.lastMessage.content.text = ProhibitwordsService.shared.filter(conversation.lastMessage.content.text)
|
||||
}
|
||||
conversationWraps.push(new ConversationWrap(conversation))
|
||||
@ -268,4 +279,32 @@ export class ChatVM extends ProviderListener {
|
||||
|
||||
WKApp.menus.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理搜索内容点击事件
|
||||
export function handleGlobalSearchClick(item: any, type: string,hideModal?:()=>void) {
|
||||
if (type === "contacts" || type === "group") {
|
||||
if(hideModal){
|
||||
hideModal()
|
||||
}
|
||||
WKApp.endpoints.showConversation(new Channel(item.channel_id, item.channel_type))
|
||||
} else if (type === "message") {
|
||||
const opts = new ShowConversationOptions()
|
||||
opts.initLocateMessageSeq = item.message_seq
|
||||
if(hideModal){
|
||||
hideModal()
|
||||
}
|
||||
WKApp.endpoints.showConversation(new Channel(item.channel.channel_id, item.channel.channel_type), opts)
|
||||
} else if (type === "file") {
|
||||
// 下载文件
|
||||
const payload = item.payload
|
||||
let downloadURL = WKApp.dataSource.commonDataSource.getImageURL(payload.url || '')
|
||||
if (downloadURL.indexOf("?") != -1) {
|
||||
downloadURL += "&filename=" + payload.name
|
||||
} else {
|
||||
downloadURL += "?filename=" + payload.name
|
||||
}
|
||||
window.open(`${downloadURL}`, 'top');
|
||||
}
|
||||
}
|
@ -80,6 +80,8 @@ import { ScreenshotCell, ScreenshotContent } from "./Messages/Screenshot";
|
||||
import ImageToolbar from "./Components/ImageToolbar";
|
||||
import { ProhibitwordsService } from "./Service/ProhibitwordsService";
|
||||
import { SubscriberList } from "./Components/Subscribers/list";
|
||||
import GlobalSearch from "./Components/GlobalSearch";
|
||||
import { handleGlobalSearchClick } from "./Pages/Chat/vm";
|
||||
|
||||
export default class BaseModule implements IModule {
|
||||
messageTone?: Howl;
|
||||
@ -1211,6 +1213,40 @@ export default class BaseModule implements IModule {
|
||||
1000
|
||||
);
|
||||
|
||||
WKApp.shared.channelSettingRegister(
|
||||
"channel.base.settingMessageHistory",
|
||||
(context) => {
|
||||
const data = context.routeData() as ChannelSettingRouteData;
|
||||
const channel = data.channel
|
||||
|
||||
return new Section({
|
||||
rows: [
|
||||
new Row({
|
||||
cell: ListItem,
|
||||
properties: {
|
||||
title: "查找聊天内容",
|
||||
onClick: () => {
|
||||
WKApp.shared.baseContext.showGlobalModal({
|
||||
body: <GlobalSearch channel={channel} onClick={(item: any, type: string) => {
|
||||
handleGlobalSearchClick(item, type,()=>{
|
||||
WKApp.shared.baseContext.hideGlobalModal()
|
||||
})
|
||||
}} />,
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
onCancel: () => {
|
||||
WKApp.shared.baseContext.hideGlobalModal()
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
1100
|
||||
);
|
||||
|
||||
WKApp.shared.channelSettingRegister(
|
||||
"channel.base.setting2",
|
||||
(context) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user