feat(朋友圈): 实现朋友圈点赞功能并优化组件结构
重构朋友圈组件结构,将核心逻辑拆分为独立组件。主要变更包括: 1. 新增点赞功能及相关API接口 2. 修改snsId类型从number到string以兼容接口 3. 在store中新增updateLikeMoment方法处理点赞状态 4. 提取FriendCard和MomentList为独立组件 5. 优化代码结构提升可维护性
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// 朋友圈相关的API接口
|
||||
import { useWebSocketStore } from "@/store/module/websocket/websocket";
|
||||
|
||||
// 朋友圈请求参数接口
|
||||
export interface FetchMomentParams {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
createTimeSec?: number;
|
||||
prevSnsId?: number;
|
||||
count?: number;
|
||||
isTimeline?: boolean;
|
||||
seq?: number;
|
||||
}
|
||||
|
||||
// 获取朋友圈数据
|
||||
export const fetchFriendsCircleData = async (params: FetchMomentParams) => {
|
||||
const { sendCommand } = useWebSocketStore.getState();
|
||||
sendCommand("CmdFetchMoment", params);
|
||||
};
|
||||
|
||||
// 点赞朋友圈
|
||||
export const likeMoment = async (params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
snsId: string;
|
||||
seq?: number;
|
||||
}) => {
|
||||
const { sendCommand } = useWebSocketStore.getState();
|
||||
const requestData = {
|
||||
cmdType: "CmdMomentInteract",
|
||||
momentInteractType: 1,
|
||||
wechatAccountId: params.wechatAccountId,
|
||||
wechatFriendId: params.wechatFriendId || 0,
|
||||
snsId: params.snsId,
|
||||
seq: params.seq || Date.now(),
|
||||
};
|
||||
|
||||
sendCommand("CmdMomentInteract", requestData);
|
||||
};
|
||||
|
||||
// 取消点赞
|
||||
export const cancelLikeMoment = async (params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
snsId: string;
|
||||
seq?: number;
|
||||
}) => {
|
||||
const { sendCommand } = useWebSocketStore.getState();
|
||||
const requestData = {
|
||||
cmdType: "CmdMomentCancelInteract",
|
||||
optType: 1,
|
||||
wechatAccountId: params.wechatAccountId,
|
||||
wechatFriendId: params.wechatFriendId || 0,
|
||||
CommentId2: "",
|
||||
CommentTime: 0,
|
||||
snsId: params.snsId,
|
||||
seq: params.seq || Date.now(),
|
||||
};
|
||||
|
||||
sendCommand("CmdMomentCancelInteract", requestData);
|
||||
};
|
||||
|
||||
// 评论朋友圈
|
||||
export const commentMoment = async (params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
snsId: string;
|
||||
sendWord: string;
|
||||
seq?: number;
|
||||
}) => {
|
||||
const { sendCommand } = useWebSocketStore.getState();
|
||||
const requestData = {
|
||||
cmdType: "CmdMomentInteract",
|
||||
wechatAccountId: params.wechatAccountId,
|
||||
wechatFriendId: params.wechatFriendId || 0,
|
||||
snsId: params.snsId,
|
||||
sendWord: params.sendWord,
|
||||
momentInteractType: 2,
|
||||
seq: params.seq || Date.now(),
|
||||
};
|
||||
|
||||
sendCommand("CmdMomentInteract", requestData);
|
||||
};
|
||||
|
||||
// 撤销评论
|
||||
export const cancelCommentMoment = async (params: {
|
||||
wechatAccountId: number;
|
||||
wechatFriendId?: number;
|
||||
snsId: string;
|
||||
CommentTime: number;
|
||||
seq?: number;
|
||||
}) => {
|
||||
const { sendCommand } = useWebSocketStore.getState();
|
||||
const requestData = {
|
||||
cmdType: "CmdMomentCancelInteract",
|
||||
optType: 2,
|
||||
wechatAccountId: params.wechatAccountId,
|
||||
wechatFriendId: params.wechatFriendId || 0,
|
||||
CommentId2: "",
|
||||
CommentTime: params.CommentTime,
|
||||
snsId: params.snsId,
|
||||
seq: params.seq || Date.now(),
|
||||
};
|
||||
|
||||
sendCommand("CmdMomentCancelInteract", requestData);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { FriendsCircleItem } from "../index.data";
|
||||
export interface FriendCardProps {
|
||||
monent: FriendsCircleItem;
|
||||
isNotMy?: boolean;
|
||||
currentKf?: any;
|
||||
wechatFriendId?: number;
|
||||
formatTime: (time: number) => string;
|
||||
}
|
||||
|
||||
export interface MomentListProps {
|
||||
MomentCommon: FriendsCircleItem[];
|
||||
MomentCommonLoading: boolean;
|
||||
currentKf?: any;
|
||||
wechatFriendId?: number;
|
||||
formatTime: (time: number) => string;
|
||||
loadMomentData: (loadMore: boolean) => void;
|
||||
}
|
||||
export interface likeListItem {
|
||||
createTime: number;
|
||||
nickName: string;
|
||||
wechatId: string;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import React from "react";
|
||||
import { Avatar, Button, Image, Spin } from "antd";
|
||||
import {
|
||||
HeartOutlined,
|
||||
MessageOutlined,
|
||||
LoadingOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { FriendsCircleItem } from "../index.data";
|
||||
import styles from "../index.module.scss";
|
||||
import { likeMoment, cancelLikeMoment } from "./api";
|
||||
import { likeListItem, FriendCardProps, MomentListProps } from "./data";
|
||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||
// 单个朋友圈项目组件
|
||||
export const FriendCard: React.FC<FriendCardProps> = ({
|
||||
monent,
|
||||
isNotMy = false,
|
||||
currentKf,
|
||||
wechatFriendId,
|
||||
formatTime,
|
||||
}) => {
|
||||
const content = monent?.momentEntity?.content || "";
|
||||
const images = monent?.momentEntity?.resUrls || [];
|
||||
const time = formatTime(monent.createTime);
|
||||
const likesCount = monent?.likeList?.length || 0;
|
||||
const commentsCount = monent?.commentList?.length || 0;
|
||||
const { updateLikeMoment } = useWeChatStore();
|
||||
const handleLike = (moment: FriendsCircleItem) => {
|
||||
console.log(currentKf);
|
||||
|
||||
//判断是否已经点赞了
|
||||
const isLiked = moment?.likeList?.some(
|
||||
(item: likeListItem) => item.wechatId === currentKf?.wechatId,
|
||||
);
|
||||
if (isLiked) {
|
||||
cancelLikeMoment({
|
||||
wechatAccountId: currentKf?.id || 0,
|
||||
wechatFriendId: wechatFriendId || 0,
|
||||
snsId: moment.snsId,
|
||||
seq: Date.now(),
|
||||
});
|
||||
// 更新点赞
|
||||
updateLikeMoment(
|
||||
moment.snsId,
|
||||
moment.likeList.filter(v => v.wechatId !== currentKf?.wechatId),
|
||||
);
|
||||
} else {
|
||||
likeMoment({
|
||||
wechatAccountId: currentKf?.id || 0,
|
||||
wechatFriendId: wechatFriendId || 0,
|
||||
snsId: moment.snsId,
|
||||
seq: Date.now(),
|
||||
});
|
||||
// 更新点赞
|
||||
updateLikeMoment(moment.snsId, [
|
||||
...moment.likeList,
|
||||
{
|
||||
createTime: Date.now(),
|
||||
nickName: currentKf?.nickname || "",
|
||||
wechatId: currentKf?.wechatId || "",
|
||||
},
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleComment = (id: string) => {
|
||||
console.log("评论:", id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.circleItem}>
|
||||
{isNotMy && (
|
||||
<div className={styles.avatar}>
|
||||
<Avatar size={36} shape="square" src="/public/assets/face/1.png" />
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.itemWrap}>
|
||||
<div className={styles.itemHeader}>
|
||||
<div className={styles.userInfo}>
|
||||
{/* <div className={styles.username}>{nickName}</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.itemContent}>
|
||||
<div className={styles.contentText}>{content}</div>
|
||||
{images && images.length > 0 && (
|
||||
<div className={styles.imageContainer}>
|
||||
{images.map((image, index) => (
|
||||
<Image
|
||||
key={index}
|
||||
src={image}
|
||||
className={styles.contentImage}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.itemFooter}>
|
||||
<div className={styles.timeInfo}>{time}</div>
|
||||
<div className={styles.actions}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<HeartOutlined />}
|
||||
onClick={() => handleLike(monent)}
|
||||
className={styles.actionButton}
|
||||
>
|
||||
{likesCount > 0 && <span>{likesCount}</span>}
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<MessageOutlined />}
|
||||
onClick={() => handleComment(monent.snsId)}
|
||||
className={styles.actionButton}
|
||||
>
|
||||
{commentsCount > 0 && <span>{commentsCount}</span>}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 点赞和评论区域 */}
|
||||
{(monent?.likeList?.length > 0 || monent?.commentList?.length > 0) && (
|
||||
<div className={styles.interactionArea}>
|
||||
{/* 点赞列表 */}
|
||||
{monent?.likeList?.length > 0 && (
|
||||
<div className={styles.likeArea}>
|
||||
<HeartOutlined className={styles.likeIcon} />
|
||||
<span className={styles.likeList}>
|
||||
{monent?.likeList?.map((like, index) => (
|
||||
<span key={`${like.wechatId}-${like.createTime}-${index}`}>
|
||||
{like.nickName}
|
||||
{index < (monent?.likeList?.length || 0) - 1 && "、"}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 评论列表 */}
|
||||
{monent?.commentList?.length > 0 && (
|
||||
<div className={styles.commentArea}>
|
||||
{monent?.commentList?.map(comment => (
|
||||
<div
|
||||
key={`${comment.wechatId}-${comment.commentTime}`}
|
||||
className={styles.commentItem}
|
||||
>
|
||||
<span className={styles.commentUser}>
|
||||
{comment.nickName}
|
||||
</span>
|
||||
<span className={styles.commentSeparator}>: </span>
|
||||
<span className={styles.commentContent}>
|
||||
{comment.content}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 朋友圈列表组件
|
||||
export const MomentList: React.FC<MomentListProps> = ({
|
||||
MomentCommon,
|
||||
MomentCommonLoading,
|
||||
formatTime,
|
||||
currentKf,
|
||||
loadMomentData,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.myCircleContent}>
|
||||
{MomentCommon.length > 0 ? (
|
||||
<>
|
||||
{MomentCommon.map((v, index) => (
|
||||
<div
|
||||
key={`${v.snsId}-${v.createTime}-${index}`}
|
||||
className={styles.itemWrapper}
|
||||
>
|
||||
<FriendCard
|
||||
monent={v}
|
||||
isNotMy={false}
|
||||
formatTime={formatTime}
|
||||
currentKf={currentKf}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{MomentCommonLoading && (
|
||||
<div className={styles.loadingMore}>
|
||||
<Spin indicator={<LoadingOutlined spin />} /> 加载中...
|
||||
</div>
|
||||
)}
|
||||
{!MomentCommonLoading && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => loadMomentData(true)}
|
||||
>
|
||||
加载更多...
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : MomentCommonLoading ? (
|
||||
<div className={styles.loadingMore}>
|
||||
<Spin indicator={<LoadingOutlined spin />} /> 加载中...
|
||||
</div>
|
||||
) : (
|
||||
<p className={styles.emptyText}>暂无我的朋友圈内容</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,412 @@
|
||||
/* ===== 组件根容器 ===== */
|
||||
.friendsCircle {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
/* 滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 折叠面板样式 ===== */
|
||||
.collapseContainer {
|
||||
margin-bottom: 1px;
|
||||
|
||||
:global(.ant-collapse-item) {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-collapse-header) {
|
||||
padding: 12px 16px !important;
|
||||
background-color: #ffffff;
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-collapse-content-box) {
|
||||
padding: 16px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 折叠面板头部 */
|
||||
.collapseHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
.avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
/* 特殊头像样式 */
|
||||
.specialAvatar {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 群组头像样式 */
|
||||
.groupAvatars {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
.groupAvatar {
|
||||
position: absolute;
|
||||
border: 1px solid #fff;
|
||||
background-color: #52c41a;
|
||||
|
||||
&:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 特殊文本样式 */
|
||||
.specialText {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 内容区域样式 ===== */
|
||||
.myCircleContent,
|
||||
.squareContent {
|
||||
padding: 0;
|
||||
|
||||
/* 项目包装器 */
|
||||
.itemWrapper {
|
||||
margin-bottom: 1px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ===== 朋友圈项目样式 ===== */
|
||||
.circleItem {
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
/* 头像样式 */
|
||||
.avatar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
/* 项目头部 */
|
||||
.itemHeader {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
/* 用户信息 */
|
||||
.userInfo {
|
||||
flex: 1;
|
||||
|
||||
.username {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 项目内容 */
|
||||
.itemContent {
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
.contentText {
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* 图片容器 */
|
||||
.imageContainer {
|
||||
margin: 8px 0;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
.contentImage {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
float: left;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 蓝色链接 */
|
||||
.blueLink {
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 项目底部 */
|
||||
.itemFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
/* 时间信息 */
|
||||
.timeInfo {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 操作按钮区域 */
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.actionButton {
|
||||
padding: 4px 8px;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 点赞和评论交互区域
|
||||
.interactionArea {
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
|
||||
.likeArea {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.likeIcon {
|
||||
color: #ff6b6b;
|
||||
margin-right: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.likeList {
|
||||
color: #576b95;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.commentArea {
|
||||
.commentItem {
|
||||
margin-bottom: 2px;
|
||||
line-height: 1.4;
|
||||
|
||||
.commentUser {
|
||||
color: #576b95;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.commentSeparator {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.commentContent {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.emptyText {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 加载更多样式 */
|
||||
.loadingMore {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
gap: 8px;
|
||||
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 图片预览Modal样式 ===== */
|
||||
.imagePreviewModal {
|
||||
:global(.ant-modal-content) {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:global(.ant-modal-header) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.ant-modal-close) {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1001;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ant-modal-body) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.previewContainer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
|
||||
.previewImage {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.navButton {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
&.prevButton {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
&.nextButton {
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.imageCounter {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export interface FriendsCircleItem {
|
||||
createTime: number;
|
||||
likeList: LikeItem[];
|
||||
momentEntity: MomentEntity;
|
||||
snsId: number;
|
||||
snsId: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Avatar, Button, Collapse, Spin, Modal } from "antd";
|
||||
import {
|
||||
HeartOutlined,
|
||||
ChromeOutlined,
|
||||
MessageOutlined,
|
||||
LoadingOutlined,
|
||||
CloseOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Collapse } from "antd";
|
||||
import { ChromeOutlined } from "@ant-design/icons";
|
||||
import { MomentList } from "./components/friendCard";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import styles from "./index.module.scss";
|
||||
import { FriendsCircleItem } from "./index.data";
|
||||
import { fetchFriendsCircleData } from "./api";
|
||||
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||
|
||||
interface FriendsCircleProps {
|
||||
wechatFriendId: number;
|
||||
wechatFriendId?: number;
|
||||
}
|
||||
|
||||
const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
@@ -36,10 +30,6 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
|
||||
// 状态管理
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
// 图片预览相关状态
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewImages, setPreviewImages] = useState<string[]>([]);
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||
|
||||
// 加载更多我的朋友圈
|
||||
const loadMomentData = async (loadMore: boolean = false) => {
|
||||
@@ -51,8 +41,8 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
wechatFriendId: wechatFriendId || 0,
|
||||
createTimeSec: Math.floor(dayjs().subtract(2, "month").valueOf() / 1000),
|
||||
prevSnsId: loadMore
|
||||
? 0
|
||||
: MomentCommon[MomentCommon.length - 1]?.snsId || 0,
|
||||
? Number(MomentCommon[MomentCommon.length - 1]?.snsId) || 0
|
||||
: 0,
|
||||
count: 10,
|
||||
isTimeline: expandedKeys.includes("1"),
|
||||
seq: Date.now(),
|
||||
@@ -64,49 +54,12 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
const handleCollapseChange = (keys: string | string[]) => {
|
||||
const keyArray = Array.isArray(keys) ? keys : [keys];
|
||||
setExpandedKeys(keyArray);
|
||||
console.log("MomentCommonLoading", MomentCommonLoading);
|
||||
if (!MomentCommonLoading && keys.length > 0) {
|
||||
clearMomentCommon();
|
||||
loadMomentData(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLike = (id: number) => {
|
||||
console.log("点赞:", id);
|
||||
};
|
||||
|
||||
const handleComment = (id: number) => {
|
||||
console.log("评论:", id);
|
||||
};
|
||||
|
||||
// 处理图片预览
|
||||
const handleImagePreview = (images: string[], index: number) => {
|
||||
setPreviewImages(images);
|
||||
setCurrentImageIndex(index);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
// 关闭图片预览
|
||||
const handleClosePreview = () => {
|
||||
setPreviewVisible(false);
|
||||
setPreviewImages([]);
|
||||
setCurrentImageIndex(0);
|
||||
};
|
||||
|
||||
// 切换到上一张图片
|
||||
const handlePrevImage = () => {
|
||||
setCurrentImageIndex(prev =>
|
||||
prev > 0 ? prev - 1 : previewImages.length - 1,
|
||||
);
|
||||
};
|
||||
|
||||
// 切换到下一张图片
|
||||
const handleNextImage = () => {
|
||||
setCurrentImageIndex(prev =>
|
||||
prev < previewImages.length - 1 ? prev + 1 : 0,
|
||||
);
|
||||
};
|
||||
|
||||
// 格式化时间戳
|
||||
const formatTime = (timestamp: number) => {
|
||||
const date = new Date(timestamp * 1000);
|
||||
@@ -119,168 +72,6 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取用户昵称
|
||||
const getUserNickName = (item: FriendsCircleItem) => {
|
||||
// 优先从点赞列表或评论列表中获取昵称
|
||||
if (item?.likeList?.length > 0) {
|
||||
return item.likeList[0].nickName;
|
||||
}
|
||||
if (item.commentList.length > 0) {
|
||||
return item.commentList[0].nickName;
|
||||
}
|
||||
return item.momentEntity.userName;
|
||||
};
|
||||
|
||||
const renderNormalItem = (monent: FriendsCircleItem, isNotMy?: boolean) => {
|
||||
// const nickName = getUserNickName(monent);
|
||||
const content = monent?.momentEntity?.content || "";
|
||||
const images = monent?.momentEntity?.resUrls || [];
|
||||
const time = formatTime(monent.createTime);
|
||||
const likesCount = monent?.likeList?.length || 0;
|
||||
const commentsCount = monent?.commentList?.length || 0;
|
||||
|
||||
return (
|
||||
<div className={styles.circleItem}>
|
||||
{isNotMy && (
|
||||
<div className={styles.avatar}>
|
||||
<Avatar size={36} shape="square" src="/public/assets/face/1.png" />
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.itemWrap}>
|
||||
<div className={styles.itemHeader}>
|
||||
<div className={styles.userInfo}>
|
||||
{/* <div className={styles.username}>{nickName}</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.itemContent}>
|
||||
<div className={styles.contentText}>{content}</div>
|
||||
{images && images.length > 0 && (
|
||||
<div className={styles.imageContainer}>
|
||||
{images.map((image, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={image}
|
||||
className={styles.contentImage}
|
||||
onClick={() => handleImagePreview(images, index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.itemFooter}>
|
||||
<div className={styles.timeInfo}>{time}</div>
|
||||
<div className={styles.actions}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<HeartOutlined />}
|
||||
onClick={() => handleLike(monent.snsId)}
|
||||
className={styles.actionButton}
|
||||
>
|
||||
{likesCount > 0 && <span>{likesCount}</span>}
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<MessageOutlined />}
|
||||
onClick={() => handleComment(monent.snsId)}
|
||||
className={styles.actionButton}
|
||||
>
|
||||
{commentsCount > 0 && <span>{commentsCount}</span>}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 点赞和评论区域 */}
|
||||
{(monent?.likeList?.length > 0 ||
|
||||
monent?.commentList?.length > 0) && (
|
||||
<div className={styles.interactionArea}>
|
||||
{/* 点赞列表 */}
|
||||
{monent?.likeList?.length > 0 && (
|
||||
<div className={styles.likeArea}>
|
||||
<HeartOutlined className={styles.likeIcon} />
|
||||
<span className={styles.likeList}>
|
||||
{monent?.likeList?.map((like, index) => (
|
||||
<span
|
||||
key={`${like.wechatId}-${like.createTime}-${index}`}
|
||||
>
|
||||
{like.nickName}
|
||||
{index < (monent?.likeList?.length || 0) - 1 && "、"}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 评论列表 */}
|
||||
{monent?.commentList?.length > 0 && (
|
||||
<div className={styles.commentArea}>
|
||||
{monent?.commentList?.map(comment => (
|
||||
<div
|
||||
key={`${comment.wechatId}-${comment.commentTime}`}
|
||||
className={styles.commentItem}
|
||||
>
|
||||
<span className={styles.commentUser}>
|
||||
{comment.nickName}
|
||||
</span>
|
||||
<span className={styles.commentSeparator}>: </span>
|
||||
<span className={styles.commentContent}>
|
||||
{comment.content}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderMomentCommon = () => {
|
||||
return (
|
||||
<div className={styles.myCircleContent}>
|
||||
{MomentCommon.length > 0 ? (
|
||||
<>
|
||||
{MomentCommon.map((v, index) => (
|
||||
<div
|
||||
key={`${v.snsId}-${v.createTime}-${index}`}
|
||||
className={styles.itemWrapper}
|
||||
>
|
||||
{renderNormalItem(v, false)}
|
||||
</div>
|
||||
))}
|
||||
{MomentCommonLoading && (
|
||||
<div className={styles.loadingMore}>
|
||||
<Spin indicator={<LoadingOutlined spin />} /> 加载中...
|
||||
</div>
|
||||
)}
|
||||
{!MomentCommonLoading && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => loadMomentData(true)}
|
||||
>
|
||||
加载更多...
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : MomentCommonLoading ? (
|
||||
<div className={styles.loadingMore}>
|
||||
<Spin indicator={<LoadingOutlined spin />} /> 加载中...
|
||||
</div>
|
||||
) : (
|
||||
<p className={styles.emptyText}>暂无我的朋友圈内容</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const collapseItems = [
|
||||
{
|
||||
key: "1",
|
||||
@@ -294,7 +85,15 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
<span className={styles.specialText}>我的朋友圈</span>
|
||||
</div>
|
||||
),
|
||||
children: renderMomentCommon(),
|
||||
children: (
|
||||
<MomentList
|
||||
currentKf={currentKf}
|
||||
MomentCommon={MomentCommon}
|
||||
MomentCommonLoading={MomentCommonLoading}
|
||||
loadMomentData={loadMomentData}
|
||||
formatTime={formatTime}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
@@ -304,7 +103,15 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
<span className={styles.specialText}>朋友圈广场</span>
|
||||
</div>
|
||||
),
|
||||
children: renderMomentCommon(),
|
||||
children: (
|
||||
<MomentList
|
||||
currentKf={currentKf}
|
||||
MomentCommon={MomentCommon}
|
||||
MomentCommonLoading={MomentCommonLoading}
|
||||
loadMomentData={loadMomentData}
|
||||
formatTime={formatTime}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(wechatFriendId
|
||||
? [
|
||||
@@ -316,7 +123,15 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
<span className={styles.specialText}>好友朋友圈</span>
|
||||
</div>
|
||||
),
|
||||
children: renderMomentCommon(),
|
||||
children: (
|
||||
<MomentList
|
||||
currentKf={currentKf}
|
||||
MomentCommon={MomentCommon}
|
||||
MomentCommonLoading={MomentCommonLoading}
|
||||
loadMomentData={loadMomentData}
|
||||
formatTime={formatTime}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -333,58 +148,6 @@ const FriendsCircle: React.FC<FriendsCircleProps> = ({ wechatFriendId }) => {
|
||||
activeKey={expandedKeys}
|
||||
onChange={handleCollapseChange}
|
||||
/>
|
||||
|
||||
{/* 图片预览Modal */}
|
||||
<Modal
|
||||
open={previewVisible}
|
||||
footer={null}
|
||||
onCancel={handleClosePreview}
|
||||
width="100vw"
|
||||
style={{ top: 0, paddingBottom: 0 }}
|
||||
bodyStyle={{
|
||||
padding: 0,
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.9)",
|
||||
}}
|
||||
closeIcon={<CloseOutlined style={{ color: "white", fontSize: 24 }} />}
|
||||
className={styles.imagePreviewModal}
|
||||
>
|
||||
<div className={styles.previewContainer}>
|
||||
{previewImages.length > 0 && (
|
||||
<>
|
||||
<img
|
||||
src={previewImages[currentImageIndex]}
|
||||
alt="预览图片"
|
||||
className={styles.previewImage}
|
||||
/>
|
||||
|
||||
{previewImages.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
className={`${styles.navButton} ${styles.prevButton}`}
|
||||
onClick={handlePrevImage}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.navButton} ${styles.nextButton}`}
|
||||
onClick={handleNextImage}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
|
||||
<div className={styles.imageCounter}>
|
||||
{currentImageIndex + 1} / {previewImages.length}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Input, Skeleton } from "antd";
|
||||
import { SearchOutlined, ChromeOutlined } from "@ant-design/icons";
|
||||
import WechatFriends from "./WechatFriends";
|
||||
@@ -26,7 +26,9 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({ loading = false }) => {
|
||||
clearSearchKeyword();
|
||||
};
|
||||
|
||||
// 过滤逻辑已移至store中,这里直接使用store返回的已过滤数据
|
||||
useEffect(() => {
|
||||
setActiveTab("chats");
|
||||
}, [kfSelected]);
|
||||
|
||||
// 渲染骨架屏
|
||||
const renderSkeleton = () => (
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
import { FriendsCircleItem } from "@/pages/pc/ckbox/weChat/components/SidebarMenu/FriendsCicle/index.data";
|
||||
interface likeListItem {
|
||||
createTime: number;
|
||||
nickName: string;
|
||||
wechatId: string;
|
||||
}
|
||||
// 微信聊天相关的类型定义
|
||||
export interface WeChatState {
|
||||
// 当前选中的联系人/群组
|
||||
@@ -26,6 +31,7 @@ export interface WeChatState {
|
||||
MomentCommonLoading: boolean;
|
||||
// MomentCommon 相关方法
|
||||
updateMomentCommonLoading: (loading: boolean) => void;
|
||||
updateLikeMoment: (snsId: string, likeList: likeListItem[]) => void;
|
||||
|
||||
loadChatMessages: (Init: boolean, To?: number) => Promise<void>;
|
||||
SearchMessage: (params: {
|
||||
|
||||
@@ -292,6 +292,19 @@ export const useWeChatStore = create<WeChatState>()(
|
||||
updateMomentCommon: moments => {
|
||||
set({ MomentCommon: moments });
|
||||
},
|
||||
updateLikeMoment: (snsId, likeList) => {
|
||||
set(state => ({
|
||||
MomentCommon: state.MomentCommon.map(item => {
|
||||
if (item.snsId === snsId) {
|
||||
return {
|
||||
...item,
|
||||
likeList: likeList,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
}));
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "wechat-storage",
|
||||
|
||||
Reference in New Issue
Block a user