前言:
自学习以来一直想开发一个自己的项目,但迟迟没有灵感。前些日子在某站上看到一个up主开发的记事项目,发觉记事本原来也是大有可为之处的。于是自己也做了这样一个麻雀虽小五脏俱全的Web应用。希望能获得大佬们的建议,给和我同样的新人们一些启发。
项目概述
技术栈
前端:Vue2全家桶+less+axios+ECharts+cropper.js
后端:node.js+express+mysql+jsonwebtoken
在线预览地址:www.w-catnip.vip:2333/
源码地址:github.com/W-catnip/Xi…
功能概述
- 支持跳转不同日期
- 支持添加指定日期的待办事项
- 支持添加每日的固定打卡事项
- 支持事项的编辑、删除、完成状态更改
- 支持当前日期的事项完成情况统计
- 支持七天内的待办事项完成情况统计
- 支持每日打卡全部完成情况统计
- 支持用户的注册与登录
- 支持用户资料更改及头像裁剪后更改
项目预览
1. 登录页
2. 主页
3. 数据页
4. 设置页
注:由于录屏限制,文件选择未在该图内展示
项目细节
尽管功能简单,但在开发过程中还是有遇到各种各样的问题,在下文中会记录一些印象较深的需求解决方案,如有错漏与不足,请各位不吝赐教。
日历的实现与折叠
实现目标:一个折叠时自动显示本周日期的可选择日期表。
实现
因为每个月的第一天是周几与每月的总天数不确定,所以我们只需要知道本月的第一天是周几,本月的天数,以及上一个月最后一天是多少号,将需要的42天做成一个数组,就可以实现一个7*6的日历表。
<script>
...
data(){
// 获得当前年月日
return: {
curDate: new Date().getDate(),
curMonth: new Date().getMonth() + 1,
curYear: new Date().getFullYear(),
highLight: null,
}
computed:{
// 日历偏移参数,获得本月第一天是周几,用于空出需要上月日期补充的部分。
delay() {
let day = new Date(this.curYear, this.curMonth - 1, 1).getDay();
let delay = day - 1;
if (day === 0) {
delay = 6;
}
return delay;
},
// 返回需要渲染的本月日期表
monthList() {
// new Date()最后一个参数是0,表示当月最后一天
let date = new Date(this.curYear, this.curMonth, 0);
// 通过当月最后一天的日期号,得到当月有多少天
let days = date.getDate();
let list = [];
// 获取上月最后一天的日期
let lastDays = new Date(this.curYear, this.curMonth - 1, 0).getDate();
for (let i = 1; i < 43; i++) {
// 用上个月的日期填充日历空缺部分
if (i < this.delay + 1) {
list.push({
id: i, // 日期标识,用于高亮
value: lastDays - this.delay + i,// 日期号
notThisMonth: true, // 标记不是本月的日期,添加不同样式,判断是否绑定点击事件
});
// 本月日期
} else if (i < days + this.delay + 1) {
list.push({
id: i,
value: i - this.delay,
notThisMonth: false,
});
// 下月日期填充空缺部分
} else {
list.push({
id: i,
value: i - days - this.delay,
notThisMonth: true,
});
}
}
return list;
},
}
...
</script>
// 渲染
<template>
...
<ul>
<li v-for="day in monthList" :key="day.id" class="monthList-day" :class="{
notThisMonth: day.notThisMonth,
selectDay: day.id === highLight,
}"
@click="!day.notThisMonth && handleSelectDay(day)">
{{ day.value }}
</li>
</ul>
...
</template>
折叠
如何在折叠后保证只显示本周日期?
我采取的是一种比较取巧的方法,因为在该应用中,始终有一个日期处于被选择状态,而且每一行只显示7个日期,所以只需要获得该日期的id,就可以得知它在第几行。在点击折叠后计算需要向上移动多少个单位让其刚好在显示区域,而将其他日期表进行隐藏,就可以定位显示到所选择的日期所在的星期。
<script>
...
computed:{
// 日历盒子的位移参数即被选择的日期所在的行数。
boxDisplacement() {
return Math.ceil(this.highLight / 7);
},
},
methods:{
// 选择日期
handleSelectDay(day) {
this.highLight = day.id;
this.curDate = day.value;
},
// 展开收起
changeListOpen() {
this.listIsOpen = !this.listIsOpen;
if (!this.listIsOpen) {
// 移动日期盒子只让本周日期显示
this.$refs.monthList.style.top = `-${this.boxDisplacement * 3 + 1.7}rem`;
} else {
this.$refs.monthList.style.top = "-2rem";
}
},
},
mounted(){
//初始化
this.highLight = this.curDate + this.delay;
this.$refs.monthList.style.top = `-${this.boxDisplacement * 3 + 1.7}rem`;
}
...
</script>
<template>
<!--添加一个遮罩层,当折叠时overflow设置为hidden,以达到只显示本周日期的目的。-->
<div class="mask" :class="{ openmask: listIsOpen }">
<!-- 本月日期表 -->
<div class="monthList" ref="monthList" :class="{ listopen: listIsOpen }">
...
<ul>
<li v-for="day in monthList" :key="day.id" class="monthList-day" :class="{
notThisMonth: day.notThisMonth,
selectDay: day.id === highLight,
}" @click="!day.notThisMonth && handleSelectDay(day)">
{{ day.value }}
</li>
</ul>
</div>
</template>
以上就是日历表的核心逻辑,最后完成的效果如下:
今日完成情况圆环概览图
这个图是用ECharts做的,放在这里倒不是因为它很难,而是因为我觉得这个图还挺有意思的,所以把配置贴上来分享一下。
methods: {
draw() {
// 基于准备好的dom,初始化echarts实例
var loopChart = this.$echarts.init(document.getElementById("loopChart"));
// 绘制图表
loopChart.setOption({
polar: {
radius: [120, "60%"],
},
color: ["#70c3f3", "#70f3f1"],
angleAxis: {
max: 1,
show: false,
startAngle: 75,
},
radiusAxis: {
type: "category",
show: false,
data: ["今日待办事项完成率", "今日打卡完成率"],
},
tooltip: {},
series: {
type: "bar",
data: this.rateData, //完成率数组
barCategoryGap: "0",
colorBy: "data",
coordinateSystem: "polar",
roundCap: true,
barWidth: 10,
showBackground: true,
},
});
},
},
watch: {
rateData() {
// 更改数据时销毁图表不然控制台会显示“这里已经有一个ECharts表了”
this.$echarts.dispose(loopChart);
this.draw();
}
},
mounted() {
// 初始化
if (!loopChart) this.draw();
},
提示弹窗
很多时候当用户点击某一个按钮时,除了事件本身进行响应,还需要一个弹窗来进行反馈,来告诉用户请求是否成功与错误信息,这是用户体验中较重要的一环。而点击事件几乎处处存在,所以我需要一个全局的弹窗,在任一组件内都可进行调用。
为了实现这一效果,我使用的是Vue.extend(),对于它,官方的用法定义是:“基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。”它可以创建一个构造函数,然后通过该构造函数可以将组件挂载到指定的元素上。
实现步骤大概如下:
1.正常编写弹窗组件;
2.创建一个js文件,在其中编写编程式使用组件的逻辑代码,即Vue.extend的使用、组件挂载和相关方法、并暴露出一个函数;
3.将该函数挂载到全局;
这样就可以在任意组件调用该函数来创建提示弹窗。但是在Vue3中已经移除了extend,采用了createApp作为替代。
其他内容
- 项目中使用了许多svg图标,于是按照网上的方式,全局封装了一个svg-icon组件,下载了svg-sprite-loader插件,实现了svg图标的优雅使用。
- 利用cropper.js实现了图片上传时的剪裁功能,本是想将blob上传到数据库进行存储以及下载操作,但奈何每次下载下来blob都变成了json而不是图片格式,捣鼓了几天无果后,采用了一个笨方法:转为base64传给后端进行存储。
- 关于后端,使用express以及mysql简单实现了curd操作,使用了token进行鉴权操作。配置并封装了axios以进行前后端的交互。
- 之所以不使用时下流行的Vue3,是因为想先熟悉Vue2再去使用Vue3进行项目的开发
最后
以上就是该项目的具体情况,希望大家多多支持,喜欢的话点个小小的⭐就是对我莫大的支持,也希望掘友们能指出一些本项目的不足之处。最后的最后,感谢您的浏览。