From 3e176bc2655d4b55d0a131fd03db3c84e3ade9d9 Mon Sep 17 00:00:00 2001 From: qikongjian Date: Wed, 24 Sep 2025 15:56:55 +0800 Subject: [PATCH] =?UTF-8?q?work-flow=E8=A7=86=E9=A2=91=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E9=80=9A=E8=BF=87video=5Fmodification?= =?UTF-8?q?=E6=9D=A5=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 4 +- app/api/server-setting/find_by_code/route.ts | 17 ++ app/test-server-config/page.tsx | 112 ++++++++++ components/pages/work-flow/media-viewer.tsx | 41 +++- .../work-flow/video-edit/EditConnection.tsx | 149 ++++--------- .../work-flow/video-edit/EditDescription.tsx | 87 ++++---- .../work-flow/video-edit/connection-config.ts | 196 ++++++++++++++++++ lib/server-config.ts | 116 ++++++++++- 8 files changed, 569 insertions(+), 153 deletions(-) create mode 100644 app/test-server-config/page.tsx create mode 100644 components/pages/work-flow/video-edit/connection-config.ts diff --git a/.env.development b/.env.development index e37d8cc..215df85 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ - -NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com +NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai +# NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com diff --git a/app/api/server-setting/find_by_code/route.ts b/app/api/server-setting/find_by_code/route.ts index dd178ae..6f500b2 100644 --- a/app/api/server-setting/find_by_code/route.ts +++ b/app/api/server-setting/find_by_code/route.ts @@ -59,6 +59,23 @@ export async function POST(request: NextRequest) { updated_at: new Date().toISOString().slice(0, 19) }; break; + + case 'video_modification': + // 视频修改功能配置 - 控制视频编辑笔图标显示 + // 可以通过查询参数 ?show=false 来测试隐藏功能 + const url = new URL(request.url); + const showParam = url.searchParams.get('show'); + const showValue = showParam !== null ? showParam === 'true' : true; // 默认显示 + + responseData = { + id: 9, + code: 'video_modification', + value: `{\n "show": ${showValue}\n}`, + note: '视频修改功能开关', + updated_at: new Date().toISOString().slice(0, 19) + }; + console.log('📋 video_modification配置:', { showParam, showValue, value: responseData.value }); + break; default: // 默认返回空配置 diff --git a/app/test-server-config/page.tsx b/app/test-server-config/page.tsx new file mode 100644 index 0000000..9d4b9e6 --- /dev/null +++ b/app/test-server-config/page.tsx @@ -0,0 +1,112 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { isVideoModificationEnabled, isGoogleLoginEnabled } from '@/lib/server-config'; + +export default function TestServerConfigPage() { + const [ssoStatus, setSsoStatus] = useState(null); + const [videoModStatus, setVideoModStatus] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const testConfigs = async () => { + setLoading(true); + setError(null); + + try { + console.log('🧪 开始测试服务器配置...'); + + // 测试SSO配置 + const ssoEnabled = await isGoogleLoginEnabled(); + console.log('📋 SSO配置结果:', ssoEnabled); + setSsoStatus(ssoEnabled); + + // 测试视频修改配置 + const videoModEnabled = await isVideoModificationEnabled(); + console.log('📋 视频修改配置结果:', videoModEnabled); + setVideoModStatus(videoModEnabled); + + console.log('✅ 所有配置测试完成'); + } catch (err) { + console.error('❌ 配置测试失败:', err); + setError(err instanceof Error ? err.message : '未知错误'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + testConfigs(); + }, []); + + return ( +
+
+

服务器配置测试页面

+ +
+

配置状态

+ +
+
+ Google登录 (sso_config): + + {ssoStatus === null ? '检测中...' : ssoStatus ? '启用' : '禁用'} + +
+ +
+ 视频修改 (video_modification): + + {videoModStatus === null ? '检测中...' : videoModStatus ? '启用' : '禁用'} + +
+
+ + {error && ( +
+

错误:

+

{error}

+
+ )} + +
+ + + +
+
+ +
+

API测试信息

+
+

SSO API: POST /api/server-setting/find_by_code {"{ code: 'sso_config' }"}

