vue3+antd 入门

1,408 阅读16分钟

一、创建项目

创建一个vue项目的目录, 在目录执行下面的命令,这样就可以创建一个 Vite 的初始化项目。

npm init vite myapp

myapp 是项目名称,执行完命令之后会出现下面窗口

image.png

这里选择 vue和TS. 进入项目目录 cd myapp,目录如下图

.
├── README.md
├── index.html           入口文件
├── package.json
├── public               资源文件
│   └── favicon.ico
├── src                  源码
│   ├── App.vue          单文件组件
│   ├── assets
│   │   └── logo.png
│   ├── components   
│   │   └── HelloWorld.vue
│   └── main.js          入口
└── vite.config.js vite工程化配置文件

执行 npm install 装依赖包,需要花些时间,然后执行 npm run dev 启动项目,看到下面的窗口就说明启动成功了。

image.png

浏览器打开 http://127.0.0.1:5173/ ,下面默认页面。

image.png

入口文件

使用 vs code 编辑器打开项目,然文件后查看src目录下main.ts文件

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

从vue中导入了createApp,然后 createApp(app) 创建实例,并且使用mount("#app"),挂载到一个页面元素上。

import App from './App.vue' 导入app组件

查看App.vue 文件

<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <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>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

文件分为三块 <script> 代码块 ,<template> 模板块,<style> 样式,所有vue文件都包含这个三个代码其中一个。

二、入门小demo

现在页面上设置一个数字,然后通过点击一个按钮,每次点击按钮数字都实现加一

打开 App.vue 文件,在文件中敲入以下代码,保存。

<template>
 <span>{{count}}</span>
  <button @click="add">点击</button>
</template>

<script setup lang="ts">
import {ref} from 'vue'
  const count = ref(0)
  const add = ()=>{
    count.value++
  }
</script>
<style scoped>
</style>

然后访问 http://127.0.0.1:5173 页面展示

image.png

通过{{count}} 渲染变量,@click="add" 增加一个点击事件,add 为调用的方法。

const count = ref(0) 定义一个响应式变量,const add = ()=>{ count.value++ },定一个add方法,每次调用方法实现 count加1。

大家可以操作下,感受下响应式的强大之处。 在jquery时代,我们需要获取dom元素对象,然后修改dom对象html内部的文本,然后vue不需要这么做,框架会帮忙监听响应式变量是否修改,然后再页面动态渲染。

三、一个复杂的例子

需求是这样的,页面上有个列表,有输入框,当然能在输入框敲入文字,然后点击回车,会将输入敲入文字追加到列表中。

打开app.vue 文件敲入下面文稿中代码

<template>
  <p>我的爱好:{{data.title}}</p>
 <input type="text" v-model="data.title">
 <ul>
  <li v-for="todo in data.todos"> {{todo}}</li>
 </ul>

</template>

<script setup lang="ts">
import {reactive} from 'vue'
const data = reactive({"title":"学习","todos":['吃饭','睡觉']})
</script>
<style scoped>
</style>

页面展示情况,如下

image.png

定义了响应式变量data,里面包含title 和 todos两个属性,这块用的是 reactive 函数,上文中使用的是ref,简单来说,两者的区别在于一个定义reactive 复杂响应类型数据 object,ref 定义简单类型如 int string bool array,在input 元素上,我们使用了 v-model 属性,这个属性的意思是 把data.title 和 input 元素的value值绑定起来,这个是双向绑定,双向绑定的意思是,input输入框文本内容会和data.title同步。

接下来,我们输入框里面输入文本,然后敲击回车,自动添加到列表,代码如下

<template>
  <p>我的爱好:{{data.title}}</p>
 <input type="text" v-model="data.title" @keydown.enter="add">
 <ul>
  <li v-for="todo in data.todos"> {{todo}}</li>
 </ul>

</template>

<script setup lang="ts">
import {reactive} from 'vue'
const data = reactive({"title":"学习","todos":['吃饭','睡觉']})
const add = ()=>{
    data.todos.push(data.title)
}

</script>
<style scoped>
</style>

定义了一个函数 const add = ()=>{ data.todos.push(data.title) },然后再 input 元素上绑定一个@keydown.enter="add",keydown.enter 代表敲击回车事件,敲击回车会调用add函数,在add 函数中把data.title 添加到data.todos 数组里面,从而实现了对页面列表数据的添加。

