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 = () => {