+

视频修改API: POST /api/server-setting/find_by_code {"{ code: 'video_modification' }"}

+

预期响应格式: {"{ code: 0, successful: true, data: { value: '{\"show\": true}' } }"}

+
+
+ +
+

请打开浏览器开发者工具查看详细的API调用日志

+
+
+
+ ); +} diff --git a/components/pages/work-flow/media-viewer.tsx b/components/pages/work-flow/media-viewer.tsx index 0cafa86..501db9d 100644 --- a/components/pages/work-flow/media-viewer.tsx +++ b/components/pages/work-flow/media-viewer.tsx @@ -14,6 +14,7 @@ import { Button, Tooltip } from 'antd'; import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools'; import { VideoEditOverlay } from './video-edit/VideoEditOverlay'; import { EditPoint as EditPointType } from './video-edit/types'; +import { isVideoModificationEnabled } from '@/lib/server-config'; interface MediaViewerProps { taskObject: TaskObject; @@ -78,6 +79,8 @@ export const MediaViewer = React.memo(function MediaViewer({ const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false); const [isLoadingDownloadAllVideosBtn, setIsLoadingDownloadAllVideosBtn] = useState(false); const [isVideoEditMode, setIsVideoEditMode] = useState(false); + // 控制钢笔图标显示的状态 - 参考谷歌登录按钮的实现 + const [showVideoModification, setShowVideoModification] = useState(false); useEffect(() => { if (isSmartChatBoxOpen) { @@ -89,6 +92,33 @@ export const MediaViewer = React.memo(function MediaViewer({ } }, [isSmartChatBoxOpen]) + // 检查视频修改功能是否启用 - 参考谷歌登录按钮的实现 + useEffect(() => { + const checkVideoModificationStatus = async () => { + try { + console.log('🔍 MediaViewer:开始检查视频修改功能状态...'); + const enabled = await isVideoModificationEnabled(); + console.log('📋 MediaViewer:视频修改功能启用状态:', enabled); + setShowVideoModification(enabled); + console.log('📋 MediaViewer:设置showVideoModification状态为:', enabled); + } catch (error) { + console.error("❌ MediaViewer:Failed to check video modification status:", error); + setShowVideoModification(false); // 出错时默认不显示 + } + }; + + checkVideoModificationStatus(); + }, []); // 只在组件挂载时执行一次 + + // 调试:监控钢笔图标显示状态 + useEffect(() => { + console.log('🔧 MediaViewer状态更新:', { + enableVideoEdit, + showVideoModification, + shouldShowPenIcon: enableVideoEdit && showVideoModification + }); + }, [enableVideoEdit, showVideoModification]); + // 音量控制函数 const toggleMute = () => { setUserHasInteracted(true); @@ -526,17 +556,20 @@ export const MediaViewer = React.memo(function MediaViewer({
- {/* 视频编辑模式切换按钮 - 临时注释 */} - {/* {enableVideoEdit && ( + {/* 视频编辑模式切换按钮 - 通过服务器配置控制显示 */} + {enableVideoEdit && showVideoModification && ( setIsVideoEditMode(!isVideoEditMode)} + onClick={() => { + console.log('🖊️ 钢笔图标被点击,切换编辑模式:', !isVideoEditMode); + setIsVideoEditMode(!isVideoEditMode); + }} className={isVideoEditMode ? 'bg-blue-500/20 border-blue-500/50' : ''} /> - )} */} + )} {/* 添加到chat去编辑 按钮 */} { diff --git a/components/pages/work-flow/video-edit/EditConnection.tsx b/components/pages/work-flow/video-edit/EditConnection.tsx index 6e561ad..9466387 100644 --- a/components/pages/work-flow/video-edit/EditConnection.tsx +++ b/components/pages/work-flow/video-edit/EditConnection.tsx @@ -6,6 +6,13 @@ import React, { useMemo } from 'react'; import { motion } from 'framer-motion'; import { ConnectionPathParams, InputBoxPosition } from './types'; +import { + CONNECTION_STYLE, + ARROW_GEOMETRY, + calculateArrowGeometry, + calculateCurvePath as calculateUnifiedCurvePath, + getConnectionAnimationConfig +} from './connection-config'; interface EditConnectionProps { /** 起始点坐标(编辑点位置) */ @@ -95,7 +102,8 @@ export function calculateInputPosition( direction = 'right'; inputX = pointX + connectionLength; inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2)); - connectionEndX = inputX; + // 箭头指向输入框左边缘的中心 + connectionEndX = inputX - 8; // 向内偏移8px,指向输入框内部 connectionEndY = inputY + inputHeight / 2; } // 其次选择左侧 @@ -103,7 +111,8 @@ export function calculateInputPosition( direction = 'left'; inputX = pointX - connectionLength - inputWidth; inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2)); - connectionEndX = inputX + inputWidth; + // 箭头指向输入框右边缘的中心 + connectionEndX = inputX + inputWidth + 8; // 向内偏移8px,指向输入框内部 connectionEndY = inputY + inputHeight / 2; } // 然后选择下方 @@ -111,23 +120,26 @@ export function calculateInputPosition( direction = 'bottom'; inputX = Math.max(margin, Math.min(containerWidth - inputWidth - margin, pointX - inputWidth / 2)); inputY = pointY + connectionLength; + // 箭头指向输入框上边缘的中心 connectionEndX = inputX + inputWidth / 2; - connectionEndY = inputY; + connectionEndY = inputY - 8; // 向内偏移8px,指向输入框内部 } // 最后选择上方 else if (spaceTop >= inputHeight + connectionLength + margin) { direction = 'top'; inputX = Math.max(margin, Math.min(containerWidth - inputWidth - margin, pointX - inputWidth / 2)); inputY = pointY - connectionLength - inputHeight; + // 箭头指向输入框下边缘的中心 connectionEndX = inputX + inputWidth / 2; - connectionEndY = inputY + inputHeight; + connectionEndY = inputY + inputHeight + 8; // 向内偏移8px,指向输入框内部 } // 如果空间不足,强制放在右侧并调整位置 else { direction = 'right'; inputX = Math.min(containerWidth - inputWidth - margin, pointX + 40); inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2)); - connectionEndX = inputX; + // 箭头指向输入框左边缘的中心 + connectionEndX = inputX - 8; // 向内偏移8px,指向输入框内部 connectionEndY = inputY + inputHeight / 2; } @@ -150,80 +162,30 @@ export const EditConnection: React.FC = ({ curvature = 0.3, animated = true }) => { + // 使用统一的样式配置 const { - color = 'rgba(255, 255, 255, 0.9)', // White color to match the reference image - strokeWidth = 2, - dashArray = '8,4' // Dashed line to match the reference image + color = CONNECTION_STYLE.color, + strokeWidth = CONNECTION_STYLE.strokeWidth, + dashArray = CONNECTION_STYLE.dashArray } = style; - // 计算箭头几何参数 - const arrowSize = 8; - const arrowHalfHeight = 4; + // 使用统一的箭头几何计算 + const arrowGeometry = useMemo(() => + calculateArrowGeometry(startPoint, endPoint), + [startPoint, endPoint] + ); - // 计算连接方向和角度 - const connectionVector = useMemo(() => { - const dx = endPoint.x - startPoint.x; - const dy = endPoint.y - startPoint.y; - const length = Math.sqrt(dx * dx + dy * dy); - return { - dx: dx / length, - dy: dy / length, - angle: Math.atan2(dy, dx) - }; - }, [startPoint, endPoint]); - - // 计算箭头的正确位置和线条终点 - const arrowGeometry = useMemo(() => { - const { dx, dy, angle } = connectionVector; - - // 箭头尖端位置(原endPoint) - const arrowTip = { x: endPoint.x, y: endPoint.y }; - - // 箭头底部中心点(线条应该连接到这里) - const arrowBase = { - x: endPoint.x - dx * arrowSize, - y: endPoint.y - dy * arrowSize - }; - - // 计算箭头三角形的三个顶点 - const perpX = -dy; // 垂直向量X - const perpY = dx; // 垂直向量Y - - const arrowPoints = [ - arrowTip, // 尖端 - { - x: arrowBase.x + perpX * arrowHalfHeight, - y: arrowBase.y + perpY * arrowHalfHeight - }, - { - x: arrowBase.x - perpX * arrowHalfHeight, - y: arrowBase.y - perpY * arrowHalfHeight - } - ]; - - return { - tip: arrowTip, - base: arrowBase, - points: arrowPoints, - angle - }; - }, [endPoint, connectionVector, arrowSize, arrowHalfHeight]); - - // 计算路径(线条终止于箭头底部中心) + // 使用统一的路径计算 const path = useMemo(() => - calculateCurvePath({ - start: startPoint, - end: arrowGeometry.base, // 连接到箭头底部中心而不是尖端 - containerSize, - curvature - }), [startPoint, arrowGeometry.base, containerSize, curvature]); + calculateUnifiedCurvePath(startPoint, arrowGeometry.center, containerSize), + [startPoint, arrowGeometry.center, containerSize] + ); - // 计算路径长度用于动画 - const pathLength = useMemo(() => { - const dx = arrowGeometry.base.x - startPoint.x; - const dy = arrowGeometry.base.y - startPoint.y; - return Math.sqrt(dx * dx + dy * dy) * 1.2; // 弧线比直线长约20% - }, [startPoint, arrowGeometry.base]); + // 获取统一的动画配置 + const animationConfig = useMemo(() => + getConnectionAnimationConfig(animated), + [animated] + ); return ( = ({ height={containerSize.height} style={{ zIndex: 10 }} > - {/* Curved dashed line - properly aligned to arrow base center */} + {/* 统一的虚线连接线 - 精确连接到箭头中心 */} = ({ strokeDasharray={dashArray} strokeLinecap="round" strokeLinejoin="round" - initial={animated ? { - pathLength: 0, - opacity: 0 - } : {}} - animate={animated ? { - pathLength: 1, - opacity: 1 - } : {}} - transition={animated ? { - pathLength: { duration: 0.6, ease: "easeInOut" }, - opacity: { duration: 0.3 } - } : {}} + initial={animationConfig.line.initial} + animate={animationConfig.line.animate} + transition={animationConfig.line.transition} style={{ - filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))' + filter: CONNECTION_STYLE.dropShadow }} /> - {/* Properly aligned arrow head with geometric precision */} + {/* 几何精确的箭头 - 与连接线完美对齐 */} `${p.x},${p.y}`).join(' ')} fill={color} - initial={animated ? { - scale: 0, - opacity: 0 - } : {}} - animate={animated ? { - scale: 1, - opacity: 1 - } : {}} - transition={animated ? { - delay: 0.4, - duration: 0.3, - type: "spring", - stiffness: 300, - damping: 25 - } : {}} + initial={animationConfig.arrow.initial} + animate={animationConfig.arrow.animate} + transition={animationConfig.arrow.transition} style={{ - filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))' + filter: CONNECTION_STYLE.dropShadow }} /> diff --git a/components/pages/work-flow/video-edit/EditDescription.tsx b/components/pages/work-flow/video-edit/EditDescription.tsx index c2ea896..37a5034 100644 --- a/components/pages/work-flow/video-edit/EditDescription.tsx +++ b/components/pages/work-flow/video-edit/EditDescription.tsx @@ -6,6 +6,12 @@ import React, { useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { EditPoint as EditPointType, EditPointStatus } from './types'; +import { + CONNECTION_STYLE, + calculateArrowGeometry, + calculateCurvePath, + getConnectionAnimationConfig +} from './connection-config'; interface EditDescriptionProps { /** 编辑点数据 */ @@ -42,25 +48,31 @@ export const EditDescription: React.FC = ({ y: (editPoint.position.y / 100) * containerSize.height }), [editPoint.position, containerSize]); - // 计算连接线路径 - const connectionPath = useMemo(() => { - const startX = editPointPosition.x; - const startY = editPointPosition.y; - const endX = connectionEnd.x; - const endY = connectionEnd.y; + // 使用统一的连接线几何计算 + const connectionGeometry = useMemo(() => { + const startPoint = { x: editPointPosition.x, y: editPointPosition.y }; + const endPoint = { x: connectionEnd.x, y: connectionEnd.y }; - // 计算控制点,创建优雅的弧线 - const deltaX = endX - startX; - const deltaY = endY - startY; - const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - - // 控制点偏移量,创建自然的弧线 - const controlOffset = Math.min(distance * 0.3, 60); - const controlX = startX + deltaX * 0.5 + (deltaY > 0 ? -controlOffset : controlOffset); - const controlY = startY + deltaY * 0.5 - Math.abs(deltaX) * 0.2; + // 使用统一的箭头几何计算 + const arrowGeometry = calculateArrowGeometry(startPoint, endPoint); - return `M ${startX} ${startY} Q ${controlX} ${controlY} ${endX} ${endY}`; - }, [editPointPosition, connectionEnd]); + // 使用统一的路径计算 + const path = calculateCurvePath(startPoint, arrowGeometry.center, containerSize); + + return { + path, + arrowPoints: arrowGeometry.points, + arrowTip: arrowGeometry.tip, + arrowBase: arrowGeometry.base, + arrowCenter: arrowGeometry.center + }; + }, [editPointPosition, connectionEnd, containerSize]); + + // 获取统一的动画配置 + const animationConfig = useMemo(() => + getConnectionAnimationConfig(true), // EditDescription总是使用动画 + [] + ); // 获取状态颜色 const getStatusColor = () => { @@ -101,7 +113,7 @@ export const EditDescription: React.FC = ({ {editPoint.description && editPoint.status !== EditPointStatus.PENDING && ( <> - {/* White dashed connection line to match reference image */} + {/* 统一的虚线连接线 - 与EditConnection完全一致 */} = ({ exit={{ opacity: 0 }} transition={{ duration: 0.5 }} > + {/* 统一的虚线连接线 - 与EditConnection完全一致 */} - {/* Arrow head */} + {/* 几何精确的箭头 - 与连接线完美对齐 */} `${p.x},${p.y}`).join(' ')} + fill={CONNECTION_STYLE.color} + initial={animationConfig.arrow?.initial} + animate={animationConfig.arrow?.animate} + exit={animationConfig.arrow?.initial} + transition={animationConfig.arrow?.transition} style={{ - filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))' + filter: CONNECTION_STYLE.dropShadow }} /> diff --git a/components/pages/work-flow/video-edit/connection-config.ts b/components/pages/work-flow/video-edit/connection-config.ts new file mode 100644 index 0000000..de99804 --- /dev/null +++ b/components/pages/work-flow/video-edit/connection-config.ts @@ -0,0 +1,196 @@ +/** + * 视频编辑连接线统一配置 + * 确保所有连接线组件使用一致的视觉参数和几何计算 + */ + +/** + * 统一的连接线视觉样式配置 + */ +export const CONNECTION_STYLE = { + // 颜色配置 + color: 'rgba(255, 255, 255, 0.9)', // 统一的白色,确保在深色背景下清晰可见 + strokeWidth: 2, // 统一的线条粗细 + dashArray: '8,4', // 统一的虚线样式:8px实线,4px间隔 + + // 阴影效果 + dropShadow: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))', + + // 动画配置 + animation: { + pathDuration: 0.6, + pathEasing: 'easeInOut', + opacityDuration: 0.3, + arrowDelay: 0.4, + arrowDuration: 0.3, + springConfig: { + stiffness: 300, + damping: 25 + } + } +} as const; + +/** + * 统一的箭头几何参数 + */ +export const ARROW_GEOMETRY = { + size: 8, // 箭头长度 + halfHeight: 4, // 箭头半高(宽度的一半) + centerOffset: 0.6 // 连接线连接到箭头的位置比例(0.6表示稍微向前偏移) +} as const; + +/** + * 弧线计算参数 + */ +export const CURVE_CONFIG = { + curvature: 0.3, // 弧线弯曲程度 + minControlOffset: 10, // 最小控制点偏移 + maxControlOffset: 60 // 最大控制点偏移 +} as const; + +/** + * 计算统一的箭头几何形状 + */ +export function calculateArrowGeometry( + startPoint: { x: number; y: number }, + endPoint: { x: number; y: number } +) { + // 计算连接方向向量 + const dx = endPoint.x - startPoint.x; + const dy = endPoint.y - startPoint.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const normalizedDx = dx / distance; + const normalizedDy = dy / distance; + + // 箭头几何计算 + const arrowTip = { x: endPoint.x, y: endPoint.y }; + const arrowBase = { + x: endPoint.x - normalizedDx * ARROW_GEOMETRY.size, + y: endPoint.y - normalizedDy * ARROW_GEOMETRY.size + }; + const arrowCenter = { + x: endPoint.x - normalizedDx * (ARROW_GEOMETRY.size * ARROW_GEOMETRY.centerOffset), + y: endPoint.y - normalizedDy * (ARROW_GEOMETRY.size * ARROW_GEOMETRY.centerOffset) + }; + + // 计算垂直向量用于箭头宽度 + const perpX = -normalizedDy; + const perpY = normalizedDx; + + const arrowPoints = [ + arrowTip, + { + x: arrowBase.x + perpX * ARROW_GEOMETRY.halfHeight, + y: arrowBase.y + perpY * ARROW_GEOMETRY.halfHeight + }, + { + x: arrowBase.x - perpX * ARROW_GEOMETRY.halfHeight, + y: arrowBase.y - perpY * ARROW_GEOMETRY.halfHeight + } + ]; + + return { + tip: arrowTip, + base: arrowBase, + center: arrowCenter, + points: arrowPoints, + direction: { dx: normalizedDx, dy: normalizedDy }, + perpendicular: { perpX, perpY }, + distance + }; +} + +/** + * 计算统一的弧线路径 + */ +export function calculateCurvePath( + startPoint: { x: number; y: number }, + endPoint: { x: number; y: number }, + containerSize: { width: number; height: number } +): string { + const dx = endPoint.x - startPoint.x; + const dy = endPoint.y - startPoint.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // 计算控制点,创建优雅的弧线 + const midX = (startPoint.x + endPoint.x) / 2; + const midY = (startPoint.y + endPoint.y) / 2; + + let controlX = midX; + let controlY = midY; + + // 根据方向调整控制点 + if (Math.abs(dx) > Math.abs(dy)) { + controlY = midY + (dy > 0 ? -1 : 1) * distance * CURVE_CONFIG.curvature; + } else { + controlX = midX + (dx > 0 ? -1 : 1) * distance * CURVE_CONFIG.curvature; + } + + // 确保控制点在容器范围内 + controlX = Math.max(CURVE_CONFIG.minControlOffset, + Math.min(containerSize.width - CURVE_CONFIG.minControlOffset, controlX)); + controlY = Math.max(CURVE_CONFIG.minControlOffset, + Math.min(containerSize.height - CURVE_CONFIG.minControlOffset, controlY)); + + // 创建二次贝塞尔曲线路径 + return `M ${startPoint.x} ${startPoint.y} Q ${controlX} ${controlY} ${endPoint.x} ${endPoint.y}`; +} + +/** + * 动画配置类型定义 + */ +export interface ConnectionAnimationConfig { + line: { + initial: Record; + animate: Record; + transition: Record; + }; + arrow: { + initial: Record; + animate: Record; + transition: Record; + }; +} + +/** + * 获取统一的动画配置 + */ +export function getConnectionAnimationConfig(animated: boolean = true): ConnectionAnimationConfig { + if (!animated) { + return { + line: { + initial: {}, + animate: {}, + transition: {} + }, + arrow: { + initial: {}, + animate: {}, + transition: {} + } + }; + } + + return { + line: { + initial: { pathLength: 0, opacity: 0 }, + animate: { pathLength: 1, opacity: 1 }, + transition: { + pathLength: { + duration: CONNECTION_STYLE.animation.pathDuration, + ease: CONNECTION_STYLE.animation.pathEasing as any + }, + opacity: { duration: CONNECTION_STYLE.animation.opacityDuration } + } + }, + arrow: { + initial: { scale: 0, opacity: 0 }, + animate: { scale: 1, opacity: 1 }, + transition: { + delay: CONNECTION_STYLE.animation.arrowDelay, + duration: CONNECTION_STYLE.animation.arrowDuration, + type: "spring" as const, + ...CONNECTION_STYLE.animation.springConfig + } + } + }; +} diff --git a/lib/server-config.ts b/lib/server-config.ts index c27e7e4..01767ee 100644 --- a/lib/server-config.ts +++ b/lib/server-config.ts @@ -2,7 +2,32 @@ * 服务端配置工具函数 */ -import { post } from '@/api/request'; +// 注意:这里不使用 @/api/request 中的 post 函数,因为它会将请求发送到远程服务器 +// 我们需要直接调用本地的 Next.js API 路由 + +/** + * 本地API请求函数 - 直接调用Next.js API路由 + */ +const localPost = async (url: string, data: any): Promise => { + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Local API request failed:', error); + throw error; + } +}; /** * SSO配置接口 @@ -14,6 +39,13 @@ export interface SSOConfig { description: string; } +/** + * 视频修改配置接口 + */ +export interface VideoModificationConfig { + show: boolean; +} + /** * 获取SSO配置 * @returns Promise @@ -21,7 +53,7 @@ export interface SSOConfig { export const getSSOConfig = async (): Promise => { try { console.log('🔍 开始获取SSO配置...'); - const res = await post(`/api/server-setting/find_by_code`, { code: 'sso_config' }); + const res = await localPost(`/api/server-setting/find_by_code`, { code: 'sso_config' }); console.log('📋 SSO API响应:', res); @@ -64,21 +96,21 @@ export const isGoogleLoginEnabled = async (): Promise => { console.log('🔍 检查Google登录是否启用...'); const config = await getSSOConfig(); console.log('📋 获得的配置:', config); - + if (!config) { console.log('❌ 没有获得配置,返回false'); return false; } - + const isEnabled = config?.show === true; - + console.log('🔍 配置检查:', { show: config?.show, provider: config?.provider, isEnabled, finalResult: isEnabled }); - + // 简化逻辑:只检查show字段,因为sso_config专门用于Google登录 return isEnabled; } catch (error) { @@ -86,3 +118,75 @@ export const isGoogleLoginEnabled = async (): Promise => { return false; // 出错时默认不显示 } }; + +/** + * 获取视频修改配置 + * @returns Promise + */ +export const getVideoModificationConfig = async (): Promise => { + try { + console.log('🔍 开始获取视频修改配置...'); + const res = await localPost(`/api/server-setting/find_by_code`, { code: 'video_modification' }); + + console.log('📋 视频修改配置API响应:', res); + + if (!res || res.code !== 0 || !res.successful || !res.data) { + console.warn('❌ Failed to fetch video modification config:', res); + return null; + } + + // 新的数据格式:data直接包含id, code, value等字段 + const { value } = res.data; + console.log('📝 视频修改配置原始value:', value); + console.log('📝 value类型:', typeof value, 'value长度:', value?.length); + + if (typeof value !== 'string' || value.length === 0) { + console.warn('❌ Invalid video modification config format:', value); + return null; + } + + try { + const config: VideoModificationConfig = JSON.parse(value); + console.log('✅ 视频修改配置解析成功:', config); + return config; + } catch (parseError) { + console.error('❌ Failed to parse video modification config:', parseError); + console.error('❌ 原始value:', JSON.stringify(value)); + return null; + } + } catch (error) { + console.error('❌ Error fetching video modification config:', error); + return null; + } +}; + +/** + * 检查是否启用视频修改功能 + * @returns Promise + */ +export const isVideoModificationEnabled = async (): Promise => { + try { + console.log('🔍 检查视频修改功能是否启用...'); + const config = await getVideoModificationConfig(); + console.log('📋 获得的视频修改配置:', config); + + if (!config) { + console.log('❌ 没有获得视频修改配置,返回false'); + return false; + } + + const isEnabled = config?.show === true; + + console.log('🔍 视频修改配置检查:', { + show: config?.show, + isEnabled, + finalResult: isEnabled + }); + + // 简化逻辑:只检查show字段 + return isEnabled; + } catch (error) { + console.error('❌ Error checking video modification status:', error); + return false; // 出错时默认不显示 + } +};