image.png

接下来,我们再列表中增加一个复选框,并且展示复选框中的列表元素个数以及和总体的元素个数。

<template>
  <p>我的爱好:{{title}}</p>
 <input type="text" v-model="title" @keydown.enter="add">
 <ul>
  <li v-for="todo in todos"><input type="checkbox" v-model="todo.selected"> {{todo.title}}</li>
 </ul>
 <p>{{seletedCount}}/{{total}}</p>

</template>

<script setup lang="ts">
import {ref,reactive,computed} from 'vue'
const title = ref("学习")
const todos = reactive([{"title":"吃饭","selected":true},{"title":"睡觉","selected":false}])
const add = ()=>{
    todos.push({"selected":false,"title":title.value})
    title.value = ""
}

const total = computed(()=>todos.length)
const seletedCount = computed(()=>{
  return todos.filter(v=>v.selected).length}
  )

</script>
<style scoped>
</style>

代码里面我分别定义了title,todos 两个响应式变量,并没有放在一个data变量里面,因为todos增加了一个seleced 属性,是否选中,是的data结构变的复杂了,不会维护,所以分开来写。然后定义了total 和 selectedcount 两个响应是变量,分别保存列表元素的个数和选中个个数。

<input type="checkbox" v-model="todo.selected">同样使用v-model属性做了绑定,todo.selected等于true,复选框就会是选中状态。

const total = computed(()=>todos.length),使用计算属性函数,函数返回列表元素的数量,当列表元素增加,total的值也会跟着改变。

const seletedCount = computed(()=>{ return todos.filter(v=>v.selected).length} ),也同样使用了计算属性,里面使用了filter 函数,filter 函数可以对列表元素过滤,筛选v.selected 等于true的元素,在通过length函数获取元素个数。

而且computed计算属性还内置了缓存功能,如果依赖数据没变化,多次使用计算属性会直接返回缓存结果,同我们直接写在模板里相比,性能也有了提升。下图是代码的展示效果。

image.png

再增加一个全选框,点击全选框来切换选中列表元素和清除列表元素。

<template>
  <p>我的爱好:{{title}}</p>
 <input type="text" v-model="title" @keydown.enter="add">
 <p><input v-model="selectAll" type="checkbox"/>全选</p>
 <ul>
  <li v-for="todo in todos"><input type="checkbox" v-model="todo.selected"> {{todo.title}}</li>
 </ul>
 <p>{{seletedCount}}/{{total}}</p>

</template>

<script setup lang="ts">
import {ref,reactive,computed} from 'vue'
const title = ref("学习")
const todos = reactive([{"title":"吃饭","selected":true},{"title":"睡觉","selected":false}])
const add = ()=>{
    todos.push({"selected":false,"title":title.value})
    title.value = ""
}

const total = computed(()=>todos.length)
const seletedCount = computed(()=>{
  return todos.filter(v=>v.selected).length}
  )
  

const selectAll = computed({

  get:function(value){ seletedCount.value = 0},
  set:function(value){ 
      todos.forEach((todo)=>{
        todo.selected = value;
       })}
}
)

</script>
<style scoped>
</style>

定义了一个变量selectAll,并且绑定到全选对应的input元素上,对应input元素选中,取消选中来操作todos数据元素,这时候就不能单一函数了,需要使用对象,对象包含两个属性,分别是get和set,对应设置元素值和获取元素值对应的方法。

获取全选input元素值的时候,调用get方法,设置seletedCount=0,设置input元素值的时候,调用set方法,修改修改todos 元素 todo.selected等于全选框的值,也就实现了全选框选中,列表元素选中,全选框不选中,列表也不选中。下面是页面展示

image.png

接下来我们增加一个删除功能,需要删除列表元素。每个元素后面增加一个x,点击x 删除元素。

<template>
  <p>我的爱好:{{title}}</p>
 <input type="text" v-model="title" @keydown.enter="add">
 <p><input v-model="selectAll" type="checkbox"/>全选</p>
 <ul>
  <li v-for="todo,index in todos">
    <input type="checkbox" v-model="todo.selected"> {{todo.title}} 
    <button @click="deleteTodo(index)">x</button>
  </li>
 </ul>
 <p>{{seletedCount}}/{{total}}</p>

