Skip to content

5.4 消息中心

5.4.1 parseMsg 组件

文档路径:

  • src\components\DataPreview\session\chat\components\parseMsg\index.tsx
  • src\components\DataPreview\session\chat\components\parseMsg\index.module.less
  • src\components\DataPreview\session\chat\components\parseMsg\textParagraph.tsx

5.4.1.1 功能概述

该文件主要负责解析和渲染不同类型的消息内容,包括图片、视频、文件、语音、名片、动态、任务等消息类型。 它提供了以下三个核心函数:

  • parseMsg: 解析并渲染普通消息
  • parseCiteMsg: 解析并渲染引用消息
  • parseForwardMsg: 解析并渲染转发消息

5.4.1.2 parseMsg 结构

parseMsg 主要用于解析并渲染普通消息,根据消息类型动态生成对应的 JSX 元素。 其输入参数是 item: IMessage, 消息对象,包含消息的类型、内容、发送者等信息。 parseMsg 主要支持的消息类型有:

  • MessageType.Image 图片消息
  • MessageType.Video 视频消息
  • MessageType.File 文件消息
  • MessageType.Voice 语音消息
  • MessageType.NameCard 名片消息
  • MessageType.Dynamic 动态消息
  • MessageType.Task 任务消息
  • 默认类型:文本消息或其他类型

其代码逻辑主要如下:

typescript
export const parseMsg = (item: IMessage): any => {
  switch (item.msgType) {
    case MessageType.Image: {
      const img: FileItemShare = parseAvatar(item.msgBody);
      if (img && img.shareLink) {
        return (
          <div
            className="con_content_txt con_content_image"
            onClick={() => command.emitter('executor', 'open', img)}>
            <Image
              className="image"
              width={300}
              src={shareOpenLink(img.shareLink)}
              preview={false}
            />
          </div>
        );
      }
      return <div className="con_content_txt">消息异常</div>;
    }
    case MessageType.Video: {
      const img: FileItemShare = parseAvatar(item.msgBody);
      if (img && img.shareLink) {
        return (
          <div
            className="con_content_txt"
            onClick={() => command.emitter('executor', 'open', img)}>
            {img?.thumbnail ? (
              <Image width={300} src={img.thumbnail} preview={false} />
            ) : (
              <div style={{ color: '#154ad8' }}>{img.name}</div>
            )}
          </div>
        );
      }
      return <div className="con_content_txt">消息异常</div>;
    }
    // 其他类型处理...
  }
};

关键点

  • 图片消息: 使用 Image 组件展示图片。 点击图片时触发 command.emitter 打开图片。
  • 视频消息: 如果有缩略图,展示缩略图;否则展示视频名称。 点击视频时触发 command.emitter 打开视频。
  • 文件消息: 展示文件名称、大小和操作按钮(在线预览、下载)。
  • 语音消息: 使用 audio 标签播放语音。
  • 名片消息: 展示名片的头像、名称和备注。 点击名片时触发 command.emitter 打开名片详情。
  • 动态消息: 展示动态的内容、资源和发布者信息。
  • 任务消息: 使用 TaskMsg 组件渲染任务内容。
  • 默认消息: 如果消息包含图片标记 $IMG,则解析并展示图片;如果消息是 URL,则使用 LinkPreview 组件展示链接预览。默认展示为纯文本消息。

注意事项

  • 消息解析的健壮性:确保所有消息类型都能正确解析,避免出现“消息异常”的情况。
  • 性能优化:使用 React.FragmentImage.PreviewGroup 优化图片渲染。
  • 安全性:使用 dangerouslySetInnerHTML 时,确保消息内容已被安全过滤,防止 XSS 攻击。
  • 扩展性:如果需要支持新的消息类型,可以在 switch 语句中添加对应的处理逻辑。

以下是如何使用 parseMsg 函数的示例:

typescript
import { parseMsg } from './parseMsg';

const MessageItem = ({ message }: { message: IMessage }) => {
  return <div className="message-item">{parseMsg(message)}</div>;
};

