Files
Toolbox/apps/app_calendar_reminder/components/EventCard.tsx
T
yangxiangyuan 154b79c8f1 feat: 新增短链接工具,优化日历提醒功能
1. 新增完整短链接工具服务,包括前端页面、后端API与数据库支持
2. 新增移动端日历提醒工具与鉴权配置
3. 优化日历事件编辑与展示:
   - 添加事件循环重复设置
   - 新增全天事件支持
   - 时间选择器自动对齐15分钟间隔
   - 保存状态加载动画
   - 事件卡片显示重复规则
4. 更新API基础地址与路由配置
5. 新增SSL证书文件与相关工具静态资源
2026-07-28 16:23:37 +08:00

184 lines
4.7 KiB
TypeScript

import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { theme } from '../theme';
import { Event } from '../types';
import { safeParseDate } from '../utils/recurrence';
interface EventCardProps {
event: Event;
onEdit: (event: Event) => void;
onToggleComplete: (event: Event) => void;
onDelete: (event: Event) => void;
}
const WEEKDAY_NAMES = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
// 生成循环规则中文描述
const getRecurrenceLabel = (recurrence: string | null): string | null => {
if (!recurrence) return null;
const parts = recurrence.split(';');
const type = parts[0];
let interval = 1;
let by: number[] = [];
for (let i = 1; i < parts.length; i++) {
const [key, val] = parts[i].split('=');
if (key === 'interval') interval = parseInt(val) || 1;
if (key === 'by') by = val.split(',').map(Number);
}
if (type === 'DAILY') {
return interval === 1 ? '每天' : `每${interval}天`;
}
if (type === 'WEEKLY') {
const prefix = interval === 1 ? '每周' : `每${interval}周`;
if (by.length > 0) {
return `${prefix} ${by.map(d => WEEKDAY_NAMES[d] || '').join('、')}`;
}
return prefix;
}
return null;
};
export default function EventCard({
event,
onEdit,
onToggleComplete,
onDelete,
}: EventCardProps) {
const formatTime = (dateString: string) => {
const date = safeParseDate(dateString);
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
});
};
const recurrenceLabel = getRecurrenceLabel(event.recurrence);
return (
<TouchableOpacity
style={[styles.container, event.completed && styles.completedContainer]}
onPress={() => onEdit(event)}
activeOpacity={0.7}
>
<TouchableOpacity
style={styles.checkbox}
onPress={() => onToggleComplete(event)}
>
{event.completed ? (
<Ionicons name="checkmark-circle" size={28} color={theme.colors.primary} />
) : (
<View style={styles.checkboxEmpty} />
)}
</TouchableOpacity>
<View style={styles.content}>
<Text
style={[
styles.title,
event.completed && styles.completedText,
]}
numberOfLines={2}
>
{event.title}
</Text>
{event.content ? (
<Text
style={[
styles.contentText,
event.completed && styles.completedText,
]}
numberOfLines={2}
>
{event.content}
</Text>
) : null}
<View style={styles.timeContainer}>
<Ionicons
name="time-outline"
size={16}
color={theme.colors.textSecondary}
/>
<Text style={styles.timeText}>{formatTime(event.start)}</Text>
{recurrenceLabel && (
<>
<Ionicons
name="repeat-outline"
size={14}
color={theme.colors.primary}
style={{ marginLeft: 8 }}
/>
<Text style={styles.recurrenceText}>{recurrenceLabel}</Text>
</>
)}
</View>
</View>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => onDelete(event)}
>
<Ionicons name="trash-outline" size={20} color={theme.colors.error} />
</TouchableOpacity>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: theme.colors.surface,
borderRadius: theme.borderRadius.lg,
padding: theme.spacing.md,
marginBottom: theme.spacing.sm,
alignItems: 'center',
borderWidth: 1,
borderColor: theme.colors.surfaceVariant,
},
completedContainer: {
opacity: 0.6,
},
checkbox: {
marginRight: theme.spacing.sm,
},
checkboxEmpty: {
width: 28,
height: 28,
borderRadius: 14,
borderWidth: 2,
borderColor: theme.colors.textSecondary,
},
content: {
flex: 1,
},
title: {
...theme.typography.h3,
color: theme.colors.text,
marginBottom: theme.spacing.xs,
},
contentText: {
...theme.typography.bodySmall,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.xs,
},
completedText: {
textDecorationLine: 'line-through',
},
timeContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
timeText: {
...theme.typography.caption,
color: theme.colors.textSecondary,
},
recurrenceText: {
...theme.typography.caption,
color: theme.colors.primary,
},
deleteButton: {
padding: theme.spacing.sm,
},
});