@@ -114,7 +118,7 @@ export default function CharacterBasicInfo({
width={18}
height={20}
/>
- Description:
+ {'Description'}:
{characterDetail.description}
diff --git a/src/app/(main)/character/[id]/(detail)/service.ts b/src/app/(main)/character/[id]/(detail)/service.ts
index f632874..d09418d 100644
--- a/src/app/(main)/character/[id]/(detail)/service.ts
+++ b/src/app/(main)/character/[id]/(detail)/service.ts
@@ -8,6 +8,7 @@ import { publicServerRequest } from '@/lib/server-request';
*/
export const fetchCharacterDetail = cache(async (id: string) => {
+ return {};
const { data } = await publicServerRequest(
`/character/select/roleInfo/${id}`,
{
diff --git a/src/app/(main)/character/[id]/chat/Main/ChatList.tsx b/src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Main/ChatList.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/ChatList.tsx
diff --git a/src/app/(main)/character/[id]/chat/Left/ArchiveHistory.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/ArchiveHistory.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Left/ArchiveHistory.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Left/ArchiveHistory.tsx
diff --git a/src/app/(main)/character/[id]/chat/Left/ChatHisory.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx
similarity index 97%
rename from src/app/(main)/character/[id]/chat/Left/ChatHisory.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx
index 65d3667..54d5ebd 100644
--- a/src/app/(main)/character/[id]/chat/Left/ChatHisory.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/ChatHisory.tsx
@@ -2,7 +2,7 @@
import React from 'react';
import { useSetAtom } from 'jotai';
-import { historyListOpenAtom } from '../atoms';
+import { historyListOpenAtom } from '../../atoms';
import Image from 'next/image';
import IconFont from '@/components/ui/iconFont';
diff --git a/src/app/(main)/character/[id]/chat/Left/Side.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx
similarity index 78%
rename from src/app/(main)/character/[id]/chat/Left/Side.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx
index e0e5bd8..80d562d 100644
--- a/src/app/(main)/character/[id]/chat/Left/Side.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/Side.tsx
@@ -1,10 +1,9 @@
'use client';
import { useAtom, useSetAtom } from 'jotai';
-import { historyListOpenAtom, leftTabActiveKeyAtom } from '../atoms';
+import { historyListOpenAtom, leftTabActiveKeyAtom } from '../../atoms';
import { cn } from '@/lib';
import Image from 'next/image';
-import { CharaterHistoryIcon } from '@/assets/chatacter';
import { useRouter } from 'next/navigation';
import { Icon } from '@/components';
import IconFont from '@/components/ui/iconFont';
@@ -19,22 +18,27 @@ export default function Side() {
const tabs = [
{
- element: (
-
-
+ render: (isActive: boolean) => (
+
+
),
key: 'info',
},
{
- element: (
-
-
+ render: (isActive: boolean) => (
+
+
),
key: 'history',
@@ -78,13 +82,13 @@ export default function Side() {
return (
setActiveKey(item.key as any)}
key={item.key}
>
- {item.element}
+ {item.render(isActive)}
);
})}
diff --git a/src/app/(main)/character/[id]/chat/Left/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx
similarity index 91%
rename from src/app/(main)/character/[id]/chat/Left/index.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx
index 30858f8..0445293 100644
--- a/src/app/(main)/character/[id]/chat/Left/index.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Left/index.tsx
@@ -2,7 +2,7 @@
import Side from './Side';
import { useAtomValue } from 'jotai';
-import { historyListOpenAtom } from '../atoms';
+import { historyListOpenAtom } from '../../atoms';
import { memo } from 'react';
import { Drawer } from '@/components';
import ChatHistory from './ChatHisory';
diff --git a/src/app/(main)/character/[id]/chat/Left/info.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Left/info.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Left/info.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Left/info.tsx
diff --git a/src/app/(main)/character/[id]/chat/Right/Background.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Right/Background.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Right/Background.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Right/Background.tsx
diff --git a/src/app/(main)/character/[id]/chat/Right/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/Right/index.tsx
similarity index 93%
rename from src/app/(main)/character/[id]/chat/Right/index.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/Right/index.tsx
index 2052210..ad50896 100644
--- a/src/app/(main)/character/[id]/chat/Right/index.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/Right/index.tsx
@@ -57,13 +57,13 @@ const SettingForm = React.memo(() => {
}}
>
-
+ (
)}
/>
-
+ (
{
-
+ (
)}
/>
-
+ (
{
-
+ (
@@ -106,13 +106,13 @@ const SettingForm = React.memo(() => {
-
+ {
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
new file mode 100644
index 0000000..0b39086
--- /dev/null
+++ b/src/app/(main)/character/[id]/chat/ChatMode/actions/index.tsx
@@ -0,0 +1,75 @@
+'use client';
+import {
+ GenerateInputIcon,
+ PhoneCallIcon,
+ PortraitModeIcon,
+} from '@/assets/chatacter';
+import { useAtom, useSetAtom } from 'jotai';
+import { isPhoneCallModeAtom, isPortraitModeAtom } from '../../atoms';
+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 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')}
+ >
+
+
+
+
+
setIsPortraitMode(!isPortraitMode)}
+ className="hover:cursor-pointer"
+ >
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/character/[id]/chat/Main/components/ChatMessageList.tsx b/src/app/(main)/character/[id]/chat/ChatMode/components/ChatMessageList.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Main/components/ChatMessageList.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/components/ChatMessageList.tsx
diff --git a/src/app/(main)/character/[id]/chat/Main/components/Message.css b/src/app/(main)/character/[id]/chat/ChatMode/components/Message.css
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Main/components/Message.css
rename to src/app/(main)/character/[id]/chat/ChatMode/components/Message.css
diff --git a/src/app/(main)/character/[id]/chat/Main/components/Message.tsx b/src/app/(main)/character/[id]/chat/ChatMode/components/Message.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Main/components/Message.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/components/Message.tsx
diff --git a/src/app/(main)/character/[id]/chat/Main/components/PortraitChat.tsx b/src/app/(main)/character/[id]/chat/ChatMode/components/PortraitChat.tsx
similarity index 100%
rename from src/app/(main)/character/[id]/chat/Main/components/PortraitChat.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/components/PortraitChat.tsx
diff --git a/src/app/(main)/character/[id]/chat/ChatMode/index.tsx b/src/app/(main)/character/[id]/chat/ChatMode/index.tsx
new file mode 100644
index 0000000..cef34cf
--- /dev/null
+++ b/src/app/(main)/character/[id]/chat/ChatMode/index.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import Input from './input';
+import Actions from './actions';
+import ChatList from './ChatList';
+import { useAtom } from 'jotai';
+import { settingOpenAtom } from '../atoms';
+import SettingForm from './Right';
+import { Drawer } from '@/components';
+import Left from './Left';
+import { ExitFullScreenIcon, FullScreenIcon } from '@/assets/common';
+import { cn } from '@/lib';
+
+export default function Main() {
+ const [settingOpen, setSettingOpen] = useAtom(settingOpenAtom);
+
+ return (
+ <>
+ {/* main */}
+
+ {/* chat list */}
+
+
+ {/* actions */}
+
+
+ {/* inputs */}
+
+
+
+ {/* 左侧 */}
+
+
+
+
+ {/* 右侧设置 */}
+
+
+
+
+ {/* 设置按钮 */}
+ setSettingOpen(!settingOpen)}
+ >
+ {settingOpen ? : }
+
+ >
+ );
+}
diff --git a/src/app/(main)/character/[id]/chat/Main/input.tsx b/src/app/(main)/character/[id]/chat/ChatMode/input.tsx
similarity index 76%
rename from src/app/(main)/character/[id]/chat/Main/input.tsx
rename to src/app/(main)/character/[id]/chat/ChatMode/input.tsx
index c02d7b8..d98fedc 100644
--- a/src/app/(main)/character/[id]/chat/Main/input.tsx
+++ b/src/app/(main)/character/[id]/chat/ChatMode/input.tsx
@@ -27,8 +27,8 @@ export default function Input() {
}, [value]);
return (
-
-
+
+
-
diff --git a/src/app/(main)/character/[id]/chat/Context/index.tsx b/src/app/(main)/character/[id]/chat/Context/index.tsx
new file mode 100644
index 0000000..d202d6b
--- /dev/null
+++ b/src/app/(main)/character/[id]/chat/Context/index.tsx
@@ -0,0 +1,15 @@
+'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.tsx b/src/app/(main)/character/[id]/chat/Main/actions.tsx
deleted file mode 100644
index bfe756f..0000000
--- a/src/app/(main)/character/[id]/chat/Main/actions.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-'use client';
-import {
- GenerateInputIcon,
- PhoneCallIcon,
- PortraitModeIcon,
-} from '@/assets/chatacter';
-import { useAtom } from 'jotai';
-import { isPortraitModeAtom } from '../atoms';
-
-export default function Actions() {
- const [isPortraitMode, setIsPortraitMode] = useAtom(isPortraitModeAtom);
- const className = 'text-[#4269D6] hover:cursor-pointer hover:text-[#0066FF]';
-
- return (
-
-
-
setIsPortraitMode(!isPortraitMode)}
- className="hover:cursor-pointer"
- >
-
-
-
- );
-}
diff --git a/src/app/(main)/character/[id]/chat/Main/index.tsx b/src/app/(main)/character/[id]/chat/Main/index.tsx
deleted file mode 100644
index b9b5335..0000000
--- a/src/app/(main)/character/[id]/chat/Main/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-'use client';
-
-import Input from './input';
-import Actions from './actions';
-import ChatList from './ChatList';
-
-export default function Main() {
- return (
-
- {/* chat list */}
-
-
- {/* actions */}
-
-
- {/* inputs */}
-
-
- );
-}
diff --git a/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx b/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx
new file mode 100644
index 0000000..30ca0be
--- /dev/null
+++ b/src/app/(main)/character/[id]/chat/PhoneCallMode/index.tsx
@@ -0,0 +1,5 @@
+'use client';
+
+export default function PhoneCallMode() {
+ return PhoneCallMode
;
+}
diff --git a/src/app/(main)/character/[id]/chat/atoms.ts b/src/app/(main)/character/[id]/chat/atoms.ts
index 109dac2..f782d6c 100644
--- a/src/app/(main)/character/[id]/chat/atoms.ts
+++ b/src/app/(main)/character/[id]/chat/atoms.ts
@@ -6,6 +6,9 @@ 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');
diff --git a/src/app/(main)/character/[id]/chat/page.tsx b/src/app/(main)/character/[id]/chat/page.tsx
index e6dc791..061a93d 100644
--- a/src/app/(main)/character/[id]/chat/page.tsx
+++ b/src/app/(main)/character/[id]/chat/page.tsx
@@ -1,47 +1,26 @@
'use client';
-import { cn } from '@/lib';
-import SettingForm from './Right';
-import { useAtom } from 'jotai';
-import { settingOpenAtom } from './atoms';
-import Main from './Main';
-import Left from './Left';
-import { Drawer } from '@/components';
+import { useAtomValue } from 'jotai';
+import { isPhoneCallModeAtom } from './atoms';
+import ChatMode from './ChatMode';
import './index.css';
-import { ExitFullScreenIcon, FullScreenIcon } from '@/assets/common';
+import PhoneCallMode from './PhoneCallMode';
+import ChatContextProvider from './Context';
export default function CharacterChat() {
- const [settingOpen, setSettingOpen] = useAtom(settingOpenAtom);
+ const isPhoneCallMode = useAtomValue(isPhoneCallModeAtom);
return (
-
-
-
- {/* 左侧 */}
-
-
-
- {/* 右侧设置 */}
-
-
-
-
- {/* 设置按钮 */}
+
setSettingOpen(!settingOpen)}
+ style={{
+ background:
+ 'linear-gradient(0deg, rgba(23, 0, 18, 0.6) 0%, rgba(0, 0, 0, 0.1) 100%)',
+ }}
+ className="relative flex h-full w-full justify-center overflow-hidden"
>
- {settingOpen ?
:
}
+ {isPhoneCallMode ?
:
}
-
+
);
}
diff --git a/src/app/(main)/character/page.tsx b/src/app/(main)/character/page.tsx
index 092b425..131a5fa 100644
--- a/src/app/(main)/character/page.tsx
+++ b/src/app/(main)/character/page.tsx
@@ -63,15 +63,13 @@ export default function Novel() {
queryKey: ['tags'],
queryFn: async () => {
const res = await fetchTags();
- return res.rows;
+ return res.rows?.map((tag: any) => ({
+ label: tag.name,
+ value: tag.id,
+ }));
},
});
- const options = tags?.map((tag: any) => ({
- label: tag.name,
- value: tag.id,
- }));
-
return (
`# ${item.label}`}
onChange={(v) => {
onSearch({ tagId: v });
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index e940ab1..99c60de 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -30,7 +30,7 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
diff --git a/src/assets/chatacter/index.tsx b/src/assets/chatacter/index.tsx
index fea58ad..d4ffaf2 100644
--- a/src/assets/chatacter/index.tsx
+++ b/src/assets/chatacter/index.tsx
@@ -235,27 +235,3 @@ export const ScrollTobottom = () => {
);
};
-
-export const CharaterHistoryIcon = () => {
- return (
-
- );
-};
diff --git a/src/components/feature/ModelSelectDialog.tsx b/src/components/feature/ModelSelectDialog.tsx
index 215579c..a8cb417 100644
--- a/src/components/feature/ModelSelectDialog.tsx
+++ b/src/components/feature/ModelSelectDialog.tsx
@@ -16,7 +16,6 @@ export default function ModelSelectDialog(props: ModelSelectDialogProps) {
defaultValuePropName: 'defaultValue',
trigger: 'onChange',
});
- console.log('ModelSelectDialog', value);
const options = [
{ label: 'Model 1', value: 'model1' },
diff --git a/src/components/feature/VoiceActorSelectDialog.tsx b/src/components/feature/VoiceActorSelectDialog.tsx
index 8916ac1..363e5a2 100644
--- a/src/components/feature/VoiceActorSelectDialog.tsx
+++ b/src/components/feature/VoiceActorSelectDialog.tsx
@@ -12,7 +12,6 @@ export default function VoiceActorSelectDialog(
props: VoiceActorSelectDialogProps
) {
const { value, onChange } = props;
- console.log('VoiceActorSelectDialog', value);
const options = [
{ label: 'Voice Actor 1', value: 'voiceActor1', gender: 'male' },
{ label: 'Voice Actor 2', value: 'voiceActor2', gender: 'female' },
diff --git a/src/components/ui/Tags.tsx b/src/components/ui/Tags.tsx
index 8be9ecf..a23aaa6 100644
--- a/src/components/ui/Tags.tsx
+++ b/src/components/ui/Tags.tsx
@@ -1,3 +1,4 @@
+'use client';
import React from 'react';
import { cn } from '@/lib';
diff --git a/src/components/ui/iconFont.tsx b/src/components/ui/iconFont.tsx
index ef223a2..ee201b9 100644
--- a/src/components/ui/iconFont.tsx
+++ b/src/components/ui/iconFont.tsx
@@ -1,3 +1,4 @@
+'use client';
interface IconFontProps {
/** 图标名称,对应 iconfont 中的图标 ID */
type: string;
diff --git a/src/components/ui/modal/index.css b/src/components/ui/modal/index.css
index 61dfe3a..0f17ffc 100644
--- a/src/components/ui/modal/index.css
+++ b/src/components/ui/modal/index.css
@@ -26,3 +26,9 @@
);
padding: 30px;
}
+
+/* 移除关闭按钮的焦点轮廓 */
+.dialog-close-btn:focus,
+.dialog-close-btn:focus-visible {
+ outline: none;
+}
diff --git a/src/components/ui/modal/index.tsx b/src/components/ui/modal/index.tsx
index c318f44..a427dc7 100644
--- a/src/components/ui/modal/index.tsx
+++ b/src/components/ui/modal/index.tsx
@@ -13,10 +13,11 @@ type ModalProps = {
trigger?: React.ReactNode;
title?: string;
classNames?: Partial>;
+ destroyOnClose?: boolean;
};
export default function Modal(props: ModalProps) {
- const { children, trigger, title, classNames } = props;
+ const { children, trigger, title, classNames, destroyOnClose = true } = props;
const [open, setOpen] = useControllableValue(props, {
defaultValue: false,
defaultValuePropName: 'defaultOpen',
@@ -24,6 +25,8 @@ export default function Modal(props: ModalProps) {
trigger: 'onOpenChange',
});
+ const hiden = destroyOnClose && !open;
+
return (
{trigger && (
@@ -35,25 +38,27 @@ export default function Modal(props: ModalProps) {
{trigger}
)}
-
-
-
-
-
- {title}
-
- setOpen(false)}
- className="translate-x-2.5 -translate-y-2.5 cursor-pointer hover:opacity-80"
- >
-
-
-
- {children}
-
-
+ {!hiden && (
+
+
+
+
+
+ {title}
+
+ setOpen(false)}
+ className="dialog-close-btn translate-x-2.5 -translate-y-2.5 cursor-pointer hover:opacity-80"
+ >
+
+
+
+ {children}
+
+
+ )}
);
}
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index efcd2bb..e3f1369 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -17,8 +17,19 @@ export const useMsg = (prefix: string) => {
[msg, prefix]
);
+ const cmnMsg = useCallback(
+ (
+ key: string,
+ values?: TranslationValues
+ ): ReturnType => {
+ return msg(`common_${key}`, values);
+ },
+ [msg]
+ );
+
return {
pageMsg,
+ cmnMsg,
msg,
};
};
diff --git a/src/i18n/request.ts b/src/i18n/request.ts
new file mode 100644
index 0000000..f3a41ef
--- /dev/null
+++ b/src/i18n/request.ts
@@ -0,0 +1,12 @@
+import { cookies } from 'next/headers';
+import { getRequestConfig } from 'next-intl/server';
+
+export default getRequestConfig(async () => {
+ const store = await cookies();
+ const cookieLocale = store.get('locale')?.value || 'en';
+
+ return {
+ locale: cookieLocale,
+ messages: (await import(`@/locales/${cookieLocale}.ts`)).default,
+ };
+});
diff --git a/src/layouts/GlobalContainer/IntlProvider.tsx b/src/layouts/GlobalContainer/IntlProvider.tsx
index c8f723e..3de8033 100644
--- a/src/layouts/GlobalContainer/IntlProvider.tsx
+++ b/src/layouts/GlobalContainer/IntlProvider.tsx
@@ -1,7 +1,15 @@
'use client';
import { NextIntlClientProvider } from 'next-intl';
-import { createContext, useContext, useState, ReactNode } from 'react';
+import {
+ createContext,
+ useContext,
+ useState,
+ useEffect,
+ ReactNode,
+ useCallback,
+} from 'react';
+import Cookies from 'js-cookie';
import zhMessages from '@/locales/zh';
import enMessages from '@/locales/en';
@@ -31,13 +39,39 @@ interface IntlProviderProps {
children: ReactNode;
}
+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;
+ }
+ return 'en';
+}
+
export function IntlProvider({ children }: IntlProviderProps) {
- const [locale, setLocale] = useState('zh');
+ const [locale, setLocaleState] = useState('en');
+
+ useEffect(() => {
+ const cookieLocale = getLocaleFromCookie();
+ if (cookieLocale) {
+ setLocaleState(cookieLocale);
+ }
+ }, []);
+
+ const setLocale = useCallback((newLocale: Locale) => {
+ setLocaleState(newLocale);
+ setLocaleToCookie(newLocale);
+ }, []);
return (
diff --git a/src/layouts/MainLayout/components/LocaleSelect.tsx b/src/layouts/MainLayout/components/LocaleSelect.tsx
new file mode 100644
index 0000000..e17d1d5
--- /dev/null
+++ b/src/layouts/MainLayout/components/LocaleSelect.tsx
@@ -0,0 +1,72 @@
+'use client';
+
+import { useLocale } from '@/layouts/GlobalContainer/IntlProvider';
+import { Select } from 'radix-ui';
+import React from 'react';
+import Image from 'next/image';
+import IconFont from '@/components/ui/iconFont';
+
+const options = [
+ {
+ icon: '/locale/cn.svg',
+ value: 'zh',
+ },
+ {
+ icon: '/locale/en.svg',
+ value: 'en',
+ },
+];
+
+const LocaleSelect = React.memo(() => {
+ const { locale, setLocale } = useLocale();
+ const currentOption = options.find((option) => option.value === locale)!;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {options.map((option) => (
+
+
+
+
+
+ ))}
+
+
+
+ );
+});
+
+export default LocaleSelect;
diff --git a/src/layouts/MainLayout/components/NavRoutes.tsx b/src/layouts/MainLayout/components/NavRoutes.tsx
new file mode 100644
index 0000000..b3116e7
--- /dev/null
+++ b/src/layouts/MainLayout/components/NavRoutes.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import React from 'react';
+import { useMsg } from '@/hooks';
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import { cn } from '@/lib';
+import IconFont from '@/components/ui/iconFont';
+
+const NavRoutes = React.memo(() => {
+ const { pageMsg } = useMsg('menu');
+ const pathname = usePathname();
+
+ const routes = [
+ {
+ path: '/novel',
+ icon: 'icon-novel',
+ label: pageMsg('novel'),
+ },
+ {
+ path: '/video',
+ icon: 'icon-video',
+ label: pageMsg('video'),
+ },
+ {
+ path: '/character',
+ icon: 'icon-character',
+ label: pageMsg('character'),
+ },
+ {
+ path: '/record',
+ icon: 'icon-record',
+ label: pageMsg('record'),
+ },
+ ];
+
+ return (
+
+ {routes.map((route) => {
+ const isActive = pathname.startsWith(route.path);
+ return (
+
+
+ {route.label}
+
+ );
+ })}
+
+ );
+});
+export default NavRoutes;
diff --git a/src/layouts/MainLayout/header.tsx b/src/layouts/MainLayout/header.tsx
index 47c0471..8712291 100644
--- a/src/layouts/MainLayout/header.tsx
+++ b/src/layouts/MainLayout/header.tsx
@@ -1,66 +1,15 @@
'use client';
import React from 'react';
-import { useMsg } from '@/hooks';
-import Link from 'next/link';
-import { usePathname } from 'next/navigation';
-import { cn } from '@/lib';
-import IconFont from '@/components/ui/iconFont';
-import { Select } from '@/components/ui/inputs';
-
-const NavRoutes = React.memo(() => {
- const { pageMsg } = useMsg('menu');
- const pathname = usePathname();
-
- const routes = [
- {
- path: '/novel',
- icon: 'icon-novel',
- label: pageMsg('novel'),
- },
- {
- path: '/video',
- icon: 'icon-video',
- label: pageMsg('video'),
- },
- {
- path: '/character',
- icon: 'icon-character',
- label: pageMsg('character'),
- },
- {
- path: '/record',
- icon: 'icon-record',
- label: pageMsg('record'),
- },
- ];
-
- return (
-
- {routes.map((route) => {
- const isActive = pathname.startsWith(route.path);
- return (
-
-
- {route.label}
-
- );
- })}
-
- );
-});
+import NavRoutes from './components/NavRoutes';
+import LocaleSelect from './components/LocaleSelect';
const RightActions = () => {
- return Avator
;
+ return (
+
+
+
+ );
};
export default function Header() {
diff --git a/src/lib/request.ts b/src/lib/request.ts
index 7a1ca2b..16c76a6 100644
--- a/src/lib/request.ts
+++ b/src/lib/request.ts
@@ -4,6 +4,7 @@ import type {
InternalAxiosRequestConfig,
} from 'axios';
import axios from 'axios';
+import Cookies from 'js-cookie';
import { getToken, saveAuthInfo } from './auth';
const instance = axios.create({
@@ -20,6 +21,17 @@ instance.interceptors.request.use(
if (token) {
config.headers.setAuthorization(`Bearer ${token}`);
}
+
+ // 从 cookie 中读取语言设置,并添加到请求头
+ // 这样后端 API 可以从请求头中获取语言信息
+ // 使用 X-Locale 避免与浏览器自动发送的 Accept-Language 冲突
+ if (typeof window !== 'undefined') {
+ const locale = Cookies.get('locale');
+ if (locale) {
+ config.headers.set('X-Locale', locale);
+ }
+ }
+
return config;
}
);
diff --git a/src/locales/en.ts b/src/locales/en.ts
index 3adf95d..2448490 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -3,4 +3,6 @@ export default {
menu_video: 'Video Comics',
menu_character: 'Characters',
menu_record: 'Record',
+
+ common_desc: 'Description',
};
diff --git a/src/locales/en/character.ts b/src/locales/en/character.ts
new file mode 100644
index 0000000..ff8b4c5
--- /dev/null
+++ b/src/locales/en/character.ts
@@ -0,0 +1 @@
+export default {};
diff --git a/src/locales/zh.ts b/src/locales/zh.ts
index 354011e..9512a4b 100644
--- a/src/locales/zh.ts
+++ b/src/locales/zh.ts
@@ -3,4 +3,6 @@ export default {
menu_video: '视频',
menu_character: '角色',
menu_record: '记录',
+
+ common_desc: '描述',
};
diff --git a/src/locales/zh/character.ts b/src/locales/zh/character.ts
new file mode 100644
index 0000000..ff8b4c5
--- /dev/null
+++ b/src/locales/zh/character.ts
@@ -0,0 +1 @@
+export default {};
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..cc4e31c
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,22 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+export function middleware(request: NextRequest) {
+ const localeCookie = request.cookies.get('locale');
+
+ if (localeCookie?.value) {
+ return NextResponse.next();
+ }
+
+ const response = NextResponse.next();
+ response.cookies.set('locale', 'en', {
+ path: '/',
+ maxAge: 60 * 60 * 24 * 365,
+ sameSite: 'lax',
+ });
+
+ return response;
+}
+
+export const config = {
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
+};