别再只用默认日历了!手把手教你用Element Plus的el-calendar打造个人任务看板(Vue 3 + TypeScript)

张开发
2026/4/19 12:51:36 15 分钟阅读

分享文章

别再只用默认日历了!手把手教你用Element Plus的el-calendar打造个人任务看板(Vue 3 + TypeScript)
从零构建Vue 3任务看板Element Plus日历组件深度实战在快节奏的工作环境中传统的待办事项列表已经难以满足现代开发者的需求。我们需要的不仅是一个简单的日期提醒工具而是一个能够直观展示任务状态、支持灵活交互的视觉化管理系统。Element Plus的el-calendar组件正是实现这一目标的绝佳起点——它提供了丰富的定制接口却常常被开发者低估其潜力。本文将带你从零开始用Vue 3和TypeScript构建一个企业级任务看板。不同于基础教程中简单的日期显示我们将实现动态任务状态可视化图标、进度条、颜色编码跨日期的拖拽任务管理基于Pinia的集中式状态管理响应式布局适配多端设备完整的TypeScript类型支持1. 环境搭建与基础配置1.1 初始化Vue 3项目使用Vite创建TypeScript项目是当前最优选择npm create vitelatest task-board --template vue-ts cd task-board npm install element-plus pinia vueuse/core配置Element Plus按需导入vite.config.tsimport AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ] })1.2 状态管理架构设计采用Pinia作为状态管理中心定义任务存储模块stores/taskStore.tsinterface Task { id: string title: string date: string // YYYY-MM-DD格式 status: pending | progress | completed priority?: number } export const useTaskStore defineStore(tasks, { state: () ({ tasks: [] as Task[], currentDragging: null as string | null }), getters: { getTasksByDate: (state) (date: string) { return state.tasks.filter(task task.date date) } }, actions: { addTask(task: OmitTask, id) { this.tasks.push({ ...task, id: crypto.randomUUID() }) }, updateTaskDate(taskId: string, newDate: string) { const task this.tasks.find(t t.id taskId) if (task) task.date newDate } } })2. 日历核心功能实现2.1 基础日历渲染创建TaskCalendar.vue组件template el-calendar v-modelcurrentDate template #date-cell{ data } CalendarCell :datedata.day / /template /el-calendar /template script setup langts import { ref } from vue const currentDate ref(new Date()) /script2.2 日期单元格深度定制开发CalendarCell.vue组件实现高级渲染template div classdate-cell dragover.preventhandleDragOver drophandleDrop div classdate-number{{ dayNumber }}/div div v-ifhasTasks classtask-indicators el-tag v-fortask in pendingTasks :keytask.id typeinfo draggable dragstarthandleDragStart(task.id) {{ task.title }} /el-tag el-progress v-ifinProgressTasks.length :percentageprogressPercentage :show-textfalse :stroke-width4 / /div /div /template script setup langts import { computed } from vue import { useTaskStore } from /stores/taskStore const props defineProps{ date: string }() const taskStore useTaskStore() const dayNumber computed(() props.date.split(-)[2]) const tasks computed(() taskStore.getTasksByDate(props.date)) const hasTasks computed(() tasks.value.length 0) const pendingTasks computed(() tasks.value.filter(t t.status pending) ) const inProgressTasks computed(() tasks.value.filter(t t.status progress) ) const progressPercentage computed(() { const total tasks.value.length const done tasks.value.filter(t t.status completed).length return Math.round((done / total) * 100) }) const handleDragStart (taskId: string) { taskStore.currentDragging taskId } const handleDrop () { if (taskStore.currentDragging) { taskStore.updateTaskDate(taskStore.currentDragging, props.date) } } /script style scoped .date-cell { height: 100%; padding: 4px; position: relative; } .date-number { font-weight: bold; margin-bottom: 4px; } .task-indicators { display: flex; flex-direction: column; gap: 2px; } /style3. 任务管理功能扩展3.1 任务创建与编辑实现任务表单组件TaskForm.vuetemplate el-dialog v-modelvisible title任务编辑 el-form :modelform label-width80px el-form-item label任务标题 el-input v-modelform.title / /el-form-item el-form-item label日期 el-date-picker v-modelform.date typedate formatYYYY-MM-DD value-formatYYYY-MM-DD / /el-form-item el-form-item label优先级 el-rate v-modelform.priority / /el-form-item el-form-item label状态 el-select v-modelform.status el-option label待处理 valuepending / el-option label进行中 valueprogress / el-option label已完成 valuecompleted / /el-select /el-form-item /el-form template #footer el-button clickvisible false取消/el-button el-button typeprimary clickhandleSubmit确认/el-button /template /el-dialog /template script setup langts import { reactive, ref } from vue import { useTaskStore } from /stores/taskStore const taskStore useTaskStore() const visible ref(false) const form reactive({ title: , date: , status: pending, priority: 0 }) const open (date?: string) { if (date) form.date date visible.value true } const handleSubmit () { taskStore.addTask(form) visible.value false resetForm() } const resetForm () { Object.assign(form, { title: , date: , status: pending, priority: 0 }) } defineExpose({ open }) /script3.2 上下文菜单集成使用VueUse的useContextMenu增强交互import { useContextMenu } from vueuse/core const { x, y, isShown } useContextMenu() const handleCellRightClick (event: MouseEvent, date: string) { event.preventDefault() x.value event.clientX y.value event.clientY isShown.value true // 存储当前操作的日期 currentContextDate.value date }4. 高级功能与性能优化4.1 虚拟滚动优化对于包含大量任务的日期实现虚拟滚动template el-scrollbar v-iftasks.length 5 height120px classtask-scroll div v-fortask in visibleTasks :keytask.id classvirtual-task :style{ height: ${itemHeight}px } {{ task.title }} /div /el-scrollbar div v-else classtask-list !-- 常规渲染 -- /div /template script setup langts import { useVirtualList } from vueuse/core const itemHeight 36 const { list: visibleTasks } useVirtualList( tasks, { itemHeight, overscan: 2 } ) /script4.2 本地持久化存储使用Pinia插件实现状态持久化import { createPinia } from pinia import { watch } from vue const pinia createPinia() pinia.use(({ store }) { const savedState localStorage.getItem(store_${store.$id}) if (savedState) { store.$patch(JSON.parse(savedState)) } watch( () store.$state, (state) { localStorage.setItem(store_${store.$id}, JSON.stringify(state)) }, { deep: true } ) })5. 主题定制与响应式设计5.1 动态主题切换利用CSS变量实现主题系统:root { --calendar-bg: #ffffff; --date-cell-hover: #f5f7fa; --task-pending: #e6a23c; --task-progress: #409eff; --task-completed: #67c23a; } .dark-mode { --calendar-bg: #1a1a1a; --date-cell-hover: #2d2d2d; }在组件中动态应用el-calendar :style{ --calendar-bg: theme dark ? #1a1a1a : #ffffff, backgroundColor: var(--calendar-bg) } !-- ... -- /el-calendar5.2 移动端适配策略使用媒体查询优化移动体验media (max-width: 768px) { .el-calendar-table .el-calendar-day { height: 60px !important; padding: 2px; } .task-indicators { display: none; } .date-number::after { content: attr(data-task-count); position: absolute; top: -5px; right: -5px; background: var(--el-color-primary); color: white; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; display: flex; align-items: center; justify-content: center; } }在真实项目中这种深度定制的日历看板可以显著提升团队的任务管理效率。一个实用的技巧是为不同优先级任务添加视觉差异——比如用红色边框标记高优先级任务这样即使在不展开详情的情况下也能快速识别关键事项。

更多文章