feat: 优化待办事项页面视觉与交互体验

- 优化待办事项页面的视觉样式与交互体验,重构头部信息展示、输入框与按钮布局、进度条样式,并改进任务项的视觉反馈与操作交互,提升整体设计一致性与用户操作流畅性。
This commit is contained in:
2026-01-17 03:18:56 +08:00
parent a4a9e0889a
commit 8b058fd40a

View File

@@ -114,71 +114,68 @@ function Todo() {
const completedCount = todos.filter((todo) => todo.completed).length const completedCount = todos.filter((todo) => todo.completed).length
const totalCount = todos.length const totalCount = todos.length
const progress = totalCount > 0 ? (completedCount / totalCount) * 100 : 0
return ( return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-white to-purple-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="min-h-screen bg-slate-50 py-12 px-4 sm:px-6 font-sans">
<div className="max-w-3xl mx-auto"> <div className="max-w-2xl mx-auto space-y-8">
{/* Header */} {/* Header */}
<div className="text-center mb-8"> <div className="flex items-end justify-between">
<h1 className="text-4xl font-bold text-gray-900 mb-2"> <div>
<h1 className="text-3xl font-bold text-slate-900 tracking-tight">
</h1>
<p className="text-gray-600"> </h1>
{completedCount} / {totalCount} <p className="text-slate-500 mt-1"></p>
</p> </div>
<div className="text-right">
<div className="text-2xl font-semibold text-slate-900">
{completedCount}
<span className="text-slate-400 text-lg">/{totalCount}</span>
</div>
<div className="text-xs font-medium text-slate-400 uppercase tracking-wider">
</div>
</div>
</div> </div>
{/* Add Todo Form */} {/* Add Todo Form */}
<form onSubmit={handleCreateTodo} className="mb-8"> <form onSubmit={handleCreateTodo} className="relative group z-10">
<div className="bg-white rounded-lg shadow-sm p-4"> <div className="relative transform transition-all duration-200 focus-within:-translate-y-1">
<div className="flex gap-3"> <input
<input type="text"
type="text" value={newTodoTitle}
value={newTodoTitle} onChange={handleInputChange}
onChange={handleInputChange} placeholder="添加新任务..."
placeholder="添加新任务..." className="w-full pl-6 pr-32 py-5 bg-white rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.04)] border-0 ring-1 ring-slate-100 focus:ring-2 focus:ring-indigo-500/50 outline-none transition-all placeholder:text-slate-400 text-lg text-slate-700"
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent" disabled={createMutation.isPending}
disabled={createMutation.isPending} />
/> <button
<button type="submit"
type="submit" disabled={createMutation.isPending || !newTodoTitle.trim()}
disabled={createMutation.isPending || !newTodoTitle.trim()} className="absolute right-3 top-3 bottom-3 px-6 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl font-medium transition-all shadow-md shadow-indigo-200 disabled:opacity-50 disabled:shadow-none hover:shadow-lg hover:shadow-indigo-300 active:scale-95"
className="px-6 py-2 bg-gradient-to-r from-indigo-500 to-purple-600 text-white rounded-lg font-medium hover:from-indigo-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-all" >
> {createMutation.isPending ? '添加中' : '添加'}
{createMutation.isPending ? '添加中...' : '添加'} </button>
</button>
</div>
</div> </div>
</form> </form>
{/* Progress Bar */} {/* Progress Bar (Only visible when there are tasks) */}
<div className="mb-8 bg-white rounded-lg shadow-sm p-4"> {totalCount > 0 && (
<div className="flex items-center justify-between mb-2"> <div className="h-1.5 w-full bg-slate-200 rounded-full overflow-hidden">
<span className="text-sm font-medium text-gray-700"></span>
<span className="text-sm font-medium text-indigo-600">
{totalCount > 0
? Math.round((completedCount / totalCount) * 100)
: 0}
%
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div <div
className="bg-gradient-to-r from-indigo-500 to-purple-600 h-2.5 rounded-full transition-all duration-300" className="h-full bg-indigo-500 transition-all duration-500 ease-out rounded-full"
style={{ style={{ width: `${progress}%` }}
width: `${totalCount > 0 ? (completedCount / totalCount) * 100 : 0}%`,
}}
/> />
</div> </div>
</div> )}
{/* Todo List */} {/* Todo List */}
<div className="space-y-3"> <div className="space-y-3">
{todos.length === 0 ? ( {todos.length === 0 ? (
<div className="bg-white rounded-lg shadow-sm p-12 text-center"> <div className="py-20 text-center">
<div className="text-gray-400 mb-4"> <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-slate-100 mb-4">
<svg <svg
className="mx-auto h-12 w-12" className="w-8 h-8 text-slate-400"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -187,149 +184,93 @@ function Todo() {
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth={2} strokeWidth={1.5}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/> />
</svg> </svg>
</div> </div>
<p className="text-gray-500 text-lg"></p> <p className="text-slate-500 text-lg font-medium"></p>
<p className="text-gray-400 text-sm mt-2"> <p className="text-slate-400 text-sm mt-1">
</p> </p>
</div> </div>
) : ( ) : (
todos.map((todo) => ( todos.map((todo) => (
<div <div
key={todo.id} key={todo.id}
className={`bg-white rounded-lg shadow-sm hover:shadow-md transition-all duration-200 p-5 border-l-4 ${ className={`group relative flex items-center p-4 bg-white rounded-xl border border-slate-100 shadow-sm transition-all duration-200 hover:shadow-md hover:border-slate-200 ${
todo.completed todo.completed ? 'bg-slate-50/50' : ''
? 'border-green-500 bg-gray-50'
: 'border-indigo-500'
}`} }`}
> >
<div className="flex items-start gap-4"> <button
{/* Checkbox */} type="button"
onClick={() => handleToggleTodo(todo.id, todo.completed)}
className={`flex-shrink-0 w-6 h-6 rounded-full border-2 transition-all duration-200 flex items-center justify-center mr-4 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
todo.completed
? 'bg-indigo-500 border-indigo-500'
: 'border-slate-300 hover:border-indigo-500 bg-white'
}`}
>
{todo.completed && (
<svg
className="w-3.5 h-3.5 text-white"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={3}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5 13l4 4L19 7"
/>
</svg>
)}
</button>
<div className="flex-1 min-w-0">
<p
className={`text-lg transition-all duration-200 truncate ${
todo.completed
? 'text-slate-400 line-through decoration-slate-300 decoration-2'
: 'text-slate-700'
}`}
>
{todo.title}
</p>
</div>
<div className="flex items-center opacity-0 group-hover:opacity-100 transition-opacity duration-200 absolute right-4 pl-4 bg-gradient-to-l from-white via-white to-transparent sm:static sm:bg-none">
<span className="text-xs text-slate-400 mr-3 hidden sm:inline-block">
{new Date(todo.createdAt).toLocaleDateString('zh-CN')}
</span>
<button <button
type="button" type="button"
onClick={() => handleToggleTodo(todo.id, todo.completed)} onClick={() => handleDeleteTodo(todo.id)}
disabled={updateMutation.isPending} className="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors focus:outline-none"
className="flex-shrink-0 pt-1 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 rounded-full disabled:opacity-50" title="删除"
> >
<div <svg
className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all ${ className="w-5 h-5"
todo.completed fill="none"
? 'bg-green-500 border-green-500' viewBox="0 0 24 24"
: 'border-gray-300 hover:border-indigo-500' stroke="currentColor"
}`} strokeWidth={1.5}
aria-hidden="true"
> >
{todo.completed && ( <path
<svg strokeLinecap="round"
className="w-4 h-4 text-white" strokeLinejoin="round"
fill="none" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
viewBox="0 0 24 24" />
stroke="currentColor" </svg>
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M5 13l4 4L19 7"
/>
</svg>
)}
</div>
</button> </button>
{/* Todo Content */}
<div className="flex-1 min-w-0">
<h3
className={`text-lg font-medium ${
todo.completed
? 'text-gray-500 line-through'
: 'text-gray-900'
}`}
>
{todo.title}
</h3>
<div className="mt-2 flex items-center gap-2 text-xs text-gray-500">
<span className="inline-flex items-center gap-1">
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{new Date(todo.createdAt).toLocaleDateString('zh-CN')}
</span>
</div>
</div>
{/* Status Badge and Delete Button */}
<div className="flex-shrink-0 flex items-center gap-2">
<span
className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-medium ${
todo.completed
? 'bg-green-100 text-green-800'
: 'bg-indigo-100 text-indigo-800'
}`}
>
{todo.completed ? '已完成' : '进行中'}
</span>
<button
type="button"
onClick={() => handleDeleteTodo(todo.id)}
disabled={deleteMutation.isPending}
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50"
title="删除任务"
>
<svg
className="w-5 h-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
</div> </div>
</div> </div>
)) ))
)} )}
</div> </div>
{/* Footer Stats */}
{todos.length > 0 && (
<div className="mt-8 grid grid-cols-2 gap-4">
<div className="bg-white rounded-lg shadow-sm p-4 text-center">
<p className="text-2xl font-bold text-indigo-600">
{totalCount - completedCount}
</p>
<p className="text-sm text-gray-600 mt-1"></p>
</div>
<div className="bg-white rounded-lg shadow-sm p-4 text-center">
<p className="text-2xl font-bold text-green-600">
{completedCount}
</p>
<p className="text-sm text-gray-600 mt-1"></p>
</div>
</div>
)}
</div> </div>
</div> </div>
) )