feat: 获取总数分类
This commit is contained in:
parent
86bdc7089b
commit
0307ed0a00
@ -43,6 +43,7 @@ export default function UserMessage() {
|
||||
};
|
||||
useEffect(() => {
|
||||
getUserInfo();
|
||||
setSteps("userName")
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
333
components/file-upload/getTotal.tsx
Normal file
333
components/file-upload/getTotal.tsx
Normal 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;
|
||||
@ -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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user