项目地址 https://github.com/hongchh/timeline-x
添加每一天的时间记录,修改某天的时间记录(因为可能记错或者忘了记某项活动)
每天的记录可以有多项活动,每项活动有对应的时间
每项活动划分到特定的类型
2016-01-20,星期五1. 学习Vue, 5h,[学习]2. 跑步,0.5h,[运动]3. 看霹雳布袋戏,2h,[休闲]
按照月份统计每天的总时间,按照年份统计每月的总时间,按照分类统计各项内容的总时间
以图表形式展示月份时间支出和年份时间支出的变化情况
以图表形式展示各种类型的活动的时间支出情况
计算总时间和平均时间
时光轴形式进行活动记录展示,便于回顾总结
轮播图形式进行活动记录展示,便于回顾总结
{ "items":[{ "content":"string", "time":"number", "type":"string"}], "year":"number", "month":"number", "date":"number", "day":"number"}
数据示例
{ "items":[{ "content":"学习Vue", "time":"5", "type":"学习"}, { "content":"跑步", "time":"0.5", "type":"运动"}, { "content":"看霹雳布袋戏", "time":"2", "type":"休闲"}], "year":"2016", "month":"01", "date":"20", "day":"5"}
整个应用分成2个主要界面:【主界面】,【权限界面】
【权限界面】用于用户登录,也是应用的启动界面
【主界面】包含3个子界面:【管理】,【时光轴】,【时光展】
【时光轴】和【时光展】用于时光展示
【管理】用于展示数据分析结果以及编辑时光记录(添加/修改)
【权限界面】验证成功之后跳转到【主界面】
【主界面】默认展示【时光轴】界面
【主界面】顶栏可以选择跳转到【管理】、【时光轴】或【时光展】界面
【主界面】顶栏选择“锁屏”之后回到【权限界面】
└─App:挂载整个应用
├─Auth:【权限界面】
│ └─StarFlow:【权限界面】底部的动画
└─Main:【主界面】的基本结构
├─Management:【管理】
│ ├─TimeAnalysisPerMonth:月份时间分析组件
│ ├─TimeAnalysisPerYear:年份时间分析组件
│ ├─TimeAnalysisByType:分类时间分析组件
│ └─EditTimeRecord:时间记录编辑组件
├─Timeline:【时光轴】
└─TimeSlide:【时光展】
└─build:构建用到的相关文件├─config:构建的配置文件├─server:应用的服务器源码│ ├─controller:服务端业务逻辑│ ├─model:数据存储逻辑│ ├─static:静态文件│ ├─views:应用的视图文件│ ├─app.js:express服务器配置文件│ └─server.js:服务器启动文件├─src:前端开发源码│ ├─assets:图片等静态资源│ ├─components:前端组件│ ├─router:前端路由│ ├─store:vuex的store│ ├─App.vue:应用的外层结构│ └─entry.js:应用的入口文件└─static:前端开发过程中用到的静态文件 └─data:存放伪数据以及伪数据生成器
使用vue-cli生成一个webpack项目模板
vue init webpack timeline-x
生成之后为了支持jade+sass开发,还需要安装相关的依赖
npm install node-sass --save-dev
npm install sass-loader --save-dev
npm install jade --save-dev
配置完成之后我们就可以在项目中使用jade和sass来进行开发了,.vue文件的模板如下,注意在template标签和style标签里面使用lang属性说明jade和sass。
div#auth h1 Auth Pageexportdefault{ name:'auth'}#authborder: 1pxsolidred
按照之前划分好的组件,在src/components文件夹里面准备好相关文件。暂时不需要写入各组件的内容,就按照上面的模板一样写个标题说明这是哪个组件即可。
组件都准备好之后安装路由
npm install vue-router --save
安装完成之后需要在使用路由的时候将其导入,我这里选择在router/index.js文件里面导入。
import Vue from'vue'import Router from'vue-router'Vue.use(Router)
按照前面确定好的页面跳转关系配置路由信息,然后测试界面跳转是否正常。
constrouter =newRouter({ mode:'history', routes: [ { path:'/auth', component: Auth }, { path:'/main', component: Main, children: [ { path:'management', component: Management }, { path:'timeline', component: Timeline }, { path:'time-slide', component: TimeSlide }, { path:'', redirect:'timeline'} ] }, { path:'/', redirect:'/auth'} ]})
UI方面使用了一些element-ui的组件,因此还需要先安装element-ui。同样地安装之后需要导入,导入方法类似之前的vue-router。注意这里还需要导入element-ui的样式文件index.css。
npm install element-ui --save
import Vue from'vue'import ElementUI from'element-ui'import'element-ui/lib/theme-default/index.css'Vue.use(ElementUI)
3个时间分析图表使用的是echarts,需要先安装echarts。安装之后import需要用到的图表就行了,这样经过webpack构建之后的产品代码就只有用到的图表的那部分代码了,可以较少产品代码体积。
npm install echarts --save
由于我只用到了柱状图和圆饼图,然后图表上面还使用了标题和提示等组件,所以只需要在entry.js里面导入这些组件即可。
import'echarts/lib/chart/pie'import'echarts/lib/chart/bar'import'echarts/lib/component/tooltip'import'echarts/lib/component/title'import'echarts/lib/component/legend'
到步骤【2】准备好的文件里面开始编写组件。在template标签中编写组件的HTML结构,然后在style标签中调节样式,业务逻辑则是放在script标签中。下面我用Auth.vue来作为例子,其他组件请参考github仓库里面的代码。
权限界面比较简单(参考上面的成品展示图),HTML结构如下,使用element-ui的栅格布局,同时用了另外一个动态背景组件star-flow。
div#authstar-flowdiv#auth-inputel-row(:gutter="20") el-col(:span="8", :offset="8") el-tooltip(:disabled="disabled", :content="errorTip", placement="bottom-start", effect="light") el-input(placeholder="请输入密码", v-model="password",type="password") template(slot="append") el-button(@click="signin") Go
样式如下,设置一下背景图、宽高度之类的。
#authwidth:100%height:100%background: url(../../assets/auth-bg.jpg) no-repeatbackground-size:100%100%background-attachment: fixed#auth-inputposition: absolute width:100%height:10%top:45%
下面是js部分了,使用es6的写法,为了使用另一个组件,需要先将其import进来然后添加到components里面。export是将当前的组件导出,其他模块才能使用到。data里面写该组件用到的数据项,methods则是这个组件里面需要的方法函数了。(有点类似angular的controller)
import StarFlow from'./StarFlow'exportdefault{ name:'auth', components: { StarFlow }, data () {return{ password:'', disabled:true, errorTip:''} }, methods: { setTip (tip) {// 消息提示,1.5秒后自动关闭this.errorTip = tipthis.disabled =falsesetTimeout(() => {this.disabled =truethis.errorTip =''},1500) }, signin () {// 验证密码解除锁屏if(!this.password) {this.setTip('密码不能为空') }else{this.$store.dispatch('unlockScreen',this.password) .then((err) => {if(err)this.setTip('密码错误')elsethis.$router.replace('/main') }) } } }}
调整组件的样式,并且改进应用的样式,添加过渡动画。过渡动画直接在router-view外面套一个transition,然后编写相应的过渡样式即可,很简便。
该项目由6个组件需要用到同一份数据,1个记录编辑组件、2个时光展示组件和3个数据分析组件。为了方便数据管理,同时也好制造机会学习Vuex,因此我这里引入vuex来进行应用的状态管理。安装vuex,然后在store/index.js里面导入vuex。
npm install vuex --save
import Vue from'vue'import Vuex from'vuex'Vue.use(Vuex)
定义1个store存放应用的全局状态:锁屏变量和时间记录。默认开启锁屏,所以lockScreen设置为true,而timeRecords则是一个时间记录数据,里面包含多条记录。这些数据在应用启动接触锁屏之后向服务器获取,在还没有开发服务器的时候,为了完成前端开发可以先在这里写一些伪数据进去。
conststore =newVuex.Store({ state: { lockScreen:true, timeRecords: [] }, mutations, actions})
在store/actions.js和store/mutations.js里面定义这些全级状态可能发生的变化。如果涉及到异步操作,则将这些涉及异步的变化放到actions.js里面,例如向服务器获取数据这个操作就是一个异步的,应该将其代码放在action里面。根据Vuex文档的介绍,mutation的代码必须要是同步的。
由于涉及跟服务器的交互,所以我们还需要一个提供http服务模块。上网查了查,有vue-resource这个东西可以用,不过好像更推荐使用axios,因此我这里也选择使用axios。下面进行安装和导入。
npm install axios --save
import axios from'axios'
准备好了之后可以开始编写了,下面是actions.js的代码,其他代码请参考github仓库。实现了后台交互功能,然后通过commit相应的mutation来更新store里面的状态。注释部分的代码是在前端开发还没有服务器的时候使用的开发代码。
exportdefault{ unlockScreen ({commit}, password) {// 验证密码,接触锁屏// 使用"npm run dev"启动时候请解除下面代码的注释,注释后面的POST代码// return new Promise((resolve, reject) => {// commit('UNLOCK')// resolve(false)// })returnaxios.post('/api/check', { password: password }).then((res) => {if(!res || res.status !==200|| res.data.err) {returntrue}else{ commit('UNLOCK')returnfalse} }) }, addRecord ({commit}, record) {// 添加记录// 使用"npm run dev"启动时候请解除下面代码的注释,注释后面的POST代码// return new Promise((resolve, reject) => {// commit('UPDATE_RECORD', record)// resolve(false)// })returnaxios.post('/api/add', record).then((res) => {if(!res || res.status !==200|| res.data.err) {returntrue}else{ commit('UPDATE_RECORD', record)returnfalse} }) }, fatchData ({commit}) {// 获取数据axios.get('/static/data/data.json').then((res) => {if(res.status ===200) { commit('FATCH_DATA', res.data) } }) }}
为了让应用有更多数据可以呈现,需要自行编造一些伪数据,我写了一个伪数据生成器,即static/data/data-generator.js。运行之后产生100多条时间记录并存放到json文件里面。具体请参考github仓库。
实现锁屏功能其实不难,只要在权限界面检验之后更新store里面的锁屏状态为false,在主界面点击顶栏的锁屏之后更新store里面的锁屏状态为true即可。但为了更完善,还需要在路由里面设置一个全局钩子,在进入相应界面之前检查一下store的锁屏状态,例如,在访问主界面的时候需要检查store里面的锁屏状态是否为true,如果是的话强行给它重定向到权限界面,因为这可能是用户通过手动修改URL来访问的。
这里有一个坑,在router的全局钩子里面访问不到store里面的状态。在stack-overflow里面找到了这个解决方案,把store也给import到router里面去,这样就可以访问到了。不知道有没有其他更优秀的方案,欢迎交流。
import store from'../store'router.beforeEach((to, from, next) => {// 对特定路径进行验证,增强锁屏功能if(to.path ==='/') { next('/auth') }elseif(to.path ==='/auth'&& !store.state.lockScreen) { next('/main') }elseif(to.path !=='/auth'&& store.state.lockScreen) { next('/auth') }else{ next() }})
前端开发基本完成之后就可以开始后台开发了,我后端使用的是express框架,按照MVC模式进行开发。由于本项目关注的是前端开发,所以后端就写得比较简单,也没有用上数据库,没有什么好讲的。主要有一点需要注意,为了方便,我更改了build的目标路径。用vue-cli生成的webpack项目build之后是在主目录下面生成一个dist文件夹来存放产品文件,我将其改成build之后静态文件放到server里面的static文件夹,而index.html则放到server里面的views文件夹,这样就不用手动去搬dist里面的文件给服务器用了。改动很简单,只需要该config/index.js里面的7-8行即可。
index: path.resolve(__dirname,'../server/views/index.html'),assetsRoot: path.resolve(__dirname,'../server'),
对接前端和后台的接口,调整代码,修正bug。如果一开始将接口想清楚、设计好的话这里基本没有什么难。本项目只有2个post数据和1个get数据的接口,没有难度。
大概用了5天写这个项目,基本入门了Vue,还是一次蛮不错的体验。为了多点练习强行加入了vuex,虽然官方推荐不要在小项目里面使用vuex,但我觉得引入vuex之后还是可以解决很多问题,思路也清晰了很多。在用vuex之前的想法是在一个外层的组件里面获取数据然后传递给子组件。后来发现如果我在编辑记录组件里面更新或者添加了一条记录,父组件和其他兄弟组件的数据不会收到更新。如果通过$emit来让父亲组件更新数据然后再使得兄弟组件更新,整个过程又太过繁琐。而Vuex的思路是弄一个全局状态来对数据进行管理,组件树上的组件都可以更新状态,状态变化之后会反馈到相应的其他组件上。这个思路很清晰,代码写起来也很顺利,也体验了vuex的强大,确实可以省事很多。
部分内容可能无法讲得很清楚,有兴趣了解的话请参考github仓库的代码。下面说说几点收获:
es6写起来感觉超棒
webpack比gulp优雅
vue值得尝试