Skip to content

Commit fa215c3

Browse files
committed
chore: 更新项目版本号至 3.3.3
- 在 .env 文件中将 VITE_VERSION 更新至 3.3.3。 - 在多个页面组件中添加骨架屏以改善初始加载体验,使用 Skeleton 组件显示加载状态。 - 在各页面中引入 useRef 和 useEffect 以管理加载状态,优化用户体验。
1 parent dd6f5e2 commit fa215c3

File tree

18 files changed

+841
-88
lines changed

18 files changed

+841
-88
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# 项目版本号
2-
VITE_VERSION=3.3.2
2+
VITE_VERSION=3.3.3
33

44
# 项目后端API地址
55
VITE_PROJECT_API=https://你的后端域名/api

src/components/Header/index.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,73 @@
11
import { Link } from 'react-router-dom';
2+
import { useEffect, useState } from 'react';
3+
import { Skeleton } from 'antd';
4+
import { useUserStore } from '@/stores';
25
import DropdownUser from './DropdownUser';
36
import DarkModeSwitcher from './DarkModeSwitcher';
47
import logo from '/logo.png';
58
import PageTab from '../PageTab';
69

710
const Header = (props: { sidebarOpen: string | boolean | undefined; setSidebarOpen: (arg0: boolean) => void }) => {
11+
const user = useUserStore((state) => state.user);
12+
const [initialLoading, setInitialLoading] = useState<boolean>(true);
13+
14+
// 等待用户信息加载完成后,取消初始加载状态
15+
useEffect(() => {
16+
if (user?.name) {
17+
setInitialLoading(false);
18+
} else {
19+
// 如果用户信息未加载,等待最多 500ms 后显示内容
20+
const timer = setTimeout(() => {
21+
setInitialLoading(false);
22+
}, 500);
23+
return () => clearTimeout(timer);
24+
}
25+
}, [user]);
26+
27+
// 初始加载时显示骨架屏
28+
if (initialLoading) {
29+
return (
30+
<header className="sticky top-0 z-[99] flex w-full bg-light-gradient dark:bg-dark-gradient">
31+
<div className="flex flex-grow items-center justify-between px-4 py-3 shadow-2 md:px-6 2xl:px-11 overflow-scroll">
32+
<div className="flex items-center gap-4 flex-1 min-w-0">
33+
{/* 移动端菜单按钮和 Logo 骨架屏 */}
34+
<div className="flex items-center gap-4 lg:hidden flex-shrink-0">
35+
<Skeleton.Button active size="default" style={{ width: 32, height: 32 }} />
36+
<Skeleton.Avatar active size={32} shape="square" />
37+
</div>
38+
39+
{/* PageTab 骨架屏 */}
40+
<div className="flex-1 min-w-0 w-2/6 overflow-x-auto">
41+
<div className="flex items-center gap-2">
42+
{[1, 2, 3].map((item) => (
43+
<Skeleton.Button key={item} active size="default" style={{ width: 100, height: 32 }} />
44+
))}
45+
</div>
46+
</div>
47+
</div>
48+
49+
{/* 右侧操作栏骨架屏 */}
50+
<div className="flex items-center gap-3 2xsm:gap-7 flex-shrink-0 ml-4">
51+
<ul className="flex items-center gap-2 2xsm:gap-4 sm:mr-4">
52+
<Skeleton.Button active size="default" style={{ width: 32, height: 30 }} />
53+
</ul>
54+
55+
<div className="sm:block hidden">
56+
<div className="flex items-center gap-2">
57+
<div className="hidden text-right lg:block space-x-4">
58+
<Skeleton.Input active size="small" style={{ width: 80, height: 30, marginBottom: 4 }} />
59+
<Skeleton.Input active size="small" style={{ width: 60, height: 30 }} />
60+
</div>
61+
62+
<Skeleton.Avatar active size={32} shape="circle" className="ml-4" />
63+
</div>
64+
</div>
65+
</div>
66+
</div>
67+
</header>
68+
);
69+
}
70+
871
return (
972
<header className="sticky top-0 z-[99] flex w-full bg-light-gradient dark:bg-dark-gradient">
1073
<div className="flex flex-grow items-center justify-between px-4 py-3 shadow-2 md:px-6 2xl:px-11 overflow-scroll">

src/components/Sidebar/index.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect, useRef, useState } from 'react';
22
import { NavLink, useLocation } from 'react-router-dom';
3+
import { Skeleton } from 'antd';
34
import SidebarLinkGroup from './SidebarLinkGroup';
45

56
import { BiEditAlt, BiFolderOpen, BiHomeSmile, BiSliderAlt, BiCategoryAlt, BiBug } from 'react-icons/bi';
@@ -32,6 +33,7 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
3233
const location = useLocation();
3334
const version = useVersionData();
3435
const { pathname } = location;
36+
const [initialLoading, setInitialLoading] = useState<boolean>(true);
3537

3638
// 创建 ref 用于触发器和侧边栏元素
3739
const trigger = useRef<HTMLButtonElement>(null);
@@ -72,6 +74,20 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
7274
}
7375
}, [sidebarExpanded]);
7476

