设计状态
我们来梳理一下需求,并用思维导图来表达:
使用“轻量级状态管理”定义状态:
/store-ds/index.js
import VuexDataState from 'vue-data-state'
export default 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有点臃肿,所以我想自己弄点新鲜的。
另外,除了数据之外,状态还可以有方法(stormbus)。
如何在组件上使用轻量级状态
// 引入状态
import VueDS from 'vue-data-state'
// 访问状态
const { reg, get } = VueDS.useStore()
// 父组件注册列表的状态
const state = reg.dataListState()
// 子组件里面获取父组件注册的状态
const dataListState = get.dataListState()
首先引入状态,然后在父组件中注册(即注入)状态,然后在子组件中获取状态。
函数名称是上面定义的名称/store-ds/index.js。
后来我们还可以模仿MVC的Controller,做一个控制类,其实也可以称为管理类。
调用什么不是重点,重点是实现什么功能。
列表管理类
我们可以为列表的状态编写一个状态管理类。
该类位于单独的 js 文件中,不需要像 Vuex 那样设置操作或模块。
/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 列表状态管理类
*/
export default function 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 // 子组件获得状态
}
}
管理类的功能: 父组件注册状态 获取状态定义列表数据的子组件容器 各类窃听风暴总线 父组件注册状态
由于使用的是本地状态,而不是全局状态,所以当需要使用它时,首先需要向父组件注册。 看起来不像全局状态那么简单,而且可以实现更好的复用前端分页查询elementUI,更容易区分数据,兄弟组件的状态也不会混淆。
子组件获取状态
因为状态必须在vue的直接函数内,所以需要先获取状态,不能等到暴风雨触发。
定义列表数据的容器
列表数据不是定义在状态上,而是定义在管理类上,因为只有主列表组件需要这个列表数据,其他组件不关心列表数据。
窃听:Storm:是否重新统计总数
你可能会发现获取数据有一个明显的区别,那就是是否需要总计统计。
当数据量非常大时,如果每次翻页都重新计算总量,这样会严重影响性能!
虽然仔细想想,有些情况下是不需要重新统计总数的,比如翻页、修改更新等,这样的操作不会影响记录总数(不管并发操作如何),所以我们不需要每次都数它们。 叙事。
文件结构
基本功能搭建完成后,剩下的就简单了。 只需构建组件并设置模板、控件、组件和使用状态。
整体结构如下:
使用列表状态
基础工作完成后,我们来看看如何在各个组件上使用状态。
查询
首先,让我们看一下查询。 用户设置查询条件后,查询控件将查询条件计入状态。
然后调用状态管理中的reloadFirstPager来获取列表数据。
查询控件支持长焦功能。
直接使用查询控件,模板内容是不是很简单?
import { reactive } from 'vue'
// 加载json
import loadJson from './control/loadjson.js'
// 状态
import VueDS from 'vue-data-state'
// 组件
import nfElFind from '/ctrl/nf-el-find/el-find-div.vue'
// 属性:模块ID、查询条件
const props = defineProps({
moduleId: [Number, String]
})
// 设置 查询的 meta
const findProps = reactive({reload: true})
loadJson(props.moduleId, 'find', findProps)
// 访问状态
const { get } = VueDS.useStore()
// 获取状态
const listState = get.dataListState()
// 用户设置查询条件后触发
const myChange = (query) => {
// 获取第一页的数据,并且重新统计总数
listState.reloadFirstPager()
}
分页
分页非常简单。 查询条件是由查询控件决定的,所以这里只需按照el-pagination的要求将分页状态设置到el-pagination的属性即可。
直接使用状态作为属性值。
// 状态
import VueDS from 'vue-data-state'
// 访问状态
const { get } = VueDS.useStore()
// 获取分页信息
const pager = get.dataListState().pager
获取分页状态直接设置el-pagination属性即可。
翻页时,el-pagination会手动改变pager.pageIndex的值,状态管理会监听其改变,然后获取对应的列表数据。
添加、修改
添加完成后,总记录数会减少,需要重新统计总记录数,然后翻到第一页。
更改后,通常记录总数不会发生变化,因此只需重新获取当前页码的数据即可。
使用表单控件和两个按钮。
import { computed, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
// 加载json
import loadJson from './control/loadjson.js'
// 状态
import VueDS from 'vue-data-state'
// 仿后端API
import service from './api/data-service.js'
// 表单组件
import elForm from '/ctrl/nf-el-form/el-form-div.vue'
// 访问状态
const { get } = VueDS.useStore()
// 定义属性
const props = defineProps({
moduleId: [Number, String], // 模块ID
formMetaId: [Number, String], // 表单的ID
dataId: Number, // 修改或者显示的记录的ID
type: String // 类型:添加、修改、查看
})
// 模块ID + 表单ID = 自己的标志
const modFormId = computed(() => props.moduleId + props.formMetaId)
// 子组件里面获取状态
const dataListState = get.dataListState(modFormId.value)
// 表单控件的 model
const model = reactive({})
// 表单控件需要的属性
const formProps = reactive({reload:false})
// 加载需要的 json
loadJson(props.moduleId, 'form_' + props.formMetaId, formProps)
// 仿后端API
const { getData, addData, updateData } = service(modFormId.value)
// 监听记录ID的变化,加载数据便于修改
watch(() => props.dataId, (id) => {
if (props.type !== 'add') {
// 加载数据
getData( id ).then((data) => {
Object.assign(model, data[0])
formProps.reload = !formProps.reload
})
}
},
{immediate: true})
// 提交数据
const mysubmit = () => {
// 判断是添加还是修改
if (props.type === 'add'){
// 添加数据
addData(model).then(() => {
ElMessage({
type: 'success',
message: '添加数据成功!'
})
// 重新加载第一页的数据
dataListState.reloadFirstPager()
})
} else if (props.type === 'update') {
// 修改数据
updateData(model, props.dataId).then(() => {
ElMessage({
type: 'success',
message: '修改数据成功!'
})
// 重新加载当前页号的数据
dataListState.reloadCurrentPager()
})
}
}
代码稍微多了一点,基本上就是在合适的时候调用状态下重新加载数据的风暴。
删除
删除也会影响记录总数,所以需要重新统计然后刷新当前页码的列表数据。
删除的代码写在操作按钮的组件上,对应删除按钮引发的争议:
case 'delete':
dialogInfo.show = false
// 删除
ElMessageBox.confirm('此操作将删除该记录, 是否继续?', '温馨提示', {
confirmButtonText: '删除',
cancelButtonText: '后悔了',
type: 'warning'
}).then(() => {
// 后端API
const { deleteData } = service(props.moduleId + meta.formMetaId)
deleteData(dataListState.choice.dataId).then(() => {
ElMessage({
type: 'success',
message: '删除成功!'
})
dataListState.reloadPager() // 刷新列表数据
})
}).catch(() => {
ElMessage({
type: 'info',
message: '已经取消了。'
})
})
break
删除成功后,调用state的dataListState.reloadPager()刷新列表页面。
快捷键
我喜欢用快捷键来进行一些操作前端分页查询elementUI,比如翻页、添加等。