这是使用vuetify3
可以开发的一个待办事项管理实例。
它主要包含事项概览、我的项目、我的团队。其中事项概览中以列表的方式列出所有的事项,在这里可以添加事项、对事项进行排序。
vuetify官网的组件部分有大量控件,我想绝大部分应用使用这些控件拼凑就足够酷了。 一个快捷的方式是:到此官网找控件,把代码拷贝出来,修改一下直接使用。
请注意:
事项数据是存储在google的firebase
中的,如果访问不便,可以使用代码中也提供的本地数据。
新建项目
可以使用之前的文章:用最快捷的方法创建vuetify3项目 的方法新建项目。
事项概览
事项概览是使用栅格(Grid)实现的,当然也可以用数据表格(Data tables)实现。使用栅格(Grid)对格式的控制可以更灵活一些。
可以在 veutify官网 选择一款栅格(Grid)
,可以在网页上直接查看实际效果,也可以直接把代码拷贝出来使用。
修改 pages
文件夹中的 index.vue
,代码如下:
<template>
<h1 class="text-grey">事项概览</h1>
<v-container fluid class="my-5">
<v-layout row justify-start class="mb-3">
<v-btn small flat color="grey" @click="sortBy('title')">
<v-icon left small>mdi-folder</v-icon>
<span class="text-lowercase">按事项名称</span>
<v-tooltip activator="parent" location="top"
><span>按事项名称重新排序</span></v-tooltip
>
</v-btn>
<v-btn small flat color="grey" @click="sortBy('person')" class="ml-3">
<v-icon small left>mdi-account</v-icon>
<span class="text-lowercase">按人员</span>
<v-tooltip activator="parent" location="top"
><span>按事项负责人重新排序</span></v-tooltip
>
</v-btn>
</v-layout>
<v-card flat v-for="project in projects" :key="project.title">
<v-layout :class="`pa-3 project ${project.status}`">
<v-row wrap>
<v-col cols="6">
<div class="text-caption text-grey">事项名称</div>
<div>{{ project.title }}</div>
</v-col>
<v-col cols="2">
<div class="text-caption text-grey">负责人</div>
<div>{{ project.person }}</div>
</v-col>
<v-col cols="2">
<div class="text-caption text-grey">期限</div>
<div>{{ project.due }}</div>
</v-col>
<v-col cols="2">
<div class="text-caption text-grey">状态</div>
<v-chip :class="`${project.status} text-white my-2`">{{
project.status
}}</v-chip>
</v-col>
</v-row>
</v-layout>
<v-divider />
</v-card>
</v-container>
</template>
<script setup>
import { ref } from "vue";
// 这是静态数据,请根据实际情况修改
/*
const projects = ref([
{
title: "设计网站的新主题",
person: "火云",
due: "2024-3-1",
status: "overdue",
content:
"为小微企业客户XXX设计一个新的网站主题,包括首页,产品页,关于页等多个页面。",
},
{
title: "实现首页的产品展示",
person: "八戒",
due: "2024-3-10",
status: "complete",
content:
"编码实现首页的产品展示功能。",
},
{
title: "实现二级页面的产品展示",
person: "悟空",
due: "2024-3-20",
status: "ongoing",
content:
"编码实现网站的二级页面的产品展示功能。",
},
{
title: "产品测试",
person: "悟净",
due: "2024-4-2",
status: "overdue",
content:
"测试产品的新功能,确保产品的稳定性。",
},
]);
*/
import { getAllProjects } from "@/fb";
const projects = ref([]);
const getAll = () => {
getAllProjects().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
projects.value.push(doc.data());
});
});
};
getAll();
const sortBy = (prop) => {
projects.value.sort((a, b) => (a[prop] < b[prop] ? -1 : 1));
};
</script>
<style scoped>
.project.complete {
border-left: 4px solid #3cd1c2;
}
.project.ongoing {
border-left: 4px solid #ffaa2c;
}
.project.overdue {
border-left: 4px solid #f83e70;
}
.v-chip.complete {
background: #3cd1c2;
}
.v-chip.ongoing {
background: #ffaa2c;
}
.v-chip.overdue {
background: #f83e70;
}
</style>
这里使用
getAll
从firebase
中加载数据,如果使用firebase
不方便,可以直接使用const projects
定义的数据。
我的项目
在component
文件夹中添加projects.vue
。
此处使用 计算属性:myProjects 筛选当前用户的事项,它使用v-expansion-panels
事项条目列表,点击每一行可以展开显示详情。
<template>
<h1 class="text-grey">项目</h1>
<div class="ma-8">
<v-expansion-panels variant="accordion">
<v-expansion-panel v-for="project in myProjects" :key="project.title">
<v-expansion-panel-title>{{ project.title }}</v-expansion-panel-title>
<v-expansion-panel-text>
<v-card>
<v-card-text class="px-4 text-grey">
<div class="font-weight-bold">期限 {{ project.due }}</div>
<div>{{ project.content }}</div>
</v-card-text>
</v-card>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</div>
</template>
<script setup>
import { ref,computed } from "vue";
// 这是静态数据,请根据实际情况修改
/*
const projects = ref([
{
title: "设计网站的新主题",
person: "火云",
due: "2024-3-1",
status: "overdue",
content:
"为小微企业客户XXX设计一个新的网站主题,包括首页,产品页,关于页等多个页面。",
},
{
title: "实现首页的产品展示",
person: "八戒",
due: "2024-3-10",
status: "complete",
content:
"编码实现首页的产品展示功能。",
},
{
title: "实现二级页面的产品展示",
person: "悟空",
due: "2024-3-20",
status: "ongoing",
content:
"编码实现网站的二级页面的产品展示功能。",
},
{
title: "产品测试",
person: "悟净",
due: "2024-4-2",
status: "overdue",
content:
"测试产品的新功能,确保产品的稳定性。",
},
]);
*/
import { getAllProjects } from "@/fb";
const projects = ref([]);
const getAll = () => {
getAllProjects().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
projects.value.push(doc.data());
});
});
};
getAll();
//自动筛选项目
const myProjects = computed(() => {
return projects.value.filter((project) => project.person === "火云");
});
</script>
我的团队
在component
文件夹中添加team.vue
。
此页面用于显示团队内容,它使用栅格(Grid)
做整体模具,每一个格子里面用一个v-card
显示一个成员信息。
<template>
<h1 class="text-grey">团队</h1>
<v-container class="my-5">
<v-row wrap>
<v-col xs12 sm6 md4 lg3 v-for="person in team" :key="person.name">
<v-card flat class="text-center ma-3" elevation="16">
<v-responsive class="pt-4">
<v-avatar size="100" color="grey">
<img :src="person.avatar" />
</v-avatar>
</v-responsive>
<v-card-text>
<div class="text-subtitle-1">{{ person.name }}</div>
<div class="text-grey">{{ person.role }}</div>
</v-card-text>
<v-card-actions>
<v-btn flat color="grey">
<v-icon>mdi-message</v-icon>
<span>消息</span>
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup>
const team = [
{ name: "玄奘", role: "客户经理", avatar: "/image/avatar-1.png" },
{ name: "悟空", role: "主程", avatar: "/image/avatar-2.png" },
{ name: "八戒", role: "程序员", avatar: "/image/avatar-3.png" },
{ name: "悟净", role: "程序员", avatar: "/image/avatar-4.png" },
{ name: "小白龙", role: "测试", avatar: "/image/avatar-5.png" },
];
</script>
头像放在了
public
文件夹内,这样是为了方便javascript使用。
到此为止,主要页面都已经准备好了,下面再用一些其它控件把这些页面串联起来。
添加事项控件
在component
中新增Popup.vue
。
这里使用v-dialog
弹窗实现添加事项的主要逻辑功能,当然,其中也展示了:
- 前端数据校验以及提交项目信息时的提交按钮遮罩效果;
- 日历控件的使用以及日期的格式化;
- 在提交后发出
projectAdded
事件,订阅者可以根据此事件做个性化处理; - 默认情况下只显示 添加事项 按钮,对话框默认情况下不显示,这样下一步该控件放在
NavBar
控件中。
<!--npm install date-fns-->
<template>
<v-dialog max-width="600" v-model="dialog">
<template v-slot:activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
color="success"
text="添加事项"
variant="flat"
></v-btn>
</template>
<template v-slot:default="{ isActive }">
<v-card title="添加新事项">
<v-card-text>
<v-form class="px-3" @submit.prevent>
<v-text-field
v-model="form_data.title"
label="名称"
prepend-icon="mdi-folder"
:rules="[rules.required, rules.min, rules.max]"
></v-text-field>
<v-textarea
v-model="form_data.content"
label="详情"
prepend-icon="mdi-note"
:rules="[rules.required, rules.min, rules.max]"
></v-textarea>
<v-menu v-model="menu" :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-text-field
v-model="formatedDate"
label="期限"
prepend-icon="mdi-calendar-month"
v-bind="props"
readonly
></v-text-field>
</template>
<v-date-picker v-model="form_data.due"></v-date-picker>
</v-menu>
<div align="right" class="mt-4">
<v-btn
type="submit"
flat
@click="submit"
color="success"
:loading="loading"
><span>保存新项目</span></v-btn
>
<v-btn
flat
text="关闭"
@click="isActive.value = false"
class="ml-2"
></v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</template>
</v-dialog>
</template>
<script setup>
import { ref, computed,watch,defineEmits} from "vue";
import format from "date-fns/format";
import { addProject } from "@/fb";
const form_data = ref({
title: "",
content: "",
due: null,
});
const loading = ref(false);
const dialog = ref(false);
const menu = ref(false);
//监控due的数据变化,如果变化了就关闭菜单
watch(
() => form_data.value.due,
() => {
menu.value = false;
}
);
// 格式化日期
const formatedDate = computed(() => {
if (form_data.value && form_data.value.due) {
//console.log(form_data.value.due);
return format(form_data.value.due, "yyyy-MM-dd");
} else {
return "";
}
});
//校验规则
const rules = {
required: (value) => !!value || "不能为空",
min: (value) => value.length >= 3 || "最少3个字符",
max: (value) => value.length <= 20 || "最多20个字符",
};
const emits = defineEmits(["projectAdded"]);
const submit = () => {
loading.value = true;
if (
!form_data.value.title ||
!form_data.value.content ||
!form_data.value.due
) {
loading.value = false;
return;
}
const newProject = {
title: form_data.value.title,
content: form_data.value.content,
person: "火云",
due: format(form_data.value.due, "yyyy-MM-dd"),
status: "ongoing",
};
addProject(newProject)
.then(() => {
console.log("事项添加成功!");
console.log(JSON.stringify(form_data.value));
loading.value = false;
dialog.value = false;
emits("projectAdded");
})
.catch((error) => {
console.error("Error writing document: ", error);
loading.value = false;
});
};
</script>
导航栏(NaveBar)
在componets
中添加NavBar.vue
。它的主要功能有:
- 使用
v-menu
实现了 下拉菜单; - 使用
v-navigation-drawer
实现了左侧菜单; - 使用
v-snackbar
显示系统消息:当popup
控件添加项目成功时,这里会收到projectAdded
事件,改变v-snackbar
的v-model
使得它可以显示提示优雅的信息。
<template >
<div class="text-center ma-2">
<v-snackbar
v-model="snackbar"
:timeout="4000"
location="top"
color="success"
>
<span>太棒了,项目添加成功!</span>
<template v-slot:actions>
<v-btn variant="text" @click="snackbar = false"
>关闭</v-btn
>
</template>
</v-snackbar>
</div>
<v-app-bar prominent density="compact" app>
<v-app-bar-nav-icon
variant="text"
@click.stop="drawer = !drawer"
></v-app-bar-nav-icon>
<v-toolbar-title>待办事项</v-toolbar-title>
<v-spacer></v-spacer>
<!--dropdown menu-->
<v-menu open-on-hover>
<template v-slot:activator="{ props }">
<v-btn flat color="grey" v-bind="props"> 下拉菜单 </v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in side_links"
:key="index"
:to="item.route"
>
<v-list-item-title>{{ item.text }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-btn append-icon="mdi-exit-to-app"> 退出 </v-btn>
</v-app-bar>
<v-navigation-drawer
:location="$vuetify.display.mobile ? 'bottom' : undefined"
temporary
v-model="drawer"
app
>
<v-card class="text-center pa-2" flat>
<v-col class="mt-5">
<v-avatar size="100">
<img src="/image/avatar-1.png" />
</v-avatar>
<p class="text-grey mt-1 text-subtitle-1">火云</p>
</v-col>
</v-card>
<v-card flat class="text-center">
<popup @projectAdded="snackbar = true"></popup>
</v-card>
<v-list>
<v-list-item
v-for="link in side_links"
:key="link.text"
:prepend-icon="link.icon"
:title="link.text"
:value="link.text"
:to="link.route"
>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script setup>
import { ref } from "vue";
const drawer = ref(false);
const snackbar = ref(false);
const side_links = [
{ icon: "mdi-view-dashboard", text: "事项概览", route: "/" },
{ icon: "mdi-folder", text: "我的项目", route: "/projects" },
{ icon: "mdi-account", text: "我的团队", route: "/team" },
];
</script>
组合控件,见证成果
修改App.vue
,下面是代码:
<!--
<v-app>: Vuetify 的根组件,提供应用的主题和基础布局。
<v-main>: 包裹主要内容的组件,确保应用内容居中且响应式布局。
<router-view />: Vue Router 的占位符组件,显示当前匹配的路由视图。
-->
<template>
<v-app>
<nav-bar />
<v-main >
<router-view />
</v-main>
</v-app>
</template>
<script setup>
//
</script>
把nav-bar
放在App.vue
中使得该工具栏应用于整个应用。
在应用程序的根目录下运行:
pnpm dev
程序启动后会出现下图所示页面,并且自动打开浏览器:
总结
通过几个简单的控件组合,我们就实现了一个五脏俱全的待办事项管理功能,这里面也使用了很多控件可以应用在其它很多场合,相信能对您使用vuetify3
起到入门的功效。
这里列出了主要代码逻辑,全部代码请在后面列出的地址下载。
查看完整代码
🪐祝好运🪐