77+
// 版本数据加载完成后,取消初始加载状态
78+
useEffect(() => {
79+
// 如果版本数据已加载(有 tag_name)或等待一段时间后,取消骨架屏
80+
if (version.tag_name) {
81+
setInitialLoading(false);
82+
} else {
83+
// 如果版本数据未加载,等待最多 1 秒后显示内容
84+
const timer = setTimeout(() => {
85+
setInitialLoading(false);
86+
}, 1000);
87+
return () => clearTimeout(timer);
88+
}
89+
}, [version]);
90+
7591
const [isSideBarTheme] = useState<'dark' | 'light'>('light');
7692

7793
// 定义导航项的样式类
@@ -233,6 +249,72 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
233249
},
234250
];
235251

252+
// 初始加载时显示骨架屏
253+
if (initialLoading) {
254+
return (
255+
<aside ref={sidebar} className={`absolute left-0 top-0 z-[999] flex h-screen w-64 flex-col overflow-y-hidden duration-300 ease-linear lg:static lg:translate-x-0 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'} ${isSideBarTheme === 'dark' ? 'bg-black dark:bg-boxdark' : 'bg-light-gradient dark:bg-dark-gradient border-r border-stroke dark:border-strokedark transition-all backdrop-blur-2xl'}`}>
256+
{/* Logo 和标题骨架屏 */}
257+
<div className="flex justify-center items-center gap-2 px-6 py-5 pb-0 lg:pt-6 mb-4">
258+
<div className="flex items-center">
259+
<Skeleton.Input active size="default" style={{ width: 100, height: 40 }} />
260+
</div>
261+
</div>
262+
263+
{/* 导航菜单骨架屏 */}
264+
<div className="no-scrollbar flex flex-col overflow-y-auto duration-300 ease-linear">
265+
<nav className="pt-2 pb-4 px-4 lg:px-6">
266+
{/* 第一个路由组 */}
267+
<div className="mb-6">
268+
<ul className="flex flex-col gap-1.5">
269+
{[1, 2, 3].map((item) => (
270+
<li key={item}>
271+
<div className="flex items-center gap-2.5 py-2 px-4">
272+
<Skeleton.Avatar active size={22} shape="square" />
273+
<Skeleton.Input active size="small" style={{ width: 80, height: 20 }} />
274+
</div>
275+
</li>
276+
))}
277+
278+
{/* 带子菜单的项 */}
279+
<li>
280+
<div className="flex items-center gap-2.5 py-2 px-4">
281+
<Skeleton.Avatar active size={22} shape="square" />
282+
<Skeleton.Input active size="small" style={{ width: 60, height: 20, flex: 1 }} />
283+
<Skeleton.Avatar active size={12} shape="circle" />
284+
</div>
285+
286+
{/* 子菜单骨架屏 */}
287+
<div className="mt-4 mb-5.5 flex flex-col gap-2.5 pl-6">
288+
{[1, 2, 3, 4].map((subItem) => (
289+
<div key={subItem} className="ml-6">
290+
<Skeleton.Input active size="small" style={{ width: 80, height: 20 }} />
291+
</div>
292+
))}
293+
</div>
294+
</li>
295+
</ul>
296+
</div>
297+
298+
{/* 第二个路由组 */}
299+
<div>
300+
<Skeleton.Input active size="small" style={{ width: 20, height: 16, marginBottom: 16, marginLeft: 16 }} />
301+
<ul className="flex flex-col gap-1.5">
302+
{[1, 2, 3].map((item) => (
303+
<li key={item}>
304+
<div className="flex items-center gap-2.5 py-2 px-4">
305+
<Skeleton.Avatar active size={22} shape="square" />
306+
<Skeleton.Input active size="small" style={{ width: 80, height: 20 }} />
307+
</div>
308+
</li>
309+
))}
310+
</ul>
311+
</div>
312+
</nav>
313+
</div>
314+
</aside>
315+
);
316+
}
317+
236318
// 渲染侧边栏组件
237319
return (
238320
<aside ref={sidebar} className={`absolute left-0 top-0 z-[999] flex h-screen w-64 flex-col overflow-y-hidden duration-300 ease-linear lg:static lg:translate-x-0 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'} ${isSideBarTheme === 'dark' ? 'bg-black dark:bg-boxdark' : 'bg-light-gradient dark:bg-dark-gradient border-r border-stroke dark:border-strokedark transition-all backdrop-blur-2xl'}`}>

src/pages/article/index.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -659,30 +659,33 @@ export default () => {
659659
<Skeleton.Input active size="default" style={{ width: 250, height: 32 }} />
660660
<Skeleton.Button active size="default" style={{ width: 80, height: 32 }} />
661661
</div>
662+
<div className="flex space-x-3 sm:pl-32 pr-10">
663+
<Skeleton.Button active size="default" style={{ width: 120, height: 32 }} />
664+
<Skeleton.Button active size="default" style={{ width: 100, height: 32 }} />
665+
<Skeleton.Button active size="default" style={{ width: 100, height: 32 }} />
666+
</div>
662667
</div>
663668
</Card>
664669

665670
{/* 表格卡片骨架屏 */}
666671
<Card className={`${titleSty} min-h-[calc(100vh-250px)] [&>.ant-card-body]:!py-2 [&>.ant-card-body]:!px-5`}>
667-
{/* 表格头部骨架屏 */}
672+
{/* 表格骨架屏 */}
668673
<div className="mb-4">
669674
{/* 表格行骨架屏 - 模拟多行 */}
670675
{[1, 2, 3, 4, 5, 6, 7, 8].map((item) => (
671676
<div key={item} className="flex items-center gap-4 mb-2 py-2 border-b border-gray-100">
672-
<Skeleton.Input active size="small" className="1/12 h-5" />
673-
<Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
674-
<Skeleton.Input active size="small" style={{ width: 200, height: 20, flex: 1 }} />
675-
<Skeleton.Input active size="small" style={{ width: 250, height: 20, flex: 1 }} />
676-
<Skeleton.Input active size="small" style={{ width: 80, height: 20 }} />
677-
<Skeleton.Input active size="small" style={{ width: 80, height: 20 }} />
678-
<Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
679-
<Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
680-
<Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
681-
<Skeleton.Input active size="small" style={{ width: 150, height: 20 }} />
682-
<Skeleton.Input active size="small" style={{ width: 100, height: 20 }} />
677+
<Skeleton.Input active size="small" style={{ width: 60, height: 40 }} />
678+
<Skeleton.Input active size="small" style={{ width: 150, height: 40 }} />
679+
<Skeleton.Input active size="small" style={{ width: 200, height: 40, flex: 1 }} />
680+
<Skeleton.Input active size="small" style={{ width: 150, height: 40 }} />
681+
<Skeleton.Input active size="small" style={{ width: 200, height: 40 }} />
682+
<Skeleton.Input active size="small" style={{ width: 100, height: 40 }} />
683+
<Skeleton.Input active size="small" style={{ width: 300, height: 40 }} />
684+
<Skeleton.Input active size="small" style={{ width: 200, height: 40 }} />
683685
</div>
684686
))}
685687
</div>
688+
686689
{/* 分页骨架屏 */}
687690
<div className="flex justify-center my-5">
688691
<Skeleton.Input active size="default" style={{ width: 300, height: 32 }} />

src/pages/cate/index.tsx

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useRef } from 'react';
22

33
import { DownOutlined } from '@ant-design/icons';
4-
import { Form, Input, Button, Tree, Modal, Spin, Dropdown, Card, MenuProps, Popconfirm, message, Radio, Select } from 'antd';
4+
import { Form, Input, Button, Tree, Modal, Spin, Dropdown, Card, MenuProps, Popconfirm, message, Radio, Select, Skeleton } from 'antd';
55
import type { DataNode } from 'antd/es/tree';
66

77
import { Cate } from '@/types/app/cate';
@@ -12,8 +12,10 @@ import './index.scss';
1212

1313
export default () => {
1414
const [loading, setLoading] = useState(false);
15+
const [initialLoading, setInitialLoading] = useState<boolean>(true);
1516
const [btnLoading, setBtnLoading] = useState(false);
1617
const [editLoading, setEditLoading] = useState(false);
18+
const isFirstLoadRef = useRef<boolean>(true);
1719

1820
const [isModelOpen, setIsModelOpen] = useState(false);
1921
const [cate, setCate] = useState<Cate>({} as Cate);
@@ -24,15 +26,21 @@ export default () => {
2426

2527
const getCateList = async () => {
2628
try {
27-
setLoading(true);
29+
// 如果是第一次加载,使用 initialLoading
30+
if (isFirstLoadRef.current) {
31+
setInitialLoading(true);
32+
} else {
33+
setLoading(true);
34+
}
2835

2936
const { data } = await getCateListAPI();
3037
data.sort((a, b) => a.order - b.order);
3138
setList(data);
32-
33-
setLoading(false);
39+
isFirstLoadRef.current = false;
3440
} catch (error) {
3541
console.error(error);
42+
} finally {
43+
setInitialLoading(false);
3644
setLoading(false);
3745
}
3846
};
@@ -173,6 +181,44 @@ export default () => {
173181
})),
174182
];
175183

