From 7f3a59282d22507b596306712ac1e9550ba3f4cb Mon Sep 17 00:00:00 2001 From: imeepos Date: Thu, 17 Jul 2025 20:09:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90CustomMultiSelect?= =?UTF-8?q?=E5=A4=9A=E9=80=89=E7=BB=84=E4=BB=B6=E5=BC=80=E5=8F=91=E5=B9=B6?= =?UTF-8?q?=E9=9B=86=E6=88=90=E5=88=B0=E9=A1=B6=E9=83=A8=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/App.tsx | 2 +- apps/desktop/src/components/CustomSelect.tsx | 260 +++++++++++++++++- apps/desktop/src/components/Navigation.tsx | 9 +- .../components/outfit/MultiSelectExample.tsx | 163 +++++++++++ .../outfit/OutfitAnalysisResult.tsx | 23 +- .../src/components/outfit/OutfitCard.tsx | 4 +- .../src/components/outfit/OutfitItemCard.tsx | 3 +- .../src/components/outfit/OutfitItemForm.tsx | 7 +- .../src/components/outfit/OutfitItemList.tsx | 3 +- .../outfit/OutfitMatchingRecommendation.tsx | 142 ++++------ .../components/outfit/OutfitSearchPanel.tsx | 21 +- apps/desktop/src/components/outfit/const.ts | 1 + apps/desktop/src/pages/OutfitMatch.tsx | 97 +++---- apps/desktop/src/pages/ProjectDetails.tsx | 82 ++---- 14 files changed, 571 insertions(+), 246 deletions(-) create mode 100644 apps/desktop/src/components/outfit/MultiSelectExample.tsx create mode 100644 apps/desktop/src/components/outfit/const.ts diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index e071d8c..c35d55b 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -81,7 +81,7 @@ function App() { } /> } /> } /> - } /> + } /> diff --git a/apps/desktop/src/components/CustomSelect.tsx b/apps/desktop/src/components/CustomSelect.tsx index 2d7ed25..daf2034 100644 --- a/apps/desktop/src/components/CustomSelect.tsx +++ b/apps/desktop/src/components/CustomSelect.tsx @@ -1,6 +1,8 @@ +import React, { useState, useRef, useEffect } from 'react'; import { ChevronDownIcon } from "lucide-react"; +import { XMarkIcon, CheckIcon } from '@heroicons/react/24/outline'; -// 自定义下拉选择组件 +// 单选下拉选择组件 export const CustomSelect: React.FC<{ value: string | number | null | undefined; onChange: (value: string) => void; @@ -31,4 +33,260 @@ export const CustomSelect: React.FC<{ ); +}; + +// 多选下拉选择组件 +interface MultiSelectOption { + value: string; + label: string; + disabled?: boolean; +} + +interface CustomMultiSelectProps { + options: MultiSelectOption[]; + value: string[]; + onChange: (value: string[]) => void; + placeholder?: string; + disabled?: boolean; + className?: string; + maxDisplayItems?: number; + searchable?: boolean; +} + +export const CustomMultiSelect: React.FC = ({ + options, + value = [], + onChange, + placeholder = "请选择...", + disabled = false, + className = "", + maxDisplayItems = 3, + searchable = false +}) => { + const [isOpen, setIsOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const dropdownRef = useRef(null); + const searchInputRef = useRef(null); + + // 过滤选项 + const filteredOptions = searchable && searchTerm + ? options.filter(option => + option.label.toLowerCase().includes(searchTerm.toLowerCase()) + ) + : options; + + // 获取选中的选项标签 + const getSelectedLabels = () => { + return value.map(val => { + const option = options.find(opt => opt.value === val); + return option ? option.label : val; + }); + }; + + // 显示的文本 + const getDisplayText = () => { + const selectedLabels = getSelectedLabels(); + + if (selectedLabels.length === 0) { + return placeholder; + } + + if (selectedLabels.length <= maxDisplayItems) { + return selectedLabels.join(', '); + } + + return `${selectedLabels.slice(0, maxDisplayItems).join(', ')} +${selectedLabels.length - maxDisplayItems}`; + }; + + // 处理选项点击 + const handleOptionClick = (optionValue: string) => { + if (value.includes(optionValue)) { + // 取消选择 + onChange(value.filter(val => val !== optionValue)); + } else { + // 添加选择 + onChange([...value, optionValue]); + } + }; + + // 移除单个选项 + const removeOption = (optionValue: string, e: React.MouseEvent) => { + e.stopPropagation(); + onChange(value.filter(val => val !== optionValue)); + }; + + // 清空所有选择 + const clearAll = (e: React.MouseEvent) => { + e.stopPropagation(); + onChange([]); + }; + + // 点击外部关闭下拉框 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + setSearchTerm(''); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + // 打开下拉框时聚焦搜索框 + useEffect(() => { + if (isOpen && searchable && searchInputRef.current) { + searchInputRef.current.focus(); + } + }, [isOpen, searchable]); + + return ( +
+ {/* 主选择框 */} +
!disabled && setIsOpen(!isOpen)} + > +
+
+ {value.length > 0 ? ( +
+ {value.length <= maxDisplayItems ? ( + // 显示所有选中的标签 + getSelectedLabels().map((label, index) => ( + + {label} + {!disabled && ( + + )} + + )) + ) : ( + // 显示简化文本 + + {getDisplayText()} + + )} +
+ ) : ( + {placeholder} + )} +
+ +
+ {value.length > 0 && !disabled && ( + + )} + +
+
+
+ + {/* 下拉选项 */} + {isOpen && ( +
+ {/* 搜索框 */} + {searchable && ( +
+ setSearchTerm(e.target.value)} + placeholder="搜索选项..." + className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500" + /> +
+ )} + + {/* 全选/取消全选 */} + {filteredOptions.length > 1 && ( +
+ +
+ )} + + {/* 选项列表 */} + {filteredOptions.length === 0 ? ( +
+ {searchTerm ? '没有找到匹配的选项' : '暂无选项'} +
+ ) : ( + filteredOptions.map((option) => { + const isSelected = value.includes(option.value); + + return ( +
!option.disabled && handleOptionClick(option.value)} + > +
+ + {option.label} + +
+ + {isSelected && ( + + + + )} +
+ ); + }) + )} +
+ )} +
+ ); }; \ No newline at end of file diff --git a/apps/desktop/src/components/Navigation.tsx b/apps/desktop/src/components/Navigation.tsx index f209a26..1279dfd 100644 --- a/apps/desktop/src/components/Navigation.tsx +++ b/apps/desktop/src/components/Navigation.tsx @@ -6,7 +6,8 @@ import { CpuChipIcon, DocumentDuplicateIcon, LinkIcon, - WrenchScrewdriverIcon + WrenchScrewdriverIcon, + SparklesIcon } from '@heroicons/react/24/outline'; const Navigation: React.FC = () => { @@ -43,6 +44,12 @@ const Navigation: React.FC = () => { icon: CpuChipIcon, description: '管理AI视频分类规则' }, + { + name: '服装搭配', + href: '/outfit-match', + icon: SparklesIcon, + description: 'AI智能服装搭配推荐' + }, { name: '便捷工具', href: '/tools', diff --git a/apps/desktop/src/components/outfit/MultiSelectExample.tsx b/apps/desktop/src/components/outfit/MultiSelectExample.tsx new file mode 100644 index 0000000..762a3fb --- /dev/null +++ b/apps/desktop/src/components/outfit/MultiSelectExample.tsx @@ -0,0 +1,163 @@ +import React, { useState } from 'react'; +import { CustomSelect, CustomMultiSelect } from '../CustomSelect'; + +// 示例:如何使用单选和多选组件 +const MultiSelectExample: React.FC = () => { + const [singleValue, setSingleValue] = useState(''); + const [multiValue, setMultiValue] = useState([]); + const [searchableMultiValue, setSearchableMultiValue] = useState([]); + + // 示例选项 + const categoryOptions = [ + { value: 'top', label: '上装' }, + { value: 'bottom', label: '下装' }, + { value: 'dress', label: '连衣裙' }, + { value: 'outerwear', label: '外套' }, + { value: 'footwear', label: '鞋类' }, + { value: 'accessory', label: '配饰' }, + { value: 'other', label: '其他' } + ]; + + const styleOptions = [ + { value: 'casual', label: '休闲风格' }, + { value: 'formal', label: '正式风格' }, + { value: 'business', label: '商务风格' }, + { value: 'street', label: '街头风格' }, + { value: 'elegant', label: '优雅风格' }, + { value: 'sporty', label: '运动风格' }, + { value: 'vintage', label: '复古风格' }, + { value: 'minimalist', label: '简约风格' }, + { value: 'bohemian', label: '波西米亚风格' }, + { value: 'gothic', label: '哥特风格' } + ]; + + return ( +
+
+

+ CustomSelect 组件使用示例 +

+ +
+ {/* 单选组件示例 */} +
+

单选组件 (CustomSelect)

+ +
+ + +

+ 当前选择: {singleValue || '未选择'} +

+
+
+ + {/* 多选组件示例 */} +
+

多选组件 (CustomMultiSelect)

+ +
+ + +

+ 当前选择: {multiValue.length > 0 ? multiValue.join(', ') : '未选择'} +

+
+
+
+ + {/* 带搜索的多选组件示例 */} +
+

带搜索的多选组件

+ +
+ + +

+ 当前选择: {searchableMultiValue.length > 0 ? searchableMultiValue.join(', ') : '未选择'} +

+
+
+ + {/* 使用说明 */} +
+

使用说明

+
+
+ CustomSelect (单选): +
    +
  • 传统的下拉选择框,只能选择一个选项
  • +
  • 使用 value (string) 和 onChange ((value: string) => void)
  • +
+
+
+ CustomMultiSelect (多选): +
    +
  • 支持选择多个选项,以标签形式显示
  • +
  • 使用 value (string[]) 和 onChange ((value: string[]) => void)
  • +
  • 支持搜索功能 (searchable=true)
  • +
  • 支持全选/取消全选
  • +
  • 可设置最大显示标签数 (maxDisplayItems)
  • +
  • 点击标签上的 × 可以移除单个选项
  • +
  • 点击右侧的 × 可以清空所有选择
  • +
+
+
+
+ + {/* 代码示例 */} +
+

代码示例

+
+{`// 单选组件
+
+
+// 多选组件
+`}
+          
+
+
+
+ ); +}; + +export default MultiSelectExample; diff --git a/apps/desktop/src/components/outfit/OutfitAnalysisResult.tsx b/apps/desktop/src/components/outfit/OutfitAnalysisResult.tsx index 7e14ced..826eaf2 100644 --- a/apps/desktop/src/components/outfit/OutfitAnalysisResult.tsx +++ b/apps/desktop/src/components/outfit/OutfitAnalysisResult.tsx @@ -9,6 +9,7 @@ import { } from '@heroicons/react/24/outline'; import { invoke } from '@tauri-apps/api/core'; import { useNotifications } from '../NotificationSystem'; +import { PROJECT_ID } from './const'; interface OutfitAnalysis { id: string; @@ -24,12 +25,10 @@ interface OutfitAnalysis { } interface OutfitAnalysisResultProps { - projectId: string; onCreateItems?: (analysisId: string) => void; } const OutfitAnalysisResult: React.FC = ({ - projectId, onCreateItems }) => { const [analyses, setAnalyses] = useState([]); @@ -42,7 +41,7 @@ const OutfitAnalysisResult: React.FC = ({ try { setLoading(true); const options = { - project_id: projectId, + project_id: PROJECT_ID, status: null, limit: 50, offset: 0 @@ -66,7 +65,7 @@ const OutfitAnalysisResult: React.FC = ({ const handleCreateItems = async (analysisId: string) => { try { await invoke('create_outfit_items_from_analysis', { - projectId, + projectId: PROJECT_ID, analysisId }); @@ -127,17 +126,15 @@ const OutfitAnalysisResult: React.FC = ({ }; useEffect(() => { - if (projectId) { + loadAnalyses(); + + // 设置定时刷新,检查分析状态 + const interval = setInterval(() => { loadAnalyses(); + }, 5000); // 每5秒刷新一次 - // 设置定时刷新,检查分析状态 - const interval = setInterval(() => { - loadAnalyses(); - }, 5000); // 每5秒刷新一次 - - return () => clearInterval(interval); - } - }, [projectId]); + return () => clearInterval(interval); + }, []); if (loading) { return ( diff --git a/apps/desktop/src/components/outfit/OutfitCard.tsx b/apps/desktop/src/components/outfit/OutfitCard.tsx index 0ddaddf..b7c01bf 100644 --- a/apps/desktop/src/components/outfit/OutfitCard.tsx +++ b/apps/desktop/src/components/outfit/OutfitCard.tsx @@ -100,7 +100,7 @@ const OutfitCard: React.FC = ({ } }; - const getCategoryIcon = (category: string) => { + const getCategoryIcon = (_category: string) => { // 这里可以根据类别返回不同的图标 return '👕'; // 简化处理,实际应用中可以使用更具体的图标 }; @@ -171,7 +171,7 @@ const OutfitCard: React.FC = ({
- {matching.items.slice(0, 4).map((item, index) => ( + {matching.items.slice(0, 4).map((item) => (
= ({ - projectId, item, isOpen, onClose, @@ -142,7 +141,7 @@ const OutfitItemForm: React.FC = ({ try { const requestData = { - project_id: projectId, + project_id: PROJECT_ID, analysis_id: item?.analysis_id || null, name: formData.name.trim(), category: formData.category.trim(), diff --git a/apps/desktop/src/components/outfit/OutfitItemList.tsx b/apps/desktop/src/components/outfit/OutfitItemList.tsx index 648eef2..c4758f4 100644 --- a/apps/desktop/src/components/outfit/OutfitItemList.tsx +++ b/apps/desktop/src/components/outfit/OutfitItemList.tsx @@ -9,6 +9,7 @@ import { } from '@heroicons/react/24/outline'; import { invoke } from '@tauri-apps/api/core'; import { useNotifications } from '../NotificationSystem'; +import { PROJECT_ID } from './const'; interface OutfitItem { id: string; @@ -53,7 +54,7 @@ const OutfitItemList: React.FC = ({ try { setLoading(true); const options = { - project_id: projectId, + project_id: PROJECT_ID, category: selectedCategory || null, limit: 100, offset: 0 diff --git a/apps/desktop/src/components/outfit/OutfitMatchingRecommendation.tsx b/apps/desktop/src/components/outfit/OutfitMatchingRecommendation.tsx index 3c5f8a0..7fccf9e 100644 --- a/apps/desktop/src/components/outfit/OutfitMatchingRecommendation.tsx +++ b/apps/desktop/src/components/outfit/OutfitMatchingRecommendation.tsx @@ -1,17 +1,16 @@ import React, { useState, useEffect } from 'react'; -import { +import { SparklesIcon, HeartIcon, - ShareIcon, BookmarkIcon, EyeIcon, - PlusIcon, ArrowPathIcon, AdjustmentsHorizontalIcon } from '@heroicons/react/24/outline'; import { HeartIcon as HeartSolidIcon, BookmarkIcon as BookmarkSolidIcon } from '@heroicons/react/24/solid'; import { invoke } from '@tauri-apps/api/core'; import { useNotifications } from '../NotificationSystem'; +import OutfitSearchPanel from './OutfitSearchPanel'; interface OutfitItem { id: string; @@ -51,11 +50,29 @@ const OutfitMatchingRecommendation: React.FC const [favorites, setFavorites] = useState>(new Set()); const [savedItems, setSavedItems] = useState>(new Set()); const [filters, setFilters] = useState({ - occasion: '', - season: '', - style: '', - minScore: 0.7 + categories: [] as string[], + styles: [] as string[], + occasions: [] as string[], + seasons: [] as string[], + colors: [] as string[], + minScore: 0.7, + confidenceLevel: '', + matchingTypes: [] as string[] }); + + // 处理筛选器变化 + const handleFiltersChange = (newFilters: any) => { + setFilters({ + categories: newFilters.categories || [], + styles: newFilters.styles || [], + occasions: newFilters.occasions || [], + seasons: newFilters.seasons || [], + colors: newFilters.colors || [], + minScore: newFilters.minScore || 0.7, + confidenceLevel: newFilters.confidenceLevel || '', + matchingTypes: newFilters.matchingTypes || [] + }); + }; const [showFilters, setShowFilters] = useState(false); const { addNotification } = useNotifications(); @@ -88,9 +105,9 @@ const OutfitMatchingRecommendation: React.FC project_id: projectId, max_recommendations: 12, min_score_threshold: filters.minScore, - occasion_filter: filters.occasion || null, - season_filter: filters.season || null, - style_filter: filters.style || null + occasion_filter: filters.occasions.length > 0 ? filters.occasions[0] : null, + season_filter: filters.seasons.length > 0 ? filters.seasons[0] : null, + style_filter: filters.styles.length > 0 ? filters.styles.join(',') : null }; const result = await invoke('generate_outfit_recommendations', { request }); @@ -185,9 +202,23 @@ const OutfitMatchingRecommendation: React.FC // 过滤推荐 const filteredRecommendations = recommendations.filter(rec => { - if (filters.occasion && !rec.occasion_tags.includes(filters.occasion)) return false; - if (filters.season && !rec.season_tags.includes(filters.season)) return false; - if (filters.style && !rec.style_description.toLowerCase().includes(filters.style.toLowerCase())) return false; + // 场合筛选 + if (filters.occasions.length > 0 && !filters.occasions.some(occasion => rec.occasion_tags.includes(occasion))) { + return false; + } + + // 季节筛选 + if (filters.seasons.length > 0 && !filters.seasons.some(season => rec.season_tags.includes(season))) { + return false; + } + + // 风格筛选 + if (filters.styles.length > 0 && !filters.styles.some(style => + rec.style_description.toLowerCase().includes(style.toLowerCase()) + )) { + return false; + } + return rec.score >= filters.minScore; }); @@ -243,84 +274,11 @@ const OutfitMatchingRecommendation: React.FC {/* 筛选面板 */} {showFilters && ( -
-
-
- - -
- -
- - -
- -
- - setFilters(prev => ({ ...prev, style: e.target.value }))} - placeholder="输入风格关键词" - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary-500 focus:border-primary-500" - /> -
- -
- - -
-
- -
- -
-
+ )} {/* 推荐列表 */} @@ -356,7 +314,7 @@ const OutfitMatchingRecommendation: React.FC {/* 搭配预览 */}
- {recommendation.items.slice(0, 4).map((item, index) => ( + {recommendation.items.slice(0, 4).map((item) => (
= ({ - handleFilterChange('categories', value)} placeholder="选择类别" - multiple />
@@ -199,12 +197,11 @@ const OutfitSearchPanel: React.FC = ({ - handleFilterChange('styles', value)} placeholder="选择风格" - multiple />
@@ -213,12 +210,11 @@ const OutfitSearchPanel: React.FC = ({ - handleFilterChange('occasions', value)} placeholder="选择场合" - multiple />
@@ -227,12 +223,11 @@ const OutfitSearchPanel: React.FC = ({ - handleFilterChange('seasons', value)} placeholder="选择季节" - multiple />
@@ -241,12 +236,11 @@ const OutfitSearchPanel: React.FC = ({ - handleFilterChange('colors', value)} placeholder="选择颜色" - multiple />
@@ -255,12 +249,11 @@ const OutfitSearchPanel: React.FC = ({ - handleFilterChange('matchingTypes', value)} placeholder="选择搭配类型" - multiple />
diff --git a/apps/desktop/src/components/outfit/const.ts b/apps/desktop/src/components/outfit/const.ts new file mode 100644 index 0000000..3c8e318 --- /dev/null +++ b/apps/desktop/src/components/outfit/const.ts @@ -0,0 +1 @@ +export const PROJECT_ID = "gen-lang-client-0413414134" \ No newline at end of file diff --git a/apps/desktop/src/pages/OutfitMatch.tsx b/apps/desktop/src/pages/OutfitMatch.tsx index 85033ba..04093db 100644 --- a/apps/desktop/src/pages/OutfitMatch.tsx +++ b/apps/desktop/src/pages/OutfitMatch.tsx @@ -17,9 +17,9 @@ import OutfitItemList from '../components/outfit/OutfitItemList'; import OutfitItemForm from '../components/outfit/OutfitItemForm'; import OutfitMatchingRecommendation from '../components/outfit/OutfitMatchingRecommendation'; import { useNotifications } from '../components/NotificationSystem'; +import { PROJECT_ID } from '../components/outfit/const'; interface OutfitMatchProps { - projectId?: string; } // 定义OutfitItem接口 @@ -43,7 +43,9 @@ interface OutfitItem { updated_at: string; } -const OutfitMatch: React.FC = ({ projectId }) => { +const OutfitMatch: React.FC = ({ }) => { + // 使用传入的projectId或默认的PROJECT_ID + const currentProjectId = PROJECT_ID; const [activeTab, setActiveTab] = useState<'upload' | 'analysis' | 'items' | 'matching'>('upload'); const [isUploading, setIsUploading] = useState(false); const [showItemForm, setShowItemForm] = useState(false); @@ -52,15 +54,6 @@ const OutfitMatch: React.FC = ({ projectId }) => { // 处理图像上传 const handleImageUpload = async (files: File[]) => { - if (!projectId) { - addNotification({ - type: 'error', - title: '错误', - message: '项目ID不存在,无法上传图片' - }); - return; - } - setIsUploading(true); try { @@ -72,7 +65,7 @@ const OutfitMatch: React.FC = ({ projectId }) => { // 保存文件到服务器 const savedPath = await invoke('save_outfit_image', { - projectId: projectId, + projectId: currentProjectId, fileName: file.name, fileData: Array.from(uint8Array) }); @@ -81,7 +74,7 @@ const OutfitMatch: React.FC = ({ projectId }) => { // 创建分析记录 const analysisRequest = { - project_id: projectId, + project_id: currentProjectId, image_path: savedPath, image_name: file.name }; @@ -160,11 +153,6 @@ const OutfitMatch: React.FC = ({ projectId }) => {

AI智能分析服装搭配,发现完美组合

- {projectId && ( -
- 项目: {projectId} -
- )} {/* 装饰性背景元素 */} @@ -186,8 +174,8 @@ const OutfitMatch: React.FC = ({ projectId }) => { key={key} onClick={() => setActiveTab(key as any)} className={`group inline-flex items-center py-4 px-1 border-b-2 font-medium text-sm ${activeTab === key - ? 'border-primary-500 text-primary-600' - : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' + ? 'border-primary-500 text-primary-600' + : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }`} > @@ -233,16 +221,13 @@ const OutfitMatch: React.FC = ({ projectId }) => {

- {projectId && ( - { - console.log('创建服装单品:', analysisId); - // 切换到服装单品标签页 - setActiveTab('items'); - }} - /> - )} + { + console.log('创建服装单品:', analysisId); + // 切换到服装单品标签页 + setActiveTab('items'); + }} + /> )} @@ -258,13 +243,11 @@ const OutfitMatch: React.FC = ({ projectId }) => {

- {projectId && ( - - )} + )} @@ -280,19 +263,17 @@ const OutfitMatch: React.FC = ({ projectId }) => {

- {projectId && ( - { - console.log('保存搭配推荐:', recommendation); - addNotification({ - type: 'success', - title: '搭配已保存', - message: '您可以在我的搭配中查看保存的搭配' - }); - }} - /> - )} + { + console.log('保存搭配推荐:', recommendation); + addNotification({ + type: 'success', + title: '搭配已保存', + message: '您可以在我的搭配中查看保存的搭配' + }); + }} + /> )} @@ -392,15 +373,13 @@ const OutfitMatch: React.FC = ({ projectId }) => { {/* 服装单品表单 */} - {projectId && ( - - )} + ); }; diff --git a/apps/desktop/src/pages/ProjectDetails.tsx b/apps/desktop/src/pages/ProjectDetails.tsx index c0ff7c3..ecc47f0 100644 --- a/apps/desktop/src/pages/ProjectDetails.tsx +++ b/apps/desktop/src/pages/ProjectDetails.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { ArrowLeft, FolderOpen, Upload, FileVideo, FileAudio, FileImage, HardDrive, Brain, Loader2, Link, Layers, Calendar, MapPin, Users, CheckCircle, Filter, Shuffle, Download, Shirt } from 'lucide-react'; +import { ArrowLeft, FolderOpen, Upload, FileVideo, FileAudio, FileImage, HardDrive, Brain, Loader2, Link, Layers, Calendar, MapPin, Users, CheckCircle, Filter, Shuffle, Download } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; import { useProjectStore } from '../store/projectStore'; import { useMaterialStore } from '../store/materialStore'; @@ -42,7 +42,6 @@ import { TemplateMatchingResultManager } from '../components/TemplateMatchingRes import { useNotifications } from '../components/NotificationSystem'; import { ProjectMaterialUsageOverviewComponent } from '../components/ProjectMaterialUsageOverview'; import { useMaterialUsage } from '../hooks/useMaterialUsage'; -import OutfitMatch from './OutfitMatch'; // 格式化时间 const formatTime = (dateString: string) => { @@ -146,8 +145,8 @@ export const ProjectDetails: React.FC = () => { const [materialClassificationFilter, setMaterialClassificationFilter] = useState('全部'); const [materialModelFilter, setMaterialModelFilter] = useState('全部'); const [materialUsageFilter, setMaterialUsageFilter] = useState('全部'); - const [materialClassificationRecords, setMaterialClassificationRecords] = useState<{[materialId: string]: any[]}>({}); - const [modelsMap, setModelsMap] = useState<{[modelId: string]: any}>({}); + const [materialClassificationRecords, setMaterialClassificationRecords] = useState<{ [materialId: string]: any[] }>({}); + const [modelsMap, setModelsMap] = useState<{ [modelId: string]: any }>({}); // 用于跟踪分类统计是否已加载的ref const classificationStatsLoadedRef = useRef(null); @@ -176,7 +175,7 @@ export const ProjectDetails: React.FC = () => { const loadAllModels = useCallback(async () => { try { const models = await invoke('get_all_models') as any[]; - const modelMap: {[modelId: string]: any} = {}; + const modelMap: { [modelId: string]: any } = {}; models.forEach(model => { modelMap[model.id] = model; }); @@ -194,7 +193,7 @@ export const ProjectDetails: React.FC = () => { const materialsToProcess = materialList || materials; // 获取每个素材的分类记录 - const classificationRecords: {[materialId: string]: any[]} = {}; + const classificationRecords: { [materialId: string]: any[] } = {}; for (const material of materialsToProcess) { try { const records = await invoke('get_material_classification_records', { materialId: material.id }) as any[]; @@ -440,7 +439,7 @@ export const ProjectDetails: React.FC = () => { }; const result = await MaterialMatchingService.executeMatching(request); - console.log({result}) + console.log({ result }) setMatchingResult(result); } catch (error) { console.error('素材匹配失败:', error); @@ -477,7 +476,7 @@ export const ProjectDetails: React.FC = () => { matchingDurationMs: 0 // 使用默认值 }); - console.log({savedResult}) + console.log({ savedResult }) // 创建素材使用记录 if (savedResult && typeof savedResult === 'object' && 'id' in savedResult) { try { @@ -688,7 +687,7 @@ export const ProjectDetails: React.FC = () => { const options = [{ label: '全部', value: '全部', count: materials.length }]; // 统计分类信息 - const categoryCount: {[category: string]: number} = {}; + const categoryCount: { [category: string]: number } = {}; // 遍历所有素材的分类记录 Object.values(materialClassificationRecords).forEach(records => { @@ -715,7 +714,7 @@ export const ProjectDetails: React.FC = () => { const options = [{ label: '全部', value: '全部', count: materials.length }]; // 统计模特信息 - const modelCounts: {[key: string]: number} = {}; + const modelCounts: { [key: string]: number } = {}; materials.forEach(material => { if (material.model_id) { const modelKey = material.model_id; @@ -918,11 +917,10 @@ export const ProjectDetails: React.FC = () => { @@ -1159,7 +1136,7 @@ export const ProjectDetails: React.FC = () => { )} - + )} @@ -1526,13 +1503,6 @@ export const ProjectDetails: React.FC = () => { )} - - {/* 服装搭配选项卡 */} - {activeTab === 'outfit-match' && project && ( -
- -
- )}