forked from 77media/video-flow
work-flow视频修改按钮通过video_modification来显示
This commit is contained in:
parent
7e49b48d0f
commit
3e176bc265
@ -1,5 +1,5 @@
|
|||||||
|
NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
|
||||||
NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com
|
# NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com
|
||||||
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
||||||
NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com
|
NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,23 @@ export async function POST(request: NextRequest) {
|
|||||||
updated_at: new Date().toISOString().slice(0, 19)
|
updated_at: new Date().toISOString().slice(0, 19)
|
||||||
};
|
};
|
||||||
break;
|
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:
|
default:
|
||||||
// 默认返回空配置
|
// 默认返回空配置
|
||||||
|
|||||||
112
app/test-server-config/page.tsx
Normal file
112
app/test-server-config/page.tsx
Normal file
@ -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<boolean | null>(null);
|
||||||
|
const [videoModStatus, setVideoModStatus] = useState<boolean | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(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 (
|
||||||
|
<div className="min-h-screen bg-gray-100 p-8">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold mb-8 text-center">服务器配置测试页面</h1>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">配置状态</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between p-4 bg-gray-50 rounded">
|
||||||
|
<span className="font-medium">Google登录 (sso_config):</span>
|
||||||
|
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
||||||
|
ssoStatus === null ? 'bg-gray-200 text-gray-600' :
|
||||||
|
ssoStatus ? 'bg-green-200 text-green-800' : 'bg-red-200 text-red-800'
|
||||||
|
}`}>
|
||||||
|
{ssoStatus === null ? '检测中...' : ssoStatus ? '启用' : '禁用'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-4 bg-gray-50 rounded">
|
||||||
|
<span className="font-medium">视频修改 (video_modification):</span>
|
||||||
|
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
||||||
|
videoModStatus === null ? 'bg-gray-200 text-gray-600' :
|
||||||
|
videoModStatus ? 'bg-green-200 text-green-800' : 'bg-red-200 text-red-800'
|
||||||
|
}`}>
|
||||||
|
{videoModStatus === null ? '检测中...' : videoModStatus ? '启用' : '禁用'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded">
|
||||||
|
<p className="text-red-800 font-medium">错误:</p>
|
||||||
|
<p className="text-red-600">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-6 flex gap-4">
|
||||||
|
<button
|
||||||
|
onClick={testConfigs}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? '测试中...' : '重新测试'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.href = '/movies/work-flow'}
|
||||||
|
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
||||||
|
>
|
||||||
|
前往Work-Flow页面
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow-md p-6">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">API测试信息</h2>
|
||||||
|
<div className="space-y-2 text-sm text-gray-600">
|
||||||
|
<p><strong>SSO API:</strong> POST /api/server-setting/find_by_code {"{ code: 'sso_config' }"}</p>
|
||||||
|
<p><strong>视频修改API:</strong> POST /api/server-setting/find_by_code {"{ code: 'video_modification' }"}</p>
|
||||||
|
<p><strong>预期响应格式:</strong> {"{ code: 0, successful: true, data: { value: '{\"show\": true}' } }"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 text-center text-sm text-gray-500">
|
||||||
|
<p>请打开浏览器开发者工具查看详细的API调用日志</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ import { Button, Tooltip } from 'antd';
|
|||||||
import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools';
|
import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools';
|
||||||
import { VideoEditOverlay } from './video-edit/VideoEditOverlay';
|
import { VideoEditOverlay } from './video-edit/VideoEditOverlay';
|
||||||
import { EditPoint as EditPointType } from './video-edit/types';
|
import { EditPoint as EditPointType } from './video-edit/types';
|
||||||
|
import { isVideoModificationEnabled } from '@/lib/server-config';
|
||||||
|
|
||||||
interface MediaViewerProps {
|
interface MediaViewerProps {
|
||||||
taskObject: TaskObject;
|
taskObject: TaskObject;
|
||||||
@ -78,6 +79,8 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false);
|
const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false);
|
||||||
const [isLoadingDownloadAllVideosBtn, setIsLoadingDownloadAllVideosBtn] = useState(false);
|
const [isLoadingDownloadAllVideosBtn, setIsLoadingDownloadAllVideosBtn] = useState(false);
|
||||||
const [isVideoEditMode, setIsVideoEditMode] = useState(false);
|
const [isVideoEditMode, setIsVideoEditMode] = useState(false);
|
||||||
|
// 控制钢笔图标显示的状态 - 参考谷歌登录按钮的实现
|
||||||
|
const [showVideoModification, setShowVideoModification] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSmartChatBoxOpen) {
|
if (isSmartChatBoxOpen) {
|
||||||
@ -89,6 +92,33 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
}
|
}
|
||||||
}, [isSmartChatBoxOpen])
|
}, [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 = () => {
|
const toggleMute = () => {
|
||||||
setUserHasInteracted(true);
|
setUserHasInteracted(true);
|
||||||
@ -526,17 +556,20 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
<div className="absolute top-4 right-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
<div className="absolute top-4 right-4 z-[21] flex items-center gap-2 transition-right duration-100" style={{
|
||||||
right: toosBtnRight
|
right: toosBtnRight
|
||||||
}}>
|
}}>
|
||||||
{/* 视频编辑模式切换按钮 - 临时注释 */}
|
{/* 视频编辑模式切换按钮 - 通过服务器配置控制显示 */}
|
||||||
{/* {enableVideoEdit && (
|
{enableVideoEdit && showVideoModification && (
|
||||||
<Tooltip placement="top" title={isVideoEditMode ? "Exit edit mode" : "Enter edit mode"}>
|
<Tooltip placement="top" title={isVideoEditMode ? "Exit edit mode" : "Enter edit mode"}>
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={PenTool}
|
icon={PenTool}
|
||||||
size='sm'
|
size='sm'
|
||||||
onClick={() => setIsVideoEditMode(!isVideoEditMode)}
|
onClick={() => {
|
||||||
|
console.log('🖊️ 钢笔图标被点击,切换编辑模式:', !isVideoEditMode);
|
||||||
|
setIsVideoEditMode(!isVideoEditMode);
|
||||||
|
}}
|
||||||
className={isVideoEditMode ? 'bg-blue-500/20 border-blue-500/50' : ''}
|
className={isVideoEditMode ? 'bg-blue-500/20 border-blue-500/50' : ''}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)} */}
|
)}
|
||||||
{/* 添加到chat去编辑 按钮 */}
|
{/* 添加到chat去编辑 按钮 */}
|
||||||
<Tooltip placement="top" title="Edit video with chat">
|
<Tooltip placement="top" title="Edit video with chat">
|
||||||
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {
|
<GlassIconButton icon={MessageCircleMore} size='sm' onClick={() => {
|
||||||
|
|||||||
@ -6,6 +6,13 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ConnectionPathParams, InputBoxPosition } from './types';
|
import { ConnectionPathParams, InputBoxPosition } from './types';
|
||||||
|
import {
|
||||||
|
CONNECTION_STYLE,
|
||||||
|
ARROW_GEOMETRY,
|
||||||
|
calculateArrowGeometry,
|
||||||
|
calculateCurvePath as calculateUnifiedCurvePath,
|
||||||
|
getConnectionAnimationConfig
|
||||||
|
} from './connection-config';
|
||||||
|
|
||||||
interface EditConnectionProps {
|
interface EditConnectionProps {
|
||||||
/** 起始点坐标(编辑点位置) */
|
/** 起始点坐标(编辑点位置) */
|
||||||
@ -95,7 +102,8 @@ export function calculateInputPosition(
|
|||||||
direction = 'right';
|
direction = 'right';
|
||||||
inputX = pointX + connectionLength;
|
inputX = pointX + connectionLength;
|
||||||
inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2));
|
inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2));
|
||||||
connectionEndX = inputX;
|
// 箭头指向输入框左边缘的中心
|
||||||
|
connectionEndX = inputX - 8; // 向内偏移8px,指向输入框内部
|
||||||
connectionEndY = inputY + inputHeight / 2;
|
connectionEndY = inputY + inputHeight / 2;
|
||||||
}
|
}
|
||||||
// 其次选择左侧
|
// 其次选择左侧
|
||||||
@ -103,7 +111,8 @@ export function calculateInputPosition(
|
|||||||
direction = 'left';
|
direction = 'left';
|
||||||
inputX = pointX - connectionLength - inputWidth;
|
inputX = pointX - connectionLength - inputWidth;
|
||||||
inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2));
|
inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2));
|
||||||
connectionEndX = inputX + inputWidth;
|
// 箭头指向输入框右边缘的中心
|
||||||
|
connectionEndX = inputX + inputWidth + 8; // 向内偏移8px,指向输入框内部
|
||||||
connectionEndY = inputY + inputHeight / 2;
|
connectionEndY = inputY + inputHeight / 2;
|
||||||
}
|
}
|
||||||
// 然后选择下方
|
// 然后选择下方
|
||||||
@ -111,23 +120,26 @@ export function calculateInputPosition(
|
|||||||
direction = 'bottom';
|
direction = 'bottom';
|
||||||
inputX = Math.max(margin, Math.min(containerWidth - inputWidth - margin, pointX - inputWidth / 2));
|
inputX = Math.max(margin, Math.min(containerWidth - inputWidth - margin, pointX - inputWidth / 2));
|
||||||
inputY = pointY + connectionLength;
|
inputY = pointY + connectionLength;
|
||||||
|
// 箭头指向输入框上边缘的中心
|
||||||
connectionEndX = inputX + inputWidth / 2;
|
connectionEndX = inputX + inputWidth / 2;
|
||||||
connectionEndY = inputY;
|
connectionEndY = inputY - 8; // 向内偏移8px,指向输入框内部
|
||||||
}
|
}
|
||||||
// 最后选择上方
|
// 最后选择上方
|
||||||
else if (spaceTop >= inputHeight + connectionLength + margin) {
|
else if (spaceTop >= inputHeight + connectionLength + margin) {
|
||||||
direction = 'top';
|
direction = 'top';
|
||||||
inputX = Math.max(margin, Math.min(containerWidth - inputWidth - margin, pointX - inputWidth / 2));
|
inputX = Math.max(margin, Math.min(containerWidth - inputWidth - margin, pointX - inputWidth / 2));
|
||||||
inputY = pointY - connectionLength - inputHeight;
|
inputY = pointY - connectionLength - inputHeight;
|
||||||
|
// 箭头指向输入框下边缘的中心
|
||||||
connectionEndX = inputX + inputWidth / 2;
|
connectionEndX = inputX + inputWidth / 2;
|
||||||
connectionEndY = inputY + inputHeight;
|
connectionEndY = inputY + inputHeight + 8; // 向内偏移8px,指向输入框内部
|
||||||
}
|
}
|
||||||
// 如果空间不足,强制放在右侧并调整位置
|
// 如果空间不足,强制放在右侧并调整位置
|
||||||
else {
|
else {
|
||||||
direction = 'right';
|
direction = 'right';
|
||||||
inputX = Math.min(containerWidth - inputWidth - margin, pointX + 40);
|
inputX = Math.min(containerWidth - inputWidth - margin, pointX + 40);
|
||||||
inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2));
|
inputY = Math.max(margin, Math.min(containerHeight - inputHeight - margin, pointY - inputHeight / 2));
|
||||||
connectionEndX = inputX;
|
// 箭头指向输入框左边缘的中心
|
||||||
|
connectionEndX = inputX - 8; // 向内偏移8px,指向输入框内部
|
||||||
connectionEndY = inputY + inputHeight / 2;
|
connectionEndY = inputY + inputHeight / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,80 +162,30 @@ export const EditConnection: React.FC<EditConnectionProps> = ({
|
|||||||
curvature = 0.3,
|
curvature = 0.3,
|
||||||
animated = true
|
animated = true
|
||||||
}) => {
|
}) => {
|
||||||
|
// 使用统一的样式配置
|
||||||
const {
|
const {
|
||||||
color = 'rgba(255, 255, 255, 0.9)', // White color to match the reference image
|
color = CONNECTION_STYLE.color,
|
||||||
strokeWidth = 2,
|
strokeWidth = CONNECTION_STYLE.strokeWidth,
|
||||||
dashArray = '8,4' // Dashed line to match the reference image
|
dashArray = CONNECTION_STYLE.dashArray
|
||||||
} = style;
|
} = style;
|
||||||
|
|
||||||
// 计算箭头几何参数
|
// 使用统一的箭头几何计算
|
||||||
const arrowSize = 8;
|
const arrowGeometry = useMemo(() =>
|
||||||
const arrowHalfHeight = 4;
|
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(() =>
|
const path = useMemo(() =>
|
||||||
calculateCurvePath({
|
calculateUnifiedCurvePath(startPoint, arrowGeometry.center, containerSize),
|
||||||
start: startPoint,
|
[startPoint, arrowGeometry.center, containerSize]
|
||||||
end: arrowGeometry.base, // 连接到箭头底部中心而不是尖端
|
);
|
||||||
containerSize,
|
|
||||||
curvature
|
|
||||||
}), [startPoint, arrowGeometry.base, containerSize, curvature]);
|
|
||||||
|
|
||||||
// 计算路径长度用于动画
|
// 获取统一的动画配置
|
||||||
const pathLength = useMemo(() => {
|
const animationConfig = useMemo(() =>
|
||||||
const dx = arrowGeometry.base.x - startPoint.x;
|
getConnectionAnimationConfig(animated),
|
||||||
const dy = arrowGeometry.base.y - startPoint.y;
|
[animated]
|
||||||
return Math.sqrt(dx * dx + dy * dy) * 1.2; // 弧线比直线长约20%
|
);
|
||||||
}, [startPoint, arrowGeometry.base]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@ -232,7 +194,7 @@ export const EditConnection: React.FC<EditConnectionProps> = ({
|
|||||||
height={containerSize.height}
|
height={containerSize.height}
|
||||||
style={{ zIndex: 10 }}
|
style={{ zIndex: 10 }}
|
||||||
>
|
>
|
||||||
{/* Curved dashed line - properly aligned to arrow base center */}
|
{/* 统一的虚线连接线 - 精确连接到箭头中心 */}
|
||||||
<motion.path
|
<motion.path
|
||||||
d={path}
|
d={path}
|
||||||
fill="none"
|
fill="none"
|
||||||
@ -241,44 +203,23 @@ export const EditConnection: React.FC<EditConnectionProps> = ({
|
|||||||
strokeDasharray={dashArray}
|
strokeDasharray={dashArray}
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
initial={animated ? {
|
initial={animationConfig.line.initial}
|
||||||
pathLength: 0,
|
animate={animationConfig.line.animate}
|
||||||
opacity: 0
|
transition={animationConfig.line.transition}
|
||||||
} : {}}
|
|
||||||
animate={animated ? {
|
|
||||||
pathLength: 1,
|
|
||||||
opacity: 1
|
|
||||||
} : {}}
|
|
||||||
transition={animated ? {
|
|
||||||
pathLength: { duration: 0.6, ease: "easeInOut" },
|
|
||||||
opacity: { duration: 0.3 }
|
|
||||||
} : {}}
|
|
||||||
style={{
|
style={{
|
||||||
filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))'
|
filter: CONNECTION_STYLE.dropShadow
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Properly aligned arrow head with geometric precision */}
|
{/* 几何精确的箭头 - 与连接线完美对齐 */}
|
||||||
<motion.polygon
|
<motion.polygon
|
||||||
points={arrowGeometry.points.map(p => `${p.x},${p.y}`).join(' ')}
|
points={arrowGeometry.points.map(p => `${p.x},${p.y}`).join(' ')}
|
||||||
fill={color}
|
fill={color}
|
||||||
initial={animated ? {
|
initial={animationConfig.arrow.initial}
|
||||||
scale: 0,
|
animate={animationConfig.arrow.animate}
|
||||||
opacity: 0
|
transition={animationConfig.arrow.transition}
|
||||||
} : {}}
|
|
||||||
animate={animated ? {
|
|
||||||
scale: 1,
|
|
||||||
opacity: 1
|
|
||||||
} : {}}
|
|
||||||
transition={animated ? {
|
|
||||||
delay: 0.4,
|
|
||||||
duration: 0.3,
|
|
||||||
type: "spring",
|
|
||||||
stiffness: 300,
|
|
||||||
damping: 25
|
|
||||||
} : {}}
|
|
||||||
style={{
|
style={{
|
||||||
filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))'
|
filter: CONNECTION_STYLE.dropShadow
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,12 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { EditPoint as EditPointType, EditPointStatus } from './types';
|
import { EditPoint as EditPointType, EditPointStatus } from './types';
|
||||||
|
import {
|
||||||
|
CONNECTION_STYLE,
|
||||||
|
calculateArrowGeometry,
|
||||||
|
calculateCurvePath,
|
||||||
|
getConnectionAnimationConfig
|
||||||
|
} from './connection-config';
|
||||||
|
|
||||||
interface EditDescriptionProps {
|
interface EditDescriptionProps {
|
||||||
/** 编辑点数据 */
|
/** 编辑点数据 */
|
||||||
@ -42,25 +48,31 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
|
|||||||
y: (editPoint.position.y / 100) * containerSize.height
|
y: (editPoint.position.y / 100) * containerSize.height
|
||||||
}), [editPoint.position, containerSize]);
|
}), [editPoint.position, containerSize]);
|
||||||
|
|
||||||
// 计算连接线路径
|
// 使用统一的连接线几何计算
|
||||||
const connectionPath = useMemo(() => {
|
const connectionGeometry = useMemo(() => {
|
||||||
const startX = editPointPosition.x;
|
const startPoint = { x: editPointPosition.x, y: editPointPosition.y };
|
||||||
const startY = editPointPosition.y;
|
const endPoint = { x: connectionEnd.x, y: connectionEnd.y };
|
||||||
const endX = connectionEnd.x;
|
|
||||||
const endY = connectionEnd.y;
|
|
||||||
|
|
||||||
// 计算控制点,创建优雅的弧线
|
// 使用统一的箭头几何计算
|
||||||
const deltaX = endX - startX;
|
const arrowGeometry = calculateArrowGeometry(startPoint, endPoint);
|
||||||
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;
|
|
||||||
|
|
||||||
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 = () => {
|
const getStatusColor = () => {
|
||||||
@ -101,7 +113,7 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{editPoint.description && editPoint.status !== EditPointStatus.PENDING && (
|
{editPoint.description && editPoint.status !== EditPointStatus.PENDING && (
|
||||||
<>
|
<>
|
||||||
{/* White dashed connection line to match reference image */}
|
{/* 统一的虚线连接线 - 与EditConnection完全一致 */}
|
||||||
<motion.svg
|
<motion.svg
|
||||||
className="absolute pointer-events-none"
|
className="absolute pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
@ -116,38 +128,39 @@ export const EditDescription: React.FC<EditDescriptionProps> = ({
|
|||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
|
{/* 统一的虚线连接线 - 与EditConnection完全一致 */}
|
||||||
<motion.path
|
<motion.path
|
||||||
d={connectionPath}
|
d={connectionGeometry.path}
|
||||||
stroke="rgba(255, 255, 255, 0.9)"
|
stroke={CONNECTION_STYLE.color}
|
||||||
strokeWidth={2}
|
strokeWidth={CONNECTION_STYLE.strokeWidth}
|
||||||
fill="none"
|
fill="none"
|
||||||
strokeDasharray="8,4"
|
strokeDasharray={CONNECTION_STYLE.dashArray}
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
initial={{ pathLength: 0, opacity: 0 }}
|
strokeLinejoin="round"
|
||||||
animate={{
|
initial={animationConfig.line?.initial}
|
||||||
pathLength: 1,
|
animate={animationConfig.line?.animate}
|
||||||
opacity: 1
|
exit={animationConfig.line?.initial}
|
||||||
}}
|
|
||||||
exit={{ pathLength: 0, opacity: 0 }}
|
|
||||||
transition={{
|
transition={{
|
||||||
|
...animationConfig.line?.transition,
|
||||||
|
// 稍微延长显示状态的动画时间
|
||||||
pathLength: { duration: 0.8, ease: "easeOut" },
|
pathLength: { duration: 0.8, ease: "easeOut" },
|
||||||
opacity: { duration: 0.5 }
|
opacity: { duration: 0.5 }
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))'
|
filter: CONNECTION_STYLE.dropShadow
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Arrow head */}
|
{/* 几何精确的箭头 - 与连接线完美对齐 */}
|
||||||
<motion.polygon
|
<motion.polygon
|
||||||
points={`${connectionEnd.x},${connectionEnd.y} ${connectionEnd.x-8},${connectionEnd.y-4} ${connectionEnd.x-8},${connectionEnd.y+4}`}
|
points={connectionGeometry.arrowPoints.map(p => `${p.x},${p.y}`).join(' ')}
|
||||||
fill="rgba(255, 255, 255, 0.9)"
|
fill={CONNECTION_STYLE.color}
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
initial={animationConfig.arrow?.initial}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={animationConfig.arrow?.animate}
|
||||||
exit={{ scale: 0, opacity: 0 }}
|
exit={animationConfig.arrow?.initial}
|
||||||
transition={{ delay: 0.4, duration: 0.3 }}
|
transition={animationConfig.arrow?.transition}
|
||||||
style={{
|
style={{
|
||||||
filter: 'drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8))'
|
filter: CONNECTION_STYLE.dropShadow
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</motion.svg>
|
</motion.svg>
|
||||||
|
|||||||
196
components/pages/work-flow/video-edit/connection-config.ts
Normal file
196
components/pages/work-flow/video-edit/connection-config.ts
Normal file
@ -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<string, any>;
|
||||||
|
animate: Record<string, any>;
|
||||||
|
transition: Record<string, any>;
|
||||||
|
};
|
||||||
|
arrow: {
|
||||||
|
initial: Record<string, any>;
|
||||||
|
animate: Record<string, any>;
|
||||||
|
transition: Record<string, any>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统一的动画配置
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -2,7 +2,32 @@
|
|||||||
* 服务端配置工具函数
|
* 服务端配置工具函数
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { post } from '@/api/request';
|
// 注意:这里不使用 @/api/request 中的 post 函数,因为它会将请求发送到远程服务器
|
||||||
|
// 我们需要直接调用本地的 Next.js API 路由
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地API请求函数 - 直接调用Next.js API路由
|
||||||
|
*/
|
||||||
|
const localPost = async <T>(url: string, data: any): Promise<T> => {
|
||||||
|
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配置接口
|
* SSO配置接口
|
||||||
@ -14,6 +39,13 @@ export interface SSOConfig {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频修改配置接口
|
||||||
|
*/
|
||||||
|
export interface VideoModificationConfig {
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取SSO配置
|
* 获取SSO配置
|
||||||
* @returns Promise<SSOConfig | null>
|
* @returns Promise<SSOConfig | null>
|
||||||
@ -21,7 +53,7 @@ export interface SSOConfig {
|
|||||||
export const getSSOConfig = async (): Promise<SSOConfig | null> => {
|
export const getSSOConfig = async (): Promise<SSOConfig | null> => {
|
||||||
try {
|
try {
|
||||||
console.log('🔍 开始获取SSO配置...');
|
console.log('🔍 开始获取SSO配置...');
|
||||||
const res = await post<any>(`/api/server-setting/find_by_code`, { code: 'sso_config' });
|
const res = await localPost<any>(`/api/server-setting/find_by_code`, { code: 'sso_config' });
|
||||||
|
|
||||||
console.log('📋 SSO API响应:', res);
|
console.log('📋 SSO API响应:', res);
|
||||||
|
|
||||||
@ -64,21 +96,21 @@ export const isGoogleLoginEnabled = async (): Promise<boolean> => {
|
|||||||
console.log('🔍 检查Google登录是否启用...');
|
console.log('🔍 检查Google登录是否启用...');
|
||||||
const config = await getSSOConfig();
|
const config = await getSSOConfig();
|
||||||
console.log('📋 获得的配置:', config);
|
console.log('📋 获得的配置:', config);
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
console.log('❌ 没有获得配置,返回false');
|
console.log('❌ 没有获得配置,返回false');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEnabled = config?.show === true;
|
const isEnabled = config?.show === true;
|
||||||
|
|
||||||
console.log('🔍 配置检查:', {
|
console.log('🔍 配置检查:', {
|
||||||
show: config?.show,
|
show: config?.show,
|
||||||
provider: config?.provider,
|
provider: config?.provider,
|
||||||
isEnabled,
|
isEnabled,
|
||||||
finalResult: isEnabled
|
finalResult: isEnabled
|
||||||
});
|
});
|
||||||
|
|
||||||
// 简化逻辑:只检查show字段,因为sso_config专门用于Google登录
|
// 简化逻辑:只检查show字段,因为sso_config专门用于Google登录
|
||||||
return isEnabled;
|
return isEnabled;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -86,3 +118,75 @@ export const isGoogleLoginEnabled = async (): Promise<boolean> => {
|
|||||||
return false; // 出错时默认不显示
|
return false; // 出错时默认不显示
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频修改配置
|
||||||
|
* @returns Promise<VideoModificationConfig | null>
|
||||||
|
*/
|
||||||
|
export const getVideoModificationConfig = async (): Promise<VideoModificationConfig | null> => {
|
||||||
|
try {
|
||||||
|
console.log('🔍 开始获取视频修改配置...');
|
||||||
|
const res = await localPost<any>(`/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<boolean>
|
||||||
|
*/
|
||||||
|
export const isVideoModificationEnabled = async (): Promise<boolean> => {
|
||||||
|
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; // 出错时默认不显示
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user