diff --git a/src/app/(main)/chat/[id]/Drawer/index.tsx b/src/app/(main)/chat/[id]/Drawer/index.tsx index ce8f427..7901709 100644 --- a/src/app/(main)/chat/[id]/Drawer/index.tsx +++ b/src/app/(main)/chat/[id]/Drawer/index.tsx @@ -77,7 +77,7 @@ export default function SettingDialog({ open, onOpenChange }: SettingProps) { )} {activeTab !== 'profile' && ( setActiveTab('profile')}> - + )} diff --git a/src/app/(main)/chat/[id]/Input.tsx b/src/app/(main)/chat/[id]/Input.tsx index 0c00fa8..e1f4eb5 100644 --- a/src/app/(main)/chat/[id]/Input.tsx +++ b/src/app/(main)/chat/[id]/Input.tsx @@ -92,7 +92,7 @@ export default function Input() {
{/* 打电话按钮 */} {}}> - +
{/* 语音录制按钮 */} diff --git a/src/app/(main)/chat/[id]/page.tsx b/src/app/(main)/chat/[id]/page.tsx index 17efa2c..7a2e4dc 100644 --- a/src/app/(main)/chat/[id]/page.tsx +++ b/src/app/(main)/chat/[id]/page.tsx @@ -39,7 +39,7 @@ export default function ChatPage() { variant="ghost" size="small" > - +
diff --git a/src/app/(main)/home/components/Filter/index.tsx b/src/app/(main)/home/components/Filter/index.tsx index e4cc1a8..2a6dd83 100644 --- a/src/app/(main)/home/components/Filter/index.tsx +++ b/src/app/(main)/home/components/Filter/index.tsx @@ -11,12 +11,11 @@ import { TagListSelect } from './TagListSelect'; import { MoreFilter } from './MoreFilter'; const Filter = () => { - const tab = useHomeStore((state) => state.tab); - const setTab = useHomeStore((state) => state.setTab); const ref = useRef(null); - const characterParams = useHomeStore((state) => state.characterParams); - const setCharacterParams = useHomeStore((state) => state.setCharacterParams); + const { tab, setTab, characterParams, novelParams, setCharacterParams, setNovelParams } = + useHomeStore(); const t = useTranslations('home'); + const currentTabTagIds = tab === 'character' ? characterParams.tagIds : novelParams.tagIds; const { data: tags = [] } = useQuery({ queryKey: ['tags', tab], @@ -71,12 +70,16 @@ const Filter = () => { ); })}
- + {tab === 'character' && } ({ label: tag.name, value: tag.id }))} - value={characterParams.tagIds || []} - onChange={(values) => setCharacterParams({ tagIds: values as string[] })} + value={currentTabTagIds || []} + onChange={(values) => + tab === 'character' + ? setCharacterParams({ tagIds: values as string[] }) + : setNovelParams({ tagIds: values as string[] }) + } /> ); diff --git a/src/app/(main)/home/components/Story/index.tsx b/src/app/(main)/home/components/Story/index.tsx index b4cb654..89db513 100644 --- a/src/app/(main)/home/components/Story/index.tsx +++ b/src/app/(main)/home/components/Story/index.tsx @@ -1,7 +1,41 @@ 'use client'; +import { InfiniteScrollList } from '@/components/ui/infinite-scroll-list'; +import AIStandardCard from '@/components/features/ai-standard-card'; +import useSmartInfiniteQuery from '../../useSmartInfiniteQuery'; +import { fetchCharacters } from '@/services/editor'; +import { useHomeStore } from '../../store'; +import { useEffect } from 'react'; +import StoryContent from '@/components/features/StoryContent'; const Story = () => { - return
开发中ing
; + const characterParams = useHomeStore((state) => state.characterParams); + + // const { dataSource, isFirstLoading, isLoadingMore, noMoreData, onLoadMore, onSearch } = + // useSmartInfiniteQuery(fetchCharacters, { + // queryKey: 'characters', + // defaultQuery: characterParams, + // }); + + // useEffect(() => { + // onSearch(characterParams); + // }, [characterParams]); + + return ( +
+ + {/* + items={dataSource} + enableLazyRender + lazyRenderMargin="500px" + columns={1} + renderItem={(character) => } + getItemKey={(character, index) => character.id + index} + hasNextPage={!noMoreData} + isLoading={isFirstLoading || isLoadingMore} + fetchNextPage={onLoadMore} + /> */} +
+ ); }; export default Story; diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index c935d73..88f8f8c 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -14,8 +14,8 @@ const HomePage = () => { return ( <> -
-
+
+
{tab === 'story' ? : } diff --git a/src/app/(main)/home/store.ts b/src/app/(main)/home/store.ts index 2a54b8c..94ae0ad 100644 --- a/src/app/(main)/home/store.ts +++ b/src/app/(main)/home/store.ts @@ -5,11 +5,17 @@ type CharacterParams = { tagIds?: string[]; }; +type NovelParams = { + tagIds?: string[]; +}; + interface HomeStore { tab: 'story' | 'character'; characterParams: CharacterParams; + novelParams: NovelParams; setTab: (tab: 'story' | 'character') => void; setCharacterParams: (params: Partial) => void; + setNovelParams: (params: Partial) => void; } export const useHomeStore = create((set, get) => ({ @@ -18,9 +24,16 @@ export const useHomeStore = create((set, get) => ({ genders: [], tagIds: [], }, + novelParams: { + tagIds: [], + }, setTab: (tab: 'story' | 'character') => set({ tab }), setCharacterParams: (params) => { const { characterParams } = get(); set({ characterParams: { ...characterParams, ...params } }); }, + setNovelParams: (params) => { + const { novelParams } = get(); + set({ novelParams: { ...novelParams, ...params } }); + }, })); diff --git a/src/app/(main)/profile/mask/page.tsx b/src/app/(main)/profile/mask/page.tsx index 91c6e75..b6fe7da 100644 --- a/src/app/(main)/profile/mask/page.tsx +++ b/src/app/(main)/profile/mask/page.tsx @@ -27,8 +27,7 @@ export default function MaskPage() { response?.isPC && ( router.push('/profile/mask/new')} - size={32} - className="text-white cursor-pointer" + className="text-white cursor-pointer text-3xl" type="icon-tianjia" /> ) diff --git a/src/components/features/StoryContent/ScrollBox.tsx b/src/components/features/StoryContent/ScrollBox.tsx new file mode 100644 index 0000000..484a4ad --- /dev/null +++ b/src/components/features/StoryContent/ScrollBox.tsx @@ -0,0 +1,94 @@ +'use client'; + +import IconFont from '@/components/ui/iconFont'; +import { cn } from '@/lib/utils'; +import React, { useCallback, useEffect, useRef } from 'react'; + +type ScrollBoxProps = { + children: React.ReactNode; + contentClassName?: string; // 内容容器的样式类名 +} & React.HTMLAttributes; + +export default function ScrollBox(props: ScrollBoxProps) { + const { children, contentClassName, ...rest } = props; + const containerRef = useRef(null); + const leftRef = useRef(null); + const rightRef = useRef(null); + + const handleScroll = useCallback(() => { + const container = containerRef.current; + if (!container) return; + const left = leftRef.current; + const right = rightRef.current; + if (!left || !right) return; + if (container.scrollLeft <= 0) { + left.style.display = 'none'; + } else { + left.style.display = 'flex'; + } + // 添加 1 像素的容差来处理浮点数精度问题 + const isAtEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 1; + if (isAtEnd) { + right.style.display = 'none'; + } else { + right.style.display = 'flex'; + } + }, []); + + useEffect(() => { + handleScroll(); + }, [handleScroll]); + + const scrollBy = (type: 'left' | 'right') => { + const container = containerRef.current; + if (!container) return; + container.scrollBy({ + left: type === 'left' ? -container.clientWidth * 0.8 : container.clientWidth * 0.8, + behavior: 'smooth', + }); + }; + + const iconClass = + 'rounded-full absolute h-8 w-8 sm:h-12 sm:w-12 cursor-pointer items-center justify-center z-10'; + const iconStyle: React.CSSProperties = { + background: 'var(--sys-color-surface-element-normal, rgba(251, 222, 255, 0.08))', + backdropFilter: 'blur(var(--sys-visualstyle-blur-s, 16px))', + top: '50%', + transform: 'translateY(-50%)', + }; + + return ( +
+
+ {children} +
+ + {/* 左箭头 */} + scrollBy('left')} + > + + + + {/* 右箭头 */} + scrollBy('right')} + > + + +
+ ); +} diff --git a/src/components/features/StoryContent/index.tsx b/src/components/features/StoryContent/index.tsx new file mode 100644 index 0000000..ebdd09a --- /dev/null +++ b/src/components/features/StoryContent/index.tsx @@ -0,0 +1,44 @@ +'use client'; + +import IconFont from '@/components/ui/iconFont'; +import ScrollBox from './ScrollBox'; +import { useState } from 'react'; + +interface StoryContentProps { + story?: any; + search?: string; +} +export default function StoryContent(props: StoryContentProps) { + const { story } = props; + + const [open, setOpen] = useState(false); + + const cList = [123, 123, 123, 123, 123, 123, 123, 123]; + + return ( +
+
+ + The Bossy CEO's Contract Lover + +
setOpen(true)} + className="cursor-pointer flex items-center gap-1 shrink-0" + > + More + +
+
+ + {cList.map((item, index) => ( +
+ {item} +
+ ))} +
+
+ ); +} diff --git a/src/components/features/ai-standard-card.tsx b/src/components/features/ai-standard-card.tsx index b79b490..428cb39 100644 --- a/src/components/features/ai-standard-card.tsx +++ b/src/components/features/ai-standard-card.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useRef, useEffect, useState } from 'react'; +import React, { useMemo } from 'react'; import { formatNumberToKMB } from '@/lib/utils'; import { Tag } from '@/components/ui/tag'; import { Avatar, AvatarImage } from '@/components/ui/avatar'; @@ -12,59 +12,55 @@ interface AIStandardCardProps { disableHover?: boolean; } +// 常量提取 +const MAX_DESCRIPTION_LINES = 7; +const MAX_VISIBLE_TAGS = 2; + +// 提取点赞数组件 +const LikeCount: React.FC<{ count: number }> = ({ count }) => ( +
+ + + {formatNumberToKMB(count)} + +
+); + +// 提取渐变遮罩层样式 +const gradientOverlayStyle = { + background: + 'var(--color-overlay-background, linear-gradient(180deg, rgba(33, 26, 43, 0.00) 0%, var(--sys-color-background-default, #211A2B) 100%))', + maskImage: 'linear-gradient(180deg, rgba(33, 26, 43, 0.00) 0%, #211A2B 100%)', +}; + const AIStandardCard: React.FC = React.memo( ({ character, disableHover = false }) => { - const { id, name, description, coverImage, headPortrait, tags } = character; - - const introContainerRef = useRef(null); - const introTextRef = useRef(null); - const [maxLines, setMaxLines] = useState(6); - - // 动态计算可用空间的行数 - useEffect(() => { - const calculateMaxLines = () => { - if (introContainerRef.current) { - const containerHeight = introContainerRef.current.offsetHeight; - const lineHeight = 20; // 对应 leading-[20px] - const calculatedLines = Math.floor(containerHeight / lineHeight); - // 确保至少显示 1 行,最多不超过合理的行数 - const finalLines = Math.max(1, Math.min(calculatedLines, 12)); - setMaxLines(finalLines); - } - }; - - calculateMaxLines(); - - // 监听窗口大小变化 - window.addEventListener('resize', calculateMaxLines); - return () => window.removeEventListener('resize', calculateMaxLines); - }, []); + const { id, name, description, coverImage, headPortrait, tags, likeCount = 0 } = character; // 获取显示的背景图片 const displayImage = coverImage; + // 可见标签 + const visibleTags = useMemo(() => tags?.slice(0, MAX_VISIBLE_TAGS) || [], [tags]); + + // 容器类名 + const containerClassName = `relative flex shrink-0 grow basis-0 cursor-pointer flex-col content-stretch items-start justify-start gap-3 ${disableHover ? '' : 'group'}`; + return ( -
+
-
+
{/* 默认渐变遮罩层 */}
-
+
{/* 角色信息区域 */}
@@ -76,26 +72,22 @@ const AIStandardCard: React.FC = React.memo( {name}
-
- {!!tags?.length && ( + {/* 标签 */} + {visibleTags.length > 0 && ( +
- {tags?.slice(0, 2).map((tag, index) => ( - + {visibleTags.map((tag, index) => ( + {tag.name} ))}
- )} -
+
+ )}
{/* 点赞数 */} -
- - - {formatNumberToKMB(1000)} - -
+
@@ -104,38 +96,28 @@ const AIStandardCard: React.FC = React.memo(
{/* 头像和名称 */}
- +
{name}
{/* 简介文本 */} -
+
-

- {description || 'No description'} -

+

{description || 'No description'}

{/* 点赞数 */} -
- - - {formatNumberToKMB(100)} - -
+
)}
diff --git a/src/components/ui/iconFont.tsx b/src/components/ui/iconFont.tsx index 91c052b..feffd2a 100644 --- a/src/components/ui/iconFont.tsx +++ b/src/components/ui/iconFont.tsx @@ -5,30 +5,15 @@ import React from 'react'; type IconFontProps = { /** 图标名称,对应 iconfont 中的图标 ID */ type: string; - /** 图标大小,如果不传则继承父元素的 font-size */ - size?: number; /** 图标颜色,默认 currentColor(继承父元素颜色) */ color?: string; /** 额外的 className */ className?: string; } & React.HTMLAttributes; -const IconFont = ({ - type, - size, - color = 'currentColor', - className = '', - ...props -}: IconFontProps) => { +const IconFont = ({ type, color = 'currentColor', className = '', ...props }: IconFontProps) => { return ( -
+
{response?.isPC && } -
+
-
+
{children}
{response?.isMobile && } diff --git a/src/layout/Providers/IntlProvider.tsx b/src/layout/Providers/IntlProvider.tsx index e5ddb13..142d4f1 100644 --- a/src/layout/Providers/IntlProvider.tsx +++ b/src/layout/Providers/IntlProvider.tsx @@ -7,7 +7,7 @@ import Cookies from 'js-cookie'; import { useMemoizedFn } from 'ahooks'; import { useLayoutStore } from '@/stores'; -type Locale = 'zh-CN' | 'en-US'; +type Locale = 'zh-TW' | 'en-US'; interface LocaleContextType { locale: Locale; setLocale: (locale: Locale) => void; @@ -33,7 +33,7 @@ function setLocaleToCookie(locale: Locale) { function getLocaleFromCookie(): Locale { const cookieLocale = Cookies.get('locale') as Locale | undefined; - if (cookieLocale && (cookieLocale === 'zh-CN' || cookieLocale === 'en-US')) { + if (cookieLocale && (cookieLocale === 'zh-TW' || cookieLocale === 'en-US')) { return cookieLocale; } setLocaleToCookie('en-US');