From f0f6f66d608f88a4d0ca631ac18c63aacd9687c0 Mon Sep 17 00:00:00 2001 From: ludan <251918489@qq.com> Date: Wed, 3 Jun 2026 15:38:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=99=BB=E5=BD=95=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E4=BD=93=E9=AA=8C=E4=BC=98=E5=8C=96=E3=80=81?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8F=B0=E6=BB=9A=E5=8A=A8=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E5=A2=9E=E5=BC=BA=E3=80=81=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E5=B1=82=E8=A7=86=E8=A7=89=E7=B2=BE=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProfilePage.tsx(登录/注册表单优化): - 新增邮箱格式、手机号、密码强度前端校验逻辑 - 输入框内联校验提示(CheckCircleFilled图标+绿色文字) - 登录态展示区增加平台能力Stats(Studio/Assets/Team) - 表单增加kicker标签区分"账户登录"/"新用户注册" - 手机验证码tab标签精简为"手机" WorkbenchPage.tsx(工作台滚动交互增强): - 新增滚动操作按钮显隐状态管理(scrollActionsVisible/direction) - 滚动时自动展示上下滚动按钮,950ms后自动隐藏 - scrollMessagesToLatest触发时间接展示滚动按钮 - 组件卸载时清理定时器避免内存泄漏 dark-green.css(主题层视觉精修): - 工作台激活态页面背景增加微光渐变和径向光晕 - 消息表面区域增加内边距和scrollbar-gutter稳定布局 - 消息列表最大宽度约束为1040px,左右padding自适应 - 消息气泡增加边框、背景、阴影层次感 - AI助手气泡与用户气泡差异化背景色 - 头像增加微边框和accent色区分 - 作者标签字号和颜色精细调整 --- src/features/profile/ProfilePage.tsx | 62 +- src/features/workbench/WorkbenchPage.tsx | 44 +- src/styles/themes/dark-green.css | 1443 +++++++++++++++++++++- 3 files changed, 1531 insertions(+), 18 deletions(-) diff --git a/src/features/profile/ProfilePage.tsx b/src/features/profile/ProfilePage.tsx index 3add68c..fa509d9 100644 --- a/src/features/profile/ProfilePage.tsx +++ b/src/features/profile/ProfilePage.tsx @@ -235,6 +235,9 @@ function ProfilePage({ const packageLabel = session?.user.activePackages?.[0]?.name || "按量积分"; const avatarUrl = session?.user.avatarUrl || localAvatarUrl || null; const displayedBio = profileBio.trim() || "这个人还没有填写个性签名"; + const emailLooksValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); + const phoneLooksValid = /^1[3-9]\d{9}$/.test(phone.trim()); + const passwordLooksReady = password.length >= (mode === "register" ? 6 : 1); useEffect(() => { setLocalAvatarUrl(session?.user.avatarUrl || readLocalProfileValue(userId, "avatar")); @@ -812,12 +815,30 @@ function ProfilePage({
-

OmniAI

-

一句话,从创意到成片

-
- AI 视频生成 - AI 图片创作 - AI 电商素材 +
+
+

OmniAI

+
+

一句话,从创意到成片

+
+ AI 视频生成 + AI 图片创作 + AI 电商素材 +
+
+ + Studio + 创作工作台 + + + Assets + 资产沉淀 + + + Team + 团队协作 + +
@@ -829,6 +850,7 @@ function ProfilePage({ OmniAI + {mode === "login" ? "账户登录" : "新用户注册"}

{mode === "login" ? "欢迎回来" : "创建账号"}

{mode === "login" ? "登录后继续你的 AI 创作之旅" : "注册即可免费体验全部功能"} @@ -868,7 +890,8 @@ function ProfilePage({ 邮箱 @@ -924,6 +947,11 @@ function ProfilePage({ autoComplete={mode === "login" ? "current-password" : "new-password"} /> {fieldErrors.password ? {fieldErrors.password} : null} + {mode === "register" && passwordLooksReady && !fieldErrors.password ? ( + + 密码长度符合要求 + + ) : null} {mode === "login" ? (

@@ -961,6 +989,11 @@ function ProfilePage({ autoComplete="email" /> {fieldErrors.email ? {fieldErrors.email} : null} + {emailLooksValid && !fieldErrors.email ? ( + + 邮箱格式正确 + + ) : null} ) : null} @@ -990,6 +1028,11 @@ function ProfilePage({ { setPhone(event.target.value); clearFieldError("phone"); }} onBlur={() => handleFieldBlur("phone", phone)} placeholder="输入手机号" autoComplete="tel" />
{fieldErrors.phone ? {fieldErrors.phone} : null} + {phoneLooksValid && !fieldErrors.phone ? ( + + 手机号格式正确 + + ) : null} ) : null} diff --git a/src/features/workbench/WorkbenchPage.tsx b/src/features/workbench/WorkbenchPage.tsx index 9777ba8..7979e62 100644 --- a/src/features/workbench/WorkbenchPage.tsx +++ b/src/features/workbench/WorkbenchPage.tsx @@ -236,6 +236,7 @@ function WorkbenchPage({ const keepaliveTasksRef = useRef>(readStoredKeepaliveTasks()); const taskAbortControllersRef = useRef>(new Map()); const lastScrollTopRef = useRef(0); + const scrollActionsHideTimerRef = useRef(null); const shouldFollowNewMessagesRef = useRef(true); const pendingScrollToLatestRef = useRef(true); const renderedMessageIdsRef = useRef([]); @@ -273,6 +274,8 @@ function WorkbenchPage({ const [promptSelectionRange, setPromptSelectionRange] = useState({ start: 0, end: 0 }); const [mentionActiveIndex, setMentionActiveIndex] = useState(0); const [composerHidden, setComposerHidden] = useState(false); + const [scrollActionsVisible, setScrollActionsVisible] = useState(false); + const [scrollActionDirection, setScrollActionDirection] = useState<"top" | "bottom" | null>(null); const [workspaceStarted, setWorkspaceStarted] = useState(false); useEffect(() => { @@ -441,6 +444,27 @@ function WorkbenchPage({ "--accent-glow": `0 0 24px rgba(${accentRgb}, 0.22)`, } as CSSProperties; + const revealScrollActionsTemporarily = useCallback((direction: "top" | "bottom") => { + setScrollActionDirection(direction); + setScrollActionsVisible(true); + if (scrollActionsHideTimerRef.current !== null) { + window.clearTimeout(scrollActionsHideTimerRef.current); + } + scrollActionsHideTimerRef.current = window.setTimeout(() => { + setScrollActionsVisible(false); + setScrollActionDirection(null); + scrollActionsHideTimerRef.current = null; + }, 950); + }, []); + + useEffect(() => { + return () => { + if (scrollActionsHideTimerRef.current !== null) { + window.clearTimeout(scrollActionsHideTimerRef.current); + } + }; + }, []); + const scrollMessagesToLatest = useCallback((behavior: ScrollBehavior = "smooth") => { const scroll = () => { const surface = messagesSurfaceRef.current; @@ -451,6 +475,7 @@ function WorkbenchPage({ setComposerHidden(false); shouldFollowNewMessagesRef.current = true; + revealScrollActionsTemporarily("bottom"); surface.scrollTo({ top: surface.scrollHeight, behavior }); lastScrollTopRef.current = surface.scrollTop; }; @@ -459,7 +484,7 @@ function WorkbenchPage({ scroll(); window.setTimeout(scroll, 80); }); - }, []); + }, [revealScrollActionsTemporarily]); const imageSettingGroups = useMemo( () => [ @@ -1373,6 +1398,9 @@ function WorkbenchPage({ const delta = top - lastScrollTopRef.current; const atTop = top <= edgeThreshold; const atBottom = top + surface.clientHeight >= surface.scrollHeight - edgeThreshold; + if (surface.scrollHeight > surface.clientHeight + edgeThreshold && Math.abs(delta) > 1) { + revealScrollActionsTemporarily(delta > 0 ? "bottom" : "top"); + } shouldFollowNewMessagesRef.current = atBottom; if (atTop || atBottom) { setComposerHidden(false); @@ -1384,7 +1412,7 @@ function WorkbenchPage({ surface.addEventListener("scroll", handleScroll, { passive: true }); return () => surface.removeEventListener("scroll", handleScroll); - }, [hasActivatedWorkspace]); + }, [hasActivatedWorkspace, revealScrollActionsTemporarily]); const scrollMessagesSurface = useCallback((direction: "top" | "bottom") => { const surface = messagesSurfaceRef.current; @@ -1392,8 +1420,9 @@ function WorkbenchPage({ const top = direction === "top" ? 0 : surface.scrollHeight; setComposerHidden(false); + revealScrollActionsTemporarily(direction); surface.scrollTo({ top, behavior: "smooth" }); - }, []); + }, [revealScrollActionsTemporarily]); const closeToolbarMenus = () => setToolbarMenuId(null); const toggleToolbarMenu = (menuId: Exclude) => { @@ -3022,10 +3051,13 @@ function WorkbenchPage({ {renderComposerToolbar(false, isGenerating)} -
+