# 嘿~我是可达鸭😘

# 前言

学完 Vue3TypeScript 后就做了这个后台管理系统项目,马上就要做好面试的准备了,所以没自己写接口,随便找了个接口做了这个项目,这个接口其实也挺 lj 的,又不全又有 bug,要不是时间不够真想自己写个一个😠在这个项目其实学到挺多的并应用了起来,主要是 封装 技巧和 TypeScript 技巧,因为在一个月前自己独立做的那个项目中,我做了其实挺久,但是做完感觉项目并不难,但是代码冗余太多了,所以在这次项目,我觉得认真考虑一下让代码更加简洁,所以在这次项目中,已经尽我所能去封装了,我觉得能封装的地方我都采用了封装的方式,一些比较公共的方法,我都尽量抽成 hook ,总体来说应该还行,收获满满~

项目源代码地址:vue3-ts-shop: 一个 Vue3&TypeScript&Element plus 的后台管理系统项目

# 项目遭遇难点

# 难点①

1. 在传一个自定义规则时不知道怎么传

介绍:

在封装完 Form 表单后,我突然在官方 Form 表单里面看到了一个自定义校验规则是校验确认密码与密码要保持一致的,它是直接拿到在 FormData 数据进行判断与 value 判断的,但当时我由于封装成了 Form 组件,我还想了好久怎么通过在配置文件来定义这个自定义规则,但是发现不太行,因为我在配置文件里面,我拿不到 FormData 数据,我一开始还纳闷为什么拿不到,后来想明白了,我的 FormData 的数据是通过我的配置项中的 Field 字段来定义的,而我直接在配置项文件里面取显然不行,所以我换了别的方法来实现这种需求

解决思路:

一般这种自定义规则都是唯一的,所以我不写在定义了 FormData 的那个封装后的组件里,那样就有点写死了,所以我写在了主页面组件里面,虽然我们这时传进去的配置项是没有我们这个自定义规则的,但是我们在点击编辑按钮,让对话框显示之前,修改导入这个组件的配置项,给它加这个自定义规则,因为在主页面这个组件里面是可以通过 ref 拿到 FormData 数据的

2. 没添加成功...

介绍:

后来发现它没反应,是我想法错了,我想了半年都感觉没错,后来通过问群里面的大佬,才点醒了我... 是一个很基本的错误,我怎么没想到呢..... 因为我传的配置数据是个死数据,你改它了,它传的还是死数据,而且 setup 只会执行一次,后面就不会执行了

解决思路:

后面大佬教了我一个技巧,将配置项数据用一个 computed 包裹,在里面修改,然后返回修改后的配置项,上面传的也是这个用 computed 包裹的配置项数据,我发现这样做后竟然成功了,其实想想也确实应该成功,因为计算属性会在你值改变的时候调用,虽然用监听也可以,但是这里用计算属性更加贴切, get 到一个小技巧了,动态数据用 computed 包裹准不会错哈哈,就算静态也可以这样做,万无一失

# 难点②

1. 在新建菜单的对话框表单不知道该怎么设计

介绍:

因为表单是树形的,它有三个级别的菜单,二级和三级需要选择上级菜单,三级菜单需要填按钮权限而不需要 Url ,一二级菜单需要 Url 而不需要按钮权限,这就需要在选择菜单级别的时候,动态的显示哪些输入框了,但是如果我选择了那些需要选择上级菜单级别的菜单,那上级菜单的数据就不应该写死的,它应该在选择后获取,而这时我就想到要给 select 绑定它的 change 事件了,同时传入该 selectfield 字段,但是后面发现不可以,它的 change 事件只能绑定一个无参数函数,传参就拿不到它选中的值了,所以没法

解决思路:

封装的 form 组件是用不了了,只能重新写一个对话框组件,谁让那个 change 事件绑定的函数不能传参呢哎

# 项目收获总结

# ①网络请求

介绍:

