From 55fe2b46df831b9ddf6db167834d286905e2abcd Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Wed, 10 Dec 2025 17:56:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mobile/mine/wechat-accounts/detail/api.ts | 42 ++- .../wechat-accounts/detail/detail.module.scss | 183 +++++------ .../mine/wechat-accounts/detail/index.tsx | 289 +++++++++++++----- .../controller/wechat/PostTransferFriends.php | 2 +- 4 files changed, 351 insertions(+), 165 deletions(-) diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts index 10dfb60a..8c14f3e9 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts @@ -94,6 +94,20 @@ export async function exportWechatMoments(params: { } ); + // 检查响应类型,如果是JSON错误响应,需要解析错误信息 + const contentType = response.headers["content-type"] || ""; + if (contentType.includes("application/json")) { + // 如果是JSON响应,说明可能是错误信息 + const text = await response.data.text(); + const errorData = JSON.parse(text); + throw new Error(errorData.message || errorData.msg || "导出失败"); + } + + // 检查响应状态 + if (response.status !== 200) { + throw new Error(`导出失败,状态码: ${response.status}`); + } + // 创建下载链接 const blob = new Blob([response.data]); const url = window.URL.createObjectURL(blob); @@ -116,6 +130,32 @@ export async function exportWechatMoments(params: { document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (error: any) { - throw new Error(error.response?.data?.message || error.message || "导出失败"); + // 如果是我们抛出的错误,直接抛出 + if (error.message && error.message !== "导出失败") { + throw error; + } + + // 处理axios错误响应 + if (error.response) { + // 如果响应是blob类型,尝试读取为文本 + if (error.response.data instanceof Blob) { + try { + const text = await error.response.data.text(); + const errorData = JSON.parse(text); + throw new Error(errorData.message || errorData.msg || "导出失败"); + } catch (parseError) { + throw new Error("导出失败,请重试"); + } + } else { + throw new Error( + error.response.data?.message || + error.response.data?.msg || + error.message || + "导出失败" + ); + } + } else { + throw new Error(error.message || "导出失败"); + } } } 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 b06f237d..95c33d8a 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 @@ -373,6 +373,12 @@ font-weight: 600; color: #52c41a; } + + .stat-value-negative { + font-size: 20px; + font-weight: 600; + color: #ff4d4f; + } } } @@ -868,9 +874,10 @@ .type-selector { display: flex; gap: 8px; - flex-wrap: wrap; + width: 100%; .type-option { + flex: 1; padding: 8px 16px; border: 1px solid #e0e0e0; border-radius: 8px; @@ -879,6 +886,7 @@ cursor: pointer; transition: all 0.2s; background: white; + text-align: center; &:hover { border-color: #1677ff; @@ -1331,82 +1339,54 @@ } } +.moments-action-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: white; + border-bottom: 1px solid #f0f0f0; + + .action-button, .action-button-dark { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 8px; + background: #1677ff; + border: none; + cursor: pointer; + transition: all 0.2s; + color: white; + + &:active { + background: #0958d9; + transform: scale(0.95); + } + + svg { + font-size: 20px; + color: white; + } + } + + .action-button-dark { + background: #1677ff; + color: white; + + &:active { + background: #0958d9; + } + } +} + .moments-content { padding: 16px 0; height: 500px; overflow-y: auto; background: #f5f5f5; - .moments-action-bar { - display: flex; - justify-content: space-between; - padding: 0 16px 16px; - - .action-button, .action-button-dark { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 70px; - height: 40px; - border-radius: 8px; - background: #1677ff; - - .action-icon-text, .action-icon-image, .action-icon-video, .action-icon-export { - width: 20px; - height: 20px; - background: rgba(255, 255, 255, 0.2); - border-radius: 4px; - margin-bottom: 2px; - position: relative; - - &::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 12px; - height: 2px; - background: white; - } - } - - .action-icon-image::after { - content: ''; - position: absolute; - top: 6px; - left: 6px; - width: 8px; - height: 8px; - border-radius: 2px; - background: white; - } - - .action-icon-video::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 0; - height: 0; - border-style: solid; - border-width: 5px 0 5px 8px; - border-color: transparent transparent transparent white; - } - - .action-text, .action-text-light { - font-size: 12px; - color: white; - } - } - - .action-button-dark { - background: #333; - } - } - .moments-list { padding: 0 16px; @@ -1459,7 +1439,7 @@ .image-grid { display: grid; - gap: 8px; + gap: 4px; width: 100%; // 1张图片:宽度拉伸,高度自适应 @@ -1469,56 +1449,57 @@ img { width: 100%; height: auto; + max-height: 400px; object-fit: cover; - border-radius: 8px; + border-radius: 4px; } } - // 2张图片:左右并列 + // 2张图片:左右并列,1:1比例 &.double { grid-template-columns: 1fr 1fr; img { width: 100%; - height: 120px; + aspect-ratio: 1 / 1; object-fit: cover; - border-radius: 8px; + border-radius: 4px; } } - // 3张图片:三张并列 + // 3张图片:三张并列,1:1比例 &.triple { grid-template-columns: 1fr 1fr 1fr; img { width: 100%; - height: 100px; + aspect-ratio: 1 / 1; object-fit: cover; - border-radius: 8px; + border-radius: 4px; } } - // 4张图片:2x2网格布局 + // 4张图片:2x2网格布局,1:1比例 &.quad { grid-template-columns: repeat(2, 1fr); img { width: 100%; - height: 140px; + aspect-ratio: 1 / 1; object-fit: cover; - border-radius: 8px; + border-radius: 4px; } } - // 5张及以上:网格布局(9宫格) + // 5张及以上:网格布局(9宫格),1:1比例 &.grid { grid-template-columns: repeat(3, 1fr); img { width: 100%; - height: 100px; + aspect-ratio: 1 / 1; object-fit: cover; - border-radius: 8px; + border-radius: 4px; } .image-more { @@ -1526,11 +1507,11 @@ align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.5); - border-radius: 8px; + border-radius: 4px; color: white; font-size: 12px; font-weight: 500; - height: 100px; + aspect-ratio: 1 / 1; } } } @@ -1547,6 +1528,34 @@ } } } + + .moments-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 16px 0; + } + + .moments-no-more { + display: flex; + align-items: center; + justify-content: center; + padding: 16px 0; + } + } + + .friends-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 16px 0; + } + + .friends-no-more { + display: flex; + align-items: center; + justify-content: center; + padding: 16px 0; } } 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 1f6e9e28..ae694b3a 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -12,13 +12,18 @@ import { Tag, Switch, DatePicker, + InfiniteScroll, } from "antd-mobile"; -import { Input, Pagination } from "antd"; +import { Input } from "antd"; import NavCommon from "@/components/NavCommon"; import { SearchOutlined, ReloadOutlined, UserOutlined, + FileTextOutlined, + PictureOutlined, + VideoCameraOutlined, + DownloadOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import style from "./detail.module.scss"; @@ -32,6 +37,7 @@ import { } from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; +import dayjs from "dayjs"; import { WechatAccountSummary, Friend, MomentItem } from "./data"; @@ -107,6 +113,84 @@ const WechatAccountDetail: React.FC = () => { } }, [id]); + // 计算账号价值 + // 规则: + // 1. 1个好友3块 + // 2. 1个群1块 + // 3. 修改过微信号10块 + const calculateAccountValue = useCallback(() => { + // 获取好友数量(优先使用概览数据,其次使用好友列表总数,最后使用账号信息) + const friendsCount = overviewData?.totalFriends || friendsTotal || accountInfo?.friendShip?.totalFriend || 0; + + // 获取群数量(优先使用概览数据,其次使用账号信息) + const groupsCount = overviewData?.highValueChatrooms || accountInfo?.friendShip?.groupNumber || 0; + + // 判断是否修改过微信号 + // 注意:需要根据实际API返回的字段来判断,可能的字段名: + // - isWechatIdModified (布尔值) + // - wechatIdModified (布尔值) + // - hasModifiedWechatId (布尔值) + // - wechatIdChangeCount (数字,大于0表示修改过) + // 如果API没有返回该字段,需要后端添加或根据其他逻辑判断 + const isWechatIdModified = + accountInfo?.isWechatIdModified || + accountInfo?.wechatIdModified || + accountInfo?.hasModifiedWechatId || + (accountInfo?.wechatIdChangeCount && accountInfo.wechatIdChangeCount > 0) || + false; + + // 计算各部分价值 + const friendsValue = friendsCount * 3; // 好友数 * 3 + const groupsValue = groupsCount * 1; // 群数 * 1 + const wechatIdModifiedValue = isWechatIdModified ? 10 : 0; // 修改过微信号 ? 10 : 0 + + // 计算总价值 + const totalValue = friendsValue + groupsValue + wechatIdModifiedValue; + + return { + value: totalValue, + formatted: `¥${totalValue.toLocaleString()}`, + breakdown: { + friends: friendsValue, + groups: groupsValue, + wechatIdModified: wechatIdModifiedValue, + friendsCount, + groupsCount, + isWechatIdModified, + }, + }; + }, [overviewData, friendsTotal, accountInfo]); + + // 计算今日价值变化 + // 规则: + // 1. 今日新增好友 * 3块 + // 2. 今日新增群 * 1块 + const calculateTodayValueChange = useCallback(() => { + // 获取今日新增好友数 + const todayNewFriends = overviewData?.todayNewFriends || accountSummary?.statistics?.todayAdded || 0; + + // 获取今日新增群数 + const todayNewChatrooms = overviewData?.todayNewChatrooms || 0; + + // 计算今日价值变化 + const friendsValueChange = todayNewFriends * 3; // 今日新增好友数 * 3 + const groupsValueChange = todayNewChatrooms * 1; // 今日新增群数 * 1 + + const totalChange = friendsValueChange + groupsValueChange; + + return { + change: totalChange, + formatted: totalChange >= 0 ? `+${totalChange.toLocaleString()}` : `${totalChange.toLocaleString()}`, + isPositive: totalChange >= 0, + breakdown: { + friends: friendsValueChange, + groups: groupsValueChange, + todayNewFriends, + todayNewChatrooms, + }, + }; + }, [overviewData, accountSummary]); + // 获取概览数据 const fetchOverviewData = useCallback(async () => { if (!id) return; @@ -122,7 +206,7 @@ const WechatAccountDetail: React.FC = () => { // 获取好友列表 - 封装为独立函数 const fetchFriendsList = useCallback( - async (page: number = 1, keyword: string = "") => { + async (page: number = 1, keyword: string = "", append: boolean = false) => { if (!id) return; setIsFetchingFriends(true); @@ -132,7 +216,7 @@ const WechatAccountDetail: React.FC = () => { const response = await getWechatFriends({ wechatAccount: id, page: page, - limit: 5, + limit: 20, keyword: keyword, }); @@ -175,15 +259,17 @@ const WechatAccountDetail: React.FC = () => { }; }); - setFriends(newFriends); + setFriends(prev => (append ? [...prev, ...newFriends] : newFriends)); setFriendsTotal(response.total); setFriendsPage(page); - setIsFriendsEmpty(newFriends.length === 0); + setIsFriendsEmpty(newFriends.length === 0 && !append); } catch (error) { console.error("获取好友列表失败:", error); setHasFriendLoadError(true); - setFriends([]); - setIsFriendsEmpty(true); + if (!append) { + setFriends([]); + setIsFriendsEmpty(true); + } Toast.show({ content: "获取好友列表失败,请检查网络连接", position: "top", @@ -238,22 +324,20 @@ const WechatAccountDetail: React.FC = () => { // 搜索好友 const handleSearch = useCallback(() => { setFriendsPage(1); - fetchFriendsList(1, searchQuery); + fetchFriendsList(1, searchQuery, false); }, [searchQuery, fetchFriendsList]); // 刷新好友列表 const handleRefreshFriends = useCallback(() => { - fetchFriendsList(friendsPage, searchQuery); + fetchFriendsList(friendsPage, searchQuery, false); }, [friendsPage, searchQuery, fetchFriendsList]); - // 分页切换 - const handlePageChange = useCallback( - (page: number) => { - setFriendsPage(page); - fetchFriendsList(page, searchQuery); - }, - [searchQuery, fetchFriendsList], - ); + // 加载更多好友 + const handleLoadMoreFriends = async () => { + if (isFetchingFriends) return; + if (friends.length >= friendsTotal) return; + await fetchFriendsList(friendsPage + 1, searchQuery, true); + }; // 初始化数据 useEffect(() => { @@ -268,7 +352,7 @@ const WechatAccountDetail: React.FC = () => { if (activeTab === "friends" && id) { setIsFriendsEmpty(false); setHasFriendLoadError(false); - fetchFriendsList(1, searchQuery); + fetchFriendsList(1, searchQuery, false); } }, [activeTab, id, fetchFriendsList, searchQuery]); @@ -298,8 +382,8 @@ const WechatAccountDetail: React.FC = () => { setInheritInfo(true); // 设置默认打招呼内容,使用当前微信账号昵称 const nickname = accountInfo?.nickname || "未知"; - setGreeting(`这个是${nickname}的新号,之前那个号没用了,重新加一下您`); - setFirstMessage(""); + setGreeting(`我是${nickname}的新号,请通过`); + setFirstMessage("这个是我的新号,重新加你一下,以后业务就用这个号!"); setShowTransferConfirm(true); }; @@ -385,10 +469,10 @@ const WechatAccountDetail: React.FC = () => { navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`); }; - const handleLoadMoreMoments = () => { + const handleLoadMoreMoments = async () => { if (isFetchingMoments) return; if (moments.length >= momentsTotal) return; - fetchMomentsList(momentsPage + 1, true); + await fetchMomentsList(momentsPage + 1, true); }; // 处理朋友圈导出 @@ -398,6 +482,18 @@ const WechatAccountDetail: React.FC = () => { return; } + // 验证时间范围不超过1个月 + if (exportStartTime && exportEndTime) { + const maxDate = dayjs(exportStartTime).add(1, "month").toDate(); + if (exportEndTime > maxDate) { + Toast.show({ + content: "日期范围不能超过1个月", + position: "top", + }); + return; + } + } + setExportLoading(true); try { // 格式化时间 @@ -418,18 +514,27 @@ const WechatAccountDetail: React.FC = () => { }); Toast.show({ content: "导出成功", position: "top" }); - setShowExportPopup(false); - // 重置筛选条件 + // 重置筛选条件(先重置,再关闭弹窗) setExportKeyword(""); setExportType(undefined); setExportStartTime(null); setExportEndTime(null); + setShowStartTimePicker(false); + setShowEndTimePicker(false); + // 延迟关闭弹窗,确保Toast显示 + setTimeout(() => { + setShowExportPopup(false); + }, 500); } catch (error: any) { console.error("导出失败:", error); + const errorMessage = error?.message || "导出失败,请重试"; Toast.show({ - content: error.message || "导出失败,请重试", + content: errorMessage, position: "top", + duration: 2000, }); + // 确保loading状态被重置 + setExportLoading(false); } finally { setExportLoading(false); } @@ -548,7 +653,7 @@ const WechatAccountDetail: React.FC = () => {