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 React from 'react'
import { SegmentContextMenu } from './SegmentContextMenu' import { SegmentContextMenu } from './SegmentContextMenu'
import { SegmentTooltip } from './SegmentTooltip' import { SegmentTooltip } from './SegmentTooltip'
import { ResourceCategoryServiceV2, ResourceCategoryV2 } from '../../services/resourceCategoryServiceV2'
export interface TrackSegment { export interface TrackSegment {
id: string id: string
@ -40,7 +41,6 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
onSegmentNameChange onSegmentNameChange
}) => { }) => {
const [editingSegmentId, setEditingSegmentId] = React.useState<string | null>(null) const [editingSegmentId, setEditingSegmentId] = React.useState<string | null>(null)
const [editingName, setEditingName] = React.useState('')
const [contextMenu, setContextMenu] = React.useState<{ const [contextMenu, setContextMenu] = React.useState<{
isOpen: boolean isOpen: boolean
position: { x: number; y: number } position: { x: number; y: number }
@ -48,6 +48,31 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
}>({ isOpen: false, position: { x: 0, y: 0 }, segment: null }) }>({ isOpen: false, position: { x: 0, y: 0 }, segment: null })
const [hoveredSegment, setHoveredSegment] = React.useState<TrackSegment | null>(null) const [hoveredSegment, setHoveredSegment] = React.useState<TrackSegment | null>(null)
const [mousePosition, setMousePosition] = React.useState({ x: 0, y: 0 }) 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) => { const getSegmentColor = (type: string) => {
switch (type) { switch (type) {
case 'video': return 'bg-blue-500 hover:bg-blue-600' 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')}` return `${minutes}:${secs.padStart(5, '0')}`
} }
const handleSegmentDoubleClick = (segment: TrackSegment) => { const handleSegmentDoubleClick = async (segment: TrackSegment) => {
setEditingSegmentId(segment.id) setEditingSegmentId(segment.id)
setEditingName(segment.name)
// 确保分类数据已加载
if (categories.length === 0 && !loadingCategories) {
await loadCategories()
}
} }
const handleSegmentRightClick = (e: React.MouseEvent, segment: TrackSegment) => { const handleSegmentRightClick = (e: React.MouseEvent, segment: TrackSegment) => {
@ -101,26 +130,14 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
}) })
} }
const handleNameSubmit = (segmentId: string) => { const handleCategorySelect = (segmentId: string, categoryTitle: string) => {
if (onSegmentNameChange && editingName.trim()) { if (onSegmentNameChange && categoryTitle) {
onSegmentNameChange(segmentId, editingName.trim()) onSegmentNameChange(segmentId, categoryTitle)
} }
setEditingSegmentId(null) 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) => { const handleSegmentMouseEnter = (e: React.MouseEvent, segment: TrackSegment) => {
setHoveredSegment(segment) setHoveredSegment(segment)
@ -141,10 +158,14 @@ export const TrackTimeline: React.FC<TrackTimelineProps> = ({
setContextMenu({ isOpen: false, position: { x: 0, y: 0 }, segment: null }) setContextMenu({ isOpen: false, position: { x: 0, y: 0 }, segment: null })
} }
const handleContextMenuEdit = () => { const handleContextMenuEdit = async () => {
if (contextMenu.segment) { if (contextMenu.segment) {
setEditingSegmentId(contextMenu.segment.id) 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}` : ''}`} 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 ? ( {editingSegmentId === segment.id ? (
<input <div className="w-full relative" onClick={(e) => e.stopPropagation()}>
type="text" {loadingCategories ? (
value={editingName} <div className="w-full bg-white text-gray-900 border border-gray-300 rounded px-2 py-1 text-sm">
onChange={(e) => setEditingName(e.target.value)} ...
onBlur={() => handleNameSubmit(segment.id)} </div>
onKeyDown={(e) => handleKeyDown(e, segment.id)} ) : (
className="w-full bg-transparent text-inherit border-none outline-none" <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 autoFocus
onClick={(e) => e.stopPropagation()} 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"> <div className="truncate flex-1">
{segment.name} {segment.name}

View File

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