样式再调整

This commit is contained in:
海龙 2025-08-26 19:47:38 +08:00
parent a4a41da618
commit 8fa0adfdc4
5 changed files with 122 additions and 46 deletions

16
app/ScreenAdapter.tsx Normal file
View File

@ -0,0 +1,16 @@
'use client'
import { useEffect } from 'react';
import { createScreenAdapter } from '@/utils/tools';
export function ScreenAdapter() {
useEffect(() => {
// 页面加载时应用
window.addEventListener('load', createScreenAdapter)
// 窗口大小改变时重新应用
window.addEventListener('resize', createScreenAdapter)
}, []);
return null; // 这个组件不渲染任何内容
}

View File

@ -2,6 +2,9 @@ import './globals.css';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Providers } from '@/components/providers'; import { Providers } from '@/components/providers';
import { ConfigProvider, theme } from 'antd'; import { ConfigProvider, theme } from 'antd';
import { useEffect } from 'react';
import { createScreenAdapter } from '@/utils/tools';
import { ScreenAdapter } from './ScreenAdapter';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'AI Movie Flow - Create Amazing Videos with AI', title: 'AI Movie Flow - Create Amazing Videos with AI',
@ -29,6 +32,7 @@ export default function RootLayout({
}} }}
> >
<Providers> <Providers>
<ScreenAdapter />
{children} {children}
</Providers> </Providers>
</ConfigProvider> </ConfigProvider>

View File