</template>

<script setup lang="ts">
import {ref,reactive,computed} from 'vue'
const title = ref("学习")
const todos = reactive([{"title":"吃饭","selected":true},{"title":"睡觉","selected":false}])
const add = ()=>{
    todos.push({"selected":false,"title":title.value})
    title.value = ""
}

const total = computed(()=>todos.length)
const seletedCount = computed(()=>{
  return todos.filter(v=>v.selected).length}
  )


const selectAll = computed({
  get:function(value){ seletedCount.value = value},
  set:function(value){ 
      todos.forEach((todo)=>{
        todo.selected = value;
       })}
      }
)

const deleteTodo = (i)=>{
  todos.splice(i,1)
}

</script>
<style scoped>
</style>

添加一个按钮,绑定一个点击事件, @click="deleteTodo(index)",deleteTodo 是todos数组的元素下标,通过下标删除元素,const deleteTodo = (i)=>{ todos.splice(i,1) } ,调用splice 函数删除元素。

四、vue的路由

学习路由之前,我们规范下我们的项目目录

├── src
│   ├── api            数据请求
│   ├── assets         静态资源
│   ├── components     组件
│   ├── pages          页面
│   ├── router         路由配置
│   ├── store          vuex数据
│   └── utils          工具函数

通过下面命令安装路由插件。

npm install vue-router@next vuex@next

静态路由

我们的页面需要引入路由系统,我们进入到 router 文件夹中,新建 index.ts,写入下面的代码:

import { createRouter, createWebHashHistory, } from 'vue-router' 
import Home from '../pages/home.vue' 
import About from '../pages/about.vue' 

const routes = [ 
    { path: '/', name: 'Home', component: Home }, 
    { path: '/about', name: 'About', component: About } 
] 

const router = createRouter{ history: createWebHashHistory(), routes }) 

export default router

上面的代码中,我们首先引入了 createRouter 和 createWebHashHistory 两个函数。createRouter 用来新建路由实例,createWebHashHistory 用来配置我们内部使用 hash 模式的路由,也就是 url 上会通过 # 来区分。

routes 数组元素 path 代表路径,component 表示调用的组件 ,about 和 home 两个组件要 import 进来。

之后在上面的代码里,我们引入两个组件 about 和 home,根据不同的访问地址/ 和/home 去渲染不同的组件,最后返回 router 即可。现在页面就会报错,提示我们找不到 about 和 home 这两个组件,然后我们去 pages 下面新建两个文件,分别输入如下内容:

about.vue

<template>
    <h>关于</h>
</template>

home.vue

<template>
    <h>首页</h>
</template>

接着在app.vue 引入两个路由链接

<template>
  <div>
    <router-link to="/">首页</router-link>|
    <router-link to="/about">关于</router-link>
  </div>
  <router-view></router-view>
</template>

代码中的 router-link 和 router-view 就是由 vue-router 注册的全局组件,router-link 负责跳转不同的页面,相当于 Vue 世界中的超链接 a 标签; router-view 负责渲染路由匹配的组件,我们可以通过把 router-view 放在不同的地方,实现复杂项目的页面布局

最后我们在 main.ts 中,加载 router 的配置,代码如下:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import  router from './router/index'
createApp(App).use(router).mount('#app')

import router from './router/index' 引入路由,然后use(router) 加载路由,然后我们在浏览器页面访问如下地址 http://127.0.0.1:5173/#/

image.png

点击关于和首页,下面的文字在会跟着变化。

动态路由

下面我们添加一个按钮,点击按钮老跳转页面。 在pages目录下创建jump.vue 文件,输入以下内容

<template>
    <h3>我是跳转页</h3>
</template>

打开app.vue 文件,输入以下内容:

<template>
  <div>
    <p><button @click="jump">跳转</button></p>
    <router-link to="/">首页</router-link>|
    <router-link to="/about">关于</router-link>
  </div>
  <router-view></router-view>
</template>
<script setup lang="ts">
import router from './router';
import Jump from './pages/jump.vue'
const jump = ()=>{
  router.addRoute({path:"/jump",component:Jump,name:"跳转"})
  router.push({path:"jump"})
}
</script>

