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

news2024/10/17 17:22:48

代码获取

知识总结

⼀、指令补充

1.指令修饰符

1.1 什么是指令修饰符?

所谓指令修饰符就是让指令的 功能更强⼤,书写更便捷

1.2 分类
1.2.1 按键修饰符
  • @keydown.enter:当enter键按下时触发

  • @keyup.enter:当enter键抬起时触发

代码演示

<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 —>可以连⽤ 即阻⽌事件冒泡也阻⽌默认⾏为

代码示例

<!-- @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 —> 失去焦点时同步数据,⽽不是输⼊时同步数据

代码示例

<!-- @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

代码示例

<!-- @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、三元表达式

<p :class="条件 ? '类名1' : '类名2'"></p>

3、对象语法

当class动态绑定的是对象时,键就是类名,值就是布尔值,如果值是true,就添加这个类,否则删除这个类

<p class="box" :class="{ 类名1: 布尔值1, 类名2: 布尔值2 }"></p>

适⽤场景:⼀个类名,来回切换

4、静态class与动态class共存

静态class与动态class共存可以共存,⼆者会合并

<p class="item" :class="条件 ? '类名1' : '类名2'"></p>

3、代码演⽰

<!-- @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、静态代码准备

<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、代码实现

<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

<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>

2. 静态代码

<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. 代码实现

<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 函数,计算得到⼀个新数据进⾏展⽰
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 静态代码
<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 代码实现
<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. 语法?

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 代码演⽰
<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

const xxx = computed({
  get() {
    return 计算结果
  },
  set(val) {
  }
})

4. 案例 - 全返反选

4.1 效果
image-20241013132929612
4.2 静态代码
<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 代码实现
<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. 监视简单类型
<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. 监视复杂类型
<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. 静态代码

<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 统计总分,求平均分

代码实现

<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;
}












本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

执行powershell脚本出错:未对文件进行数字签名

解决执行powershell脚本时出错&#xff1a;未对文件 \test.ps1进行数字签名。无法在当前系统上运行该脚本 前言 今天从github上下载了一个PowerShell脚本要在本地运行&#xff0c;运行的时候出现了未对文件进行数字签名的问题&#xff0c;然后在这里记录下怎么解决 解决方法…

选择2024年开发App的理由,费用分析与效益

App开发费用受复杂度、团队、地理位置、平台等因素影响。低代码平台如ZohoCreator提供经济高效开发方案&#xff0c;降低费用并提升灵活性。2024年&#xff0c;企业需考虑这些因素制定长期规划。 调查显示&#xff1a; 企业估算应用开发费用时&#xff0c;常采用以下公式&…

大厂面试真题-组合和聚合的区别是什么

组合和聚合比较类似&#xff0c;二者都表示整体和部分之间的关系。 聚合关系的特点是&#xff1a;整体由部分构成&#xff0c;但是整体和部分之间并不是强依赖的关系&#xff0c;而是弱依 赖的关系&#xff0c;也就是说&#xff0c;即使整体不存在了&#xff0c;部分仍然存在…

算法专题六: 模拟与分治快排

目录 模拟1. 替换所有的问号2. 提莫攻击3. Z字形变换4. 外观数列5. 数青蛙分治快排1. 颜色分类2. 排序数组3. 数组中的第K个最大元素4. 库存管理Ⅲ 模拟 1. 替换所有的问号 算法思路: 本题就是简单的模拟, 只需按照题目的思路遍历所有的字符, 如果为?则将其替换, 替换时寻找2…

今日指数项目day8实战权限管理器(上)

3.权限管理器 3.1 权限列表展示功能 1&#xff09;原型效果 2&#xff09;接口说明 功能描述&#xff1a; 查询所有权限集合 服务路径&#xff1a; /api/permissions 服务方法&#xff1a;Get 请求参数&#xff1a;无响应数据格式: {"code": 1,"data":…

Python自然语言处理之pyltp模块介绍、安装与常见操作案例

pyltp是哈尔滨工业大学社会计算与信息检索研究中心推出的一款基于Python封装的自然语言处理工具&#xff0c;它提供了哈工大LTP&#xff08;Language Technology Platform&#xff09;工具包的接口。LTP工具包以其强大的中文分词、词性标注、命名实体识别、依存句法分析等功能&…

Idea不能创建java8切换路径

顶部的Server URL改成https://start.aliyun.com/

Ubuntu配置应用开机自启动

