mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-09-03 06:24:24 +08:00
refactor(components, config): 重构配置管理和组件导入以提升一致性和性能
- 更新多个组件以使用新的 AppConfigProvider,简化配置获取逻辑。 - 移除不再使用的 InjectData 组件,整合配置管理。 - 优化主题和其他配置的获取方式,提升组件性能和可维护性。 - 更新相关组件以适应新的配置结构,确保功能正常。
This commit is contained in:
@@ -9,7 +9,7 @@ import HCaptchaType from '@hcaptcha/react-hcaptcha';
|
||||
import List from './components/List';
|
||||
import HCaptcha from '@/components/HCaptcha';
|
||||
import EmojiBag from '@/components/EmojiBag';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import './index.scss';
|
||||
|
||||
@@ -52,9 +52,8 @@ const CommentForm = ({ articleId }: Props) => {
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||
const [captchaError, setCaptchaError] = useState<string>('');
|
||||
|
||||
// 获取HCaptcha配置
|
||||
const config = useConfigStore();
|
||||
const hasHCaptcha = !!config?.other?.hcaptcha_key;
|
||||
const { other } = useAppConfig();
|
||||
const hasHCaptcha = !!other?.hcaptcha_key;
|
||||
|
||||
const {
|
||||
register,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useAuthorStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { FiShield, FiUser } from 'react-icons/fi';
|
||||
|
||||
const Copyright = () => {
|
||||
const author = useAuthorStore((state) => state.author);
|
||||
const { author } = useAppConfig();
|
||||
|
||||
// 增加一个 fallback,防止未加载时出现空隙
|
||||
const authorName = author?.name || '匿名作者';
|
||||
|
||||
@@ -4,13 +4,13 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getRandomArticleListAPI } from '@/api/article';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { Article } from '@/types/app/article';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import RandomArticleSvg from '@/assets/svg/other/article.svg';
|
||||
|
||||
const RandomArticle = () => {
|
||||
const { theme } = useConfigStore();
|
||||
const { theme } = useAppConfig();
|
||||
|
||||
const [list, setList] = useState<Article[]>([]);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { addWebDataAPI, getWebTypeListAPI } from '@/api/web';
|
||||
import { Bounce, toast, ToastOptions } from 'react-toastify';
|
||||
import HCaptchaType from '@hcaptcha/react-hcaptcha';
|
||||
import HCaptcha from '@/components/HCaptcha';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
const toastConfig: ToastOptions = {
|
||||
@@ -40,8 +40,8 @@ export default () => {
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||
const [captchaError, setCaptchaError] = useState<string>('');
|
||||
|
||||
const config = useConfigStore();
|
||||
const hasHCaptcha = !!config?.other?.hcaptcha_key;
|
||||
const { other } = useAppConfig();
|
||||
const hasHCaptcha = !!other?.hcaptcha_key;
|
||||
|
||||
const [typeList, setTypeList] = useState<WebType[]>([]);
|
||||
const getWebTypeList = async () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from 'next/link';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { Web } from '@/types/app/web';
|
||||
import { useConfigStore, useAuthorStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import ApplyForAdd from './components/ApplyForAdd';
|
||||
|
||||
// 默认头像
|
||||
@@ -223,8 +223,7 @@ const FriendCard = ({ item, type, index }: { item: Web; type: string; index: num
|
||||
};
|
||||
|
||||
export default ({ data }: { data: { [string: string]: { order: number; list: Web[] } } }) => {
|
||||
const web = useConfigStore((state) => state.web);
|
||||
const author = useAuthorStore((state) => state.author);
|
||||
const { web, author } = useAppConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -10,8 +10,7 @@ import Tools from '@/components/Tools';
|
||||
import Confetti from '@/components/Confetti';
|
||||
import RouteChangeHandler from '@/components/RouteChangeHandler';
|
||||
|
||||
import { getWebConfigDataAPI } from '@/api/config';
|
||||
import { Web } from '@/types/app/config';
|
||||
import { getAppConfigCacheAPI, getWebConfigCacheAPI } from '@/lib/config';
|
||||
|
||||
// 加载样式文件
|
||||
import '@/styles/tailwind.css';
|
||||
@@ -19,7 +18,7 @@ import '@/styles/global.scss';
|
||||
import '@/styles/index.scss';
|
||||
import BaiduStatis from '@/components/BaiduStatis';
|
||||
import FloatingBlock from '@/components/FloatingBlock';
|
||||
import InjectData from '@/components/InjectData';
|
||||
import AppConfigProvider from '@/components/AppConfigProvider';
|
||||
|
||||
// 加载本地字体
|
||||
const LXGWWenKai = localFont({
|
||||
@@ -29,8 +28,7 @@ const LXGWWenKai = localFont({
|
||||
|
||||
// 生成动态metadata
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const response = await getWebConfigDataAPI<{ value: Web }>('web');
|
||||
const data = response?.data?.value as Web;
|
||||
const data = await getWebConfigCacheAPI();
|
||||
|
||||
return {
|
||||
title: {
|
||||
@@ -93,8 +91,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
}
|
||||
|
||||
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||
const response = await getWebConfigDataAPI<{ value: Web }>('web');
|
||||
const data = response?.data?.value as Web;
|
||||
const { web: data, theme, other, author } = await getAppConfigCacheAPI();
|
||||
|
||||
return (
|
||||
<html lang="zh-CN" className={LXGWWenKai.className}>
|
||||
@@ -108,33 +105,33 @@ export default async function RootLayout({ children }: Readonly<{ children: Reac
|
||||
</>
|
||||
)}
|
||||
{/* 百度统计 */}
|
||||
<BaiduStatis />
|
||||
<BaiduStatis other={other} />
|
||||
</head>
|
||||
|
||||
{/* 监听路由变化 */}
|
||||
<RouteChangeHandler />
|
||||
|
||||
<body id="root" className={`dark:bg-black-a!`}>
|
||||
{/* 数据注入 */}
|
||||
<InjectData />
|
||||
{/* 🎉 礼花效果 */}
|
||||
{/* <Confetti /> */}
|
||||
<AppConfigProvider web={data} theme={theme} other={other} author={author}>
|
||||
{/* 🎉 礼花效果 */}
|
||||
{/* <Confetti /> */}
|
||||
|
||||
{/* 进度条组件 */}
|
||||
<NProgress />
|
||||
{/* 顶部导航组件 */}
|
||||
<Header />
|
||||
{/* 进度条组件 */}
|
||||
<NProgress />
|
||||
{/* 顶部导航组件 */}
|
||||
<Header theme={theme} />
|
||||
|
||||
{/* 主体内容 */}
|
||||
<div className="min-h-[calc(100vh-300px)]">{children}</div>
|
||||
{/* 主体内容 */}
|
||||
<div className="min-h-[calc(100vh-300px)]">{children}</div>
|
||||
|
||||
{/* 底部组件 */}
|
||||
<Footer />
|
||||
{/* 右侧工具栏组件 */}
|
||||
{/* <Tools /> */}
|
||||
{/* 底部组件 */}
|
||||
<Footer />
|
||||
{/* 右侧工具栏组件 */}
|
||||
{/* <Tools /> */}
|
||||
|
||||
{/* 悬浮块 */}
|
||||
<FloatingBlock />
|
||||
{/* 悬浮块 */}
|
||||
<FloatingBlock />
|
||||
</AppConfigProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -5,8 +5,7 @@ import Container from '@/components/Container';
|
||||
import ArticleLayout from '@/components/ArticleLayout';
|
||||
import Sidebar from '@/components/Sidebar';
|
||||
|
||||
import { getWebConfigDataAPI } from '@/api/config';
|
||||
import { Theme } from '@/types/app/config';
|
||||
import { getThemeConfigCacheAPI, getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{ page: number }>;
|
||||
@@ -15,18 +14,17 @@ interface Props {
|
||||
export default async (props: Props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const page = searchParams.page ?? 1;
|
||||
const response = await getWebConfigDataAPI<{ value: Theme }>('theme');
|
||||
const data = response?.data?.value as Theme;
|
||||
const [theme, covers] = await Promise.all([getThemeConfigCacheAPI(), getThemeCoversCacheAPI()]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Lantern data={['新', '春', '快', '乐']} /> */}
|
||||
|
||||
<Slide src={data?.swiper_image}>
|
||||
<Slide src={theme?.swiper_image} covers={covers}>
|
||||
{/* 星空背景组件 */}
|
||||
<Starry />
|
||||
{/* 打字机组件 */}
|
||||
<Typed className="absolute top-[45%] sm:top-[40%] left-[50%] transform -translate-x-1/2 w-[80%] text-center text-white xs:text-xl sm:text-[30px] leading-7 sm:leading-[40px] md:leading-[50px] custom_text_shadow"></Typed>
|
||||
<Typed swiperText={theme?.swiper_text} className="absolute top-[45%] sm:top-[40%] left-[50%] transform -translate-x-1/2 w-[80%] text-center text-white xs:text-xl sm:text-[30px] leading-7 sm:leading-[40px] md:leading-[50px] custom_text_shadow"></Typed>
|
||||
</Slide>
|
||||
|
||||
<Container>
|
||||
|
||||
@@ -12,7 +12,7 @@ import HCaptcha from '@/components/HCaptcha';
|
||||
import Show from '@/components/Show';
|
||||
import { addRecordCommentDataAPI, getRecordCommentListAPI } from '@/api/recordComment';
|
||||
import { RecordComment } from '@/types/app/recordComment';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
|
||||
interface Props {
|
||||
recordId: number;
|
||||
@@ -50,8 +50,8 @@ export default function RecordCommentPanel({ recordId, onCountChange }: Props) {
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||
const [captchaError, setCaptchaError] = useState('');
|
||||
|
||||
const config = useConfigStore();
|
||||
const hasHCaptcha = !!config?.other?.hcaptcha_key;
|
||||
const { other } = useAppConfig();
|
||||
const hasHCaptcha = !!other?.hcaptcha_key;
|
||||
|
||||
const methods = useForm<CommentForm>({});
|
||||
const { setValue, setFocus, reset, handleSubmit } = methods;
|
||||
|
||||
@@ -3,6 +3,7 @@ import Slide from '@/components/Slide';
|
||||
import Classics from '@/components/ArticleLayout/Classics';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { getTagArticleListAPI } from '@/api/tag';
|
||||
import { getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: number }>;
|
||||
@@ -16,7 +17,10 @@ export default async (props: Props) => {
|
||||
const page = searchParams.page ?? 1;
|
||||
const name = searchParams.name;
|
||||
|
||||
const { data } = await getTagArticleListAPI(id, { pageNum: page, pageSize: 8 });
|
||||
const [{ data }, covers] = await Promise.all([
|
||||
getTagArticleListAPI(id, { pageNum: page, pageSize: 8 }),
|
||||
getThemeCoversCacheAPI(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -24,7 +28,7 @@ export default async (props: Props) => {
|
||||
<meta name="description" content={name} />
|
||||
|
||||
<div>
|
||||
<Slide isRipple={false}>
|
||||
<Slide isRipple={false} covers={covers}>
|
||||
{/* 星空背景组件 */}
|
||||
<Starry />
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { addWallDataAPI, getCateListAPI } from '@/api/wall';
|
||||
import { Bounce, toast, ToastContainer, ToastOptions } from 'react-toastify';
|
||||
import HCaptchaType from '@hcaptcha/react-hcaptcha';
|
||||
import HCaptcha from '@/components/HCaptcha';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
const toastConfig: ToastOptions = {
|
||||
@@ -42,8 +42,8 @@ export default () => {
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||
const [captchaError, setCaptchaError] = useState<string>('');
|
||||
|
||||
const config = useConfigStore();
|
||||
const hasHCaptcha = !!config?.other?.hcaptcha_key;
|
||||
const { other } = useAppConfig();
|
||||
const hasHCaptcha = !!other?.hcaptcha_key;
|
||||
|
||||
const [cateList, setCateList] = useState<Cate[]>([]);
|
||||
const getCateList = async () => {
|
||||
|
||||
31
src/components/AppConfigProvider/index.tsx
Normal file
31
src/components/AppConfigProvider/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, type ReactNode } from 'react';
|
||||
|
||||
import { Other, Theme, Web } from '@/types/app/config';
|
||||
import { User } from '@/types/app/user';
|
||||
|
||||
interface AppConfigValue {
|
||||
web: Web;
|
||||
theme: Theme;
|
||||
other: Other;
|
||||
author: User;
|
||||
}
|
||||
|
||||
const AppConfigContext = createContext<AppConfigValue | null>(null);
|
||||
|
||||
interface Props extends AppConfigValue {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// 传递数据给子组件
|
||||
export default function AppConfigProvider({ web, theme, other, author, children }: Props) {
|
||||
return <AppConfigContext.Provider value={{ web, theme, other, author }}>{children}</AppConfigContext.Provider>;
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
export function useAppConfig() {
|
||||
const value = useContext(AppConfigContext);
|
||||
if (!value) throw new Error('useAppConfig 必须在 AppConfigProvider 内使用');
|
||||
return value;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import { getThemeCovers } from '@/lib/theme';
|
||||
import { getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
import { Article } from '@/types/app/article';
|
||||
import ArticleMeta from '@/components/ArticleLayout/components/ArticleMeta';
|
||||
import Empty from '@/components/Empty';
|
||||
@@ -11,7 +11,7 @@ interface CardProps {
|
||||
}
|
||||
|
||||
const Card = async ({ data }: CardProps) => {
|
||||
const covers = await getThemeCovers();
|
||||
const covers = await getThemeCoversCacheAPI();
|
||||
|
||||
const genArticleInfo = (data: Article) => {
|
||||
if (data.description?.trim()?.length) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import { getThemeCovers } from '@/lib/theme';
|
||||
import { getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
import { Article } from '@/types/app/article';
|
||||
import ArticleMeta from '@/components/ArticleLayout/components/ArticleMeta';
|
||||
import Empty from '@/components/Empty';
|
||||
@@ -11,7 +11,7 @@ interface ClassicsProps {
|
||||
}
|
||||
|
||||
const Classics = async ({ data }: ClassicsProps) => {
|
||||
const covers = await getThemeCovers();
|
||||
const covers = await getThemeCoversCacheAPI();
|
||||
|
||||
const genArticleInfo = (data: Article) => {
|
||||
if (data.description?.trim()?.length) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { Article } from '@/types/app/article';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
interface WaterfallProps {
|
||||
data: Paginate<Article[]>;
|
||||
covers: string[];
|
||||
}
|
||||
|
||||
const breakpointColumnsObj = {
|
||||
@@ -16,9 +16,7 @@ const breakpointColumnsObj = {
|
||||
700: 2,
|
||||
};
|
||||
|
||||
export default ({ data }: WaterfallProps) => {
|
||||
const { theme } = useConfigStore();
|
||||
|
||||
export default ({ data, covers }: WaterfallProps) => {
|
||||
return (
|
||||
<Masonry breakpointCols={breakpointColumnsObj} className="masonry-grid mb-12" columnClassName="masonry-grid_column">
|
||||
{data.result.map((item) => (
|
||||
@@ -27,7 +25,7 @@ export default ({ data }: WaterfallProps) => {
|
||||
<div className="overflow-hidden h-32">
|
||||
<div
|
||||
className="relative h-full bg-cover bg-no-repeat bg-center scale-100 hover:scale-125 z-10 transition-[scale] duration-300 ease-out"
|
||||
style={{ backgroundImage: `url(${getRandomImage(item.cover, theme.covers)})` }}
|
||||
style={{ backgroundImage: `url(${getRandomImage(item.cover, covers)})` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="py-2 px-4">
|
||||
|
||||
@@ -6,12 +6,13 @@ import Card from './Card';
|
||||
import Pagination from '../Pagination';
|
||||
|
||||
import { getArticlePagingAPI } from '@/api/article';
|
||||
import { getThemeConfig } from '@/lib/theme';
|
||||
import { getThemeConfigCacheAPI, getThemeCoversCacheAPI } from '@/lib/theme';
|
||||
import { getSwiperListAPI } from '@/api/swiper';
|
||||
|
||||
export default async ({ page }: { page: number }) => {
|
||||
const { data: swiper } = await getSwiperListAPI();
|
||||
const theme = await getThemeConfig();
|
||||
const theme = await getThemeConfigCacheAPI();
|
||||
const covers = await getThemeCoversCacheAPI();
|
||||
const sidebar = theme?.right_sidebar ?? [];
|
||||
|
||||
// 按order排序轮播图(顺序越小越靠前)
|
||||
@@ -32,7 +33,7 @@ export default async ({ page }: { page: number }) => {
|
||||
|
||||
{theme.is_article_layout === 'classics' && <Classics data={data} />}
|
||||
{theme.is_article_layout === 'card' && <Card data={data} />}
|
||||
{theme.is_article_layout === 'waterfall' && <Waterfall data={data} />}
|
||||
{theme.is_article_layout === 'waterfall' && <Waterfall data={data} covers={covers} />}
|
||||
|
||||
{!!data.total && <Pagination total={data?.pages} page={page} className="flex justify-center mt-5" />}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useConfigStore } from '@/stores';
|
||||
|
||||
export default function BaiduAnalytics() {
|
||||
const other = useConfigStore((state) => state.other);
|
||||
import { Other } from '@/types/app/config';
|
||||
|
||||
export default function BaiduAnalytics({ other }: { other: Other }) {
|
||||
useEffect(() => {
|
||||
if (other?.baidu_token) {
|
||||
window._hmt = window._hmt || [];
|
||||
|
||||
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
|
||||
import { Button, useDisclosure } from '@/ThriveUI';
|
||||
import { BiCog, BiCommand } from 'react-icons/bi';
|
||||
import { IoSearchOutline, IoArrowUpOutline, IoLogoRss } from 'react-icons/io5';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import Search from '../Search';
|
||||
import Rss from '../Tools/components/Rss';
|
||||
@@ -20,7 +21,8 @@ const FloatingBlock = () => {
|
||||
const [isExpanded, setIsExpanded] = useState(false); // 展开状态的变量
|
||||
const [isDragging, setIsDragging] = useState(false); // 拖拽状态
|
||||
const constraintsRef = useRef(null); // 拖拽约束参考
|
||||
const { isDark, setIsDark, web } = useConfigStore();
|
||||
const { web } = useAppConfig();
|
||||
const { isDark, setIsDark } = useConfigStore();
|
||||
const { isOpen: isSearchOpen, onOpen: onSearchOpen, onClose: onSearchClose } = useDisclosure();
|
||||
const { isOpen: isRssOpen, onOpen: onRssOpen, onClose: onRssClose } = useDisclosure();
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha';
|
||||
import { forwardRef, Ref } from 'react';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { useConfigStore } from '@/stores';
|
||||
|
||||
export default forwardRef(({ setToken }: { setToken: (token: string) => void }, ref: Ref<HCaptcha>) => {
|
||||
const config = useConfigStore();
|
||||
const sitekey = config?.other?.hcaptcha_key;
|
||||
const { other } = useAppConfig();
|
||||
const isDark = useConfigStore((state) => state.isDark);
|
||||
const sitekey = other?.hcaptcha_key;
|
||||
|
||||
// 如果没有配置 hcaptcha_key,不渲染组件
|
||||
if (!sitekey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HCaptcha theme={config.isDark ? 'dark' : 'light'} sitekey={sitekey} onVerify={setToken} ref={ref} />
|
||||
<HCaptcha theme={isDark ? 'dark' : 'light'} sitekey={sitekey} onVerify={setToken} ref={ref} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { LuMenu } from 'react-icons/lu';
|
||||
import { BsFillMoonStarsFill } from 'react-icons/bs';
|
||||
|
||||
import { Cate } from '@/types/app/cate';
|
||||
import { Theme } from '@/types/app/config';
|
||||
import { getCateListAPI } from '@/api/cate';
|
||||
import { getCateNavHref, getCateNavRel, getCateNavTarget } from '@/utils/cateNav';
|
||||
|
||||
@@ -25,10 +26,10 @@ const submenuPanelClass =
|
||||
const submenuItemClass =
|
||||
'group/item relative flex w-full items-center min-w-0 px-5 py-2.5 text-[15px] text-[#666] dark:text-white transition-colors duration-150 hover:text-primary! hover:bg-[#f2f2f2] dark:hover:bg-[#323e50] before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:h-0 before:w-[3px] before:rounded-r-full before:bg-primary before:transition-[height] before:duration-150 hover:before:h-[50%]';
|
||||
|
||||
export default () => {
|
||||
export default ({ theme }: { theme: Theme }) => {
|
||||
const patchName = usePathname();
|
||||
|
||||
const { isDark, setIsDark, theme } = useConfigStore();
|
||||
const { isDark, setIsDark } = useConfigStore();
|
||||
|
||||
// 这些路径段不需要改变导航样式
|
||||
const isPathSty = ['/my', '/wall', '/record', '/equipment', '/tags', '/resume', '/album', '/fishpond', '/friend'].some((path) => patchName.includes(path));
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { getWebConfigDataAPI } from '@/api/config';
|
||||
import { useAuthorStore, useConfigStore } from '@/stores';
|
||||
import { Web, Theme, Other } from '@/types/app/config';
|
||||
import { getAuthorDataAPI } from '@/api/user';
|
||||
|
||||
export default () => {
|
||||
const setAuthor = useAuthorStore((state) => state.setAuthor);
|
||||
|
||||
// 获取作者信息
|
||||
const getAuthorData = async () => {
|
||||
const { data: user } = await getAuthorDataAPI();
|
||||
setAuthor(user);
|
||||
};
|
||||
|
||||
const { setWeb, setTheme, setOther } = useConfigStore();
|
||||
|
||||
// 获取项目配置
|
||||
const getConfigData = async () => {
|
||||
const webResponse = await getWebConfigDataAPI<{ value: Web }>('web');
|
||||
const web = webResponse?.data?.value as Web;
|
||||
setWeb(web);
|
||||
|
||||
const themeResponse = await getWebConfigDataAPI<{ value: Theme }>('theme');
|
||||
const theme = themeResponse?.data?.value as Theme;
|
||||
setTheme(theme);
|
||||
|
||||
const otherResponse = await getWebConfigDataAPI<{ value: Other }>('other');
|
||||
const other = otherResponse?.data?.value as Other;
|
||||
setOther(other);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getAuthorData();
|
||||
getConfigData();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
actionPrimaryClass,
|
||||
actionTextColClass,
|
||||
} from '@/components/ActionCard/styles';
|
||||
import { useConfigStore, useAuthorStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { generateArticlePoster } from '@/utils/generateArticlePoster';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -46,8 +46,7 @@ export default function ArticleSharePoster({ data, minimal = false, className, s
|
||||
const [posterUrl, setPosterUrl] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const web = useConfigStore((s) => s.web);
|
||||
const author = useAuthorStore((s) => s.author);
|
||||
const { web, author } = useAppConfig();
|
||||
|
||||
const buildPoster = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getRandomArticleListAPI } from '@/api/article';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { Article } from '@/types/app/article';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import RandomArticleSvg from '@/assets/svg/other/article.svg';
|
||||
@@ -19,7 +19,7 @@ const RANKING_COLORS = [
|
||||
] as const;
|
||||
|
||||
const HotArticle = () => {
|
||||
const { theme } = useConfigStore();
|
||||
const { theme } = useAppConfig();
|
||||
|
||||
const [list, setList] = useState<Article[]>([]);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { motion, useMotionValue, useTransform, animate, useInView } from 'framer-motion';
|
||||
import TimerSvg from '@/assets/svg/other/timer.svg';
|
||||
import SidebarCard from '@/components/Sidebar/SidebarCard';
|
||||
@@ -188,7 +188,7 @@ const statItems = [
|
||||
] as const;
|
||||
|
||||
export default () => {
|
||||
const { web } = useConfigStore();
|
||||
const { web } = useAppConfig();
|
||||
const [timeDiff, setTimeDiff] = useState(() => calculateTimeDifference(web?.create_time));
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -3,19 +3,17 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import Ripple from '@/components/Ripple';
|
||||
import { getRandomImage } from '@/utils';
|
||||
import { useConfigStore } from '@/stores';
|
||||
|
||||
interface Props {
|
||||
src?: string;
|
||||
covers?: string[];
|
||||
isRipple?: boolean;
|
||||
fullImage?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export default ({ src, isRipple = true, fullImage = false, children }: Props) => {
|
||||
const theme = useConfigStore((state) => state.theme);
|
||||
|
||||
const fallbackImage = useMemo(() => getRandomImage(undefined, theme.covers), [theme.covers]);
|
||||
export default ({ src, covers = [], isRipple = true, fullImage = false, children }: Props) => {
|
||||
const fallbackImage = useMemo(() => getRandomImage(undefined, covers), [covers]);
|
||||
const bgImage = src?.trim() || fallbackImage;
|
||||
|
||||
const sty = {
|
||||
|
||||
@@ -7,6 +7,7 @@ import moon from './image/moon.svg';
|
||||
import search from './image/search.svg';
|
||||
import returnTop from './image/returnTop.svg';
|
||||
import rss from './image/rss.svg';
|
||||
import { useAppConfig } from '@/components/AppConfigProvider';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import Search from '../Search';
|
||||
import Rss from './components/Rss';
|
||||
@@ -14,7 +15,8 @@ import Rss from './components/Rss';
|
||||
const itemSty = 'p-2 hover:bg-[#edf5ff] dark:hover:bg-[#4e5969] cursor-pointer ';
|
||||
|
||||
export default () => {
|
||||
const { isDark, setIsDark, web } = useConfigStore();
|
||||
const { web } = useAppConfig();
|
||||
const { isDark, setIsDark } = useConfigStore();
|
||||
const { isOpen: isSwiper, onOpen: onSwiperOpen, onClose: onSwiperClose } = useDisclosure();
|
||||
const { isOpen: isRssOpen, onOpen: onRssOpen, onClose: onRssClose } = useDisclosure();
|
||||
const onReturnTop = () => {
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useConfigStore } from '@/stores';
|
||||
import Typed from 'typed.js';
|
||||
|
||||
export default ({ className }: { className?: string }) => {
|
||||
const { theme } = useConfigStore();
|
||||
interface Props {
|
||||
className?: string;
|
||||
swiperText?: string[];
|
||||
}
|
||||
|
||||
export default ({ className, swiperText = [] }: Props) => {
|
||||
const el = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (theme.swiper_text) {
|
||||
const typed = new Typed(el.current, {
|
||||
strings: theme.swiper_text,
|
||||
typeSpeed: 100,
|
||||
backSpeed: 30,
|
||||
loop: true,
|
||||
});
|
||||
if (!swiperText.length) return;
|
||||
|
||||
return () => typed.destroy();
|
||||
}
|
||||
}, [theme]);
|
||||
const typed = new Typed(el.current, {
|
||||
strings: swiperText,
|
||||
typeSpeed: 100,
|
||||
backSpeed: 30,
|
||||
loop: true,
|
||||
});
|
||||
|
||||
return () => typed.destroy();
|
||||
}, [swiperText]);
|
||||
|
||||
return <span ref={el} className={className} />;
|
||||
};
|
||||
|
||||
36
src/lib/config.ts
Normal file
36
src/lib/config.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { cache } from 'react';
|
||||
|
||||
import { getWebConfigDataAPI } from '@/api/config';
|
||||
import { getAuthorDataAPI } from '@/api/user';
|
||||
import { Other, Theme, Web } from '@/types/app/config';
|
||||
import { User } from '@/types/app/user';
|
||||
|
||||
export const getWebConfigCacheAPI = cache(async () => {
|
||||
const { data } = await getWebConfigDataAPI<{ value: Web }>('web');
|
||||
return data?.value as Web;
|
||||
});
|
||||
|
||||
export const getThemeConfigCacheAPI = cache(async () => {
|
||||
const { data } = await getWebConfigDataAPI<{ value: Theme }>('theme');
|
||||
return data?.value as Theme;
|
||||
});
|
||||
|
||||
export const getOtherConfigCacheAPI = cache(async () => {
|
||||
const { data } = await getWebConfigDataAPI<{ value: Other }>('other');
|
||||
return data?.value as Other;
|
||||
});
|
||||
|
||||
export const getAuthorDataCacheAPI = cache(async () => {
|
||||
const { data } = await getAuthorDataAPI();
|
||||
return data as User;
|
||||
});
|
||||
|
||||
export const getAppConfigCacheAPI = cache(async () => {
|
||||
const [web, theme, other, author] = await Promise.all([
|
||||
getWebConfigCacheAPI(),
|
||||
getThemeConfigCacheAPI(),
|
||||
getOtherConfigCacheAPI(),
|
||||
getAuthorDataCacheAPI(),
|
||||
]);
|
||||
return { web, theme, other, author };
|
||||
});
|
||||
@@ -1,12 +1,8 @@
|
||||
import { cache } from 'react';
|
||||
|
||||
import { getWebConfigDataAPI } from '@/api/config';
|
||||
import { Theme } from '@/types/app/config';
|
||||
import { getThemeConfigCacheAPI } from '@/lib/config';
|
||||
import { parseThemeCovers } from '@/utils/cover';
|
||||
|
||||
export const getThemeConfig = cache(async () => {
|
||||
const { data } = await getWebConfigDataAPI<{ value: Theme }>('theme');
|
||||
return data?.value as Theme;
|
||||
});
|
||||
export { getThemeConfigCacheAPI } from '@/lib/config';
|
||||
|
||||
export const getThemeCovers = cache(async () => parseThemeCovers((await getThemeConfig())?.covers));
|
||||
export const getThemeCoversCacheAPI = cache(async () => parseThemeCovers((await getThemeConfigCacheAPI())?.covers));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import useConfigStore from './modules/config'
|
||||
import useAuthorStore from './modules/author'
|
||||
import useConfigStore from './modules/config';
|
||||
|
||||
export { useConfigStore, useAuthorStore };
|
||||
export { useConfigStore };
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { User } from '@/types/app/user';
|
||||
|
||||
interface AuthorState {
|
||||
// 作者信息
|
||||
author: User;
|
||||
setAuthor: (data: User) => void;
|
||||
}
|
||||
|
||||
export default create<AuthorState>((set) => ({
|
||||
author: {} as User,
|
||||
setAuthor: (data: User) => set(() => ({ author: data })),
|
||||
}));
|
||||
@@ -1,43 +1,23 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||
import { Other, Theme, Web } from '@/types/app/config';
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
|
||||
interface ConfigState {
|
||||
// 是否暗黑模式
|
||||
interface ThemeState {
|
||||
isDark: boolean;
|
||||
setIsDark: (status: boolean) => void;
|
||||
|
||||
// 网站配置
|
||||
web: Web;
|
||||
setWeb: (data: Web) => void;
|
||||
|
||||
// 主题配置
|
||||
theme: Theme;
|
||||
setTheme: (data: Theme) => void;
|
||||
|
||||
// 其他配置
|
||||
other: Other;
|
||||
setOther: (data: Other) => void;
|
||||
}
|
||||
|
||||
type ThemePersist = Pick<ThemeState, 'isDark'>;
|
||||
|
||||
export default create(
|
||||
persist<ConfigState>(
|
||||
persist<ThemeState, [], [], ThemePersist>(
|
||||
(set) => ({
|
||||
isDark: false,
|
||||
setIsDark: (status: boolean) => set(() => ({ isDark: status })),
|
||||
|
||||
web: {} as Web,
|
||||
setWeb: (data: Web) => set(() => ({ web: data })),
|
||||
|
||||
theme: {} as Theme,
|
||||
setTheme: (data: Theme) => set(() => ({ theme: data })),
|
||||
|
||||
other: {} as Other,
|
||||
setOther: (data: Other) => set(() => ({ other: data }))
|
||||
setIsDark: (status: boolean) => set({ isDark: status }),
|
||||
}),
|
||||
{
|
||||
name: 'config_storage',
|
||||
storage: createJSONStorage(() => localStorage)
|
||||
}
|
||||
)
|
||||
)
|
||||
storage: createJSONStorage<ThemePersist>(() => localStorage),
|
||||
partialize: (state) => ({ isDark: state.isDark }),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user