5.4.1.3 parseCiteMsg 结构

parseCiteMsg 用于解析并渲染引用消息,展示被引用的消息内容。 其输入参数是 item: IMessage, 消息对象,包含消息的类型、内容、发送者等信息。 parseMsg 主要支持的消息类型:

  • MessageType.Image:图片消息
  • MessageType.File:文件消息
  • MessageType.Voice:语音消息
  • MessageType.NameCard:名片消息
  • MessageType.Dynamic:动态消息
  • MessageType.Task:任务消息
  • 默认类型:文本消息或其他类型

其代码逻辑主要如下:

typescript
export const parseCiteMsg = (item: IMessage): any => {
  switch (item.msgType) {
    case MessageType.Image: {
      const img: FileItemShare = parseAvatar(item.msgBody);
      if (img && img.thumbnail) {
        return (
          <div className="con_content_cite_txt">
            <span>{item.from.name}:</span>
            <Image
              src={img.thumbnail}
              preview={{ src: shareOpenLink(img.shareLink) }}
            />
          </div>
        );
      }
      return <div className="con_content_cite_txt">消息异常</div>;
    }
    // 其他类型处理...
  }
};

关键点

  • 引用消息的展示:在消息内容前添加发送者的名称。
  • 图片引用:展示图片的缩略图。点击图片时打开大图预览。
  • 文件引用:展示文件的名称和大小。提供下载链接。
  • 语音引用:使用 audio 标签播放语音。
  • 名片引用:展示名片的头像、名称和备注。
  • 动态引用:展示动态的内容和资源。
  • 任务引用:使用 TaskMsg 组件渲染任务内容。
  • 默认引用:如果消息包含图片标记 $IMG,则解析并展示图片。
  • 默认展示为纯文本消息。

5.4.1.4 parseForwardMsg 结构

parseForwardMsg 主要用于解析并渲染转发消息,展示转发的消息内容。 其输入参数是 item: IMessage[] 用于转发的消息列表和 viewForward?: (item: IMessage[]) => void 查看转发消息的回调函数。

其代码逻辑主要如下:

typescript
export const parseForwardMsg = (
  item: IMessage[],
  viewForward?: (item: IMessage[]) => void,
) => {
  let formName = Array.from(
    new Set(item.map((msg: IMessage) => msg.from.name).filter((name: string) => name)),
  );
  let showName =
    formName && formName.length > 2
      ? '群聊'
      : `${formName[0]}${formName[1] ? '和' + formName[1] : ''}的`;
  return (
    <div
      className="con_content_forward_txt"
      onClick={() => viewForward && viewForward(item)}>
      <div className="con_content_forward_session">{`${showName}会话消息`}</div>
      {item.map((msg: IMessage, idx: number) => {
        if (idx > 2) return;
        switch (msg.msgType) {
          case MessageType.Image: {
            const img: FileItemShare = parseAvatar(msg.msgBody);
            if (img)
              return (
                <div className="con_content_forward_msg">
                  {msg.from.name}:{img.name}
                </div>
              );
            return <div className="con_content_forward_msg">消息异常</div>;
          }
          // 其他类型处理...
        }
      })}
    </div>
  );
};

关键点

  • 转发消息的展示:展示转发的会话名称(如“群聊”或“某某和某某的会话消息”)。
  • 消息内容:默认展示前 3 条消息内容。支持图片、文件、语音、任务等类型的消息。
  • 点击事件:点击转发消息时,触发 viewForward 回调函数,查看完整的转发消息。

5.4.1.5 TextParagraph 结构

TextParagraph 是一个用于展示文本消息的 React 组件。它支持以下功能:

  • 自动检测文本内容的高度,判断是否需要折叠显示。
  • 支持将文本中的链接解析为可点击的超链接。
  • 提供“展开/收起”功能,方便用户查看完整的长文本内容。

其代码结构主要如下:

