From 5cb01064efbff0e5992189f23e071c44c2f419f3 Mon Sep 17 00:00:00 2001
From: liuyonghe0111 <1763195287@qq.com>
Date: Thu, 25 Dec 2025 16:22:49 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E4=BD=93=E4=B8=AD=E6=96=87?=
=?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=BA=E7=B9=81=E4=BD=93;=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E8=A7=92=E8=89=B2=E5=8D=A1;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/(main)/chat/[id]/Drawer/index.tsx | 2 +-
src/app/(main)/chat/[id]/Input.tsx | 2 +-
src/app/(main)/chat/[id]/page.tsx | 2 +-
.../(main)/home/components/Filter/index.tsx | 17 +-
.../(main)/home/components/Story/index.tsx | 36 +++-
src/app/(main)/home/page.tsx | 4 +-
src/app/(main)/home/store.ts | 13 ++
src/app/(main)/profile/mask/page.tsx | 3 +-
.../features/StoryContent/ScrollBox.tsx | 94 +++++++++
.../features/StoryContent/index.tsx | 44 +++++
src/components/features/ai-standard-card.tsx | 114 +++++------
src/components/ui/iconFont.tsx | 19 +-
src/i18n/zh-CN.ts | 180 ------------------
src/i18n/zh-TW.ts | 180 ++++++++++++++++++
.../BasicLayout/components/LocaleSwitch.tsx | 4 +-
src/layout/BasicLayout/index.tsx | 6 +-
src/layout/Providers/IntlProvider.tsx | 4 +-
17 files changed, 439 insertions(+), 285 deletions(-)
create mode 100644 src/components/features/StoryContent/ScrollBox.tsx
create mode 100644 src/components/features/StoryContent/index.tsx
delete mode 100644 src/i18n/zh-CN.ts
create mode 100644 src/i18n/zh-TW.ts
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(
{/* 头像和名称 */}
{/* 简介文本 */}
-
+
-
- {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 (
-