【vue】指令补充+样式绑定+计算属性+侦听器

代码获取

知识总结

⼀、指令补充

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 事件修饰符
  1. @事件名.stop ---> 阻⽌冒泡

  2. @事件名.prevent --->阻⽌默认⾏为

  3. @事件名.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. 如何监听回⻋键?

​ 答: 1、 @keydown.enter -> 回⻋按下

​ 2、@keyup.enter -> 回⻋抬起

  1. 如何阻⽌默认⾏为、阻⽌冒泡?

​ 答: 1、@click.prevent -> 阻⽌默认⾏为

​ 2、@click.stop -> 阻⽌冒泡

​ 3、 @click.prevent.stop -> 可以链式调⽤, ⼆者都阻⽌

  1. 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 总结
  1. v-model如何收集下拉列表的值?

​ 答: v-model写在select上, 关联是选中option的value

  1. v-model如何收集单选框的值?

​ 答: 给单选框添加value属性, v-model收集选中单选框的value

  1. 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、总结

  1. 如何通过class动态控制元素的样式?

​ 答: 1、 :class="三元表达式"

​ 2、 :class="{ 类名: 布尔值 }" , 布尔值为true则添加类名; 否则移除

  1. 静态和动态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、思路

  1. 基于数据,动态渲染tab(v-for)

  2. 准备⼀个下标 记录⾼亮哪⼀个 tab

  3. 基于下标动态切换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. 总结

  1. 如何给元素动态绑定style?

​ 答: :style="{ 属性名1: 表达式1, 属性名2: 表达式2, ... }"

三、计算属性

1. 基本使⽤

1.1 概念

基于现有的数据,计算出来的新数据; 当现有的数据变化,会⾃动重新计算。

1.2 语法
  1. 使⽤ 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 注意
  1. 计算属性必须 有返回值

  2. 使⽤和 ref/reactive 数据⼀样 , 可⽤于 插值 , 也可 配合指令

1.7 总结
  1. 何时⽤计算属性?

​ 答:基于现在数据计算得到新数据的时候

  1. 语法?
js 复制代码
import { computed } from 'vue'

const totalGifts = computed(() => {
  return 返回值;
})

2. 计算属性 VS 普通函数

2.1 计算属性
2.1.1 作⽤

封装了⼀段对于基于现有数据,计算求得⼀个新数据

2.1.2 语法
  1. 写在computed函数中,必须返回

  2. 作为属性,直接使⽤

    • js中获取计算属性: 计算属性.value

    • 模板中使⽤计算属性:{{ 计算属性 }} 或 配合指令

2.2 普通函数
2.2.1 作⽤

封装⼀段js代码,⽤于调⽤以处理业务逻辑。

2.2.2 语法
  1. 直接在script下定义

  2. 作为函数调⽤

    • js中调⽤:函数名(实参列表)

    • 模板中调⽤ {{ 函数名 (实参列表) }} 或者 @事件名="⽅法名(实参列表)"

  3. 计算属性的优势

    • 缓存特性(提升性能), 函数没有缓存特性

    • 计算属性会对计算出来的结果缓存, 再次使⽤直接读取缓存

    • 只有依赖项变了, 才会⾃动重新计算, 并再次缓存新数据

2.3 总结
  1. 计算属性与普通函数的区别?

​ 答: 1、计算属性基于依赖 有缓存特性,普通函数 没有缓存

​ 2、当基于现有的数据计算得到新数据,推荐使⽤计算属性

​ 3、当处理业务逻辑时,推荐使⽤函数,⽐如点击事件的处理函数

3. 计算属性的完整写法

3.1 思考

既然计算属性也是属性,能访问,应该也能修改了?

  1. 计算属性默认的写法,只能读(展⽰数据),不能 "改"

  2. 如果要 "改" → 需要⽤计算属性的完整写法

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 总结
  1. 何时使⽤计算属性完整写法?

​ 答:当 修改计算属性 时, 也就是当计算属性配合v-model的时候

  1. 完整写法的是?

​ 答: 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. 语法

  1. 监视简单类型
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>
  1. 监视复杂类型
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. 总结

  1. 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;

}

复制代码
相关推荐
byzh_rc8 分钟前
[微机原理与系统设计-从入门到入土] 微型计算机基础
开发语言·javascript·ecmascript
m0_471199638 分钟前
【小程序】订单数据缓存 以及针对海量库存数据的 懒加载+数据分片 的具体实现方式
前端·vue.js·小程序
编程大师哥9 分钟前
Java web
java·开发语言·前端
A小码哥10 分钟前
Vibe Coding 提示词优化的四个实战策略
前端
Murrays11 分钟前
【React】01 初识 React
前端·javascript·react.js
大喜xi14 分钟前
ReactNative 使用百分比宽度时,aspectRatio 在某些情况下无法正确推断出高度,导致图片高度为 0,从而无法显示
前端
helloCat14 分钟前
你的前端代码应该怎么写
前端·javascript·架构
电商API_1800790524715 分钟前
大麦网API实战指南:关键字搜索与详情数据获取全解析
java·大数据·前端·人工智能·spring·网络爬虫
康一夏16 分钟前
CSS盒模型(Box Model) 原理
前端·css
web前端12316 分钟前
React Hooks 介绍与实践要点
前端·react.js