This commit is contained in:
root 2025-07-11 15:56:50 +08:00
parent 231b28d0eb
commit 0b322d06fb
2 changed files with 494 additions and 0 deletions

View File

@ -0,0 +1,200 @@
# 素材分类筛选功能测试指南
## 🧪 测试准备
### 1. 确保分类数据存在
在测试前,确保系统中已有分类数据:
- 进入"资源分类管理"页面
- 创建几个测试分类(如:商业、教育、科技、娱乐)
- 为每个分类设置不同的颜色和AI提示
### 2. 准备测试素材
上传一些测试素材,文件名和标签包含分类关键词:
**商业类素材**:
- 文件名:`商业宣传片.mp4`, `business_intro.mp4`
- 标签:["商业", "企业", "宣传"]
**教育类素材**:
- 文件名:`教育培训视频.mp4`, `education_demo.mp4`
- 标签:["教育", "培训", "学习"]
**科技类素材**:
- 文件名:`科技产品演示.mp4`, `tech_showcase.mp4`
- 标签:["科技", "技术", "创新"]
## 🔍 测试用例
### 测试用例1: 基础分类筛选
**步骤**:
1. 进入任意项目详情页面
2. 查看素材管理区域的分类筛选器
3. 验证"全部"按钮显示总素材数量
4. 点击"商业"分类按钮
5. 验证只显示商业类素材
**预期结果**:
- ✅ 分类按钮正确显示
- ✅ 数量统计准确
- ✅ 筛选结果正确
- ✅ 按钮状态正确切换
### 测试用例2: 分类数量统计
**步骤**:
1. 查看每个分类按钮上的数量显示
2. 手动计算符合条件的素材数量
3. 对比系统显示的数量
**预期结果**:
- ✅ 数量统计准确
- ✅ 总数等于各分类数量之和(考虑重复分类)
### 测试用例3: 组合筛选
**步骤**:
1. 选择"商业"分类
2. 再选择"已使用"状态
3. 验证结果同时满足两个条件
**预期结果**:
- ✅ 多重筛选正确工作
- ✅ 筛选条件叠加生效
### 测试用例4: 分类颜色显示
**步骤**:
1. 查看各分类按钮的颜色
2. 对比分类管理中设置的颜色
3. 验证选中状态的颜色变化
**预期结果**:
- ✅ 分类颜色正确显示
- ✅ 选中状态高亮显示
### 测试用例5: 空状态处理
**步骤**:
1. 选择一个没有匹配素材的分类
2. 验证空状态显示
**预期结果**:
- ✅ 显示"没有找到匹配的素材"
- ✅ 提示调整筛选条件
### 测试用例6: 分类匹配规则
**步骤**:
1. 创建包含特定标签的素材
2. 验证标签匹配是否正确
3. 测试文件名匹配
4. 测试AI提示关键词匹配
**预期结果**:
- ✅ 标签匹配正确
- ✅ 文件名匹配正确
- ✅ AI提示匹配正确
## 📊 性能测试
### 大量分类测试
1. 创建20+个分类
2. 验证界面显示是否正常
3. 测试筛选性能
### 大量素材测试
1. 上传100+个素材
2. 验证分类统计性能
3. 测试筛选响应速度
## 🐛 边界情况测试
### 特殊字符处理
- 测试包含特殊字符的分类名称
- 测试包含特殊字符的文件名和标签
### 空数据处理
- 测试没有分类数据的情况
- 测试没有素材的情况
- 测试分类加载失败的情况
### 网络异常处理
- 测试网络断开时的行为
- 测试分类服务异常时的处理
## 📝 测试记录模板
```
测试日期: ____
测试人员: ____
浏览器: ____
版本: ____
测试用例1: 基础分类筛选
- 分类按钮显示: ✅/❌
- 数量统计: ✅/❌
- 筛选结果: ✅/❌
- 状态切换: ✅/❌
- 备注: ____
测试用例2: 分类数量统计
- 数量准确性: ✅/❌
- 总数计算: ✅/❌
- 备注: ____
测试用例3: 组合筛选
- 多重筛选: ✅/❌
- 条件叠加: ✅/❌
- 备注: ____
测试用例4: 分类颜色显示
- 颜色正确: ✅/❌
- 选中高亮: ✅/❌
- 备注: ____
测试用例5: 空状态处理
- 空状态显示: ✅/❌
- 提示信息: ✅/❌
- 备注: ____
测试用例6: 分类匹配规则
- 标签匹配: ✅/❌
- 文件名匹配: ✅/❌
- AI提示匹配: ✅/❌
- 备注: ____
总体评价: ____
发现问题: ____
改进建议: ____
```
## 🔧 常见问题排查
### 问题1: 分类不显示
**排查步骤**:
1. 检查浏览器控制台错误
2. 验证分类服务API是否正常
3. 检查分类数据是否存在且is_active=true
### 问题2: 数量统计错误
**排查步骤**:
1. 检查匹配规则逻辑
2. 验证素材标签和文件名数据
3. 测试AI提示关键词设置
### 问题3: 筛选不生效
**排查步骤**:
1. 检查筛选逻辑实现
2. 验证状态管理是否正确
3. 测试组合筛选的条件叠加
### 问题4: 界面显示异常
**排查步骤**:
1. 检查CSS样式是否正确
2. 验证响应式布局
3. 测试不同浏览器兼容性
---
通过系统的测试,确保素材分类筛选功能稳定可靠,为用户提供良好的使用体验!

