diff --git a/.gitignore b/.gitignore index 2e615ab..ae2b90c 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,11 @@ next-env.d.ts *.zip *.rpm +# Docker images (large files, use Git LFS if needed) +/docker-images/**/*.tar +/docker-images/**/*.tar.gz +!docker-images/.gitkeep + # Temporary files deploy-commands.txt 构建速查表.txt diff --git a/Dockerfile b/Dockerfile index 644c882..c563afc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -243,6 +243,8 @@ COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma # 复制 Prisma 包,确保运行时存在 @prisma/client 及其生成器 COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma +# 复制 Prisma CLI(用于运行时执行 migrate deploy) +COPY --from=builder --chown=nextjs:nodejs /app/node_modules/prisma ./node_modules/prisma # 复制 Canvas 模块(可选依赖) # 如果 canvas 未安装,builder 阶段已创建占位符,所以这个 COPY 不会失败 diff --git a/app/admin/_components/AppearanceSettingsPanel.tsx b/app/admin/_components/AppearanceSettingsPanel.tsx index 4caaa69..d8009bc 100644 --- a/app/admin/_components/AppearanceSettingsPanel.tsx +++ b/app/admin/_components/AppearanceSettingsPanel.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react' import Image from 'next/image' import { useThemeStyles, useTheme, cn } from '@/lib/ui-styles' -import { animations, gradientBackgrounds, glowEffects, textAnimations } from '@/lib/ui-animations' +import { animations, gradientBackgrounds, glowEffects, textAnimations, scrollToElementWithSpring } from '@/lib/ui-animations' import type { SessionUser } from './types' type AppearanceSettingsPanelProps = { @@ -67,6 +67,8 @@ export default function AppearanceSettingsPanel({ sessionUser, variant = 'embedd const logoRef = useRef(null) const toggleRef = useRef(null) const storageRef = useRef(null) + const navCardRef = useRef(null) + const [navCardHeight, setNavCardHeight] = useState(0) const sectionRefs: Record<(typeof sectionConfig)[number]['key'], RefObject> = useMemo( () => ({ @@ -114,6 +116,33 @@ export default function AppearanceSettingsPanel({ sessionUser, variant = 'embedd return () => observer.disconnect() }, []) + // 动态计算导航卡片高度,用于设置状态卡片位置 + useEffect(() => { + const updateNavCardHeight = () => { + if (navCardRef.current) { + // 使用 getBoundingClientRect 获取更准确的高度 + const rect = navCardRef.current.getBoundingClientRect() + const height = rect.height + setNavCardHeight(height) + document.documentElement.style.setProperty('--nav-card-height', `${height}px`) + } + } + // 初始计算 + updateNavCardHeight() + // 延迟再次计算,确保 DOM 完全渲染 + const timeoutId = setTimeout(updateNavCardHeight, 100) + const resizeObserver = new ResizeObserver(updateNavCardHeight) + if (navCardRef.current) { + resizeObserver.observe(navCardRef.current) + } + window.addEventListener('resize', updateNavCardHeight) + return () => { + clearTimeout(timeoutId) + resizeObserver.disconnect() + window.removeEventListener('resize', updateNavCardHeight) + } + }, []) + const fetchSettings = useCallback(async () => { setLoading(true) try { @@ -247,7 +276,14 @@ export default function AppearanceSettingsPanel({ sessionUser, variant = 'embedd const scrollToSection = (key: SectionKey) => { const ref = sectionRefs[key] - ref?.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + if (!ref?.current) return + + // 使用苹果风格的缓入缓出滚动(页面级滚动,和分类管理保持一致) + scrollToElementWithSpring(ref.current, null, { + offset: 96, // 考虑 sticky top-24 (6rem) 的高度,和左侧小卡片上面对齐 + duration: 600, + easing: 'ease-in-out', + }) setActiveNav(key) } @@ -365,10 +401,10 @@ export default function AppearanceSettingsPanel({ sessionUser, variant = 'embedd