# 嘿~我是可达鸭😘
# 前言
学完
Vue3
和TypeScript
后就做了这个后台管理系统项目,马上就要做好面试的准备了,所以没自己写接口,随便找了个接口做了这个项目,这个接口其实也挺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 事件了,同时传入该select
的field
字段,但是后面发现不可以,它的change
事件只能绑定一个无参数函数,传参就拿不到它选中的值了,所以没法解决思路:
封装的
form
组件是用不了了,只能重新写一个对话框组件,谁让那个change
事件绑定的函数不能传参呢哎
# 项目收获总结
# ①网络请求
介绍:
在网络请求方面,我将它独立封装到了一个单独的文件夹,最后再导出使用,这里我还运用了一个网络请求的封装技巧,就是采用类封装的方式来封装他,在使用网络请求时需使用这个类的实例对象来调用网络请求,这样的好处是在我们要去
new
这个网络请求类的实例对象时我们可以传入这个实例对象私有的一些参数,以请求拦截器为例,然后在类的构造器中就会对我们传入的请求拦截器进行保存并使用,变成了new
出来的这个实例对象独有的,之后我们通过这个实例去调用网络请求时,就会触发这个请求拦截器,但是如果我们去new
一个没有传入请求拦截器的实例,那么在使用它去调用网络请求时,就不会触发请求拦截器类封装实现思路:
定义一个网络请求类
netRequest
, 里面有两个属性,一个属性是实例instance
(这个用来存axios
的实例对象),另一个属性 interceptors(这个用来存我们传入的私有拦截器,这个属性可选,所以用?)构造器
constructor
需要有三个步骤第一步:用
axios.create(config)
创建一个实例化对象赋值给 instance 属性,然后将config.interceptors
赋值给 interceptors 属性,这一步做的是存值操作;第二步:给
instance
使用私用的拦截拦截,这里用?.
因为不一定有传然后下面定义一个
request
方法,返回值为promise
,因为我们要把接收到的返回值传出去给页面拿到,而不是直接在这里就拿到了,这个方法的内容就是调用instance
的request
发送请求,但是因为我们每次发送都要调用这个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= $event
,v-model
在事件上的使用本质是:modelValue=变量a
和@input=变量a= $event.target.value
), 所以我们父组件可以用v-model
把值传给Form
组件,然后Form
组件里面接收到ModelValue
后我们不能用 v-model 的语法糖了,而是要拆开写,因为我们拿到的返回值不能直接赋值了,而是要将它发出去,将它的各个Field
字段传入输入框组件,然后监听它的update:modelValue
,拿到它的$event
(改变后的值),将它 emit 出去给父组件,这样就实现了跨组件双向绑定啦
# ⑤Table 表格封装
介绍:
表格部分比表单复用还严重,基本处处都用到,所以这里对表格部分也是进行了封装,思想其实跟表单的封装一样的,传配置项什么都是一样的,主要这里有一个难点,就是如果我们的数据不想让它正常展示,我们需要把数据拿到手,然后将它做出一下改变,比如放在
el-button
里面再通过插槽传进去,这种该怎么做,或者我们自己传出个编辑删除按钮部分通过插槽传进去该怎么做实现思路:
类似于索引列全选列的,我们可以通过传
showSelectColumn:true
或showIndexColumn: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 发出去即可,然后主页面要用的时候监听就好了