View File

@ -0,0 +1,294 @@
/**
*
*
*/
import { invoke } from '@tauri-apps/api/core'
import { VideoSegment } from './mediaService'
import { Model } from './modelService'
export interface ImportMaterialConfig {
// 基础配置
projectId: string
sourceDirectory: string
selectedModelIds: string[]
// 导入选项
includeSubdirectories: boolean
fileTypes: string[] // 支持的文件类型
// 自动分镜配置
enableAutoSegmentation: boolean
segmentationConfig: {
method: 'scene' | 'time' | 'smart' // 分镜方法
sceneThreshold: number // 场景变化阈值 (0-1)
timeInterval: number // 时间间隔分镜(秒)
minSegmentDuration: number // 最小片段时长(秒)
maxSegmentDuration: number // 最大片段时长(秒)
}
// 处理选项
generateThumbnails: boolean
extractMetadata: boolean
autoTagging: boolean
}
export interface ImportProgress {
stage: 'scanning' | 'importing' | 'segmenting' | 'processing' | 'completed' | 'error'
progress: number // 0-100
message: string
currentFile?: string
processedFiles: number
totalFiles: number
segmentedVideos: number
generatedSegments: number
}
export interface ImportResult {
success: boolean
message: string
importedMaterials: VideoSegment[]
generatedSegments: VideoSegment[]
skippedFiles: string[]
errors: string[]
statistics: {
totalFiles: number
importedFiles: number
segmentedVideos: number
totalSegments: number
processingTime: number
}
}
export interface FileInfo {
path: string
name: string
size: number
type: string
duration?: number
resolution?: string
isSupported: boolean
}
export class MaterialImportService {
/**
*
*/
static getDefaultConfig(projectId: string): ImportMaterialConfig {
return {
projectId,
sourceDirectory: '',
selectedModelIds: [],
includeSubdirectories: true,
fileTypes: ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'm4v'],
enableAutoSegmentation: true,
segmentationConfig: {
method: 'smart',
sceneThreshold: 0.3,
timeInterval: 30,
minSegmentDuration: 3,
maxSegmentDuration: 60
},
generateThumbnails: true,
extractMetadata: true,
autoTagging: true
}
}
/**
*
*/
static async scanDirectory(directoryPath: string, config: ImportMaterialConfig): Promise<FileInfo[]> {
try {
const result = await invoke('scan_import_directory', {
directoryPath,
includeSubdirectories: config.includeSubdirectories,
fileTypes: config.fileTypes
})
return result as FileInfo[]
} catch (error) {
console.error('Failed to scan directory:', error)
throw new Error(`扫描目录失败: ${error}`)
}
}
/**
*
*/
static async startImport(config: ImportMaterialConfig): Promise<{ taskId: string }> {
try {
const result = await invoke('start_material_import', { config })
return result as { taskId: string }
} catch (error) {
console.error('Failed to start import:', error)
throw new Error(`开始导入失败: ${error}`)
}
}
/**
*
*/
static async getImportProgress(taskId: string): Promise<ImportProgress> {
try {
const result = await invoke('get_import_progress', { taskId })
return result as ImportProgress
} catch (error) {
console.error('Failed to get import progress:', error)
throw new Error(`获取导入进度失败: ${error}`)
}
}
/**
*
*/
static async cancelImport(taskId: string): Promise<boolean> {
try {
await invoke('cancel_import', { taskId })
return true
} catch (error) {
console.error('Failed to cancel import:', error)
return false
}
}
/**
*
*/
static async getImportResult(taskId: string): Promise<ImportResult> {
try {
const result = await invoke('get_import_result', { taskId })
return result as ImportResult
} catch (error) {
console.error('Failed to get import result:', error)
throw new Error(`获取导入结果失败: ${error}`)
}
}
/**
*
*/
static validateConfig(config: ImportMaterialConfig): string[] {
const errors: string[] = []
if (!config.projectId) {
errors.push('项目ID不能为空')
}
if (!config.sourceDirectory) {
errors.push('请选择素材目录')
}
if (config.fileTypes.length === 0) {
errors.push('请至少选择一种文件类型')
}
if (config.enableAutoSegmentation) {
const segConfig = config.segmentationConfig
if (segConfig.sceneThreshold < 0 || segConfig.sceneThreshold > 1) {
errors.push('场景变化阈值必须在0-1之间')
}
if (segConfig.timeInterval <= 0) {
errors.push('时间间隔必须大于0')
}
if (segConfig.minSegmentDuration <= 0) {
errors.push('最小片段时长必须大于0')
}
if (segConfig.maxSegmentDuration <= segConfig.minSegmentDuration) {
errors.push('最大片段时长必须大于最小片段时长')
}
}
return errors
}
/**
*
*/
static estimateImportTime(fileCount: number, config: ImportMaterialConfig): number {
// 基础导入时间(每个文件)
let timePerFile = 5 // 秒
// 自动分镜增加时间
if (config.enableAutoSegmentation) {
timePerFile += 15
}
// 生成缩略图增加时间
if (config.generateThumbnails) {
timePerFile += 3
}
// 元数据提取增加时间
if (config.extractMetadata) {
timePerFile += 2
}
// 自动标签增加时间
if (config.autoTagging) {
timePerFile += 5
}
return Math.round(fileCount * timePerFile)
}
/**
*
*/
static getSupportedFileTypes(): Array<{value: string, label: string, description: string}> {
return [
{ value: 'mp4', label: 'MP4', description: '最常用的视频格式' },
{ value: 'avi', label: 'AVI', description: '经典视频格式' },
{ value: 'mov', label: 'MOV', description: 'QuickTime视频格式' },
{ value: 'mkv', label: 'MKV', description: '高质量视频格式' },
{ value: 'wmv', label: 'WMV', description: 'Windows媒体格式' },
{ value: 'flv', label: 'FLV', description: 'Flash视频格式' },
{ value: 'm4v', label: 'M4V', description: 'iTunes视频格式' },
{ value: 'webm', label: 'WebM', description: 'Web视频格式' },
{ value: '3gp', label: '3GP', description: '移动设备格式' }
]
}
/**
*
*/
static getSegmentationMethods(): Array<{value: string, label: string, description: string}> {
return [
{
value: 'smart',
label: '智能分镜',
description: '结合场景变化和时间间隔的智能分镜'
},
{
value: 'scene',
label: '场景分镜',
description: '根据场景变化自动分镜'
},
{
value: 'time',
label: '时间分镜',
description: '按固定时间间隔分镜'
}
]
}
/**
*
*/
static async previewSegmentation(
filePath: string,
config: ImportMaterialConfig['segmentationConfig']
): Promise<Array<{start: number, end: number, thumbnail?: string}>> {
try {
const result = await invoke('preview_segmentation', { filePath, config })
return result as Array<{start: number, end: number, thumbnail?: string}>
} catch (error) {
console.error('Failed to preview segmentation:', error)
throw new Error(`预览分镜失败: ${error}`)
}
}
}