feat(weChat): 添加朋友圈功能组件及样式

- 新增FriendsCircle组件实现朋友圈展示功能
- 添加朋友圈样式文件包含布局和交互样式
- 修改SidebarMenu组件支持朋友圈选项卡
- 根据kfSelected状态控制朋友圈选项卡显示
This commit is contained in:
超级老白兔
2025-09-16 15:17:05 +08:00
parent 4d69c8e570
commit 21396f5a1d
3 changed files with 460 additions and 19 deletions

View File

@@ -0,0 +1,230 @@
.friendsCircle {
height: 100%;
overflow-y: auto;
padding: 0;
background-color: #f5f5f5;
}
.itemWrapper {
margin-bottom: 1px;
}
// 可折叠组件样式
.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: 12px;
}
.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;
}
}
// 当内容为空时的样式
.emptyText {
padding: 20px;
text-align: center;
color: #999;
font-size: 14px;
margin: 0;
}
}
// 普通朋友圈项目样式
.circleItem {
background-color: #ffffff;
padding: 16px;
border-bottom: 1px solid #e8e8e8;
}
.itemHeader {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
}
.avatar {
flex-shrink: 0;
}
.userInfo {
flex: 1;
}
.username {
font-size: 16px;
font-weight: 500;
color: #333;
line-height: 1.4;
}
.itemContent {
margin-left: 52px;
margin-bottom: 12px;
}
.contentText {
font-size: 14px;
color: #333;
line-height: 1.6;
margin-bottom: 8px;
word-wrap: break-word;
}
.imageContainer {
margin: 8px 0;
}
.contentImage {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 4px;
margin-right: 8px;
margin-bottom: 8px;
}
.blueLink {
color: #1890ff;
font-size: 14px;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.itemFooter {
display: flex;
align-items: center;
justify-content: space-between;
margin-left: 52px;
}
.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;
}
}
// 滚动条样式
.friendsCircle::-webkit-scrollbar {
width: 6px;
}
.friendsCircle::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.friendsCircle::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
&:hover {
background: #a8a8a8;
}
}

View File

