From debca09f6cffa4fa35b9fa1c82f13be529076d21 Mon Sep 17 00:00:00 2001 From: liuyonghe0111 <1763195287@qq.com> Date: Thu, 6 Nov 2025 14:15:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=89=93=E7=94=B5?= =?UTF-8?q?=E8=AF=9D=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- pnpm-lock.yaml | 53 ++++++------- .../character/[id]/chat/ChatMode/ChatList.tsx | 6 +- .../[id]/chat/ChatMode/Left/ChatHisory.tsx | 5 +- .../[id]/chat/ChatMode/Left/Side.tsx | 8 +- .../[id]/chat/ChatMode/Left/index.tsx | 5 +- .../[id]/chat/ChatMode/actions/index.tsx | 8 +- .../character/[id]/chat/ChatMode/index.tsx | 6 +- .../character/[id]/chat/Context/index.tsx | 15 ---- .../[id]/chat/Main/actions/index.tsx | 75 +++++++++++++++++++ .../[id]/chat/PhoneCallMode/index.tsx | 65 +++++++++++++++- src/app/(main)/character/[id]/chat/atoms.ts | 16 ---- src/app/(main)/character/[id]/chat/page.tsx | 26 +++---- src/app/(main)/character/[id]/chat/store.ts | 27 +++++++ src/app/layout.tsx | 6 +- src/layouts/GlobalContainer/index.tsx | 15 ---- .../MainLayout/components/LocaleSelect.tsx | 2 +- .../IntlProvider.tsx | 27 ++++--- .../QueryProvider.tsx | 0 src/layouts/Providers/index.tsx | 11 +++ 20 files changed, 254 insertions(+), 126 deletions(-) delete mode 100644 src/app/(main)/character/[id]/chat/Context/index.tsx create mode 100644 src/app/(main)/character/[id]/chat/Main/actions/index.tsx delete mode 100644 src/app/(main)/character/[id]/chat/atoms.ts create mode 100644 src/app/(main)/character/[id]/chat/store.ts delete mode 100644 src/layouts/GlobalContainer/index.tsx rename src/layouts/{GlobalContainer => Providers}/IntlProvider.tsx (76%) rename src/layouts/{GlobalContainer => Providers}/QueryProvider.tsx (100%) create mode 100644 src/layouts/Providers/index.tsx diff --git a/package.json b/package.json index eff92d3..f06d12b 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "axios": "^1.12.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "jotai": "^2.15.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", "next": "15.5.4", @@ -27,7 +26,8 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-virtuoso": "^4.14.1", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a5608c..faf4285 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 - jotai: - specifier: ^2.15.0 - version: 2.15.0(@types/react@19.2.2)(react@19.1.0) js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -59,6 +56,9 @@ importers: tailwind-merge: specifier: ^3.3.1 version: 3.3.1 + zustand: + specifier: ^5.0.8 + version: 5.0.8(@types/react@19.2.2)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -2110,24 +2110,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jotai@2.15.0: - resolution: {integrity: sha512-nbp/6jN2Ftxgw0VwoVnOg0m5qYM1rVcfvij+MZx99Z5IK13eGve9FJoCwGv+17JvVthTjhSmNtT5e1coJnr6aw==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@babel/core': '>=7.0.0' - '@babel/template': '>=7.0.0' - '@types/react': '>=17.0.0' - react: '>=17.0.0' - peerDependenciesMeta: - '@babel/core': - optional: true - '@babel/template': - optional: true - '@types/react': - optional: true - react: - optional: true - js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -2883,6 +2865,24 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zustand@5.0.8: + resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -5076,11 +5076,6 @@ snapshots: jiti@2.6.1: {} - jotai@2.15.0(@types/react@19.2.2)(react@19.1.0): - optionalDependencies: - '@types/react': 19.2.2 - react: 19.1.0 - js-cookie@3.0.5: {} js-tokens@4.0.0: {} @@ -5904,3 +5899,9 @@ snapshots: yallist@5.0.0: {} yocto-queue@0.1.0: {} + + zustand@5.0.8(@types/react@19.2.2)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): + optionalDependencies: + '@types/react': 19.2.2 + react: 19.1.0 + use-sync-external-store: 1.6.0(react@19.1.0) diff --git a/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx b/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx index 2f08263..100e341 100644 --- a/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx +++ b/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx @@ -1,12 +1,12 @@ 'use client'; -import { useAtomValue } from 'jotai'; -import { isPortraitModeAtom } from '../atoms'; +import { useChatStore } from '../store'; + import ChatMessageList from './components/ChatMessageList'; import PortraitChat from './components/PortraitChat'; export default function ChatList() { - const isPortraitMode = useAtomValue(isPortraitModeAtom); + const isPortraitMode = useChatStore((state) => state.isPortraitMode); return (
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx index 54d5ebd..0dc88ae 100644 --- a/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx +++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx @@ -1,13 +1,12 @@ 'use client'; import React from 'react'; -import { useSetAtom } from 'jotai'; -import { historyListOpenAtom } from '../../atoms'; +import { useChatStore } from '../../store'; import Image from 'next/image'; import IconFont from '@/components/ui/iconFont'; const ChatHistory = React.memo(() => { - const setHistoryListOpen = useSetAtom(historyListOpenAtom); + const setHistoryListOpen = useChatStore((state) => state.setHistoryListOpen); return (
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx index 80d562d..d8b7124 100644 --- a/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx +++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx @@ -1,7 +1,6 @@ 'use client'; -import { useAtom, useSetAtom } from 'jotai'; -import { historyListOpenAtom, leftTabActiveKeyAtom } from '../../atoms'; +import { useChatStore } from '../../store'; import { cn } from '@/lib'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; @@ -11,8 +10,9 @@ import ArchiveHistory from './ArchiveHistory'; import Info from './info'; export default function Side() { - const [activeKey, setActiveKey] = useAtom(leftTabActiveKeyAtom); - const setHistoryListOpen = useSetAtom(historyListOpenAtom); + const activeKey = useChatStore((state) => state.leftTabActiveKey); + const setActiveKey = useChatStore((state) => state.setLeftTabActiveKey); + const setHistoryListOpen = useChatStore((state) => state.setHistoryListOpen); const Component = activeKey === 'info' ? Info : ArchiveHistory; const router = useRouter(); diff --git a/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx index 0445293..d653061 100644 --- a/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx +++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx @@ -1,14 +1,13 @@ 'use client'; import Side from './Side'; -import { useAtomValue } from 'jotai'; -import { historyListOpenAtom } from '../../atoms'; +import { useChatStore } from '../../store'; import { memo } from 'react'; import { Drawer } from '@/components'; import ChatHistory from './ChatHisory'; const Left = memo(() => { - const historyListOpen = useAtomValue(historyListOpenAtom); + const historyListOpen = useChatStore((state) => state.historyListOpen); return (
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx index 0b39086..e0031f7 100644 --- a/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx +++ b/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx @@ -4,15 +4,15 @@ import { PhoneCallIcon, PortraitModeIcon, } from '@/assets/chatacter'; -import { useAtom, useSetAtom } from 'jotai'; -import { isPhoneCallModeAtom, isPortraitModeAtom } from '../../atoms'; +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, setIsPortraitMode] = useAtom(isPortraitModeAtom); - const setIsPhoneCallMode = useSetAtom(isPhoneCallModeAtom); + 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 = [ diff --git a/src/app/(main)/character/[id]/chat/ChatMode/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/index.tsx index cef34cf..a6e1afc 100644 --- a/src/app/(main)/character/[id]/chat/ChatMode/index.tsx +++ b/src/app/(main)/character/[id]/chat/ChatMode/index.tsx @@ -3,8 +3,7 @@ import Input from './input'; import Actions from './actions'; import ChatList from './ChatList'; -import { useAtom } from 'jotai'; -import { settingOpenAtom } from '../atoms'; +import { useChatStore } from '../store'; import SettingForm from './Right'; import { Drawer } from '@/components'; import Left from './Left'; @@ -12,7 +11,8 @@ import { ExitFullScreenIcon, FullScreenIcon } from '@/assets/common'; import { cn } from '@/lib'; export default function Main() { - const [settingOpen, setSettingOpen] = useAtom(settingOpenAtom); + const settingOpen = useChatStore((state) => state.settingOpen); + const setSettingOpen = useChatStore((state) => state.setSettingOpen); return ( <> diff --git a/src/app/(main)/character/[id]/chat/Context/index.tsx b/src/app/(main)/character/[id]/chat/Context/index.tsx deleted file mode 100644 index d202d6b..0000000 --- a/src/app/(main)/character/[id]/chat/Context/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import { createContext } from 'react'; - -interface ChatContextType {} - -export const ChatContext = createContext({}); - -export default function ChatContextProvider({ - children, -}: { - children: React.ReactNode; -}) { - return {children}; -} diff --git a/src/app/(main)/character/[id]/chat/Main/actions/index.tsx b/src/app/(main)/character/[id]/chat/Main/actions/index.tsx new file mode 100644 index 0000000..e0031f7 --- /dev/null +++ b/src/app/(main)/character/[id]/chat/Main/actions/index.tsx @@ -0,0 +1,75 @@ +'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 ( +
+
+ {suggestMessages.map((message, index) => ( +
+ {message} +
+ ))} + + + +
+ {/* action */} +
+
null} className="flex items-center gap-5"> +
+ +
+
setIsPhoneCallMode(true)} + className={cn(className, 'relative')} + > + + phone call +
+
+
setIsPortraitMode(!isPortraitMode)} + className="hover:cursor-pointer" + > + +
+
+
+ ); +} diff --git a/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx b/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx index 30ca0be..b83fe64 100644 --- a/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx +++ b/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx @@ -1,5 +1,68 @@ 'use client'; +import IconFont from '@/components/ui/iconFont'; +import { cn } from '@/lib'; +import Image from 'next/image'; +import { useState } from 'react'; +import { useChatStore } from '../store'; + +const message1 = + 'The threads of fate intertwine once more... I have been awaiting your arrival, seeker.The threads of fate intertwine once more... I have been awaiting your arrival, seeker.The threads of fate intertwine once more... I have been awaiting your arrival, seeker.'; +const message2 = + 'The threads of fate intertwine once more... I have been awaiting your arrival, seeker.The threads of fate intertwine once more'; + export default function PhoneCallMode() { - return
PhoneCallMode
; + const setIsPhoneCallMode = useChatStore((state) => state.setIsPhoneCallMode); + const [isTextVisible, setIsTextVisible] = useState(true); + + return ( + <> +
+
+
+ avatar + {'Character 1 · 18'} +
+
{'00:01'}
+
+
+ {isTextVisible && ( + <> +
{message1}
+
{message2}
+ + )} +
+
+ Tag to top +
+
setIsPhoneCallMode(false)} + className={cn( + 'flex-center mb-25 h-20 w-20 cursor-pointer rounded-full bg-white/10', + 'bg-[linear-gradient(180deg,rgba(255,59,48,1)0%,rgba(222,46,36,1)100%)]' + )} + > + +
+
+ + {/* 显示文本按钮 */} +
setIsTextVisible(!isTextVisible)} + className={cn( + 'flex-center absolute top-8 right-10 h-10 w-10 cursor-pointer rounded-full', + 'bg-white/10 hover:bg-white/20' + )} + > + +
+ + ); } diff --git a/src/app/(main)/character/[id]/chat/atoms.ts b/src/app/(main)/character/[id]/chat/atoms.ts deleted file mode 100644 index f782d6c..0000000 --- a/src/app/(main)/character/[id]/chat/atoms.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { atom } from 'jotai'; - -// 是否打开两侧的设置 -export const settingOpenAtom = atom(true); - -// 是否是立绘模式 -export const isPortraitModeAtom = atom(false); - -// 是否是通话模式 -export const isPhoneCallModeAtom = atom(false); - -// 左侧 tab active key -export const leftTabActiveKeyAtom = atom<'info' | 'history'>('info'); - -// 左侧 角色历史列表 -export const historyListOpenAtom = atom(false); diff --git a/src/app/(main)/character/[id]/chat/page.tsx b/src/app/(main)/character/[id]/chat/page.tsx index 061a93d..fa3ec50 100644 --- a/src/app/(main)/character/[id]/chat/page.tsx +++ b/src/app/(main)/character/[id]/chat/page.tsx @@ -1,26 +1,22 @@ 'use client'; -import { useAtomValue } from 'jotai'; -import { isPhoneCallModeAtom } from './atoms'; +import { useChatStore } from './store'; import ChatMode from './ChatMode'; import './index.css'; import PhoneCallMode from './PhoneCallMode'; -import ChatContextProvider from './Context'; export default function CharacterChat() { - const isPhoneCallMode = useAtomValue(isPhoneCallModeAtom); + const isPhoneCallMode = useChatStore((state) => state.isPhoneCallMode); return ( - -
- {isPhoneCallMode ? : } -
-
+
+ {isPhoneCallMode ? : } +
); } diff --git a/src/app/(main)/character/[id]/chat/store.ts b/src/app/(main)/character/[id]/chat/store.ts new file mode 100644 index 0000000..3bb1913 --- /dev/null +++ b/src/app/(main)/character/[id]/chat/store.ts @@ -0,0 +1,27 @@ +import { create } from 'zustand'; + +export const useChatStore = create<{ + // UI state + isPhoneCallMode: boolean; + setIsPhoneCallMode: (isPhoneCallMode: boolean) => void; + settingOpen: boolean; + setSettingOpen: (settingOpen: boolean) => void; + isPortraitMode: boolean; + setIsPortraitMode: (isPortraitMode: boolean) => void; + leftTabActiveKey: 'info' | 'history'; + setLeftTabActiveKey: (leftTabActiveKey: 'info' | 'history') => void; + historyListOpen: boolean; + setHistoryListOpen: (historyListOpen: boolean) => void; + // data state +}>((set) => ({ + isPhoneCallMode: false, + setIsPhoneCallMode: (isPhoneCallMode) => set({ isPhoneCallMode }), + settingOpen: false, + setSettingOpen: (settingOpen) => set({ settingOpen }), + isPortraitMode: false, + setIsPortraitMode: (isPortraitMode) => set({ isPortraitMode }), + leftTabActiveKey: 'info', + setLeftTabActiveKey: (leftTabActiveKey) => set({ leftTabActiveKey }), + historyListOpen: false, + setHistoryListOpen: (historyListOpen) => set({ historyListOpen }), +})); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 99c60de..07514b6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; import { Geist, Geist_Mono } from 'next/font/google'; import './globals.css'; -import GlobalContainer from '@/layouts/GlobalContainer'; +import Providers from '@/layouts/Providers'; import Script from 'next/script'; const geistSans = Geist({ @@ -30,11 +30,11 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} >