@ -84,7 +84,9 @@ const RenderTemplateStoryMode = ({
// 本地加载状态,用于 UI 反馈 // 本地加载状态,用于 UI 反馈
const [localLoading, setLocalLoading] = useState(0); const [localLoading, setLocalLoading] = useState(0);
// 控制输入框显示状态 // 控制输入框显示状态
const [inputVisible, setInputVisible] = useState<{ [key: string]: boolean }>({}); const [inputVisible, setInputVisible] = useState<{ [key: string]: boolean }>(
{}
);
const router = useRouter(); const router = useRouter();
// 组件挂载时获取模板列表 // 组件挂载时获取模板列表
useEffect(() => { useEffect(() => {
@ -98,15 +100,18 @@ const RenderTemplateStoryMode = ({
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Element; const target = event.target as Element;
// 检查是否点击了输入框相关的元素 // 检查是否点击了输入框相关的元素
if (!target.closest('.ant-tooltip') && !target.closest('[data-alt*="field-ai-button"]')) { if (
!target.closest(".ant-tooltip") &&
!target.closest('[data-alt*="field-ai-button"]')
) {
// 关闭所有打开的输入框 // 关闭所有打开的输入框
setInputVisible({}); setInputVisible({});
} }
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener("mousedown", handleClickOutside);
return () => { return () => {
document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener("mousedown", handleClickOutside);
}; };
}, []); }, []);
@ -164,7 +169,7 @@ const RenderTemplateStoryMode = ({
const templateListRender = () => { const templateListRender = () => {
return ( return (
<div className="w-1/3 p-4 border-r border-white/[0.1]"> <div className="w-1/3 p-4 border-r border-white/[0.1]">
<div className="space-y-4 max-h-[700px] overflow-y-auto pr-3 template-list-scroll"> <div className="space-y-4 max-h-[700px] overflow-y-auto template-list-scroll">
{templateStoryList.map((template, index) => ( {templateStoryList.map((template, index) => (
<div <div
key={template.id} key={template.id}
@ -195,17 +200,11 @@ const RenderTemplateStoryMode = ({
<div className="flex gap-3 py-4 border-b border-white/[0.1] h-[300px]"> <div className="flex gap-3 py-4 border-b border-white/[0.1] h-[300px]">
{/* 左侧图片 */} {/* 左侧图片 */}
<div className="w-1/4"> <div className="w-1/4">
<div <Image
data-alt="template-preview-image" src={selectedTemplate.image_url[0]}
className="relative w-full aspect-square rounded-xl overflow-hidden group cursor-pointer" alt={selectedTemplate.name}
> className="w-4 h-5 !object-contain transition-all duration-500 group-hover:scale-105 group-hover:rotate-1"
<img />
src={selectedTemplate.image_url[0]}
alt={selectedTemplate.name}
className="w-full h-full object-contain transition-all duration-500 group-hover:scale-105 group-hover:rotate-1"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
</div>
</div> </div>
{/* 右侧信息 - 增加文本渲染空间 */} {/* 右侧信息 - 增加文本渲染空间 */}
@ -217,7 +216,7 @@ const RenderTemplateStoryMode = ({
{selectedTemplate.name} {selectedTemplate.name}
</h2> </h2>
<div <div
className="flex-1 overflow-y-auto max-h-96 pr-3" className="flex-1 overflow-y-auto max-h-96 "
style={{ style={{
scrollbarWidth: "thin", scrollbarWidth: "thin",
scrollbarColor: "rgba(156,163,175,0.2) rgba(0,0,0,0)", scrollbarColor: "rgba(156,163,175,0.2) rgba(0,0,0,0)",
@ -235,7 +234,7 @@ const RenderTemplateStoryMode = ({
{/* 变量字段填写区域 */} {/* 变量字段填写区域 */}
{selectedTemplate?.fillable_content && {selectedTemplate?.fillable_content &&
selectedTemplate.fillable_content.length > 0 && ( selectedTemplate.fillable_content.length > 0 && (
<div className="p-4 border-t border-white/10"> <div className="pt-2 border-t border-white/10">
<h3 <h3
data-alt="variables-section-title" data-alt="variables-section-title"
className="text-lg font-semibold text-white mb-4" className="text-lg font-semibold text-white mb-4"
@ -275,7 +274,10 @@ const RenderTemplateStoryMode = ({
field.field_name, field.field_name,
field.value || "" field.value || ""
); );
setInputVisible(prev => ({ ...prev, [field.field_name]: false })); setInputVisible((prev) => ({
...prev,
[field.field_name]: false,
}));
}} }}
icon={<Sparkles className="w-4 h-4" />} icon={<Sparkles className="w-4 h-4" />}
width="w-8" width="w-8"
@ -289,14 +291,19 @@ const RenderTemplateStoryMode = ({
root: "max-w-none", root: "max-w-none",
}} }}
open={inputVisible[field.field_name]} open={inputVisible[field.field_name]}
onOpenChange={(visible) => setInputVisible(prev => ({ ...prev, [field.field_name]: visible }))} onOpenChange={(visible) =>
setInputVisible((prev) => ({
...prev,
[field.field_name]: visible,
}))
}
trigger="contextMenu" trigger="contextMenu"
styles={{ root: { zIndex: 1000 } }} styles={{ root: { zIndex: 1000 } }}
> >
{/* 图片 */} {/* 图片 */}
<div <div
data-alt={`field-thumbnail-${index}`} data-alt={`field-thumbnail-${index}`}
className="w-24 h-24 rounded-xl overflow-hidden border border-white/20 bg-white/[0.05] flex items-center justify-center cursor-pointer hover:scale-105 transition-all duration-200" className="w-24 h-24 rounded-xl overflow-hidden border border-white/10 bg-white/0 flex items-center justify-center cursor-pointer hover:scale-105 transition-all duration-200"
> >
<Image <Image
src={ src={
@ -328,7 +335,12 @@ const RenderTemplateStoryMode = ({
<Tooltip title="AI generate image" placement="top"> <Tooltip title="AI generate image" placement="top">
<button <button
data-alt={`field-ai-button-${index}`} data-alt={`field-ai-button-${index}`}
onClick={() => setInputVisible(prev => ({ ...prev, [field.field_name]: !prev[field.field_name] }))} onClick={() =>
setInputVisible((prev) => ({
...prev,
[field.field_name]: !prev[field.field_name],
}))
}
className="w-6 h-6 bg-purple-500 hover:bg-purple-600 text-white rounded-full flex items-center justify-center transition-all duration-200 hover:scale-110 shadow-lg" className="w-6 h-6 bg-purple-500 hover:bg-purple-600 text-white rounded-full flex items-center justify-center transition-all duration-200 hover:scale-110 shadow-lg"
> >
<Sparkles className="w-3.5 h-3.5" /> <Sparkles className="w-3.5 h-3.5" />
@ -352,7 +364,11 @@ const RenderTemplateStoryMode = ({
} }
return true; return true;
}} }}
customRequest={async ({ file, onSuccess, onError }) => { customRequest={async ({
file,
onSuccess,
onError,
}) => {
try { try {
const fileObj = file as File; const fileObj = file as File;
const uploadedUrl = await uploadFile( const uploadedUrl = await uploadFile(

View File

@ -12,10 +12,10 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(true); const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background" id="app">
<Sidebar collapsed={sidebarCollapsed} onToggle={setSidebarCollapsed} /> <Sidebar collapsed={sidebarCollapsed} onToggle={setSidebarCollapsed} />
<TopBar collapsed={sidebarCollapsed} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} /> <TopBar collapsed={sidebarCollapsed} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)} />
{children} {children}
</div> </div>
); );
} }

View File

@ -1,17 +1,16 @@
import { ScriptSlice, ScriptSliceType } from "@/app/service/domain/valueObject"; import { ScriptSlice, ScriptSliceType } from "@/app/service/domain/valueObject";
export function parseScriptEntity(text: string):ScriptSlice { export function parseScriptEntity(text: string): ScriptSlice {
const scriptSlice = new ScriptSlice( const scriptSlice = new ScriptSlice(
// 生成唯一ID单次使用即可 // 生成唯一ID单次使用即可
`${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`, `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
ScriptSliceType.text, ScriptSliceType.text,
text, text,
{} {}
); );
return scriptSlice; return scriptSlice;
} }
/** /**
* @description * @description
* @param {Function} func - * @param {Function} func -
@ -22,16 +21,57 @@ export function parseScriptEntity(text: string):ScriptSlice {
* const throttledFn = throttle(() => { console.log('触发'); }, 1000); * const throttledFn = throttle(() => { console.log('触发'); }, 1000);
* window.addEventListener('resize', throttledFn); * window.addEventListener('resize', throttledFn);
*/ */
export function throttle<T extends (...args: any[]) => any>(func: T, delay: number=100): (...args: Parameters<T>) => void { export function throttle<T extends (...args: any[]) => any>(
if (typeof delay !== 'number' || delay < 0) { func: T,
throw new Error('throttle: 第二个参数必须是非负数'); delay: number = 100
): (...args: Parameters<T>) => void {
if (typeof delay !== "number" || delay < 0) {
throw new Error("throttle: 第二个参数必须是非负数");
}
let lastCall = 0;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func(...args);
} }
let lastCall = 0; };
return (...args: Parameters<T>) => { }
const now = Date.now(); /**
if (now - lastCall >= delay) { * 1920x1080屏幕适配的CSS样式
lastCall = now; *
func(...args); */
} export function createScreenAdapter(): void {
}; // 获取当前窗口尺寸
const currentWidth = window.innerWidth;
const currentHeight = window.innerHeight;
// 计算缩放比例 (1920x1080)
const wScale = currentWidth / 1920;
const hScale = currentHeight / 1080;
// 检查app节点是否存在
const app = document.getElementById("app");
if (!app) {
console.error("未找到app节点");
return;
}
// 创建样式元素
const style = document.createElement("style");
// 设置CSS样式
style.textContent = `
body {
#app {
transform-origin: top left;
transform: scale(${wScale}, ${hScale});
width: 1920px;
height: 1080px;
}
}
`;
// 将样式添加到head
document.head.appendChild(style);
} }