184+
// 初始加载时显示骨架屏
185+
if (initialLoading) {
186+
return (
187+
<div>
188+
{/* Title 骨架屏 */}
189+
<Card className="[&>.ant-card-body]:!py-2 [&>.ant-card-body]:!px-5 mb-4">
190+
<div className="flex justify-between items-center">
191+
<Skeleton.Input active size="large" style={{ width: 150, height: 32 }} />
192+
<Skeleton.Button active size="large" style={{ width: 120, height: 40 }} />
193+
</div>
194+
</Card>
195+
196+
{/* 树形结构骨架屏 */}
197+
<Card className={`border-stroke [&>.ant-card-body]:!p-[30px_20px] [&>.ant-card-body]:!pb-6 mt-2 min-h-[calc(100vh-160px)]`}>
198+
{[1, 2, 3, 4, 5, 6].map((item) => (
199+
<div key={item} className="mb-4">
200+
<div className="flex items-center justify-between mb-2">
201+
<Skeleton.Input active size="default" style={{ width: 200, height: 24 }} />
202+
<Skeleton.Button active size="small" style={{ width: 60, height: 24 }} />
203+
</div>
204+
{/* 子项骨架屏 */}
205+
{item <= 3 && (
206+
<div className="ml-6 space-y-2">
207+
{[1, 2, 3].map((child) => (
208+
<div key={child} className="flex items-center justify-between">
209+
<Skeleton.Input active size="small" style={{ width: 150, height: 20 }} />
210+
<Skeleton.Button active size="small" style={{ width: 60, height: 20 }} />
211+
</div>
212+
))}
213+
</div>
214+
)}
215+
</div>
216+
))}
217+
</Card>
218+
</div>
219+
);
220+
}
221+
176222
return (
177223
<div>
178224
<Title value="分类管理">

src/pages/comment/index.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -240,19 +240,23 @@ export default () => {
240240

241241
{/* 表格卡片骨架屏 */}
242242
<Card className={`${titleSty} mt-2 min-h-[calc(100vh-270px)] [&>.ant-card-body]:!py-2 [&>.ant-card-body]:!px-5`}>
243-
{/* 表格行骨架屏 - 模拟多行 */}
244-
{[1, 2, 3, 4, 5, 6, 7, 8].map((item) => (
245-
<div key={item} className="flex items-center gap-4 mb-2 py-2 border-b border-gray-100">
246-
<Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
247-
<Skeleton.Input active size="small" style={{ width: 100, height: 20 }} />
248-
<Skeleton.Input active size="small" style={{ width: 250, height: 20, flex: 1 }} />
249-
<Skeleton.Input active size="small" style={{ width: 120, height: 20 }} />
250-
<Skeleton.Input active size="small" style={{ width: 150, height: 20 }} />
251-
<Skeleton.Input active size="small" style={{ width: 150, height: 20 }} />
252-
<Skeleton.Input active size="small" style={{ width: 150, height: 20 }} />
253-
<Skeleton.Input active size="small" style={{ width: 100, height: 20 }} />
254-
</div>
255-
))}
243+
{/* 表格骨架屏 */}
244+
<div className="mb-4">
245+
{/* 表格行骨架屏 - 模拟多行 */}
246+
{[1, 2, 3, 4, 5, 6, 7, 8].map((item) => (
247+
<div key={item} className="flex items-center gap-4 mb-2 py-2 border-b border-gray-100">
248+
<Skeleton.Input active size="small" style={{ width: 60, height: 40 }} />
249+
<Skeleton.Input active size="small" style={{ width: 150, height: 40 }} />
250+
<Skeleton.Input active size="small" style={{ width: 200, height: 40, flex: 1 }} />
251+
<Skeleton.Input active size="small" style={{ width: 150, height: 40 }} />
252+
<Skeleton.Input active size="small" style={{ width: 200, height: 40 }} />
253+
<Skeleton.Input active size="small" style={{ width: 100, height: 40 }} />
254+
<Skeleton.Input active size="small" style={{ width: 300, height: 40 }} />
255+
<Skeleton.Input active size="small" style={{ width: 200, height: 40 }} />
256+
</div>
257+
))}
258+
</div>
259+
256260
{/* 分页骨架屏 */}
257261
<div className="flex justify-center my-5">
258262
<Skeleton.Input active size="default" style={{ width: 300, height: 32 }} />

0 commit comments

Comments
 (0)