一、脚手架
1. Vue代码开发方式
Vue有2种开发方式
1.1 传统开发模式
传统开发模式: 直接在HTML文件中, 通过<script src="vue.js"></script>
目录结构:
Vue-tradition-way
index.html
index.css
index.js
vue.js
优点: 简单, 上手快
缺点: 功能单一(难以使用模块导入导出), 开发体验差
1.2 现代(工程化)开发模式
工程化开发模式: 需要基于Vite/Webpack等进行开发, 这种方式是企业采用的方式, 之后学习都采用这种方式.
目录结构:
优点: 功能全面, 开发体验好...(在这种环境下, 我们修改代码, 不需要手动刷新, 浏览器会自动帮我们刷新)
缺点: 目录结构复杂, 看上去不易上手, 提高了理解难度
2. 准备工程化环境
1> 安装Nodejs
去官方下载: Node.js — Run JavaScript Everywhere
node -v npm-v 检测node 和 npm是否安装
2> npm 换源: 建议使用淘宝源,下载速度快:npm config set registry https://registry.npmmirror.com
3> 其他的包管理器
npm
yarn
pnpm
4> 三个包管理器使用的命令
npm装包: npm i 包名 yarn装包: yarn add 包名 pnpm装包: pnpm i 包名
npm删包: npm un 包名 yarn删包: yarn remove 包名 pnpm删包: pnpm un 包名
3. 创建Vue工程化项目
3.1 Vue3脚手架
脚手架: 最早来源于建筑工地, 是一个可以保证各项工作顺利开展的平台, 是基础. 基于这个环境, 可以做更多的工作.
Vue3的脚手架: 提供了一个编写Vue3代码的的工程化环境, 基于这个环境, 快速学习或者开发Vue项目.
好处: 拿来就用, 零配置, 降低Vue3的学习门槛, 让我们更加关注Vue3的语法, 而不必过多在意环境的搭建.
3.2 创建项目
步骤
1. 选定代码的存放位置, 比如D盘
2. 在选定的位置处, 打开命令行窗口: 输入cmd点击回车, 执行命令行`npm create
vue@latest',跟随提示进行后续操作
3. 进入项目根目录
4. 安装项目依赖
5. 启动项目
6. 浏览器运行, 观察效果
4. 认识脚手架目录及文件
目录和文件的解读
src下面写源码, 使用vite打包工具,把vue,css,图片进行打包成浏览器认识的html,css,js,然后交给index.html通过浏览器呈现在我们面前
5. 分析3个入口文件的关系
三个入口文件: main.js(整个项目的打包入口), App.vue(Vue代码的入口),index.thml(项目的入口网页)
mian.js :整个项目的打包入口
App.vue : Vue代码的入口(欢迎界面)
图解: 基本上就是src里面的main.js引用了其他的文件,其他文件又引用了别的文件, 然后就一起被vite打包了,然后处理成css,js,img,最后交给index.html,当浏览器访问5173端口号的时候,就是访问整个index.html文件. 打包前很多的vue文件浏览器不能识别, 必须通过vite来进行处理(vue文件->网页可以识别的文件) mian.js是架起vue代码和网页代码的一座桥梁,它通过对vue代码进行打包,打包后变成js,css,img, 才可以被网页运行起来.
6. Vue单文件和清理目录结构
Vue单文件介绍
引入
代码写⼀起、担⼼class类名、js变量名 重名冲突?Vue中如何避免呢?
此时就要引入Vue单文件了
Vue单文件(一个.vue位结尾的文件)
1. Vue推荐采用.vue文件进行Vue代码的开发
2. 一个Vue单文件的组成: 3部分 = script(JS) + template(HTML)+ style(CSS)
3. 好处: 一个.vue文件时一个独立的模块, 就是一个独立的作用域, 无需担心变量重名
4. style 里面有个scoped属性(当前的style样式只针对当前的template里面的html标签生效,不会影响到别的.vue里面的标签样式), 是为了避免css样式冲突.
5. Vue 文件浏览器不能识别, 需要借助 vite 进行打包, 打包成 html,css,js,图片等, 然后通过 index.html 进行访问.
清理目录文件
为了便于后续学习Vue代码,这⾥需要清除默认不需要的⽬录结构
步骤
清理欢迎界面:
1> 删除 assets 目录
2> 删除 components 目录
3> 把 App.vue(Vue代码的入口) 的三部分(js,html,css)清空
4> 清空 main.js , 自己重写编写代码
1. 导入 createApp
2. 导入 App.vue
3. 创建应用并指明渲染的位置
具体操作
后续我们如果要在页面显示什么标签, 就在template里面写
注意要保证项目启动: npm run dex
整体流程
最后我们再把js和css里面的代码清空, 此时就是空白的界面
然后我们就可以把assets和componets给删除了
最后重写 main.js 这个打包入口文件
7. setup简写+差值+响应式渲染
传统环境使用Vue和工程化环境使用Vue
传统方式渲染 Hello world:
1. 引入 vue3 的源代码
2. 准备渲染容器
3. 创建应用并指定渲染的位置, 同时提供数据并返回
4. 差值进行渲染{{ 表达式 }}
工程化环境下渲染 Hello world:
1. 在 App.vue中提供 setup 函数, 声明数据并返回
2. 差值渲染 {{表达式}}
演示过程
简写vs不简写
直接在script 里面写setup属性就可以了
具体代码:
不简写
<!-- js -->
<script>
//默认导出 setup
//setup不简写
export default {
setup() {
// 声明数据
const msg = "Hello Vue3+vite"
//返回数据
return {
msg
}
}
}
</script>
<!-- html -->
<template>
<!-- 差值进行渲染 -->
<h1>{{ msg }}</h1>
</template>
<!-- css -->
<style scoped></style>
简写
<!-- js -->
<!-- setup简写 -->
<script setup>
//申明变量, 此时不用返回,因为默认给我们返回了
const msg = "Hello"
</script>
<!-- html -->
<template>
<h1>{{ msg }}</h1>
</template>
<!-- css -->
<style scoped></style>
给数据加上响应式
主要是要要在js里面进行按需导入,引用要用到的响应式函数
<!-- js -->
<!-- setup简写 -->
<script setup>
// 按需导入响应式数据 ref,reactive
//申明变量, 此时不用返回,因为默认给我们返回了
import { ref, reactive } from 'vue'
//创建一个 ref 响应式数据
const msg1 = ref("Hello 小白")
//创建一个 reactive响应式对象
const obj = reactive({
name: "xiaobai",
age: 19
})
//创建函数
const fn = () => {
return 90
}
</script>
<!-- html -->
<template>
<!-- 1. 直接放变量 -->
<h1>{{ msg1 }}</h1>
<!-- 2. 对象.属性 -->
<h1>我是{{ obj.name }}今年{{ obj.age }}岁了</h1>
<!-- 3. 三元表达式 -->
<h1>{{ obj.age > 18 ? '成年' : '未成年' }}</h1>
<!--4. 算数运算 -->
<h1>明年{{ obj.age + 1 }}岁</h1>
<!-- 5.函数的调用 -->
<h1>我考了{{ fn() }}分</h1>
<!-- 6. 方法的调用 -->
<h1>{{ msg1 }}有{{ msg1.split(' ').length }}个单词</h1>
</template>
<!-- css -->
<style scoped></style>
运行结果
安装插件快速生成script,template,style标签
为了今后可以快速⽣成 vue ⽂件的三部分组成,建议安装VSCode的插件 Vue3 Snippets, 然后在 vue ⽂件中输⼊ vbase 即可快速⽣成模版
二、指令
1. 指令介绍
指令的概念, 作用, 分类
指令:
1. 概念: 英文叫做 directive, 作用在标签上, 以 v- 开头的属性(指令就是一个作用在标签上的属性), 是vue 的一种特殊语法.
2. 作用: 增强标签渲染数据的能力
3. 分类:
1> 内容渲染指令: 作用类似于差值表达式, 把表达式的结果渲染到双标签中.
2> 属性绑定指令: 把表达式的值和标签的属性动态绑定.(标签内容变了, 属性也会进行变化).
3> 事件绑定指令: 用来与标签进行事件绑定, 处理用户交互.
4> 条件渲染指令: 根据表达式的true或false, 决定标签是否展示.
5> 列表渲染指令: 基于数组循环生成一套列表.
6> 双向绑定指令: 数据 < - > 视图 (数据和视图相互影响)
2. 内容渲染指令: v-html/v-text
内容渲染指令的作用类似于差值表达式, 把表达式的结果渲染到双标签中.
v-html = "表达式": 会解析标签
v-text = "表达式": 不会解析标签(相当于差值表达式)
<!-- vbase快速生成模板结构 -->
<script setup>
import { ref } from 'vue'
const str = ref('<span style = "color:red">Hello Vue3</span>')
</script>
<template>
<div>
<!-- 会解析span标签 -->
<p v-html="str">
</p>
<!-- 不会解析span标签,直接当作一个字符串处理 -->
<!-- 和差值没有区别 -->
<p v-text="str">
</p>
<!-- 差值表示 -->
<p>{{ str }}</p>
</div>
</template>
<style scoped></style>
具体的执行流程
3. 属性绑定指令: v-bind
差值表达式是不能作用于标签属性的渲染的, 它只能用于双标签内容的展示
为了把 vue表达式 的 结果 与 标签 的属性动态绑定,我们引入属性绑定指令v-bind
语法: v-bind:属性名 = "表达式"
简写: :属性名 = "表达式" 注意: 冒号一定不能丢!
<script setup>
import { ref } from 'vue'
const msg = ref('hello vue')
const url = ref('http://www.baidu.com')
const imgurl = ref('https://bkimg.cdn.bcebos.com/pic/f31fbe096b63f6246b60d38cc81dfcf81a4c500f05b0?x-bce-process=image/format,f_auto/quality,Q_70/resize,m_lfit,limit_1,w_536')
</script>
<template>
<!-- v-bind 不简写 -->
<div v-bind:title="msg">
{{ msg }}
</div>
<!-- v-bind 简写 -->
<div :title="msg">
</div>
<!-- 练习 百度一下,加冒号 -->
<a :href="url">百度一下</a>
<!-- 练习 百度一下,不加冒号 -->
<a href="url">百度一下</a>
<!-- 练习 放置网络图片 -->
<br>
<img :src="imgurl">
</template>
<style scoped></style>
这样在HTML里面我们就写的会很简洁, 内容都可以放到js里面
4. 事件绑定指令: v-on
1. 作用: 与DOM元素进行事件绑定/处理.(与标签进行事件绑定, 处理用户交互.)
2. 语法(3种)
v-on:事件名="三种写法" 这里的事件是大都是js原生的事件 如: click()点击事件
1> v-on:事件名="内联代码"
2> v-on:事件名="处理函数"
3> v-on事件名="处理函数(实参列表)"
3. 简写: v-on:简写为@
@事件名="处理函数"
<script setup>
// 按需导出
import { ref } from 'vue'
// 计数器
const count = ref(0)
// 自增函数(无参函数)
//ref修饰的变量在js使用需要.value
const increase = () => {
count.value++;
}
//有参函数
const add = (num) => {
count.value += num
}
</script>
<template>
<div>
<p>{{ count }}</p>
<!-- 1. 内敛/行内代码(一句话的时候) -->
<button v-on:click="count++">+1</button>
<!-- 2. 调用无参函数 -->
<button v-on:click="increase">+1</button>
<!-- 3. 调用有参函数 -->
<button v-on:click="add(1)">+1</button>
<button v-on:click="add(5)">+5</button>
<!-- 简写 -->
<button @click="add(5)">+10</button>
</div>
</template>
<style scoped></style>
5. 条件绑定指令: v-show / v-if/v-else
1. 作用: 根据vue表达式值是true还是false, 决定某个元素是显示还是隐藏
2. 语法:
v-show="布尔表达式" 原理: 是控制style里面的display属性设置是否隐藏的
v-if="布尔表达式"
如果表达式是true, 则盒子都显示; 否则,盒子都隐藏
先看这么一个例子, 我们设置在vue开发者工具v-show,v-if为false
v-show修饰的标签多了style属性,且设置为display, 而v-if修饰的标签直接被注释掉了
3. 区别:
控制元素显示或者隐藏的原理不同:
1> v-show: 是通过控制元素css的display属性控制元素显示或者隐藏.(true->显示, false->display属性设置为none
2> v-if: 是通过创建/插入元素或者移除DOM元素控制显示或者隐藏.(true->插入DOM元素, false->移除DOM元素)
4. 如何选择?
如果频繁的控制元素显示或者隐藏, 推荐v-show(这个元素始终在网页上面,v-if会一直创建,销毁元素这样的话会使得DOM数的开销性能变大); 如果并不频繁控制元素显示或者隐藏, 推荐v-if
5. v-if还可以配合v-else-if和v-else做多分支或多分支的条件渲染
具体代码
<!-- 决定元素是隐藏还是显示 -->
<script setup>
import { ref } from 'vue'
// 控制盒子是否可见
const visible = ref(true)
// 模拟是否登录
const isLogin = ref(true)
//成绩
const mark = ref(100)
</script>
<template>
<div>
<!-- v-show -->
<div class="red" v-show="visible"></div>
<!-- v-if -->
<div class="green" v-if="visible"></div>
<hr/>
<!-- v-if配合v-else做双分支渲染 -->
<!-- 需求: 登录显示xxx,欢迎回来, 没登陆显示你好请登录 -->
<div v-if="isLogin">xxx,欢迎回来</div>
<!-- v-else 是不需要加条件的-->
<div v-else>你好,请登录</div>
<hr/>>
<!-- v-if配合v-else-if和v-else做多分支渲染 -->
<!-- 判断成绩是优秀,良好,差 -->
<div v-if="mark>=90">优秀</div>
<div v-else-if="mark>=70">良好</div>
<div v-else>差</div>
</div>
</template>
<style scoped>
.red,
.green {
width: 200px;
height: 100px;
}
.red {
background: red;
}
.green {
background: green;
}
</style>
运行结果
6. 小案例1: 切换图片
效果:
需求:
默认展示数组中的第⼀张图片,点击上⼀页下⼀页来回切换数组中的图片
1. 要用到弹性布局(设置为fex后,弹性子元素通常在弹性盒子内一行显示。默认情况每个容器只有一行),在父元素定义flex,子元素也要定义flex
2. 实现功能
1> 准备图片路径数组
2> 准备一个响应式下标数据(默认值为0)
3> 给按钮添加点击事件 每次点击会触发index的增大或者减小.然后从而转化img的图片
4> 让下标自增或自减
5> 给按钮添加条件渲染指令 处理边界
具体代码
<script setup>
import { ref } from 'vue'
//1. 声明图片路径数组
const imgList = [
'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif',
'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-01.gif',
'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-02.gif',
'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-03.gif',
'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-04.png',
'https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-05.png']
//2. 声明响应式下标,初始值为0
const index = ref(0)
</script>
<template>
<div>
<!-- 添加点击事件click,每次点击,index-1 -->
<!-- 添加条件渲染, 控制下标的边界 -->
<button @click="index--" v-if="index > 0">上一页</button>
</div>
<!-- v-bind 不简写 -->
<!-- <img v-bind:src="imgList[index]" alt="img"> -->
<!-- 简写 -->
<img :src="imgList[index]" alt="img">
<div>
<!-- 添加点击事件click,每次点击,index+1 -->
<!-- 添加条件渲染, 控制下标的边界 -->
<button @click="index++" v-if="index < imgList.length
- 1">下一页</button>
</div>
</template>
<style>
#app {
/* 定义为弹性盒子,展现在一行 */
display: flex;
width: 500px;
height: 240px;
}
/* */
img {
width: 240px;
height: 240px;
}
#app div {
flex: 1;
/**使得 #app 内的每个 div 元素在容器中占据同等的宽度,分配的宽度是剩余空间的平分。 */
display: flex;
/**每一个盒子都设置为弹性布局 */
justify-content: center;
align-items: center;
}
</style>
7. 小案例2: 可折叠面板
可折叠面版: 我们需要的效果图
我们要的效果是, 点击收起,下面的文字就会被折叠
步骤:
1. 搭建HTML框架
2. 准备一个响应式的布尔数据(按钮和盒子的状态有俩种,要么展开要么收起来,下面的盒子显示就设置为true, 不显示就设置为false, 每次点击收起, 就会改变布尔值)
3. 通过 v-show 绑定布尔值控制盒子的显示或隐藏
4. 给按钮绑定点击事件, 每次点击就让布尔值取反
5. 布尔值控制按钮名称
具体代码
<script setup>
import { ref } from 'vue'
// 是否可见, 默认不可见, 初始值是 false
const visible = ref(false)
</script>
<template>
<!-- 面板区域 -->
<h3>可折叠面板</h3>
<div class="pane1">
<!-- 标题区域 -->
<div class="title">
<h4>自由与爱情</h4>
<!-- 每次点击就取反, 并且按钮的显示信息也会跟着改变 -->
<span class="btn" v-on:click="visible = !visible">{{ visible === false ? '展开' : '收起' }}</span>
</div>
<div>
<!-- 主体内容区域 -->
<!-- 默认为显示不可见 -->
<div class="container" v-show="visible">
<p>声明诚可贵</p>
<p>爱情价更高</p>
<P>若为自由故</P>
<p>俩者皆可抛</p>
</div>
</div>
</div>
</template>
<style lang="scss">
// 如果lang = "scss"设置, 项目运行不出来, 需要安装sass模块, 在cmd里面运行 npm i sass -D,再重写运行项目: npm run dev
/* 背景色 */
body {
background: #ddd;
}
#app {
width: 400px;
margin: 20px auto;
padding: 1em 2em 2em;
border: 4px solid green;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
background: #fff;
border-radius: 1em;
h3 {
/* css不支持渲染嵌套的写法因此我们使用scss */
// 让h3水平居中
text-align: center;
}
}
.pane1 {
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
h4 {
margin: 0;
line-height: 2;
}
.btn {
cursor: pointer; //鼠标移动到上面之后, 变成手形
}
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
border-top-color: transparent;
}
}
</style>
注意: 如果lang = "scss"设置, 项目运行不出来, 需要安装sass模块, 在cmd里面运行 npm i sass -D,再重写运行项目: npm run dev
css不支持渲染嵌套的写法因此我们使用scss
8. 列表渲染指令: v-for
1. 作用: 基于数组/对象/数字 循环生产列表
2. 语法:
1> 想循环谁, 就给谁添加v-for
2> <li v-for="(值变量,下标变量) in 数组"></li> 值变量是当前的数组元素, 下标变量是数组的下标
<script setup>
import { ref } from 'vue'
// 遍历数字数组
const nums = ref([11, 22, 33, 44])
// 遍历对象数组,商品列表
const goodsList = ref([
{ id: 1, name: '篮球', price: 100 },
{ id: 2, name: '乒乓球', price: 10 },
{ id: 3, name: '排球', price: 60 }
])
// 遍历对象
const obj = {
id: 1000,
name: 'xxiao',
age: 90
}
</script>
<template>
<div>
<!-- 根据数组的元素生成四个li分别包含11,22,33,44 -->
<ul>
<li v-for="(item, index) in nums">
<!-- 遍历数字数组 -->
{{ item }}=>{{ index }}
</li>
</ul>
<!-- 根据goods-list的对象生成 div-->
<!-- 遍历对象数组 -->
<div class="goods-list">
<div class="goods-item" v-for="(item) in goodsList">
<p>id ={{ item.id }}</p>
<p>name={{ item.name }}</p>
<p>price={{ item.price }}</p>
</div>
</div>
<!-- 遍历对象 -->
<!-- -->
<div class="obj">
<!-- key是对象的键,value是对象的值,index是对象的下标 -->
<div class="obj-item" v-for="(key, value, index) in obj">
<p>{{ key }} : {{ value }} =>{{ index }}</p>
</div>
</div>
<ul>
<!-- 遍历数字 1-5 -->
<li v-for="(item,index) in 5">
{{ item }} -> {{ index }}
</li>
</ul>
</div>
</template>
<style scoped></style>
9. 案例三: 书架
需求
1.根据左侧数据渲染出右侧列表(v-for)
2.点击删除按钮时,应该把当前⾏从列表中删除(获取当前⾏的index,利⽤splice删除)
具体代码
<script setup>
import { ref } from 'vue'
const bookList = ref([
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《⽔浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' }
])
//删除->任意位置删除splice
const onDel = (i) => {
// console.log(i) i为当前点击下标
// 删除前先确认
if (window.confirm('确认删除吗')) {
bookList.value.splice(i, 1);//从i下标开始删,删除1个
}
}
</script>
<template>
<h3>我的书架</h3>
<ul>
<li v-for="(item, index) in bookList">
<!-- 基于这个模板循环生成li-->
<span>{{ item.name }}</span>
<span>{{ item.author }}</span>
<!-- 给按钮绑定点击事件 -->
<button @click="onDel(index)">删除</button>
</li>
</ul>
</template>
<style>
#app {
width: 400px;
margin: 100px auto;
}
ul {
/* 去除小圆点 */
list-style: none;
}
ul li {
display: flex;
justify-content: space-around;
padding: 10px 0;
border-bottom: 1px solid #ccc;
}
h3 {
text-align: center;
}
</style>
10. v-for中的key
添加 key 属性
1> 作用: 提高 vue 在更新列表的更新性能
2> 语法: :key = "不重复的唯一值"
3> 原理: vue内部会尽可能的复用DOM,不去创建新的或者更新DOM,做最小的更新. 加了key且为id(不重复的唯一值),通过key来标明当前元素是否发生了变化, 如果key不变, vue直接复用之前的DOM做最小的更新.(key好比对这个元素进行了标记, 改了数据后, vue就会根据这个数据去生成新的DOM结构,然后和原来的DOM进行对比,然后决定哪里更新,会尽可能的去复用上一次的DOM,而不去创建新的.此时我们如果删除id=0的书, 其他1-4的书不变,那么1-4的书不会改变,只有id=0的书做更改)
4> key 的类型: 数字 或 字符串
5> key的选择: 首选 id, 其次用下标(下标会变化一般不使用)
无key的情况下删除元素, 发现影响了很多个地方
加了key之后,发现只影响了一个地方
11. 双向绑定指令: v-model
1. 双向: 数据和视图
1> 当数据变了, 视图会变化(数据驱动视图的思想)
2> 当视图遍历, 数据会变化
数据 <-> 视图
2. 作用: 经常用在表单元素上, 比如输入框, 下拉列表, 单选框, 复选框, 文本域等.用于实现数据和标签vule属性的双向绑定, 进而可以快速收集表单数据.
3. 语法: v-model="表达式"
具体代码:
<script setup>
import { reactive } from 'vue';
//登录表单
const loginForm = reactive({
username: '',
password: ''
})
</script>
<template>
<div>
<!-- 实现双向绑定 -->
账号:<input type="text" v-model="loginForm.username"><br><br>
密码:<input type="password" v-model="loginForm.password"><br><br>
<button>登录</button>
<button>重置</button>
</div>
</template>
<style scoped></style>
12. 案例四: 记事本
需求
1> 能够在输入框输入代办事项
2> 能够添加待办任务
3> 能够删除待办任务
4> 能够清空待办任务
5> 能够给待办任务计数
主要的搭建框架
添加待办事项分析
本质: 往数组里面添加了一个对象
部分代码:
删除待办事项:
本质 : 获取下标, 进行删除
部分代码:
合计和清空
本质: 合计本身就是数数组有多少个元素,我们直接就使用数组长度来代替.清空直接让数组指向一个空数组即可.
部分代码
完整代码
<script setup>
// 导⼊ todo 样式
import './styles/index.css'
import { ref } from 'vue'
// 代办任务列表
const todoList = ref([
{ id: 321, name: '吃饭', finished: false },
{ id: 666, name: '睡觉', finished: true },
{ id: 195, name: '打⾖⾖', finished: false }
])
//任务名称
const title = ref('')
//添加
const onAdd = () => {
// 去除title的首位空格
const name = title.value.trim()
// 非空校验
// if(name.length === 0)
if (!name) {//name有值为false,无值为true
return alert('名称不能为空')
}
// 给 todoList 末尾添加一个新对象
todoList.value.push({
// 使用时间戳生成id(时间的毫秒数)
// 获取时间戳的三种方式
// Date.now()
// new Date().geTime()
// +new Date()
id: Date.now(),//时间戳
name: name,//任务名称
finished: false//刚加进去都是false
})
// 清空输入框
title.value = ''
}
//删除
const onDel = (i) => {
// 删除前的确认提示
if (confirm('确认删除?')) {//点击确认就进入if
// 调用 splice() 从当前点击的位置删除1个
todoList.value.splice(i, 1);
}
}
//清空
const onClear = () => {
if (confirm('确认清空?')) {
todoList.value = [];
}
}
</script>
<template>
<!-- section相当于div -->
<section clsss="todoapp">
<!-- 头部 -->
<header class="header">
<h1>记事本</h1>
<!-- 双向绑定,输入的数据<->视图 收集用户输入的值 -->
<input placeholder="请输⼊任务" class="new-todo" v-model="title" />
<!-- 把上面的值加在任务待办表单里面 -->
<button class="add" v-on:click="onAdd">添加任务</button>
</header>
<!-- 主体 -->
<section class="main">
<ul class="todo-list">
<!-- 循环生成li 也就是任务待办事项-->
<li class="todo" v-for="(item, index) in todoList" :key="item.id">
<div class="view">
<!-- 序号+待办事项 -->
<span class="index">{{ index + 1 }}</span>
<label>{{ item.name }}</label>
<!-- 点击×就进行删除 -->
<button class="destroy" @click="onDel(index)"></button>
</div>
</li>
</ul>
</section>
<!-- 尾部 -->
<footer class="footer">
<span class="todo-count">合 计: <strong> {{ todoList.length }} </strong></span>
<button class="clear-completed" @click="onClear()">清空任务</button>
</footer>
</section>
</template>
我们样式放在src里面style里面
html,
body {
margin: 0;
padding: 0;
}
body {
background: #fff;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
#app {
background: #fff;
margin: 180px 0 40px 0;
padding: 15px;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
#app .header input {
border: 2px solid rgba(175, 47, 47, 0.8);
border-radius: 10px;
}
#app .add {
position: absolute;
right: 15px;
top: 15px;
height: 68px;
width: 140px;
text-align: center;
background-color: rgba(175, 47, 47, 0.8);
color: #fff;
cursor: pointer;
font-size: 18px;
border-radius: 0 10px 10px 0;
}
#app input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#app input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
#app input::input-placeholder {
font-style: italic;
font-weight: 300;
color: gray;
}
#app h1 {
position: absolute;
top: -120px;
width: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 60px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.8);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}
.main {
position: relative;
z-index: 2;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
}
.todo-list li {
position: relative;
font-size: 24px;
height: 60px;
box-sizing: border-box;
border-bottom: 1px solid #e6e6e6;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list .view .index {
position: absolute;
color: gray;
left: 10px;
top: 20px;
font-size: 22px;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none;
/* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle+label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked+label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 50px auto 0;
color: #bfbfbf;
font-size: 15px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio: 0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}