markdown
textParagraph/
├── 引入依赖
│   ├── React: 用于构建组件。
│   ├── useState, useEffect, useRef: React 钩子,用于管理状态和 DOM 引用。
│   ├── parseTolink: 工具函数,用于将文本中的链接解析为超链接。
│   ├── index.module.less: 样式文件。
├── 接口定义
│   └── Iprops: 定义组件的 props。
├── 核心组件
│   └── TextParagraph: 主组件。
└── 导出组件
    └── export default TextParagraph

该组件主要定义了组件的输入参数 props

typescript
interface Iporps {
  msgBody: string; // 消息的文本内容
}

其中的 msgBody 是消息的文本内容,支持普通文本和包含链接的文本。

核心组件 TextParagraph 是用于渲染消息文本内容,自动检测文本高度,判断是否需要折叠显示, 支持“展开/收起”功能,将文本中的链接解析为可点击的超链接。

其参数有 props: Iprops 包含消息的文本内容。 另外包含状态 show: boolean 是否显示“展开”按钮,初始值为 false。 当文本高度超过 150px 时,显示“展开”按钮。 点击“展开”按钮后,显示完整文本并隐藏按钮。 除此之外,还有参数 ref: React.RefObject<any> 用于获取文本容器的 DOM 节点,检测其高度。

其核心逻辑主要以下几条:

  • 文本高度检测:

    使用 useEffect 钩子在组件挂载后检测文本容器的高度。 如果高度超过 150px,则设置 show 状态为 true,显示“展开”按钮。

  • 链接解析:

    使用 parseTolink 工具函数将文本中的链接解析为超链接。 通过 dangerouslySetInnerHTML 将解析后的 HTML 内容插入到文本容器中。

  • 展开/收起功能:

    当文本高度超过 150px 时,显示“展开”按钮。 点击“展开”按钮后,显示完整文本并隐藏按钮。

其代码实现主要如下:

typescript
const TextParagraph = (props: Iporps) => {
  const ref = useRef<any>(); // 引用文本容器
  const [show, setShow] = useState(false); // 是否显示“展开”按钮

  useEffect(() => {
    if (ref.current) {
      const textHeight = ref.current.clientHeight; // 获取文本容器高度
      setShow(textHeight > 150 ? true : false); // 判断是否需要折叠
    }
  }, []);

  return (
    <div className="con_content_txt">
      <div style={{ display: 'flex' }}>
        <div
          className={show ? styles.showText : ''} // 根据状态应用样式
          dangerouslySetInnerHTML={{ __html: parseTolink(props.msgBody) }} // 插入解析后的 HTML
          ref={ref} // 绑定引用
        />
        {show ? (
          <div className={styles.showLink}>
            <a style={{ whiteSpace: 'nowrap' }} onClick={() => setShow(false)}>
              展开
            </a>
          </div>
        ) : (
          <></>
        )}
      </div>
    </div>
  );
};

parseTolink 是一个工具函数 其功能是将文本中的链接解析为可点击的超链接。

就比如对于示例,输入:

"请访问 https://example.com 查看详情。"

则会输出:

"请访问 <a href='https://example.com' target='_blank'>https://example.com</a> 查看详情。"

其使用方式是

typescript
dangerouslySetInnerHTML={{ __html: parseTolink(props.msgBody) }}

以下是如何使用 TextParagraph 组件的示例代码:

typescript
import React from 'react';
import TextParagraph from './textParagraph';

const Example = () => {
  const longText = `
    这是一个很长的文本消息,包含多个段落和链接。
    请访问 https://example.com 查看详情。
    这是第二段内容,继续访问 https://another-example.com。
  `;

  return (
    <div>
      <h3>消息展示</h3>
      <TextParagraph msgBody={longText} />
    </div>
  );
};

export default Example;