@@ -0,0 +1,217 @@
import React from "react";
import { Avatar, Button, Collapse } from "antd";
import { HeartOutlined, MessageOutlined } from "@ant-design/icons";
import styles from "./index.module.scss";
// 朋友圈数据类型定义
interface FriendsCircleItem {
id: string;
avatar: string;
username: string;
content: string;
images?: string[];
time: string;
likes?: number;
comments?: number;
}
// 模拟朋友圈数据
const mockFriendsCircleData: FriendsCircleItem[] = [
{
id: "1",
avatar: "",
username: "我的朋友圈",
content: "",
time: "",
},
{
id: "2",
avatar: "",
username: "朋友圈广场",
content: "",
time: "",
},
{
id: "3",
avatar: "/public/assets/face/1.png",
username: "老坑爹-解放双手,释放时间",
content:
"🎉🎊🎈欢迎小伙伴加入单群聊客宝地盘思慕斯蛋糕的小伙伴们的支持与信任!!!",
images: ["/public/assets/face/1.png"],
time: "2025年9月16日 13:48",
likes: 0,
comments: 0,
},
{
id: "4",
avatar: "/public/assets/face/1.png",
username: "老坑爹-解放双手,释放时间",
content: "一整年卡1好的产品有用户的好评是买卖说的再多不如用户的有说服力",
images: ["/public/assets/face/1.png"],
time: "2025年9月16日 11:33",
likes: 0,
comments: 0,
},
{
id: "5",
avatar: "/public/assets/face/1.png",
username: "老坑爹-解放双手,释放时间",
content:
"两个小时637朵卡今天的努力也是给我自己最好的礼物🎁坚持就是胜利第二年开干。",
images: ["/public/assets/face/1.png"],
time: "2025年9月16日 11:03",
likes: 0,
comments: 0,
},
{
id: "6",
avatar: "/public/assets/face/1.png",
username: "老坑爹-解放双手,释放时间",
content: "老坑爹如果不幸苦,没有品质保障,客户会无限复购?",
images: ["/public/assets/face/1.png"],
time: "2025年9月16日 10:33",
likes: 0,
comments: 0,
},
];
const FriendsCircle: React.FC = () => {
const handleLike = (id: string) => {
console.log("点赞:", id);
};
const handleComment = (id: string) => {
console.log("评论:", id);
};
const renderMyFriendsCircle = () => {
// 显示部分朋友圈数据作为"我的朋友圈"
const myCircleData = mockFriendsCircleData.slice(2, 4);
return (
<div className={styles.myCircleContent}>
{myCircleData.length > 0 ? (
myCircleData.map(item => (
<div key={item.id} className={styles.itemWrapper}>
{renderNormalItem(item)}
</div>
))
) : (
<p className={styles.emptyText}></p>
)}
</div>
);
};
const renderFriendsSquare = () => {
// 显示剩余的朋友圈数据作为"朋友圈广场"
const squareData = mockFriendsCircleData.slice(4);
return (
<div className={styles.squareContent}>
{squareData.length > 0 ? (
squareData.map(item => (
<div key={item.id} className={styles.itemWrapper}>
{renderNormalItem(item)}
</div>
))
) : (
<p className={styles.emptyText}>广</p>
)}
</div>
);
};
const collapseItems = [
{
key: "1",
label: (
<div className={styles.collapseHeader}>
<Avatar size={32} className={styles.specialAvatar} />
<span className={styles.specialText}></span>
</div>
),
children: renderMyFriendsCircle(),
},
{
key: "2",
label: (
<div className={styles.collapseHeader}>
<div className={styles.groupAvatars}>
<Avatar size={16} className={styles.groupAvatar} />
<Avatar size={16} className={styles.groupAvatar} />
<Avatar size={16} className={styles.groupAvatar} />
<Avatar size={16} className={styles.groupAvatar} />
</div>
<span className={styles.specialText}>广</span>
</div>
),
children: renderFriendsSquare(),
},
];
const renderNormalItem = (item: FriendsCircleItem) => {
return (
<div className={styles.circleItem}>
<div className={styles.itemHeader}>
<Avatar size={40} src={item.avatar} className={styles.avatar} />
<div className={styles.userInfo}>
<div className={styles.username}>{item.username}</div>
</div>
</div>
<div className={styles.itemContent}>
<div className={styles.contentText}>{item.content}</div>
{item.images && item.images.length > 0 && (
<div className={styles.imageContainer}>
{item.images.map((image, index) => (
<img
key={index}
src={image}
alt="朋友圈图片"
className={styles.contentImage}
/>
))}
</div>
)}
<div className={styles.blueLink}></div>
</div>
<div className={styles.itemFooter}>
<div className={styles.timeInfo}>{item.time}</div>
<div className={styles.actions}>
<Button
type="text"
size="small"
icon={<HeartOutlined />}
onClick={() => handleLike(item.id)}
className={styles.actionButton}
/>
<Button
type="text"
size="small"
icon={<MessageOutlined />}
onClick={() => handleComment(item.id)}
className={styles.actionButton}
/>
</div>
</div>
</div>
);
};
return (
<div className={styles.friendsCircle}>
{/* 可折叠的特殊模块,包含所有朋友圈数据 */}
<Collapse
items={collapseItems}
className={styles.collapseContainer}
ghost
/>
</div>
);
};
export default FriendsCircle;

View File

@@ -1,13 +1,9 @@
import React, { useState } from "react";
import { Input, Skeleton } from "antd";
import {
SearchOutlined,
UserOutlined,
ChromeOutlined,
MessageOutlined,
} from "@ant-design/icons";
import { SearchOutlined, ChromeOutlined } from "@ant-design/icons";
import WechatFriends from "./WechatFriends";
import MessageList from "./MessageList/index";
import FriendsCircle from "./FriendsCicle";
import styles from "./SidebarMenu.module.scss";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
interface SidebarMenuProps {
@@ -18,6 +14,7 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({ loading = false }) => {
const searchKeyword = useCkChatStore(state => state.searchKeyword);
const setSearchKeyword = useCkChatStore(state => state.setSearchKeyword);
const clearSearchKeyword = useCkChatStore(state => state.clearSearchKeyword);
const kfSelected = useCkChatStore(state => state.kfSelected);
const [activeTab, setActiveTab] = useState("chats");
@@ -105,12 +102,14 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({ loading = false }) => {
>
<span></span>
</div>
<div
className={`${styles.tabItem} ${activeTab === "groups" ? styles.active : ""}`}
onClick={() => setActiveTab("groups")}
>
<span></span>
</div>
{kfSelected != 0 && (
<div
className={`${styles.tabItem} ${activeTab === "friendsCicle" ? styles.active : ""}`}
onClick={() => setActiveTab("friendsCicle")}
>
<span></span>
</div>
)}
</div>
</div>
);
@@ -122,13 +121,8 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({ loading = false }) => {
return <MessageList />;
case "contracts":
return <WechatFriends />;
case "groups":
return (
<div className={styles.emptyState}>
<ChromeOutlined style={{ fontSize: 48, color: "#ccc" }} />
<p></p>
</div>
);
case "friendsCicle":
return <FriendsCircle />;
default:
return null;
}