fix: 修复模板导入的bug

This commit is contained in:
imeepos
2025-07-14 23:14:45 +08:00
parent 49c5b1a033
commit 595d2f75fd
7 changed files with 401 additions and 72 deletions

View File

@@ -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>
</>
)}

View File

@@ -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>
))}