以上代码实现了动态路由,通过 router.addRoute({path:"/jump",component:Jump,name:"跳转"}) 方法注册了一个路由,然后 router.push 跳转。

image.png

路由嵌套

在前端一个页面通常由这种情况,页面的左侧是菜单树,上面是导航栏,中间内容部分,内容部分会随着点击菜单树变化而变化。

对应这种情况可以通过嵌套路由来实现,

/home/about                           /home/jump
+------------------+                  +-----------------+
| home             |                  | home            |
| +--------------+ |                  | +-------------+ |
| | about        | |  +------------>  | | jump        | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

在pages文件目录下创建child.vue 文件,文件内容如下:

<template>
    <h3>我子页面1111</h3>
</template>

接着注册路由,打开router/index.ts 代码如下

import { createRouter, createWebHashHistory, } from 'vue-router' 
import Home from '../pages/home.vue' 
import About from '../pages/about.vue' 
import Child from '../pages/child.vue' 

const routes = [ 
    { path: '/', name: 'Home', component: Home,
     children:[
        { path: 'child', name: 'Child', component: Child } 
     ]
    }, 
    { path: '/about', name: 'About', component: About } 
] 

const router = createRouter({ history: createWebHashHistory(), routes })

export default router

在home 路由下创建 children ,里面为子路由 child。

再开发pages/home.vue ,代码如下:

<template>
    <h3>我是首页</h3>
    <p><button @click="action">count++</button></p>
    <router-view></router-view>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const action = ()=>{
    store.commit("add")
}
</script>

代码里面增加了一行 <router-view></router-view>, 子路由对应的组件将通过 router-view 渲染出来。

打开浏览器,访问 http://127.0.0.1:5173/#/child ,页面显示如下

image.png

在页面的最下方显示子路由页面数据。

五、像搭积木一样开发应用

实际项目开发中,涉及到很多通用模块,这些模块我们需要抽象成组件,以方便在其他页面中使用。当然还有一个页面包含元素太多,我们也需要拆分成多个组件,这样代码就有层次感,也好维护。 你可以看下面 Vue 官方的示例图,它对组件化开发做了形象化的展示。图中的左边是一个网页,可以按照功能模块抽象成很多组件,这些组件就像积木一样拼接成网页。

image.png

组件开发

一个.vue 文件就可以看作一个组件,一个组件包含三个模块,js 代码,页面内容,css 样式。 下面看一个例子 .vue

写一个组件显示星星的组件,在components 目录下创建一个star.vue 文件,代码内容如下

<template>
   <p>{{rate}}</p>
</template>

<script setup lang="ts">
import {ref} from 'vue'
const rate = ref("★★★★★") 
</script>

在jump页面调用这个组件,pages/jump.vue 代码内容如下:

<template>
    <h3>我是跳转页</h3>
    <Star/>
    <Star/>
    <Star/>
</template>
<script setup lang="ts">
import Star from "../components/star.vue"
</script>

导入 import Star from "../components/star.vue",组件,然后通过 <Star/>方式调用组件。

浏览器打开 http://127.0.0.1:5173/#/jump 页面内容如下

image.png

在页面中显示了三行五星,是不是很简单。

组件传参

组件只有这个展示固定的信息数量,是不能满足多种业务场景的,我们需要控制星星展示的数量。

继续打开 components/star.vue,输入以下代码:

<template>
   <p>{{rate}}</p>
</template>

<script setup lang="ts">
import { defineProps,computed } from 'vue';
const props = defineProps({ count: Number})
const rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.count, 10 - props.count))
</script>

通过 defineProps函数来定义个参数 count,count 来自于父组件传来的参数。然后通过 props.count 获取父组件传来的参数,然后计算展示出来的星星个数。

在 pages/jump.vue 父组件调用star 组件,并且传参

<template>
    <h3>我是跳转页</h3>
    <Star :count="3"/>
    <Star :count="4"/>
    <Star :count="5"/>
</template>
<script setup lang="ts">
import Star from "../components/star.vue"
</script>

通过 :count="3",就可以把值传递给子组件 star,浏览器打开 http://127.0.0.1:5173/#/jump ,看下效果

image.png

组件事件

接下来我们实现一个功能,鼠标点击星星上面,改变实体星星个数。 在 components/star.vue 增加以下代码。

<template>
   <p @click="onRate(num)">{{rate}}</p>
