feat: 获取总数分类

This commit is contained in:
jinyaqiu 2025-07-14 19:18:28 +08:00
parent 86bdc7089b
commit 0307ed0a00
3 changed files with 336 additions and 0 deletions

View File

@ -43,6 +43,7 @@ export default function UserMessage() {
};
useEffect(() => {
getUserInfo();
setSteps("userName")
}, []);
return (

View File

@ -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<string, number>;
}
type TimeRange = 'day' | 'week' | 'month' | 'all';
const MediaStatsScreen = () => {
const [isLoading, setIsLoading] = useState(false);
const [stats, setStats] = useState<MediaStats | null>(null);
const [timeRange, setTimeRange] = useState<TimeRange>('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 = () => (
<View style={styles.timeRangeContainer}>
{(['day', 'week', 'month', 'all'] as TimeRange[]).map((range) => (
<TouchableOpacity
key={range}
style={[
styles.timeRangeButton,
timeRange === range && styles.timeRangeButtonActive
]}
onPress={() => {
setTimeRange(range);
}}
disabled={isLoading}
>
<Text style={[
styles.timeRangeButtonText,
timeRange === range && styles.timeRangeButtonTextActive
]}>
{{
day: '今天',
week: '本周',
month: '本月',
all: '全部'
}[range]}
</Text>
</TouchableOpacity>
))}
</View>
);
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}></Text>
</View>
<TimeRangeSelector />
<TouchableOpacity
style={[styles.button, isLoading && styles.buttonDisabled]}
onPress={getMediaStatistics}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}></Text>
)}
</TouchableOpacity>
{stats && (
<View style={styles.statsContainer}>
<View style={styles.statsRow}>
<StatItem label="总文件数" value={stats.total.toString()} />
<StatItem label="照片" value={stats.photos.toString()} />
</View>
<View style={styles.statsRow}>
<StatItem label="视频" value={stats.videos.toString()} />
<StatItem label="音频" value={stats.audios.toString()} />
<StatItem label="其他" value={stats.others.toString()} />
</View>
<View style={styles.monthlyContainer}>
<Text style={styles.sectionTitle}></Text>
{Object.entries(stats.byMonth)
.sort(([a], [b]) => b.localeCompare(a))
.map(([month, count]) => (
<View key={month} style={styles.monthlyItem}>
<Text style={styles.monthText}>{month}</Text>
<Text style={styles.countText}>{count} </Text>
</View>
))}
</View>
</View>
)}
</View>
);
};
const StatItem = ({ label, value }: { label: string; value: string }) => (
<View style={styles.statItem}>
<Text style={styles.statValue}>{value}</Text>
<Text style={styles.statLabel}>{label}</Text>
</View>
);
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;

View File

@ -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) {
}
/>
<AutoUploadScreen />
<MediaStatsScreen />
</View>
<View className="w-full">