refactor(Skeleton): 重构骨架屏组件结构和样式
- 将原有单一骨架屏拆分为头部、侧边栏、聊天区域和右侧面板多个模块 - 优化骨架屏的样式细节,使其更接近真实UI - 添加加载状态提示和交互效果
This commit is contained in:
@@ -1,111 +1,185 @@
|
||||
.skeletonLayout {
|
||||
height: 100vh;
|
||||
// 头部骨架样式
|
||||
.headerSkeleton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.headerLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.headerCenter {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.headerRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 左侧边栏骨架样式
|
||||
.siderContent {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.skeletonHeader {
|
||||
height: 64px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
.searchBox {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tabBar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.contactList {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.contactItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
.skeletonVerticalSider {
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
.contactInfo {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.verticalUserList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
.contactTime {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.verticalUserItem {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
// 聊天区域骨架样式
|
||||
.chatContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.chatHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.chatHeaderLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chatHeaderRight {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.messageArea {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.loadingTip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loadingText {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.inputToolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
// 右侧面板骨架样式
|
||||
.rightPanel {
|
||||
background: #fff;
|
||||
border-left: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.profileSection {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.profileHeader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.profileDetails {
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detailItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeletonSider {
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
padding: 16px;
|
||||
.tagSection {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.searchSkeleton {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tabsSkeleton {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.contactListSkeleton {
|
||||
.contactItemSkeleton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.contactInfoSkeleton {
|
||||
margin-left: 12px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeletonMainContent {
|
||||
background-color: #f5f5f5;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.chatHeaderSkeleton {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.chatContentSkeleton {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.messageSkeleton {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
&.leftMessage {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&.rightMessage {
|
||||
align-self: flex-end;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputAreaSkeleton {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Skeleton, Layout } from "antd";
|
||||
import { Skeleton, Layout, Spin } from "antd";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import styles from "./index.module.scss";
|
||||
import pageStyles from "../../index.module.scss";
|
||||
|
||||
@@ -17,100 +18,190 @@ interface PageSkeletonProps {
|
||||
const PageSkeleton: React.FC<PageSkeletonProps> = ({ loading, children }) => {
|
||||
if (!loading) return <>{children}</>;
|
||||
|
||||
const antIcon = <LoadingOutlined style={{ fontSize: 16 }} spin />;
|
||||
|
||||
return (
|
||||
<Layout className={pageStyles.ckboxLayout}>
|
||||
{/* 顶部标题栏骨架 */}
|
||||
<Header className={pageStyles.header}>
|
||||
<Skeleton.Button active size="large" shape="square" block />
|
||||
<div className={styles.headerSkeleton}>
|
||||
<div className={styles.headerLeft}>
|
||||
<Skeleton.Avatar active size="small" shape="circle" />
|
||||
<Skeleton.Input active size="small" style={{ width: "80px" }} />
|
||||
</div>
|
||||
<div className={styles.headerCenter}>
|
||||
<Skeleton.Input active size="small" style={{ width: "200px" }} />
|
||||
</div>
|
||||
<div className={styles.headerRight}>
|
||||
<Skeleton.Button active size="small" style={{ width: "60px" }} />
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
<Layout>
|
||||
{/* 垂直侧边栏骨架 */}
|
||||
<Sider width={60} className={pageStyles.verticalSider}>
|
||||
<div className={styles.verticalUserList}>
|
||||
{Array(5)
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
<div
|
||||
key={`vertical-${index}`}
|
||||
className={styles.verticalUserItem}
|
||||
>
|
||||
<Skeleton.Avatar active size="large" shape="circle" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
{/* 左侧联系人边栏骨架 */}
|
||||
{/* 左侧联系人列表骨架 */}
|
||||
<Sider width={280} className={pageStyles.sider}>
|
||||
<div className={styles.searchSkeleton}>
|
||||
<Skeleton.Input active size="small" block />
|
||||
</div>
|
||||
<div className={styles.tabsSkeleton}>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="small"
|
||||
shape="square"
|
||||
style={{ width: "30%" }}
|
||||
/>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="small"
|
||||
shape="square"
|
||||
style={{ width: "30%" }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.contactListSkeleton}>
|
||||
{Array(8)
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
<div
|
||||
key={`contact-${index}`}
|
||||
className={styles.contactItemSkeleton}
|
||||
>
|
||||
<Skeleton.Avatar active size="large" shape="circle" />
|
||||
<div className={styles.contactInfoSkeleton}>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "60%" }}
|
||||
/>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "80%" }}
|
||||
/>
|
||||
<div className={styles.siderContent}>
|
||||
{/* 搜索框 */}
|
||||
<div className={styles.searchBox}>
|
||||
<Skeleton.Input active size="large" block />
|
||||
</div>
|
||||
|
||||
{/* 标签栏 */}
|
||||
<div className={styles.tabBar}>
|
||||
<Skeleton.Button active size="small" style={{ width: "60px" }} />
|
||||
<Skeleton.Button active size="small" style={{ width: "60px" }} />
|
||||
<Skeleton.Button active size="small" style={{ width: "60px" }} />
|
||||
</div>
|
||||
|
||||
{/* 联系人列表 */}
|
||||
<div className={styles.contactList}>
|
||||
{Array(10)
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
<div key={`contact-${index}`} className={styles.contactItem}>
|
||||
<Skeleton.Avatar active size="large" shape="circle" />
|
||||
<div className={styles.contactInfo}>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "80px" }}
|
||||
/>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "120px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.contactTime}>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "40px" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
{/* 主内容区骨架 */}
|
||||
<Content className={styles.skeletonMainContent}>
|
||||
<div className={styles.chatHeaderSkeleton}>
|
||||
<Skeleton.Avatar active size="large" shape="circle" />
|
||||
<Skeleton.Input active size="small" style={{ width: "30%" }} />
|
||||
{/* 主聊天区域骨架 */}
|
||||
<Content className={styles.chatContent}>
|
||||
{/* 聊天头部 */}
|
||||
<div className={styles.chatHeader}>
|
||||
<div className={styles.chatHeaderLeft}>
|
||||
<Skeleton.Avatar active size="large" shape="circle" />
|
||||
<Skeleton.Input active size="small" style={{ width: "100px" }} />
|
||||
</div>
|
||||
<div className={styles.chatHeaderRight}>
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.chatContentSkeleton}>
|
||||
{Array(5)
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
<div
|
||||
key={`message-${index}`}
|
||||
className={`${styles.messageSkeleton} ${index % 2 === 0 ? styles.leftMessage : styles.rightMessage}`}
|
||||
>
|
||||
<Skeleton.Avatar active size="small" shape="circle" />
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: index % 2 === 0 ? "60%" : "40%" }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 消息区域 */}
|
||||
<div className={styles.messageArea}>
|
||||
<div className={styles.loadingTip}>
|
||||
<Spin indicator={antIcon} />
|
||||
<span className={styles.loadingText}>
|
||||
加载速度与好友数量有关,请耐心等待...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.inputAreaSkeleton}>
|
||||
<Skeleton.Input active size="large" block />
|
||||
|
||||
{/* 输入区域 */}
|
||||
<div className={styles.inputArea}>
|
||||
<div className={styles.inputToolbar}>
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
<Skeleton.Button active size="small" shape="circle" />
|
||||
</div>
|
||||
<div className={styles.inputField}>
|
||||
<Skeleton.Input active size="large" block />
|
||||
<Skeleton.Button active size="large" style={{ width: "60px" }} />
|
||||
</div>
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
{/* 右侧个人信息面板骨架 */}
|
||||
<Sider width={300} className={styles.rightPanel}>
|
||||
<div className={styles.profileSection}>
|
||||
<div className={styles.profileHeader}>
|
||||
<Skeleton.Avatar active size={80} shape="circle" />
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "100px", marginTop: "12px" }}
|
||||
/>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "60px", marginTop: "4px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.profileDetails}>
|
||||
<div className={styles.detailItem}>
|
||||
<Skeleton.Input active size="small" style={{ width: "60px" }} />
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "120px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.detailItem}>
|
||||
<Skeleton.Input active size="small" style={{ width: "40px" }} />
|
||||
<Skeleton.Input active size="small" style={{ width: "80px" }} />
|
||||
</div>
|
||||
<div className={styles.detailItem}>
|
||||
<Skeleton.Input active size="small" style={{ width: "40px" }} />
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "100px" }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.detailItem}>
|
||||
<Skeleton.Input active size="small" style={{ width: "40px" }} />
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "140px" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.tagSection}>
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "40px", marginBottom: "12px" }}
|
||||
/>
|
||||
<div className={styles.tags}>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "50px", marginRight: "8px" }}
|
||||
/>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "60px", marginRight: "8px" }}
|
||||
/>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="small"
|
||||
style={{ width: "70px" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Sider>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user