</template>

<script setup lang="ts">
import { defineProps,computed,defineEmits} from 'vue';
const props = defineProps({ count: Number})
let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.count, 10 - props.count))
let emits = defineEmits('update-rate')
function onRate(num){
     emits('update-rate',num)
}
</script>

我们使用 defineEmit 在组件里面抛出了一个事件update-rate,在子组件中点击点击事件,来调用父组件传递过来update-rate事件。

在父组件 pages/jump.vue 加入如下代码。

<template>
    <h3>我是跳转页</h3>
    <Star :count="score" @update-rate="update(5)"/>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import Star from "../components/star.vue"
const score = ref(3)
const update =(num)=>{ 
    score.value = num
}

</script>

通过 @update-rate="update(5)" 给子组件事件update-rate,增加一个update方法,在方法中改变score 值,来修改星星数量。

image.png

六、数据流管理

上面我们讲了组件之间如何传参,如何抛出事件,通过事件实现组件之间交互,如果有一些数据,我们需要在多个页面之间共享,那通过传参的方式,来实现会非常繁琐,需要每个组件都接受参数,逻辑非常混乱。

Vuex给我提供一个store模块,集中式存储管理应用的所有组件的状态,也就是组件之间共享的数据。通过下面命令安装vuex。

npm install vuex@next

安装完成后,我们在src/store 中先新建 index.ts,在下面的代码中,我们使用 createStore 来创建一个数据存储,我们称之为 store。代码逻辑如下:

import {createStore} from 'vuex'

const store = createStore({
    state(){
        return {
            count:888
        }
    },
    mutations:{
        add(state){
            state.count++
        }
    }
}
)

export default store

store 内部除了数据,还需要一个 mutation 配置去修改数据,你可以把这个 mutation 理解为数据更新的申请单,mutation 内部的函数会把 state 作为参数,我们直接操作 state.count 就可以完成数据的修改。

接下来在main.ts 文件中加载store

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import  router from './router/index'
import store from './store/index'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')

引入方式和路由模块一样

接下来看怎么使用这个store,打开pages/about.vue 文件,写如下代码:

<template>
    <h3>关于</h3>
    <div>{{count}}</div>
</template>

<script setup lang="ts">
import { useStore } from 'vuex'
import { computed } from 'vue';
const store = useStore()
const count = computed(()=>store.state.count)
</script>

从 vuex 导入 useStore 函数,然后调用 store = useStore(),接着定义一个count 响应变量,通过computed 更新它的值。浏览器打开http://127.0.0.1:5173/#/about ,页面效果如下。

image.png

页面展示了初始化数据888,接下来我们在首页页面增加一个按钮,通过点击按钮实现自增。

打开pages/home.vue 文件,写如下代码:

<template>
    <h3>我是首页</h3>
    <p><button @click="action">count++</button></p>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const action = ()=>{
    store.commit("add")
}
</script>

增加一个按钮,通过点击按钮,实现count++,定义了action 函数,在函数内部通过store.commit("add") ,调用store 的add 方法实现自增。在action函数中并没有直接操作store.state.count++ ,因为这样是不允许,虽然这么些代码不会报错,store存储的值,不能直接修改,要通过store 方法修改。

我们看下效果,打开http://127.0.0.1:5173/#/ 页面。

image.png

然后点击count++ 按钮,然后再切换到关于页面,看下效果。

image.png

页面上数字已经改变了,我们在首页点击按钮,然后再关于页面展示数字,实现了全局存储,组件之间访问。

接下来,我实现一个功能,在页面增加一个数字,数字的结果卫count*2,并且同步更新。

打开src/store/index.ts ,写入以下代码:

import {createStore} from 'vuex'

const store = createStore({
    state(){
        return {
            count:888
        }
    },
    getters:{
         double(state){
             return state.count*2 
            } 
    },
    mutations:{
        add(state){
            state.count++
        }
    }
}
)

export default store

在store定义了 在 getters 对象中定义了 double ,double 方法返回 state.count*2 ,接下来看怎么使用 double函数。

打开pages/about.vue 文件,写如下代码:

<template>
    <h3>关于</h3>
    <div>{{count}}*2={{dobule}}</div>
</template>

