feat(character): 详情页面适配移动端
This commit is contained in:
parent
023398cbea
commit
abdf1f4615
|
|
@ -1,10 +1,14 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import type React from 'react';
|
||||||
import { useAsyncFn } from '@/hooks/tools';
|
import { useAsyncFn } from '@/hooks/tools';
|
||||||
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
|
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
|
||||||
|
|
||||||
export default function ChatButton({ id }: { id: string }) {
|
export default function ChatButton({
|
||||||
|
id,
|
||||||
|
...props
|
||||||
|
}: { id: string } & React.ComponentProps<'button'>) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const createChannel = useStreamChatStore((s) => s.createChannel);
|
const createChannel = useStreamChatStore((s) => s.createChannel);
|
||||||
|
|
||||||
|
|
@ -15,7 +19,7 @@ export default function ChatButton({ id }: { id: string }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button loading={loading} onClick={() => createChannelAndPush()} variant="primary">
|
<Button loading={loading} onClick={() => createChannelAndPush()} variant="primary" {...props}>
|
||||||
Chat
|
Chat
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,33 @@ import ChatButton from './ChatButton';
|
||||||
import { fetchCharacter } from './service';
|
import { fetchCharacter } from './service';
|
||||||
import { Chip } from '@/components/ui/chip';
|
import { Chip } from '@/components/ui/chip';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
|
import { IconButton } from '@/components/ui/button';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const character = await fetchCharacter(id);
|
const character = await fetchCharacter(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex px-4 pt-10">
|
<div className="flex h-full flex-col items-center mx-auto max-w-[752px] px-4 pt-4 sm:pt-10">
|
||||||
<div className="mx-auto w-full max-w-[752px]">
|
{/* header */}
|
||||||
|
<div className="w-full flex mb-4 items-center justify-between">
|
||||||
|
<Link href="/home">
|
||||||
|
<IconButton variant="ghost" size="large" iconfont="icon-arrow-left" />
|
||||||
|
</Link>
|
||||||
|
<IconButton variant="tertiary" size="large" iconfont="icon-Like" />
|
||||||
|
</div>
|
||||||
|
{/* 内容区 */}
|
||||||
|
<div className="mx-auto flex-1 w-full">
|
||||||
<header className="flex items-end gap-10 justify-between">
|
<header className="flex items-end gap-10 justify-between">
|
||||||
<div className="flex gap-6 items-end">
|
<div className="flex flex-1 sm:flex-row flex-col gap-6 items-center sm:items-end">
|
||||||
<Avatar className="size-32">
|
<Avatar className="size-32">
|
||||||
<AvatarImage src={character?.headPortrait} />
|
<AvatarImage src={character?.headPortrait} />
|
||||||
<AvatarFallback>{character?.name?.slice(0, 2)}</AvatarFallback>
|
<AvatarFallback>{character?.name?.slice(0, 2)}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div className="text-center sm:text-left">
|
||||||
<div className="txt-headline-s">{character?.name}</div>
|
<div className="txt-headline-s">{character?.name}</div>
|
||||||
<div className="flex flex-wrap mt-4 gap-2">
|
<div className="flex flex-wrap mt-4 gap-2 justify-center sm:justify-start">
|
||||||
{character?.tags?.map((tag: any) => (
|
{character?.tags?.map((tag: any) => (
|
||||||
<Chip className="rounded-xs" key={tag.tagId}>
|
<Chip className="rounded-xs" key={tag.tagId}>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
|
|
@ -27,15 +37,39 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="sm:block hidden">
|
||||||
<ChatButton id={id}></ChatButton>
|
<ChatButton id={id}></ChatButton>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="mt-12 rounded-2xl bg-white/10 p-6">
|
<div className="w-full flex bg-white/10 rounded-m py-3 mt-12">
|
||||||
|
<div className="w-full flex items-center flex-col">
|
||||||
|
<div className="text-white">
|
||||||
|
<IconButton variant="ghost" size="small" iconfont="icon-Like-fill" />
|
||||||
|
9.9k
|
||||||
|
</div>
|
||||||
|
<div className="text-txt-tertiary">Liked</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center flex-col">
|
||||||
|
<div className="text-[rgba(255,64,180,1)]">
|
||||||
|
<IconButton
|
||||||
|
className="text-[rgba(255,64,180,1)]"
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
iconfont="icon-icon_im_other"
|
||||||
|
/>
|
||||||
|
123456
|
||||||
|
</div>
|
||||||
|
<div className="text-txt-tertiary">Hot</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 rounded-2xl bg-white/10 p-6">
|
||||||
<div className="txt-headline-s">Introduction</div>
|
<div className="txt-headline-s">Introduction</div>
|
||||||
<div className="mt-4">{character?.description}</div>
|
<div className="mt-4">{character?.description}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="sm:hidden w-full mb-4">
|
||||||
|
<ChatButton className="w-full" id={id}></ChatButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export default function Input() {
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="small"
|
size="small"
|
||||||
iconfont="icon-voice_msg"
|
iconfont="icon-voice_msg"
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0 hover:bg-surface-element-hover"
|
||||||
/>
|
/>
|
||||||
<AuthHeightTextarea
|
<AuthHeightTextarea
|
||||||
placeholder="Chat"
|
placeholder="Chat"
|
||||||
|
|
@ -112,7 +112,7 @@ export default function Input() {
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => null}
|
onClick={() => null}
|
||||||
className={cn('bg-surface-element-hover flex-shrink-0')}
|
className={cn('hover:bg-surface-element-hover flex-shrink-0')}
|
||||||
iconfont="icon-prompt"
|
iconfont="icon-prompt"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,19 @@ import { useMedia } from '@/hooks/tools';
|
||||||
import Notice from './components/Notice';
|
import Notice from './components/Notice';
|
||||||
import { items } from './BottomBar';
|
import { items } from './BottomBar';
|
||||||
|
|
||||||
const mobileHidenMenus = ['/profile/edit', '/profile/account'];
|
const mobileHidenMenus = ['/profile/edit', '/profile/account', '/character/:id'];
|
||||||
|
|
||||||
|
// 将路由模式转换为正则表达式进行匹配
|
||||||
|
function matchRoutePattern(pathname: string, patterns: string[]): boolean {
|
||||||
|
return patterns.some((pattern) => {
|
||||||
|
// 将路由模式中的 :param 转换为正则表达式
|
||||||
|
const regexPattern = pattern
|
||||||
|
.replace(/:[^/]+/g, '[^/]+') // 将 :id, :userId 等替换为 [^/]+
|
||||||
|
.replace(/\//g, '\\/'); // 转义斜杠
|
||||||
|
const regex = new RegExp(`^${regexPattern}$`);
|
||||||
|
return regex.test(pathname);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function Topbar() {
|
function Topbar() {
|
||||||
const [isBlur, setIsBlur] = useState(false);
|
const [isBlur, setIsBlur] = useState(false);
|
||||||
|
|
@ -22,7 +34,7 @@ function Topbar() {
|
||||||
const response = useMedia();
|
const response = useMedia();
|
||||||
const searchParamsString = searchParams.toString();
|
const searchParamsString = searchParams.toString();
|
||||||
const redirectURL = `${pathname}${searchParamsString ? `?${searchParamsString}` : ''}`;
|
const redirectURL = `${pathname}${searchParamsString ? `?${searchParamsString}` : ''}`;
|
||||||
// const loginHref = `/login?redirect=${encodeURIComponent(redirectURL)}`;
|
const loginHref = `/login?redirect=${encodeURIComponent(redirectURL)}`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleScroll(event: Event) {
|
function handleScroll(event: Event) {
|
||||||
|
|
@ -73,7 +85,7 @@ function Topbar() {
|
||||||
const rightDomRender = () => {
|
const rightDomRender = () => {
|
||||||
if (!user)
|
if (!user)
|
||||||
return (
|
return (
|
||||||
<Link href="/login" prefetch>
|
<Link href={loginHref} prefetch>
|
||||||
<Button size="small">Login in / Sign up</Button>
|
<Button size="small">Login in / Sign up</Button>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
@ -96,7 +108,7 @@ function Topbar() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (response && !response.sm && mobileHidenMenus.some((item) => item === pathname)) return null;
|
if (response && !response.sm && matchRoutePattern(pathname, mobileHidenMenus)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue