From e513390ccb115fe6b4c00145fe04b7ac79b96950 Mon Sep 17 00:00:00 2001 From: oneao Date: Wed, 1 Jul 2026 22:11:08 +0800 Subject: [PATCH] 20260701221108 --- code/app/app-rn/.env.development | 2 +- .../src/app/clothes/(tabs)/home/index.tsx | 94 +++-- .../src/app/clothes/(tabs)/manage/index.tsx | 139 +++---- .../src/app/clothes/(tabs)/outfit/index.tsx | 215 ++++++++--- .../src/app/clothes/outfit/edit/index.tsx | 36 +- .../src/app/finance/(tabs)/analysis/index.tsx | 355 ++++++++++-------- .../src/app/finance/(tabs)/home/index.tsx | 200 ++++++---- .../app/app-rn/src/components/BottomSheet.tsx | 8 +- 志愿/志愿.html | 255 +++++++++++++ 9 files changed, 864 insertions(+), 440 deletions(-) create mode 100644 志愿/志愿.html diff --git a/code/app/app-rn/.env.development b/code/app/app-rn/.env.development index b8c3c1e9..8a103d2e 100644 --- a/code/app/app-rn/.env.development +++ b/code/app/app-rn/.env.development @@ -1 +1 @@ -EXPO_PUBLIC_API_URL=http://192.168.5.23:8087 \ No newline at end of file +EXPO_PUBLIC_API_URL=http://192.168.5.47:8087 \ No newline at end of file diff --git a/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx b/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx index 6cacbd4d..1f8c1460 100644 --- a/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx +++ b/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Modal, Pressable, Text, TouchableOpacity, View } from "react-native"; import { router, useFocusEffect } from "expo-router"; -import { Button, Card, Dialog, Label, SearchField } from "heroui-native"; +import { Button, Card, Dialog, Label, TextField } from "heroui-native"; import { CheckIcon, FilterIcon, @@ -419,31 +419,32 @@ export default function ClothingHome() { const renderHeader = useCallback( () => ( - - - - - - - - - - - - - + + + 共 {clothes.length} 件衣服 + + + + + + 筛选{activeFilterCount > 0 ? ` ${activeFilterCount}` : ""} + + + + + ), - [activeFilterCount, handleOpenFilter, handleRefresh, keyword], + [activeFilterCount, handleOpenFilter, handleRefresh], ); const filterChanged = @@ -472,14 +473,6 @@ export default function ClothingHome() { title={loading ? "正在加载衣橱" : "暂无衣服"} description={loading ? "请稍候" : "新增第一件衣服后,可以在这里筛选和管理"} center="screen" - footer={ - !loading ? ( - - ) : undefined - } /> )} /> @@ -488,17 +481,30 @@ export default function ClothingHome() { - - 筛选衣服 - - - 重置 - + + + 筛选衣服 + + + + 重置 + + + + + + + @@ -550,6 +556,14 @@ export default function ClothingHome() { + router.push("/clothes/edit" as any)} + > + + + ( - openEditCategory(item)} > - + {item.name} openEditCategory(item)} + className="w-8 h-8 items-center justify-center rounded-full" + onPress={(e) => { e.stopPropagation?.(); handleDeleteCategory(item); }} > - + - handleDeleteCategory(item)} - > - - - + ), [handleDeleteCategory, openEditCategory], ); @@ -448,75 +442,69 @@ export default function ClothingMetadataManage() { const collapsed = collapsedGroups.has(group.id); return ( - - {/* Group header — same row style as categories */} + + {/* Group header */} toggleGroup(group.id)} + onLongPress={() => openEditTagGroup(group)} > - + {group.name} - + {groupTags.length} { e.stopPropagation?.(); openEditTagGroup(group); }} - > - - - { e.stopPropagation?.(); handleDeleteTagGroup(group); }} > - + + - {/* Tags — same row style as categories, colored dot instead of icon */} + {/* Tags — with left border to show hierarchy */} {!collapsed && ( - + {groupTags.map((tag) => ( - openEditTag(tag)} > - + {tag.name} openEditTag(tag)} + className="w-7 h-7 items-center justify-center rounded-full" + onPress={(e) => { e.stopPropagation?.(); handleDeleteTag(tag); }} > - + - handleDeleteTag(tag)} - > - - - + ))} {groupTags.length === 0 && ( - 暂无标签 + 暂无标签 )} openCreateTag(group.id)} > - + + + 添加标签 @@ -548,10 +536,10 @@ export default function ClothingMetadataManage() { - - + + @@ -563,32 +551,19 @@ export default function ClothingMetadataManage() { - - - - - - - - - {/* 分类 */} - 分类 + + 分类 + + + + {currentCategories.length > 0 ? ( currentCategories.map(renderCategory) ) : ( @@ -598,8 +573,14 @@ export default function ClothingMetadataManage() { )} {/* 标签 */} - - 标签 + + 标签 + currentTagGroups.length > 0 ? openCreateTag() : openCreateTagGroup()} + > + + {currentTagGroups.length > 0 ? ( currentTagGroups.map(renderTagGroup) diff --git a/code/app/app-rn/src/app/clothes/(tabs)/outfit/index.tsx b/code/app/app-rn/src/app/clothes/(tabs)/outfit/index.tsx index 14430f68..846e4ce3 100644 --- a/code/app/app-rn/src/app/clothes/(tabs)/outfit/index.tsx +++ b/code/app/app-rn/src/app/clothes/(tabs)/outfit/index.tsx @@ -1,13 +1,14 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Modal, Pressable, Text, TouchableOpacity, View } from "react-native"; import { router, useFocusEffect } from "expo-router"; -import { Button, Card, Dialog } from "heroui-native"; +import { Button, Dialog, Label, TextField } from "heroui-native"; +import BottomSheet from "@/components/BottomSheet"; import { FileTextIcon, + FilterIcon, PencilIcon, PlusIcon, RotateCcwIcon, - SparklesIcon, Trash2Icon, } from "lucide-react-native"; import LayoutView from "@/components/layout/view"; @@ -17,7 +18,7 @@ import { useListLoading, useLoading } from "@/hooks/useLoading"; import { useModuleChatNotifier } from "@/hooks/useModuleChatNotifier"; import { LoadingOverlay } from "@/components/LoadingOverlay"; import { useClothingStore, useFamilyStore } from "@/store"; -import { ClothingOutfit, ClothingStatus, ClothingTargetType } from "@/types/clothing"; +import { ClothingOutfit, ClothingTargetType } from "@/types/clothing"; export default function ClothingOutfitList() { const familyId = useFamilyStore((s) => s.getFamilyId()); @@ -84,6 +85,10 @@ export default function ClothingOutfitList() { const [activeItem, setActiveItem] = useState(null); const [menuPos, setMenuPos] = useState({ x: 0, y: 0 }); const [deleteConfirm, setDeleteConfirm] = useState(null); + const [keyword, setKeyword] = useState(""); + const [showFilterSheet, setShowFilterSheet] = useState(false); + const [filterCategoryId, setFilterCategoryId] = useState(null); + const [filterTagIds, setFilterTagIds] = useState([]); const handleDeleteRequest = useCallback(() => { if (!activeItem) return; @@ -139,21 +144,55 @@ export default function ClothingOutfitList() { [outfitClothes], ); + const filteredOutfits = useMemo(() => { + const kw = keyword.trim().toLowerCase(); + return outfits.filter((item) => { + if (filterCategoryId && item.category_id !== filterCategoryId) return false; + if (filterTagIds.length > 0) { + const itemTagIds = outfitTagIds.get(item.id); + if (!itemTagIds) return false; + if (!filterTagIds.every((tid) => itemTagIds.includes(tid))) return false; + } + if (!kw) return true; + return item.name.toLowerCase().includes(kw); + }); + }, [outfits, keyword, filterCategoryId, filterTagIds, outfitTagIds]); + + const activeFilterCount = useMemo(() => { + let c = 0; + if (filterCategoryId) c++; + if (filterTagIds.length > 0) c++; + return c; + }, [filterCategoryId, filterTagIds]); + const renderHeader = useCallback( () => ( - - - - + + + 共 {outfits.length} 套搭配 + + + setShowFilterSheet(true)} + > + + + 搜索{activeFilterCount > 0 ? ` ${activeFilterCount}` : ""} + + + + + ), - [handleRefresh], + [activeFilterCount, handleRefresh, outfits.length], ); const renderItem = useCallback( @@ -164,7 +203,7 @@ export default function ClothingOutfitList() { return ( router.push(`/clothes/outfit/detail/${item.id}` as any)} @@ -173,33 +212,24 @@ export default function ClothingOutfitList() { setActiveItem(item); }} > - - - - - - - {item.name} - - - {getClothesCount(item)} 件衣服 - {categoryName ? ` · ${categoryName}` : ""} - - {itemTags.length > 0 && ( - - {itemTags.slice(0, 2).map((tag) => ( - - {tag!.name} - - ))} - - )} - - + + + + + {item.name} + + {getClothesCount(item)} 件衣服{categoryName ? ` · ${categoryName}` : ""} + + {itemTags.length > 0 && ( + + {itemTags.slice(0, 3).map((tag) => ( + + {tag!.name} + + ))} + + )} + ); }, @@ -209,18 +239,16 @@ export default function ClothingOutfitList() { return ( String(item.id)} renderItem={renderItem} - numColumns={2} - columnWrapperStyle={{ gap: 12 }} header={renderHeader()} refreshing={refreshing} onRefresh={handleRefresh} showsVerticalScrollIndicator={false} contentContainerStyle={[ { paddingHorizontal: 16, paddingBottom: 120 }, - outfits.length === 0 ? { flexGrow: 1 } : undefined, + filteredOutfits.length === 0 ? { flexGrow: 1 } : undefined, ]} ListEmptyComponent={() => ( router.push("/clothes/outfit/edit" as any)}> - - 新增搭配 - - ) : undefined - } /> )} /> + + + + + + + 搜索搭配 + + { setFilterCategoryId(null); setFilterTagIds([]); setKeyword(""); }} + > + + 重置 + + + + + + + + + + + + + setFilterCategoryId(null)} + > + 全部 + + {categories.filter(c => c.type === ClothingTargetType.Outfit).map(c => { + const selected = filterCategoryId === c.id; + return ( + setFilterCategoryId(selected ? null : c.id)} + > + {c.name} + + ); + })} + + + + + + {tags.filter(t => t.type === ClothingTargetType.Outfit).length > 0 ? ( + + {tags.filter(t => t.type === ClothingTargetType.Outfit).map(t => { + const selected = filterTagIds.includes(t.id); + return ( + setFilterTagIds(prev => selected ? prev.filter(id => id !== t.id) : [...prev, t.id])} + > + {t.name} + + ); + })} + + ) : ( + 暂无标签 + )} + + + + + + + + router.push("/clothes/outfit/edit" as any)} + > + + + new Map(clothes.map((item) => [item.id, item])), [clothes]); const outfitCategories = useMemo( @@ -620,7 +617,7 @@ export default function ClothingOutfitEdit() { ref={canvasRef} collapsable={false} className="w-full flex-1" - style={{ aspectRatio: canvasFullscreen ? undefined : 1 }} + style={{ aspectRatio: 3 / 4 }} onLayout={(event) => setCanvasSize(event.nativeEvent.layout.width)} > {items.length === 0 && ( @@ -715,18 +712,12 @@ export default function ClothingOutfitEdit() { - - {/* 画布 */} - {!canvasFullscreen && ( - - {canvasContent} - - )} + + {canvasContent} +