注意事项

  • 性能优化: useEffect 钩子仅在组件挂载时执行一次,避免重复计算文本高度。 使用 dangerouslySetInnerHTML 时,确保输入的 HTML 已经过滤,防止 XSS 攻击。

  • 样式控制: 文本折叠的高度限制为 150px,可以根据需求在样式文件中调整。 确保 index.module.less 文件中的样式类名与组件中的类名一致。

  • 链接解析: parseTolink 函数需要确保对所有可能的链接格式进行正确解析。 如果消息内容中包含特殊字符(如 HTML 标签),需要对其进行转义处理。

  • 兼容性: 组件依赖于现代浏览器的 dangerouslySetInnerHTMLref 功能,确保在支持这些特性的环境中运行。

组件交互流程

  • 组件挂载: 使用 useRef 获取文本容器的 DOM 节点。 使用 useEffect 检测文本高度,判断是否需要折叠显示。

  • 文本渲染: 使用 parseTolink 将文本中的链接解析为超链接。 使用 dangerouslySetInnerHTML 将解析后的 HTML 插入到文本容器中。

  • 用户交互: 如果文本高度超过 150px,显示“展开”按钮。 用户点击“展开”按钮后,显示完整文本并隐藏按钮。

5.4.2 information 组件

文件路径src\components\DataPreview\session\chat\GroupContent\information.tsx

5.4.2.1 功能概述

Information 是一个用于展示消息接收人列表的组件,支持以下功能:

  • 已读/未读消息的分类展示:通过标签页切换查看已读和未读的接收人列表。
  • 搜索过滤功能:支持通过输入关键字过滤接收人列表。
  • 滚动加载:未读列表支持滚动加载更多接收人数据。
  • 接收人信息展示:展示接收人的头像、名称、标签以及消息接收时间。

5.4.2.2 文件结构

markdown
information/
├── 引入依赖
│   ├── React: 用于构建组件。
│   ├── antd: 提供 Drawer、List、Tabs 等 UI 组件。
│   ├── orgCtrl: 用户和组织相关的控制器。
│   ├── ScrollList: 自定义滚动列表组件。
│   ├── showChatTime: 工具函数,用于格式化时间。
├── 核心组件
│   └── Information: 主组件。
├── 辅助函数
│   └── filterLables: 过滤接收人列表。
│   └── loadLabelItem: 渲染接收人列表项。
└── 导出组件
    └── export default Information

5.4.2.3 组件详解

  1. 接口定义

    information 组件接受以下两个参数:

typescript
const Information = ({ msg, onClose }: { msg: IMessage; onClose: Function }) => { ... }
  • msg: IMessage:消息对象,包含消息的元数据、已读/未读接收人列表等信息。
  • onClose: Function:关闭抽屉的回调函数。
  1. 核心组件

    功能:

    • 展示消息接收人列表,分为“已读”和“未读”两类。
    • 支持通过搜索框过滤接收人列表。
    • 支持滚动加载未读接收人数据。

    状态:

    • tabsKey: string:当前选中的标签页的 key
    • unreadInfo: IMessageLabel[]:未读接收人列表,初始值为 msg.unreadInfo

5.4.2.4 核心逻辑

  1. 标签过滤 通过 filterLables 函数对接收人列表进行过滤,支持根据接收人名称、标签或编码进行匹配。
typescript
const filterLables = (labels: IMessageLabel[], filter: string) => {
  if (filter === "") return labels;
  return labels.filter((i) => {
    if (i.label.includes(filter)) return true;
    var entity = orgCtrl.user.findMetadata<schema.XTarget>(i.userId);
    return entity && (entity.name.includes(filter) || entity.code.includes(filter));
  });
};
  1. 已读列表 通过 readList 函数渲染已读接收人列表,支持搜索过滤。
typescript
const readList = () => {
  const [filter, setFilter] = useState('');
  return (
    <ScrollList
      loaded
      searchValue={filter}
      height="calc(100vh - 220px)"
      setSearchValue={(v) => setFilter(v)}
      data={filterLables(
        msg.labels.filter((a) => a.designateId != msg.metadata.designateId),
        filter
      )}
      renderItem={loadLabelItem}
    />
  );
};
  1. 未读列表 通过 unRead 函数渲染未读接收人列表,支持搜索过滤和滚动加载。