<script setup lang="ts">
import { useStore } from 'vuex'
import { computed } from 'vue';
const store = useStore()
const count = computed(()=>store.state.count)
const dobule = computed(()=>store.getters.double)
</script>

定义了变量 dobule, 值通过计数属性调用 store.getters.double 函数。

打开 http://127.0.0.1:5173/#/about 看下效果

image.png

double的值随着count在变化。

接下来,我在实现一个功能,在about,页面增加一个按钮,点击按钮之后,延迟一秒实现count+1.

打开src/store/index.ts ,写入以下代码:

import {createStore} from 'vuex'

const store = createStore({
    state(){
        return {
            count:888
        }
    },
    getters:{
         double(state){
             return state.count*2 
            } ,
    },
    actions:{ 
        asyncAdd({commit}){ 
            setTimeout(()=>{ commit('add') },1000) 
        } 
    },
    mutations:{
        add(state){
            state.count++
        }
    }
}
)

export default store

在actions 对象中添加了 asyncAdd 方法,参数为,commit ,然后使用 setTimeout 函数每隔 1秒执行commit("add"),实际上是在调用 mutations的add 方法。

接着我们在about 页面增加一个按钮调用这个函数。 打开pages/about.vue 文件,写如下代码:

<template>
    <h3>关于</h3>
    <div>{{count}}*2={{dobule}}</div>
    <p><button @click="add">add</button></p>
</template>

<script setup lang="ts">
import { useStore } from 'vuex'
import { computed } from 'vue';
const store = useStore()
const count = computed(()=>store.state.count)
const dobule = computed(()=>store.getters.double)

const add = ()=>{
    store.dispatch("asyncAdd")
}
</script>

增加了 add,方法体里面 store.dispatch("asyncAdd") 调用 store 中的asyncAdd。

可以看下效果 http://127.0.0.1:5173/#/about 页面,点击add 按钮,延迟一秒之后 实现了加1. image.png

总结一下 mutations 写同步的方法,actions 写异步的方法,比如请求服务端接口。

项目中如果所有页面的数据到包保存到一个store里面,那维护起来会非常麻烦,而且可以可能出现命名冲突,那么可以为每个功能逻辑设计自己的store,下面我讲下store的命名空间。

首先在pages页面下创建一个 store.ts 文件,内容如下:

import {createStore} from 'vuex'

const Counter = {
    namespaced:true,
    state(){
        return {
            count:666
        }
    },
    getters:{
         double(state){
             return state.count*2 
            } 
    },

    actions:{ 
        asyncAdd(content){ 
            setTimeout(()=>{ content.commit('add') },1000) 
        },
    },
    mutations:{
        add(state){
            state.count++
        },
    }
}

export default Counter

namespaced:true 开启命名空间,count 初始值为666

接着,在store/index.ts 文件中加入以下代码:

import {createStore} from 'vuex'
import counter from '../pages/store'

const store = createStore({
    state(){
        return {
            count:888
        }
    },
    getters:{
         double(state){
             return state.count*2 
            } ,
    },

    actions:{ 
        asyncAdd({commit}){ 
            setTimeout(()=>{ commit('add') },1000) 
        },
    },
    mutations:{
        add(state){
            state.count++
        },
    },
    modules:{
        a:counter
    }
}
)

export default store

导入 import counter from '../pages/store',文件中增加 modules:{ a:counter }

打开pages/about.vue 文件,修改代码调用子模块代码

<template>
    <h3>关于</h3>
    <div>{{count}}*2={{dobule}}</div>
    <p><button @click="add">add</button></p>
</template>

<script setup lang="ts">
import { useStore } from 'vuex'
import { computed } from 'vue';
const store = useStore()
const count = computed(()=>store.state.a.count)
const dobule = computed(()=>store.getters["a/double"])

const add = ()=>{
    store.dispatch("a/asyncAdd")
}


</script>

访问store.state 子模块count 这样 store.state.a.count,访问方法需要 "a/asyncAdd" ,路径上加上a,下面我们看下效果,打开 http://127.0.0.1:5173/#/about 页面。

image.png 初始的值为 666,点击了add 按钮之后值变成667,说明代码没有问题。

七、请求后端接口

业务场景中,我们经常需要调用后端服务,然后拿到后端数据在渲染页面,接下来,我们写一个这样的例子。 我们在about页面增加一个按钮init,从服务器端获取,count初始值。

