技术栈
- vite2
- vue 3.0.5
- vue-router 4.0.6
- vue-data-state 0.1.1
- element-plus 1.0.2-beta.39
前情回顾
前面介绍的表单控件和查询控件,都是原子性的,实现自己的功能即可。而这里要介绍的是管理后台里面的各个组件之间的状态关系。
为啥需要状态?因为组件划分的非常原子化(细腻),所以造成了很多的组件,那么组件之间就需要一种“通讯方式”,这个就是状态了。不仅仅是传递数据,还可以实现事件总线。
页面结构
一般的后台管理大体是这样的结构:
后台页面结构.png
具体项目里页面结构会有一些变化,但是总体结构不会有太大的改变。
做出来的效果大体是这样的:
一种后台管理的效果
- 动态菜单 根据用户权限加载需要的菜单。
- 动态 tab 点击一下左面的菜单,创建一个新的tab,然后加载对应的组件,一般是列表页面(组件),也可以是其他页面(组件)。
- 查询 各种查询条件那是必备的,总不能没有查询功能吧,查询控件需要提供查询条件。
- 操作按钮组 里面可以有常见的添加、修改、删除、查看按钮,也可以有自定义的其他按钮。可以“弹窗”也可以直接调用后端API。
- 列表 显示客户需要的数据,看起来简单,但是要和查询、翻页、添加、修改、删除等功能配合。
- 分页 这是和列表最接近的一个需求,因为数据有可能很大,不能一次性都显示出来,那么就需要分页处理,所以分页控件和列表控件就是天然CP。
- 表单(添加、修改) 数据提交之后,为了便于确认数据添加成功,是不是需要通知列表去更新数据呢?总不能填完数据,列表一点变化都没有吧。
- 删除 数据删掉了,不管是物理删除还是逻辑删除,列表里面都不需要再显示出来了。也就是说删除后要通知列表更新数据。
总之,各个组件直接需要统筹一下状态关系。
视频演示
我们来看一下实际效果。
设计状态
我们整理一下需求,用脑图表达出来:
1使用“轻量级状态管理”定义状态:
/store-ds/index.js
import VuexDataState from'vue-data-state' exportdefault VuexDataState.createStore({ global: { // 全局状态 userOnline: { name: 'jyk'// } }, local: { // 局部状态 dataListState () { // 获取列表数据的状态 dataPagerState return { query: {}, // 查询条件 pager: { // 分页参数 pageTotal: 100, // 0:需要统计总数;其他:不需要统计总数 pageSize: 5, // 一页记录数 pageIndex: 1, // 第几页的数据,从 1 开始 orderBy: { id: false } // 排序字段 }, choice: { // 列表里面选择的记录 dataId: '', // 单选,便于修改和删除 dataIds: [], // 多选,便于批量删除 row: {}, // 选择的记录数据,仅限于列表里面的。 rows: [] // 选择的记录数据,仅限于列表里面的。 }, hotkey: () => {}, // 处理快捷键的事件,用于操作按钮 reloadFirstPager: () => {}, // 重新加载第一页,统计总数(添加后) reloadCurrentPager: () => {}, // 重新加载当前页,不统计总数(修改后) reloadPager: () => {} // 重新加载当前页,统计总数(删除后) } } }, init (state) { } })
这里没有使用 Vuex,因为我觉得 Vuex 有点臃肿,还是自己做的清爽。另外,状态里面除了数据之外,还可以有方法(事件总线)。
组件里面使用轻量级状态的方法
// 引入状态 import VueDS from'vue-data-state' // 访问状态 const { reg, get } = VueDS.useStore() // 父组件注册列表的状态 const state = reg.dataListState() // 子组件里面获取父组件注册的状态 const dataListState = get.dataListState()
先引入状态,然后在父组件注册(也就是注入)状态,然后在子组件就可以获取状态。函数名就是 /store-ds/index.js 里面定义的名称。
然后我们还可以仿照 MVC 的 Controllar ,做一个控制类,当然也可以叫做管理类。叫什么不是重点,重点是实现了什么功能。
列表的管理类
我们可以为列表的状态写一个状态的管理类。这个类是在单独的 js 文件里面,并不需要像 Vuex 那样去设置 action 或者 module。
/control/data-list.js
import { watch, reactive } from'vue' // 状态 import VueDS from'vue-data-state' // 仿后端API import service from'../api/dataList-service.js' /** * * 数据列表的通用管理类 * * 注册列表的状态 * * 关联获取数据的方式 * * 设置快捷键 * @param {string} modeluId 模块ID * @returns 列表状态管理类 */ exportdefaultfunction dataListControl (modeluId) { // 显示数据列表的数组 const dataList = reactive([]) // 模拟后端API const { loadDataList } = service() // 访问状态 const { reg, get } = VueDS.useStore() // 子组件里面获取父组件注册的状态 const dataListState = get.dataListState() // 数据加载中 let isLoading = false /** * 父组件注册状态 * @returns 注册列表状态 */ const regDataListState = () => { // 注册列表的状态,用于分页、查询、添加、修改、删除等 const state = reg.dataListState() // 重新加载第一页,统计总数(添加、查询后) state.reloadFirstPager = () => { isLoading = true state.pager.pageIndex = 1// 显示第一页 // 获取数据 loadDataList(modeluId, state.pager, state.query, true).then((data) => { state.pager.pageTotal = data.count dataList.length = 0 dataList.push(...data.list) isLoading = false }) } // 先执行一下,获取初始数据 state.reloadFirstPager() // 重新加载当前页,不统计总数(修改后) state.reloadCurrentPager = () => { // 获取数据 loadDataList(modeluId, state.pager, state.query).then((data) => { dataList.length = 0 dataList.push(...data) }) } // 重新加载当前页,统计总数(删除后) state.reloadPager = () => { // 获取数据 loadDataList(modeluId, state.pager, state.query, true).then((data) => { state.pager.pageTotal = data.count dataList.length = 0 dataList.push(...data.list) }) } // 监听,用于翻页控件的翻页。翻页,获取指定页号的数据 watch(() => state.pager.pageIndex, () => { // 避免重复加载 if (isLoading) { // 不获取数据 return } // 获取数据 loadDataList(modeluId, state.pager, state.query).then((data) => { dataList.length = 0 dataList.push(...data) }) }) return state } return { setHotkey, // 设置快捷键,(后面介绍) regDataListState, // 父组件注册状态 dataList, // 父组件获得列表 dataListState // 子组件获得状态 } }
管理类的功能:
- 父组件注册状态
- 子组件获取状态
- 定义列表数据的容器
- 各种监听
- 事件总线
1父组件注册状态因为使用的是局部的状态,并不是全局状态,所以在需要使用的时候,首先需要在父组件里面注册一下。看起来似乎没有全局状态简单,但是可以更好的实现复用,更轻松的区分数据,兄弟组件的状态不会混淆。2子组件获取状态因为或者状态必须在vue的直接函数内才行,所以才需要先把状态获取出来,而不能等到触发事件了再获取。3定义列表数据的容器列表数据并没有在状态里面定义,而是在管理类里面定义的,因为主要列表组件才需要这个列表数据,其他的组件并不关心列表数据。4监听:
- 监听页号的变化,依据当前的查询条件获取新的记录,用于翻页,不用重新统计总数。
5事件:
- 统计总数并且翻到第一页,用于查询条件变化,添加新记录。
- 重新获取当前页号的列表数据,用于修改数据后的更新。
- 重新获取当前页号的列表数据,并且统计总记录数,用于删除数据后的更新。
6是否重新统计总数可能你会发现上面获取数据里面有一个明显的区别,那就是是否需要统计总数。在数据量非常大的情况下,如果每次翻页都重新统计总数,那么会严重影响性能!其实仔细考虑一下,一些情况是不用重新统计总数的,比如翻页、修改后的更新等,这些操作都不会影响总记录数(不考虑并发操作),那么我们也就不必每次都重新统计。