20260701221108
This commit is contained in:
1 parent
e4ac6f7cc9
commit
e513390ccb
9 files changed
+666
-242
No files matched your search
@@ -1 +1 @@
|
|||||||
EXPO_PUBLIC_API_URL=http://192.168.5.23:8087
|
EXPO_PUBLIC_API_URL=http://192.168.5.47:8087
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Modal, Pressable, Text, TouchableOpacity, View } from "react-native";
|
import { Modal, Pressable, Text, TouchableOpacity, View } from "react-native";
|
||||||
import { router, useFocusEffect } from "expo-router";
|
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 {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
@@ -419,31 +419,32 @@ export default function ClothingHome() {
|
|||||||
|
|
||||||
const renderHeader = useCallback(
|
const renderHeader = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<View className="gap-3 pb-4">
|
<View className="flex-row items-center pb-4">
|
||||||
<SearchField value={keyword} onChange={setKeyword}>
|
<Text className="text-xs text-gray-400 flex-1">
|
||||||
<SearchField.Group>
|
共 {clothes.length} 件衣服
|
||||||
<SearchField.SearchIcon />
|
</Text>
|
||||||
<SearchField.Input placeholder="搜索衣服、品牌、备注" />
|
<View className="flex-row gap-2 items-center">
|
||||||
<SearchField.ClearButton />
|
<TouchableOpacity
|
||||||
</SearchField.Group>
|
className="h-10 px-3 rounded-full bg-accent flex-row items-center gap-1.5"
|
||||||
</SearchField>
|
activeOpacity={0.8}
|
||||||
|
onPress={handleOpenFilter}
|
||||||
<View className="flex-row gap-2">
|
>
|
||||||
<Button className="flex-1" onPress={() => router.push("/clothes/edit" as any)}>
|
<FilterIcon size={16} color="#fff" />
|
||||||
<PlusIcon size={18} color="#fff" />
|
<Text className="text-sm font-medium text-white">
|
||||||
<Button.Label>新增衣服</Button.Label>
|
筛选{activeFilterCount > 0 ? ` ${activeFilterCount}` : ""}
|
||||||
</Button>
|
</Text>
|
||||||
<Button variant="secondary" isIconOnly onPress={handleRefresh}>
|
</TouchableOpacity>
|
||||||
<RotateCcwIcon size={18} color="#374151" />
|
<TouchableOpacity
|
||||||
</Button>
|
className="h-10 w-10 rounded-full bg-gray-100 items-center justify-center"
|
||||||
<Button variant="secondary" onPress={handleOpenFilter}>
|
activeOpacity={0.75}
|
||||||
<FilterIcon size={17} color="#374151" />
|
onPress={handleRefresh}
|
||||||
<Button.Label>筛选{activeFilterCount > 0 ? ` ${activeFilterCount}` : ""}</Button.Label>
|
>
|
||||||
</Button>
|
<RotateCcwIcon size={18} color="#4B5563" />
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
[activeFilterCount, handleOpenFilter, handleRefresh, keyword],
|
[activeFilterCount, handleOpenFilter, handleRefresh],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterChanged =
|
const filterChanged =
|
||||||
@@ -472,14 +473,6 @@ export default function ClothingHome() {
|
|||||||
title={loading ? "正在加载衣橱" : "暂无衣服"}
|
title={loading ? "正在加载衣橱" : "暂无衣服"}
|
||||||
description={loading ? "请稍候" : "新增第一件衣服后,可以在这里筛选和管理"}
|
description={loading ? "请稍候" : "新增第一件衣服后,可以在这里筛选和管理"}
|
||||||
center="screen"
|
center="screen"
|
||||||
footer={
|
|
||||||
!loading ? (
|
|
||||||
<Button className="mt-4" onPress={() => router.push("/clothes/edit" as any)}>
|
|
||||||
<PlusIcon size={18} color="#fff" />
|
|
||||||
<Button.Label>新增衣服</Button.Label>
|
|
||||||
</Button>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -488,8 +481,10 @@ export default function ClothingHome() {
|
|||||||
<BottomSheet.Portal>
|
<BottomSheet.Portal>
|
||||||
<BottomSheet.Overlay />
|
<BottomSheet.Overlay />
|
||||||
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
||||||
<View className="flex-row items-center justify-between">
|
<View className="flex-row items-center">
|
||||||
<Text className="text-lg font-semibold text-gray-900">筛选衣服</Text>
|
<View className="flex-1" />
|
||||||
|
<Text className="text-center text-lg font-semibold text-gray-900">筛选衣服</Text>
|
||||||
|
<View className="flex-1 items-end">
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
||||||
onPress={handleResetFilter}
|
onPress={handleResetFilter}
|
||||||
@@ -498,6 +493,17 @@ export default function ClothingHome() {
|
|||||||
<Text className="text-xs text-gray-600">重置</Text>
|
<Text className="text-xs text-gray-600">重置</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TextField>
|
||||||
|
<Label className="mb-2">关键词</Label>
|
||||||
|
<BottomSheet.Input
|
||||||
|
value={keyword}
|
||||||
|
onChangeText={setKeyword}
|
||||||
|
placeholder="搜索衣服、品牌、备注"
|
||||||
|
returnKeyType="search"
|
||||||
|
/>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
<View>
|
<View>
|
||||||
<Label className="mb-2">分类</Label>
|
<Label className="mb-2">分类</Label>
|
||||||
@@ -550,6 +556,14 @@ export default function ClothingHome() {
|
|||||||
</BottomSheet.Portal>
|
</BottomSheet.Portal>
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
className="absolute bottom-20 right-5 w-14 h-14 rounded-full bg-accent items-center justify-center shadow-lg z-50"
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => router.push("/clothes/edit" as any)}
|
||||||
|
>
|
||||||
|
<PlusIcon size={24} color="#fff" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
visible={!!activeItem}
|
visible={!!activeItem}
|
||||||
transparent
|
transparent
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
FolderIcon,
|
FolderIcon,
|
||||||
PencilIcon,
|
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
RotateCcwIcon,
|
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
TagIcon,
|
TagIcon,
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
@@ -415,29 +413,25 @@ export default function ClothingMetadataManage() {
|
|||||||
|
|
||||||
const renderCategory = useCallback(
|
const renderCategory = useCallback(
|
||||||
(item: ClothingCategory) => (
|
(item: ClothingCategory) => (
|
||||||
<View
|
<TouchableOpacity
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="flex-row items-center gap-3 bg-white rounded-xl px-3 py-3 mb-2"
|
className="flex-row items-center gap-2.5 bg-gray-50 rounded-2xl px-3.5 py-3 mb-1.5"
|
||||||
|
activeOpacity={0.6}
|
||||||
|
onPress={() => openEditCategory(item)}
|
||||||
>
|
>
|
||||||
<View className="h-9 w-9 rounded-xl bg-gray-100 items-center justify-center">
|
<View className="h-9 w-9 rounded-xl bg-white items-center justify-center">
|
||||||
<FolderIcon size={18} color="#6B7280" />
|
<FolderIcon size={18} color="#6B7280" />
|
||||||
</View>
|
</View>
|
||||||
<Text className="flex-1 text-sm font-medium text-gray-900" numberOfLines={1}>
|
<Text className="flex-1 text-sm font-medium text-gray-900" numberOfLines={1}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="w-9 h-9 items-center justify-center rounded-full bg-gray-100"
|
className="w-8 h-8 items-center justify-center rounded-full"
|
||||||
onPress={() => openEditCategory(item)}
|
onPress={(e) => { e.stopPropagation?.(); handleDeleteCategory(item); }}
|
||||||
>
|
>
|
||||||
<PencilIcon size={15} color="#374151" />
|
<Trash2Icon size={14} color="#EF4444" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
|
||||||
className="w-9 h-9 items-center justify-center rounded-full bg-red-50"
|
|
||||||
onPress={() => handleDeleteCategory(item)}
|
|
||||||
>
|
|
||||||
<Trash2Icon size={15} color="#EF4444" />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
|
||||||
),
|
),
|
||||||
[handleDeleteCategory, openEditCategory],
|
[handleDeleteCategory, openEditCategory],
|
||||||
);
|
);
|
||||||
@@ -448,75 +442,69 @@ export default function ClothingMetadataManage() {
|
|||||||
const collapsed = collapsedGroups.has(group.id);
|
const collapsed = collapsedGroups.has(group.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={group.id} className="mb-3">
|
<View key={group.id} className="mb-1.5">
|
||||||
{/* Group header — same row style as categories */}
|
{/* Group header */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="flex-row items-center gap-3 bg-white rounded-xl px-3 py-3"
|
className="flex-row items-center gap-2.5 bg-gray-50 rounded-2xl px-3.5 py-3"
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.6}
|
||||||
onPress={() => toggleGroup(group.id)}
|
onPress={() => toggleGroup(group.id)}
|
||||||
|
onLongPress={() => openEditTagGroup(group)}
|
||||||
>
|
>
|
||||||
<View className="h-9 w-9 rounded-xl bg-gray-100 items-center justify-center">
|
<View className="h-9 w-9 rounded-xl bg-white items-center justify-center">
|
||||||
<TagIcon size={18} color="#6B7280" />
|
<TagIcon size={18} color="#6B7280" />
|
||||||
</View>
|
</View>
|
||||||
<Text className="flex-1 text-sm font-semibold text-gray-900" numberOfLines={1}>
|
<Text className="flex-1 text-sm font-semibold text-gray-900" numberOfLines={1}>
|
||||||
{group.name}
|
{group.name}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronDownIcon
|
<Text className="text-xs text-gray-400">{groupTags.length}</Text>
|
||||||
size={16}
|
|
||||||
color="#9CA3AF"
|
|
||||||
style={{ transform: [{ rotate: collapsed ? "0deg" : "180deg" }] }}
|
|
||||||
/>
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="w-8 h-8 items-center justify-center rounded-full bg-gray-100"
|
className="w-7 h-7 items-center justify-center rounded-full"
|
||||||
onPress={(e) => { e.stopPropagation?.(); openEditTagGroup(group); }}
|
|
||||||
>
|
|
||||||
<PencilIcon size={14} color="#374151" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
className="w-8 h-8 items-center justify-center rounded-full bg-red-50"
|
|
||||||
onPress={(e) => { e.stopPropagation?.(); handleDeleteTagGroup(group); }}
|
onPress={(e) => { e.stopPropagation?.(); handleDeleteTagGroup(group); }}
|
||||||
>
|
>
|
||||||
<Trash2Icon size={14} color="#EF4444" />
|
<Trash2Icon size={13} color="#EF4444" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<ChevronDownIcon
|
||||||
|
size={16}
|
||||||
|
color="#D1D5DB"
|
||||||
|
style={{ transform: [{ rotate: collapsed ? "0deg" : "180deg" }] }}
|
||||||
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
{/* Tags — same row style as categories, colored dot instead of icon */}
|
{/* Tags — with left border to show hierarchy */}
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<View className="mt-2 ml-3">
|
<View className="ml-2.5 pl-3 border-l-2 border-gray-100">
|
||||||
{groupTags.map((tag) => (
|
{groupTags.map((tag) => (
|
||||||
<View
|
<TouchableOpacity
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
className="flex-row items-center gap-3 bg-white rounded-xl px-3 py-2.5 mb-1 ml-9"
|
className="flex-row items-center gap-2.5 py-2.5 border-b border-gray-50"
|
||||||
|
activeOpacity={0.6}
|
||||||
|
onPress={() => openEditTag(tag)}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
className="w-3 h-3 rounded-full"
|
className="w-2.5 h-2.5 rounded-full ml-0.5"
|
||||||
style={{ backgroundColor: tag.color || "#D1D5DB" }}
|
style={{ backgroundColor: tag.color || "#D1D5DB" }}
|
||||||
/>
|
/>
|
||||||
<Text className="flex-1 text-sm text-gray-900" numberOfLines={1}>
|
<Text className="flex-1 text-sm text-gray-700" numberOfLines={1}>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="w-8 h-8 items-center justify-center rounded-full bg-gray-100"
|
className="w-7 h-7 items-center justify-center rounded-full"
|
||||||
onPress={() => openEditTag(tag)}
|
onPress={(e) => { e.stopPropagation?.(); handleDeleteTag(tag); }}
|
||||||
>
|
>
|
||||||
<PencilIcon size={14} color="#374151" />
|
<Trash2Icon size={13} color="#EF4444" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
|
||||||
className="w-8 h-8 items-center justify-center rounded-full bg-red-50"
|
|
||||||
onPress={() => handleDeleteTag(tag)}
|
|
||||||
>
|
|
||||||
<Trash2Icon size={14} color="#EF4444" />
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
|
||||||
))}
|
))}
|
||||||
{groupTags.length === 0 && (
|
{groupTags.length === 0 && (
|
||||||
<Text className="text-xs text-gray-400 py-2 ml-9">暂无标签</Text>
|
<Text className="text-xs text-gray-400 py-2">暂无标签</Text>
|
||||||
)}
|
)}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="flex-row items-center gap-1.5 py-2 ml-9"
|
className="flex-row items-center gap-1.5 py-2.5"
|
||||||
onPress={() => openCreateTag(group.id)}
|
onPress={() => openCreateTag(group.id)}
|
||||||
>
|
>
|
||||||
<PlusIcon size={14} color="#9CA3AF" />
|
<View className="w-4 h-4 rounded-full border border-dashed border-gray-300 items-center justify-center">
|
||||||
|
<PlusIcon size={9} color="#9CA3AF" />
|
||||||
|
</View>
|
||||||
<Text className="text-xs text-gray-400">添加标签</Text>
|
<Text className="text-xs text-gray-400">添加标签</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -548,10 +536,10 @@ export default function ClothingMetadataManage() {
|
|||||||
<LayoutView.ScrollView
|
<LayoutView.ScrollView
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
contentContainerStyle={{ paddingBottom: 120 }}
|
contentContainerStyle={{ paddingBottom: 40 }}
|
||||||
>
|
>
|
||||||
<View className="gap-3 mb-4">
|
<View className="gap-3 mb-3">
|
||||||
<View className="flex-row items-center justify-between">
|
<View className="flex-row items-center">
|
||||||
<Tabs value={activeType} onValueChange={setActiveType} variant="primary" className="flex-1">
|
<Tabs value={activeType} onValueChange={setActiveType} variant="primary" className="flex-1">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Indicator />
|
<Tabs.Indicator />
|
||||||
@@ -563,32 +551,19 @@ export default function ClothingMetadataManage() {
|
|||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<TouchableOpacity
|
|
||||||
className="ml-3 w-10 h-10 rounded-full bg-gray-100 items-center justify-center"
|
|
||||||
onPress={handleRefresh}
|
|
||||||
>
|
|
||||||
<RotateCcwIcon size={18} color="#4B5563" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="flex-row gap-2">
|
|
||||||
<Button className="flex-1" size="sm" onPress={openCreateCategory}>
|
|
||||||
<PlusIcon size={16} color="#fff" />
|
|
||||||
<Button.Label>分类</Button.Label>
|
|
||||||
</Button>
|
|
||||||
<Button className="flex-1" size="sm" onPress={openCreateTagGroup}>
|
|
||||||
<PlusIcon size={16} color="#fff" />
|
|
||||||
<Button.Label>标签组</Button.Label>
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" size="sm" className="flex-1" onPress={() => openCreateTag()} isDisabled={currentTagGroups.length === 0}>
|
|
||||||
<PlusIcon size={16} color="#374151" />
|
|
||||||
<Button.Label>标签</Button.Label>
|
|
||||||
</Button>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 分类 */}
|
{/* 分类 */}
|
||||||
<Text className="text-xs font-semibold text-gray-400 mb-2 ml-1">分类</Text>
|
<View className="flex-row items-center justify-between mb-2">
|
||||||
|
<Text className="text-sm font-semibold text-gray-500">分类</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="w-7 h-7 rounded-full bg-gray-100 items-center justify-center"
|
||||||
|
onPress={openCreateCategory}
|
||||||
|
>
|
||||||
|
<PlusIcon size={16} color="#6B7280" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
{currentCategories.length > 0 ? (
|
{currentCategories.length > 0 ? (
|
||||||
currentCategories.map(renderCategory)
|
currentCategories.map(renderCategory)
|
||||||
) : (
|
) : (
|
||||||
@@ -598,8 +573,14 @@ export default function ClothingMetadataManage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 标签 */}
|
{/* 标签 */}
|
||||||
<View className="mt-5 mb-2">
|
<View className="flex-row items-center justify-between mt-3 mb-2">
|
||||||
<Text className="text-xs font-semibold text-gray-400 ml-1">标签</Text>
|
<Text className="text-sm font-semibold text-gray-500">标签</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="w-7 h-7 rounded-full bg-gray-100 items-center justify-center"
|
||||||
|
onPress={() => currentTagGroups.length > 0 ? openCreateTag() : openCreateTagGroup()}
|
||||||
|
>
|
||||||
|
<PlusIcon size={16} color="#6B7280" />
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
{currentTagGroups.length > 0 ? (
|
{currentTagGroups.length > 0 ? (
|
||||||
currentTagGroups.map(renderTagGroup)
|
currentTagGroups.map(renderTagGroup)
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Modal, Pressable, Text, TouchableOpacity, View } from "react-native";
|
import { Modal, Pressable, Text, TouchableOpacity, View } from "react-native";
|
||||||
import { router, useFocusEffect } from "expo-router";
|
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 {
|
import {
|
||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
|
FilterIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
RotateCcwIcon,
|
RotateCcwIcon,
|
||||||
SparklesIcon,
|
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import LayoutView from "@/components/layout/view";
|
import LayoutView from "@/components/layout/view";
|
||||||
@@ -17,7 +18,7 @@ import { useListLoading, useLoading } from "@/hooks/useLoading";
|
|||||||
import { useModuleChatNotifier } from "@/hooks/useModuleChatNotifier";
|
import { useModuleChatNotifier } from "@/hooks/useModuleChatNotifier";
|
||||||
import { LoadingOverlay } from "@/components/LoadingOverlay";
|
import { LoadingOverlay } from "@/components/LoadingOverlay";
|
||||||
import { useClothingStore, useFamilyStore } from "@/store";
|
import { useClothingStore, useFamilyStore } from "@/store";
|
||||||
import { ClothingOutfit, ClothingStatus, ClothingTargetType } from "@/types/clothing";
|
import { ClothingOutfit, ClothingTargetType } from "@/types/clothing";
|
||||||
|
|
||||||
export default function ClothingOutfitList() {
|
export default function ClothingOutfitList() {
|
||||||
const familyId = useFamilyStore((s) => s.getFamilyId());
|
const familyId = useFamilyStore((s) => s.getFamilyId());
|
||||||
@@ -84,6 +85,10 @@ export default function ClothingOutfitList() {
|
|||||||
const [activeItem, setActiveItem] = useState<ClothingOutfit | null>(null);
|
const [activeItem, setActiveItem] = useState<ClothingOutfit | null>(null);
|
||||||
const [menuPos, setMenuPos] = useState({ x: 0, y: 0 });
|
const [menuPos, setMenuPos] = useState({ x: 0, y: 0 });
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState<ClothingOutfit | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<ClothingOutfit | null>(null);
|
||||||
|
const [keyword, setKeyword] = useState("");
|
||||||
|
const [showFilterSheet, setShowFilterSheet] = useState(false);
|
||||||
|
const [filterCategoryId, setFilterCategoryId] = useState<number | null>(null);
|
||||||
|
const [filterTagIds, setFilterTagIds] = useState<number[]>([]);
|
||||||
|
|
||||||
const handleDeleteRequest = useCallback(() => {
|
const handleDeleteRequest = useCallback(() => {
|
||||||
if (!activeItem) return;
|
if (!activeItem) return;
|
||||||
@@ -139,21 +144,55 @@ export default function ClothingOutfitList() {
|
|||||||
[outfitClothes],
|
[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(
|
const renderHeader = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<View className="gap-3 pb-4">
|
<View className="flex-row items-center pb-4">
|
||||||
<View className="flex-row gap-2">
|
<Text className="text-xs text-gray-400 flex-1">
|
||||||
<Button className="flex-1" onPress={() => router.push("/clothes/outfit/edit" as any)}>
|
共 {outfits.length} 套搭配
|
||||||
<PlusIcon size={18} color="#fff" />
|
</Text>
|
||||||
<Button.Label>新增搭配</Button.Label>
|
<View className="flex-row gap-2 items-center">
|
||||||
</Button>
|
<TouchableOpacity
|
||||||
<Button variant="secondary" isIconOnly onPress={handleRefresh}>
|
className="h-10 px-3 rounded-full bg-accent flex-row items-center gap-1.5"
|
||||||
<RotateCcwIcon size={18} color="#374151" />
|
activeOpacity={0.8}
|
||||||
</Button>
|
onPress={() => setShowFilterSheet(true)}
|
||||||
|
>
|
||||||
|
<FilterIcon size={16} color="#fff" />
|
||||||
|
<Text className="text-sm font-medium text-white">
|
||||||
|
搜索{activeFilterCount > 0 ? ` ${activeFilterCount}` : ""}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
className="h-10 w-10 rounded-full bg-gray-100 items-center justify-center"
|
||||||
|
activeOpacity={0.75}
|
||||||
|
onPress={handleRefresh}
|
||||||
|
>
|
||||||
|
<RotateCcwIcon size={18} color="#4B5563" />
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
[handleRefresh],
|
[activeFilterCount, handleRefresh, outfits.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
@@ -164,7 +203,7 @@ export default function ClothingOutfitList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="mb-3 flex-1"
|
className="bg-white rounded-xl mb-2 flex-row overflow-hidden"
|
||||||
activeOpacity={0.82}
|
activeOpacity={0.82}
|
||||||
delayLongPress={260}
|
delayLongPress={260}
|
||||||
onPress={() => router.push(`/clothes/outfit/detail/${item.id}` as any)}
|
onPress={() => router.push(`/clothes/outfit/detail/${item.id}` as any)}
|
||||||
@@ -173,33 +212,24 @@ export default function ClothingOutfitList() {
|
|||||||
setActiveItem(item);
|
setActiveItem(item);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card className="overflow-hidden p-0">
|
<View className="w-24" style={{ aspectRatio: 3 / 4 }}>
|
||||||
<View className="w-full bg-gray-50" style={{ aspectRatio: 1 }}>
|
<ClothingImage fileKey={getCoverKey(item)} label={item.name} className="w-full h-full bg-gray-50" resizeMode="contain" />
|
||||||
<ClothingImage fileKey={getCoverKey(item)} label={item.name} className="w-full h-full" resizeMode="contain" />
|
|
||||||
</View>
|
</View>
|
||||||
<Card.Body className="px-3 pt-3 pb-2">
|
<View className="flex-1 px-3 py-3 justify-center">
|
||||||
<Card.Title className="text-sm" numberOfLines={1}>
|
<Text className="text-[15px] font-semibold text-gray-900" numberOfLines={1}>{item.name}</Text>
|
||||||
{item.name}
|
<Text className="text-xs text-gray-400 mt-0.5">
|
||||||
</Card.Title>
|
{getClothesCount(item)} 件衣服{categoryName ? ` · ${categoryName}` : ""}
|
||||||
<Card.Description className="mt-0.5">
|
</Text>
|
||||||
{getClothesCount(item)} 件衣服
|
|
||||||
{categoryName ? ` · ${categoryName}` : ""}
|
|
||||||
</Card.Description>
|
|
||||||
{itemTags.length > 0 && (
|
{itemTags.length > 0 && (
|
||||||
<View className="flex-row gap-1 mt-1.5">
|
<View className="flex-row gap-1 mt-1.5">
|
||||||
{itemTags.slice(0, 2).map((tag) => (
|
{itemTags.slice(0, 3).map((tag) => (
|
||||||
<View
|
<View key={tag!.id} className="rounded-full px-2 py-0.5" style={{ backgroundColor: tag!.color || "#F3F4F6" }}>
|
||||||
key={tag!.id}
|
|
||||||
className="rounded-full px-2 py-0.5"
|
|
||||||
style={{ backgroundColor: tag!.color || "#F3F4F6" }}
|
|
||||||
>
|
|
||||||
<Text className="text-[10px]" style={{ color: tag!.color ? "#fff" : "#6B7280" }}>{tag!.name}</Text>
|
<Text className="text-[10px]" style={{ color: tag!.color ? "#fff" : "#6B7280" }}>{tag!.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</Card.Body>
|
</View>
|
||||||
</Card>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -209,18 +239,16 @@ export default function ClothingOutfitList() {
|
|||||||
return (
|
return (
|
||||||
<LayoutView>
|
<LayoutView>
|
||||||
<LayoutView.FlatList
|
<LayoutView.FlatList
|
||||||
data={outfits}
|
data={filteredOutfits}
|
||||||
keyExtractor={(item) => String(item.id)}
|
keyExtractor={(item) => String(item.id)}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
numColumns={2}
|
|
||||||
columnWrapperStyle={{ gap: 12 }}
|
|
||||||
header={renderHeader()}
|
header={renderHeader()}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
{ paddingHorizontal: 16, paddingBottom: 120 },
|
{ paddingHorizontal: 16, paddingBottom: 120 },
|
||||||
outfits.length === 0 ? { flexGrow: 1 } : undefined,
|
filteredOutfits.length === 0 ? { flexGrow: 1 } : undefined,
|
||||||
]}
|
]}
|
||||||
ListEmptyComponent={() => (
|
ListEmptyComponent={() => (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -228,18 +256,99 @@ export default function ClothingOutfitList() {
|
|||||||
title={loading ? "正在加载搭配" : "暂无搭配"}
|
title={loading ? "正在加载搭配" : "暂无搭配"}
|
||||||
description={loading ? "请稍候" : "创建第一套搭配后,可以在这里查看和编辑"}
|
description={loading ? "请稍候" : "创建第一套搭配后,可以在这里查看和编辑"}
|
||||||
center="screen"
|
center="screen"
|
||||||
footer={
|
|
||||||
!loading ? (
|
|
||||||
<Button className="mt-4" onPress={() => router.push("/clothes/outfit/edit" as any)}>
|
|
||||||
<SparklesIcon size={18} color="#fff" />
|
|
||||||
<Button.Label>新增搭配</Button.Label>
|
|
||||||
</Button>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<BottomSheet isOpen={showFilterSheet} onOpenChange={setShowFilterSheet}>
|
||||||
|
<BottomSheet.Portal>
|
||||||
|
<BottomSheet.Overlay />
|
||||||
|
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
||||||
|
<View className="flex-row items-center">
|
||||||
|
<View className="flex-1" />
|
||||||
|
<Text className="text-center text-lg font-semibold text-gray-900">搜索搭配</Text>
|
||||||
|
<View className="flex-1 items-end">
|
||||||
|
<TouchableOpacity
|
||||||
|
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
||||||
|
onPress={() => { setFilterCategoryId(null); setFilterTagIds([]); setKeyword(""); }}
|
||||||
|
>
|
||||||
|
<RotateCcwIcon size={14} color="#4B5563" />
|
||||||
|
<Text className="text-xs text-gray-600">重置</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TextField>
|
||||||
|
<Label className="mb-2">关键词</Label>
|
||||||
|
<BottomSheet.Input
|
||||||
|
value={keyword}
|
||||||
|
onChangeText={setKeyword}
|
||||||
|
placeholder="搜索搭配名称"
|
||||||
|
returnKeyType="search"
|
||||||
|
/>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Label className="mb-2">分类</Label>
|
||||||
|
<View className="flex-row flex-wrap gap-2">
|
||||||
|
<TouchableOpacity
|
||||||
|
className={`rounded-full border px-3 py-2 ${filterCategoryId == null ? "border-gray-900 bg-gray-900" : "border-gray-200 bg-white"}`}
|
||||||
|
onPress={() => setFilterCategoryId(null)}
|
||||||
|
>
|
||||||
|
<Text className={`text-xs ${filterCategoryId == null ? "text-white" : "text-gray-700"}`}>全部</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{categories.filter(c => c.type === ClothingTargetType.Outfit).map(c => {
|
||||||
|
const selected = filterCategoryId === c.id;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={c.id}
|
||||||
|
className={`rounded-full border px-3 py-2 ${selected ? "border-gray-900 bg-gray-900" : "border-gray-200 bg-white"}`}
|
||||||
|
onPress={() => setFilterCategoryId(selected ? null : c.id)}
|
||||||
|
>
|
||||||
|
<Text className={`text-xs ${selected ? "text-white" : "text-gray-700"}`}>{c.name}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Label className="mb-2">标签</Label>
|
||||||
|
{tags.filter(t => t.type === ClothingTargetType.Outfit).length > 0 ? (
|
||||||
|
<View className="flex-row flex-wrap gap-2">
|
||||||
|
{tags.filter(t => t.type === ClothingTargetType.Outfit).map(t => {
|
||||||
|
const selected = filterTagIds.includes(t.id);
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={t.id}
|
||||||
|
className={`rounded-full border px-3 py-2 ${selected ? "border-gray-900 bg-gray-900" : "border-gray-200 bg-white"}`}
|
||||||
|
onPress={() => setFilterTagIds(prev => selected ? prev.filter(id => id !== t.id) : [...prev, t.id])}
|
||||||
|
>
|
||||||
|
<Text className={`text-xs ${selected ? "text-white" : "text-gray-700"}`}>{t.name}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<Text className="text-sm text-gray-500">暂无标签</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button className="w-full" onPress={() => setShowFilterSheet(false)}>
|
||||||
|
<Button.Label>确定</Button.Label>
|
||||||
|
</Button>
|
||||||
|
</BottomSheet.Content>
|
||||||
|
</BottomSheet.Portal>
|
||||||
|
</BottomSheet>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
className="absolute bottom-20 right-5 w-14 h-14 rounded-full bg-accent items-center justify-center shadow-lg z-50"
|
||||||
|
activeOpacity={0.85}
|
||||||
|
onPress={() => router.push("/clothes/outfit/edit" as any)}
|
||||||
|
>
|
||||||
|
<PlusIcon size={24} color="#fff" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
visible={!!activeItem}
|
visible={!!activeItem}
|
||||||
transparent
|
transparent
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { Button, Card, Input, Label, TextArea, TextField } from "heroui-native";
|
|||||||
import {
|
import {
|
||||||
BringToFrontIcon,
|
BringToFrontIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Maximize2Icon,
|
|
||||||
Minimize2Icon,
|
|
||||||
MinusIcon,
|
MinusIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
@@ -174,7 +172,6 @@ export default function ClothingOutfitEdit() {
|
|||||||
const [pageLoading, setPageLoading] = useState(true);
|
const [pageLoading, setPageLoading] = useState(true);
|
||||||
const [capturingCover, setCapturingCover] = useState(false);
|
const [capturingCover, setCapturingCover] = useState(false);
|
||||||
const [showClothesSheet, setShowClothesSheet] = useState(false);
|
const [showClothesSheet, setShowClothesSheet] = useState(false);
|
||||||
const [canvasFullscreen, setCanvasFullscreen] = useState(false);
|
|
||||||
|
|
||||||
const clothesById = useMemo(() => new Map(clothes.map((item) => [item.id, item])), [clothes]);
|
const clothesById = useMemo(() => new Map(clothes.map((item) => [item.id, item])), [clothes]);
|
||||||
const outfitCategories = useMemo(
|
const outfitCategories = useMemo(
|
||||||
@@ -620,7 +617,7 @@ export default function ClothingOutfitEdit() {
|
|||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
className="w-full flex-1"
|
className="w-full flex-1"
|
||||||
style={{ aspectRatio: canvasFullscreen ? undefined : 1 }}
|
style={{ aspectRatio: 3 / 4 }}
|
||||||
onLayout={(event) => setCanvasSize(event.nativeEvent.layout.width)}
|
onLayout={(event) => setCanvasSize(event.nativeEvent.layout.width)}
|
||||||
>
|
>
|
||||||
{items.length === 0 && (
|
{items.length === 0 && (
|
||||||
@@ -715,18 +712,12 @@ export default function ClothingOutfitEdit() {
|
|||||||
<Button variant="danger-soft" size="sm" isDisabled={!selectedItem} onPress={handleDeleteSelected}>
|
<Button variant="danger-soft" size="sm" isDisabled={!selectedItem} onPress={handleDeleteSelected}>
|
||||||
<Trash2Icon size={16} color="#EF4444" />
|
<Trash2Icon size={16} color="#EF4444" />
|
||||||
</Button>
|
</Button>
|
||||||
<View className="flex-1" />
|
|
||||||
<Button variant="secondary" size="sm" onPress={() => setCanvasFullscreen((v) => !v)}>
|
|
||||||
{canvasFullscreen ? <Minimize2Icon size={16} color="#374151" /> : <Maximize2Icon size={16} color="#374151" />}
|
|
||||||
</Button>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 画布 */}
|
{/* 画布 */}
|
||||||
{!canvasFullscreen && (
|
|
||||||
<Card className="mb-4 overflow-hidden p-0">
|
<Card className="mb-4 overflow-hidden p-0">
|
||||||
{canvasContent}
|
{canvasContent}
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
|
||||||
|
|
||||||
<Button className="mb-4" variant="secondary" onPress={() => setShowClothesSheet(true)}>
|
<Button className="mb-4" variant="secondary" onPress={() => setShowClothesSheet(true)}>
|
||||||
<PlusIcon size={18} color="#374151" />
|
<PlusIcon size={18} color="#374151" />
|
||||||
@@ -779,25 +770,6 @@ export default function ClothingOutfitEdit() {
|
|||||||
</BottomSheet.Portal>
|
</BottomSheet.Portal>
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
|
|
||||||
{canvasFullscreen && (
|
|
||||||
<View
|
|
||||||
className="absolute inset-0 bg-black z-50 items-center justify-center"
|
|
||||||
style={{ paddingTop: 0 }}
|
|
||||||
>
|
|
||||||
<View className="absolute top-0 left-0 right-0 flex-row justify-end px-4 py-3 z-10">
|
|
||||||
<TouchableOpacity
|
|
||||||
className="w-10 h-10 rounded-full bg-white/20 items-center justify-center"
|
|
||||||
onPress={() => setCanvasFullscreen(false)}
|
|
||||||
>
|
|
||||||
<Minimize2Icon size={20} color="#fff" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
<View className="w-full" style={{ aspectRatio: 1, maxHeight: "90%" } as any}>
|
|
||||||
{canvasContent}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<LoadingOverlay visible={isLoading || pageLoading} />
|
<LoadingOverlay visible={isLoading || pageLoading} />
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Text, TouchableOpacity, View } from "react-native";
|
import { Dimensions, ScrollView, Text, TouchableOpacity, View } from "react-native";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
/* ============== 饼图颜色 ============== */
|
/* ============== 饼图颜色 ============== */
|
||||||
@@ -632,8 +632,10 @@ export default function FinanceAnalysis() {
|
|||||||
<BottomSheet.Portal>
|
<BottomSheet.Portal>
|
||||||
<BottomSheet.Overlay />
|
<BottomSheet.Overlay />
|
||||||
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
||||||
<View className="flex-row items-center justify-between">
|
<View className="flex-row items-center">
|
||||||
<Text className="text-lg font-semibold text-gray-900">筛选分析</Text>
|
<View className="flex-1" />
|
||||||
|
<Text className="text-center text-lg font-semibold text-gray-900">筛选分析</Text>
|
||||||
|
<View className="flex-1 items-end">
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
||||||
onPress={handleResetFilter}
|
onPress={handleResetFilter}
|
||||||
@@ -642,7 +644,13 @@ export default function FinanceAnalysis() {
|
|||||||
<Text className="text-xs text-gray-600">重置</Text>
|
<Text className="text-xs text-gray-600">重置</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
style={{ maxHeight: Dimensions.get("window").height * 0.55 }}
|
||||||
|
contentContainerStyle={{ gap: 16, paddingBottom: 8 }}
|
||||||
|
>
|
||||||
<View>
|
<View>
|
||||||
<Label className="mb-2">日期范围</Label>
|
<Label className="mb-2">日期范围</Label>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -738,7 +746,8 @@ export default function FinanceAnalysis() {
|
|||||||
|
|
||||||
<View>
|
<View>
|
||||||
<Label className="mb-2">分类</Label>
|
<Label className="mb-2">分类</Label>
|
||||||
<View className="flex-row flex-wrap gap-2">
|
{/* 全部 */}
|
||||||
|
<View className="flex-row flex-wrap gap-2 mb-2">
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className={`h-9 px-3 rounded-full items-center justify-center border ${
|
className={`h-9 px-3 rounded-full items-center justify-center border ${
|
||||||
draftFilter.categoryId == null
|
draftFilter.categoryId == null
|
||||||
@@ -754,7 +763,48 @@ export default function FinanceAnalysis() {
|
|||||||
全部
|
全部
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{categories.map((cat) => {
|
</View>
|
||||||
|
{/* 支出 */}
|
||||||
|
<Text className="text-xs text-gray-400 mb-1.5 ml-0.5">支出</Text>
|
||||||
|
<View className="flex-row flex-wrap gap-2 mb-3">
|
||||||
|
{categories
|
||||||
|
.filter((c) => c.type === FinanceType.Expense)
|
||||||
|
.map((cat) => {
|
||||||
|
const selected = draftFilter.categoryId === cat.id;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={cat.id}
|
||||||
|
className={`h-9 px-3 rounded-full flex-row items-center gap-1.5 border ${
|
||||||
|
selected ? "bg-accent/10 border-accent" : "bg-white border-gray-200"
|
||||||
|
}`}
|
||||||
|
activeOpacity={0.75}
|
||||||
|
onPress={() =>
|
||||||
|
setDraftFilter((prev) => ({
|
||||||
|
...prev,
|
||||||
|
categoryId: prev.categoryId === cat.id ? null : cat.id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FinanceCategoryIcon
|
||||||
|
fileKey={cat.icon_file_key}
|
||||||
|
label={cat.name}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className={`text-sm ${selected ? "text-accent font-semibold" : "text-gray-700"}`}
|
||||||
|
>
|
||||||
|
{cat.name}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
{/* 收入 */}
|
||||||
|
<Text className="text-xs text-gray-400 mb-1.5 ml-0.5">收入</Text>
|
||||||
|
<View className="flex-row flex-wrap gap-2">
|
||||||
|
{categories
|
||||||
|
.filter((c) => c.type === FinanceType.Income)
|
||||||
|
.map((cat) => {
|
||||||
const selected = draftFilter.categoryId === cat.id;
|
const selected = draftFilter.categoryId === cat.id;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -795,8 +845,9 @@ export default function FinanceAnalysis() {
|
|||||||
returnKeyType="search"
|
returnKeyType="search"
|
||||||
/>
|
/>
|
||||||
</TextField>
|
</TextField>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<View className="flex-row gap-3 pt-2">
|
<View className="flex-row gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Image, Modal, Pressable, ScrollView, Text, TouchableOpacity, View } from "react-native";
|
import { Dimensions, Image, Modal, Pressable, ScrollView, Text, TouchableOpacity, View } from "react-native";
|
||||||
import { Button, Label, TextField } from "heroui-native";
|
import { Button, Label, TextField } from "heroui-native";
|
||||||
import BottomSheet from "@/components/BottomSheet";
|
import BottomSheet from "@/components/BottomSheet";
|
||||||
import { router, useFocusEffect } from "expo-router";
|
import { router, useFocusEffect } from "expo-router";
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
FilterIcon,
|
FilterIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
RotateCcwIcon,
|
RotateCcwIcon,
|
||||||
SearchIcon,
|
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -427,33 +426,6 @@ export default function Home() {
|
|||||||
[userId],
|
[userId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderCategoryOption = useCallback(
|
|
||||||
(item: FinanceCategory) => {
|
|
||||||
const selected = draftFilter.categoryId === item.id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={item.id}
|
|
||||||
className={`w-[31%] rounded-xl px-2 py-3 items-center border ${
|
|
||||||
selected ? "border-gray-900 bg-gray-100" : "border-gray-100 bg-white"
|
|
||||||
}`}
|
|
||||||
activeOpacity={0.75}
|
|
||||||
onPress={() =>
|
|
||||||
setDraftFilter((prev) => ({
|
|
||||||
...prev,
|
|
||||||
categoryId: prev.categoryId === item.id ? null : item.id,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FinanceCategoryIcon fileKey={item.icon_file_key} label={item.name} size={28} />
|
|
||||||
<Text className="mt-1.5 text-xs text-gray-700" numberOfLines={1}>
|
|
||||||
{item.name}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[draftFilter.categoryId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutView>
|
<LayoutView>
|
||||||
@@ -485,8 +457,10 @@ export default function Home() {
|
|||||||
<BottomSheet.Portal>
|
<BottomSheet.Portal>
|
||||||
<BottomSheet.Overlay />
|
<BottomSheet.Overlay />
|
||||||
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
<BottomSheet.Content animateOnMount={false} className="gap-4">
|
||||||
<View className="flex-row items-center justify-between">
|
<View className="flex-row items-center">
|
||||||
<Text className="text-lg font-semibold text-gray-900">筛选记账</Text>
|
<View className="flex-1" />
|
||||||
|
<Text className="text-center text-lg font-semibold text-gray-900">筛选记账</Text>
|
||||||
|
<View className="flex-1 items-end">
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
className="h-9 px-3 rounded-full bg-gray-100 flex-row items-center gap-1"
|
||||||
onPress={handleResetFilter}
|
onPress={handleResetFilter}
|
||||||
@@ -495,7 +469,13 @@ export default function Home() {
|
|||||||
<Text className="text-xs text-gray-600">重置</Text>
|
<Text className="text-xs text-gray-600">重置</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
style={{ maxHeight: Dimensions.get("window").height * 0.55 }}
|
||||||
|
contentContainerStyle={{ gap: 16, paddingBottom: 8 }}
|
||||||
|
>
|
||||||
<View>
|
<View>
|
||||||
<Label className="mb-2">日期范围</Label>
|
<Label className="mb-2">日期范围</Label>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -522,36 +502,98 @@ export default function Home() {
|
|||||||
|
|
||||||
<View>
|
<View>
|
||||||
<Label className="mb-2">分类</Label>
|
<Label className="mb-2">分类</Label>
|
||||||
<ScrollView
|
{/* 全部 */}
|
||||||
style={{ maxHeight: 240 }}
|
<View className="flex-row flex-wrap gap-2 mb-2">
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
>
|
|
||||||
{/* 支出 */}
|
|
||||||
<Text className="text-xs text-gray-400 mb-1.5 ml-0.5">支出</Text>
|
|
||||||
<View className="flex-row flex-wrap gap-2 mb-3">
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
className={`w-[31%] rounded-xl px-2 py-3 items-center border ${
|
className={`h-9 px-3 rounded-full items-center justify-center border ${
|
||||||
draftFilter.categoryId == null
|
draftFilter.categoryId == null
|
||||||
? "border-gray-900 bg-gray-100"
|
? "bg-accent/10 border-accent"
|
||||||
: "border-gray-100 bg-white"
|
: "bg-white border-gray-200"
|
||||||
}`}
|
}`}
|
||||||
activeOpacity={0.75}
|
activeOpacity={0.75}
|
||||||
onPress={() => setDraftFilter((prev) => ({ ...prev, categoryId: null }))}
|
onPress={() => setDraftFilter((prev) => ({ ...prev, categoryId: null }))}
|
||||||
>
|
>
|
||||||
<SearchIcon size={24} color="#6B7280" />
|
<Text
|
||||||
<Text className="mt-1.5 text-xs text-gray-700">全部</Text>
|
className={`text-sm ${draftFilter.categoryId == null ? "text-accent font-semibold" : "text-gray-700"}`}
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{categories.filter(c => c.type === FinanceType.Expense).map(renderCategoryOption)}
|
</View>
|
||||||
|
{/* 支出 */}
|
||||||
|
<Text className="text-xs text-gray-400 mb-1.5 ml-0.5">支出</Text>
|
||||||
|
<View className="flex-row flex-wrap gap-2 mb-3">
|
||||||
|
{categories
|
||||||
|
.filter((c) => c.type === FinanceType.Expense)
|
||||||
|
.map((cat) => {
|
||||||
|
const selected = draftFilter.categoryId === cat.id;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={cat.id}
|
||||||
|
className={`h-9 px-3 rounded-full flex-row items-center gap-1.5 border ${
|
||||||
|
selected ? "bg-accent/10 border-accent" : "bg-white border-gray-200"
|
||||||
|
}`}
|
||||||
|
activeOpacity={0.75}
|
||||||
|
onPress={() =>
|
||||||
|
setDraftFilter((prev) => ({
|
||||||
|
...prev,
|
||||||
|
categoryId: prev.categoryId === cat.id ? null : cat.id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FinanceCategoryIcon
|
||||||
|
fileKey={cat.icon_file_key}
|
||||||
|
label={cat.name}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className={`text-sm ${selected ? "text-accent font-semibold" : "text-gray-700"}`}
|
||||||
|
>
|
||||||
|
{cat.name}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
{/* 收入 */}
|
{/* 收入 */}
|
||||||
<Text className="text-xs text-gray-400 mb-1.5 ml-0.5">收入</Text>
|
<Text className="text-xs text-gray-400 mb-1.5 ml-0.5">收入</Text>
|
||||||
<View className="flex-row flex-wrap gap-2">
|
<View className="flex-row flex-wrap gap-2">
|
||||||
{categories.filter(c => c.type === FinanceType.Income).map(renderCategoryOption)}
|
{categories
|
||||||
|
.filter((c) => c.type === FinanceType.Income)
|
||||||
|
.map((cat) => {
|
||||||
|
const selected = draftFilter.categoryId === cat.id;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={cat.id}
|
||||||
|
className={`h-9 px-3 rounded-full flex-row items-center gap-1.5 border ${
|
||||||
|
selected ? "bg-accent/10 border-accent" : "bg-white border-gray-200"
|
||||||
|
}`}
|
||||||
|
activeOpacity={0.75}
|
||||||
|
onPress={() =>
|
||||||
|
setDraftFilter((prev) => ({
|
||||||
|
...prev,
|
||||||
|
categoryId: prev.categoryId === cat.id ? null : cat.id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FinanceCategoryIcon
|
||||||
|
fileKey={cat.icon_file_key}
|
||||||
|
label={cat.name}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className={`text-sm ${selected ? "text-accent font-semibold" : "text-gray-700"}`}
|
||||||
|
>
|
||||||
|
{cat.name}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="flex-row gap-3 pt-2">
|
<View className="flex-row gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
|||||||
@@ -148,15 +148,15 @@ function AppBottomSheetRoot({
|
|||||||
const adjustForKeyboard = useCallback((kbHeight: number) => {
|
const adjustForKeyboard = useCallback((kbHeight: number) => {
|
||||||
const input = focusedInputRef.current;
|
const input = focusedInputRef.current;
|
||||||
if (!input) {
|
if (!input) {
|
||||||
keyboardY.setValue(-kbHeight);
|
keyboardY.setValue(-Math.min(kbHeight, 0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
input.measureInWindow((x, y, w, h) => {
|
input.measureInWindow((x, y, w, h) => {
|
||||||
const screenBottom = SCREEN_H - kbHeight;
|
const screenBottom = SCREEN_H - kbHeight;
|
||||||
const inputBottom = y + h;
|
const inputBottom = y + h;
|
||||||
const needed = inputBottom - screenBottom - 18;
|
const needed = inputBottom - screenBottom;
|
||||||
keyboardY.setValue(needed > 0 ? -needed : 0);
|
keyboardY.setValue(needed > 0 ? -(needed - 16) : 0);
|
||||||
});
|
});
|
||||||
}, 50);
|
}, 50);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -279,7 +279,7 @@ const AppBottomSheetInput = forwardRef<TextInput, ComponentProps<typeof Input>>(
|
|||||||
);
|
);
|
||||||
AppBottomSheetInput.displayName = "AppBottomSheetInput";
|
AppBottomSheetInput.displayName = "AppBottomSheetInput";
|
||||||
|
|
||||||
function AppBottomSheetTitle(props: TextProps) { return <Text {...props} />; }
|
function AppBottomSheetTitle(props: TextProps) { return <Text className="text-center text-base font-semibold" {...props} />; }
|
||||||
function AppBottomSheetDescription(props: TextProps) { return <Text {...props} />; }
|
function AppBottomSheetDescription(props: TextProps) { return <Text {...props} />; }
|
||||||
|
|
||||||
const BottomSheet = Object.assign(AppBottomSheetRoot, {
|
const BottomSheet = Object.assign(AppBottomSheetRoot, {
|
||||||
|
|||||||
+255
@@ -0,0 +1,255 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>稳妥</title>
|
||||||
|
<style>
|
||||||
|
/* From extension vscode.github */
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.vscode-dark img[src$=\#gh-light-mode-only],
|
||||||
|
.vscode-light img[src$=\#gh-dark-mode-only],
|
||||||
|
.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only],
|
||||||
|
.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.task-list-item {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-item-checkbox {
|
||||||
|
margin-left: -20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--color-note: #0969da;
|
||||||
|
--color-tip: #1a7f37;
|
||||||
|
--color-warning: #9a6700;
|
||||||
|
--color-severe: #bc4c00;
|
||||||
|
--color-caution: #d1242f;
|
||||||
|
--color-important: #8250df;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-note: #2f81f7;
|
||||||
|
--color-tip: #3fb950;
|
||||||
|
--color-warning: #d29922;
|
||||||
|
--color-severe: #db6d28;
|
||||||
|
--color-caution: #f85149;
|
||||||
|
--color-important: #a371f7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.markdown-alert {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: inherit;
|
||||||
|
border-left: .25em solid #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert>:first-child {
|
||||||
|
margin-top: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert>:last-child {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert .markdown-alert-title {
|
||||||
|
display: flex;
|
||||||
|
font-weight: 500;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert .markdown-alert-title .octicon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: visible !important;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-note {
|
||||||
|
border-left-color: var(--color-note);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-note .markdown-alert-title {
|
||||||
|
color: var(--color-note);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-important {
|
||||||
|
border-left-color: var(--color-important);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-important .markdown-alert-title {
|
||||||
|
color: var(--color-important);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-warning {
|
||||||
|
border-left-color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-warning .markdown-alert-title {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-tip {
|
||||||
|
border-left-color: var(--color-tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-tip .markdown-alert-title {
|
||||||
|
color: var(--color-tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-caution {
|
||||||
|
border-left-color: var(--color-caution);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-alert.markdown-alert-caution .markdown-alert-title {
|
||||||
|
color: var(--color-caution);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="vscode-body vscode-light">
|
||||||
|
<span id="markdown-mermaid" aria-hidden="true" data-config="{"darkModeTheme":"vscode","lightModeTheme":"vscode","maxTextSize":50000,"clickDrag":"alt","showControls":"onHoverOrFocus","resizable":true,"maxHeight":""}"></span>
|
||||||
|
<h2 id="稳妥">稳妥</h2>
|
||||||
|
<h3 id="郑州轻工业大学-一本">郑州轻工业大学 (一本)</h3>
|
||||||
|
<h4 id="123学费较高">123(学费较高)</h4>
|
||||||
|
<h5 id="专业">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701093108050.png" alt="image-20260701093108050"></p>
|
||||||
|
<h5 id="分数线">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701093425247.png" alt="image-20260701093425247"></p>
|
||||||
|
<h4 id="101">101</h4>
|
||||||
|
<h5 id="专业-1">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701093325640.png" alt="image-20260701093325640"></p>
|
||||||
|
<h5 id="分数线-1">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701093231929.png" alt="image-20260701093231929"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701093241772.png" alt="image-20260701093241772"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701093248127.png" alt="image-20260701093248127"></p>
|
||||||
|
<h3 id="郑州航空工业管理学院二本">郑州航空工业管理学院(二本)</h3>
|
||||||
|
<h4 id="102可以冲一下">102(可以冲一下)</h4>
|
||||||
|
<h5 id="专业-2">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701094121933.png" alt="image-20260701094121933"></p>
|
||||||
|
<h5 id="分数线-2">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701094055711.png" alt="image-20260701094055711"></p>
|
||||||
|
<h4 id="103但是有个数学">103(但是有个数学)</h4>
|
||||||
|
<h5 id="专业-3">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701094202954.png" alt="image-20260701094202954"></p>
|
||||||
|
<h5 id="分数线-3">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701094233722.png" alt="image-20260701094233722"></p>
|
||||||
|
<h3 id="河南理工大学">河南理工大学</h3>
|
||||||
|
<h4 id="100">100</h4>
|
||||||
|
<h5 id="专业比较多还包括自动化">专业(比较多,还包括自动化)</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101335516.png" alt="image-20260701101335516"></p>
|
||||||
|
<h5 id="分数线-4">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101415448.png" alt="image-20260701101415448"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101409043.png" alt="image-20260701101409043"></p>
|
||||||
|
<h4 id="102">102</h4>
|
||||||
|
<h5 id="专业-4">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101549185.png" alt="image-20260701101549185"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101605409.png" alt="image-20260701101605409"></p>
|
||||||
|
<h5 id="分数线-5">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101512967.png" alt="image-20260701101512967"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701101534213.png" alt="image-20260701101534213"></p>
|
||||||
|
<h4 id="103-104-测绘--材料专业">103 104 测绘 / 材料专业</h4>
|
||||||
|
<h3 id="河南科技大学">河南科技大学</h3>
|
||||||
|
<h4 id="101机械和自动化类">101(机械和自动化类)</h4>
|
||||||
|
<h5 id="专业-5">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103052528.png" alt="image-20260701103052528"></p>
|
||||||
|
<h5 id="分数线-6">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103032586.png" alt="image-20260701103032586"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103039388.png" alt="image-20260701103039388"></p>
|
||||||
|
<h4 id="104学费较高">104(学费较高)</h4>
|
||||||
|
<h5 id="专业-6">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701102907636.png" alt="image-20260701102907636"></p>
|
||||||
|
<h5 id="分数线-7">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701102921490.png" alt="image-20260701102921490"></p>
|
||||||
|
<h3 id="河南中医药大学">河南中医药大学</h3>
|
||||||
|
<h4 id="108">108</h4>
|
||||||
|
<h5 id="专业-7">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103353866.png" alt="image-20260701103353866"></p>
|
||||||
|
<h5 id="分数线-8">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103340349.png" alt="image-20260701103340349"></p>
|
||||||
|
<h3 id="河南师范大学">河南师范大学</h3>
|
||||||
|
<h4 id="245">245</h4>
|
||||||
|
<h5 id="专业学费较高">专业(学费较高)</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103844547.png" alt="image-20260701103844547"></p>
|
||||||
|
<h5 id="分数线没找到">分数线(没找到)</h5>
|
||||||
|
<h4 id="255">255</h4>
|
||||||
|
<h5 id="专业学费较高-1">专业(学费较高)</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701103929167.png" alt="image-20260701103929167"></p>
|
||||||
|
<h5 id="分数线没找到-1">分数线(没找到)</h5>
|
||||||
|
<h3 id="河南工业大学">河南工业大学</h3>
|
||||||
|
<h4 id="105">105</h4>
|
||||||
|
<h5 id="专业学费较高-2">专业(学费较高)</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701104928407.png" alt="image-20260701104928407"></p>
|
||||||
|
<h5 id="分数线没找到-2">分数线(没找到)</h5>
|
||||||
|
<h3 id="河南财经政法大学">河南财经政法大学</h3>
|
||||||
|
<h4 id="104">104</h4>
|
||||||
|
<h5 id="专业包含个数学专业">专业(包含个数学专业)</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135237856.png" alt="image-20260701135237856"></p>
|
||||||
|
<h5 id="分数线-9">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135219577.png" alt="image-20260701135219577"></p>
|
||||||
|
<h3 id="天津城建大学">天津城建大学</h3>
|
||||||
|
<h4 id="124">124</h4>
|
||||||
|
<h5 id="专业-8">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135440277.png" alt="image-20260701135440277"></p>
|
||||||
|
<h5 id="分数线-10">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135515298.png" alt="image-20260701135515298"></p>
|
||||||
|
<h4 id="901">901</h4>
|
||||||
|
<h5 id="专业-9">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135447970.png" alt="image-20260701135447970"></p>
|
||||||
|
<h5 id="分数线-11">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135526139.png" alt="image-20260701135526139"></p>
|
||||||
|
<h3 id="太原工业学院">太原工业学院</h3>
|
||||||
|
<h4 id="105106">105、106</h4>
|
||||||
|
<h5 id="专业-10">专业</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135918595.png" alt="image-20260701135918595"></p>
|
||||||
|
<h5 id="分数线-12">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135933254.png" alt="image-20260701135933254"></p>
|
||||||
|
<h3 id="烟台大学">烟台大学</h3>
|
||||||
|
<h4 id="103">103</h4>
|
||||||
|
<h5 id="专业专业很多">专业,专业很多</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701140201585.png" alt="image-20260701140201585"></p>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701140217740.png" alt="image-20260701140217740"></p>
|
||||||
|
<h5 id="分数线-13">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701140234770.png" alt="image-20260701140234770"></p>
|
||||||
|
<h3 id="应急管理大学-102-103">应急管理大学 102 103</h3>
|
||||||
|
<h5 id="可以冲">可以冲</h5>
|
||||||
|
<h3 id="学校">学校</h3>
|
||||||
|
<h4 id="100-1">100</h4>
|
||||||
|
<h5 id="专业-11">专业</h5>
|
||||||
|
<h5 id="分数线-14">分数线</h5>
|
||||||
|
<p><img src="file:///d:\workspace\志愿\images\image-20260701135041125.png" alt="image-20260701135041125"></p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user