首先按照,进入项目目录, 安装 axios 插件

 npm install axios

创建utils 目录,在目录中新建reques.ts 文件,文件内容如下:

import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'

const request = axios.create({
    baseURL: "http://127.0.0.1:8888",
   
});

export default function (config: AxiosRequestConfig) {
    return request(config).then((response: AxiosResponse) => response.data)
}

引入axio,创建request 对象设置 baseURL 为 http://127.0.0.1:8888, 并且最后导出默认函数

创建api目录,在目录下创建index.ts 文件内容如下:

import request from '../utils/request';

export async function data(): Promise<any> {
    const data = {}
    return request({
        url: `/count`,
        method: 'POST',
        data,
    });
}

引入 request 模块,并且创建 data 方法,并且导出。

打开src/store/index.ts,写入以下代码:

import {createStore} from 'vuex'
import { data } from '../api/index'

const store = createStore({
    state(){
        return {
            count:888
        }
    },
    getters:{
         double(state){
             return state.count*2 
            } ,
    },
    actions:{ 
        asyncAdd({commit}){ 
            setTimeout(()=>{ commit('add') },1000) 
        },
        async initCount({commit}){
            let res = await data()
            commit('initCount',res) 
        } 
    },
    mutations:{
        add(state){
            state.count++
        },
        initCount(state,data){
            state.count = data
        }
    }
}
)

export default store

在 actions 中创建 initCount 方法,并且调用 mutations 中 initCount 方法,初始化值为 state.count 为后端接口返回的值。

接下来在about 页面创建 一个按钮,并且调用 initCount 方法。 代码如下:

<template>
    <h3>关于</h3>
    <div>{{count}}*2={{dobule}}</div>
    <p><button @click="init">init</button><button @click="add">add</button></p>
</template>

<script setup lang="ts">
import { useStore } from 'vuex'
import { computed } from 'vue';
const store = useStore()
const count = computed(()=>store.state.count)
const dobule = computed(()=>store.getters.double)

const add = ()=>{
    store.dispatch("asyncAdd")
}

const init = ()=>{
    store.dispatch("initCount")
}
</script>

代码不用解释了,在上面讲过。接下来在启动一个服务端程序,我的是golang 写,实际过程中大家可以随意,主要是实现 http://127.0.0.1:8888/count 接口,返回值1, 代码如下:

package main

import (
   "fmt"
   "log"
   "net/http"
)

func main() {
   http.HandleFunc("/count", doRequest)     //   设置访问路由
   err := http.ListenAndServe(":8888", nil) //设置监听的端口
   if err != nil {
      log.Fatal("ListenAndServe: ", err)
   }
   fmt.Println("ListenAndServe:8000")

}

func doRequest(w http.ResponseWriter, r *http.Request) {
   fmt.Println("++++++++++")
   //r.ParseForm()
   //w.Header().Set("Content-Type", "application/json")
   w.Header().Set("Access-Control-Allow-Origin", "*") //允许访问所有域
   // 必须,设置服务器支持的所有跨域请求的方法
   w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
   // 服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
   w.Header().Set("Access-Control-Allow-Headers", "content-type")
   // 可选,设置XMLHttpRequest的响应对象能拿到的额外字段
   //w.Header().Set("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
   // 可选,是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
   //w.Header().Set("Access-Control-Allow-Credentials", "true")
   //io.WriteString(w, "1")
   w.Write([]byte("1"))
}

执行 go run main.go 启动程序。

八、使用antd开发应用。

学习了上面vue的基本知识之后,下面我们学习适应antd 组件库。下面链接地址是vue antd组件库的官网。 表单 Form - Ant Design Vue (antdv.com)

下图是form 表达组件

image.png

在最下面有四个按钮

image.png

分别是调试、切换TS 和 JS 语法,复制代码,展开代码,下面我们复制代码

