JavaScript 之 Vue3 入门到精通(一篇文章精通系列)【WebStorm版】
项目源代码:https://download.csdn.net/download/qq_44757034/86440913
一、Vite创建Vue3 项目
npm init vite@latest my-vue-app -- --template vue
创建成功
用WebStorm打开项目
自动下载好对应的依赖
启动运行项目
npm run dev
http://127.0.0.1:5173/
提示要安装插件
二、Vue3基本语法
1、定义全局变量
<template>
<h1>{{msg}}</h1>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
let msg = "itblueBox"
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
2、定义函数
翻转字符串函数
<template>
<h1>{{msg}}</h1>
<h1>{{reMsg(msg)}}</h1>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
let msg = "itblueBox"
function reMsg(val){
return val.split('').reverse().join('')
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
3、定义响应式ref
<template>
<h1>{{msg}}</h1>
<h1>{{reMsg(msg)}}</h1>
<button @click="setMsg">点击修改</button>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
let msg = "itblueBox"
function reMsg(val){
return val.split('').reverse().join('')
}
function setMsg(){
msg = "red!!!"
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
点击,发现没有反应
数据以及发生变化,但是没有响应式
设置响应式
//通过 ref 创建响应式对象
<template>
<h1>{{msg}}</h1>
<h1>{{reMsg(msg)}}</h1>
<button @click="setMsg">点击修改</button>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import {ref} from 'vue'
//通过 ref 创建响应式对象
let msg = ref("itblueBox")
function reMsg(val){
return val.split('').reverse().join('')
}
function setMsg(){
msg.value = "red!!!"
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
分析HelloWorld.vue
4、响应式reactive
<template>
<div>
<h1>用户名:{{user.username}}</h1>
<h1>年龄:{{user.age}}</h1>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref} from 'vue'
var user = ref({
username: 'itblueBox',
age:18
})
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
显示成功
显示成功
刷新页面
点击修改年龄,值已经被修改,但是页面没有变化
值设置为value的方式
设置响应式的转换
<template>
<div>
<h1>用户名:{{user.username}}</h1>
<h1>年龄:{{user.age}}</h1>
<button @click="setUserAge">点击修改年龄</button>
<h1>老师:{{ teacher.username }}</h1>
<button @click="setTeacherName">修改名字</button>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive} from 'vue'
var user = ref({
username: 'itblueBox',
age:18
})
const teacher = reactive({
username: 'itblueBox teacher',
sex:"男",
age:18
})
function setUserAge(){
//user.age = 14
user.value.age = 14
console.log(user)
}
function setTeacherName(){
teacher.username = "redBox"
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
<template>
<div>
<h1>用户名:{{user.username}}</h1>
<h1>年龄:{{user.age}}</h1>
<button @click="setUserAge">点击修改年龄</button>
<h1>老师:{{ teacher.username }}</h1>
<button @click="setTeacherName">修改名字</button>
<h1>用户名:{{user2.username}}</h1>
<h1>年龄:{{user2.age}}</h1>
<button @click="setUser2Age">点击修改年龄</button>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive} from 'vue'
var user = ref({
username: 'itblueBox',
age:18
})
const teacher = reactive({
username: 'itblueBox teacher',
sex:"男",
age:18
})
function setUserAge(){
//user.age = 14
user.value.age = 14
console.log(user)
}
function setTeacherName(){
teacher.username = "redBox"
}
let user2 = reactive(user.value)
function setUser2Age(){
user2.age = 100;
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
修改成功
5、Vue3事件对象与传递参数
事件对象
<template>
<div>
<h1>用户名:{{user.username}}</h1>
<h1>年龄:{{user.age}}</h1>
<button @click="setUserAge">点击修改年龄</button>
<h1>老师:{{ teacher.username }}</h1>
<button @click="setTeacherName">修改名字</button>
<h1>用户名:{{user2.username}}</h1>
<h1>年龄:{{user2.age}}</h1>
<button @click="setUser2Age">点击修改年龄</button>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive} from 'vue'
var user = ref({
username: 'itblueBox',
age:18
})
const teacher = reactive({
username: 'itblueBox teacher',
sex:"男",
age:18
})
function setUserAge(event){
//user.age = 14
user.value.age = 14
console.log(user)
console.log(event)
}
function setTeacherName(){
teacher.username = "redBox"
}
let user2 = reactive(user.value)
function setUser2Age(){
user2.age = 100;
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
传递参数
<button @click="setUserAge(10000)">点击修改年龄</button>
function setUserAge(value){
//user.age = 14
user.value.age = value
console.log(user)
}
@H_847_3270@
<button @click="setUserAge(10000,$event)">点击修改年龄</button>
传递参数的同时传递事件对象
function setUserAge(value,event){
//user.age = 14
user.value.age = value
console.log(user)
console.log(event)
}
6、Vue3计算属性
6.1引入computed
将计算的结果进行缓存,防止多次调用损失性能
<template>
<div>
<h1>{{msg}}</h1>
<h1>{{ reMsg }}</h1>
<h1>{{ reMsg }}</h1>
<h1>{{ reMsg }}</h1>
<button @click="setMsg('itred',$event)"></button>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref,computed,reactive} from 'vue'
let msg = reactive("itblueBox")
function setMsg(value,event){
msg.value = value
console.log(value)
console.log(event)
}
const reMsg = computed( function (){
console.log(123)
return msg.split("").reverse().join("")
}
)
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
我们可以发现虽然调用了三遍,但是函数只执行了一次
6.2 设置值和修改值
<template>
<div>
<h1>{{msg}}</h1>
<h1>{{ reMsg }}</h1>
<h1>{{ reMsg }}</h1>
<h1>{{ reMsg }}</h1>
<button @click="setMsg('itred',$event)">修改内容</button>
<button @click="setReMsg">修改计算属性reMsg</button>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref,computed} from 'vue'
let msg = ref("itblueBox")
function setMsg(value,event){
msg.value = value
console.log(value)
console.log(event)
}
const reMsg = computed( {
get:()=>{
console.log(123)
return msg.value.split("").reverse().join("")
},
set:(value)=>{
msg.value = value.split("").reverse().join("")
}
})
function setReMsg(){
reMsg.value = "zhngsan";
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
上述
reMsg
默认加载的时候调用了get
方法点击
修改计算属性reMsg
的时候调用set方法后又调用get方法
7、Vue3监听数据变化
7.1、单个数据监听
watch(msg,(newValue,oldValue)=>{
console.log("newValue",newValue)
console.log("oldValue",oldValue)
})
监听对象
<h1>{{user.name}}</h1>
<button @click="user.name = '赵六'">修改名字</button>
watch(
()=> user.name,
(newValue,oldValue)=>{
console.log("newValue",newValue)
console.log("oldValue",oldValue)
})
7.2、多个数据监听
同时监听多个数据
watch(
[msg,()=> user.name],
(newValue,oldValue)=>{
console.log("newValue",newValue)
console.log("oldValue",oldValue)
})
8、Vue3常见指令与样式
8.1、class
<template>
<div>
<h1>{{msg}}</h1>
<h1 v-bind:class="classname"></h1>
<h1 :class="classname"></h1>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref, computed, watch, reactive} from 'vue'
let msg = ref("itblueBox")
let classname = ref('Box bgRed')
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
.Box{
width: 200px;
height: 200px;
background-color: pink;
}
.bgRed{
background-color: red;
}
</style>
8.2、id
<template>
<div>
<h1>{{msg}}</h1>
<h1 v-bind:class="classname"></h1>
<h1 :class="classname"></h1>
<h1 :id="Box"></h1>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref, computed, watch, reactive} from 'vue'
let msg = ref("itblueBox")
let classname = ref('Box bgRed')
let Box = ref('Box')
</script>
<style scoped>
.Box{
width: 200px;
height: 200px;
background-color: pink;
}
.bgRed{
background-color: red;
}
#Box{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
8.3、title
<h1 :title="desc" :class="classname"></h1>
let desc = ref("这是一个Box")
8.4、富文本显示
<div class="content">{{html}}</div>
let html = ref("<h1>这是HTML的内容</h1>")
我们发现显示的是文本
设置富文本显示
<div class="content" v-html="html"></div>
8.5、点击事件
监听点击事件
全显示
<div v-on:click="count++">{{count}}</div>
let count = ref(0)
简写
8.6、点击切换样式
<h1 :class="{Box:true,bgRed:toggle}" @click="toggle = !toggle" ></h1>
let toggle = ref(true)
8.7、:style
<h1 :style="h1Style"></h1>
let h1Style = reactive({
background:"skyblue",
width:"200px",
height:"200px"
})
有横杠的属性使用驼峰命名法
borderStyle:"solid",
9、Vue父子组件数据传递Props
9.1 定义子组件在父组件当中引用
清空一下App.vue的数据
<template>
<div>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>
新建自定义组件
<template>
<h1>
<span>
{{num}}
</span>
------------
<span>
{{title}}
</span>
</h1>
</template>
<script setup>
let num = 1;
let title = "今天是个好日子"
</script>
<style scoped>
</style>
在App.vue当中
<template>
<div>
<ListItem></ListItem>
</div>
</template>
<script setup>
import ListItem from "./components/ListItem.vue"
</script>
<style scoped>
</style>
9.2 设置父组件给子组件传递数据
<template>
<div>
<ListItem :num="article.num" :title="article.title"></ListItem>
<!-- prop = property -->
</div>
</template>
<script setup>
import ListItem from "./components/ListItem.vue"
import {reactive} from "vue";
let article = reactive({
num:10,
title:"定义propss"
})
</script>
<style scoped>
</style>
<template>
<h1>
<span>
{{props.num}}
</span>
---------
<span>
{{props.title}}
</span>
</h1>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
num:Number,
title:String
})
</script>
<style scoped>
</style>
刷新页面
9.3 传递数组
<template>
<div>
<ListItem :num="article.num" :title="article.title"></ListItem>
<!-- prop = property -->
<h1>列表循环</h1>
<ListItem :num="item.num" :title="item.title" v-for="item in articleList"></ListItem>
</div>
</template>
<script setup>
import ListItem from "./components/ListItem.vue"
import {reactive} from "vue";
let article = reactive({
num:10,
title:"定义propss"
})
let articleList = reactive([
{
num:10,
title:"定义propss1"
},
{
num:11,
title:"定义propss2"
}
])
</script>
<style scoped>
</style>
刷新页面
9.4 传递对象
<ListItem :num="article.num" :title="article.title" :article="article"></ListItem>
<!-- prop = property -->
<h1>列表循环</h1>
<ListItem
:num="item.num"
:title="item.title"
:article="item"
v-for="item in articleList">
</ListItem>
<template>
<h1>
<span>
{{props.num}}
</span>
---------
<span>
{{props.title}}
</span>
<br>
article
<span>
{{props.article.num}}
</span>
---------
<span>
{{props.article.title}}
</span>
</h1>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
num:Number,
title:String,
article:Object
})
</script>
<style scoped>
</style>
刷新页面
10、Vue3自定义事件
10.1 定义子组件在父组件当中引用(父组件的数据传递给子组件)
子组件当中定义事件
<template>
<h1 @click="sendRead">
<span>
{{props.num}}
</span>
---------
<span>
{{props.title}}
</span>
<br>
article
<span>
{{props.article.num}}
</span>
---------
<span>
{{props.article.title}}
</span>
</h1>
</template>
<script setup>
import { defineProps,defineEmits } from 'vue'
const props = defineProps({
num:Number,
title:String,
article:Object
})
const emit = defineEmits(['finishRead','reading'])
function sendRead(){
emit('finishRead')
}
</script>
<style scoped>
</style>
<template>
<div>
<ListItem :num="article.num" :title="article.title" :article="article"></ListItem>
<!-- prop = property -->
<h1>列表循环</h1>
<ListItem
:num="item.num"
:title="item.title"
:article="item"
v-for="item in articleList"
@finishRead="changeTitle(item)"
>
</ListItem>
</div>
</template>
<script setup>
import ListItem from "./components/ListItem.vue"
import {reactive} from "vue";
let article = reactive({
num:10,
title:"定义propss"
})
let articleList = reactive([
{
num:10,
title:"定义propss1"
},
{
num:11,
title:"定义propss2"
}
])
function changeTitle(item){
item.title += '【已读】'
}
</script>
<style scoped>
</style>
点击
10.2 定义子组件在父组件当中引用(子组件的数据传递给父组件)
emit('finishRead','【已经阅读】')
<template>
<div>
<ListItem :num="article.num" :title="article.title" :article="article"></ListItem>
<!-- prop = property -->
<h1>列表循环</h1>
<ListItem
:num="item.num"
:title="item.title"
:article="item"
v-for="item in articleList"
@finishRead="changeTitle(item,$event)"
>
</ListItem>
</div>
</template>
<script setup>
import ListItem from "./components/ListItem.vue"
import {reactive} from "vue";
let article = reactive({
num:10,
title:"定义propss"
})
let articleList = reactive([
{
num:10,
title:"定义propss1"
},
{
num:11,
title:"定义propss2"
}
])
function changeTitle(item,$event){
item.title += $event
}
</script>
<style scoped>
</style>
点击
点击几次就增加几次
设置点击事件,设置点击内容内容不增加
function changeTitle(item,$event){
if(item.title.indexOf($event) == -1){
item.title += $event
}
}
点击多次内容不累加
采用下角标的方式实现
<template>
<div>
<ListItem :num="article.num" :title="article.title" :article="article"></ListItem>
<!-- prop = property -->
<h1>列表循环</h1>
<ListItem
:num="item.num"
:title="item.title"
:article="item"
v-for="(item,index) in articleList"
@finishRead="changeTitle(index,$event)"
>
</ListItem>
</div>
</template>
<script setup>
import ListItem from "./components/ListItem.vue"
import {reactive} from "vue";
let article = reactive({
num:10,
title:"定义propss"
})
let articleList = reactive([
{
num:10,
title:"定义propss1"
},
{
num:11,
title:"定义propss2"
}
])
function changeTitle(index,$event){
if(articleList[index].title.indexOf($event) == -1){
articleList[index].title += $event
}
}
</script>
<style scoped>
</style>
实现效果是一样的
11、Vue3路由
11.1 、设置路由
创建一些其他的页面
<template>
<h1>Home</h1>
</template>
<script>
export default {
name: "Home"
}
</script>
<style scoped>
</style>
<template>
<h1>About</h1>
</template>
<script>
export default {
name: "About"
}
</script>
<style scoped>
</style>
<template>
<h1>Buycart</h1>
</template>
<script>
export default {
name: "Buycart"
}
</script>
<style scoped>
</style>
设置路由
安装vue-router
npm install vue-router@4
完善index.js
import {
createRouter,
createWebHistory,
createWebHashHistory
} from "vue-router"
import Home from "../views/Home.vue";
import About from "../views/About.vue";
import Buycart from "../views/Buycart.vue";
//2、定义一些路由
//每个路由都需要映射到一个组件
//我们后面再讨论嵌套路由
const routes = [
{path:"/",component:Home,name:"home"},
{path:"/about",component:About,name:"About"},
{path:"/buycart",component:Buycart,name:"Buycart"},
];
//3、创建路由实例并传递‘routes’配置
//你可以在这里输入更多的配置,但是我们在这里
const router = createRouter({
//4、内部提供了 history 模式的实现。为了简单起见,我们在这里使用hash模式
history:createWebHashHistory(),
routes, //routes:routes 的缩写
})
export default router
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index.js'
let app = createApp(App)
app.use(router)
app.mount('#app')
<template>
<div>
<router-view></router-view>
</div>
</template>
<script setup>
</script>
<style scoped>
</style>
清除样式所带来的干扰
11.2 、设置跳转
<template>
<div>
<router-link to="/">跳转至首页</router-link> |
<router-link to="/about">跳转至about</router-link> |
<button @click="router.push('/')">跳转至首页</button>
<button @click="goAbout">跳转至abou</button>
</div>
<router-view></router-view>
</template>
<script setup>
import { useRoute, useRouter } from "vue-router";
// 获取路由信息
let route = useRoute();
// 执行路由的跳转
let router = useRouter();
function goAbout() {
console.log(route);
router.push("/about");
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
11.3 、携带参数
{
path:"/product/:id",
component:()=>{
return import('../views/Product.vue')
},
name:"product"
},
访问:http://127.0.0.1:5173/#/product/123
12、Vue3状态管理
12.1 、安装Vuex相关内容
停止运行项目
npm install vuex@next --save
12.2、创建状态管理的内容(使用Vuex)
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
export default store;
在main.js当中使用
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index.js'
import store from "./store/index.js";
let app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
完善store下的index.js
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state,payload) {
state.count+=payload
}
},
getters:{
totalPrice(state){
return state.count * 98.8;
}
},
actions:{
asyncAdd(store,payload){
setTimeout(()=>{
store.commit('increment',10)
},1000)
}
}
})
export default store;
12.3、使用状态管理(同步添加数据)
<template>
<h1>Buycart</h1>
<h1>商品数量:{{ store.state.count }}</h1>
<button @click="addProd">添加商品数量+2</button>
</template>
<script setup>
import {useStore} from 'vuex'
let store = useStore()
function addProd(){
store.commit('increment',2)
}
</script>
<style scoped>
</style>
访问
http://127.0.0.1:5173/#/buycart
点击以后每次加2
12.4、使用状态管理(异步添加数据)
<template>
<h1>Buycart</h1>
<h1>商品数量:{{ store.state.count }}</h1>
<button @click="addProd">添加商品数量+2</button>
<button @click="asyncAddProd">异步添加商品数量+10</button>
</template>
<script setup>
import {useStore} from 'vuex'
let store = useStore()
function addProd(){
store.commit('increment',2)
}
function asyncAddProd(){
store.dispatch('asyncAdd');
}
</script>
<style scoped>
</style>
点击以后有延迟的在新增数据
使用参数传递新增数据
store.commit('increment',payload)
store.dispatch('asyncAdd',5);
<template>
<h1>Buycart</h1>
<h1>商品数量:{{ store.state.count }}</h1>
<button @click="addProd">添加商品数量+2</button>
<button @click="asyncAddProd(5)">异步添加商品数量+5</button>
</template>
<script setup>
import {useStore} from 'vuex'
let store = useStore()
function addProd(){
store.commit('increment',2)
}
function asyncAddProd(val){
store.dispatch('asyncAdd',val);
}
</script>
<style scoped>
</style>
获取商品总价
<h1>商品总价{{ store.getters.totalPrice }}</h1>
三、Vue3项目案例
1、vite按需加载antdesing与less
官网:https://www.antdv.com/docs/vue/migration-v3-cn
安装命令
npm i --save ant-design-vue
安装成功
局部引入组件
安装
npm i unplugin-vue-components -D
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import Components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [AntDesignVueResolver()],
}),
],
});
2、引入组件
<template>
<h1>About</h1>
<a-button type="primary">Primary Button</a-button>
</template>
<script setup>
</script>
<style scoped>
</style>
3、设置发起请求
安装axios
npm install axios
import axios from "axios";
const request = axios.create({
// 配置接口请求的基准路径
baseURL: "http://localhost:8080/Metashop/api",
});
// 响应拦截器
request.interceptors.response.use(
(response) => {
if (response.status == 200) {
return response.data;
} else {
return response;
}
},
function (error) {
return Promise.reject(error);
}
);
export const getHomepage = (params) => {
return request({
method: "GET",
url: "/homepage",
// params选项用来配置QUERY的参数 ?query=xxxx
params,
});
};
export const getProducts = () => {
return request({
method: "GET",
url: "/products",
});
};
重新启动项目后访问:http://127.0.0.1:5173/#/
成功
获取数据
4、suspense异步组件
在子组件当中定义发起请求,在父组件当中显示数据
自定义异步组件
<template>
<h1>首页</h1>
<ul>
<li v-for="(item,i) in hero">
<h3>{{item.category}}</h3>
</li>
</ul>
</template>
<script setup>
import * as api from '../api/index';
import {reactive} from "vue";
let result = await api.getHomepage();
const hero = reactive(result.hero);
</script>
<style scoped>
</style>
完善Home
<template>
<suspense>
<template #fallback>
<div class="loading">
<h1>Loading</h1>
</div>
</template>
<template #default>
<h1><HomeAsync></HomeAsync></h1>
</template>
</suspense>
</template>
<script setup>
import { onMounted, defineAsyncComponent } from "vue";
const HomeAsync = defineAsyncComponent(() =>
import("../components/HomeCom.vue")
);
onMounted(async () => {
// let result = await api.getHomepage();
// console.log(result);
});
</script>
<style lang="less" scoped></style>
设置加载页面的优化,我们下载一些加载动画
https://loading.io/spinner/
选择一个
复制这段代码
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="
margin: auto;
background: #fff;
display: block;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
"
width="200px"
height="200px"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
>
<defs>
<clipPath id="ldio-ssf8off6o8l-cp" x="0" y="0" width="100" height="100">
<rect x="0" y="5" width="100" height="46"></rect>
</clipPath>
</defs>
<path
d="M70 75.2H34.1l-4.1-18.4l-0.7-3l-1-4.7c0 0 0 0 0-0.1c0-0.1 0-0.1-0.1-0.2c0 0 0-0.1-0.1-0.1c0 0 0-0.1-0.1-0.1 c0 0-0.1-0.1-0.1-0.1c0 0-0.1-0.1-0.1-0.1c0 0-0.1-0.1-0.1-0.1c0 0 0 0-0.1-0.1L22.3 44c0-0.1 0-0.2 0-0.3c0-1.9-1.6-3.5-3.5-3.5 s-3.5 1.6-3.5 3.5c0 1.9 1.6 3.5 3.5 3.5c0.7 0 1.4-0.2 2-0.6l4.8 3.7L31.5 77c0 0 0 0 0 0l-5.6 7.7c-0.3 0.5-0.4 1.1-0.1 1.6 c0.3 0.5 0.8 0.8 1.3 0.8h4c-0.8 0.8-1.3 1.9-1.3 3.2c0 2.6 2.1 4.7 4.7 4.7c2.6 0 4.7-2.1 4.7-4.7c0-1.2-0.5-2.3-1.3-3.2h29 c-0.8 0.8-1.3 1.9-1.3 3.2c0 2.6 2.1 4.7 4.7 4.7c2.6 0 4.7-2.1 4.7-4.7c0-1.2-0.5-2.3-1.3-3.2H77c0.8 0 1.5-0.7 1.5-1.5 s-0.7-1.5-1.5-1.5H30l4.3-6h36.8c0.7 0 1.3-0.5 1.4-1.1l7.5-27.3c0.2-0.8-0.2-1.6-1-1.8c-0.8-0.2-1.6 0.2-1.8 1l-1.3 4.7l-0.8 3"
fill="#dddddd"
></path>
<polygon
points="31.3 53.1 35.7 73.2 68.5 73.2 74 53.1"
fill="#dddddd"
></polygon>
<g clip-path="url(#ldio-ssf8off6o8l-cp)">
<g>
<g transform="translate(50 41)">
<path
d="M6.5-6.7C6.1-6.9 5.7-7.2 5.3-7.4C5-7.5 4.6-7.7 4.3-7.8C3.1-2.2-4-3.7-2.9-9.3c-0.4 0-0.7 0-1.1 0 c-0.5 0-1 0.1-1.4 0.2c-1.8 0.3-3.6 0.9-5.3 1.8l1.1 4.2l3.1-0.8L-8.7 6.9L3.2 9.3L5.4-1.5l2.5 2l2.7-3.4C9.5-4.4 8.1-5.7 6.5-6.7z"
fill="#e15b64"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(35 17)">
<path
d="M3.4-5.3L2.5-5l0.8-2.3L1.1-6.3l-1.2-2.2l-1.6 4.6l-4.6-1.6l0.9 2.3l-2.2 1.2l2.3 0.8L-6-0.9 c-0.6 0.3-0.8 0.9-0.5 1.5l1 2.1C-5.2 3.4-4.6 3.6-4 3.3l0.1-0.1l2.1 4.5C-1.4 8.4-0.7 8.7 0 8.3l1.7-0.8l1.7-0.8L5 5.9l1.7-0.8 C7.4 4.8 7.7 4 7.4 3.3L5.2-1.1l0.1-0.1c0.6-0.3 0.8-0.9 0.5-1.5l-1-2.1C4.6-5.4 3.9-5.6 3.4-5.3z"
fill="#f47e60"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(66 26)">
<path
d="M-4.5-3.7L1.9-6l0.5-0.2L2-7.2l-6.9 2.5C-5.7-4.4-6.1-3.5-6-2.7c0 0.1 0 0.2 0.1 0.3l3 8.2 C-2.5 6.9-1.3 7.4-0.2 7l5.6-2C5.9 4.8 6.2 4.2 6 3.7L3.2-3.9l-0.4-1L2.4-4.7L1.9-4.5l-3.2 1.2l-2.7 1c-0.3 0.1-0.6 0-0.8-0.2 c-0.1-0.1-0.1-0.1-0.1-0.2C-5.1-3.1-4.9-3.6-4.5-3.7z"
fill="#f8b26a"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(55 6)">
<polygon
points="0 -4.9 1.6 -1.7 5.1 -1.1 2.6 1.3 3.2 4.9 0 3.2 -3.2 4.9 -2.6 1.3 -5.1 -1.1 -1.6 -1.7"
fill="#abbd81"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</polygon>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
</g>
<g clip-path="url(#ldio-ssf8off6o8l-cp)">
<g transform="translate(0 -75)">
<g>
<g transform="translate(50 41)">
<path
d="M6.5-6.7C6.1-6.9 5.7-7.2 5.3-7.4C5-7.5 4.6-7.7 4.3-7.8C3.1-2.2-4-3.7-2.9-9.3c-0.4 0-0.7 0-1.1 0 c-0.5 0-1 0.1-1.4 0.2c-1.8 0.3-3.6 0.9-5.3 1.8l1.1 4.2l3.1-0.8L-8.7 6.9L3.2 9.3L5.4-1.5l2.5 2l2.7-3.4C9.5-4.4 8.1-5.7 6.5-6.7z"
fill="#e15b64"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(35 17)">
<path
d="M3.4-5.3L2.5-5l0.8-2.3L1.1-6.3l-1.2-2.2l-1.6 4.6l-4.6-1.6l0.9 2.3l-2.2 1.2l2.3 0.8L-6-0.9 c-0.6 0.3-0.8 0.9-0.5 1.5l1 2.1C-5.2 3.4-4.6 3.6-4 3.3l0.1-0.1l2.1 4.5C-1.4 8.4-0.7 8.7 0 8.3l1.7-0.8l1.7-0.8L5 5.9l1.7-0.8 C7.4 4.8 7.7 4 7.4 3.3L5.2-1.1l0.1-0.1c0.6-0.3 0.8-0.9 0.5-1.5l-1-2.1C4.6-5.4 3.9-5.6 3.4-5.3z"
fill="#f47e60"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(66 26)">
<path
d="M-4.5-3.7L1.9-6l0.5-0.2L2-7.2l-6.9 2.5C-5.7-4.4-6.1-3.5-6-2.7c0 0.1 0 0.2 0.1 0.3l3 8.2 C-2.5 6.9-1.3 7.4-0.2 7l5.6-2C5.9 4.8 6.2 4.2 6 3.7L3.2-3.9l-0.4-1L2.4-4.7L1.9-4.5l-3.2 1.2l-2.7 1c-0.3 0.1-0.6 0-0.8-0.2 c-0.1-0.1-0.1-0.1-0.1-0.2C-5.1-3.1-4.9-3.6-4.5-3.7z"
fill="#f8b26a"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(55 6)">
<polygon
points="0 -4.9 1.6 -1.7 5.1 -1.1 2.6 1.3 3.2 4.9 0 3.2 -3.2 4.9 -2.6 1.3 -5.1 -1.1 -1.6 -1.7"
fill="#abbd81"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</polygon>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
</g>
</g>
</svg>
将刚刚的代码复制到这里
添加加载内容和百分比
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="
margin: auto;
background: #fff;
display: block;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
"
width="200px"
height="200px"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
>
<defs>
<clipPath id="ldio-ssf8off6o8l-cp" x="0" y="0" width="100" height="100">
<rect x="0" y="5" width="100" height="46"></rect>
</clipPath>
</defs>
<path
d="M70 75.2H34.1l-4.1-18.4l-0.7-3l-1-4.7c0 0 0 0 0-0.1c0-0.1 0-0.1-0.1-0.2c0 0 0-0.1-0.1-0.1c0 0 0-0.1-0.1-0.1 c0 0-0.1-0.1-0.1-0.1c0 0-0.1-0.1-0.1-0.1c0 0-0.1-0.1-0.1-0.1c0 0 0 0-0.1-0.1L22.3 44c0-0.1 0-0.2 0-0.3c0-1.9-1.6-3.5-3.5-3.5 s-3.5 1.6-3.5 3.5c0 1.9 1.6 3.5 3.5 3.5c0.7 0 1.4-0.2 2-0.6l4.8 3.7L31.5 77c0 0 0 0 0 0l-5.6 7.7c-0.3 0.5-0.4 1.1-0.1 1.6 c0.3 0.5 0.8 0.8 1.3 0.8h4c-0.8 0.8-1.3 1.9-1.3 3.2c0 2.6 2.1 4.7 4.7 4.7c2.6 0 4.7-2.1 4.7-4.7c0-1.2-0.5-2.3-1.3-3.2h29 c-0.8 0.8-1.3 1.9-1.3 3.2c0 2.6 2.1 4.7 4.7 4.7c2.6 0 4.7-2.1 4.7-4.7c0-1.2-0.5-2.3-1.3-3.2H77c0.8 0 1.5-0.7 1.5-1.5 s-0.7-1.5-1.5-1.5H30l4.3-6h36.8c0.7 0 1.3-0.5 1.4-1.1l7.5-27.3c0.2-0.8-0.2-1.6-1-1.8c-0.8-0.2-1.6 0.2-1.8 1l-1.3 4.7l-0.8 3"
fill="#dddddd"
></path>
<polygon
points="31.3 53.1 35.7 73.2 68.5 73.2 74 53.1"
fill="#dddddd"
></polygon>
<g clip-path="url(#ldio-ssf8off6o8l-cp)">
<g>
<g transform="translate(50 41)">
<path
d="M6.5-6.7C6.1-6.9 5.7-7.2 5.3-7.4C5-7.5 4.6-7.7 4.3-7.8C3.1-2.2-4-3.7-2.9-9.3c-0.4 0-0.7 0-1.1 0 c-0.5 0-1 0.1-1.4 0.2c-1.8 0.3-3.6 0.9-5.3 1.8l1.1 4.2l3.1-0.8L-8.7 6.9L3.2 9.3L5.4-1.5l2.5 2l2.7-3.4C9.5-4.4 8.1-5.7 6.5-6.7z"
fill="#e15b64"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(35 17)">
<path
d="M3.4-5.3L2.5-5l0.8-2.3L1.1-6.3l-1.2-2.2l-1.6 4.6l-4.6-1.6l0.9 2.3l-2.2 1.2l2.3 0.8L-6-0.9 c-0.6 0.3-0.8 0.9-0.5 1.5l1 2.1C-5.2 3.4-4.6 3.6-4 3.3l0.1-0.1l2.1 4.5C-1.4 8.4-0.7 8.7 0 8.3l1.7-0.8l1.7-0.8L5 5.9l1.7-0.8 C7.4 4.8 7.7 4 7.4 3.3L5.2-1.1l0.1-0.1c0.6-0.3 0.8-0.9 0.5-1.5l-1-2.1C4.6-5.4 3.9-5.6 3.4-5.3z"
fill="#f47e60"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(66 26)">
<path
d="M-4.5-3.7L1.9-6l0.5-0.2L2-7.2l-6.9 2.5C-5.7-4.4-6.1-3.5-6-2.7c0 0.1 0 0.2 0.1 0.3l3 8.2 C-2.5 6.9-1.3 7.4-0.2 7l5.6-2C5.9 4.8 6.2 4.2 6 3.7L3.2-3.9l-0.4-1L2.4-4.7L1.9-4.5l-3.2 1.2l-2.7 1c-0.3 0.1-0.6 0-0.8-0.2 c-0.1-0.1-0.1-0.1-0.1-0.2C-5.1-3.1-4.9-3.6-4.5-3.7z"
fill="#f8b26a"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(55 6)">
<polygon
points="0 -4.9 1.6 -1.7 5.1 -1.1 2.6 1.3 3.2 4.9 0 3.2 -3.2 4.9 -2.6 1.3 -5.1 -1.1 -1.6 -1.7"
fill="#abbd81"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</polygon>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
</g>
<g clip-path="url(#ldio-ssf8off6o8l-cp)">
<g transform="translate(0 -75)">
<g>
<g transform="translate(50 41)">
<path
d="M6.5-6.7C6.1-6.9 5.7-7.2 5.3-7.4C5-7.5 4.6-7.7 4.3-7.8C3.1-2.2-4-3.7-2.9-9.3c-0.4 0-0.7 0-1.1 0 c-0.5 0-1 0.1-1.4 0.2c-1.8 0.3-3.6 0.9-5.3 1.8l1.1 4.2l3.1-0.8L-8.7 6.9L3.2 9.3L5.4-1.5l2.5 2l2.7-3.4C9.5-4.4 8.1-5.7 6.5-6.7z"
fill="#e15b64"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(35 17)">
<path
d="M3.4-5.3L2.5-5l0.8-2.3L1.1-6.3l-1.2-2.2l-1.6 4.6l-4.6-1.6l0.9 2.3l-2.2 1.2l2.3 0.8L-6-0.9 c-0.6 0.3-0.8 0.9-0.5 1.5l1 2.1C-5.2 3.4-4.6 3.6-4 3.3l0.1-0.1l2.1 4.5C-1.4 8.4-0.7 8.7 0 8.3l1.7-0.8l1.7-0.8L5 5.9l1.7-0.8 C7.4 4.8 7.7 4 7.4 3.3L5.2-1.1l0.1-0.1c0.6-0.3 0.8-0.9 0.5-1.5l-1-2.1C4.6-5.4 3.9-5.6 3.4-5.3z"
fill="#f47e60"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(66 26)">
<path
d="M-4.5-3.7L1.9-6l0.5-0.2L2-7.2l-6.9 2.5C-5.7-4.4-6.1-3.5-6-2.7c0 0.1 0 0.2 0.1 0.3l3 8.2 C-2.5 6.9-1.3 7.4-0.2 7l5.6-2C5.9 4.8 6.2 4.2 6 3.7L3.2-3.9l-0.4-1L2.4-4.7L1.9-4.5l-3.2 1.2l-2.7 1c-0.3 0.1-0.6 0-0.8-0.2 c-0.1-0.1-0.1-0.1-0.1-0.2C-5.1-3.1-4.9-3.6-4.5-3.7z"
fill="#f8b26a"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
<g>
<g transform="translate(55 6)">
<polygon
points="0 -4.9 1.6 -1.7 5.1 -1.1 2.6 1.3 3.2 4.9 0 3.2 -3.2 4.9 -2.6 1.3 -5.1 -1.1 -1.6 -1.7"
fill="#abbd81"
>
<animateTransform
attributeName="transform"
type="rotate"
keyTimes="0;1"
values="0;360"
dur="0.7462686567164178s"
repeatCount="indefinite"
></animateTransform>
</polygon>
</g>
<animateTransform
attributeName="transform"
type="translate"
keyTimes="0;1"
values="0 0;0 75"
dur="1.4925373134328357s"
repeatCount="indefinite"
></animateTransform>
</g>
</g>
</g>
</svg>
<h3>加载中:{{ progress }}%</h3>
</template>
<script setup>
let props = defineProps({
progress: Number,
});
</script>
<style scoped>
h3 {
position: fixed;
top: 100px;
left: 50%;
font-size: 30px;
font-weight: 900;
}
</style>
访问页面
5、导航菜单栏实现现有图标使用
<template>
<Header></Header>
<router-view></router-view>
</template>
<script setup>
import Header from './components/Header.vue'
import { useRoute, useRouter } from "vue-router";
// 获取路由信息
let route = useRoute();
// 执行路由的跳转
let router = useRouter();
</script>
<style>
</style>
完善Header.vue
设置布局
<template>
<div class="header" >
<div class="logo" @click="router.push('/')">
<img src="../assets/img/logo_rect.jpg" alt="logo" />
</div>
<a-input-search
v-model:value="data.value"
placeholder="搜索商品或商品号"
class="input-search"
@search="onSearch"
/>
<a-menu v-model:selectedKeys="data.current" mode="horizontal">
<a-menu-item key="help">
<template #icon>
<question-circle-outlined />
</template>
帮助
</a-menu-item>
<a-menu-item key="salesroom">
<template #icon>
<ShopOutlined />
</template>
门店
</a-menu-item>
<a-sub-menu key="account">
<template #icon>
<UserOutlined />
</template>
<template #title>账户</template>
</a-sub-menu>
<a-sub-menu key="buycart">
<template #icon>
<CarOutlined />
</template>
<template #title>购物车</template>
<a-menu-item-group title="购物商品">
<a-menu-item v-for="(item, i) in store.state.buycarts" :key="item.id">
<div class="prod-item">
<div class="left">
<img :src="item.imgsrc" :alt="item.title" />
</div>
<div class="middle">
<div class="title">{{ item.title }}</div>
<div class="content">
<span class="num">数量:{{ item.num }}</span>
<div class="control">
<span
class="btn"
@click.stop="store.commit('addBuycartsNum', i)"
>+</span
>
<span
class="btn"
@click.stop="store.commit('minusBuycartsNum', i)"
>-</span
>
</div>
</div>
</div>
<div class="right">
<div class="price">¥ {{ item.price * item.num }}</div>
</div>
</div>
</a-menu-item>
<a-menu-item key="totalPrice">
<div class="total">
<span>总价:</span>
<span class="num"> ¥ {{ store.getters.totalPrice }}</span>
</div>
</a-menu-item>
</a-menu-item-group>
</a-sub-menu>
</a-menu>
</div>
</template>
<script setup>
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import { reactive } from "vue";
import {
ShopOutlined,
UserOutlined,
CarOutlined,
QuestionCircleOutlined,
} from "@ant-design/icons-vue";
const store = useStore();
const router = useRouter();
const data = reactive({
value: "",
current: ["help"],
});
const onSearch = () => {};
</script>
<style scoped>
.header{
display: flex;
justify-content: space-between;
align-content: center;
position: fixed;
z-index: 10000;
width: 100%;
}
.logo{
margin-right: 40px;
}
</style>
完善样式
安装less、less-loader
# .scss and .sass
npm install -D sass
# .less
npm install less-loader less --save-dev
# .styl and .stylus
npm install -D stylus
<style lang="less" scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
z-index: 1000000;
width: 100%;
background-color: #fff;
Box-shadow: 0 0 10px #ccc;
transition: all 0.5s;
}
.header.hidden {
transform: translate(0, -100%);
}
.logo {
margin-right: 40px;
img {
height: 30px;
}
}
.input-search {
display: flex;
flex: 1;
height: 40px;
align-items: center;
}
</style>
6、轮播图倾斜与颜色转变
<template>
<div class="homeswiper">
<div class="swiperBg">
<a-carousel arrows autoplay>
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<left-circle-outlined />
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<right-circle-outlined />
</div>
</template>
<div><h3>1</h3></div>
<div><h3>2</h3></div>
<div><h3>3</h3></div>
<div><h3>4</h3></div>
</a-carousel>
</div>
</div>
</template>
<script setup>
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue';
</script>
<!--
scoped 设置样式作用域仅仅在当前有效
:deep 控制当前组件内部组件样式
-->
<style lang="less" scoped>
/* For demo */
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 160px;
line-height: 160px;
background: #364d79;
overflow: hidden;
}
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
z-index: 1;
}
.ant-carousel :deep(.custom-slick-arrow:before) {
display: none;
}
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
</style>
在HomeCom首页当中引入改组件
<template>
<div class="homepage">
<HomeSwiper></HomeSwiper>
</div>
</template>
<script setup>
import HomeSwiper from "./Home/HomeSwiper.vue"
import * as api from '../api/index';
import {reactive} from "vue";
let result = await api.getHomepage();
const hero = reactive(result.hero);
</script>
<style scoped>
</style>
设置倾斜
<template>
<div class="homepage">
<HomeSwiper></HomeSwiper>
</div>
</template>
<script setup>
import HomeSwiper from "./Home/HomeSwiper.vue"
import * as api from '../api/index';
import {reactive} from "vue";
let result = await api.getHomepage();
const hero = reactive(result.hero);
</script>
<style lang="less" scoped>
.homepage{
padding-top: 46px;
height: 575px;
position: relative;
}
</style>
<template>
<div class="homeswiper">
<div class="swiperBg">
<a-carousel arrows autoplay>
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<left-circle-outlined />
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<right-circle-outlined />
</div>
</template>
<div><h3>1</h3></div>
<div><h3>2</h3></div>
<div><h3>3</h3></div>
<div><h3>4</h3></div>
</a-carousel>
</div>
</div>
</template>
<script setup>
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue';
</script>
<!--
scoped 设置样式作用域仅仅在当前有效
:deep 控制当前组件内部组件样式
-->
<style lang="less" scoped>
.swiperBg {
position: absolute;
display: block;
width: 100%;
height: 575px;
background-color: #00d9ff;
transform-origin: 0 0;
transform: skew(0, -8deg);
transition: all 0.5s;
}
.swiper-item {
height: 575px;
.swiper-Box {
display: flex;
min-width: 1200px;
justify-content: center;
.swiper-left {
width: 340px;
padding-right: 100px;
display: flex;
flex-direction: column;
align-items: flex-end;
margin-top: 100px;
text-align: right;
}
h3 {
font-size: 30px;
font-weight: 900;
margin: 0;
line-height: 40px;
}
h1 {
font-size: 60px;
font-weight: 900;
margin: 0;
line-height: 70px;
}
.banner-right {
margin-top: 45px;
transform-origin: 0 100%;
transform: skew(0, -8deg);
overflow: hidden;
border-radius: 20px;
border-bottom-right-radius: 80px;
img {
transform-origin: 0 100%;
transform: skew(0, 8deg);
border-radius: 20px;
}
}
}
}
/* For demo */
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 575px;
line-height: 160px;
overflow: hidden;
}
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
z-index: 1;
}
.ant-carousel :deep(.custom-slick-arrow:before) {
display: none;
}
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
</style>
在HomeCom.vue得到请求对应的数据,将数据设置到子组件当中
:banner="data.banner"
<template>
<div class="homepage">
<HomeSwiper :banner="data.banner"></HomeSwiper>
</div>
</template>
<script setup>
import HomeSwiper from "./Home/HomeSwiper.vue"
import * as api from '../api/index';
import {reactive} from "vue";
let result = await api.getHomepage();
const data = reactive({ banner: result.banner });
</script>
<style lang="less" scoped>
.homepage{
padding-top: 46px;
height: 575px;
position: relative;
}
</style>
在HomeSwiper子组件当中获取父组件数据,实现走马灯切换图片
<script setup>
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons-vue';
import {reactive,defineProps} from "vue";
const props = defineProps({banner:Array})
const data = reactive({
bgColor: props.banner[0].bg_color,
})
console.log(props)
function changeFn(from,to) {
data.bgColor = props.banner[to].bg_color
}
</script>
7、实现轮播图负负得正的倾斜效果
<template>
<div class="homeswiper">
<div class="swiperBg" :style="{ backgroundColor: data.bgColor }"></div>
<a-carousel arrows autoplay :beforeChange="changeFn">
<template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<left-circle-outlined />
</div>
</template>
<template #nextArrow>
<div class="custom-slick-arrow" style="right: 10px">
<right-circle-outlined />
</div>
</template>
<div class="swiper-item" v-for="(item, i) in props.banner">
<div class="swiper-Box">
<div class="swiper-left">
<h3 :style="{ color: item.text_color }">
{{ item.desktop_sub_title || item.sub_title }}
</h3>
<h1 :style="{ color: item.text_color }">
{{ item.desktop_title || item.title }}
</h1>
</div>
<div class="banner-right">
<img
:src="`https://pixl.decathlon.com.cn/${item.picture_desktop}/banner.jpg`"
:alt="item.title"
/>
</div>
</div>
</div>
</a-carousel>
</div>
</template>
<script setup>
import { LeftCircleOutlined, RightCircleOutlined } from "@ant-design/icons-vue";
import { reactive } from "vue";
const props = defineProps({ banner: Array });
const data = reactive({
bgColor: props.banner[0].bg_color,
});
function changeFn(from, to) {
// console.log(to);
data.bgColor = props.banner[to].bg_color;
}
</script>
<style lang="less" scoped>
.swiperBg {
position: absolute;
display: block;
width: 100%;
height: 575px;
background-color: orangered;
transform-origin: 0 0;
transform: skew(0, -8deg);
transition: all 0.5s;
}
.swiper-item {
height: 575px;
.swiper-Box {
display: flex;
min-width: 1200px;
justify-content: center;
.swiper-left {
width: 340px;
padding-right: 100px;
display: flex;
flex-direction: column;
align-items: flex-end;
margin-top: 100px;
text-align: right;
}
h3 {
font-size: 30px;
font-weight: 900;
margin: 0;
line-height: 40px;
}
h1 {
font-size: 60px;
font-weight: 900;
margin: 0;
line-height: 70px;
}
.banner-right {
margin-top: 45px;
transform-origin: 0 100%;
transform: skew(0, -8deg);
overflow: hidden;
border-radius: 20px;
border-bottom-right-radius: 80px;
img {
transform-origin: 0 100%;
transform: skew(0, 8deg);
border-radius: 20px;
}
}
}
}
/* For demo */
.ant-carousel :deep(.slick-slide) {
text-align: center;
height: 575px;
line-height: 160px;
overflow: hidden;
}
.ant-carousel :deep(.slick-arrow.custom-slick-arrow) {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
z-index: 1;
}
.ant-carousel :deep(.custom-slick-arrow:before) {
display: none;
}
.ant-carousel :deep(.custom-slick-arrow:hover) {
opacity: 0.5;
}
.ant-carousel :deep(.slick-slide h3) {
color: #fff;
}
</style>
8、实现分类组件的应用
<template>
<div class="homepage">
<HomeSwiper :banner="data.banner"></HomeSwiper>
<div class="live">
<h1>生活类别100+</h1>
<div class="live-list">
<div
class="live-item"
v-for="index in 10"
v-if="data.sports.length !== 0"
>
<div class="live-btn">
<img
:src="data.sports[index].menuThumbnail"
:alt="data.sports[index].displayName"
/>
<h3>{{ data.sports[index].displayName }}</h3>
</div>
</div>
</div>
<a-button type="primary" size="large" @click="router.push('/product')"
>立即享受生活</a-button
>
</div>
</div>
</template>
<script setup>
import HomeSwiper from "./Home/HomeSwiper.vue";
import * as api from "../api/index";
import { reactive } from "vue";
import { useRouter } from "vue-router";
let result = await api.getHomepage();
const router = useRouter();
const data = reactive({ banner: result.banner, sports: result.sports });
console.log(result);
</script>
<style lang="less" scoped>
.homepage {
padding-top: 46px;
height: 575px;
position: relative;
}
.live {
width: 1200px;
margin: 40px auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 40px;
h1 {
font-size: 30px;
}
.live-list {
display: flex;
flex-wrap: wrap;
.live-item {
width: 240px;
}
.live-btn {
display: flex;
height: 73px;
width: 200px;
background-color: #fff;
border-radius: 8px 8px 25px 8px;
margin-bottom: 30px;
Box-shadow: 5px 5px 5px #ccc;
align-items: center;
justify-content: center;
position: relative;
img {
position: absolute;
width: 80px;
height: 80px;
left: -15px;
top: -15px;
}
h3 {
font-weight: 900;
font-size: 20px;
}
}
}
}
</style>
刷新页面
9、鼠标滚动控制全屏切换
设置请求的接口
export const getProducts = () => {
return request({
method: "GET",
url: "/products",
});
};
在store当中设置对应状态管理
isFullscreen:false
getFullScreen(state,payload){
state.isFullscreen = payload;
}
完善Product.vue
实现状态栏隐藏
鼠标滚动
10、实现产品列表的展示
完善样式
全部代码
<template>
<h1>Product</h1>
<h1>产品id是{{ route.params.id }}</h1>
<div class="loading" v-show="data.isLoading">
<Loading></Loading>
</div>
<div class="product" v-show="!data.isLoading">
<div class="prod-list" :class="{hidden:store.state.isFullscreen}">
<h1> <SketchOutlined>产品推荐</SketchOutlined> </h1>
<div class="products">
<div class="prod-item" v-for="(prod,pI) in data.products">
<div class="prod-title">
{{ prod.title }}
</div>
<div class="img">
<img :src="prod.imgsrc" :alt="prod.title">
</div>
<a-button type="primary" block >
<template #icon>
<ShoppingCartOutlined></ShoppingCartOutlined>
</template>
加入购物车
</a-button>
</div>
</div>
<div class="products">
</div>
</div>
<div class="scene-list" :class="{hidden:store.state.isFullscreen}">
<h3><RadarChartOutlined>切换使用场景</RadarChartOutlined> </h3>
</div>
</div>
</template>
<script setup>
import Loading from "../components/Loading.vue";
import {
SketchOutlined,
RadarChartOutlined,
ShoppingCartOutlined,
} from "@ant-design/icons-vue";
import { useRoute } from 'vue-router'
import {onMounted, reactive} from 'vue'
import * as api from '../api/index.js'
import {useStore} from 'vuex'
const route = useRoute();
const store = useStore();
console.log(route)
const data = reactive({
products :[],
isLoading:true,
scenes:[]
})
//生命周期函数x
onMounted(async () => {
//await 等待数据返回结果 ,使用await 的同时 必须使用async
let result = await api.getProducts()
console.log(result);
data.isLoading = false;
data.products = result.list;
data.scenes = result.hdr;
})
window.addEventListener('mousewheel' ,(e)=>{
// console.log(e)
if(e.deltaY > 0){
store.commit("getFullScreen",true)
}
if(e.deltaY < 0){
store.commit("getFullScreen",false)
}
})
</script>
<style lang="less" scoped>
.desc {
position: fixed;
z-index: 100000;
background-color: rgba(255, 255, 255, 0.5);
width: 600px;
top: 100px;
left: 50%;
margin-left: -300px;
transition: all 0.5s;
transform: translate(-100vw, 0);
padding: 15px;
}
.desc.active {
transform: translate(0, 0);
}
.prod-list {
width: 300px;
height: 100vh;
padding: 60px 0 0;
position: fixed;
z-index: 100000;
transition: all 0.5s;
background-color: rgb(208, 255, 0);
left: 0;
top: 0;
h1 {
font-size: 20px;
font-weight: 900;
padding: 10px 25px 0;
}
.products {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.prod-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 250px;
background-color: #fff;
border-radius: 20px;
overflow: hidden;
margin: 10px 0;
Box-shadow: 2px 2px 5px #666;
transition: all 0.3s;
&.active {
Box-shadow: 2px 2px 5px #666, 0px 0px 10px red;
}
&:hover {
transform: translate(0px, -5px);
Box-shadow: 2px 2px 5px #666, 0px 0px 10px orangered;
// background-color: orange;
}
img {
width: 190px;
}
.prod-title {
padding: 0 20px;
}
}
}
}
.prod-list.hidden {
transform: translate(-100%, 0);
}
.scene-list {
width: 300px;
height: 100vh;
padding: 60px 0 0;
position: fixed;
z-index: 100000;
transition: all 0.5s;
background-color: rgba(182, 73, 73, 0.8);
right: 0;
top: 0;
h3 {
font-size: 20px;
font-weight: 900;
padding: 0 30px;
}
.scenes {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.scene-item {
padding: 6px 0;
img {
width: 250px;
border-radius: 10px;
Box-shadow: 2px 2px 10px #666;
transition: all 0.3s;
&.active {
Box-shadow: 2px 2px 5px #666, 0px 0px 10px red;
}
&:hover {
transform: translate(0px, -5px);
Box-shadow: 2px 2px 5px #666, 0px 0px 10px orangered;
}
}
}
}
.scene-list.hidden {
transform: translate(100%, 0);
}
</style>
继续完善列表功能
<div class="scenes">
<div class="scene-item" v-for="(scene,index) in data.scenes">
<img :src="`./files/hdr/${scene}.jpg`" :alt="scene">
</div>
</div>
设置点以及其他功能开发
pIndex:0,
sceneIndex: 0
实现点击选择
防止手残全部代码
<template>
<h1>Product</h1>
<h1>产品id是{{ route.params.id }}</h1>
<div class="loading" v-show="data.isLoading">
<Loading></Loading>
</div>
<div class="product" v-show="!data.isLoading">
<div class="prod-list" :class="{hidden:store.state.isFullscreen}">
<h1> <SketchOutlined>产品推荐</SketchOutlined> </h1>
<div class="products">
<div class="prod-item"
:class="{ active : pI == data.pIndex }"
v-for="(prod,pI) in data.products"
@click="changeModel(prod,pI)">
<div class="prod-title">
{{ prod.title }}
</div>
<div class="img">
<img :src="prod.imgsrc" :alt="prod.title">
</div>
<a-button type="primary" block >
<template #icon>
<ShoppingCartOutlined></ShoppingCartOutlined>
</template>
加入购物车
</a-button>
</div>
</div>
<div class="products">
</div>
</div>
<div class="scene-list" :class="{hidden:store.state.isFullscreen}">
<h3><RadarChartOutlined>切换使用场景</RadarChartOutlined> </h3>
<div class="scenes">
<div class="scene-item"
v-for="(scene,index) in data.scenes"
@click="changeHdr(scene,index)">
<img :class="{ active : index == data.sceneIndex }" :src="`./files/hdr/${scene}.jpg`" :alt="scene">
</div>
</div>
</div>
</div>
</template>
<script setup>
import Loading from "../components/Loading.vue";
import {
SketchOutlined,
RadarChartOutlined,
ShoppingCartOutlined,
} from "@ant-design/icons-vue";
import { useRoute } from 'vue-router'
import {onMounted, reactive} from 'vue'
import * as api from '../api/index.js'
import {useStore} from 'vuex'
const route = useRoute();
const store = useStore();
console.log(route)
const data = reactive({
products :[],
isLoading:true,
scenes:[],
pIndex:0,
sceneIndex: 0
})
//生命周期函数x
onMounted(async () => {
//await 等待数据返回结果 ,使用await 的同时 必须使用async
let result = await api.getProducts()
console.log(result);
data.isLoading = false;
data.products = result.list;
data.scenes = result.hdr;
})
function changeModel(prod,pI){
data.pIndex = pI;
}
function changeHdr(scene,index){
data.sceneIndex = index;
}
window.addEventListener('mousewheel' ,(e)=>{
// console.log(e)
if(e.deltaY > 0){
store.commit("getFullScreen",true)
}
if(e.deltaY < 0){
store.commit("getFullScreen",false)
}
})
</script>
<style lang="less" scoped>
.desc {
position: fixed;
z-index: 100000;
background-color: rgba(255, 255, 255, 0.5);
width: 600px;
top: 100px;
left: 50%;
margin-left: -300px;
transition: all 0.5s;
transform: translate(-100vw, 0);
padding: 15px;
}
.desc.active {
transform: translate(0, 0);
}
.prod-list {
width: 300px;
height: 100vh;
padding: 60px 0 0;
position: fixed;
z-index: 100000;
transition: all 0.5s;
background-color: rgba(255, 255, 255, 0.8);
left: 0;
top: 0;
h1 {
font-size: 20px;
font-weight: 900;
padding: 10px 25px 0;
}
.products {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.prod-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 250px;
background-color: #fff;
border-radius: 20px;
overflow: hidden;
margin: 10px 0;
Box-shadow: 2px 2px 5px #666;
transition: all 0.3s;
&.active {
Box-shadow: 2px 2px 5px #666, 0px 0px 10px red;
}
&:hover {
transform: translate(0px, -5px);
Box-shadow: 2px 2px 5px #666, 0px 0px 10px orangered;
// background-color: orange;
}
img {
width: 190px;
}
.prod-title {
padding: 0 20px;
}
}
}
}
.prod-list.hidden {
transform: translate(-100%, 0);
}
.scene-list {
width: 300px;
height: 100vh;
padding: 60px 0 0;
position: fixed;
z-index: 100000;
transition: all 0.5s;
background-color: rgba(255, 255, 255, 0.8);
right: 0;
top: 0;
h3 {
font-size: 20px;
font-weight: 900;
padding: 0 30px;
}
.scenes {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.scene-item {
padding: 6px 0;
img {
width: 250px;
border-radius: 10px;
Box-shadow: 2px 2px 10px #666;
transition: all 0.3s;
&.active {
Box-shadow: 2px 2px 5px #666, 0px 0px 10px red;
}
&:hover {
transform: translate(0px, -5px);
Box-shadow: 2px 2px 5px #666, 0px 0px 10px orangered;
}
}
}
}
.scene-list.hidden {
transform: translate(100%, 0);
}
</style>
11、使用Vuex状态管理完成购物车模块
buycarts:[]
addBuycarts(state,payload){
state.buycarts.push(payload)
},
addBuycartsNum(state,payload){
state.buycarts[payload].num++
},
minusBuycartsNum(state,payload){
state.buycarts[payload].num--;
if(state.buycarts[payload].num == 0){
state.buycarts.splice(payload,1);//删除对应的内容
}
}
getters:{
totalPrice(state){
let total = state.buycarts.reduce((pre,item) =>{ //reduce数组进行求和
return pre + item.price * item.num;
},0);
return total;
},
},
设置加入购物车
<a-button type="primary" block @click.stop="addBuycart(prod)">
<template #icon>
<ShoppingCartOutlined></ShoppingCartOutlined>
</template>
加入购物车
</a-button>
function addBuycart(prod){
let product = {...prod,num:1};
store.commit('addBuycarts',product)
}
完善购物车样式
<style>
.ant-menu-submenu {
z-index: 1000000000 !important;
}
body .ant-menu-sub .ant-menu-item {
height: auto;
}
.ant-menu-sub .ant-menu-item .prod-item {
display: flex;
width: 400px;
justify-content: space-between;
}
.ant-menu .prod-item img {
width: 100px;
height: 100px;
}
.ant-menu-item .prod-item .middle {
width: 220px;
height: 100px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.ant-menu-item .prod-item .middle .title {
font-size: 16px;
font-weight: 900;
}
.ant-menu-item .prod-item .middle .content {
display: flex;
justify-content: space-between;
}
.ant-menu-item .prod-item .middle .btn {
display: inline-block;
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
border: 1px solid #ccc;
margin: 0 5px;
}
.ant-menu-item .prod-item .right .price {
font-weight: 900;
color: orange;
font-size: 16px;
display: flex;
justify-content: center;
align-items: center;
}
.ant-menu-item .total {
font-weight: 900;
display: flex;
justify-content: flex-end;
font-size: 16px;
}
.ant-menu-item .total .num {
color: orange;
padding-right: 20px;
}
</style>
访问:http://127.0.0.1:5173/#/product/212121212
添加商品
完善功能实现重复商品添加购物车(累加数量)
function addBuycart(prod){
let product = {...prod,num:1};
let index = 0;
let isExist = store.state.buycarts.some((item,i)=>{
if(product.id == item.id){
index = i
return true
}else {
return false
}
})
if(isExist){
store.commit('addBuycartsNum',index)
}else {
store.commit('addBuycarts',product)
}
}