From 0307ed0a007952293bfad0f1f47f2c360348b1a2 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Mon, 14 Jul 2025 19:18:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=8E=B7=E5=8F=96=E6=80=BB=E6=95=B0?= =?UTF-8?q?=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/user-message.tsx | 1 + components/file-upload/getTotal.tsx | 333 +++++++++++++++++++++++++++ components/user-message.tsx/look.tsx | 2 + 3 files changed, 336 insertions(+) create mode 100644 components/file-upload/getTotal.tsx diff --git a/app/(tabs)/user-message.tsx b/app/(tabs)/user-message.tsx index 9494a85..30f72f1 100644 --- a/app/(tabs)/user-message.tsx +++ b/app/(tabs)/user-message.tsx @@ -43,6 +43,7 @@ export default function UserMessage() { }; useEffect(() => { getUserInfo(); + setSteps("userName") }, []); return ( diff --git a/components/file-upload/getTotal.tsx b/components/file-upload/getTotal.tsx new file mode 100644 index 0000000..b5af839 --- /dev/null +++ b/components/file-upload/getTotal.tsx @@ -0,0 +1,333 @@ +import * as MediaLibrary from 'expo-media-library'; +import React, { useState } from 'react'; +import { ActivityIndicator, Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +interface MediaStats { + total: number; + photos: number; + videos: number; + audios: number; + others: number; + byMonth: Record; +} + +type TimeRange = 'day' | 'week' | 'month' | 'all'; + +const MediaStatsScreen = () => { + const [isLoading, setIsLoading] = useState(false); + const [stats, setStats] = useState(null); + const [timeRange, setTimeRange] = useState('week'); // 默认显示一周 + + const getDateRange = (range: TimeRange) => { + const now = new Date(); + const start = new Date(now); + + switch (range) { + case 'day': + start.setDate(now.getDate() - 1); + break; + case 'week': + start.setDate(now.getDate() - 7); + break; + case 'month': + start.setMonth(now.getMonth() - 1); + break; + case 'all': + default: + return null; // 返回 null 表示不限制时间范围 + } + + return { start, end: now }; + }; + + const getMediaStatistics = async () => { + setIsLoading(true); + try { + // 1. 请求媒体库权限 + const { status } = await MediaLibrary.requestPermissionsAsync(); + if (status !== 'granted') { + Alert.alert('权限被拒绝', '需要访问媒体库权限来获取统计信息'); + return; + } + + // 2. 设置时间范围 + const dateRange = getDateRange(timeRange); + const createdAfter = dateRange ? Math.floor(dateRange.start.getTime() / 1000) : 0; + const endTime = dateRange?.end ? Math.floor(dateRange.end.getTime() / 1000) : undefined; + + // 3. 分页获取媒体资源,每次10条 + let hasNextPage = true; + let after = undefined; + let allAssets: MediaLibrary.Asset[] = []; + const pageSize = 10; // 每次获取10条 + + while (hasNextPage) { + const media = await MediaLibrary.getAssetsAsync({ + first: pageSize, + after, + mediaType: ['photo', 'video', 'audio', 'unknown'], + sortBy: 'creationTime', // 按创建时间降序,最新的在前面 + createdAfter: Date.now() - 24 * 30 * 12 * 60 * 60 * 1000, // 时间戳(毫秒) + createdBefore: Date.now(), // 时间戳(毫秒) + }); + + // 如果没有数据,直接退出 + if (media.assets.length === 0) { + break; + } + + // 检查每条记录是否在时间范围内 + for (const asset of media.assets) { + const assetTime = asset.creationTime ? new Date(asset.creationTime).getTime() / 1000 : 0; + + // 如果设置了结束时间,并且当前记录的时间早于开始时间,则停止 + if (endTime && assetTime > endTime) { + continue; // 跳过这条记录 + } + + // 如果设置了开始时间,并且当前记录的时间早于开始时间,则停止 + if (createdAfter && assetTime < createdAfter) { + hasNextPage = false; + break; + } + + allAssets.push(asset); + } + + // 更新游标和是否还有下一页 + hasNextPage = media.hasNextPage && media.assets.length === pageSize; + after = media.endCursor; + + // 如果没有更多数据或者已经获取了足够的数据 + if (!hasNextPage || allAssets.length >= 1000) { + break; + } + } + + console.log(`总共获取到 ${allAssets.length} 个媒体文件`); + + // 4. 统计不同类型媒体的数量 + const stats: MediaStats = { + total: allAssets.length, + photos: 0, + videos: 0, + audios: 0, + others: 0, + byMonth: {}, + }; + + allAssets.forEach(asset => { + // 统计类型 + if (asset.mediaType === 'photo') stats.photos++; + else if (asset.mediaType === 'video') stats.videos++; + else if (asset.mediaType === 'audio') stats.audios++; + else stats.others++; + + // 按月份统计 + if (asset.creationTime) { + const date = new Date(asset.creationTime); + const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + stats.byMonth[monthKey] = (stats.byMonth[monthKey] || 0) + 1; + } + }); + + setStats(stats); + } catch (error) { + console.error('获取媒体库统计信息失败:', error); + Alert.alert('错误', '获取媒体库统计信息失败'); + } finally { + setIsLoading(false); + } + }; + + // 时间范围选择器 + const TimeRangeSelector = () => ( + + {(['day', 'week', 'month', 'all'] as TimeRange[]).map((range) => ( + { + setTimeRange(range); + }} + disabled={isLoading} + > + + {{ + day: '今天', + week: '本周', + month: '本月', + all: '全部' + }[range]} + + + ))} + + ); + + return ( + + + 媒体库统计 + + + + + + {isLoading ? ( + + ) : ( + 获取媒体库统计 + )} + + + {stats && ( + + + + + + + + + + + + + 按月统计 + {Object.entries(stats.byMonth) + .sort(([a], [b]) => b.localeCompare(a)) + .map(([month, count]) => ( + + {month} + {count} 个文件 + + ))} + + + )} + + ); +}; + +const StatItem = ({ label, value }: { label: string; value: string }) => ( + + {value} + {label} + +); + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#fff', + padding: 16, + }, + header: { + marginBottom: 16, + alignItems: 'center', + }, + title: { + fontSize: 20, + fontWeight: 'bold', + color: '#333', + }, + timeRangeContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 16, + paddingHorizontal: 8, + }, + timeRangeButton: { + paddingVertical: 8, + paddingHorizontal: 16, + borderRadius: 16, + backgroundColor: '#f0f0f0', + }, + timeRangeButtonActive: { + backgroundColor: '#007AFF', + }, + timeRangeButtonText: { + color: '#666', + fontSize: 14, + }, + timeRangeButtonTextActive: { + color: '#fff', + fontWeight: '600', + }, + button: { + backgroundColor: '#007AFF', + paddingVertical: 12, + borderRadius: 8, + alignItems: 'center', + marginBottom: 20, + }, + buttonDisabled: { + opacity: 0.6, + }, + buttonText: { + color: '#fff', + fontSize: 16, + fontWeight: '600', + }, + statsContainer: { + backgroundColor: '#f8f8f8', + borderRadius: 12, + padding: 16, + }, + statsRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 16, + }, + statItem: { + alignItems: 'center', + flex: 1, + }, + statValue: { + fontSize: 20, + fontWeight: 'bold', + color: '#007AFF', + marginBottom: 4, + }, + statLabel: { + fontSize: 12, + color: '#666', + }, + monthlyContainer: { + marginTop: 16, + }, + sectionTitle: { + fontSize: 16, + fontWeight: '600', + color: '#333', + marginBottom: 12, + }, + monthlyItem: { + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: 10, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + monthText: { + fontSize: 14, + color: '#333', + }, + countText: { + fontSize: 14, + color: '#666', + }, +}); + +export default MediaStatsScreen; \ No newline at end of file diff --git a/components/user-message.tsx/look.tsx b/components/user-message.tsx/look.tsx index ffb4c46..7498d00 100644 --- a/components/user-message.tsx/look.tsx +++ b/components/user-message.tsx/look.tsx @@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'; import { ActivityIndicator, Image, TouchableOpacity, View } from 'react-native'; import AutoUploadScreen from '../file-upload/autoUploadScreen'; import FilesUploader from '../file-upload/files-uploader'; +import MediaStatsScreen from '../file-upload/getTotal'; interface Props { setSteps?: (steps: Steps) => void; @@ -64,6 +65,7 @@ export default function Look(props: Props) { } /> +