Files
frontend/src/components/business/ReportDialog/ReportDialog.tsx

335 lines
9.0 KiB
TypeScript
Raw Normal View History

/**
*
*
*/
import React, { useState, useCallback } from 'react';
import {
Modal,
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
TextInput,
ActivityIndicator,
Alert,
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useAppColors } from '../../../theme';
import { spacing, borderRadius, fontSizes } from '../../../theme';
import reportService, { ReportReason, ReportTargetType, REPORT_REASONS } from '../../../services/reportService';
export interface ReportDialogProps {
visible: boolean;
targetType: ReportTargetType;
targetId: string;
onClose: () => void;
onSuccess?: () => void;
}
// 目标类型中文名称映射
const TARGET_TYPE_LABELS: Record<ReportTargetType, string> = {
post: '帖子',
comment: '评论',
message: '消息',
};
export const ReportDialog: React.FC<ReportDialogProps> = ({
visible,
targetType,
targetId,
onClose,
onSuccess,
}) => {
const colors = useAppColors();
const styles = useStyles(colors);
const [selectedReason, setSelectedReason] = useState<ReportReason | null>(null);
const [description, setDescription] = useState('');
const [submitting, setSubmitting] = useState(false);
// 提交举报
const handleSubmit = useCallback(async () => {
if (!selectedReason) {
Alert.alert('提示', '请选择举报原因');
return;
}
setSubmitting(true);
try {
const result = await reportService.createReport(
targetType,
targetId,
selectedReason,
description.trim() || undefined
);
if (result) {
Alert.alert('举报成功', '感谢您的反馈,我们会尽快处理', [
{
text: '确定',
onPress: () => {
onClose();
onSuccess?.();
},
},
]);
} else {
Alert.alert('举报失败', '请稍后重试');
}
} catch (error) {
console.error('Submit report error:', error);
Alert.alert('举报失败', '网络错误,请稍后重试');
} finally {
setSubmitting(false);
}
}, [selectedReason, description, targetType, targetId, onClose, onSuccess]);
// 重置状态
const handleClose = useCallback(() => {
setSelectedReason(null);
setDescription('');
onClose();
}, [onClose]);
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={handleClose}
>
<View style={styles.overlay}>
<View style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>
{TARGET_TYPE_LABELS[targetType]}
</Text>
<TouchableOpacity
style={styles.closeButton}
onPress={handleClose}
disabled={submitting}
>
<MaterialCommunityIcons
name="close"
size={24}
color={colors.textSecondary}
/>
</TouchableOpacity>
</View>
{/* 内容 */}
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
{/* 举报原因选择 */}
<Text style={styles.sectionTitle}></Text>
<View style={styles.reasonList}>
{REPORT_REASONS.map((item) => (
<TouchableOpacity
key={item.value}
style={[
styles.reasonItem,
selectedReason === item.value && styles.reasonItemSelected,
]}
onPress={() => setSelectedReason(item.value)}
disabled={submitting}
>
<View style={styles.radioContainer}>
<View
style={[
styles.radio,
selectedReason === item.value && styles.radioSelected,
]}
>
{selectedReason === item.value && (
<View style={styles.radioDot} />
)}
</View>
<Text style={styles.reasonLabel}>{item.label}</Text>
</View>
</TouchableOpacity>
))}
</View>
{/* 补充说明 */}
<Text style={styles.sectionTitle}></Text>
<TextInput
style={styles.textInput}
placeholder="请提供更多详细信息..."
placeholderTextColor={colors.textTertiary}
multiline
maxLength={500}
value={description}
onChangeText={setDescription}
editable={!submitting}
/>
<Text style={styles.charCount}>{description.length}/500</Text>
</ScrollView>
{/* 底部按钮 */}
<View style={styles.footer}>
<TouchableOpacity
style={styles.cancelButton}
onPress={handleClose}
disabled={submitting}
>
<Text style={styles.cancelButtonText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.submitButton,
(!selectedReason || submitting) && styles.submitButtonDisabled,
]}
onPress={handleSubmit}
disabled={!selectedReason || submitting}
>
{submitting ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<Text style={styles.submitButtonText}></Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
);
};
// 创建样式
const useStyles = (colors: ReturnType<typeof useAppColors>) =>
StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
padding: spacing.lg,
},
container: {
backgroundColor: colors.backgroundPrimary,
borderRadius: borderRadius.lg,
width: '100%',
maxWidth: 400,
maxHeight: '80%',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: spacing.lg,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
title: {
fontSize: fontSizes.lg,
fontWeight: '600',
color: colors.textPrimary,
},
closeButton: {
padding: spacing.xs,
},
content: {
padding: spacing.lg,
maxHeight: 400,
},
sectionTitle: {
fontSize: fontSizes.md,
fontWeight: '500',
color: colors.textPrimary,
marginBottom: spacing.sm,
},
reasonList: {
marginBottom: spacing.lg,
},
reasonItem: {
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
reasonItemSelected: {
backgroundColor: colors.backgroundSecondary,
marginHorizontal: -spacing.sm,
paddingHorizontal: spacing.sm,
borderRadius: borderRadius.sm,
},
radioContainer: {
flexDirection: 'row',
alignItems: 'center',
},
radio: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: colors.border,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.sm,
},
radioSelected: {
borderColor: colors.primary,
},
radioDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: colors.primary,
},
reasonLabel: {
fontSize: fontSizes.md,
color: colors.textPrimary,
},
textInput: {
borderWidth: 1,
borderColor: colors.border,
borderRadius: borderRadius.md,
padding: spacing.sm,
fontSize: fontSizes.md,
color: colors.textPrimary,
minHeight: 100,
textAlignVertical: 'top',
},
charCount: {
fontSize: fontSizes.sm,
color: colors.textTertiary,
textAlign: 'right',
marginTop: spacing.xs,
},
footer: {
flexDirection: 'row',
padding: spacing.lg,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
gap: spacing.sm,
},
cancelButton: {
flex: 1,
paddingVertical: spacing.sm,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.border,
alignItems: 'center',
},
cancelButtonText: {
fontSize: fontSizes.md,
color: colors.textSecondary,
},
submitButton: {
flex: 1,
paddingVertical: spacing.sm,
borderRadius: borderRadius.md,
backgroundColor: colors.primary,
alignItems: 'center',
},
submitButtonDisabled: {
backgroundColor: colors.primary + '60',
},
submitButtonText: {
fontSize: fontSizes.md,
color: '#FFFFFF',
fontWeight: '500',
},
});
export default ReportDialog;