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/pages/mobile/mine/wechat-accounts/detail/detail.module.scss b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss index 354503b6..954921fc 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss @@ -1050,6 +1050,101 @@ height: 500px; overflow-y: auto; + // 健康分评估区域 + .health-score-section { + background: #ffffff; + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + + .health-score-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 12px; + } + + .health-score-info { + .health-score-status { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + + .status-tag { + background: #ffebeb; + color: #ff4d4f; + font-size: 12px; + padding: 2px 8px; + border-radius: 4px; + } + + .status-time { + font-size: 12px; + color: #999; + } + } + + .health-score-display { + display: flex; + align-items: center; + + .score-circle-wrapper { + width: 100px; + height: 100px; + margin-right: 24px; + position: relative; + + .score-circle { + width: 100%; + height: 100%; + border-radius: 50%; + background: #fff; + border: 8px solid #ff4d4f; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .score-number { + font-size: 28px; + font-weight: 700; + color: #ff4d4f; + line-height: 1; + } + + .score-label { + font-size: 12px; + color: #999; + margin-top: 4px; + } + } + } + + .health-score-stats { + flex: 1; + + .stats-row { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + + .stats-label { + font-size: 14px; + color: #666; + } + + .stats-value { + font-size: 14px; + color: #333; + font-weight: 500; + } + } + } + } + } + } + .health-score-card { background: #ffffff; border-radius: 12px; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index a5a3b29d..333eb353 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -11,6 +11,7 @@ import { Avatar, Tag, Switch, + DatePicker, } from "antd-mobile"; import { Input, Pagination } from "antd"; import NavCommon from "@/components/NavCommon"; @@ -25,11 +26,14 @@ import { getWechatAccountDetail, getWechatFriends, transferWechatFriends, + getWechatAccountOverview, + getWechatMoments, + exportWechatMoments, } from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; -import { WechatAccountSummary, Friend } from "./data"; +import { WechatAccountSummary, Friend, MomentItem } from "./data"; const WechatAccountDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); @@ -38,11 +42,10 @@ const WechatAccountDetail: React.FC = () => { const [accountSummary, setAccountSummary] = useState(null); const [accountInfo, setAccountInfo] = useState(null); + const [overviewData, setOverviewData] = useState(null); const [showRestrictions, setShowRestrictions] = useState(false); const [showTransferConfirm, setShowTransferConfirm] = useState(false); - const [selectedDevices, setSelectedDevices] = useState( - [], - ); + const [selectedDevices, setSelectedDevices] = useState([]); const [inheritInfo, setInheritInfo] = useState(true); const [transferLoading, setTransferLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); @@ -56,6 +59,22 @@ const WechatAccountDetail: React.FC = () => { const [isFetchingFriends, setIsFetchingFriends] = useState(false); const [hasFriendLoadError, setHasFriendLoadError] = useState(false); const [isFriendsEmpty, setIsFriendsEmpty] = useState(false); + const [moments, setMoments] = useState([]); + const [momentsPage, setMomentsPage] = useState(1); + const [momentsTotal, setMomentsTotal] = useState(0); + const [isFetchingMoments, setIsFetchingMoments] = useState(false); + const [momentsError, setMomentsError] = useState(null); + const MOMENTS_LIMIT = 10; + + // 导出相关状态 + const [showExportPopup, setShowExportPopup] = useState(false); + const [exportKeyword, setExportKeyword] = useState(""); + const [exportType, setExportType] = useState(undefined); + const [exportStartTime, setExportStartTime] = useState(null); + const [exportEndTime, setExportEndTime] = useState(null); + const [showStartTimePicker, setShowStartTimePicker] = useState(false); + const [showEndTimePicker, setShowEndTimePicker] = useState(false); + const [exportLoading, setExportLoading] = useState(false); // 获取基础信息 const fetchAccountInfo = useCallback(async () => { @@ -86,6 +105,19 @@ const WechatAccountDetail: React.FC = () => { } }, [id]); + // 获取概览数据 + const fetchOverviewData = useCallback(async () => { + if (!id) return; + try { + const response = await getWechatAccountOverview(id); + if (response) { + setOverviewData(response); + } + } catch (e) { + console.error("获取概览数据失败:", e); + } + }, [id]); + // 获取好友列表 - 封装为独立函数 const fetchFriendsList = useCallback( async (page: number = 1, keyword: string = "") => { @@ -102,26 +134,44 @@ const WechatAccountDetail: React.FC = () => { keyword: keyword, }); - const newFriends = response.list.map((friend: any) => ({ - id: friend.id.toString(), - avatar: friend.avatar || "/placeholder.svg", - nickname: friend.nickname || "未知用户", - wechatId: friend.wechatId || "", - remark: friend.memo || "", - addTime: friend.createTime || new Date().toISOString().split("T")[0], - lastInteraction: - friend.lastInteraction || new Date().toISOString().split("T")[0], - tags: friend.tags - ? friend.tags.map((tag: string, index: number) => ({ - id: `tag-${index}`, - name: tag, - color: getRandomTagColor(), - })) - : [], - region: friend.region || "未知", - source: friend.source || "未知", - notes: friend.notes || "", - })); + const newFriends = response.list.map((friend: any) => { + const memoTags = Array.isArray(friend.memo) + ? friend.memo + : friend.memo + ? String(friend.memo) + .split(/[,\s,、]+/) + .filter(Boolean) + : []; + + const tagList = Array.isArray(friend.tags) + ? friend.tags + : friend.tags + ? [friend.tags] + : []; + + return { + id: friend.id.toString(), + avatar: friend.avatar || "/placeholder.svg", + nickname: friend.nickname || "未知用户", + wechatId: friend.wechatId || "", + remark: friend.notes || "", + addTime: + friend.createTime || new Date().toISOString().split("T")[0], + lastInteraction: + friend.lastInteraction || new Date().toISOString().split("T")[0], + tags: memoTags.map((tag: string, index: number) => ({ + id: `tag-${index}`, + name: tag, + color: getRandomTagColor(), + })), + statusTags: tagList, + region: friend.region || "未知", + source: friend.source || "未知", + notes: friend.notes || "", + value: friend.value, + valueFormatted: friend.valueFormatted, + }; + }); setFriends(newFriends); setFriendsTotal(response.total); @@ -143,6 +193,46 @@ const WechatAccountDetail: React.FC = () => { [id], ); + const fetchMomentsList = useCallback( + async (page: number = 1, append: boolean = false) => { + if (!id) return; + setIsFetchingMoments(true); + setMomentsError(null); + try { + const response = await getWechatMoments({ + wechatId: id, + page, + limit: MOMENTS_LIMIT, + }); + + const list: MomentItem[] = (response.list || []).map((moment: any) => ({ + id: moment.id?.toString() || Math.random().toString(), + snsId: moment.snsId, + type: moment.type, + content: moment.content || "", + resUrls: moment.resUrls || [], + commentList: moment.commentList || [], + likeList: moment.likeList || [], + createTime: moment.createTime || "", + momentEntity: moment.momentEntity || {}, + })); + + setMoments(prev => (append ? [...prev, ...list] : list)); + setMomentsTotal(response.total || list.length); + setMomentsPage(page); + } catch (error) { + console.error("获取朋友圈数据失败:", error); + setMomentsError("获取朋友圈数据失败"); + if (!append) { + setMoments([]); + } + } finally { + setIsFetchingMoments(false); + } + }, + [id], + ); + // 搜索好友 const handleSearch = useCallback(() => { setFriendsPage(1); @@ -167,8 +257,9 @@ const WechatAccountDetail: React.FC = () => { useEffect(() => { if (id) { fetchAccountInfo(); + fetchOverviewData(); } - }, [id, fetchAccountInfo]); + }, [id, fetchAccountInfo, fetchOverviewData]); // 监听标签切换 - 只在切换到好友列表时请求一次 useEffect(() => { @@ -179,6 +270,14 @@ const WechatAccountDetail: React.FC = () => { } }, [activeTab, id, fetchFriendsList, searchQuery]); + useEffect(() => { + if (activeTab === "moments" && id) { + if (moments.length === 0) { + fetchMomentsList(1, false); + } + } + }, [activeTab, id, fetchMomentsList, moments.length]); + // 工具函数 const getRandomTagColor = (): string => { const colors = [ @@ -222,7 +321,7 @@ const WechatAccountDetail: React.FC = () => { await transferWechatFriends({ wechatId: id, devices: selectedDevices.map(device => device.id), - inherit: inheritInfo, + inherit: inheritInfo }); Toast.show({ @@ -277,6 +376,85 @@ const WechatAccountDetail: React.FC = () => { navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`); }; + const handleLoadMoreMoments = () => { + if (isFetchingMoments) return; + if (moments.length >= momentsTotal) return; + fetchMomentsList(momentsPage + 1, true); + }; + + // 处理朋友圈导出 + const handleExportMoments = useCallback(async () => { + if (!id) { + Toast.show({ content: "微信ID不存在", position: "top" }); + return; + } + + setExportLoading(true); + try { + // 格式化时间 + const formatDate = (date: Date | null): string | undefined => { + if (!date) return undefined; + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; + }; + + await exportWechatMoments({ + wechatId: id, + keyword: exportKeyword || undefined, + type: exportType, + startTime: formatDate(exportStartTime), + endTime: formatDate(exportEndTime), + }); + + Toast.show({ content: "导出成功", position: "top" }); + setShowExportPopup(false); + // 重置筛选条件 + setExportKeyword(""); + setExportType(undefined); + setExportStartTime(null); + setExportEndTime(null); + } catch (error: any) { + console.error("导出失败:", error); + Toast.show({ + content: error.message || "导出失败,请重试", + position: "top", + }); + } finally { + setExportLoading(false); + } + }, [id, exportKeyword, exportType, exportStartTime, exportEndTime]); + + const formatMomentDateParts = (dateString: string) => { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) { + return { day: "--", month: "--" }; + } + const day = date.getDate().toString().padStart(2, "0"); + const month = `${date.getMonth() + 1}月`; + return { day, month }; + }; + + const formatMomentTimeAgo = (dateString: string) => { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) { + return dateString || "--"; + } + const diff = Date.now() - date.getTime(); + const minutes = Math.floor(diff / (1000 * 60)); + if (minutes < 1) return "刚刚"; + if (minutes < 60) return `${minutes}分钟前`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}小时前`; + const days = Math.floor(hours / 24); + if (days < 7) return `${days}天前`; + return date.toLocaleDateString("zh-CN", { + month: "2-digit", + day: "2-digit", + }); + }; + return ( } loading={loadingInfo}>
@@ -319,73 +497,223 @@ const WechatAccountDetail: React.FC = () => { onChange={handleTabChange} className={style["tabs"]} > - +
-
-
-
- {accountInfo?.friendShip?.totalFriend ?? "-"} + {/* 健康分评估区域 */} +
+
健康分评估
+
+
+ {overviewData?.healthScoreAssessment?.statusTag || "已添加加人"} + 最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "18:44:14"}
-
好友数量
-
-
-
- +{accountSummary?.statistics.todayAdded ?? "-"} +
+
+
+
+ {overviewData?.healthScoreAssessment?.score || 67} +
+
SCORE
+
+
+
+
+
每日限额
+
{overviewData?.healthScoreAssessment?.dailyLimit || 0} 人
+
+
+
今日已加
+
{overviewData?.healthScoreAssessment?.todayAdded || 0} 人
+
+
-
今日新增
-
- 今日可添加: - - {accountSummary?.statistics.todayAdded ?? 0}/ - {accountSummary?.statistics.addLimit ?? 0} - -
-
-
-
-
-
-
-
-
- {accountInfo?.friendShip?.groupNumber ?? "-"} + + {/* 账号价值和好友数量区域 */} +
+ {/* 账号价值 */} +
+
+
账号价值
+
-
群聊数量
-
-
-
- {accountInfo?.activity?.yesterdayMsgCount ?? "-"} +
+ {overviewData?.accountValue?.formatted || `¥${overviewData?.accountValue?.value || "29,800"}`} +
+
+ + {/* 今日价值变化 */} +
+
+
今日价值变化
+
+
+
+ {overviewData?.todayValueChange?.formatted || `+${overviewData?.todayValueChange?.change || "500"}`}
-
今日消息
-
-
设备信息
-
- 设备名称: - {accountInfo?.deviceName ?? "-"} + + {/* 好友数量和今日新增好友区域 */} +
+ {/* 好友总数 */} +
+
+
好友总数
+
+
+
+ {overviewData?.totalFriends || accountInfo?.friendShip?.totalFriend || "0"} +
-
- 系统类型: - {accountInfo?.deviceType ?? "-"} + + {/* 今日新增好友 */} +
+
+
今日新增好友
+
+
+
+ +{overviewData?.todayNewFriends || accountSummary?.statistics.todayAdded || "0"} +
-
- 系统版本: - {accountInfo?.deviceVersion ?? "-"} +
+ + {/* 高价群聊区域 */} +
+ {/* 高价群聊 */} +
+
+
高价群聊
+
+
+
+ {overviewData?.highValueChatrooms || accountInfo?.friendShip?.groupNumber || "0"} +
+ + {/* 今日新增群聊 */} +
+
+
今日新增群聊
+
+
+
+ +{overviewData?.todayNewChatrooms || "0"} +
+
+
+ + +
+ + + +
+ {/* 健康分评估区域 */} +
+
健康分评估
+
+
+ {overviewData?.healthScoreAssessment?.statusTag || "已添加加人"} + 最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "18:44:14"} +
+
+
+
+
+ {overviewData?.healthScoreAssessment?.score || 67} +
+
SCORE
+
+
+
+
+
每日限额
+
{overviewData?.healthScoreAssessment?.dailyLimit || 0} 人
+
+
+
今日已加
+
{overviewData?.healthScoreAssessment?.todayAdded || 0} 人
+
+
+
+
+
+ + {/* 基础构成 */} +
+
基础构成
+ {(overviewData?.healthScoreAssessment?.baseComposition && + overviewData.healthScoreAssessment.baseComposition.length > 0 + ? overviewData.healthScoreAssessment.baseComposition + : [ + { name: "账号基础分", formatted: "+60" }, + { name: "已修改微信号", formatted: "+10" }, + { name: "好友数量加成", formatted: "+12", friendCount: 5595 }, + ] + ).map((item, index) => ( +
+
+ {item.name} + {item.friendCount ? ` (${item.friendCount})` : ""} +
+
= 0 + ? style["health-item-value-positive"] + : style["health-item-value-negative"] + } + > + {item.formatted || `${item.score ?? 0}`} +
+
+ ))} +
+ + {/* 动态记录 */} +
+
动态记录
+ {overviewData?.healthScoreAssessment?.dynamicRecords && + overviewData.healthScoreAssessment.dynamicRecords.length > 0 ? ( + overviewData.healthScoreAssessment.dynamicRecords.map( + (record, index) => ( +
+
+ + {record.title || record.description || "记录"} + {record.statusTag && ( + + {record.statusTag} + + )} +
+
= 0 + ? style["health-item-value-positive"] + : style["health-item-value-negative"] + } + > + {record.formatted || + (record.score && record.score > 0 + ? `+${record.score}` + : record.score || "-")} +
+
+ ), + ) + ) : ( +
暂无动态记录
+ )}
+ 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`} + title={`好友${activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`} key="friends" >
@@ -412,6 +740,23 @@ const WechatAccountDetail: React.FC = () => {
+ {/* 好友概要 */} +
+
+
好友总数
+
+ {friendsTotal || overviewData?.totalFriends || 0} +
+
+
+
+
好友总估值
+
+ {overviewData?.accountValue?.formatted || "¥1,500,000"} +
+
+
+ {/* 好友列表 */}
{isFetchingFriends && friends.length === 0 ? ( @@ -437,36 +782,44 @@ const WechatAccountDetail: React.FC = () => { {friends.map(friend => (
handleFriendClick(friend)} > - -
-
+
+ +
+
+
- {friend.nickname} - {friend.remark && ( - - ({friend.remark}) - - )} + {friend.nickname || "未知好友"}
+
-
- {friend.wechatId} +
+ ID: {friend.wechatId || "-"}
-
- {friend.tags?.map((tag, index) => ( - + {friend.statusTags?.map((tag, idx) => ( + - {typeof tag === "string" ? tag : tag.name} - + {tag} + ))} + {friend.remark && ( + + {friend.remark} + + )} +
+
+
+
+ {friend.valueFormatted + || (typeof friend.value === "number" + ? `¥${friend.value.toLocaleString()}` + : "估值 -")}
@@ -490,45 +843,118 @@ const WechatAccountDetail: React.FC = () => {
- -
- {accountSummary?.restrictions && - accountSummary.restrictions.length > 0 ? ( -
- {accountSummary.restrictions.map(restriction => ( -
-
-
- {restriction.reason} -
-
- {restriction.date - ? formatDateTime(restriction.date) - : "暂无时间"} -
-
-
- - {restriction.level === 1 - ? "低风险" - : restriction.level === 2 - ? "中风险" - : "高风险"} - -
-
- ))} + + +
+ {/* 功能按钮栏 */} +
+
+ + 文本 +
+
+ + 图片 +
+
+ + 视频 +
+
setShowExportPopup(true)} + > + + 导出 +
+
+ + {/* 朋友圈列表 */} +
+ {isFetchingMoments && moments.length === 0 ? ( +
+ +
+ ) : momentsError ? ( +
{momentsError}
+ ) : moments.length === 0 ? ( +
暂无朋友圈内容
+ ) : ( + moments.map(moment => { + const { day, month } = formatMomentDateParts( + moment.createTime, + ); + const timeAgo = formatMomentTimeAgo(moment.createTime); + const imageCount = moment.resUrls?.length || 0; + // 根据图片数量选择对应的grid类,参考素材管理的实现 + let gridClass = ""; + if (imageCount === 1) gridClass = style["single"]; + else if (imageCount === 2) gridClass = style["double"]; + else if (imageCount === 3) gridClass = style["triple"]; + else if (imageCount === 4) gridClass = style["quad"]; + else if (imageCount > 4) gridClass = style["grid"]; + + return ( +
+
+
{day}
+
{month}
+
+
+ {moment.content && ( +
+ {moment.content} +
+ )} + {imageCount > 0 && ( +
+
+ {moment.resUrls + .slice(0, 9) + .map((url, index) => ( + 朋友圈图片 + ))} + {imageCount > 9 && ( +
+ +{imageCount - 9} +
+ )} +
+
+ )} +
+ + {timeAgo} + +
+
+
+ ); + }) + )} +
+ + {moments.length < momentsTotal && ( +
+
- ) : ( -
暂无风险记录
)}
+
@@ -614,7 +1040,10 @@ const WechatAccountDetail: React.FC = () => {
同步原有信息
- + {inheritInfo ? "是" : "否"} @@ -647,6 +1076,153 @@ const WechatAccountDetail: React.FC = () => {
+ {/* 朋友圈导出弹窗 */} + setShowExportPopup(false)} + bodyStyle={{ borderRadius: "16px 16px 0 0" }} + > +
+
+

导出朋友圈

+ +
+ +
+ {/* 关键词搜索 */} +
+ + setExportKeyword(e.target.value)} + allowClear + /> +
+ + {/* 类型筛选 */} +
+ +
+
setExportType(undefined)} + > + 全部 +
+
setExportType(4)} + > + 文本 +
+
setExportType(1)} + > + 图片 +
+
setExportType(3)} + > + 视频 +
+
+
+ + {/* 开始时间 */} +
+ + setShowStartTimePicker(true)} + /> + setShowStartTimePicker(false)} + onConfirm={val => { + setExportStartTime(val); + setShowStartTimePicker(false); + }} + /> +
+ + {/* 结束时间 */} +
+ + setShowEndTimePicker(true)} + /> + setShowEndTimePicker(false)} + onConfirm={val => { + setExportEndTime(val); + setShowEndTimePicker(false); + }} + /> +
+
+ +
+ + +
+
+
+ {/* 好友详情弹窗 */} {/* Removed */} diff --git a/Cunkebao/src/store/index.ts b/Cunkebao/src/store/index.ts index 7b74a38e..14263a67 100644 --- a/Cunkebao/src/store/index.ts +++ b/Cunkebao/src/store/index.ts @@ -2,13 +2,11 @@ export * from "./module/user"; export * from "./module/app"; export * from "./module/settings"; -export * from "./module/websocket/websocket"; // 导入store实例 import { useUserStore } from "./module/user"; import { useAppStore } from "./module/app"; import { useSettingsStore } from "./module/settings"; -import { useWebSocketStore } from "./module/websocket/websocket"; // 导出持久化store创建函数 export { @@ -34,7 +32,6 @@ export interface StoreState { user: ReturnType; app: ReturnType; settings: ReturnType; - websocket: ReturnType; } // 便利的store访问函数 @@ -42,14 +39,12 @@ export const getStores = (): StoreState => ({ user: useUserStore.getState(), app: useAppStore.getState(), settings: useSettingsStore.getState(), - websocket: useWebSocketStore.getState(), }); // 获取特定store状态 export const getUserStore = () => useUserStore.getState(); export const getAppStore = () => useAppStore.getState(); export const getSettingsStore = () => useSettingsStore.getState(); -export const getWebSocketStore = () => useWebSocketStore.getState(); // 清除所有持久化数据(使用工具函数) export const clearAllPersistedData = clearAllData; @@ -61,7 +56,6 @@ export const getPersistKeys = () => Object.values(PERSIST_KEYS); export const subscribeToUserStore = useUserStore.subscribe; export const subscribeToAppStore = useAppStore.subscribe; export const subscribeToSettingsStore = useSettingsStore.subscribe; -export const subscribeToWebSocketStore = useWebSocketStore.subscribe; // 组合订阅函数 export const subscribeToAllStores = (callback: (state: StoreState) => void) => { @@ -74,14 +68,10 @@ export const subscribeToAllStores = (callback: (state: StoreState) => void) => { const unsubscribeSettings = useSettingsStore.subscribe(() => { callback(getStores()); }); - const unsubscribeWebSocket = useWebSocketStore.subscribe(() => { - callback(getStores()); - }); return () => { unsubscribeUser(); unsubscribeApp(); unsubscribeSettings(); - unsubscribeWebSocket(); }; }; 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/Server/application/cunkebao/controller/wechat/PostTransferFriends.php b/Server/application/cunkebao/controller/wechat/PostTransferFriends.php index 1839231a..6bb0007a 100644 --- a/Server/application/cunkebao/controller/wechat/PostTransferFriends.php +++ b/Server/application/cunkebao/controller/wechat/PostTransferFriends.php @@ -74,7 +74,7 @@ class PostTransferFriends extends BaseController $taskId = Db::name('customer_acquisition_task')->insertGetId([ 'name' => '迁移好友('. $wechat['nickname'] .')', - 'sceneId' => 1, + 'sceneId' => 10, 'sceneConf' => json_encode($sceneConf), 'reqConf' => json_encode($reqConf), 'tagConf' => json_encode([]), 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;