有些自己下载的应用想要配置开机自启动&#xff0c;比如snipaste&#xff0c;steam等&#xff0c;可以通过一下步骤进行配置 点击应用展开图标 这个里面就是开机自启动的一些应用&#xff0c;可以将自己想要自启动的应用添加到这个里面来 以上是snipaste配置&#xff0c;起…

大数据治理:构建数据驱动的智能决策体系

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

RocketMq详解:三、RocketMq通用生产和消费方法改造

文章目录 1.背景2.通用方法改造2.1添加maven依赖2.2 RocketMq基础配置2.3 配置类2.5 消息传输的对象和结果2.4 消息生产者2.5 消息消费者2.6 功能测试 1.背景 在第二章&#xff1a;《RocketMq详解&#xff1a;二、SpringBoot集成RocketMq》中我们已经实现了消费基本生产和消费…

CISCN2022-cactus

这周在疯狂学kernel pwn。 记录一下这题&#xff0c;race conditonmsg_msgpipe_buffer&#xff0c;kaslrsmepsmapkpti。 漏洞很简单&#xff0c;所有操作都没加锁&#xff0c;就是race condition了。edit什么的都只能2次。 很明显了&#xff0c;一次泄露基址&#xff0c;一次劫…

unidbg console debugger 调试技巧

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 打开debug日志 编辑 unidbg-android/src/test/resources/log4j.properties 把 log4j.logger.com.github.unidbg.AbstractEmulator 改为 DEBUG 当运行报错时…

lenovo联想 ThinkPad E14 Gen 2,E15 Gen 2 AMD(20T6,20T7,20T8,20T9)原厂Win10系统镜像下载

适用机型&#xff1a;【20T6、20T7、20T8、20T9】 链接&#xff1a;https://pan.baidu.com/s/1AVTvmiIHjafsFw8P7_jMPg?pwdzux5 提取码&#xff1a;zux5 联想原装WIN系统自带所有驱动、出厂主题壁纸、系统属性联机支持标志、系统属性专属LOGO标志、Office办公软件、联想电脑…

C#实现CRC32算法

CRC32 是一种校验和算法&#xff0c;用于检测消息是否未被修改。 它被广泛使用&#xff1a;例如&#xff0c;计算以太网发送包校验和。 public class CRC32 {private static readonly uint[] Crc32Table new uint[256];static CRC32(){uint i, j;uint crc;for (i 0; i < …

《深度学习》OpenCV 风格迁移、DNN模块 案例解析及实现

目录 一、风格迁移 1、什么是风格迁移 2、步骤 1&#xff09;训练 2&#xff09;迁移 二、DNN模块 1、什么是DNN模块 2、DNN模块特点 1&#xff09;轻量 2&#xff09;外部依赖性低 3&#xff09;方便 4&#xff09;集成 5&#xff09;通用性 3、流程图 4、图像…

软件设计之Redis(1)

软件设计之Redis(1) 路线图推荐&#xff1a; 【Java学习路线-极速版】【Java架构师技术图谱】 尚硅谷Redis零基础到进阶&#xff0c;最强redis7教程&#xff0c;阳哥亲自带练&#xff08;附redis面试题&#xff09; 资料可以去尚硅谷官网免费领取 学习内容&#xff1a; Redi…

Unity3D 观察者模式

Unity3D 泛型事件系统 观察者模式 观察者模式是一种行为设计模式&#xff0c;通过订阅机制&#xff0c;可以让对象触发事件时&#xff0c;通知多个其他对象。 在游戏逻辑中&#xff0c;UI 界面通常会监听一些事件&#xff0c;当数据层发生变化时&#xff0c;通过触发事件&am…

【JavaSE基础】Java 变量

为什么需要变量 变量是程序的基本组成单位 class Test{public static void main(String[] args){int a 1; //定义一个变量&#xff0c;类型为int&#xff0c;变量名为a&#xff0c;并赋值为1int b 3; //定义另一个变量&#xff0c;类型为int&#xff0c;变量名为b&#xff0…

sqli-labs less-25 and/or绕过

来到less-25 我们可以看到下面有提示&#xff0c;Hint: Your Input is Filtered with following result: 说明本关卡有过滤&#xff0c; 构造 http://192.168.140.130/sq/Less-25/?id1’ 页面报错&#xff0c;从报错可以得知闭合方式为,所以 用注释符&#xff0c;发现注释符…