feat: 优化代码
This commit is contained in:
parent
346cfa7ab3
commit
e2d9348ee0
|
|
@ -1,75 +0,0 @@
|
|||
'use client';
|
||||
import {
|
||||
GenerateInputIcon,
|
||||
PhoneCallIcon,
|
||||
PortraitModeIcon,
|
||||
} from '@/assets/chatacter';
|
||||
import { useChatStore } from '../../store';
|
||||
import IconFont from '@/components/ui/iconFont';
|
||||
import { cn } from '@/lib';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function Actions() {
|
||||
const isPortraitMode = useChatStore((state) => state.isPortraitMode);
|
||||
const setIsPortraitMode = useChatStore((state) => state.setIsPortraitMode);
|
||||
const setIsPhoneCallMode = useChatStore((state) => state.setIsPhoneCallMode);
|
||||
const className = 'text-[#0066FF] cursor-pointer hover:text-[#4269D6]';
|
||||
|
||||
const suggestMessages = [
|
||||
'The threads of fate intertwine once more...The threads of fate intert',
|
||||
'The threads of fate intertwine once more.',
|
||||
'The threads of fate intertwine once more.',
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="relative mb-5 flex flex-col gap-2">
|
||||
{suggestMessages.map((message, index) => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'rgba(36, 44, 80, 0.9)',
|
||||
width: '70%',
|
||||
}}
|
||||
className="flex h-10 cursor-pointer items-center rounded-full px-4"
|
||||
key={`message-${index}`}
|
||||
>
|
||||
<span className="line-clamp-2 text-xs">{message}</span>
|
||||
</div>
|
||||
))}
|
||||
<span
|
||||
style={{ left: '71%' }}
|
||||
className="flex-center absolute bottom-0 h-7 w-7 cursor-pointer rounded-full bg-black/40"
|
||||
>
|
||||
<IconFont type="icon-zhongxie" size={18} />
|
||||
</span>
|
||||
</div>
|
||||
{/* action */}
|
||||
<div className="flex justify-between">
|
||||
<div onClick={() => null} className="flex items-center gap-5">
|
||||
<div className={className}>
|
||||
<GenerateInputIcon />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setIsPhoneCallMode(true)}
|
||||
className={cn(className, 'relative')}
|
||||
>
|
||||
<PhoneCallIcon />
|
||||
<Image
|
||||
className="absolute right-0 bottom-0"
|
||||
src="/component/vip.svg"
|
||||
alt="phone call"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setIsPortraitMode(!isPortraitMode)}
|
||||
className="hover:cursor-pointer"
|
||||
>
|
||||
<PortraitModeIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ export default function PhoneCallMode() {
|
|||
'bg-white/10 hover:bg-white/20'
|
||||
)}
|
||||
>
|
||||
<IconFont type="icon-tonghua" size={20} />
|
||||
<IconFont type="icon-zimu" size={22} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
'use client';
|
||||
import { TagSelect, VirtualGrid, Rate } from '@/components';
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useInfiniteScroll } from '@/hooks/useInfiniteScroll';
|
||||
import useSmartInfiniteQuery from '@/hooks/useSmartInfiniteQuery';
|
||||
import { fetchCharacters } from './service-client';
|
||||
import { fetchTags } from '@/services/tag';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
|
@ -16,11 +15,16 @@ const RoleCard: React.FC<any> = React.memo(({ item }) => {
|
|||
onClick={() => router.push(`/character/${item.id}`)}
|
||||
className="cover-bg relative flex h-full w-full cursor-pointer flex-col justify-between overflow-hidden rounded-[20]"
|
||||
style={{
|
||||
backgroundImage: `url(${item.from || '/test.png'})`,
|
||||
backgroundImage: `url(${item.coverImage || '/test.png'})`,
|
||||
}}
|
||||
>
|
||||
{/* from */}
|
||||
<Image src={item.from || '/test.png'} alt="from" width={55} height={78} />
|
||||
<img
|
||||
src={item.coverImage || '/test.png'}
|
||||
alt="from"
|
||||
width={55}
|
||||
height={78}
|
||||
/>
|
||||
|
||||
{/* info */}
|
||||
<div className="px-2.5 pb-3">
|
||||
|
|
@ -54,8 +58,9 @@ export default function Novel() {
|
|||
noMoreData,
|
||||
onLoadMore,
|
||||
onSearch,
|
||||
} = useInfiniteScroll(fetchCharacters, {
|
||||
} = useSmartInfiniteQuery(fetchCharacters, {
|
||||
queryKey: 'characters',
|
||||
defaultQuery: { tagId: undefined },
|
||||
});
|
||||
|
||||
// 使用useQuery查询tags
|
||||
|
|
@ -84,7 +89,7 @@ export default function Novel() {
|
|||
options={tags}
|
||||
render={(item) => `# ${item.label}`}
|
||||
onChange={(v) => {
|
||||
onSearch({ tagId: v });
|
||||
onSearch({ tagId: v as any });
|
||||
}}
|
||||
className="mx-12.5 my-7.5 mb-7.5"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default function RootLayout({
|
|||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<Script
|
||||
src="//at.alicdn.com/t/c/font_5054282_z80k01jnmui.js"
|
||||
src="//at.alicdn.com/t/c/font_5054282_g1w4osco3ua.js"
|
||||
strategy="afterInteractive"
|
||||
async
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
import { keepPreviousData, useQuery } from '@tanstack/react-query';
|
||||
import { useDebounceFn, useMemoizedFn, useThrottleFn } from 'ahooks';
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
type ParamsType<Q = any> = {
|
||||
index: number;
|
||||
limit: number;
|
||||
query: Q;
|
||||
};
|
||||
|
||||
type PropsType<T = any, Q = any> = {
|
||||
queryKey: string;
|
||||
defaultQuery?: Q;
|
||||
defaultIndex?: number;
|
||||
limit?: number;
|
||||
isRowSame?: (d: T, n: T) => boolean;
|
||||
};
|
||||
|
||||
type RequestType<T = any, Q = any> = (
|
||||
params: ParamsType<Q>
|
||||
) => Promise<{ rows: T[]; total: number } | undefined>;
|
||||
|
||||
type UseInfiniteScrollValue<T = any, Q = any> = {
|
||||
query: Q;
|
||||
onLoadMore: () => void;
|
||||
onSearch: (query: Q) => void;
|
||||
dataSource: T[];
|
||||
total: number;
|
||||
// 是否正在加载第一页,包括 初始化加载 和 参数改变时加载
|
||||
isFirstLoading: boolean;
|
||||
isLoadingMore: boolean;
|
||||
noMoreData: boolean;
|
||||
};
|
||||
|
||||
type DataType<T = any> = {
|
||||
rows: T[];
|
||||
total: number;
|
||||
index: number;
|
||||
};
|
||||
|
||||
const useSmartInfiniteQuery = <T = any, Q = any>(
|
||||
request: RequestType<T, Q>,
|
||||
props: PropsType<T, Q>
|
||||
): UseInfiniteScrollValue<T, Q> => {
|
||||
const {
|
||||
queryKey,
|
||||
defaultQuery,
|
||||
defaultIndex = 1,
|
||||
limit = 20,
|
||||
isRowSame = (d, n) => {
|
||||
return (d as any)?.id === (n as any)?.id;
|
||||
},
|
||||
} = props;
|
||||
const [query, setQuery] = useState<Q>(defaultQuery as Q);
|
||||
const index = useRef<number>(defaultIndex);
|
||||
|
||||
// 判断第一页数据和缓存中的第一页数据是否相等
|
||||
const isSameData = useMemoizedFn(
|
||||
(prevData: DataType<T>, result: Omit<DataType<T>, 'index'>) => {
|
||||
// 没有缓存,必不相等
|
||||
if (prevData.total <= 0 || !prevData.rows?.length) {
|
||||
return false;
|
||||
}
|
||||
// 如果第一个元素相等,则认为数据相等(实际上并不一定)
|
||||
if (!isRowSame(prevData.rows[0], result?.rows[0])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
const { data, refetch, isFetching } = useQuery({
|
||||
queryKey: [queryKey, query],
|
||||
placeholderData: keepPreviousData,
|
||||
queryFn: async ({ client }) => {
|
||||
const params = {
|
||||
index: index.current,
|
||||
limit,
|
||||
query,
|
||||
};
|
||||
const result = await request(params);
|
||||
const prevData = (client.getQueryData([
|
||||
queryKey,
|
||||
query,
|
||||
]) as DataType<T>) ?? {
|
||||
rows: [],
|
||||
total: 0,
|
||||
index: 1,
|
||||
};
|
||||
|
||||
// 如果是第一页
|
||||
if (params.index === defaultIndex) {
|
||||
// 第一页数据和缓存中相等
|
||||
if (isSameData(prevData, result!)) {
|
||||
// 更新索引
|
||||
index.current = prevData.index;
|
||||
// 直接返回缓存里的
|
||||
return prevData;
|
||||
} else {
|
||||
// 第一页数据和缓存中不等, 返回新的数据
|
||||
return {
|
||||
total: result?.total || 0,
|
||||
rows: result?.rows || [],
|
||||
index: params.index,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 不是第一页, 返回合并后的数据
|
||||
return {
|
||||
total: result?.total || 0,
|
||||
rows: [...(prevData?.rows || []), ...(result?.rows || [])],
|
||||
index: params.index,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const { run: onLoadMore } = useDebounceFn(
|
||||
() => {
|
||||
if (isFetching) return;
|
||||
index.current = index.current + 1;
|
||||
refetch();
|
||||
},
|
||||
{
|
||||
wait: 300,
|
||||
maxWait: 500,
|
||||
}
|
||||
);
|
||||
|
||||
const onSearch = useMemoizedFn((query: Q) => {
|
||||
index.current = defaultIndex;
|
||||
setQuery(query);
|
||||
});
|
||||
|
||||
return {
|
||||
query,
|
||||
onLoadMore,
|
||||
onSearch,
|
||||
dataSource: data?.rows || [],
|
||||
total: data?.total || 0,
|
||||
isFirstLoading: isFetching && index.current === defaultIndex,
|
||||
isLoadingMore: isFetching && index.current > defaultIndex,
|
||||
noMoreData: data?.total === data?.rows?.length && !isFetching,
|
||||
};
|
||||
};
|
||||
|
||||
export default useSmartInfiniteQuery;
|
||||
|
|
@ -33,12 +33,10 @@ interface IntlProviderProps {
|
|||
}
|
||||
|
||||
function setLocaleToCookie(locale: Locale) {
|
||||
if (typeof window === 'undefined') return;
|
||||
Cookies.set('locale', locale, { expires: 365, path: '/' });
|
||||
}
|
||||
|
||||
function getLocaleFromCookie(): Locale {
|
||||
if (typeof window === 'undefined') return 'en';
|
||||
const cookieLocale = Cookies.get('locale') as Locale | undefined;
|
||||
if (cookieLocale && (cookieLocale === 'zh' || cookieLocale === 'en')) {
|
||||
return cookieLocale;
|
||||
|
|
|
|||
Loading…
Reference in New Issue