diff --git a/Cunkebao/src/pages/mobile/mine/setting/index.tsx b/Cunkebao/src/pages/mobile/mine/setting/index.tsx index 1ddcf226..c002543f 100644 --- a/Cunkebao/src/pages/mobile/mine/setting/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/setting/index.tsx @@ -8,7 +8,6 @@ import { LogoutOutlined, SettingOutlined, LockOutlined, - ReloadOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import { useUserStore } from "@/store/module/user"; @@ -16,7 +15,7 @@ import { useSettingsStore } from "@/store/module/settings"; import style from "./index.module.scss"; import NavCommon from "@/components/NavCommon"; import { sendMessageToParent, TYPE_EMUE } from "@/utils/postApp"; -import { updateChecker } from "@/utils/updateChecker"; +import { clearApplicationCache } from "@/utils/cacheCleaner"; interface SettingItem { id: string; @@ -58,13 +57,35 @@ const Setting: React.FC = () => { const handleClearCache = () => { Dialog.confirm({ content: "确定要清除缓存吗?这将清除所有本地数据。", - onConfirm: () => { - sendMessageToParent( - { - action: "clearCache", - }, - TYPE_EMUE.FUNCTION, - ); + onConfirm: async () => { + const handler = Toast.show({ + icon: "loading", + content: "正在清理缓存...", + duration: 0, + }); + try { + await clearApplicationCache(); + sendMessageToParent( + { + action: "clearCache", + }, + TYPE_EMUE.FUNCTION, + ); + handler.close(); + Toast.show({ + icon: "success", + content: "缓存清理完成", + position: "top", + }); + } catch (error) { + console.error("clear cache failed", error); + handler.close(); + Toast.show({ + icon: "fail", + content: "缓存清理失败,请稍后再试", + position: "top", + }); + } }, }); }; diff --git a/Cunkebao/src/utils/cacheCleaner.ts b/Cunkebao/src/utils/cacheCleaner.ts new file mode 100644 index 00000000..3be09fbd --- /dev/null +++ b/Cunkebao/src/utils/cacheCleaner.ts @@ -0,0 +1,70 @@ +// 全局缓存清理工具:浏览器存储 + IndexedDB + Zustand store +import { clearAllPersistedData } from "@/store"; +import { useUserStore } from "@/store/module/user"; +import { useAppStore } from "@/store/module/app"; +import { useSettingsStore } from "@/store/module/settings"; + +const isBrowser = typeof window !== "undefined"; + +const safeStorageClear = (storage?: Storage) => { + if (!storage) return; + try { + storage.clear(); + } catch (error) { + console.warn("清理存储失败:", error); + } +}; + +export const clearBrowserStorage = () => { + if (!isBrowser) return; + safeStorageClear(window.localStorage); + safeStorageClear(window.sessionStorage); + try { + clearAllPersistedData(); + } catch (error) { + console.warn("清理持久化 store 失败:", error); + } +}; + +export const clearAllIndexedDB = async (): Promise => { + if (!isBrowser || !window.indexedDB || !indexedDB.databases) return; + + const databases = await indexedDB.databases(); + const deleteJobs = databases + .map(db => db.name) + .filter((name): name is string => Boolean(name)) + .map( + name => + new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(name); + request.onsuccess = () => resolve(); + request.onerror = () => reject(new Error(`删除数据库 ${name} 失败`)); + request.onblocked = () => { + setTimeout(() => { + const retry = indexedDB.deleteDatabase(name); + retry.onsuccess = () => resolve(); + retry.onerror = () => + reject(new Error(`删除数据库 ${name} 失败`)); + }, 100); + }; + }), + ); + + await Promise.allSettled(deleteJobs); +}; + +export const resetAllStores = () => { + const userStore = useUserStore.getState(); + const appStore = useAppStore.getState(); + const settingsStore = useSettingsStore.getState(); + + userStore?.clearUser?.(); + appStore?.resetAppState?.(); + settingsStore?.resetSettings?.(); +}; + +export const clearApplicationCache = async () => { + clearBrowserStorage(); + await clearAllIndexedDB(); + resetAllStores(); +}; diff --git a/Moncter/src/utils/cacheCleaner.ts b/Moncter/src/utils/cacheCleaner.ts new file mode 100644 index 00000000..78f30c50 --- /dev/null +++ b/Moncter/src/utils/cacheCleaner.ts @@ -0,0 +1,73 @@ +// 缓存清理工具,统一处理浏览器存储与 Zustand store +import { clearAllPersistedData } from "@/store"; +import { useUserStore } from "@/store/module/user"; +import { useAppStore } from "@/store/module/app"; +import { useSettingsStore } from "@/store/module/settings"; + +const isBrowser = typeof window !== "undefined"; + +const safeStorageClear = (storage?: Storage) => { + if (!storage) return; + try { + storage.clear(); + } catch (error) { + console.warn("清理存储失败:", error); + } +}; + +export const clearBrowserStorage = () => { + if (!isBrowser) return; + safeStorageClear(window.localStorage); + safeStorageClear(window.sessionStorage); + // 清理自定义持久化数据 + try { + clearAllPersistedData(); + } catch (error) { + console.warn("清理持久化 store 失败:", error); + } +}; + +export const clearAllIndexedDB = async (): Promise => { + if (!isBrowser || !window.indexedDB || !indexedDB.databases) return; + + const databases = await indexedDB.databases(); + const deleteJobs = databases + .map(db => db.name) + .filter((name): name is string => Boolean(name)) + .map( + name => + new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(name); + request.onsuccess = () => resolve(); + request.onerror = () => + reject(new Error(`删除数据库 ${name} 失败`)); + request.onblocked = () => { + setTimeout(() => { + const retry = indexedDB.deleteDatabase(name); + retry.onsuccess = () => resolve(); + retry.onerror = () => + reject(new Error(`删除数据库 ${name} 失败`)); + }, 100); + }; + }), + ); + + await Promise.allSettled(deleteJobs); +}; + +export const resetAllStores = () => { + const userStore = useUserStore.getState(); + const appStore = useAppStore.getState(); + const settingsStore = useSettingsStore.getState(); + + userStore?.clearUser?.(); + appStore?.resetAppState?.(); + settingsStore?.resetSettings?.(); +}; + +export const clearApplicationCache = async () => { + clearBrowserStorage(); + await clearAllIndexedDB(); + resetAllStores(); +}; + diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts index 32bb9604..7e77433e 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts @@ -28,14 +28,30 @@ export function getTrafficPoolList() { "GET", ); } +type ListRequestOptions = { + debounceGap?: number; +}; + // 好友列表 -export function getContactList(params) { - return request("/v1/kefu/wechatFriend/list", params, "GET"); +export function getContactList(params, options?: ListRequestOptions) { + return request( + "/v1/kefu/wechatFriend/list", + params, + "GET", + undefined, + options?.debounceGap, + ); } // 群列表 -export function getGroupList(params) { - return request("/v1/kefu/wechatChatroom/list", params, "GET"); +export function getGroupList(params, options?: ListRequestOptions) { + return request( + "/v1/kefu/wechatChatroom/list", + params, + "GET", + undefined, + options?.debounceGap, + ); } // 分组列表 export function getLabelsListByGroup(params) { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss new file mode 100644 index 00000000..1d8603b0 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/RedPacketMessage.module.scss @@ -0,0 +1,160 @@ +// 红包消息样式 +.redPacketMessage { + background: transparent; + box-shadow: none; + max-width: 300px; +} + +.redPacketCard { + position: relative; + display: flex; + flex-direction: column; + padding: 16px 20px; + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); + border-radius: 8px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3); + overflow: hidden; + + // 红包装饰背景 + &::before { + content: ""; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient( + circle, + rgba(255, 215, 0, 0.15) 0%, + transparent 70% + ); + animation: shimmer 3s ease-in-out infinite; + } + + // 金色装饰边框 + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 2px solid rgba(255, 215, 0, 0.4); + border-radius: 8px; + pointer-events: none; + } + + &:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(255, 107, 107, 0.4); + background: linear-gradient(135deg, #ff7b7b 0%, #ff6b7f 100%); + } + + &:active { + transform: translateY(0); + } +} + +@keyframes shimmer { + 0%, + 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(180deg); + } +} + +.redPacketHeader { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; + position: relative; + z-index: 1; +} + +.redPacketIcon { + font-size: 32px; + line-height: 1; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); + animation: bounce 2s ease-in-out infinite; +} + +@keyframes bounce { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-4px); + } +} + +.redPacketTitle { + flex: 1; + font-size: 16px; + font-weight: 600; + color: #ffffff; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + letter-spacing: 0.5px; + line-height: 1.4; + word-break: break-word; +} + +.redPacketFooter { + display: flex; + align-items: center; + justify-content: flex-end; + position: relative; + z-index: 1; + padding-top: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.3); +} + +.redPacketLabel { + font-size: 12px; + color: rgba(255, 255, 255, 0.9); + font-weight: 500; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + + &::before { + content: "💰"; + margin-right: 4px; + font-size: 14px; + } +} + +// 消息文本样式(用于错误提示) +.messageText { + line-height: 1.4; + white-space: pre-wrap; + word-break: break-word; + color: #8c8c8c; + font-size: 13px; +} + +// 响应式设计 +@media (max-width: 768px) { + .redPacketMessage { + max-width: 200px; + } + + .redPacketCard { + padding: 12px 16px; + } + + .redPacketIcon { + font-size: 28px; + } + + .redPacketTitle { + font-size: 14px; + } + + .redPacketLabel { + font-size: 11px; + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx new file mode 100644 index 00000000..75bf86ff --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage/index.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import styles from "./RedPacketMessage.module.scss"; + +interface RedPacketData { + nativeurl?: string; + paymsgid?: string; + sendertitle?: string; + [key: string]: any; +} + +interface RedPacketMessageProps { + content: string; +} + +const RedPacketMessage: React.FC = ({ content }) => { + const renderErrorMessage = (fallbackText: string) => ( +
{fallbackText}
+ ); + + if (typeof content !== "string" || !content.trim()) { + return renderErrorMessage("[红包消息 - 无效内容]"); + } + + try { + const trimmedContent = content.trim(); + const jsonData: RedPacketData = JSON.parse(trimmedContent); + + // 验证是否为红包消息 + const isRedPacket = + jsonData.nativeurl && + typeof jsonData.nativeurl === "string" && + jsonData.nativeurl.includes( + "wxpay://c2cbizmessagehandler/hongbao/receivehongbao", + ); + + if (!isRedPacket) { + return renderErrorMessage("[红包消息 - 格式错误]"); + } + + const title = jsonData.sendertitle || "恭喜发财,大吉大利"; + const paymsgid = jsonData.paymsgid || ""; + + return ( +
+
+
+
🧧
+
{title}
+
+
+ 微信红包 +
+
+
+ ); + } catch (e) { + console.warn("红包消息解析失败:", e); + return renderErrorMessage("[红包消息 - 解析失败]"); + } +}; + +export default RedPacketMessage; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index bd5813d0..ccd4759b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -7,6 +7,7 @@ import VideoMessage from "./components/VideoMessage"; import ClickMenu from "./components/ClickMeau"; import LocationMessage from "./components/LocationMessage"; import SystemRecommendRemarkMessage from "./components/SystemRecommendRemarkMessage/index"; +import RedPacketMessage from "./components/RedPacketMessage"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { formatWechatTime } from "@/utils/common"; import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; @@ -254,6 +255,7 @@ const MessageRecord: React.FC = ({ contract }) => { msg?: ChatRecord, contract?: ContractData | weChatGroup, ) => { + console.log("红包"); if (isLegacyEmojiContent(trimmedContent)) { return renderEmojiContent(rawContent); } @@ -261,6 +263,17 @@ const MessageRecord: React.FC = ({ contract }) => { const jsonData = tryParseContentJson(trimmedContent); if (jsonData && typeof jsonData === "object") { + // 判断是否为红包消息 + if ( + jsonData.nativeurl && + typeof jsonData.nativeurl === "string" && + jsonData.nativeurl.includes( + "wxpay://c2cbizmessagehandler/hongbao/receivehongbao", + ) + ) { + return ; + } + if (jsonData.type === "file" && msg && contract) { return ( = ({ contract }) => { if (!msg) { return { avatar: "", nickname: "" }; } - const member = - groupRender.find(user => user?.identifier === msg?.sender?.wechatId) || - groupRender.find(user => user?.wechatId === msg?.sender?.wechatId); + + const member = groupRender.find( + user => user?.identifier === msg?.senderWechatId, + ); + console.log(member, "member"); return { - avatar: member?.avatar || msg?.sender?.avatar || "", - nickname: member?.nickname || msg?.sender?.nickname || "", + avatar: member?.avatar || msg?.avatar, + nickname: member?.nickname || msg?.senderNickname, }; }; @@ -615,7 +630,7 @@ const MessageRecord: React.FC = ({ contract }) => { const isOwn = msg?.isSend; const isGroup = !!contract.chatroomId; - const groupUser = isGroup ? renderGroupUser(msg) : null; + return (
= ({ contract }) => { )} } className={styles.messageAvatar} />
{!isOwn && (
- {groupUser?.nickname} + {renderGroupUser(msg)?.nickname}
)} <> diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/extend.ts b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/extend.ts index cf2b8117..fff567c7 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/extend.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/WechatFriends/extend.ts @@ -18,7 +18,7 @@ export const getAllFriends = async () => { let hasMore = true; while (hasMore) { - const result = await getContactList({ page, limit }); + const result = await getContactList({ page, limit }, { debounceGap: 0 }); const friendList = result?.list || []; if ( @@ -56,7 +56,7 @@ export const getAllGroups = async () => { let hasMore = true; while (hasMore) { - const result = await getGroupList({ page, limit }); + const result = await getGroupList({ page, limit }, { debounceGap: 0 }); const groupList = result?.list || []; if (!groupList || !Array.isArray(groupList) || groupList.length === 0) { diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts index 0d8f71d3..53610a63 100644 --- a/Touchkebao/src/utils/dbAction/message.ts +++ b/Touchkebao/src/utils/dbAction/message.ts @@ -659,7 +659,7 @@ export class MessageManager { updatedSession.sortKey = this.generateSortKey(updatedSession); await chatSessionService.update(serverId, updatedSession); - console.log(`会话时间已更新: ${serverId} -> ${newTime}`); + await this.triggerCallbacks(userId); } } catch (error) { console.error("更新会话时间失败:", error); @@ -830,7 +830,7 @@ export class MessageManager { }; await chatSessionService.create(sessionWithSortKey); - console.log(`创建新会话: ${session.nickname || session.wechatId}`); + await this.triggerCallbacks(userId); } catch (error) { console.error("创建会话失败:", error); throw error;