在网络请求方面,我将它独立封装到了一个单独的文件夹,最后再导出使用,这里我还运用了一个网络请求的封装技巧,就是采用类封装的方式来封装他,在使用网络请求时需使用这个类的实例对象来调用网络请求,这样的好处是在我们要去 new 这个网络请求类的实例对象时我们可以传入这个实例对象私有的一些参数,以请求拦截器为例,然后在类的构造器中就会对我们传入的请求拦截器进行保存并使用,变成了 new 出来的这个实例对象独有的,之后我们通过这个实例去调用网络请求时,就会触发这个请求拦截器,但是如果我们去 new 一个没有传入请求拦截器的实例,那么在使用它去调用网络请求时,就不会触发请求拦截器

类封装实现思路:

定义一个网络请求类 netRequest , 里面有两个属性,一个属性是实例 instance (这个用来存 axios 的实例对象),另一个属性 interceptors(这个用来存我们传入的私有拦截器,这个属性可选,所以用?)

构造器 constructor 需要有三个步骤

第一步:用 axios.create(config) 创建一个实例化对象赋值给 instance 属性,然后将 config.interceptors 赋值给 interceptors 属性,这一步做的是存值操作;

第二步:给 instance 使用私用的拦截拦截,这里用 ?. 因为不一定有传

然后下面定义一个 request 方法,返回值为 promise ,因为我们要把接收到的返回值传出去给页面拿到,而不是直接在这里就拿到了,这个方法的内容就是调用 instancerequest 发送请求,但是因为我们每次发送都要调用这个 request ,然后在配置项里面要传入是 method 来区分是什么请求,所以我们可以在下面定义 get、post 等方法内容为调用这个的 request 方法,传入我们调用的时候传入的配置项外加一个 {method:get} 等这样的形式,之后调用网络请求,只需要利用我们这个类的实例对象去调用 get 方法,post 方法传入配置项就可以了

# ②Vuex

介绍:

正常在组件调用 vuex 需要从 vuex 中导入 useStore 函数并调用获得 store 实例才能取到 vuex 的数据,但是这样取到的 store 实例是 any 类型的,意味着随便取值编译都不会报错,所以这里就用一个技巧给它类型限制,自己写一个 useStore 函数,这个函数的作用就是调用从 vuex 中导入 useStore 函数,所以自己定义的这个 useStore 函数调用后也会得到 store 实例,不过我们可以在返回的时候给它定义返回值接口类型限制

实现思路:

首先要拿到根模块的类型接口 IRootState , 然后定义一个子模块类型接口 IRootWithModule ,比如有子模块 login ,那就需要拿到它对应的类型接口 ILoginState ,然后将他们合并,也就是拿到类型限制

interface IRootWithModule:{
	login: ILoginState
}
type IStoreType = {
	IRootState,
	IRootWithModule
}

做到这一步,我们就可以在 Vuex 的根模块里面定义一个 useStore 函数,这个函数就是返回值就是调用从 Vuex 导入的 useStore 函数,这时我们就可以把我们刚才定义的那个类型限制给这个返回值,因为这个返回值就是 store 对象, Vuex 内部就有个 Store 类型,它是一个泛型,我们就可以把我们刚才定义的丢进去泛型里面,之后我们在页面使用 Vuex 的时候,就直接导入我们定义的这个 useStore 函数而不去导入 Vuex 内部的,这样就实现了类型限制啦~

export function useStore(): Store<IStoreType> {
return useVuexStore()
}

# ③动态路由注册权限菜单

介绍:

在本项目中每个登录用户都有不同的操作权限,所以会显示不同的菜单列表,这里就要从后台拿到该用户有权限访问的菜单列表,然后从所有菜单列表对应的路由信息中一一筛选出进行动态路由注册

实现思路:

首先需要拿到所有的菜单数据,讲它的所有页面与路由信息写好,之后在登陆后获取用户的菜单信息,通过该信息与所有的菜单数据一一匹配,然后取出其中的路由信息进行 router.addRoute 动态路由注册就好了

# ④Form 表单封装

介绍:

