一、Vue3的生命周期及在setup中的生命周期钩子
Vue官网:https://cn.vuejs.org/api/composition-api-lifecycle.html
在Vue3中,生命周期勾子函数被重新设计,以更好地支持组合式API的使用。
1、Vue3中的生命周期勾子函数
setup:在组件实例创建之前执行,用于组件的设置,例如响应式数据的创建、计算属性的设置、事件监听器的设置等。
beforeCreate:在组件实例创建之前执行,与Vue2.x中的beforeCreate钩子相同。
created:在组件实例创建之后执行,与Vue2.x中的created钩子相同。
beforeMount:在组件挂载到DOM之前执行,与Vue2.x中的beforeMount钩子相同。
mounted:在组件挂载到DOM之后执行,与Vue2.x中的mounted钩子相同。
beforeUpdate:在组件更新之前执行,与Vue2.x中的beforeUpdate钩子相同。
updated:在组件更新之后执行,与Vue2.x中的updated钩子相同。
beforeUnmount:在组件卸载之前执行,与Vue2.x中的beforeDestroy钩子相同。
unmounted:在组件卸载之后执行,与Vue2.x中的destroyed钩子相同。
需要注意的是,Vue3中没有activated和deactivated钩子函数,因为在Vue3中,keep-alive组件的行为被改变,而不再需要这两个钩子函数。
2、案例 onBeforeMount, onMounted, onBeforeUnmount, onUnmounted函数
<template>
<div>
<h1>Vue 3 生命周期</h1>
</div>
</template>
<script setup lang="ts">
import { onBeforeMount, onMounted, onBeforeUnmount, onUnmounted } from "vue";
onBeforeMount(() => {
console.log("组件挂载前");
});
onMounted(() => {
console.log("组件挂载完成");
});
onBeforeUnmount(() => {
console.log("组件卸载之前");
});
onUnmounted(() => {
console.log("组件卸载完成");
});
</script>
3、案例 onBeforeUpdate, onUpdated函数用法
<template>
<div>
<p>更新前的值:{{ beforeUpdateMessage }}</p>
<p>更新后的值:{{ message }}</p>
<button @click="updateMessage">更新</button>
</div>
</template>
<script setup lang="ts">
import { onBeforeUpdate, onUpdated, ref } from "vue";
const message = ref("A");
const beforeUpdateMessage = ref("A");
const updateMessage = () => {
beforeUpdateMessage.value = message.value;
message.value = message.value === "A" ? "B" : "A";
};
onBeforeUpdate(() => {
console.log("更新前的message:", beforeUpdateMessage.value);
});
onUpdated(() => {
console.log("更新后的message:", message.value);
});
</script>
4、案例 onMounted, onActivated, onUnmounted, onUpdated, onDeactivated等函数用法
在 Vue 3 中, 组件用于缓存组件实例,避免多次渲染和销毁。
具体来说, 组件可以用来缓存那些需要频繁切换显示和隐藏的组件,如页面中的 tab 切换组件、模态框等。这样,在组件被缓存后,当下次需要显示该组件时,Vue 会直接从缓存中取出该组件实例并重新挂载,而不需要重新创建和渲染该组件。这样可以大大提高页面的响应速度和用户体验。
需要注意的是, 组件只会缓存有状态的组件,即那些具有自己的数据和生命周期的组件。而对于那些无状态的组件,如纯展示型组件,不应该使用 进行缓存,因为它们的渲染代价非常小,不值得进行缓存和复用。
(1)运行效果
(2)App.vue
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<keep-alive>
<HelloWorld></HelloWorld>
</keep-alive>
</template>
(3)HelloWorld.vue
<script setup lang="ts">
import { objectToString } from '@vue/shared';
import { ref, reactive, onMounted, onActivated, onUnmounted, onUpdated, onDeactivated } from 'vue';
const user = reactive({ // 使用reactive声明对象类型变量,如Object、Array、Date...
name: '无名氏',
gender: '男'
})
onMounted(() => {
console.log("组件挂载")
})
onUnmounted(() => {
console.log("组件卸载")
})
onUpdated(() => {
console.log("组件更新")
})
onActivated(() => {
console.log("keepAlive 组件 激活")
})
onDeactivated(() => {
console.log("keepAlive 组件 非激活")
})
function changeUser() {
console.log(user.name)
if (user.name == "无名氏") {
user.name = "小红"
user.gender = "女"
}
else {
user.name = "无名氏"
user.gender = "男"
}
}
</script>
<template>
<div>
<input type="text" v-model="user.name" />
<input type="text" v-model="user.gender" />
<button @click="changeUser">切换姓名</button>
</div>
</template>
二、setup() 函数
1、setup函数是处于生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的。
2、setup() 钩子是在组件中使用组合式 API 的入口
3、在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用。
4、setup 函数将接收两个参数,props&context
Props :props接收父组件传入的值,为Proxy对象,且为响应式,所以不能使用 ES6 解构,它会消除 prop 的响应性
context:官方解释=>context 是一个普通的 JavaScript 对象,它暴露组件的三个 property:
5、setup中没有this
6、setup函数只会在组件初始化的时候执行一次
7、setup函数在beforeCreate生命周期钩子执行之前执行
三、setup函数语法糖案例
随着Vue3和TypeScript的大浪潮不断袭来,越来越多的Vue项目采用了TypeScript的语法来编写代码,而Vue3的JS中的Setup语法糖也越来越广泛的使用,给我们这些以前用弱类型的JS语法编写Vue代码的人不少冲击,不过随着大量的学习和代码编写,经历过一段难熬的时间后,逐步适应了这种和之前差别不小的写法和冲击。
下面介绍总结了Vue3中一些常见的基于TypeScript的Setup语法与组合式 API的处理代码案例。
TypeScript(简称ts)是微软推出的静态类型的语言,相比于js,TypeScript拥有强类型、编译器严谨的语法检查、更加严苛的语法,TypeScript 是 JS类型的超集,并支持了泛型、类型、命名空间、枚举等特性,弥补了 JS 在大型应用开发中的不足。TypeScript 是 JavaScript 的强类型版本,最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。
基于TypeScript的Setup语法糖写法越来越多,熟练使用的话,需要一个学习过程,另外ElementPlus控件也有了一些不同的变化,而且它的官方案例代码基本上采用了Setup语法糖的写法来提供例子代码。
提示: vue3.2 版本开始才能使用语法糖!
在 Vue3.0 中变量必须 return 出来, template 中才能使用;而在 Vue3.2 中只需要在 script 标签上加上 setup 属性,无需 return , template 便可直接使用。
1、使用Setup语法糖实现两个页面间传参案例
defineProps用于父组件向子组件传值。
defineEmits用于子组件向父组件传值。
(1)案例(父传子,父:Father.vue,子:Son.vue)
在vue3中,父组件通过v-bind来传值,子组件使用defineProps来接收
Father.vue
<script setup lang="ts">
import Son from "./Son.vue";
import { ref } from "vue";
let age = ref(18);
</script>
<template>
<Son msg="Vue3 Setup语法糖案例" :nameList="['张三', '李四', '王五']" :age="18"/>
</template>
Son.vue
<script setup lang="ts">
// defineProps是一个函数,传来的参数只读,不能修改
const props = defineProps({
msg: {
type: String,
required: true,
default: "你好吗?如果父组件不给我传值,这就是msg的默认值",
},
nameList: Array,
age:Number
});
</script>
<template>
<h1>子组件接收父组件传递过来的参数如下</h1>
<h2>msg:{{ msg }}</h2>
<h2>age:{{ age }}</h2>
<h2>nameList:</h2>
<ul>
<li v-for="name in nameList">{{ name }}</li>
</ul>
</template>
(2)案例(子传父,父:Father1.vue,子:Son1.vue)
在Vue3中,子组件通过defineEmits()函数触发事件,父组件通过v-on指令监听子组件自定义事件。
Father1.vue
<script setup lang="ts">
import Son1 from "./Son1.vue";
function onAction(msg:string, age:number){
alert(msg + ": " + age);
}
</script>
<template>
<!-- 父组件可以使用 v-on 监听子组件触发的事件 -->
<!-- 这里的处理函数接收了子组件触发事件时的额外参数并将它赋值给了本地状态: -->
<Son1 @response="onAction" />
</template>
Son1.vue
<script setup lang="ts">
// 声明触发的事件
const emit = defineEmits(['response'])
const onChange = function () {
// 带参数触发,一个字符串参数,一个数字参数
emit('response', 'hello from child', 18)
};
</script>
<template>
<button @click="onChange">子传父</button>
</template>
(3)验证类型:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
(4)父传子各种类型使用案例
Father2.vue
<template>
<Son
:str="str"
:num="num"
:bool="bool"
:arr="arr"
:obj="obj"
:date="date"
:a="a"
:b="b"
:getConsole="getConsole"
id="abc"
class="bcd"
></Son>
</template>
<script setup lang="ts">
import Son from "./Son2.vue";
// 定义属性
const str = "前端开发";
const num = 18;
const bool = true;
const arr = ["计算机", "电子", "通信", "数媒技术", "物联网"];
const obj = {
name: "Julia",
age: 20,
};
const date = new Date();
const a = Symbol("学会学习,决心奋斗,");
const b = Symbol("追求先进,争取全面!");
// 定义方法
const getConsole = () => {
console.log("传递给子组件的方法");
};
</script>
Son2.vue
<script setup lang="ts">
// defineProps是一个函数,传来的参数只读,不能修改
const props = defineProps({
str: String,
num: Number,
bool: Boolean,
arr: Array,
obj: Object,
date: Date,
getConsole: Function,
message: Object,
a: Symbol,
b: Symbol,
});
</script>
<template>
<h3 v-bind="$attrs">字符串: {{ props.str }}</h3>
<h3>数字: {{ props.num }}</h3>
<h3>布尔: {{ props.bool }}</h3>
<h3>数组: {{ props.arr }}</h3>
<h3>对象: {{ props.obj }}</h3>
<h3>日期: {{ props.date }}</h3>
<h3>Symbol: {{ props.a }} - {{ props.b }}</h3>
</template>
(5)子传父:计数器案例
Father3.vue
<template>
<h1>当前数值: {{ counter }}</h1>
<Son @add="add" @sub="sub"></Son>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Son from "./Son3.vue";
// 1、定义的属性
const counter = ref(0);
// 2、定义的方法
function add(num: number) {
counter.value = num;
}
function sub(num: number) {
counter.value = num;
}
</script>
Son3.vue
<template>
<div>
<input type="text" v-model.number="num" />
<button @click="increment">加1</button>
<button @click="decrement">减1</button>
</div>
</template>
<script setup lang="ts">
import { ref, defineEmits } from "vue";
// 1、定义的属性
const num = ref(0);
// 2、定义发射给父组件的方法
const emits = defineEmits(["add", "sub"]);
// 3、定义新增和递减计数方法
const increment = () => {
num.value++;
emits("add", num.value);
};
const decrement = () => {
num.value--;
emits("sub", num.value);
};
</script>
<style scoped>
* {
margin: 5px;
color: blue;
font-size: 24px;
}
</style>
2、data和methods
由于 setup 不需写 return ,所以直接声明数据即可
(1)运行效果
(2)Counter.vue
<script setup lang="ts">
import { ref, reactive } from "vue";
// defineProps是一个函数,传来的参数只读,不能修改
defineProps<{ msg: string }>()
const counter = ref(0) // 定义一个常量count,初值存的是个引用
const words = "hello" // 使用const定义string类型常量
let score = ref(100) // 使用ref声明基本类型变量
const obj = reactive({ // 使用reactive声明对象类型变量,如Object、Array、Date...
key: '姓名',
value: '小明'
})
// 变量
const today = reactive({ // 使用reactive声明对象类型变量,如Object、Array、Date...
year: '',
month: '',
day: '',
})
// 函数
function getToday() {
let d = new Date();
// 获取年,getFullYear()返回4位的数字
let year = d.getFullYear();
// 获取月,月份比较特殊,0是1月,11是12月
let month_tmp = d.getMonth() + 1;
// 变成两位
let month = (month_tmp < 10 ? '0' + month_tmp : month_tmp).toString();
// 获取日
let day_tmp = d.getDate();
// 日都变成2位
let day = (day_tmp < 10 ? '0' + day_tmp : day_tmp).toString();
today.year = year.toString()
today.month = month
today.day = day
}
function printLog() {
getToday() // 调用函数
console.log(today)
console.log(score.value)
console.log(obj.key)
}
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="counter++">count is {{ counter }}</button>
</div>
<p>{{ words }}</p>
<p>{{ obj.key }}:{{ obj.value }}</p>
<p>分数:<input v-model="score" type="text" /></p>
<p><button @click="printLog">打印日志</button></p>
</template>
3、ref 函数
Vue
3中的ref函数是一个用于创建响应式数据的函数,它可以将一个普通的JavaScript变量转换成响应式数据,从而能够在视图中动态地更新。ref函数返回一个对象,该对象包含一个.value属性,该属性的值为原始值的响应式引用。
(1)运行效果
(2)参考代码
<template>
<h1>{{ a }}</h1>
<h1>{{ b }}</h1>
<button @click="increase">点击</button>
</template>
<script setup lang="ts">
import { ref, computed} from 'vue'
// 引用实例对象(引用对象)
const a = ref(0)
// 在template里直接使用变量名就可以,但是更改数据时,却要使用xxx.value
const b = computed(() => {
return a.value * 2
})
const increase = () => {
a.value++
}
</script>
4、Reactive 函数
Vue3中的reactive函数是一个用于创建响应式对象的函数,它可以将一个普通的JavaScript对象转换成响应式对象,从而能够在视图中动态地更新。reactive函数返回一个对象,该对象的所有属性都是响应式的。
(1)运行效果
(2)参考代码
<template>
<h1>{{ data.a }}</h1>
<h1>{{ data.b }}</h1>
<button @click="data.increase">点击</button>
</template>
<script setup lang="ts">
import { reactive, computed, toRefs } from 'vue'
// 定义一个接口MyDataProps
interface MyDataProps {
a: number;
b: number;
increase: () => void;
}
// 使用reactive定义一个对象data,是MyDataProps类型
const data: MyDataProps = reactive({
a: 0,
b: computed(() => data.a * 5),
increase: () => { data.a++ }
})
</script>
5、computed函数:定义计算属性
说明:使用computed定义计算属性,当需要依赖的数据变化后,会重新计算。
一般使用计算属性来描述依赖响应式状态的复杂逻辑。就是这个计算属性的值依赖于其他响应式属性的值,依赖的属性发生变化,那么这个计算属性的值就会进行重新计算。
(1)案例 使用computed函数,计算全名
<template>
<h1>{{ fullName }}</h1>
firstName:<input type="text" v-model="user.firstName"><br/>
lastName:<input type="text" v-model="user.lastName">
</template>
<script setup lang="ts">
import { reactive, computed } from "vue";
interface User {
firstName: string;
lastName: string;
}
let user:User = reactive({
"firstName":"",
"lastName":"",
})
// 定义一个计算属性 fullName,用于拼接 firstName 和 lastName
const fullName = computed(() => {
return user.firstName + " " + user.lastName;
});
</script>
(2)案例 使用computed函数,计算优秀成绩
初始状态
每点击一次添加按钮,会添加一个成绩,并筛选出优秀的成绩
<script setup lang="ts">
import { ref, computed } from "vue";
// 成绩列表
const scoreList = ref([33, 43, 93, 80, 66, 77]);
// 计算属性 此处筛选出成绩大于等于90的数据
const betterList = computed(() => scoreList.value.filter((item) => item >= 90));
// 定义函数addScore,随机生成个成绩并加入成绩列表,计算属性也会改变
const addScore = () => {
let s = Math.round(Math.random() * 100); // 随机生成[0-100]间的随机数
scoreList.value.push(s);
};
</script>
<template>
<div>
<p>分数:{{ scoreList }}</p>
<p>优秀:{{ betterList }}</p>
<button @click="addScore">添加</button>
</div>
</template>
6、Watch函数的使用
在Vue3中,watch函数是用来监听数据变化并执行相应操作的方法。
在组合式API中,可以使用watch函数在每次响应式状态发生变化时触发回调函数,watch的第一个参数可以是不同形式的“数据源”:它可以是一个 ref(包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组: watch()是懒执行的:仅当数据源变化时,才会执行回调,例如:
(1)使用Watch函数监测输入用户名的变化
<template>
<div>
<p>你的名字是:{{ name }}</p>
<input v-model="name" type="text" />
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
const name = ref("张三");
watch(
name,
(newValue, oldValue ) => {
console.log(`名字从 ${oldValue} 变为 ${newValue}`);
}
);
</script>
(2)使用Watch函数监测输入手机号码的格式
<script setup lang="ts">
import { ref, watch } from "vue";
// 监测手机号变化
const phone = ref("");
const isPhoneValid = ref(false);
watch(() => phone.value,(newValue, oldValue) => {
if (/^1[3-9]\d{9}$/.test(newValue)) {
isPhoneValid.value = true;
} else {
isPhoneValid.value = false;
}
}
);
/*
watch(phone, function(newValue, oldValue) {
if (/^1[3-9]\d{9}$/.test(newValue)) {
isPhoneValid.value = true;
} else {
isPhoneValid.value = false;
}
});
*/
</script>
<template>
<h1>监测输入的手机号,判断格式是否正确</h1>
<div>
<label>请输入手机号码:</label>
<input type="text" v-model="phone" />
<p v-if="isPhoneValid">手机号码格式正确</p>
<p v-else style="color: red">请输入有效的手机号码</p>
</div>
</template>
<style scoped>
* {
font-size: 24px;
}
h1 {
color: blue;
}
p {
color: green;
}
</style>
(3)使用Watch函数监测成绩个数
<script setup lang="ts">
import { ref, computed, watch } from "vue";
// 成绩列表
const scoreList = ref([33, 43, 93, 80, 66, 77]);
// 计算属性 此处筛选出成绩大于等于90的数据
const betterList = computed(() => scoreList.value.filter((item) => item >= 90));
// 定义函数addScore,随机生成若干个成绩并加入成绩列表,计算属性也会改变
const addScore = () => {
let n = Math.round(Math.random() * 3) // 随机生成[0-3]的随机数
console.log("随机生成", n, "个成绩!")
for (let i = 0; i < n; i++) {
let s = Math.round(Math.random() * 100); // 随机生成[0-100]的随机数
scoreList.value.push(s);
}
};
// watch函数监听scoreList的长度
watch(() => scoreList.value.length, (newValue, oldValue) => {
console.log('newValue===', newValue);
console.log('oldValue===', oldValue);
})
</script>
<template>
<div>
<p>分数:{{ scoreList }}</p>
<p>优秀:{{ betterList }}</p>
<button @click="addScore">添加</button>
</div>
</template>
(4)使用Watch函数同时监听多个属性
<template>
<h1>{{ user.fullName }}</h1>
firstName:<input type="text" v-model="user.firstName" /><br />
lastName:<input type="text" v-model="user.lastName" />
</template>
<script setup lang="ts">
import { reactive, computed, watch } from "vue";
interface User {
firstName: string;
lastName: string;
fullName:string;
}
let user: User = reactive({
firstName: "",
lastName: "",
fullName: "",
});
watch([() => user.firstName, () => user.lastName], ([newFirstName, newLastName]) => {
user.fullName = user.firstName + " " + user.lastName;
});
</script>
7、案例:购物车
<template>
<div>
<h2>商品列表</h2>
<table border="1">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>购买</th>
</tr>
<tr v-for="(product, index) in products" :key="product.id">
<!-- 展示商品名称和价格,并绑定点击事件添加到购物车 -->
<td>{{ product.name }}</td>
<td>¥{{ product.price }}</td>
<td><button @click="addToCart(product)">添加到购物车</button></td>
</tr>
</table>
<!-- 购物车列表 -->
<h2>购物车</h2>
<table border="1">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>购买</th>
</tr>
<tr v-for="(item, index) in cart" :key="index">
<!-- 展示商品名称和价格,并绑定点击事件添加到购物车 -->
<td>{{ item.product.name }}</td>
<td>¥{{ item.product.price }}</td>
<td>{{ item.quantity }}</td>
<td><button @click="increaseQuantity(item)">+</button></td>
<td><button @click="decreaseQuantity(item)">-</button></td>
<td><button @click="removeFromCart(index)">删除</button></td>
</tr>
</table>
</div>
</template>
<script setup lang="ts">
import { defineComponent, reactive } from "vue";
// 定义商品列表
const productsData = [
{ id: 1, name: "联想拯救者Y9000P2023", price: 6199.0 },
{ id: 2, name: "联想小新笔记本", price: 5948.0 },
{ id: 3, name: "华为笔记本电脑HUAWEI MateBook D15/D14 SE版", price: 3500.0 },
];
// 定义购物车数据
const cartData = [];
// 使用 reactive 创建响应式数据 products 和 cart
const products = reactive(productsData);
const cart = reactive(cartData);
// 添加商品到购物车的函数
function addToCart(product) {
// 检查购物车中是否已经存在该商品,如果是,就增加该商品的数量,否则将商品添加到购物车中
const cartItem = cart.find((item) => item.product.id === product.id);
if (cartItem) {
cartItem.quantity++;
} else {
cart.push({ product, quantity: 1 });
}
}
// 增加商品数量的函数
function increaseQuantity(item) {
item.quantity++;
}
// 减少商品数量的函数
function decreaseQuantity(item) {
if (item.quantity > 1) {
item.quantity--;
}
}
// 从购物车中删除商品的函数
function removeFromCart(index) {
if (confirm("确定要删除该商品吗?")) {
cart.splice(index, 1);
}
}
</script>
8、案例:购物车(使用组件的方式,需要用到父子间传参)
稍微修改下样式,运行结果如下。
(1)ProductList.vue商品列表组件
<template>
<div>
<h2>商品列表</h2>
<table border="1" class="altrowstable">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>购买</th>
</tr>
<tr v-for="(product, index) in products" :key="product.id">
<!-- 展示商品名称和价格,并绑定点击事件添加到购物车 -->
<td>{{ product.name }}</td>
<td>¥{{ product.price }}</td>
<td><button @click="addToCart(product)">添加到购物车</button></td>
</tr>
</table>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from "vue";
defineProps({
products: {
type: Array,
required: true,
},
});
const emits = defineEmits(["add-to-cart"]);
// 添加商品到购物车的函数
function addToCart(product) {
emits("add-to-cart", product);
}
</script>
(2)CartList.vue购物车组件
<template>
<div>
<h2>购物车</h2>
<table border="1" class="altrowstable">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
<tr v-for="(item, index) in cart" :key="index">
<!-- 展示商品名称和价格,并绑定点击事件增加/减少商品数量,以及删除商品 -->
<td>{{ item.product.name }}</td>
<td>¥{{ item.product.price }}</td>
<td>{{ item.quantity }}</td>
<td>
<button @click="increaseQuantity(item)">+</button>
<button @click="decreaseQuantity(item)">-</button>
<button @click="removeFromCart(index)">删除</button>
</td>
</tr>
</table>
</div>
</template>
<script setup lang="ts">
defineProps({
cart: {
type: Array,
required: true,
},
});
const emits = defineEmits(["increase-quantity", "decrease-quantity", "remove-from-cart"]);
// 增加商品数量的函数
function increaseQuantity(item) {
emits("increase-quantity", item);
}
// 减少商品数量的函数
function decreaseQuantity(item) {
emits("decrease-quantity", item);
}
// 从购物车中删除商品的函数
function removeFromCart(index) {
if (confirm("确定要删除该商品吗?")) {
emits("remove-from-cart", index);
}
}
</script>
<style scoped>
button {
margin: 5px;
font-size: 14px;
color: blue;
}
</style>
(3)ShoppingCart.vue购物车主页面,使用前面定义的两个组件
<template>
<div>
<ProductList :products="products" @add-to-cart="addToCart" />
<div v-if="cart.length != 0">
<CartList
:cart="cart"
@increase-quantity="increaseQuantity"
@decrease-quantity="decreaseQuantity"
@remove-from-cart="removeFromCart"
/>
<button @click="clearCart" style="margin-top: 10px">清空购物车</button>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import ProductList from "./ProductList.vue";
import CartList from "./CartList.vue";
// 商品列表数据
const productsData = [
{ id: 1, name: "联想拯救者Y9000P2023", price: 6199.0 },
{ id: 2, name: "联想小新笔记本", price: 5948.0 },
{ id: 3, name: "华为笔记本电脑HUAWEI MateBook D15/D14 SE版", price: 3500.0 },
{ id: 4, name: "Think Pad笔记本", price: 13500.0 },
{ id: 5, name: "小米笔记本", price: 5500.0 },
];
// 购物车数据
const cartData = [];
// 创建响应式数据
const products = reactive(productsData);
const cart = reactive(cartData);
// 添加商品到购物车
function addToCart(product) {
const cartItem = cart.find((item) => item.product.id === product.id);
if (cartItem) {
cartItem.quantity++;
} else {
cart.push({ product, quantity: 1 });
}
}
// 增加商品数量
function increaseQuantity(item) {
item.quantity++;
}
// 减少商品数量
function decreaseQuantity(item) {
if (item.quantity > 1) {
item.quantity--;
}
}
// 从购物车中删除商品
function removeFromCart(index) {
cart.splice(index, 1);
}
// 清空购物车
function clearCart() {
if (confirm("确定要清空购物车吗?")) {
cart.length = 0;
}
}
</script>
<style>
button {
font-size: 14px;
color: mediumvioletred;
}
table.altrowstable {
font-family: verdana, arial, sans-serif;
font-size: 11px;
color: #333333;
border-width: 1px;
border-color: #a9c6c9;
border-collapse: collapse;
width: 600px;
}
table.altrowstable th {
border-width: 1px;
padding: 4px;
border-style: solid;
border-color: #a9c6c9;
}
table.altrowstable td {
border-width: 1px;
padding: 4px;
border-style: solid;
border-color: #a9c6c9;
}
.oddrowcolor {
background-color: #d4e3e5;
}
.evenrowcolor {
background-color: #c3dde0;
}
</style>