typescript
const unRead = () => {
  const [filter, setFilter] = useState('');
  return (
    <ScrollList
      loaded
      data={filterLables(unreadInfo, filter)}
      searchValue={filter}
      height="calc(100vh - 220px)"
      setSearchValue={(v) => setFilter(v)}
      renderItem={loadLabelItem}
      onLoadMore={() => {
        msg.chat.target.loadMembers().then(() => {
          setUnreadInfo(msg.unreadInfo);
        });
      }}
    />
  );
};
  1. 接收人列表项 通过 loadLabelItem 函数渲染接收人列表中的每一项,包括头像、名称、标签和接收时间。
typescript
const loadLabelItem = (item: IMessageLabel) => {
  return (
    <List.Item
      style={{ cursor: 'pointer', padding: 6 }}
      actions={
        item.time.length > 0
          ? [<div key={item.time}>{showChatTime(item.time)}</div>]
          : []
      }>
      <List.Item.Meta
        avatar={<TeamIcon entityId={item.designateId} size={42} />}
        title={<strong>{item.labeler.name}</strong>}
        description={
          <div style={{ lineHeight: '16px' }}>
            <div className="ellipsis1">{item.label}</div>
          </div>
        }
      />
    </List.Item>
  );
};
  1. 标签页配置 通过 items 数组配置“已读”和“未读”标签页。
typescript
const items: TabsProps['items'] = [
  {
    key: 'unRead',
    label: `未读(${msg.chat.memberCount - 1 - msg.readedIds.length})`,
    children: unRead(),
  },
  { key: 'read', label: `已读(${msg.readedIds.length})`, children: readList() },
];
  1. 抽屉渲染 通过 Drawer 组件渲染消息接收人列表的抽屉。
typescript
return (
  <Drawer width={480} title={'消息接收人列表'} onClose={() => onClose()} closable open>
    <Tabs
      centered
      items={items}
      defaultActiveKey={'unRead'}
      activeKey={tabsKey}
      onChange={(e) => setTabsKey(e)}
    />
  </Drawer>
);

5.4.2.5 辅助函数

filterLables:过滤接收人列表,支持根据标签、名称或编码进行匹配。

loadLabelItem: 渲染接收人列表中的每一项,包括头像、名称、标签和接收时间。

5.4.2.6 使用示例

以下是如何使用 Information 组件的示例代码:

typescript
import React from 'react';
import Information from './information';

const Example = ({ msg }) => {
  const handleClose = () => {
    console.log('关闭抽屉');
  };

  return <Information msg={msg} onClose={handleClose} />;
};

export default Example;

5.4.2.7 注意事项

  1. 性能优化: 使用 useStateuseEffect 管理状态,避免不必要的重复渲染。 滚动加载未读列表时,确保数据的增量加载逻辑正确。

  2. 样式控制: 确保 ScrollListList.Item 的样式与整体 UI 风格一致。 使用 ellipsis1 类名控制标签的文本溢出效果。

  3. 数据完整性: 确保 msg 对象中包含完整的已读和未读接收人数据。 在滚动加载时,确保 msg.chat.target.loadMembers 方法能够正确更新 unreadInfo

  4. 扩展性: 如果需要支持更多的接收人分类(如“已回复”),可以在 items 数组中添加新的标签页配置。

5.4.2.8 组件交互流程

  1. 组件挂载: 初始化 unreadInfo 状态为 msg.unreadInfo。 默认选中“未读”标签页。

  2. 标签页切换: 用户点击标签页时,更新 tabsKey 状态,切换到对应的接收人列表。

  3. 搜索过滤: 用户在搜索框中输入关键字时,调用 filterLables 函数过滤接收人列表。

  4. 滚动加载: 用户滚动到未读列表底部时,调用 msg.chat.target.loadMembers 方法加载更多接收人数据。

  5. 关闭抽屉: 用户点击关闭按钮时,调用 onClose 回调函数关闭抽屉。

云原生应用研究院版权所有