This commit is contained in:
root 2025-07-13 00:04:50 +08:00
parent 8908f0c9c1
commit 455d929ba5
2 changed files with 73 additions and 31 deletions

View File

@ -1,6 +1,7 @@
import React from 'react'
import { SegmentContextMenu } from './SegmentContextMenu'
import { SegmentTooltip } from './SegmentTooltip'
import { ResourceCategoryServiceV2, ResourceCategoryV2 } from '../../services/resourceCategoryServiceV2'
export interface TrackSegment {
id: string
@ -40,7 +41,6 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
onSegmentNameChange
}) => {
const [editingSegmentId, setEditingSegmentId] = React.useState<string | null>(null)
const [editingName, setEditingName] = React.useState('')
const [contextMenu, setContextMenu] = React.useState<{
isOpen: boolean
position: { x: number; y: number }
@ -48,6 +48,31 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
}>({ isOpen: false, position: { x: 0, y: 0 }, segment: null })
const [hoveredSegment, setHoveredSegment] = React.useState<TrackSegment | null>(null)
const [mousePosition, setMousePosition] = React.useState({ x: 0, y: 0 })
const [categories, setCategories] = React.useState<ResourceCategoryV2[]>([])
const [loadingCategories, setLoadingCategories] = React.useState(false)
// 加载分类数据
const loadCategories = React.useCallback(async () => {
if (loadingCategories) return
try {
setLoadingCategories(true)
const result = await ResourceCategoryServiceV2.getAllCategories({
include_cloud: true
})
setCategories(result)
} catch (error) {
console.error('Failed to load categories:', error)
} finally {
setLoadingCategories(false)
}
}, [loadingCategories])
// 组件挂载时加载分类
React.useEffect(() => {
loadCategories()
}, [loadCategories])
const getSegmentColor = (type: string) => {
switch (type) {
case 'video': return 'bg-blue-500 hover:bg-blue-600'
@ -85,9 +110,13 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
return `${minutes}:${secs.padStart(5, '0')}`
}
const handleSegmentDoubleClick = (segment: TrackSegment) => {
const handleSegmentDoubleClick = async (segment: TrackSegment) => {
setEditingSegmentId(segment.id)
setEditingName(segment.name)
// 确保分类数据已加载
if (categories.length === 0 && !loadingCategories) {
await loadCategories()
}
}
const handleSegmentRightClick = (e: React.MouseEvent, segment: TrackSegment) => {
@ -101,26 +130,14 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
})
}
const handleNameSubmit = (segmentId: string) => {
if (onSegmentNameChange && editingName.trim()) {
onSegmentNameChange(segmentId, editingName.trim())
const handleCategorySelect = (segmentId: string, categoryTitle: string) => {
if (onSegmentNameChange && categoryTitle) {
onSegmentNameChange(segmentId, categoryTitle)
}
setEditingSegmentId(null)
setEditingName('')
}
const handleNameCancel = () => {
setEditingSegmentId(null)
setEditingName('')
}
const handleKeyDown = (e: React.KeyboardEvent, segmentId: string) => {
if (e.key === 'Enter') {
handleNameSubmit(segmentId)
} else if (e.key === 'Escape') {
handleNameCancel()
}
}
const handleSegmentMouseEnter = (e: React.MouseEvent, segment: TrackSegment) => {
setHoveredSegment(segment)
@ -141,10 +158,14 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
setContextMenu({ isOpen: false, position: { x: 0, y: 0 }, segment: null })
}
const handleContextMenuEdit = () => {
const handleContextMenuEdit = async () => {
if (contextMenu.segment) {
setEditingSegmentId(contextMenu.segment.id)
setEditingName(contextMenu.segment.name)
// 确保分类数据已加载
if (categories.length === 0 && !loadingCategories) {
await loadCategories()
}
}
}
@ -205,16 +226,38 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
title={`${segment.name}\n类型: ${segment.type}\n开始: ${formatTime(segment.start_time)}\n结束: ${formatTime(segment.end_time)}\n时长: ${formatTime(segment.duration)}${segment.resource_path ? `\n资源: ${segment.resource_path}` : ''}`}
>
{editingSegmentId === segment.id ? (
<input
type="text"
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
onBlur={() => handleNameSubmit(segment.id)}
onKeyDown={(e) => handleKeyDown(e, segment.id)}
className="w-full bg-transparent text-inherit border-none outline-none"
autoFocus
onClick={(e) => e.stopPropagation()}
/>
<div className="w-full relative" onClick={(e) => e.stopPropagation()}>
{loadingCategories ? (
<div className="w-full bg-white text-gray-900 border border-gray-300 rounded px-2 py-1 text-sm">
...
</div>
) : (
<select
value=""
onChange={(e) => {
if (e.target.value) {
handleCategorySelect(segment.id, e.target.value)
}
}}
onBlur={() => setEditingSegmentId(null)}
className="w-full bg-white text-gray-900 border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer"
autoFocus
size={Math.min(categories.length + 2, 8)} // 限制下拉框高度
>
<option value="">...</option>
<option value={segment.name} className="text-gray-600">
: {segment.name}
</option>
{categories
.filter(category => category.is_active)
.map((category) => (
<option key={category.id} value={category.title} className="flex items-center">
{category.title}
</option>
))}
</select>
)}
</div>
) : (
<div className="truncate flex-1">
{segment.name}

View File

@ -400,7 +400,6 @@ const TemplateDetailPageV2: React.FC = () => {
<h2 className="text-lg font-semibold text-gray-900 mb-4"></h2>
<div className="space-y-4">
{templateDetail.tracks.map((track) => {
console.log(track)
return (
<TrackTimeline
key={track.id}