在本次项目中,因为多处地方都要用到 Form 表单,如果在每个用到的地方都一个个写,会多出现代码冗余,所以进行了封装处理,由配置项决定表单的类型与样式,将 Element Plus 进行组件的二次封装

实现思路:

首先我们配置项肯定是要传一个 formItems 的,这个是表单的主要配置,建立好基本的结构后,接收到这个 formItems 然后遍历他,多少个子项就多少个 formItem ,然后子项里面可以传 label(决定 formItems 的 label),type(输入框的类型)、field(输入框的字段名,这个很重要,因为我们要传入 FormData 给我们封装的这个 Form 组件的数据就由这个遍历出来的)以此类推,然后我们还需要传入 FormData 数据进行双向绑定输入框,这里 FormData 的数据需要从配置项里面遍历出每个子项的 field 字段,他们就可以作为 FormData 数据,然后这时有个重要的知识点来了,我们把 FormData 数据传入我们封装的 Form 组件后该怎么处理,正常我们使用 el-input 组件时是直接用 v-model 的,但这里我们的 FormData 值是定义在使用 Form 组件的父组件上的,就不能用 v-model ( v-model 在组件上的使用本质是 :modelValue=变量a@update:modelValue=变量a= $eventv-model 在事件上的使用本质是 :modelValue=变量a@input=变量a= $event.target.value ), 所以我们父组件可以用 v-model 把值传给 Form 组件,然后 Form 组件里面接收到 ModelValue 后我们不能用 v-model 的语法糖了,而是要拆开写,因为我们拿到的返回值不能直接赋值了,而是要将它发出去,将它的各个 Field 字段传入输入框组件,然后监听它的 update:modelValue ,拿到它的 $event (改变后的值),将它 emit 出去给父组件,这样就实现了跨组件双向绑定啦

# ⑤Table 表格封装

介绍:

表格部分比表单复用还严重,基本处处都用到,所以这里对表格部分也是进行了封装,思想其实跟表单的封装一样的,传配置项什么都是一样的,主要这里有一个难点,就是如果我们的数据不想让它正常展示,我们需要把数据拿到手,然后将它做出一下改变,比如放在 el-button 里面再通过插槽传进去,这种该怎么做,或者我们自己传出个编辑删除按钮部分通过插槽传进去该怎么做

实现思路:

类似于索引列全选列的,我们可以通过传 showSelectColumn:trueshowIndexColumn:true 来决定它有没有,对于插槽的话,我们在遍历我们传进去的 propList 时 (跟上面的 formItems 一个道理),可以在它里面弄一个

<template #default="scope" v-if="item.slotName">
   <slot :name="item.slotName" :row="scope.row"></slot>
</template>

如果我们在 propList 里面的子项有传了 slotName , 那么我们就可以在表格组件里面传入名为 slotName 的具名插槽,而且这个具名插槽还能拿到当前那个一行的所有数据,妙不可言~

# ⑥搜索模块、表单模块、对话框模块的封装

介绍:

因为在本次项目中,多个页面都是复用了搜索模块、表单模块,对话框模块,如果每个主页面都要写一遍这些模块,就有些麻烦了,所以我们把他们基本相同的结构封装成 components,不同的在单独用不同的模块,这样就提高了代码的复用性,主页面需要用到模块的时候引入传入配置项和数据就可以了,需要用到里面的函数,直接监听我们发出来的函数就好了,不过这种封装思想在做后台是笔记常用的,因为后台管理系统大部分都一个样的,我在上一个原创项目就深有体会,后台管理系统写了一堆垃圾复用代码.... 在这次就想着复用了~

实现思路:

其实实现很简单,我们只是把那些调用 Vuex 的网络请求方法,表单数据 FormData 等都放在该页面而已,主页面如果要取,通过 ref 取就可以了,对于一些例如搜索或者编辑的函数,我们直接通过 emit 发出去即可,然后主页面要用的时候监听就好了

请我喝[茶]~( ̄▽ ̄)~*

呆头鸭 微信支付

微信支付

呆头鸭 支付宝

支付宝