mirror of
https://github.com/JefferyHcool/BiliNote.git
synced 2026-05-06 20:42:52 +08:00
fix(layout): 优化首页布局并添加可调整面板 fixes #123
- 使用 react-resizable-panels 实现可调整大小的面板 - 重新布局首页结构,分为左、中、右三个可调整区域 - 更新 NoteForm 和 NoteHistory 组件以适应新布局 - 调整 History 组件样式,优化滚动体验 - 更新项目依赖,添加 react-resizable-panels
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^2.1.8",
|
||||
"react-router-dom": "^7.5.1",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"remark-gfm": "1.0.0",
|
||||
|
||||
54
BillNote_frontend/src/components/ui/resizable.tsx
Normal file
54
BillNote_frontend/src/components/ui/resizable.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as React from "react"
|
||||
import { GripVerticalIcon } from "lucide-react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function ResizablePanelGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
|
||||
return (
|
||||
<ResizablePrimitive.PanelGroup
|
||||
data-slot="resizable-panel-group"
|
||||
className={cn(
|
||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ResizablePanel({
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
||||
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
|
||||
}
|
||||
|
||||
function ResizableHandle({
|
||||
withHandle,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||
withHandle?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
data-slot="resizable-handle"
|
||||
className={cn(
|
||||
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
||||
<GripVerticalIcon className="size-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePrimitive.PanelResizeHandle>
|
||||
)
|
||||
}
|
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
||||
@@ -2,10 +2,10 @@
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
html,body{
|
||||
html, body, #root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 修改滚动条轨道颜色 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px; /* 控制滚动条的宽度 */
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '@/components/ui/resizable'
|
||||
|
||||
interface IProps {
|
||||
NoteForm: React.ReactNode
|
||||
@@ -19,11 +20,11 @@ const HomeLayout: FC<IProps> = ({ NoteForm, Preview, History }) => {
|
||||
const [, setShowSettings] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col overflow-hidden bg-white">
|
||||
<div className="flex flex-1">
|
||||
{/* 左侧部分:Header + 表单 */}
|
||||
<aside className="flex w-[340px] flex-col border-r border-neutral-200 bg-white">
|
||||
{/* Header */}
|
||||
<div className="flex h-screen flex-col overflow-hidden">
|
||||
<ResizablePanelGroup direction="horizontal" className="h-full w-full">
|
||||
{/* 左边表单 */}
|
||||
<ResizablePanel defaultSize={18} minSize={10} maxSize={35}>
|
||||
<aside className="flex h-full flex-col overflow-hidden border-r border-neutral-200 bg-white">
|
||||
<header className="flex h-16 items-center justify-between px-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded-2xl">
|
||||
@@ -46,32 +47,26 @@ const HomeLayout: FC<IProps> = ({ NoteForm, Preview, History }) => {
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 表单内容 */}
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
{/*<NoteForm />*/}
|
||||
{NoteForm}
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto p-4">{NoteForm}</div>
|
||||
</aside>
|
||||
<aside className="flex h-full w-[300px] flex-col border-r border-neutral-200 bg-white">
|
||||
{/* Header */}
|
||||
</ResizablePanel>
|
||||
|
||||
{/* 表单内容 */}
|
||||
{/*<NoteForm />*/}
|
||||
{History}
|
||||
<ResizableHandle />
|
||||
|
||||
{/* 中间历史 */}
|
||||
<ResizablePanel defaultSize={16} minSize={10} maxSize={30}>
|
||||
<aside className="flex h-full flex-col overflow-hidden border-r border-neutral-200 bg-white">
|
||||
<div className="flex-1 overflow-auto p-4">{History}</div>
|
||||
</aside>
|
||||
</ResizablePanel>
|
||||
|
||||
{/* 右侧预览区域 */}
|
||||
<main className="h-screen flex-1 overflow-hidden bg-white p-6">
|
||||
{/*<Outlet />*/}
|
||||
{Preview}
|
||||
</main>
|
||||
</div>
|
||||
<ResizableHandle />
|
||||
|
||||
{/* 页脚 */}
|
||||
{/*<footer className="h-12 bg-white shadow-inner flex items-center justify-center text-sm text-neutral-600">*/}
|
||||
{/* © 2025 BiliNote. All rights reserved.*/}
|
||||
{/*</footer>*/}
|
||||
{/* 右边预览 */}
|
||||
<ResizablePanel defaultSize={55} minSize={30}>
|
||||
<main className="flex h-full flex-col overflow-hidden bg-white p-6">{Preview}</main>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ const History = () => {
|
||||
<Clock className="h-4 w-4 text-neutral-500" />
|
||||
<h2 className="text-base font-medium text-neutral-900">生成历史</h2>
|
||||
</div>
|
||||
<ScrollArea className="h-[800px] w-full">
|
||||
<ScrollArea className="w-full sm:h-[480px] md:h-[720px] lg:h-[92%]">
|
||||
{/*<div className="w-full flex-1 overflow-y-auto">*/}
|
||||
<NoteHistory onSelect={setCurrentTask} selectedId={currentTaskId} />
|
||||
{/*</div>*/}
|
||||
|
||||
@@ -34,6 +34,7 @@ import NoteHistory from '@/pages/HomePage/components/NoteHistory.tsx'
|
||||
import { useModelStore } from '@/store/modelStore'
|
||||
import { Alert } from 'antd'
|
||||
import { Textarea } from '@/components/ui/textarea.tsx'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area.tsx'
|
||||
// ✅ 定义表单 schema
|
||||
const formSchema = z.object({
|
||||
video_url: z.string().url('请输入正确的视频链接'),
|
||||
@@ -150,9 +151,16 @@ const NoteForm = () => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex h-full w-full flex-col overflow-hidden p-4">
|
||||
<div className="flex w-full items-center gap-2 py-1.5">
|
||||
<Button type="submit" className="bg-primary w-full sm:w-full" disabled={isGenerating()}>
|
||||
{isGenerating() && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isGenerating() ? '正在生成…' : '生成笔记'}
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollArea className="sm:h-[400px] md:h-[800px]">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
|
||||
<div className="space-y-2">
|
||||
<div className="my-3 flex items-center justify-between">
|
||||
<h2 className="block">视频链接</h2>
|
||||
@@ -223,7 +231,9 @@ const NoteForm = () => {
|
||||
<Info className="hover:text-primary h-4 w-4 cursor-pointer text-neutral-400" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="max-w-[200px] text-xs">质量越高,下载体积越大,速度越慢</p>
|
||||
<p className="max-w-[200px] text-xs">
|
||||
质量越高,下载体积越大,速度越慢
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -261,7 +271,9 @@ const NoteForm = () => {
|
||||
<Info className="hover:text-primary h-4 w-4 cursor-pointer text-neutral-400" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="max-w-[200px] text-xs">不同模型返回质量不同,可自行测试</p>
|
||||
<p className="max-w-[200px] text-xs">
|
||||
不同模型返回质量不同,可自行测试
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -339,7 +351,9 @@ const NoteForm = () => {
|
||||
<Info className="hover:text-primary h-4 w-4 cursor-pointer text-neutral-400" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">选择要包含的笔记元素,比如时间戳、截图提示或总结</p>
|
||||
<p className="text-xs">
|
||||
选择要包含的笔记元素,比如时间戳、截图提示或总结
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -397,16 +411,9 @@ const NoteForm = () => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className={'flex w-full items-center gap-2 py-1.5'}>
|
||||
{/* 提交按钮 */}
|
||||
<Button type="submit" className="bg-primary w-full" disabled={isGenerating()}>
|
||||
{isGenerating() && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{isGenerating() ? '正在生成…' : '生成笔记'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</ScrollArea>
|
||||
|
||||
{/* 添加一些额外的说明或功能介绍 */}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const NoteHistory: FC<NoteHistoryProps> = ({ onSelect, selectedId }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2 overflow-hidden">
|
||||
{tasks.map(task => (
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
Reference in New Issue
Block a user