本文档用于练习、完整记录视频课程的内容,原项目视频网址:https://www.bilibili.com/video/BV157411V7Dh?p=1
项目初始化(在vscode的terminal中执行)
C:\Users\Administrator\Desktop\vue_todo>npm init -y
#会生成package.json文件
{
"name": "vue_todo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
webpack用来打包(-D开发环境)
C:\Users\Administrator\Desktop\vue_todo>npm install -D webpack webpack-cli
#会增加node_modules目录存放安装的依赖包
#package.json文件增加下面几行内容(表示开发时依赖的2个包)
"devDependencies": {
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
}
编写项目入口index.html
#vscode通过!回车,快速创建html内容模板,直接输入div快速打开div标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
创建main.js 作用创建Vue根实例 挂载App组件(通过npm install vue来安装vue)
#创建src目录,在src目录下创建main.js
C:\Users\Administrator\Desktop\vue_todo>npm install vue
#package.json文件增加下面几行内容
"dependencies": {
"vue": "^2.6.12"
}
#main.js
//1、创建vue根实例
import Vue from 'vue'
new Vue({
el:'#app'
})
//2、挂载App组件
挂载App组件
//模板
<template>
<div>this is App</div>
</template>
<script>
//用于导出的组件名称
export default {
name: 'App'
}
</script>
<style scoped>
//样式
</style>
//创建vue根实例
import Vue from 'vue'
//导入App组件
import App from './App.vue'
//创建vue根实例
new Vue({
el:'#app',
//局部组建挂载
components:{
// 组件名:组件对象(对象通过App.vue导入)
App:App
},
//template: '<App/>'的作用将**组件标签'<App/>'**,插入替换<div id="app"></div>中的内容。
template: '<App/>'
})
//挂载App组件
使用webpack打包(因为浏览器不识别import Vue from 'vue')
#在项目根目录下,创建默认打包文件webpack.config.js
//导入path模块
const path = require('path')
module.exports = {
//导出2个对象,
//打包入口文件
entry: './src/main.js',
//打包的出口文件
output:{
filename: 'bundle.js',
path: path.resolve(__dirname,'dist') //path需要使用绝对路径,要引入node的path
}
}
在package.json文件中编写打包脚本,用于打包(前提:安装vue-loader)
#在script增加build这一行
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
#在控制台使用C:\Users\Administrator\Desktop\vue_todo>npm run build打包
安装vue-loader(依赖css-loader)
C:\Users\Administrator\Desktop\vue_todo>npm install -D vue-loader vue-template-compiler
C:\Users\Administrator\Desktop\vue_todo>npm install -D css-loader
配置打包规则和插件,修改默认webpack配置文件webpack.config.js
//导入path模块
const path = require('path')
//引入vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
//导出2个对象,
//打包入口文件
entry: './src/main.js',
//打包的出口文件
output:{
filename: 'bundle.js',
path: path.resolve(__dirname,'dist') //path需要使用绝对路径,要引入node的path
},
//打包规则,以.vue结尾用vue-loader打包
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
}]
},
//插件
plugins: [
new VueLoaderPlugin()
]
}
运行打包脚本:
C:\Users\Administrator\Desktop\vue_todo>npm run build
#mode: 'production',或者development打包的文件一个可读一个压缩不可读。
修改index.html 的js引入文件
<script src="./dist/bundle.js"></script>
处理webpack默认引入vue.common.js不是vue.js(配置后重新打包,在浏览器打开index.html)
#修改默认webpack配置文件webpack.config.js,在插件下增加
resolve: {
alias: {
'vue':'vue/dist/vue.js'
}
}
官方文档:https://www.webpackjs.com/loaders/file-loader/
安装
C:\Users\Administrator\Desktop\vue_todo>npm install -D file-loader
#安装后package.json,的devDependencies增加一个loader
配置
修改webpack.config.js的module增加一个test
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.(jpg|jpeg|png|svg)$/,
loader: 'file-loader'
}]
},
创建存放图片的目录如下(并放入bg.jpg图片):
修改App.vue,导入图片,(然后运行npm run build,在dist中生成名字哈希后的图片)
测试输出img的值,修改App.vue,重新打包,然后浏览器打开index.html,通过F12查看 console.log
打包不用hash文件名,保存原名,修改打包规则,增加选项配置,重新打包,再次访问index.html。
url-loader : 功能类似file-loader,在文件大小低于指定限制时,返回一个dataurl,将小图片以base64打包在js中,减少http请求次数。
安装
C:\Users\Administrator\Desktop\vue_todo>npm install -D url-loader
配置webpack.config.js,增加limit(单位byte),然后运行npm run build,可以看到只有2个文件被生成到dist目录
test: /\.(jpg|jpeg|png|svg)$/,
loader: 'url-loader',
options: {
name: '[name].[ext]',
limit: 2048
在assets 中放入2个小图片,再App.vue中引入
因为前面安装了css-loader,这里只安装一个style-loader
C:\Users\admin\Desktop\vue_todo>npm install -D style-loader
配置打包文件
创建css文件
在App.vue中引入css文件
import './assets/styles/test.css'
npm run build打包测试
目前stylus 作为node项目普遍使用的css预处理器被广泛应用于vue项目
vscode插件安装: language-stylus:提供语法高亮效果和一些支持。 Supremacy:自动化格式stylus的插件,定制stylue格式,比如是否要;,:
setting.json 设置()
//stylus 不适用{}
"stylusSupremacy.insertBraces": false,
//stylus 不适用;
"stylusSupremacy.insertSemicolons": false,
设置之后的stylus样子如下:
安装
C:\Users\Administrator\Desktop\vue_todo>npm install -D stylus stylus-loader
配置
编写styl文件
在App.vue中导入styl文件
打包,并测试访问index.html,通过F12发现,找不到图片
临时解决方案,将index.html放到dist目录,并修改js的引用位置
再次打包,并访问index.html查看已经生效。
修改App.vue,增加h1标签
再次build,并访问index.html(在杰哥的视频中,要修改打包的配置use: ['vue-style-loader','css-loader','stylus-loader'],试了一下不好使,不用改vue-style-loader)
打包时,在特定时刻,自动执行的处理程序。
演示:
安装
C:\Users\Administrator\Desktop\vue_todo>npm install -D html-webpack-plugin
配置webpack.config.js
//开头增加
const HtmlWebpackPlugin = require('html-webpack-plugin')
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
})
],
在项目根目录下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
删除dist目录,重新打包,可以看到新生成的dist目录下的index.html文件
在打包之前删除dist目录的文件
npm install -D clean-webpack-plugin
配置webpack.config.js
// 引入clean-webpack-plugin,es6解构语法
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
//...省略部分内容...
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new CleanWebpackPlugin()
],
打包(修改filename: 'bundle.js'为其他名字)测试查看bundle.js是否被删除。
自动添加浏览器厂商前缀,依赖于postcss-loader
npm install -D postcss-loader autoprefixer
配置webpack.config.js
test: /\.styl(us)?$/,
use: ['style-loader','css-loader','postcss-loader','stylus-loader']
配置postcss.config.js(新建)
module.exports = {
plugins: [
require('autoprefixer')
]
}
在package.json中增加
"browserslist": [
"defaults",
"not ie < 11",
"last 2 versions",
"> 1%",
"iOS 7",
"last 3 iOS versions"
]
打包测试
webpack-dev-server 提供简单的web服务器,并且实时重新加载
安装: npm install -D webpack-dev-server
配置webpack.config.js
module.exports = {
mode: 'development',
//导出2个对象,
//打包入口文件
devServer: {
contentBase: './dist',
open: true
},
配置package.json,作用npm run start 执行webpack-dev-server,加载devserver配置
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server"
},
更改App.vue的内容发现,浏览器内容实时刷新
运行时更新各种模块,无需进行完全刷新。
编辑App.vue,
<template>
<div>
<h1>Todolist</h1>
<input type="text" v-model="content">
<button @click="addTodo">添加</button>
<ul>
<li v-for="(item,index) of todoData" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
import './assets/styles/global.styl'
export default {
name: 'App',
data() {
return {
todoData: ["todo1","todo2","todo3"],
content:""
}
},
methods: {
addTodo(){
if (this.content === "") return
this.todoData.push(this.content)
this.content = ""
}
}
}
</script>
<style lang="stylus" scoped>
li:nth-of-type(odd)
color: blue
</style>
配置webpack.config.js
//引入插件
const webpack = require('webpack')
//增加hot: true
devServer: {
contentBase: './dist',
open: true,
hot: true
},
//实例化new webpack.HotModuleReplacementPlugin()
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin()
],
重新运行npm run start,在页面新增加一个todo4. 更改App.vue,把blue改为red,查看,页面todo4依然存在,这就是模块热替换。
用于定位源代码错误位置
配置webpack.config.js,在module.exports中增加
mode: 'production',
devtool: 'eval',
也可以(development默认就是eval):
webpack.dev.js :用于开发,专注于开发调试。 webpack.prod.js : 用于生产,构建更小的目标文件,改善加载时间
将原来的webpack.config.js改名为webpack.dev.js,复制一份改为webpack.prod.js
修改webpack.prod.js
//导入path模块
const path = require('path')
//引入vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'production',
//导出2个对象,
//打包入口文件
entry: './src/main.js',
//打包的出口文件
output:{
filename: 'bundle.js',
path: path.resolve(__dirname,'dist') //path需要使用绝对路径,要引入node的path
},
//打包规则,以.vue结尾用vue-loader打包
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.(jpg|jpeg|png|svg)$/,
loader: 'url-loader',
options: {
name: '[name].[ext]',
limit: 2048
},
},{
test: /\.css$/,
// 用多个loader注意loader顺序,用use关键字,
//loader按照从右到左,从下到上执行
use: ['style-loader','css-loader']
},{
test: /\.styl(us)?$/,
use: ['style-loader','css-loader','postcss-loader','stylus-loader']
}]
},
//插件
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new CleanWebpackPlugin(),
],
resolve: {
alias: {
'vue':'vue/dist/vue.js'
}
}
}
修改package.json,删除原来的脚本。
"scripts": {
"dev": "webpack-dev-server --config ./webpack.dev.js",
"build": "webpack --config ./webpack.prod.js"
},
测试npm run dev与npm run build 的效果。
用于设置webpack的公共配置部分。
安装
npm install -D webpack-merge
创建build文件夹,将全面的webpack.prod.js和webpack.dev.js文件,移动到build文件夹,创建webpack.base.js文件
webpack.base.js
//导入path模块
const path = require('path')
//引入vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入clean-webpack-plugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
//引入插件
const webpack = require('webpack')
module.exports = {
//打包入口文件
entry: './src/main.js',
//打包的出口文件
output:{
filename: 'bundle.js',
path: path.resolve(__dirname,'../dist') //path需要使用绝对路径,要引入node的path
},
//打包规则,以.vue结尾用vue-loader打包
module: {
rules: [{
test: /\.vue$/,
loader: 'vue-loader'
},{
test: /\.(jpg|jpeg|png|svg)$/,
loader: 'url-loader',
options: {
name: '[name].[ext]',
limit: 2048
},
},{
test: /\.css$/,
// 用多个loader注意loader顺序,用use关键字,
//loader按照从右到左,从下到上执行
use: ['style-loader','css-loader']
},{
test: /\.styl(us)?$/,
use: ['style-loader','css-loader','postcss-loader','stylus-loader']
}]
},
//插件
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new CleanWebpackPlugin(),
],
resolve: {
alias: {
'vue':'vue/dist/vue.js'
}
}
}
webpack.dev.js
const baseConfig = require('./webpack.base.js')
const {merge} = require('webpack-merge')
//引入插件
const webpack = require('webpack')
const devConfig = {
mode: 'development',
//导出2个对象,
devtool: 'eval',
devServer: {
contentBase: './dist',
open: true,
hot: true
},
//插件
plugins: [
new webpack.HotModuleReplacementPlugin()
],
}
module.exports = merge(baseConfig,devConfig)
webpack.prod.js
const baseConfig = require('./webpack.base.js')
const {merge} = require('webpack-merge')
const prodConfig = {
mode: 'production',
}
module.exports =merge(baseConfig,prodConfig)
通过npm run dev 与 npm run build测试(注意script多一层build目录"dev": "webpack-dev-server --config ./build/webpack.dev.js")
安装
npm install -D babel-loader @babel/core
修改webpack.base.js
module: {
rules: [{
test: /\.js$/,
exclude: /node_module/,
loader: "babel-loader"
},{
创建.babel.rc配置文件
{
"presets": ["@babel/preset-env"]
}
在main.js增加以下2行用于调试: const arr = [1,2,3].map(item => item + 1) console.log(arr)
npm run dev,在浏览器中查看main.js的内容
settings.json(快捷键ctrl+shift+p,输入json ,点击Preferences: Open Settings (JSON))
{
"terminal.integrated.shell.windows": "C:\\WINDOWS\\System32\\cmd.exe",
"editor.tabSize": 2,
"editor.formatOnSave": true,
"files.autoSave": "onFocusChange",
//stylus 不使用{}
"stylusSupremacy.insertBraces": false,
//stylus 不使用;
"stylusSupremacy.insertSemicolons": false,
//在style样式中缩进2个空格
"vetur.format.styleInitialIndent": true,
//在script样式中,缩进2个空格
"vetur.format.scritpInitialIndent": true,
"vetur.format.defaultFormatterOptions": {
"prettier": {
//适用单引号
"singleQuote": true,
//不适用;号
"semi": false,
"proseWrap": "nerver",
"printWidth": 80,
},
"prettyhtml": {
//超过80字符换行显示
"printWidth": 80,
// "singleQuote": false,
// "wrapAttributes": false,
// "sortAttributes": false
}
}
}
C:\Users\Administrator>git clone https://github.com/vuejs/vue-devtools
Cloning into 'vue-devtools'...
remote: Enumerating objects: 32, done.
remote: Counting objects: 100% (32/32), done.
remote: Compressing objects: 100% (28/28), done.
remote: Total 9964 (delta 10), reused 14 (delta 4), pack-reused 9932 eceiving objects: 100% (9964/9964), 8.79 MiB | 2.02Receiving objects: 100% (9964/9964), 8.90 MiB | 1.9
2 MiB/s, done.
Resolving deltas: 100% (6549/6549), done.
C:\Users\Administrator>npm install -g yarn
C:\Users\Administrator\AppData\Roaming\npm\yarn -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js
C:\Users\Administrator\AppData\Roaming\npm\yarnpkg -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js
+ yarn@1.22.5
added 1 package in 1.656s
C:\Users\Administrator>yarn --version
1.22.5
#如果--version没反应,卸了重装一下试试。
C:\Users\Administrator>cd vue-devtools
C:\Users\Administrator\vue-devtools>yarn install
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
info There appears to be trouble with your network connection. Retrying...
warning url-loader@1.1.2: Invalid bin field for "url-loader".
info fsevents@1.2.9: The platform "win32" is incompatible with this module.
info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
[4/5] Linking dependencies...
[5/5] Building fresh packages...
Done in 5285.52s.
C:\Users\Administrator\vue-devtools>npm run build
#修改vue-devtools\packages\shell-chrome\manifest.json 的"persistent": true
#复制shell-chrome到vue-devtools\shells\下,并重名令chrome(shells需要手动创建)
#在浏览器中--设置--扩展程序--开发者模式--加载已解压的扩展程序--选择到vue-devtools\shells\chrome目录
访问vue的站点:
拆分为3个组件
- MainHeader组件
- MainTodo组件
- TodoItem组件
- TodoInfo组件
- MainFooter组件
main.js
//创建vue根实例
import Vue from 'vue'
//导入App组件
import App from './App.vue'
//创建vue根实例
new Vue({
el: '#app',
//局部组建挂载
components: {
// 组件名:组件对象(对象通过App.vue导入)
App: App
},
template: '<App/>'
})
App.vue
<template>
<div>
<main-header></main-header>
</div>
</template>
<script>
import './assets/styles/global.styl'
import MainHeader from './components/MainHeader.vue'
export default {
name: 'App',
components: {
//组件名:组件对象,驼峰写法在template中转换为小写
MainHeader: MainHeader
}
}
</script>
<style lang="stylus" scoped></style>
src/assets/styles/global.styl
*
margin: 0
padding: 0
body
// 背景图片
background-image: url('../images/bg.jpg')
background-attachment: fixed
background-repeat: no-repeat
background-position: center center
background-size: cover
background-color: #fff
font: 14px 'microsoft yehei'
font-weight: 300
src/components/MainHeader.vue (components目录和MainHeader.vue需要创建)
<template>
<header class="main-header">
<h1>TodoList</h1>
</header>
</template>
<script>
export default {
name: "MainHeader"
}
</script>
<style lang="stylus" scoped>
.main-header
text-align: center
h1
margin: 20px
font-size: 100px
font-weight: 100
color: rgb(252, 157, 154)
text-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1)
</style>
运行npm run dev查看浏览器效果
css 模块化,将常用的配置集中化,创建src/assets/styles/theme.styl
$red = rgb(254, 67, 101)
$lightred = rgb(252, 157, 154)
$yellow = rgb(249, 205, 173)
$green = rgb(131, 175, 155)
$lightgreen = rgb(200, 200, 169)
修改MainHeader.vue,通过@import导入,通过$lightred引用定义,这里的@是webpack的别名('@': path.resolve(__dirname, '../src'))
<style lang="stylus" scoped>
@import '../assets/styles/theme.styl'
.main-header
text-align: center
h1
margin: 20px
font-size: 100px
font-weight: 100
color: $lightred
text-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1)
</style>
在wenpack中设置别名
修改build/webpack.base.js
resolve: {
alias: {
'vue': 'vue/dist/vue.js',
'@': path.resolve(__dirname, '../src'),
'style': path.resolve(__dirname, '../src/assets/styles')
}
}
修改MainHeader.vue,重新启动npm run dev,查看浏览器效果相同
<style lang="stylus" scoped>
@import '~style/theme.styl'
.main-header
text-align: center
h1
margin: 20px
font-size: 100px
font-weight: 100
color: $lightred
text-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1)
</style>
创建目录与组件文件
编辑MainTodo.vue
<template>
<div>
<input type="text" />
</div>
</template>
<script>
export default {
name: 'MainTodo'
}
</script>
<style lang="stylus" scoped></style>
编辑App.vue
<template>
<div>
<main-header></main-header>
<main-todo></main-todo>
</div>
</template>
<script>
import './assets/styles/global.styl'
import MainHeader from './components/MainHeader.vue'
import MainTodo from './components/MainTodo/MainTodo.vue'
export default {
name: 'App',
components: {
//组件名:组件对象,驼峰写法在template中转换为小写
MainHeader: MainHeader,
MainTodo: MainTodo
}
}
</script>
<style lang="stylus" scoped></style>
修改MainTodo.vue
<template>
<div class="main-todo">
<input type="text" class="add-todo" placeholder="what to do?" autofocus />
</div>
</template>
<script>
export default {
name: 'MainTodo'
}
</script>
<style lang="stylus" scoped>
.main-todo
margin: 0 auto
width: 600px
background-color: #fff
box-shadow: 0 0 5px #666
.add-todo
padding: 16px 16px 16px 36px
width: 100%
font-size: 24px
font-font-weight: inherit
font-font-family: inherit
color: inherit
border: none
outline: none
box-sizing: border-box
</style>
### TodoItem 实现
编写TodoItem.vue
```shell
<template>
<div>
<input type="checkbox" />
<label>todo1</label>
<button></button>
</div>
</template>
<script>
export default {
name: 'TodoItem'
}
</script>
<style lang="stylus" scoped></style>
在MainTodo.vue中导入子组件TodoItem
<template>
<div class="main-todo">
<input type="text" class="add-todo" placeholder="what to do?" autofocus />
<todo-item></todo-item>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
export default {
name: 'MainTodo',
components: {
TodoItem
}
}
</script>
<style lang="stylus" scoped>
.main-todo
margin: 0 auto
width: 600px
background-color: #fff
box-shadow: 0 0 5px #666
.add-todo
padding: 16px 16px 16px 36px
width: 100%
font-size: 24px
font-font-weight: inherit
font-font-family: inherit
color: inherit
border: none
outline: none
box-sizing: border-box
</style>
在webpack.base.js设置别名:
resolve: {
alias: {
'vue': 'vue/dist/vue.js',
'@': path.resolve(__dirname, '../src'),
'style': path.resolve(__dirname, '../src/assets/styles'),
'images': path.resolve(__dirname, '../src/assets/images')
}
}
定义css公共函数,此函数清除默认样式
编辑TodoItem.vue
<template>
<div class="todo-item">
<input type="checkbox" />
<label>todo1</label>
<button></button>
</div>
</template>
<script>
export default {
name: 'TodoItem'
}
</script>
<style lang="stylus" scoped>
@import '~style/theme.styl'
@import '~style/mixins.styl'
.todo-item
display: flex
justify-content: space-between
padding: 10px
font-size: 24px
&:hover
button:after
content: 'x'
font-size: 24px
color: $lightred
&.completed
color: #d9d9d9
text-decoration: line-through
input
width: 50px
height: 30px
text-align: center
cleanDfaultStyle()
&:after
content: url('~images/unchecked.svg')
&:checked:after
content: url('~images/checked.svg')
label
flex: 1
transition: color 0.5s
button
width: 40px
cleanDfaultStyle()
background-color: transparent
cursor: pointer
</style>
核心:父组件向子组件传值
添加功能:
MainHeader.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item v-for="(item, index) in todoData" :key="index" :todo="item"></todo-item>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
let id = 0
export default {
name: 'MainTodo',
data() {
return {
todoData: [],
content:''
}
},
methods:{
addTodo(){
if (this.content === '' ) return
this.todoData.unshift({
id:id++,
content: this.content,
completed: false
})
this.content=''
}
},
components: {
TodoItem
}
}
</script>
<style lang="stylus" scoped>
.main-todo
margin: 0 auto
width: 600px
background-color: #fff
box-shadow: 0 0 5px #666
.add-todo
padding: 16px 16px 16px 36px
width: 100%
font-size: 24px
font-font-weight: inherit
font-font-family: inherit
color: inherit
border: none
outline: none
box-sizing: border-box
</style>
Todoitem.vue
<template>
<div class="todo-item">
<input type="checkbox" />
<label>{{todo.content}}</label>
<button></button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props:{
todo:Object
}
}
</script>
<style lang="stylus" scoped>
@import '~style/theme.styl'
@import '~style/mixins.styl'
.todo-item
display: flex
justify-content: space-between
padding: 10px
font-size: 24px
border-top: 1px solid rgba(0, 0, 0, 0.1)
&:hover
button:after
content: 'x'
font-size: 24px
color: $lightred
&.completed
color: #d9d9d9
text-decoration: line-through
input
width: 50px
height: 30px
text-align: center
cleanDfaultStyle()
&:after
content: url('~images/unchecked.svg')
&:checked:after
content: url('~images/checked.svg')
label
flex: 1
transition: color 0.5s
button
width: 40px
cleanDfaultStyle()
background-color: transparent
cursor: pointer
</style>
选中功能,核心点--样式绑定
TodoItem.vue
<template>
<div :class="['todo-item',todo.completed ? 'completed': '']">
<input type="checkbox" v-model="todo.completed" />
<label>{{todo.content}}</label>
<button></button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props:{
todo:Object
}
}
</script>
<style lang="stylus" scoped>
@import '~style/theme.styl'
@import '~style/mixins.styl'
.todo-item
display: flex
justify-content: space-between
padding: 10px
font-size: 24px
border-top: 1px solid rgba(0, 0, 0, 0.1)
&:hover
button:after
content: 'x'
font-size: 24px
color: $lightred
&.completed
color: #d9d9d9
text-decoration: line-through
input
width: 50px
height: 30px
text-align: center
cleanDfaultStyle()
&:after
content: url('~images/unchecked.svg')
&:checked:after
content: url('~images/checked.svg')
label
flex: 1
transition: color 0.5s
button
width: 40px
cleanDfaultStyle()
background-color: transparent
cursor: pointer
</style>
删除功能 , 核心点子组件向父组件传值
TodoItem.vue
<template>
<div :class="['todo-item',todo.completed ? 'completed': '']">
<input type="checkbox" v-model="todo.completed" />
<label>{{todo.content}}</label>
<button @click="delItem"></button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props:{
todo:Object
},
methods: {
delItem(){
this.$emit('del',this.todo.id)
}
}
}
</script>
<style lang="stylus" scoped>
@import '~style/theme.styl'
@import '~style/mixins.styl'
.todo-item
display: flex
justify-content: space-between
padding: 10px
font-size: 24px
border-top: 1px solid rgba(0, 0, 0, 0.1)
&:hover
button:after
content: 'x'
font-size: 24px
color: $lightred
&.completed
color: #d9d9d9
text-decoration: line-through
input
width: 50px
height: 30px
text-align: center
cleanDfaultStyle()
&:after
content: url('~images/unchecked.svg')
&:checked:after
content: url('~images/checked.svg')
label
flex: 1
transition: color 0.5s
button
width: 40px
cleanDfaultStyle()
background-color: transparent
cursor: pointer
</style>
MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in todoData"
:key="index"
:todo="item"
@del="handleDelete"
></todo-item>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
let id = 0
export default {
name: 'MainTodo',
data() {
return {
todoData: [],
content:''
}
},
methods:{
addTodo(){
if (this.content === '' ) return
this.todoData.unshift({
id:id++,
content: this.content,
completed: false
})
this.content=''
},
handleDelete(id){
this.todoData.splice(this.todoData.findIndex(item => item.id=== id),1)
}
},
components: {
TodoItem
}
}
</script>
<style lang="stylus" scoped>
.main-todo
margin: 0 auto
width: 600px
background-color: #fff
box-shadow: 0 0 5px #666
.add-todo
padding: 16px 16px 16px 36px
width: 100%
font-size: 24px
font-font-weight: inherit
font-font-family: inherit
color: inherit
border: none
outline: none
box-sizing: border-box
</style>
修改mixins.styl
cleanDfaultStyle()
appearance: none
border: none
outline: none
btn(c, border = false)
padding: 0 10px
border-radius: 5px
cursor: pointer
cleanDfaultStyle()
if (border == true)
border: 1px solid c
else
background-color: c
coler: #fff
primaryBtn()
btn(rgb(252, 157, 154))
primaryBorderBtn()
btn(rgb(252, 157, 154), true)
infoBtn()
btn(rgb(131, 175, 155))
修改TodoInfo.vue
<template>
<div class="todo-info">
<span class="total">1 item left</span>
<div class="tabs">
<a
class="btn primary border"
v-for="(item, index) in states "
:key="index"
>{{item}}</a>
</div>
<button class="btn info">Clear Completed</button>
</div>
</template>
<script>
export default {
name: 'TodoInfo',
data(){
return {
states : ['all', 'active', 'completed']
}
},
}
</script>
<style lang="stylus" scoped>
@import '~style/theme.styl'
@import '~style/mixins.styl'
.todo-info
display: flex
justify-content: space-between
font-weight: 400
padding: 5px 10px
line-height: 30px
border-top: 1px solid rgba(0, 0, 0, 0.1)
.total
color: $red
.tabs
display: flex
justify-content: space-between
width: 200px
.btn.primary.border
primaryBorderBtn()
&.actived
primaryBtn()
.btn.info
infoBtn()
</style>
修改MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in todoData"
:key="index"
:todo="item"
@del="handleDelete"
></todo-item>
<todo-info></todo-info>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
import TodoInfo from './coms/TodoInfo.vue'
let id = 0
export default {
name: 'MainTodo',
data() {
return {
todoData: [],
content:''
}
},
methods:{
addTodo(){
if (this.content === '' ) return
this.todoData.unshift({
id:id++,
content: this.content,
completed: false
})
this.content=''
},
handleDelete(id){
this.todoData.splice(this.todoData.findIndex(item => item.id=== id),1)
}
},
components: {
TodoItem,
TodoInfo
}
}
</script>
<style lang="stylus" scoped>
.main-todo
margin: 0 auto
width: 600px
background-color: #fff
box-shadow: 0 0 5px #666
.add-todo
padding: 16px 16px 16px 36px
width: 100%
font-size: 24px
font-font-weight: inherit
font-font-family: inherit
color: inherit
border: none
outline: none
box-sizing: border-box
</style>
统计功能: watch监听
MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in todoData"
:key="index"
:todo="item"
@del="handleDelete"
></todo-item>
<todo-info :total="total"></todo-info>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
import TodoInfo from './coms/TodoInfo.vue'
let id = 0
export default {
name: 'MainTodo',
data() {
return {
todoData: [],
content:'',
total: 0
}
},
watch: {
todoData: {
deep: true,
handler(){
this.total = this.todoData.filter(item => item.completed == false).length
}
}
},
methods:{
addTodo(){
if (this.content === '' ) return
this.todoData.unshift({
id:id++,
content: this.content,
completed: false
})
this.content=''
},
handleDelete(id){
this.todoData.splice(this.todoData.findIndex(item => item.id=== id),1)
}
},
components: {
TodoItem,
TodoInfo
}
}
</script>
TodoInfo.vue
<template>
<div class="todo-info">
<span class="total">{{total}} item left</span>
<div class="tabs">
<a
class="btn primary border"
v-for="(item, index) in states "
:key="index"
>{{item}}</a>
</div>
<button class="btn info">Clear Completed</button>
</div>
</template>
<script>
export default {
name: 'TodoInfo',
props: {
total: Number
},
data(){
return {
states : ['all', 'active', 'completed']
}
},
}
</script>
切换显示功能
TodoInfo.vue
<template>
<div class="todo-info">
<span class="total">{{total}} item left</span>
<div class="tabs">
<a
:class="['btn','primary', 'border', state == item? 'actived':'']"
v-for="(item, index) in states "
:key="index"
@click="toggleState(item)"
>{{item}}</a>
</div>
<button class="btn info">Clear Completed</button>
</div>
</template>
<script>
export default {
name: 'TodoInfo',
props: {
total: Number
},
data(){
return {
states : ['all', 'active', 'completed'],
state: 'all'
}
},
methods: {
toggleState(state){
this.state = state
}
}
}
</script>
改变父组件的显示
TodoInfo.vue
<template>
<div class="todo-info">
<span class="total">{{total}} item left</span>
<div class="tabs">
<a
:class="['btn','primary', 'border', state == item? 'actived':'']"
v-for="(item, index) in states "
:key="index"
@click="toggleState(item)"
>{{item}}</a>
</div>
<button class="btn info">Clear Completed</button>
</div>
</template>
<script>
export default {
name: 'TodoInfo',
props: {
total: Number
},
data(){
return {
states : ['all', 'active', 'completed'],
state: 'all'
}
},
methods: {
toggleState(state){
this.state = state
this.$emit('toggleState',state)
}
}
}
</script>
MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in filterData"
:key="index"
:todo="item"
@del="handleDelete"
></todo-item>
<todo-info :total="total" @toggleState="handlerToggleState"></todo-info>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
import TodoInfo from './coms/TodoInfo.vue'
let id = 0
export default {
name: 'MainTodo',
data() {
return {
todoData: [],
content:'',
total: 0,
filter: 'all'
}
},
watch: {
todoData: {
deep: true,
handler(){
this.total = this.todoData.filter(item => item.completed == false).length
}
}
},
methods:{
addTodo(){
if (this.content === '' ) return
this.todoData.unshift({
id:id++,
content: this.content,
completed: false
})
this.content=''
},
handleDelete(id){
this.todoData.splice(this.todoData.findIndex(item => item.id=== id),1)
},
handlerToggleState(state){
this.filter = state
}
},
computed:{
filterData(){
switch(this.filter){
case 'all':
return this.todoData
break
case 'active':
return this.todoData.filter(item => item.completed == false)
break
case 'completed':
return this.todoData.filter(item => item.completed == true)
break
}
}
},
components: {
TodoItem,
TodoInfo
}
}
</script>
删除功能,点击clear completed,删除所有已经完成的待办事项。
TodoInfo.vue
<template>
<div :class="['todo-item',todo.completed ? 'completed': '']">
<input type="checkbox" v-model="todo.completed" />
<label>{{todo.content}}</label>
<button @click="delItem"></button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props:{
todo:Object
},
methods: {
delItem(){
this.$emit('del',this.todo.id)
}
}
}
</script>
MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in filterData"
:key="index"
:todo="item"
@del="handleDelete"
></todo-item>
<todo-info
:total="total"
@toggleState="handlerToggleState"
@clearCompleted="handlerClear"
></todo-info>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'
import TodoInfo from './coms/TodoInfo.vue'
let id = 0
export default {
name: 'MainTodo',
data() {
return {
todoData: [],
content:'',
total: 0,
filter: 'all'
}
},
watch: {
todoData: {
deep: true,
handler(){
this.total = this.todoData.filter(item => item.completed == false).length
}
}
},
methods:{
addTodo(){
if (this.content === '' ) return
this.todoData.unshift({
id:id++,
content: this.content,
completed: false
})
this.content=''
},
handleDelete(id){
this.todoData.splice(this.todoData.findIndex(item => item.id=== id),1)
},
handlerToggleState(state){
this.filter = state
},
handlerClear(){
this.todoData = this.todoData.filter(item => item.completed == false)
}
},
computed:{
filterData(){
switch(this.filter){
case 'all':
return this.todoData
break
case 'active':
return this.todoData.filter(item => item.completed == false)
break
case 'completed':
return this.todoData.filter(item => item.completed == true)
break
}
}
},
components: {
TodoItem,
TodoInfo
}
}
</script>
编辑components/MainFooter.vue
<template>
<footer class="main-footer">Written By T</footer>
</template>
<style lang="stylus" scoped>
.main-footer
margin-top: 20px
text-align: center
color: #fff
text-shadow: 5px 5px 5px #000
</style>
编辑App.vue文件
<template>
<div>
<main-header></main-header>
<main-todo></main-todo>
<main-footer></main-footer>
</div>
</template>
<script>
import './assets/styles/global.styl'
import MainHeader from './components/MainHeader.vue'
import MainTodo from './components/MainTodo/MainTodo.vue'
import MainFooter from './components/MainFooter.vue'
export default {
name: 'App',
components: {
//组件名:组件对象,驼峰写法在template中转换为小写
MainHeader: MainHeader,
MainTodo: MainTodo,
MainFooter
}
}
</script>
<style lang="stylus" scoped></style>
至此项目完成。