Merge branch 'develop' of https://gitee.com/cunkebao/cunkebao_v3 into develop

This commit is contained in:
wong
2025-12-02 17:47:32 +08:00
9 changed files with 622 additions and 35 deletions

View File

@@ -10,6 +10,13 @@ const { token } = useUserStore.getState();
const DEFAULT_DEBOUNCE_GAP = 1000;
const debounceMap = new Map<string, number>();
// 需要高频轮询、不走截流的接口白名单(按实际接口路径调整)
const NO_DEBOUNCE_URLS = [
"/wechat/friend/list", // 好友列表
"/wechat/group/list", // 群组列表
"/wechat/message/list", // 消息列表
];
const instance: AxiosInstance = axios.create({
baseURL: (import.meta as any).env?.VITE_API_BASE_URL || "/api",
timeout: 20000,
@@ -64,21 +71,31 @@ export function request(
url: string,
data?: any,
method: Method = "GET",
config?: AxiosRequestConfig,
// 允许通过 config.debounce 控制是否开启截流,默认开启
config?: AxiosRequestConfig & { debounce?: boolean },
debounceGap?: number,
): Promise<any> {
const gap =
typeof debounceGap === "number" ? debounceGap : DEFAULT_DEBOUNCE_GAP;
const key = `${method}_${url}_${JSON.stringify(data)}`;
const now = Date.now();
const last = debounceMap.get(key) || 0;
if (gap > 0 && now - last < gap) {
// Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' });
return Promise.reject("请求过于频繁,请稍后再试");
}
debounceMap.set(key, now);
const axiosConfig: AxiosRequestConfig = {
const enableDebounce = config?.debounce !== false;
const isInNoDebounceList = NO_DEBOUNCE_URLS.some(pattern =>
url.includes(pattern),
);
const shouldDebounce = enableDebounce && !isInNoDebounceList;
if (shouldDebounce) {
const key = `${method}_${url}_${JSON.stringify(data)}`;
const now = Date.now();
const last = debounceMap.get(key) || 0;
if (gap > 0 && now - last < gap) {
// Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' });
return Promise.reject("请求过于频繁,请稍后再试");
}
debounceMap.set(key, now);
}
const axiosConfig: AxiosRequestConfig & { debounce?: boolean } = {
url,
method,
...config,

View File

@@ -9,11 +9,20 @@ import { useUserStore } from "@/store/module/user";
const DEFAULT_DEBOUNCE_GAP = 1000;
const debounceMap = new Map<string, number>();
// 需要高频轮询、不走截流的接口白名单(按实际接口路径调整)
const NO_DEBOUNCE_URLS = [
"/wechat/friend/list",
"/wechat/group/list",
"/wechat/message/list",
];
interface RequestConfig extends AxiosRequestConfig {
headers: {
headers?: {
Client?: string;
"Content-Type"?: string;
};
// 是否开启截流,默认开启
debounce?: boolean;
}
const instance: AxiosInstance = axios.create({
@@ -63,14 +72,23 @@ export function request(
): Promise<any> {
const gap =
typeof debounceGap === "number" ? debounceGap : DEFAULT_DEBOUNCE_GAP;
const key = `${method}_${url}_${JSON.stringify(data)}`;
const now = Date.now();
const last = debounceMap.get(key) || 0;
if (gap > 0 && now - last < gap) {
// Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' });
return Promise.reject("请求过于频繁,请稍后再试");
const enableDebounce = config?.debounce !== false;
const isInNoDebounceList = NO_DEBOUNCE_URLS.some(pattern =>
url.includes(pattern),
);
const shouldDebounce = enableDebounce && !isInNoDebounceList;
if (shouldDebounce) {
const key = `${method}_${url}_${JSON.stringify(data)}`;
const now = Date.now();
const last = debounceMap.get(key) || 0;
if (gap > 0 && now - last < gap) {
// Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' });
return Promise.reject("请求过于频繁,请稍后再试");
}
debounceMap.set(key, now);
}
debounceMap.set(key, now);
const axiosConfig: RequestConfig = {
url,

View File

@@ -2,7 +2,7 @@
.redPacketMessage {
background: transparent;
box-shadow: none;
max-width: 300px;
width: 260px;
}
.redPacketCard {

View File

@@ -1,4 +1,6 @@
import React from "react";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import styles from "./RedPacketMessage.module.scss";
interface RedPacketData {
@@ -10,9 +12,15 @@ interface RedPacketData {
interface RedPacketMessageProps {
content: string;
msg?: ChatRecord;
contract?: ContractData | weChatGroup;
}
const RedPacketMessage: React.FC<RedPacketMessageProps> = ({ content }) => {
const RedPacketMessage: React.FC<RedPacketMessageProps> = ({
content,
msg,
contract,
}) => {
const renderErrorMessage = (fallbackText: string) => (
<div className={styles.messageText}>{fallbackText}</div>
);
@@ -39,10 +47,42 @@ const RedPacketMessage: React.FC<RedPacketMessageProps> = ({ content }) => {
const title = jsonData.sendertitle || "恭喜发财,大吉大利";
const paymsgid = jsonData.paymsgid || "";
const nativeurl = jsonData.nativeurl || "";
// 处理红包点击事件,发送 socket 请求
const handleRedPacketClick = () => {
if (!contract || !paymsgid || !nativeurl) {
console.warn("红包点击失败:缺少必要参数", {
contract,
paymsgid,
nativeurl,
});
return;
}
const isGroup = !!contract.chatroomId;
const wechatFriendId = isGroup ? 0 : contract.id;
// 发送 socket 请求
useWebSocketStore.getState().sendCommand("CmdOpenLuckyMoney", {
wechatAccountId: contract.wechatAccountId,
wechatFriendId: wechatFriendId,
paymsgid: paymsgid,
nativeurl: nativeurl,
});
console.log("发送红包打开请求:", {
cmdType: "CmdOpenLuckyMoney",
wechatAccountId: contract.wechatAccountId,
wechatFriendId: wechatFriendId,
paymsgid: paymsgid,
nativeurl: nativeurl,
});
};
return (
<div className={styles.redPacketMessage}>
<div className={styles.redPacketCard}>
<div className={styles.redPacketCard} onClick={handleRedPacketClick}>
<div className={styles.redPacketHeader}>
<div className={styles.redPacketIcon}>🧧</div>
<div className={styles.redPacketTitle}>{title}</div>

View File

@@ -0,0 +1,132 @@
// 转账消息样式
.transferMessage {
background: transparent;
box-shadow: none;
width: 260px;
max-width: 260px;
}
.transferCard {
position: relative;
display: flex;
flex-direction: column;
padding: 12px;
background: #ff9500; // 橙色背景
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(255, 149, 0, 0.2);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(255, 149, 0, 0.3);
}
&:active {
transform: translateY(0);
}
&.transferDisabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
transform: none;
box-shadow: 0 2px 8px rgba(255, 149, 0, 0.2);
}
}
}
.transferHeader {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 5px;
}
.transferIcon {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
flex-shrink: 0;
font-size: 18px;
color: #ffffff;
border: 2px solid #ffffff;
}
.transferAmount {
font-size: 16px;
color: #ffffff;
flex: 1;
}
.transferStatus {
font-size: 13px;
color: #ffffff;
margin-bottom: 5px;
opacity: 0.95;
}
.transferDivider {
height: 1px;
background: rgba(255, 255, 255, 0.3);
margin-bottom: 6px;
}
.transferFooter {
display: flex;
align-items: center;
justify-content: flex-start;
}
.transferLabel {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
font-weight: 400;
line-height: 1.4;
}
// 消息文本样式(用于错误提示)
.messageText {
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
color: #8c8c8c;
font-size: 13px;
}
// 响应式设计
@media (max-width: 768px) {
.transferMessage {
max-width: 200px;
}
.transferCard {
padding: 12px;
}
.transferIcon {
width: 28px;
height: 28px;
svg {
width: 18px;
height: 18px;
}
}
.transferAmount {
font-size: 18px;
}
.transferStatus {
font-size: 12px;
}
.transferLabel {
font-size: 11px;
}
}

View File

@@ -0,0 +1,154 @@
import React from "react";
import { SwapOutlined } from "@ant-design/icons";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import styles from "./TransferMessage.module.scss";
interface TransferData {
title?: string;
feedesc?: string;
payMemo?: string;
transferid?: string;
transcationid?: string;
invalidtime?: string;
paysubtype?: string;
[key: string]: any;
}
interface TransferMessageProps {
content: string;
msg?: ChatRecord;
contract?: ContractData | weChatGroup;
}
const TransferMessage: React.FC<TransferMessageProps> = ({
content,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
msg, // 保留参数以保持与 RedPacketMessage 组件的一致性,未来可能会用到
contract,
}) => {
const renderErrorMessage = (fallbackText: string) => (
<div className={styles.messageText}>{fallbackText}</div>
);
if (typeof content !== "string" || !content.trim()) {
return renderErrorMessage("[转账消息 - 无效内容]");
}
try {
const trimmedContent = content.trim();
const jsonData: TransferData = JSON.parse(trimmedContent);
// 验证是否为转账消息
const isTransfer =
jsonData.title === "微信转账" ||
(jsonData.transferid && jsonData.feedesc);
if (!isTransfer) {
return renderErrorMessage("[转账消息 - 格式错误]");
}
const amount = jsonData.feedesc || "¥0.00";
// 判断转账状态
const getTransferStatus = (
data: TransferData,
): { text: string; canClick: boolean } => {
const paySubType = data.paysubtype || "";
switch (paySubType) {
case "1":
return { text: "待朋友确认收钱", canClick: true };
case "2":
return { text: "已过期", canClick: false };
case "3":
return { text: "已领取", canClick: false };
case "4":
return { text: "已退回", canClick: false };
default:
// 默认情况:可能是待领取
return { text: "待朋友确认收钱", canClick: true };
}
};
const { text: statusText, canClick } = getTransferStatus(jsonData);
// 处理转账点击事件,发送 socket 请求
const handleTransferClick = () => {
// 如果状态不允许点击,直接返回
if (!canClick) {
console.log("转账状态不允许点击:", statusText);
return;
}
if (
!contract ||
!jsonData.transferid ||
!jsonData.transcationid ||
!jsonData.invalidtime ||
!jsonData.paysubtype
) {
console.warn("转账点击失败:缺少必要参数", {
contract,
transferid: jsonData.transferid,
transcationid: jsonData.transcationid,
invalidtime: jsonData.invalidtime,
paysubtype: jsonData.paysubtype,
});
return;
}
const isGroup = !!contract.chatroomId;
const wechatFriendId = isGroup ? 0 : contract.id;
// 发送 socket 请求
useWebSocketStore.getState().sendCommand("CmdReceiveTransMoney", {
wechatAccountId: contract.wechatAccountId,
wechatFriendId: wechatFriendId,
transcationid: jsonData.transcationid,
transferid: jsonData.transferid,
invalidtime: jsonData.invalidtime,
paysubtype: jsonData.paysubtype,
});
console.log("发送转账接收请求:", {
cmdType: "CmdReceiveTransMoney",
wechatAccountId: contract.wechatAccountId,
wechatFriendId: wechatFriendId,
transcationid: jsonData.transcationid,
transferid: jsonData.transferid,
invalidtime: jsonData.invalidtime,
paysubtype: jsonData.paysubtype,
});
};
return (
<div className={styles.transferMessage}>
<div
className={`${styles.transferCard} ${!canClick ? styles.transferDisabled : ""}`}
onClick={handleTransferClick}
>
<div className={styles.transferHeader}>
<div className={styles.transferIcon}>
<SwapOutlined style={{ fontSize: 20 }} />
</div>
<div className="destion">
<div className={styles.transferAmount}>{amount}</div>
<div className={styles.transferStatus}>{statusText}</div>
</div>
</div>
<div className={styles.transferDivider}></div>
<div className={styles.transferFooter}>
<span className={styles.transferLabel}></span>
</div>
</div>
</div>
);
} catch (e) {
console.warn("转账消息解析失败:", e);
return renderErrorMessage("[转账消息 - 解析失败]");
}
};
export default TransferMessage;

View File

@@ -8,9 +8,11 @@ import ClickMenu from "./components/ClickMeau";
import LocationMessage from "./components/LocationMessage";
import SystemRecommendRemarkMessage from "./components/SystemRecommendRemarkMessage/index";
import RedPacketMessage from "./components/RedPacketMessage";
import TransferMessage from "./components/TransferMessage";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { formatWechatTime } from "@/utils/common";
import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji";
import { parseSystemMessage } from "@/utils/filter";
import styles from "./com.module.scss";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { useContactStore } from "@/store/module/weChat/contacts";
@@ -148,6 +150,7 @@ type GroupRenderItem = {
const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
// 右键菜单状态
const [contextMenu, setContextMenu] = useState({
visible: false,
@@ -169,6 +172,8 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
const isLoadingData = useWeChatStore(state => state.isLoadingData);
const showCheckbox = useWeChatStore(state => state.showCheckbox);
const prevMessagesRef = useRef(currentMessages);
const isLoadingMoreRef = useRef(false);
const scrollPositionRef = useRef<number>(0);
const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox);
const updateEnterModule = useWeChatStore(state => state.updateEnterModule);
const currentCustomer = useCustomerStore(state =>
@@ -255,7 +260,6 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
msg?: ChatRecord,
contract?: ContractData | weChatGroup,
) => {
console.log("红包");
if (isLegacyEmojiContent(trimmedContent)) {
return renderEmojiContent(rawContent);
}
@@ -271,7 +275,23 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
"wxpay://c2cbizmessagehandler/hongbao/receivehongbao",
)
) {
return <RedPacketMessage content={rawContent} />;
return (
<RedPacketMessage
content={rawContent}
msg={msg}
contract={contract}
/>
);
}
// 判断是否为转账消息
if (
jsonData.title === "微信转账" ||
(jsonData.transferid && jsonData.feedesc)
) {
return (
<TransferMessage content={rawContent} msg={msg} contract={contract} />
);
}
if (jsonData.type === "file" && msg && contract) {
@@ -443,7 +463,10 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
}
});
if (currentMessages.length > prevLength && !hasVideoStateChange) {
// 如果正在加载更早的消息,不自动滚动到底部
if (isLoadingMoreRef.current && currentMessages.length > prevLength) {
// 不滚动,等待加载完成后在另一个 useEffect 中恢复滚动位置
} else if (currentMessages.length > prevLength && !hasVideoStateChange) {
scrollToBottom();
} else if (isLoadingData && !hasVideoStateChange) {
scrollToBottom();
@@ -453,6 +476,22 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
prevMessagesRef.current = currentMessages;
}, [currentMessages, isLoadingData]);
// 监听加载状态,当加载完成时恢复滚动位置
useEffect(() => {
if (!messagesLoading && isLoadingMoreRef.current) {
// 等待DOM更新后恢复滚动位置
requestAnimationFrame(() => {
const container = messagesContainerRef.current;
if (container) {
const scrollHeight = container.scrollHeight;
const newScrollTop = scrollHeight - scrollPositionRef.current;
container.scrollTop = newScrollTop;
}
isLoadingMoreRef.current = false;
});
}
}, [messagesLoading]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
@@ -745,6 +784,12 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
if (messagesLoading || !currentMessagesHasMore) {
return;
}
// 保存当前滚动位置(距离底部的距离)
const container = messagesContainerRef.current;
if (container) {
scrollPositionRef.current = container.scrollHeight - container.scrollTop;
isLoadingMoreRef.current = true;
}
loadChatMessages(false);
};
@@ -823,7 +868,7 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
};
return (
<div className={styles.messagesContainer}>
<div ref={messagesContainerRef} className={styles.messagesContainer}>
<div
className={styles.loadMore}
onClick={() => loadMoreMessages()}
@@ -839,14 +884,16 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
{groupMessagesByTime(currentMessages).map((group, groupIndex) => (
<React.Fragment key={`group-${groupIndex}`}>
{group.messages
.filter(v => [10000].includes(v.msgType))
.map(msg => (
<div
key={`divider-${msg.id}`}
className={styles.messageTime}
dangerouslySetInnerHTML={{ __html: msg.content }}
></div>
))}
.filter(v => [10000, -10001].includes(v.msgType))
.map(msg => {
// 解析系统消息提取纯文本移除img标签和_wc_custom_link_标签
const parsedText = parseSystemMessage(msg.content);
return (
<div key={`divider-${msg.id}`} className={styles.messageTime}>
{parsedText}
</div>
);
})}
{group.messages
.filter(v => [570425393, 90000].includes(v.msgType))
@@ -874,7 +921,7 @@ const MessageRecord: React.FC<MessageRecordProps> = ({ contract }) => {
})}
<div className={styles.messageTime}>{group.time}</div>
{group.messages
.filter(v => ![10000, 570425393, 90000].includes(v.msgType))
.filter(v => ![10000, 570425393, 90000, -10001].includes(v.msgType))
.map(msg => {
return renderMessage(msg);
})}

View File

@@ -47,6 +47,7 @@ const MessageList: React.FC<MessageListProps> = () => {
} = useMessageStore();
const [filteredSessions, setFilteredSessions] = useState<ChatSession[]>([]);
const [syncing, setSyncing] = useState(false); // 同步状态
const hasEnrichedRef = useRef(false); // 是否已做过未知联系人补充
// 右键菜单相关状态
const [contextMenu, setContextMenu] = useState<{
@@ -296,7 +297,130 @@ const MessageList: React.FC<MessageListProps> = () => {
};
}, [contextMenu.visible]);
// ==================== 数据加载 ====================
// ==================== 数据加载 & 未知联系人补充 ====================
// 同步完成后,检查是否存在“未知联系人”或缺失头像/昵称的会话,并异步补充详情
const enrichUnknownContacts = async () => {
if (!currentUserId) return;
if (hasEnrichedRef.current) return; // 避免重复执行
// 只在会话有数据时执行
if (!sessions || sessions.length === 0) return;
const needEnrich = sessions.filter(s => {
const noName = !s.conRemark && !s.nickname && !s.wechatId;
const isUnknownNickname = s.nickname === "未知联系人";
const noAvatar = !s.avatar;
return noName || isUnknownNickname || noAvatar;
});
if (needEnrich.length === 0) {
hasEnrichedRef.current = true;
return;
}
hasEnrichedRef.current = true;
// 逐个异步拉取详情,失败不打断整体流程
for (const session of needEnrich) {
try {
let detailResult: any = null;
if (session.type === "friend") {
detailResult = await getWechatFriendDetail({ id: session.id });
} else {
detailResult = await getWechatChatroomDetail({ id: session.id });
}
const detail = detailResult?.detail;
if (!detail) continue;
// 更新会话列表 UI
setSessionState(prev =>
prev.map(s =>
s.id === session.id && s.type === session.type
? {
...s,
avatar:
session.type === "group"
? detail.chatroomAvatar || s.avatar
: detail.avatar || s.avatar,
nickname: detail.nickname || s.nickname,
conRemark: detail.conRemark || s.conRemark,
wechatId: detail.wechatId || s.wechatId,
}
: s,
),
);
// 同步到会话数据库
await MessageManager.updateSession({
userId: currentUserId,
id: session.id,
type: session.type,
avatar:
session.type === "group"
? detail.chatroomAvatar || session.avatar
: detail.avatar || session.avatar,
nickname: detail.nickname || session.nickname,
conRemark: detail.conRemark || session.conRemark,
wechatId: detail.wechatId || session.wechatId,
});
// 同步到联系人数据库(方便后续搜索、其它页面使用)
const contactBase: any = {
serverId: `${session.type}_${session.id}`,
userId: currentUserId,
id: session.id,
type: session.type,
wechatAccountId: detail.wechatAccountId,
nickname: detail.nickname || "",
conRemark: detail.conRemark || "",
avatar:
session.type === "group"
? detail.chatroomAvatar || ""
: detail.avatar || "",
lastUpdateTime: new Date().toISOString(),
sortKey: "",
searchKey: (detail.conRemark || detail.nickname || "").toLowerCase(),
};
if (session.type === "group") {
Object.assign(contactBase, {
chatroomId: detail.chatroomId,
chatroomOwner: detail.chatroomOwner,
selfDisplayName: detail.selfDisplyName,
notice: detail.notice,
});
} else {
Object.assign(contactBase, {
wechatFriendId: detail.id,
wechatId: detail.wechatId,
alias: detail.alias,
gender: detail.gender,
region: detail.region,
signature: detail.signature,
phone: detail.phone,
quanPin: detail.quanPin,
groupId: detail.groupId,
});
}
// 使用 upsert 逻辑:如果已存在就更新,不存在则新增
const existContact = await ContactManager.getContactByIdAndType(
currentUserId,
session.id,
session.type,
);
if (existContact) {
await ContactManager.updateContact(contactBase);
} else {
await ContactManager.addContact(contactBase);
}
} catch (error) {
console.error("补拉未知联系人详情失败:", error, session);
}
}
};
// 与服务器同步数据优化版逐页同步立即更新UI
const syncWithServer = async () => {
@@ -379,6 +503,8 @@ const MessageList: React.FC<MessageListProps> = () => {
console.log(
`会话同步完成: 成功${successCount}页, 失败${failCount}页, 共处理${totalProcessed}条数据`,
);
// 同步完成后,异步补充未知联系人信息
enrichUnknownContacts();
} catch (error) {
console.error("同步服务器数据失败:", error);
} finally {

View File

@@ -40,6 +40,25 @@ export const messageFilter = (message: string) => {
case !!parsed.linkUrl:
return "[链接]";
// 微信红包消息:包含 paymsgid 或 nativeurl 中包含红包链接
case !!(
parsed.paymsgid ||
(parsed.nativeurl &&
parsed.nativeurl.includes(
"wxpay://c2cbizmessagehandler/hongbao/receivehongbao",
))
):
return parsed.sendertitle
? `[微信红包] ${parsed.sendertitle}`
: "[微信红包]";
// 微信转账消息:包含 title="微信转账" 或 transferid + feedesc
case !!(
parsed.title === "微信转账" ||
(parsed.transferid && parsed.feedesc)
):
return parsed.feedesc ? `[微信转账] ${parsed.feedesc}` : "[微信转账]";
// 文本消息:包含 text 或 content
case !!(parsed.text || parsed.content):
return parsed.text || parsed.content;
@@ -67,3 +86,37 @@ export const messageFilter = (message: string) => {
return message;
}
};
/**
* 解析系统消息中的HTML标签提取纯文本
* 例如:<img src="SystemMessages_HongbaoIcon.png"/> 你领取了许老板的<_wc_custom_link_>红包</_wc_custom_link_>。
* 提取为:🧧 你领取了许老板的红包
*/
export const parseSystemMessage = (html: string): string => {
if (!html) return "";
let text = html;
// 移除所有 <img> 标签
text = text.replace(/<img[^>]*>/gi, "");
// 提取 <_wc_custom_link_> 标签内的文本内容
// 匹配 <_wc_custom_link_ ...>内容</_wc_custom_link_>
text = text.replace(
/<_wc_custom_link_[^>]*>(.*?)<\/_wc_custom_link_>/gi,
"$1",
);
// 清理多余的空格(将多个连续空格替换为单个空格)
text = text.replace(/\s+/g, " ");
// 去除首尾空格
text = text.trim();
// 如果消息内容包含红包相关关键词,在前面添加🧧图标
if (/红包|hongbao/i.test(text)) {
text = `🧧 ${text}`;
}
return text;
};