知识总结
⼀、指令补充
1.指令修饰符
1.1 什么是指令修饰符?
所谓指令修饰符就是让指令的 功能更强⼤,书写更便捷
1.2 分类
1.2.1 按键修饰符
-
@keydown.enter:当enter键按下时触发
-
@keyup.enter:当enter键抬起时触发
代码演示
js
<template>
<div>
<input type="text" @keydown.enter="onKeyDown">
</div>
</template>
<script setup>
const onKeyDown = () => {
console.log('onKeyDown')
}
</script>
<style scoped></style>
1.2.2 事件修饰符
-
@事件名.stop ---> 阻⽌冒泡
-
@事件名.prevent --->阻⽌默认⾏为
-
@事件名.stop.prevent --->可以连⽤ 即阻⽌事件冒泡也阻⽌默认⾏为
代码示例
vue
<!-- @format -->
<script setup>
// p标签的点击事件
const onPClick = () => {
console.log('onPClick')
}
const onDivClick = () => {
console.log('onDivClick')
}
</script>
<template>
<!-- 事件修饰符 -->
<div @click="onDivClick">
<!-- .prevent: 阻止默认行为 -->
<a
href="https://baidu.com"
@click.prevent
>百度一下</a
>
<!-- .stop: 阻止冒泡,同名事件不会向上级传递 -->
<p @click.stop="onPClick"></p>
<!-- 修饰符的链式调用: 表明两个同时阻止 -->
<a
href="https://baidu.com"
@click.stop.prevent
>百度一下</a
>
</div>
</template>
<style scoped>
div {
width: 400px;
height: 200px;
background: plum;
}
div a {
display: block;
width: 100px;
text-decoration: none;
background: tomato;
text-align: center;
color: #fff;
}
div p {
width: 200px;
height: 100px;
background: rebeccapurple;
}
</style>
1.2.3 v-model修饰符
-
v-model.trim ---> 去除⾸尾空格
-
v-model.number ---> 尝试⽤parseFloat()转数字
-
v-model.lazy ---> 失去焦点时同步数据,⽽不是输⼊时同步数据
代码示例
vue
<!-- @format -->
<script setup>
import { reactive } from 'vue'
const goods = reactive({
name: '',
price: ''
})
</script>
<template>
<div>
<!-- .lazy修饰符 -->
<!-- 名称:
<input
type="text"
v-model.lazy="goods.name" /><br /><br /> -->
<!-- .trim修饰符 -->
名称:
<input
type="text"
v-model.trim="goods.name" /><br /><br />
<!-- .number修饰符 -->
价格:
<input
type="text"
v-model.number="goods.price" /><br /><br />
</div>
</template>
<style scoped></style>
1.3 总结
- 如何监听回⻋键?
答: 1、 @keydown.enter -> 回⻋按下
2、@keyup.enter -> 回⻋抬起
- 如何阻⽌默认⾏为、阻⽌冒泡?
答: 1、@click.prevent -> 阻⽌默认⾏为
2、@click.stop -> 阻⽌冒泡
3、 @click.prevent.stop -> 可以链式调⽤, ⼆者都阻⽌
- v-model的3个修饰符的作⽤是什么?
答: 1、.lazy -> 失去焦点再同步
2、.trim -> 去除⾸尾空格
3、.number -> 尝试转数字
2. v-model⽤在其他表单元素上
2.1 讲解内容
常⻅的表单元素都可以⽤ v-model 绑定关联, 作⽤是可以快速 获取 或 设置 表单元素的值
它会根据 控件类型 ⾃动选取 正确的属性 来更新元素
输⼊框 input:text ------> value
⽂本域 textarea ------> value
下拉菜单 select ------> value
单选框 input:radio ------> value
复选框 input:checkbox ------> checked / value
代码示例
vue
<!-- @format -->
<script setup>
import { ref } from 'vue'
// 自我介绍
const intro = ref('')
// 收集城市
const city = ref('SH')
// 收集血型
const blood = ref('ab')
// 是否同意用户协议
const isAgree = ref(false)
// 收集爱好
const hobby = ref(['ZQ', 'PB'])
</script>
<template>
<div>
<!-- 文本域 -->
<textarea v-model="intro" cols="30" rows="4" placeholder="请输入自我介绍"></textarea>
<br />
<br />
<!-- 下菜菜单 -->
<select v-model="city">
<option value="BJ">北京</option>
<option value="SH">上海</option>
<option value="SZ">深圳</option>
<option value="HZ">杭州</option>
</select>
<br />
<br />
<!-- 单选框:多个当中只能选择一个,需要给单选框手动添加 value 属性 -->
<input type="radio" value="a" v-model="blood" />A
<input type="radio" value="b" v-model="blood" />B
<input type="radio" value="ab" v-model="blood" />AB
<input type="radio" value="o" v-model="blood" />O
<br />
<br />
<input type="checkbox" v-model="isAgree" />是否同意用户协议
<br />
<br />
<input v-model="hobby" type="checkbox" value="LQ" />篮球
<input v-model="hobby" type="checkbox" value="ZQ" />足球
<input v-model="hobby" type="checkbox" value="YMQ" />羽毛球
<input v-model="hobby" type="checkbox" value="PPQ" />乒乓球
<br />
<input v-model="hobby" type="checkbox" value="PB" />跑步
<input v-model="hobby" type="checkbox" value="YY" />游戏
<input v-model="hobby" type="checkbox" value="PLT" />普拉提
<input v-model="hobby" type="checkbox" value="LDW" />拉丁舞
</div>
</template>
<style scoped></style>
2.2 总结
- v-model如何收集下拉列表的值?
答: v-model写在select上, 关联是选中option的value
- v-model如何收集单选框的值?
答: 给单选框添加value属性, v-model收集选中单选框的value
- v-model作⽤在复选框上组要注意什么?
答: 1、⼀个复选框, v-model绑定 布尔值 , 关联 checked 属性
2、⼀组复选框, v-model绑定 数组 , 关联 value 属性, 给复选框 ⼿动添加 value
⼆、样式绑定
1. 基本介绍
1、概述
为了⽅便开发者进⾏样式控制, Vue 扩展了 v-bind 的语法, 可以针对 class 类名 和 style ⾏内样式两个属性进⾏控制, 进⽽通过数据控制元素的样式
2、分类
2.1、操作class
2.2、操作style
2、操作class
1、语法
:class = "三元表达式 / 对象"
2、三元表达式
vue
<p :class="条件 ? '类名1' : '类名2'"></p>
3、对象语法
当class动态绑定的是对象时,键就是类名,值就是布尔值,如果值是true,就添加这个类,否则删除这个类
vue
<p class="box" :class="{ 类名1: 布尔值1, 类名2: 布尔值2 }"></p>
适⽤场景:⼀个类名,来回切换
4、静态class与动态class共存
静态class与动态class共存可以共存,⼆者会合并
vue
<p class="item" :class="条件 ? '类名1' : '类名2'"></p>
3、代码演⽰
vue
<!-- @format -->
<script setup>
import { ref } from 'vue'
// 是否激活
const isActive = ref(true)
</script>
<template>
<div>
<!-- 1. 三元绑定 -->
<p :class="isActive ? 'active' : ''">Active1</p>
<!-- 2. 对象绑定 -->
<p :class="{ active: isActive }">Active2</p>
<!-- 3. 静态class与动态class可以共存,二者会合并 -->
<p class="item" :class="{ active: isActive }">
Active3
</p>
</div>
</template>
<style scoped>
.active {
color: red;
}
</style>
4、总结
- 如何通过class动态控制元素的样式?
答: 1、 :class="三元表达式"
2、 :class="{ 类名: 布尔值 }" , 布尔值为true则添加类名; 否则移除
- 静态和动态class可以共存吗?
答:可以 共存, ⼆者会合并
5、案例-京东秒杀-tab栏切换导航⾼亮
1、需求
当我们点击哪个tab⻚签时,哪个tab⻚签就⾼亮
2、静态代码准备
vue
<script setup>
// tabs 列表
const tabs = [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每⽇特价' },
{ id: 3, name: '品类秒杀' }
]
</script>
<template>
<div>
<ul>
<li><a class="active" href="#">京东秒杀</a></li>
</ul>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
}
ul {
display: flex;
border-bottom: 2px solid #e01222;
padding: 0 10px;
}
li {
width: 100px;
height: 50px;
line-height: 50px;
list-style: none;
text-align: center;
}
li a {
display: block;
text-decoration: none;
font-weight: bold;
color: #333333;
}
li a.active {
background-color: #e01222;
color: #fff;
}
</style>
3、思路
-
基于数据,动态渲染tab(v-for)
-
准备⼀个下标 记录⾼亮哪⼀个 tab
-
基于下标动态切换class的类名
4、代码实现
vue
<script setup>
// tabs 列表
const tabs = [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每⽇特价' },
{ id: 3, name: '品类秒杀' }
]
import { ref } from 'vue'
const activeTabIndex = ref(0)
</script>
<template>
<div>
<ul>
<li v-for="(tab, index) in tabs" :key="tab.id">
<a :class="{active: activeTabIndex === index}" href="#" @click="activeTabIndex = index">{{ tab.name }}</a>
</li>
</ul>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
}
ul {
display: flex;
border-bottom: 2px solid #e01222;
padding: 0 10px;
}
li {
width: 100px;
height: 50px;
line-height: 50px;
list-style: none;
text-align: center;
}
li a {
display: block;
text-decoration: none;
font-weight: bold;
color: #333333;
}
li a.active {
background-color: #e01222;
color: #fff;
}
</style>
6、案例-进度条
1. 操作style
html
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
2. 静态代码
vue
<script setup></script>
<template>
<div class="progress">
<div class="inner" style="width: 50%">
<span>50%</span>
</div>
</div>
<button>设置25%</button>
<button>设置50%</button>
<button>设置75%</button>
<button>设置100%</button>
</template>
<style>
.progress {
height: 25px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
height: 20px;
border-radius: 10px;
text-align: right;
position: relative;
background-color: #409eff;
background-size: 20px 20px;
box-sizing: border-box;
transition: all 1s;
}
.inner span {
position: absolute;
right: -25px;
bottom: -25px;
}
</style>
3. 代码实现
vue
<script setup>
import { ref } from 'vue'
const currentCount = ref(0);
const totalCount = ref(4);
</script>
<template>
<div class="progress">
<div class="inner" :style="{width: (currentCount / totalCount * 100) + '%'}">
<span>{{currentCount / totalCount * 100}}%</span>
</div>
</div>
<button v-for="i in totalCount + 1" :key="index" @click="currentCount = i-1">设置{{ (i - 1) / totalCount * 100 }}%</button>
</template>
<style>
.progress {
height: 25px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
height: 20px;
border-radius: 10px;
text-align: right;
position: relative;
background-color: #409eff;
background-size: 20px 20px;
box-sizing: border-box;
transition: all 1s;
}
.inner span {
position: absolute;
right: -25px;
bottom: -25px;
}
</style>
4. 总结
- 如何给元素动态绑定style?
答: :style="{ 属性名1: 表达式1, 属性名2: 表达式2, ... }"
三、计算属性
1. 基本使⽤
1.1 概念
基于现有的数据,计算出来的新数据; 当现有的数据变化,会⾃动重新计算。
1.2 语法
- 使⽤ computed 函数,计算得到⼀个新数据进⾏展⽰
js
const 新数据 = computed(() => {
// some code ...
return 结果
})
// 商品列表(原始数据)
const goodsList = ref([
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '书籍', num: 2 }
])
1.3 案例
⽐如我们可以使⽤计算属性实现下⾯这个业务场景
1.4 静态代码
vue
<script setup>
import { ref } from 'vue'
// 商品列表(原始数据)
const goodsList = ref([
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '书籍', num: 2 }
])
</script>
<template>
<h3>zxj2022的礼物清单</h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="item in goodsList" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<!-- ⽬标:统计求和, 展⽰礼物总数 -->
<p>礼物总数:? 个</p>
</template>
<style>
table {
width: 350px;
border: 1px solid #333;
}
table th,
table td {
border: 1px solid #333;
}
table td {
text-align: center;
}
</style>
1.5 代码实现
js
<script setup>
import { ref,computed } from 'vue'
// 商品列表(原始数据)
const goodsList = ref([
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '书籍', num: 2 }
])
// 由于这⾥只有原始数据,没有直接给我们提供商品总数量
// 但是我们可以基于现有的 goodsList 计算得到总数量
// 那么推荐把总数量声明为 计算属性
const totalGifts = computed(() => {
return goodsList.value.reduce((total, item) => total + item.num, 0)
})
</script>
<template>
<h3>zxj2022的礼物清单</h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="item in goodsList" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<!-- ⽬标:统计求和, 展⽰礼物总数 -->
<p>礼物总数:{{ totalGifts }} 个</p>
</template>
<style >
table {
width: 350px;
border: 1px solid #333;
}
table th,
table td {
border: 1px solid #333;
}
table td {
text-align: center;
}
</style>
1.6 注意
-
计算属性必须 有返回值
-
使⽤和 ref/reactive 数据⼀样 , 可⽤于 插值 , 也可 配合指令
1.7 总结
- 何时⽤计算属性?
答:基于现在数据计算得到新数据的时候
- 语法?
js
import { computed } from 'vue'
const totalGifts = computed(() => {
return 返回值;
})
2. 计算属性 VS 普通函数
2.1 计算属性
2.1.1 作⽤
封装了⼀段对于基于现有数据,计算求得⼀个新数据
2.1.2 语法
-
写在computed函数中,必须返回
-
作为属性,直接使⽤
-
js中获取计算属性: 计算属性.value
-
模板中使⽤计算属性:{{ 计算属性 }} 或 配合指令
-
2.2 普通函数
2.2.1 作⽤
封装⼀段js代码,⽤于调⽤以处理业务逻辑。
2.2.2 语法
-
直接在script下定义
-
作为函数调⽤
-
js中调⽤:函数名(实参列表)
-
模板中调⽤ {{ 函数名 (实参列表) }} 或者 @事件名="⽅法名(实参列表)"
-
-
计算属性的优势
-
缓存特性(提升性能), 函数没有缓存特性
-
计算属性会对计算出来的结果缓存, 再次使⽤直接读取缓存
-
只有依赖项变了, 才会⾃动重新计算, 并再次缓存新数据
-
2.3 总结
- 计算属性与普通函数的区别?
答: 1、计算属性基于依赖 有缓存特性,普通函数 没有缓存
2、当基于现有的数据计算得到新数据,推荐使⽤计算属性
3、当处理业务逻辑时,推荐使⽤函数,⽐如点击事件的处理函数
3. 计算属性的完整写法
3.1 思考
既然计算属性也是属性,能访问,应该也能修改了?
-
计算属性默认的写法,只能读(展⽰数据),不能 "改"
-
如果要 "改" → 需要⽤计算属性的完整写法
3.2 代码演⽰
js
<script setup>
import { computed } from 'vue'
// 完整写法 = get + set
const uname = computed({
// 使⽤计算属性的时候,⾃动触发get,get内部必须 `return 计算结果`
get() {
// 计算得到新数据
return '姬霓太美'
},
// 给计算属性赋值的时候,⾃动触发set,并接收赋予的新值val
set(val) {
// ⼀段修改逻辑
console.log(val)
}
})
</script>
<template>
<input type="text" v-model="uname" />
</template>
3.3 总结
- 何时使⽤计算属性完整写法?
答:当 修改计算属性 时, 也就是当计算属性配合v-model的时候
- 完整写法的是?
答: get + set
js
const xxx = computed({
get() {
return 计算结果
},
set(val) {
}
})
4. 案例 - 全返反选
4.1 效果
4.2 静态代码
vue
<script setup>
import { ref } from 'vue'
// 计划列表
const planList = ref([
{ id: 12, name: '跑步', done: false },
{ id: 76, name: '看书', done: false },
{ id: 31, name: '撸码', done: false },
{ id: 49, name: '追剧', done: false }
])
</script>
<template>
<p>
<span>
<input type="checkbox" id="all" />
<label for="all">全选</label>
</span>
<button>反选</button>
</p>
<ul>
<li>
<input type="checkbox" />
<span>xxx</span>
</li>
</ul>
</template>
<style lang="scss">
* {
margin: 0;
padding: 0;
}
div {
width: 400px;
margin: 100px auto;
padding: 15px;
font-size: 18px;
background: plum;
p {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
button {
padding: 3px 6px;
}
}
}
ul {
list-style: none;
li {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
border-top: 1px solid #ccc;
span.completed {
color: #ddd;
text-decoration: line-through;
}
}
}
input {
margin-right: 8px;
}
</style>
4.3 代码实现
vue
<script setup>
import { ref, computed } from 'vue'
// 计划列表
const planList = ref([
{ id: 12, name: '跑步', done: true },
{ id: 76, name: '看书', done: false },
{ id: 31, name: '撸码', done: false },
{ id: 49, name: '追剧', done: false }
])
// 全选绑定
const allDone = computed({
// 使⽤计算属性⾃动触发 get
get() {
// every: 检测每⼀个
return planList.value.every(plan => plan.done)
},
// 修改计算属性⾃动触发 set
set(val) {
// val: 给计算属性赋予的新值,在这⾥就是表全选复选框的状态
// 遍历 planList 数组,把每个⼩选的 done 属性与 val 保持⼀致即可
planList.value.forEach(plan => plan.done = val)
}
})
</script>
<template>
<p>
<span>
<input type="checkbox" id="all" v-model="allDone"/>
<label for="all" >全选</label>
</span>
<button @click="planList.forEach(plan => plan.done = !plan.done)">反选</button>
</p>
<ul>
<li v-for="(plan, index) in planList" :key="plan.id">
<input type="checkbox" v-model="plan.done" />
<span :class="{completed: plan.done}">{{ plan.name }}</span>
</li>
</ul>
</template>
<style lang="scss">
* {
margin: 0;
padding: 0;
}
div {
width: 400px;
margin: 100px auto;
padding: 15px;
font-size: 18px;
background: plum;
p {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
button {
padding: 3px 6px;
}
}
}
ul {
list-style: none;
li {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
border-top: 1px solid #ccc;
span.completed {
color: #ddd;
text-decoration: line-through;
}
}
}
input {
margin-right: 8px;
}
</style>
四、侦听器
1. 作⽤
监视数据变化, 执⾏DOM操作或异步操作
2. 语法
- 监视简单类型
vue
<script setup>
import { ref, watch } from 'vue'
// 搜索框关键字
const keyword = ref('')
// 监视 keyword, 只要 keyword 的值变了, 就会执⾏回调函数
watch(keyword, (newVal, oldVal) => {
// newVal: 新值
// oldVal: 旧值
})
</script>
<template>
<div>
<input type="text" v-model="keyword" placeholder="请输⼊关键字" />
</div>
</template>
- 监视复杂类型
vue
<script setup>
import { reactive, watch } from 'vue'
// 表单对象
const obj = reactive({
username: '',
password: ''
})
// 监视 obj 的变化
watch(obj, (newVal, oldVal) => {
console.log(newVal, oldVal)
})
</script>
<template>
<div>
<input type="text" v-model="obj.username" placeholder="请输⼊⽤⼾名" />
<br />
<br />
<input type="text" v-model="obj.password" placeholder="请输⼊密码" />
</div>
</template>
3. 总结
- watch的作⽤是?
答: 监视响应式数据的变化, 当数据变了, 针对性的DOM操作或异步操作
五、案例-成绩管理
1. 效果
2. 静态代码
vue
<script setup>
import { ref } from 'vue'
// 成绩列表
const scoreList = ref([
{ id: 19, subject: '语⽂', score: 94 },
{ id: 27, subject: '数学', score: 59 },
{ id: 12, subject: '英语', score: 92 }
])
</script>
<template>
<div class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科⽬</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>语⽂</td>
<td class="red">46</td>
<td><a href="#">删除</a></td>
</tr>
<tr>
<td>2</td>
<td>英语</td>
<td>80</td>
<td><a href="#">删除</a></td>
</tr>
</tbody>
<tbody>
<tr>
<td colspan="5">
<span class="none">暂⽆数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分: 246</span>
<span style="margin-left: 50px">平均分: 79</span>
</td>
</tr>
</tfoot>
</table>
</div>
<form class="form">
<div class="form-item">
<div class="label">科⽬:</div>
<div class="input">
<input type="text" placeholder="请输⼊科⽬" />
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input type="text" placeholder="请输⼊分数" />
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button class="submit">添加</button>
</div>
</div>
</form>
</div>
</template>
<style>
.score-case {
width: 1000px;
margin: 50px auto;
display: flex;
}
.score-case .table {
flex: 4;
}
.score-case .table table {
width: 100%;
border-spacing: 0;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
.score-case .table table th {
background: #f5f5f5;
}
.score-case .table table tr:hover td {
background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
text-align: center;
padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {
color: red;
}
.score-case .table .none {
height: 100px;
line-height: 100px;
color: #999;
}
.score-case .form {
flex: 1;
padding: 20px;
}
.score-case .form .form-item {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.score-case .form .form-item .label {
width: 60px;
text-align: right;
font-size: 14px;
}
.score-case .form .form-item .input {
flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 200px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
}
.score-case .form .form-item input::placeholder {
color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {
appearance: none;
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 10px;
margin-right: 10px;
font-size: 12px;
background: #ccc;
}
.score-case .form .form-item .submit {
border-color: #069;
background: #069;
color: #fff;
}
</style>
3. 功能描述
3.1 渲染功能
3.2 添加功能
3.3 删除功能
3.4 统计总分,求平均分
代码实现
vue
<script setup>
import { ref, computed,watch } from 'vue'
const SCORE_LIST_KEY = 'scoreList'
// 成绩列表
const scoreList = ref(JSON.parse(localStorage.getItem(SCORE_LIST_KEY)) || [
{ id: 19, subject: '语⽂', score: 94 },
{ id: 27, subject: '数学', score: 59 },
{ id: 12, subject: '英语', score: 92 }
])
// 总分
const total = computed(() => {
return scoreList.value.reduce((total, item) => total + item.score, 0)
})
// 平均分
const average = computed(() => {
if (scoreList.value.length === 0) return 0.00
return (total.value / scoreList.value.length).toFixed(2)
})
// 删除指定索引的成绩
const deleteByIndex = (index) => {
if (index < 0 || index >= scoreList.value.length) return
if (confirm('确定删除吗?')) {
scoreList.value.splice(index, 1)
}
}
// 添加成绩
const scoreObj = ref({ subject: '', score: '' })
const addScore = () => {
if (!scoreObj.value.subject || !scoreObj.value.score) {
alert('请填写科目和分数')
return
}
scoreList.value.push({
id: Date.now(),
subject: scoreObj.value.subject,
score: Number(scoreObj.value.score)
})
scoreObj.value.subject = ''
scoreObj.value.score = ''
}
// 监听 scoreList 的变化, 存储到 localStorage, 持久化存储
watch(scoreList, (newVal) => {
localStorage.setItem(SCORE_LIST_KEY, JSON.stringify(newVal))
}, { deep: true })
</script>
<template>
<div class="score-case">
<!-- 表格部分 -->
<div class="table">
<table>
<!-- 表头部分, 不需要变动 -->
<thead>
<tr>
<th>编号</th>
<th>科⽬</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<!-- 内容部分, 需要依据 scoreList 动态改变 -->
<tbody v-for="(score, index) in scoreList" :key="score.id">
<tr>
<td>{{ index + 1 }}</td>
<td>{{ score.subject }}</td>
<td :class="{ red: score.score < 60 }">{{ score.score }}</td>
<td><a href="#" @click="deleteByIndex(index)">删除</a></td>
</tr>
</tbody>
<!-- 底部部分, 不需要变动 -->
<tbody>
<tr>
<td colspan="5">
<span class="none">暂⽆数据</span>
</td>
</tr>
</tbody>
<!-- 尾部部分, 需要计算属性补充 -->
<tfoot>
<tr>
<td colspan="5">
<span>总分: {{ total }}</span>
<span style="margin-left: 50px">平均分: {{ average }}</span>
</td>
</tr>
</tfoot>
</table>
</div>
<!-- 表单部分 -->
<form class="form">
<div class="form-item">
<div class="label">科⽬:</div>
<div class="input">
<input type="text" placeholder="请输⼊科⽬" v-model="scoreObj.subject" />
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input type="text" placeholder="请输⼊分数" v-model="scoreObj.score" />
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button class="submit" @click="addScore()">添加</button>
</div>
</div>
</form>
</div>
</template>
<style>
.score-case {
width: 1000px;
margin: 50px auto;
display: flex;
}
.score-case .table {
flex: 4;
}
.score-case .table table {
width: 100%;
border-spacing: 0;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
.score-case .table table th {
background: #f5f5f5;
}
.score-case .table table tr:hover td {
background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
text-align: center;
padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {
color: red;
}
.score-case .table .none {
height: 100px;
line-height: 100px;
color: #999;
}
.score-case .form {
flex: 1;
padding: 20px;
}
.score-case .form .form-item {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.score-case .form .form-item .label {
width: 60px;
text-align: right;
font-size: 14px;
}
.score-case .form .form-item .input {
flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 200px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
}
.score-case .form .form-item input::placeholder {
color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {
appearance: none;
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 10px;
margin-right: 10px;
font-size: 12px;
background: #ccc;
}
.score-case .form .form-item .submit {
border-color: #069;
background: #069;
color: #fff;
}
</style>
width: 100%;
border-spacing: 0;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
.score-case .table table th {
background: #f5f5f5;
}
.score-case .table table tr:hover td {
background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
text-align: center;
padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {
color: red;
}
.score-case .table .none {
height: 100px;
line-height: 100px;
color: #999;
}
.score-case .form {
flex: 1;
padding: 20px;
}
.score-case .form .form-item {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.score-case .form .form-item .label {
width: 60px;
text-align: right;
font-size: 14px;
}
.score-case .form .form-item .input {
flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 200px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
}
.score-case .form .form-item input::placeholder {
color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {
appearance: none;
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 10px;
margin-right: 10px;
font-size: 12px;
background: #ccc;
}
.score-case .form .form-item .submit {
border-color: #069;
background: #069;
color: #fff;
}