fix: 修复模板导入的bug
This commit is contained in:
@@ -134,7 +134,7 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
|
||||
</label>
|
||||
|
||||
<div
|
||||
className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
|
||||
className={`cursour-pointer border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
|
||||
dragOver
|
||||
? 'border-blue-400 bg-blue-50'
|
||||
: errors.file_path
|
||||
@@ -161,11 +161,8 @@ export const ImportTemplateModal: React.FC<ImportTemplateModalProps> = ({
|
||||
) : (
|
||||
<>
|
||||
<Upload className="w-8 h-8 text-gray-400 mx-auto mb-2" />
|
||||
<div className="text-sm text-gray-600 mb-2">
|
||||
拖拽 draft_content.json 文件到此处
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mb-3">
|
||||
或者点击下方按钮选择文件
|
||||
<div className="text-sm text-gray-600 mb-2 cursour-pointer ">
|
||||
选择 draft_content.json 文件上传
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X, Calendar, Clock, Monitor, Layers, FileText, Image, Video, Music, Type, Sparkles } from 'lucide-react';
|
||||
import { Template, TemplateMaterialType, TrackType } from '../../types/template';
|
||||
import { X, Calendar, Clock, Monitor, Layers, FileText, Image, Video, Music, Type, Sparkles, CheckCircle, XCircle, AlertCircle, Upload, Cloud } from 'lucide-react';
|
||||
import { Template, TemplateMaterial, TemplateMaterialType, TrackType } from '../../types/template';
|
||||
|
||||
interface TemplateDetailModalProps {
|
||||
template: Template;
|
||||
@@ -66,12 +66,116 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 检查文件是否存在(基于数据库字段和路径)
|
||||
const getFileExistenceInfo = (material: any) => {
|
||||
const hasRemoteFile = material.remote_url && material.remote_url.trim() !== '';
|
||||
|
||||
if (hasRemoteFile) {
|
||||
return {
|
||||
icon: <Cloud className="w-4 h-4 text-blue-600" />,
|
||||
text: '云端文件',
|
||||
color: 'text-blue-600'
|
||||
};
|
||||
} else if (material.file_exists) {
|
||||
return {
|
||||
icon: <CheckCircle className="w-4 h-4 text-green-600" />,
|
||||
text: '本地文件存在',
|
||||
color: 'text-green-600'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
icon: <XCircle className="w-4 h-4 text-red-600" />,
|
||||
text: '文件不存在',
|
||||
color: 'text-red-600'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 获取上传状态(基于数据库字段)
|
||||
const getUploadStatusInfo = (material: any) => {
|
||||
if (material.upload_success) {
|
||||
return {
|
||||
icon: <CheckCircle className="w-4 h-4 text-green-600" />,
|
||||
text: '上传成功',
|
||||
color: 'text-green-600'
|
||||
};
|
||||
} else {
|
||||
// 根据 upload_status 显示具体状态
|
||||
switch (material.upload_status) {
|
||||
case 'Uploading':
|
||||
return {
|
||||
icon: <Upload className="w-4 h-4 text-blue-600" />,
|
||||
text: '上传中',
|
||||
color: 'text-blue-600'
|
||||
};
|
||||
case 'Failed':
|
||||
|
||||
case 'Pending':
|
||||
return {
|
||||
icon: <AlertCircle className="w-4 h-4 text-yellow-600" />,
|
||||
text: '待上传',
|
||||
color: 'text-yellow-600'
|
||||
};
|
||||
case 'Skipped':
|
||||
return {
|
||||
icon: <AlertCircle className="w-4 h-4 text-gray-600" />,
|
||||
text: '已跳过',
|
||||
color: 'text-gray-600'
|
||||
};
|
||||
default:
|
||||
return {
|
||||
icon: <AlertCircle className="w-4 h-4 text-gray-600" />,
|
||||
text: '未上传',
|
||||
color: 'text-gray-600'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 解析文字素材的文本内容和样式
|
||||
const parseTextContent = (metadata: string | null) => {
|
||||
if (!metadata) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(metadata);
|
||||
const obj = {
|
||||
content: tryParse(parsed.content || null),
|
||||
font_family: parsed.font_family || null,
|
||||
font_size: parsed.font_size || null,
|
||||
color: parsed.color || null
|
||||
};
|
||||
console.log({ obj })
|
||||
return obj;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const tryParse = (str: any) => {
|
||||
try {
|
||||
if (typeof str === 'string') {
|
||||
return JSON.parse(str)
|
||||
}
|
||||
return str;
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', label: '概览', icon: Monitor },
|
||||
{ id: 'materials', label: '素材', icon: FileText },
|
||||
{ id: 'tracks', label: '轨道', icon: Layers },
|
||||
];
|
||||
|
||||
const getMaterialName = (material: TemplateMaterial) => {
|
||||
if (material.material_type === TemplateMaterialType.Text) {
|
||||
return parseTextContent(material.metadata!)?.content?.text
|
||||
}
|
||||
return material.name
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl w-full max-w-4xl mx-4 max-h-[90vh] overflow-hidden">
|
||||
@@ -100,11 +204,10 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id as any)}
|
||||
className={`flex items-center py-4 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
activeTab === tab.id
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
className={`flex items-center py-4 px-1 border-b-2 font-medium text-sm transition-colors ${activeTab === tab.id
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4 mr-2" />
|
||||
{tab.label}
|
||||
@@ -165,6 +268,25 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 模板信息 */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-3">模板信息</h3>
|
||||
<div className="grid grid-cols-1 gap-3 text-sm">
|
||||
<div className="flex items-center">
|
||||
<FileText className="w-4 h-4 text-gray-500 mr-2" />
|
||||
<span className="text-gray-600">模板ID:</span>
|
||||
<span className="ml-1 font-mono text-blue-600 text-xs">{template.id}</span>
|
||||
</div>
|
||||
{template.source_file_path && (
|
||||
<div className="flex items-center">
|
||||
<FileText className="w-4 h-4 text-gray-500 mr-2" />
|
||||
<span className="text-gray-600">源文件:</span>
|
||||
<span className="ml-1 text-gray-900 break-all">{template.source_file_path}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 创建信息 */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-3">创建信息</h3>
|
||||
@@ -179,13 +301,6 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||
<span className="text-gray-600">更新时间:</span>
|
||||
<span className="ml-1 text-gray-900">{formatDate(template.updated_at)}</span>
|
||||
</div>
|
||||
{template.source_file_path && (
|
||||
<div className="flex items-center md:col-span-2">
|
||||
<FileText className="w-4 h-4 text-gray-500 mr-2" />
|
||||
<span className="text-gray-600">源文件:</span>
|
||||
<span className="ml-1 text-gray-900 break-all">{template.source_file_path}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -198,42 +313,97 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||
素材列表 ({template.materials.length})
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-3">
|
||||
{template.materials.map((material) => (
|
||||
<div key={material.id} className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start space-x-3">
|
||||
{getMaterialIcon(material.material_type)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 truncate">
|
||||
{material.name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
类型: {material.material_type}
|
||||
</div>
|
||||
{material.original_path && (
|
||||
<div className="text-xs text-gray-500 break-all">
|
||||
路径: {material.original_path}
|
||||
{template.materials.map((material) => {
|
||||
const uploadStatus = getUploadStatusInfo(material);
|
||||
const fileExistence = getFileExistenceInfo(material);
|
||||
|
||||
return (
|
||||
<div key={material.id} className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start space-x-3">
|
||||
{getMaterialIcon(material.material_type)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 truncate">
|
||||
{getMaterialName(material)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1 space-y-1">
|
||||
<div>ID: <span className="font-mono text-blue-600">{material.id}</span></div>
|
||||
<div>原始ID: <span className="font-mono text-purple-600">{material.original_id}</span></div>
|
||||
<div>类型: {material.material_type}</div>
|
||||
|
||||
{/* 文件存在状态 */}
|
||||
<div className="flex items-center space-x-1">
|
||||
{fileExistence.icon}
|
||||
<span className={fileExistence.color}>{fileExistence.text}</span>
|
||||
</div>
|
||||
|
||||
{/* 上传状态 */}
|
||||
<div className="flex items-center space-x-1">
|
||||
{uploadStatus.icon}
|
||||
<span className={uploadStatus.color}>{uploadStatus.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* 文字素材显示文本内容 */}
|
||||
{material.material_type === 'Text' && material.metadata && (() => {
|
||||
const textData = parseTextContent(material.metadata);
|
||||
return textData ? (
|
||||
<div className="text-xs text-gray-700 bg-gray-100 p-2 rounded mt-1">
|
||||
<div className="font-medium text-gray-600 mb-1">文本内容:</div>
|
||||
<div className="break-words mb-2">
|
||||
{textData.content?.text || '无文本内容'}
|
||||
</div>
|
||||
{(textData.font_family || textData.font_size || textData.color) && (
|
||||
<div className="text-xs text-gray-500 space-y-1">
|
||||
{textData.font_family && (
|
||||
<div>字体: {textData.font_family}</div>
|
||||
)}
|
||||
{textData.font_size && (
|
||||
<div>大小: {textData.font_size}px</div>
|
||||
)}
|
||||
{textData.color && (
|
||||
<div>颜色: {textData.color}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-gray-500 bg-gray-100 p-2 rounded mt-1">
|
||||
无法解析文本内容
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* 非文字素材显示文件路径 */}
|
||||
{material.material_type !== 'Text' && material.original_path && (
|
||||
<div className="text-xs text-gray-500 break-all mt-1">
|
||||
路径: {material.original_path}
|
||||
</div>
|
||||
)}
|
||||
{material.remote_url && (
|
||||
<div className="text-xs text-gray-500 break-all mt-1">
|
||||
云端URL: {material.remote_url}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right text-xs text-gray-500">
|
||||
{material.duration && (
|
||||
<div>时长: {formatDuration(material.duration)}</div>
|
||||
)}
|
||||
{material.file_size && (
|
||||
<div>大小: {formatFileSize(material.file_size)}</div>
|
||||
)}
|
||||
{material.width && material.height && (
|
||||
<div>尺寸: {material.width}×{material.height}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right text-xs text-gray-500">
|
||||
{material.duration && (
|
||||
<div>时长: {formatDuration(material.duration)}</div>
|
||||
)}
|
||||
{material.file_size && (
|
||||
<div>大小: {formatFileSize(material.file_size)}</div>
|
||||
)}
|
||||
{material.width && material.height && (
|
||||
<div>尺寸: {material.width}×{material.height}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -245,37 +415,53 @@ export const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
|
||||
轨道列表 ({template.tracks.length})
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-4">
|
||||
{template.tracks.map((track) => (
|
||||
<div key={track.id} className="bg-gray-50 p-4 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
{getTrackIcon(track.track_type)}
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
{track.name}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
({track.track_type})
|
||||
</span>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-medium text-gray-900">
|
||||
{track.name}
|
||||
</span>
|
||||
<div className="text-xs text-gray-500">
|
||||
<span>ID: <span className="font-mono text-green-600">{track.id}</span></span>
|
||||
<span className="ml-3">类型: {track.track_type}</span>
|
||||
<span className="ml-3">索引: {track.track_index}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">
|
||||
{track.segments.length} 个片段
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{track.segments.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{track.segments.map((segment) => (
|
||||
<div key={segment.id} className="bg-white p-3 rounded border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm text-gray-900">{segment.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{formatDuration(segment.start_time)} - {formatDuration(segment.end_time)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
时长: {formatDuration(segment.duration)}
|
||||
<div className="text-xs text-gray-500 space-y-1">
|
||||
<div>
|
||||
<span>片段ID: <span className="font-mono text-orange-600">{segment.id}</span></span>
|
||||
<span className="ml-3">索引: {segment.segment_index}</span>
|
||||
</div>
|
||||
<div>时长: {formatDuration(segment.duration)}</div>
|
||||
{segment.template_material_id && (
|
||||
<div>
|
||||
使用素材ID: <span className="font-mono text-blue-600">{segment.template_material_id}</span>
|
||||
</div>
|
||||
)}
|
||||
{!segment.template_material_id && (
|
||||
<div className="text-gray-400">未关联素材</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user