<template>
  <a-form :model="formState" :label-col="labelCol" :wrapper-col="wrapperCol">
    <a-form-item label="Activity name">
      <a-input v-model:value="formState.name" />
    </a-form-item>
    <a-form-item label="Instant delivery">
      <a-switch v-model:checked="formState.delivery" />
    </a-form-item>
    <a-form-item label="Activity type">
      <a-checkbox-group v-model:value="formState.type">
        <a-checkbox value="1" name="type">Online</a-checkbox>
        <a-checkbox value="2" name="type">Promotion</a-checkbox>
        <a-checkbox value="3" name="type">Offline</a-checkbox>
      </a-checkbox-group>
    </a-form-item>
    <a-form-item label="Resources">
      <a-radio-group v-model:value="formState.resource">
        <a-radio value="1">Sponsor</a-radio>
        <a-radio value="2">Venue</a-radio>
      </a-radio-group>
    </a-form-item>
    <a-form-item label="Activity form">
      <a-input v-model:value="formState.desc" type="textarea" />
    </a-form-item>
    <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
      <a-button type="primary" @click="onSubmit">Create</a-button>
      <a-button style="margin-left: 10px">Cancel</a-button>
    </a-form-item>
  </a-form>
</template>
<script lang="ts">
import { defineComponent, reactive, toRaw } from 'vue';
import type { UnwrapRef } from 'vue';

interface FormState {
  name: string;
  delivery: boolean;
  type: string[];
  resource: string;
  desc: string;
}
export default defineComponent({
  setup() {
    const formState: UnwrapRef<FormState> = reactive({
      name: '',
      delivery: false,
      type: [],
      resource: '',
      desc: '',
    });
    const onSubmit = () => {
      console.log('submit!', toRaw(formState));
    };
    return {
      labelCol: { style: { width: '150px' } },
      wrapperCol: { span: 14 },
      formState,
      onSubmit,
    };
  },
});
</script>

antd组件库代码都是以a 开头,对应每个组件都有自己的api ,点击右侧栏API,可以看到具体api的用法,对应上面组件对应的属性,都有详细的解释,大家可以看下。

image.png

安装 ant-design-vue 插件

npm i --save ant-design-vue

打开main.ts 文件引入antd ,代码如下

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import  router from './router/index'
import store from './store/index'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const app = createApp(App)
app.use(router)
app.use(store)
app.use(Antd)
app.mount('#app')

代码不用过多解释,上面讲过

下面打开App.vue 文件直接复制上面:

<template>
  <a-form :model="formState" :label-col="labelCol" :wrapper-col="wrapperCol">
    <a-form-item label="Activity name">
      <a-input v-model:value="formState.name" />
    </a-form-item>
    <a-form-item label="Instant delivery">
      <a-switch v-model:checked="formState.delivery" />
    </a-form-item>
    <a-form-item label="Activity type">
      <a-checkbox-group v-model:value="formState.type">
        <a-checkbox value="1" name="type">Online</a-checkbox>
        <a-checkbox value="2" name="type">Promotion</a-checkbox>
        <a-checkbox value="3" name="type">Offline</a-checkbox>
      </a-checkbox-group>
    </a-form-item>
    <a-form-item label="Resources">
      <a-radio-group v-model:value="formState.resource">
        <a-radio value="1">Sponsor</a-radio>
        <a-radio value="2">Venue</a-radio>
      </a-radio-group>
    </a-form-item>
    <a-form-item label="Activity form">
      <a-input v-model:value="formState.desc" type="textarea" />
    </a-form-item>
    <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
      <a-button type="primary" @click="onSubmit">Create</a-button>
      <a-button style="margin-left: 10px">Cancel</a-button>
    </a-form-item>
  </a-form>
</template>
<script lang="ts">
import { defineComponent, reactive, toRaw } from 'vue';
import type { UnwrapRef } from 'vue';

interface FormState {
  name: string;
  delivery: boolean;
  type: string[];
  resource: string;
  desc: string;
}
export default defineComponent({
  setup() {
    const formState: UnwrapRef<FormState> = reactive({
      name: '',
      delivery: false,
      type: [],
      resource: '',
      desc: '',
    });
    const onSubmit = () => {
      console.log('submit!', toRaw(formState));
    };
    return {
      labelCol: { style: { width: '150px' } },
      wrapperCol: { span: 14 },
      formState,
      onSubmit,
    };
  },
});
</script>

访问 http://127.0.0.1:5173/#/ ,可以看到效果

image.png

在实际项目中根据实际需要,复制上述代码模块,即可。学习组件库,需要熟悉每个组件的api功能,我们不需要记住每个组件的用法,知道有这个组件,然后先查文档即可。