feat: 实现目标列表左滑删除功能及相关组件
- 在目标列表中添加左滑删除功能,用户可通过左滑手势显示删除按钮并确认删除目标 - 修改 GoalCard 组件,使用 Swipeable 组件包装卡片内容,支持删除操作 - 更新目标列表页面,集成删除目标的逻辑,确保与 Redux 状态管理一致 - 添加开发模式下的模拟数据,方便测试删除功能 - 更新相关文档,详细描述左滑删除功能的实现和使用方法
This commit is contained in:
@@ -4,7 +4,6 @@ import { useAppDispatch } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { completeTask, skipTask } from '@/store/tasksSlice';
|
||||
import { TaskListItem } from '@/types/goals';
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { Alert, Animated, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
@@ -27,7 +26,7 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
|
||||
// 当任务进度变化时,启动动画
|
||||
React.useEffect(() => {
|
||||
const targetProgress = task.progressPercentage > 0 ? Math.min(task.progressPercentage, 100) : 2;
|
||||
const targetProgress = task.progressPercentage > 0 ? Math.min(task.progressPercentage, 100) : 6;
|
||||
|
||||
Animated.timing(progressAnimation, {
|
||||
toValue: targetProgress,
|
||||
@@ -36,7 +35,6 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
}).start();
|
||||
}, [task.progressPercentage, progressAnimation]);
|
||||
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
@@ -179,51 +177,93 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, { backgroundColor: colorTokens.background }]}
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
backgroundColor: task.status === 'completed'
|
||||
? 'rgba(248, 250, 252, 0.8)' // 已完成任务使用稍微透明的背景色
|
||||
: colorTokens.background
|
||||
}
|
||||
]}
|
||||
onPress={handleTaskPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
{/* 头部区域 */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.titleSection}>
|
||||
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
|
||||
<View style={styles.cardContent}>
|
||||
{/* 左侧图标区域 */}
|
||||
<View style={styles.iconSection}>
|
||||
<View style={[
|
||||
styles.iconCircle,
|
||||
{
|
||||
backgroundColor: task.status === 'completed'
|
||||
? '#EDE9FE' // 完成状态使用更深的紫色背景
|
||||
: '#F3E8FF',
|
||||
}
|
||||
]}>
|
||||
<Image
|
||||
source={require('@/assets/images/task/icon-copy.png')}
|
||||
style={styles.taskIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 右侧信息区域 */}
|
||||
<View style={styles.infoSection}>
|
||||
{/* 任务标题 */}
|
||||
<Text style={[
|
||||
styles.title,
|
||||
{
|
||||
color: task.status === 'completed'
|
||||
? '#6B7280'
|
||||
: colorTokens.text,
|
||||
}
|
||||
]} numberOfLines={1}>
|
||||
{task.title}
|
||||
</Text>
|
||||
{renderActionIcons()}
|
||||
|
||||
{/* 进度条 */}
|
||||
<View style={styles.progressContainer}>
|
||||
{/* 背景进度条 */}
|
||||
<View style={styles.progressBackground} />
|
||||
|
||||
{/* 实际进度条 */}
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.progressBar,
|
||||
{
|
||||
width: progressAnimation.interpolate({
|
||||
inputRange: [0, 100],
|
||||
outputRange: ['6%', '100%'], // 最小显示6%确保可见
|
||||
}),
|
||||
backgroundColor: task.status === 'completed'
|
||||
? '#8B5CF6' // 完成状态也使用紫色
|
||||
: task.progressPercentage > 0
|
||||
? '#8B5CF6'
|
||||
: '#C7D2FE', // 浅紫色,表示待开始
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* 进度文字 */}
|
||||
<Text style={[
|
||||
styles.progressText,
|
||||
{
|
||||
color: task.progressPercentage > 20 || task.status === 'completed'
|
||||
? '#FFFFFF'
|
||||
: '#374151', // 进度较少时使用深色文字
|
||||
}
|
||||
]}>
|
||||
{task.currentCount}/{task.targetCount} 次
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 状态和优先级标签 */}
|
||||
<View style={styles.tagsContainer}>
|
||||
<View style={styles.statusTag}>
|
||||
<MaterialIcons name="schedule" size={12} color="#6B7280" />
|
||||
<Text style={styles.statusTagText}>{getStatusText(task.status)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 进度条 */}
|
||||
<View style={styles.progressBar}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.progressFill,
|
||||
{
|
||||
width: progressAnimation.interpolate({
|
||||
inputRange: [0, 100],
|
||||
outputRange: ['0%', '100%'],
|
||||
}),
|
||||
backgroundColor: task.progressPercentage > 0 ? colorTokens.primary : '#E5E7EB',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{task.progressPercentage > 0 && task.progressPercentage < 100 && (
|
||||
<View style={styles.progressGlow} />
|
||||
)}
|
||||
{/* 进度百分比文本 */}
|
||||
<View style={styles.progressTextContainer}>
|
||||
<Text style={styles.progressText}>{task.currentCount}/{task.targetCount}</Text>
|
||||
</View>
|
||||
{/* 操作按钮 */}
|
||||
{renderActionIcons()}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
@@ -231,8 +271,8 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
padding: 14,
|
||||
borderRadius: 30,
|
||||
marginBottom: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
@@ -240,6 +280,91 @@ const styles = StyleSheet.create({
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
cardContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
iconSection: {
|
||||
flexShrink: 0,
|
||||
},
|
||||
iconCircle: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
backgroundColor: '#F3E8FF', // 浅紫色背景
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
infoSection: {
|
||||
flex: 1,
|
||||
gap: 8,
|
||||
},
|
||||
title: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
lineHeight: 20,
|
||||
color: '#1F2937', // 深蓝紫色文字
|
||||
marginBottom: 2,
|
||||
},
|
||||
progressContainer: {
|
||||
position: 'relative',
|
||||
height: 14,
|
||||
justifyContent: 'center',
|
||||
marginTop: 4,
|
||||
},
|
||||
progressBackground: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 14,
|
||||
backgroundColor: '#F3F4F6',
|
||||
borderRadius: 10,
|
||||
},
|
||||
progressBar: {
|
||||
height: 14,
|
||||
borderRadius: 10,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
minWidth: '6%', // 确保最小宽度可见
|
||||
},
|
||||
progressText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#FFFFFF',
|
||||
textAlign: 'center',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1,
|
||||
},
|
||||
actionIconsContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
flexShrink: 0,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#F3F4F6',
|
||||
},
|
||||
skipIconContainer: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
backgroundColor: '#F3F4F6',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
taskIcon: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
// 保留其他样式以备后用
|
||||
header: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
@@ -248,38 +373,6 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
lineHeight: 22,
|
||||
flex: 1,
|
||||
},
|
||||
actionIconsContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
flexShrink: 0,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#F3F4F6',
|
||||
},
|
||||
skipIconContainer: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: '#F3F4F6',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
taskIcon: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
tagsContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
@@ -312,7 +405,7 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '500',
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
progressBar: {
|
||||
progressBarOld: {
|
||||
height: 6,
|
||||
backgroundColor: '#F3F4F6',
|
||||
borderRadius: 3,
|
||||
@@ -360,11 +453,6 @@ const styles = StyleSheet.create({
|
||||
borderColor: '#E5E7EB',
|
||||
zIndex: 1,
|
||||
},
|
||||
progressText: {
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
color: '#374151',
|
||||
},
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
@@ -391,10 +479,6 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
infoSection: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
infoTag: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -409,3 +493,4 @@ const styles = StyleSheet.create({
|
||||
color: '#374151',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user