feat: 增强任务管理功能,新增任务筛选和状态统计组件
- 在目标页面中集成任务筛选标签,支持按状态(全部、待完成、已完成)过滤任务 - 更新任务卡片,展示任务状态和优先级信息 - 新增任务进度卡片,统计各状态任务数量 - 优化空状态展示,根据筛选条件动态显示提示信息 - 引入新图标和图片资源,提升界面视觉效果
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import CreateGoalModal from '@/components/CreateGoalModal';
|
import CreateGoalModal from '@/components/CreateGoalModal';
|
||||||
import { TaskCard } from '@/components/TaskCard';
|
import { TaskCard } from '@/components/TaskCard';
|
||||||
|
import { TaskFilterTabs, TaskFilterType } from '@/components/TaskFilterTabs';
|
||||||
import { TaskProgressCard } from '@/components/TaskProgressCard';
|
import { TaskProgressCard } from '@/components/TaskProgressCard';
|
||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors';
|
||||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||||
@@ -13,7 +14,7 @@ import dayjs from 'dayjs';
|
|||||||
import { LinearGradient } from 'expo-linear-gradient';
|
import { LinearGradient } from 'expo-linear-gradient';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Alert, FlatList, RefreshControl, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { Alert, FlatList, Image, RefreshControl, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
export default function GoalsScreen() {
|
export default function GoalsScreen() {
|
||||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||||
@@ -40,6 +41,7 @@ export default function GoalsScreen() {
|
|||||||
|
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [selectedFilter, setSelectedFilter] = useState<TaskFilterType>('all');
|
||||||
|
|
||||||
// 页面聚焦时重新加载数据
|
// 页面聚焦时重新加载数据
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@@ -160,6 +162,30 @@ export default function GoalsScreen() {
|
|||||||
router.push('/goals-detail');
|
router.push('/goals-detail');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 计算各状态的任务数量
|
||||||
|
const taskCounts = {
|
||||||
|
all: tasks.length,
|
||||||
|
pending: tasks.filter(task => task.status === 'pending').length,
|
||||||
|
completed: tasks.filter(task => task.status === 'completed').length,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据筛选条件过滤任务
|
||||||
|
const filteredTasks = React.useMemo(() => {
|
||||||
|
switch (selectedFilter) {
|
||||||
|
case 'pending':
|
||||||
|
return tasks.filter(task => task.status === 'pending');
|
||||||
|
case 'completed':
|
||||||
|
return tasks.filter(task => task.status === 'completed');
|
||||||
|
default:
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
}, [tasks, selectedFilter]);
|
||||||
|
|
||||||
|
// 处理筛选变化
|
||||||
|
const handleFilterChange = (filter: TaskFilterType) => {
|
||||||
|
setSelectedFilter(filter);
|
||||||
|
};
|
||||||
|
|
||||||
// 渲染任务项
|
// 渲染任务项
|
||||||
const renderTaskItem = ({ item }: { item: TaskListItem }) => (
|
const renderTaskItem = ({ item }: { item: TaskListItem }) => (
|
||||||
<TaskCard
|
<TaskCard
|
||||||
@@ -171,16 +197,34 @@ export default function GoalsScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 渲染空状态
|
// 渲染空状态
|
||||||
const renderEmptyState = () => (
|
const renderEmptyState = () => {
|
||||||
<View style={styles.emptyState}>
|
let title = '暂无任务';
|
||||||
<Text style={[styles.emptyStateTitle, { color: colorTokens.text }]}>
|
let subtitle = '创建目标后,系统会自动生成相应的任务';
|
||||||
暂无任务
|
|
||||||
</Text>
|
if (selectedFilter === 'pending') {
|
||||||
<Text style={[styles.emptyStateSubtitle, { color: colorTokens.textSecondary }]}>
|
title = '暂无待完成的任务';
|
||||||
创建目标后,系统会自动生成相应的任务
|
subtitle = '当前没有待完成的任务';
|
||||||
</Text>
|
} else if (selectedFilter === 'completed') {
|
||||||
</View>
|
title = '暂无已完成的任务';
|
||||||
);
|
subtitle = '完成一些任务后,它们会显示在这里';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
<Image
|
||||||
|
source={require('@/assets/images/task/ImageEmpty.png')}
|
||||||
|
style={styles.emptyStateImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={[styles.emptyStateTitle, { color: colorTokens.text }]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.emptyStateSubtitle, { color: colorTokens.textSecondary }]}>
|
||||||
|
{subtitle}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 渲染加载更多
|
// 渲染加载更多
|
||||||
const renderLoadMore = () => {
|
const renderLoadMore = () => {
|
||||||
@@ -209,39 +253,71 @@ export default function GoalsScreen() {
|
|||||||
end={{ x: 1, y: 1 }}
|
end={{ x: 1, y: 1 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 装饰性圆圈 */}
|
<View style={{
|
||||||
<View style={styles.decorativeCircle1} />
|
position: 'absolute',
|
||||||
<View style={styles.decorativeCircle2} />
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
backgroundColor: '#7A5AF8',
|
||||||
|
height: 233,
|
||||||
|
borderBottomLeftRadius: 24,
|
||||||
|
borderBottomRightRadius: 24,
|
||||||
|
}}>
|
||||||
|
{/* 右下角图片 */}
|
||||||
|
<Image
|
||||||
|
source={require('@/assets/images/task/imageTodo.png')}
|
||||||
|
style={styles.bottomRightImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{/* 标题区域 */}
|
{/* 标题区域 */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={[styles.pageTitle, { color: colorTokens.text }]}>
|
<View>
|
||||||
任务
|
<Text style={[styles.pageTitle, { color: '#FFFFFF' }]}>
|
||||||
|
等待完成
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.pageTitle2, { color: '#FFFFFF' }]}>
|
||||||
|
让我们检查你的目标!
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.headerButtons}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.goalsButton}
|
|
||||||
onPress={handleNavigateToGoals}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="flag" size={16} color="#0EA5E9" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.addButton}
|
|
||||||
onPress={() => setShowCreateModal(true)}
|
|
||||||
>
|
|
||||||
<Text style={styles.addButtonText}>+</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 任务进度卡片 */}
|
{/* 任务进度卡片 */}
|
||||||
<TaskProgressCard tasks={tasks} />
|
<View >
|
||||||
|
<TaskProgressCard
|
||||||
|
tasks={tasks}
|
||||||
|
headerButtons={
|
||||||
|
<View style={styles.cardHeaderButtons}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.cardGoalsButton, { borderColor: colorTokens.primary }]}
|
||||||
|
onPress={handleNavigateToGoals}
|
||||||
|
>
|
||||||
|
<MaterialIcons name="flag" size={16} color={colorTokens.primary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.cardAddButton, { backgroundColor: colorTokens.primary }]}
|
||||||
|
onPress={() => setShowCreateModal(true)}
|
||||||
|
>
|
||||||
|
<Text style={styles.cardAddButtonText}>+</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 任务筛选标签 */}
|
||||||
|
<TaskFilterTabs
|
||||||
|
selectedFilter={selectedFilter}
|
||||||
|
onFilterChange={handleFilterChange}
|
||||||
|
taskCounts={taskCounts}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 任务列表 */}
|
{/* 任务列表 */}
|
||||||
<View style={styles.taskListContainer}>
|
<View style={styles.taskListContainer}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={tasks}
|
data={filteredTasks}
|
||||||
renderItem={renderTaskItem}
|
renderItem={renderTaskItem}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
contentContainerStyle={styles.taskList}
|
contentContainerStyle={styles.taskList}
|
||||||
@@ -340,6 +416,12 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
|
pageTitle2: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '400',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
lineHeight: 24,
|
||||||
|
},
|
||||||
addButton: {
|
addButton: {
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
@@ -362,7 +444,6 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
taskListContainer: {
|
taskListContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
||||||
borderTopLeftRadius: 24,
|
borderTopLeftRadius: 24,
|
||||||
borderTopRightRadius: 24,
|
borderTopRightRadius: 24,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -377,6 +458,11 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingVertical: 60,
|
paddingVertical: 60,
|
||||||
},
|
},
|
||||||
|
emptyStateImage: {
|
||||||
|
width: 223,
|
||||||
|
height: 59,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
emptyStateTitle: {
|
emptyStateTitle: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
@@ -395,4 +481,40 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
bottomRightImage: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 56,
|
||||||
|
right: 36,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
},
|
||||||
|
// 任务进度卡片中的按钮样式
|
||||||
|
cardHeaderButtons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
cardGoalsButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: '#F3F4F6',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
cardAddButton: {
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
borderRadius: 14,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
cardAddButtonText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
lineHeight: 18,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
assets/images/task/ImageEmpty.png
Normal file
BIN
assets/images/task/ImageEmpty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/images/task/iconTaskHeader.png
Normal file
BIN
assets/images/task/iconTaskHeader.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/images/task/imageTodo.png
Normal file
BIN
assets/images/task/imageTodo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@@ -1,8 +1,9 @@
|
|||||||
import { Colors } from '@/constants/Colors';
|
import { Colors } from '@/constants/Colors';
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
import { TaskListItem } from '@/types/goals';
|
import { TaskListItem } from '@/types/goals';
|
||||||
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
interface TaskCardProps {
|
interface TaskCardProps {
|
||||||
task: TaskListItem;
|
task: TaskListItem;
|
||||||
@@ -50,28 +51,39 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCategoryColor = (category?: string) => {
|
const getPriorityColor = (status: string) => {
|
||||||
if (!category) return '#6B7280';
|
switch (status) {
|
||||||
if (category.includes('运动') || category.includes('健身')) return '#EF4444';
|
case 'overdue':
|
||||||
if (category.includes('工作')) return '#3B82F6';
|
return '#EF4444'; // High - 过期任务
|
||||||
if (category.includes('健康')) return '#10B981';
|
case 'in_progress':
|
||||||
if (category.includes('财务')) return '#F59E0B';
|
return '#F59E0B'; // Medium - 进行中
|
||||||
return '#6B7280';
|
case 'completed':
|
||||||
|
return '#10B981'; // Low - 已完成
|
||||||
|
default:
|
||||||
|
return '#6B7280'; // Default - 待开始
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPriorityText = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'overdue':
|
||||||
|
return '高';
|
||||||
|
case 'in_progress':
|
||||||
|
return '中';
|
||||||
|
case 'completed':
|
||||||
|
return '低';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
const today = new Date();
|
const month = date.toLocaleDateString('zh-CN', { month: 'short' });
|
||||||
const tomorrow = new Date(today);
|
const day = date.getDate();
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
return `${day} ${month}`;
|
||||||
|
|
||||||
if (date.toDateString() === today.toDateString()) {
|
|
||||||
return '今天';
|
|
||||||
} else if (date.toDateString() === tomorrow.toDateString()) {
|
|
||||||
return '明天';
|
|
||||||
} else {
|
|
||||||
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -80,71 +92,74 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
|||||||
onPress={() => onPress?.(task)}
|
onPress={() => onPress?.(task)}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
|
{/* 头部区域 */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleSection}>
|
||||||
|
<View style={styles.iconContainer}>
|
||||||
|
<Image
|
||||||
|
source={require('@/assets/images/task/iconTaskHeader.png')}
|
||||||
|
style={styles.taskIcon}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
|
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
|
||||||
{task.title}
|
{task.title}
|
||||||
</Text>
|
</Text>
|
||||||
{task.goal?.category && (
|
|
||||||
<View style={[styles.categoryTag, { backgroundColor: getCategoryColor(task.goal.category) }]}>
|
|
||||||
<Text style={styles.categoryText}>{task.goal?.category}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View style={[styles.statusTag, { backgroundColor: getStatusColor(task.status) }]}>
|
|
||||||
<Text style={styles.statusText}>{getStatusText(task.status)}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{task.description && (
|
{/* 状态和优先级标签 */}
|
||||||
<Text style={[styles.description, { color: colorTokens.textSecondary }]} numberOfLines={2}>
|
<View style={styles.tagsContainer}>
|
||||||
{task.description}
|
<View style={styles.statusTag}>
|
||||||
</Text>
|
<MaterialIcons name="schedule" size={12} color="#6B7280" />
|
||||||
)}
|
<Text style={styles.statusTagText}>{getStatusText(task.status)}</Text>
|
||||||
|
|
||||||
<View style={styles.progressContainer}>
|
|
||||||
<View style={styles.progressInfo}>
|
|
||||||
<Text style={[styles.progressText, { color: colorTokens.textSecondary }]}>
|
|
||||||
进度: {task.currentCount}/{task.targetCount}
|
|
||||||
</Text>
|
|
||||||
<Text style={[styles.progressText, { color: colorTokens.textSecondary }]}>
|
|
||||||
{task.progressPercentage}%
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.progressBar, { backgroundColor: colorTokens.border }]}>
|
<View style={[styles.priorityTag, { backgroundColor: getPriorityColor(task.status) }]}>
|
||||||
<View
|
<MaterialIcons name="flag" size={12} color="#FFFFFF" />
|
||||||
style={[
|
<Text style={styles.priorityTagText}>{getPriorityText(task.status)}</Text>
|
||||||
styles.progressFill,
|
|
||||||
{
|
|
||||||
width: `${task.progressPercentage}%`,
|
|
||||||
backgroundColor: getStatusColor(task.status),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* 进度条 */}
|
||||||
|
<View style={styles.progressBar}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.progressFill,
|
||||||
|
{
|
||||||
|
width: `${Math.min(task.progressPercentage, 100)}%`,
|
||||||
|
backgroundColor: colorTokens.primary,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 底部信息 */}
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Text style={[styles.dateText, { color: colorTokens.textSecondary }]}>
|
<View style={styles.teamSection}>
|
||||||
{formatDate(task.startDate)}
|
{/* 模拟团队成员头像 */}
|
||||||
</Text>
|
<View style={styles.avatars}>
|
||||||
|
<View style={[styles.avatar, { backgroundColor: '#FBBF24' }]}>
|
||||||
{task.status === 'pending' || task.status === 'in_progress' ? (
|
<Text style={styles.avatarText}>A</Text>
|
||||||
<View style={styles.actionButtons}>
|
</View>
|
||||||
<TouchableOpacity
|
<View style={[styles.avatar, { backgroundColor: '#34D399' }]}>
|
||||||
style={[styles.actionButton, styles.skipButton]}
|
<Text style={styles.avatarText}>B</Text>
|
||||||
onPress={() => onSkip?.(task)}
|
</View>
|
||||||
>
|
<View style={[styles.avatar, { backgroundColor: '#60A5FA' }]}>
|
||||||
<Text style={styles.skipButtonText}>跳过</Text>
|
<Text style={styles.avatarText}>C</Text>
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.actionButton, styles.completeButton]}
|
|
||||||
onPress={() => onComplete?.(task)}
|
|
||||||
>
|
|
||||||
<Text style={styles.completeButtonText}>完成</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.infoSection}>
|
||||||
|
<View style={styles.infoTag}>
|
||||||
|
<MaterialIcons name="event" size={12} color="#6B7280" />
|
||||||
|
<Text style={styles.infoTagText}>{formatDate(task.startDate)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.infoTag}>
|
||||||
|
<MaterialIcons name="chat-bubble-outline" size={12} color="#6B7280" />
|
||||||
|
<Text style={styles.infoTagText}>2</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
@@ -162,99 +177,119 @@ const styles = StyleSheet.create({
|
|||||||
elevation: 3,
|
elevation: 3,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
marginBottom: 12,
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
},
|
||||||
titleContainer: {
|
titleSection: {
|
||||||
flex: 1,
|
flexDirection: 'row',
|
||||||
marginRight: 8,
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
iconContainer: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: '#7A5AF8',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
taskIcon: {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
marginBottom: 4,
|
lineHeight: 22,
|
||||||
|
flex: 1,
|
||||||
},
|
},
|
||||||
categoryTag: {
|
tagsContainer: {
|
||||||
paddingHorizontal: 8,
|
flexDirection: 'row',
|
||||||
paddingVertical: 2,
|
gap: 8,
|
||||||
borderRadius: 12,
|
marginBottom: 12,
|
||||||
alignSelf: 'flex-start',
|
|
||||||
},
|
|
||||||
categoryText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
},
|
||||||
statusTag: {
|
statusTag: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 4,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: '#F3F4F6',
|
||||||
|
},
|
||||||
|
statusTagText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#374151',
|
||||||
|
},
|
||||||
|
priorityTag: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
},
|
},
|
||||||
statusText: {
|
priorityTagText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '500',
|
||||||
color: '#FFFFFF',
|
color: '#FFFFFF',
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
fontSize: 14,
|
|
||||||
marginBottom: 12,
|
|
||||||
lineHeight: 20,
|
|
||||||
},
|
|
||||||
progressContainer: {
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
progressInfo: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginBottom: 6,
|
|
||||||
},
|
|
||||||
progressText: {
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
},
|
||||||
progressBar: {
|
progressBar: {
|
||||||
height: 6,
|
height: 2,
|
||||||
borderRadius: 3,
|
backgroundColor: '#E5E7EB',
|
||||||
|
borderRadius: 1,
|
||||||
|
marginBottom: 16,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
progressFill: {
|
progressFill: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
borderRadius: 3,
|
borderRadius: 1,
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
dateText: {
|
teamSection: {
|
||||||
fontSize: 12,
|
flexDirection: 'row',
|
||||||
fontWeight: '500',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
actionButtons: {
|
avatars: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: -8,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
avatarText: {
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
infoSection: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
},
|
},
|
||||||
actionButton: {
|
infoTag: {
|
||||||
paddingHorizontal: 12,
|
flexDirection: 'row',
|
||||||
paddingVertical: 6,
|
alignItems: 'center',
|
||||||
borderRadius: 16,
|
gap: 4,
|
||||||
},
|
paddingHorizontal: 8,
|
||||||
skipButton: {
|
paddingVertical: 4,
|
||||||
|
borderRadius: 12,
|
||||||
backgroundColor: '#F3F4F6',
|
backgroundColor: '#F3F4F6',
|
||||||
},
|
},
|
||||||
skipButtonText: {
|
infoTagText: {
|
||||||
color: '#6B7280',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
completeButton: {
|
|
||||||
backgroundColor: '#10B981',
|
|
||||||
},
|
|
||||||
completeButtonText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
|
color: '#374151',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
175
components/TaskFilterTabs.tsx
Normal file
175
components/TaskFilterTabs.tsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
|
export type TaskFilterType = 'all' | 'pending' | 'completed';
|
||||||
|
|
||||||
|
interface TaskFilterTabsProps {
|
||||||
|
selectedFilter: TaskFilterType;
|
||||||
|
onFilterChange: (filter: TaskFilterType) => void;
|
||||||
|
taskCounts: {
|
||||||
|
all: number;
|
||||||
|
pending: number;
|
||||||
|
completed: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TaskFilterTabs: React.FC<TaskFilterTabsProps> = ({
|
||||||
|
selectedFilter,
|
||||||
|
onFilterChange,
|
||||||
|
taskCounts,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.tabContainer}>
|
||||||
|
{/* 全部 Tab */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tab,
|
||||||
|
selectedFilter === 'all' && styles.activeTab
|
||||||
|
]}
|
||||||
|
onPress={() => onFilterChange('all')}
|
||||||
|
>
|
||||||
|
<Text style={[
|
||||||
|
styles.tabText,
|
||||||
|
selectedFilter === 'all' && styles.activeTabText
|
||||||
|
]}>
|
||||||
|
全部
|
||||||
|
</Text>
|
||||||
|
<View style={[
|
||||||
|
styles.badge,
|
||||||
|
selectedFilter === 'all' ? styles.activeBadge : styles.inactiveBadge
|
||||||
|
]}>
|
||||||
|
<Text style={[
|
||||||
|
styles.badgeText,
|
||||||
|
selectedFilter === 'all' && styles.activeBadgeText
|
||||||
|
]}>
|
||||||
|
{taskCounts.all}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* 待完成 Tab */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tab,
|
||||||
|
selectedFilter === 'pending' && styles.activeTab
|
||||||
|
]}
|
||||||
|
onPress={() => onFilterChange('pending')}
|
||||||
|
>
|
||||||
|
<Text style={[
|
||||||
|
styles.tabText,
|
||||||
|
selectedFilter === 'pending' && styles.activeTabText
|
||||||
|
]}>
|
||||||
|
待完成
|
||||||
|
</Text>
|
||||||
|
<View style={[
|
||||||
|
styles.badge,
|
||||||
|
selectedFilter === 'pending' ? styles.activeBadge : styles.inactiveBadge
|
||||||
|
]}>
|
||||||
|
<Text style={[
|
||||||
|
styles.badgeText,
|
||||||
|
selectedFilter === 'pending' && styles.activeBadgeText
|
||||||
|
]}>
|
||||||
|
{taskCounts.pending}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* 已完成 Tab */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.tab,
|
||||||
|
selectedFilter === 'completed' && styles.activeTab
|
||||||
|
]}
|
||||||
|
onPress={() => onFilterChange('completed')}
|
||||||
|
>
|
||||||
|
<Text style={[
|
||||||
|
styles.tabText,
|
||||||
|
selectedFilter === 'completed' && styles.activeTabText
|
||||||
|
]}>
|
||||||
|
已完成
|
||||||
|
</Text>
|
||||||
|
<View style={[
|
||||||
|
styles.badge,
|
||||||
|
selectedFilter === 'completed' ? styles.activeBadge : styles.inactiveBadge
|
||||||
|
]}>
|
||||||
|
<Text style={[
|
||||||
|
styles.badgeText,
|
||||||
|
selectedFilter === 'completed' && styles.activeBadgeText
|
||||||
|
]}>
|
||||||
|
{taskCounts.completed}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
},
|
||||||
|
tabContainer: {
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 4,
|
||||||
|
flexDirection: 'row',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 2,
|
||||||
|
elevation: 1,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E5E7EB',
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
borderRadius: 20,
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
activeTab: {
|
||||||
|
backgroundColor: '#7A5AF8',
|
||||||
|
shadowColor: '#7A5AF8',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
tabText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#374151',
|
||||||
|
},
|
||||||
|
activeTabText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
minWidth: 20,
|
||||||
|
height: 20,
|
||||||
|
borderRadius: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
},
|
||||||
|
inactiveBadge: {
|
||||||
|
backgroundColor: '#E5E7EB',
|
||||||
|
},
|
||||||
|
activeBadge: {
|
||||||
|
backgroundColor: '#EF4444',
|
||||||
|
},
|
||||||
|
badgeText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#374151',
|
||||||
|
},
|
||||||
|
activeBadgeText: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,140 +1,139 @@
|
|||||||
import { TaskListItem } from '@/types/goals';
|
import { TaskListItem } from '@/types/goals';
|
||||||
import React from 'react';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
interface TaskProgressCardProps {
|
interface TaskProgressCardProps {
|
||||||
tasks: TaskListItem[];
|
tasks: TaskListItem[];
|
||||||
|
headerButtons?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TaskProgressCard: React.FC<TaskProgressCardProps> = ({
|
export const TaskProgressCard: React.FC<TaskProgressCardProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
|
headerButtons,
|
||||||
}) => {
|
}) => {
|
||||||
// 计算今日任务完成进度
|
// 计算各状态的任务数量
|
||||||
const todayTasks = tasks.filter(task => task.isToday);
|
const pendingTasks = tasks.filter(task => task.status === 'pending');
|
||||||
const completedTodayTasks = todayTasks.filter(task => task.status === 'completed');
|
const completedTasks = tasks.filter(task => task.status === 'completed');
|
||||||
const progressPercentage = todayTasks.length > 0
|
const skippedTasks = tasks.filter(task => task.status === 'skipped');
|
||||||
? Math.round((completedTodayTasks.length / todayTasks.length) * 100)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 计算进度角度
|
|
||||||
const progressAngle = (progressPercentage / 100) * 360;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{/* 左侧内容 */}
|
{/* 标题区域 */}
|
||||||
<View style={styles.leftContent}>
|
<View style={styles.header}>
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<Text style={styles.title}>今日目标</Text>
|
<Text style={styles.title}>任务状态统计</Text>
|
||||||
<Text style={styles.subtitle}>加油,快完成啦!</Text>
|
<Text style={styles.subtitle}>各状态任务数量分布</Text>
|
||||||
</View>
|
</View>
|
||||||
|
{headerButtons && (
|
||||||
|
<View style={styles.headerButtons}>
|
||||||
|
{headerButtons}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 右侧进度圆环 */}
|
{/* 状态卡片区域 */}
|
||||||
<View style={styles.progressContainer}>
|
<View style={styles.statusCards}>
|
||||||
{/* 背景圆环 */}
|
{/* 待完成 卡片 */}
|
||||||
<View style={[styles.progressCircle, styles.progressBackground]} />
|
<View style={styles.statusCard}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
{/* 进度圆环 */}
|
<MaterialIcons name="pending" size={16} color="#7A5AF8" />
|
||||||
<View style={[styles.progressCircle, styles.progressFill]}>
|
<Text style={styles.cardLabel} numberOfLines={1}>待完成</Text>
|
||||||
<View
|
</View>
|
||||||
style={[
|
<Text style={styles.cardCount}>{pendingTasks.length}</Text>
|
||||||
styles.progressArc,
|
|
||||||
{
|
|
||||||
width: 68,
|
|
||||||
height: 68,
|
|
||||||
borderRadius: 34,
|
|
||||||
borderWidth: 6,
|
|
||||||
borderColor: '#8B5CF6',
|
|
||||||
borderTopColor: progressAngle > 0 ? '#8B5CF6' : 'transparent',
|
|
||||||
borderRightColor: progressAngle > 90 ? '#8B5CF6' : 'transparent',
|
|
||||||
borderBottomColor: progressAngle > 180 ? '#8B5CF6' : 'transparent',
|
|
||||||
borderLeftColor: progressAngle > 270 ? '#8B5CF6' : 'transparent',
|
|
||||||
transform: [{ rotate: '-90deg' }],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 进度文字 */}
|
{/* 已完成 卡片 */}
|
||||||
<View style={styles.progressTextContainer}>
|
<View style={styles.statusCard}>
|
||||||
<Text style={styles.progressText}>{progressPercentage}%</Text>
|
<View style={styles.cardHeader}>
|
||||||
|
<MaterialIcons name="check-circle" size={16} color="#10B981" />
|
||||||
|
<Text style={styles.cardLabel} numberOfLines={1}>已完成</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.cardCount}>{completedTasks.length}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 已跳过 卡片 */}
|
||||||
|
<View style={styles.statusCard}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<MaterialIcons name="skip-next" size={16} color="#6B7280" />
|
||||||
|
<Text style={styles.cardLabel} numberOfLines={1}>已跳过</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.cardCount}>{skippedTasks.length}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: '#8B5CF6',
|
backgroundColor: '#FFFFFF',
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
marginHorizontal: 20,
|
marginHorizontal: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
marginBottom: 20,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
headerButtons: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
gap: 8,
|
||||||
shadowColor: '#8B5CF6',
|
|
||||||
shadowOffset: { width: 0, height: 4 },
|
|
||||||
shadowOpacity: 0.3,
|
|
||||||
shadowRadius: 8,
|
|
||||||
elevation: 8,
|
|
||||||
},
|
|
||||||
leftContent: {
|
|
||||||
flex: 1,
|
|
||||||
marginRight: 20,
|
|
||||||
},
|
|
||||||
textContainer: {
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
color: '#FFFFFF',
|
fontSize: 20,
|
||||||
fontSize: 16,
|
fontWeight: '700',
|
||||||
fontWeight: '600',
|
color: '#1F2937',
|
||||||
marginBottom: 2,
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
color: '#6B7280',
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
opacity: 0.9,
|
|
||||||
},
|
},
|
||||||
progressContainer: {
|
statusCards: {
|
||||||
width: 80,
|
flexDirection: 'row',
|
||||||
height: 80,
|
justifyContent: 'space-between',
|
||||||
justifyContent: 'center',
|
gap: 12,
|
||||||
|
},
|
||||||
|
statusCard: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: 12,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E5E7EB',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
minHeight: 80,
|
||||||
|
},
|
||||||
|
cardHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
marginBottom: 8,
|
||||||
|
gap: 6,
|
||||||
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
progressCircle: {
|
cardLabel: {
|
||||||
position: 'absolute',
|
fontSize: 11,
|
||||||
width: 80,
|
fontWeight: '500',
|
||||||
height: 80,
|
color: '#1F2937',
|
||||||
borderRadius: 40,
|
lineHeight: 14,
|
||||||
},
|
},
|
||||||
progressBackground: {
|
cardCount: {
|
||||||
borderWidth: 6,
|
fontSize: 24,
|
||||||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
|
||||||
},
|
|
||||||
progressFill: {
|
|
||||||
borderWidth: 6,
|
|
||||||
borderColor: 'transparent',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
progressArc: {
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
progressTextContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
progressText: {
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
|
color: '#1F2937',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
// 原子调色板(与设计图一致)
|
// 原子调色板(与设计图一致)
|
||||||
export const palette = {
|
export const palette = {
|
||||||
// Primary
|
// Primary
|
||||||
primary: '#87CEEB',
|
primary: '#7A5AF8',
|
||||||
ink: '#192126',
|
ink: '#FFFFFF',
|
||||||
|
|
||||||
// Secondary / Neutrals
|
// Secondary / Neutrals
|
||||||
neutral100: '#888F92',
|
neutral100: '#888F92',
|
||||||
@@ -18,7 +18,7 @@ export const palette = {
|
|||||||
purple: '#A48AED',
|
purple: '#A48AED',
|
||||||
red: '#ED4747',
|
red: '#ED4747',
|
||||||
orange: '#FCC46F',
|
orange: '#FCC46F',
|
||||||
blue: '#87CEEB', // 更贴近logo背景的天空蓝
|
blue: '#7A5AF8', // 更贴近logo背景的天空蓝
|
||||||
blueSecondary: '#4682B4', // 钢蓝色,用于选中状态
|
blueSecondary: '#4682B4', // 钢蓝色,用于选中状态
|
||||||
green: '#9ceb87', // 温暖的绿色,用于心情日历等
|
green: '#9ceb87', // 温暖的绿色,用于心情日历等
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
Reference in New Issue
Block a user