技术栈 Vue3 + ElementUI + TypeScript
简介
开发后台管理系统的过程中,会出现一些管理员配置功能的部分,比如一些开放接口权限的开通。
分析一下,“开放接口权限的开通”,会有很多的权限可以开通,那就是多选。另外,开放接口肯定会进行分类,类别唯一。按类进行多选,再加上个全选的功能。
支持多选的同时还需要分类,会有点复杂,就需要设计好数据结构,内部有具体类别的标识与选中的数据存储。
至此,需求就分析完毕了。
接下来就是实现思路:
- 获取多选项与相应的分类,构造合适的数据结构
- 页面部分渲染分类的多选项数据
- 监听类别全选、选项勾选事件
- 点击确认时合并数据
实现
数据结构
选中的选项id
checkedForm: Array<Number>
可选项list
interface optionsObj {
openApiId: Number,
apiName: String,
apiType: String
}
allApiList: Array<optionsObj>
分类list
interface typeItem {
dictLabel: String,
dictValue: String
}
typeList: Array<typeItem>
render分类list
构造分类list是最核心的点。
interface openApiType{
text: String, // 类别名称
value: String, // 类别值
subApi: Array<optionsObj>, // 该类的可选项
checkedApi: Array<Number>, // 该类已选项
allCheck: Boolean, // 该类全选状态
isIndeterminate: Boolean // 多选不确定状态
}
openApiTypeList: Array<openApiType>
//
页面布局
<el-tabs tab-position="left">
<el-tab-pane v-for="typeItem in openApiTypeList" :key="typeItem.value" :label="typeItem.text">
<el-checkbox
:indeterminate="typeItem.isIndeterminate"
v-model="typeItem.allCheck"
@change="changeOpenApiAllCheck(typeItem)"
>全选</el-checkbox
>
<el-checkbox-group v-model="typeItem.checkedApi" @change="handleCheckedApiChange(typeItem)">
<el-checkbox
v-for="(item, index) in typeItem.subApi"
:label="item.openApiId"
:key="index"
>{{ item.apiName }}</el-checkbox
>
</el-checkbox-group>
</el-tab-pane>
</el-tabs>
<br/>
<el-button type="" @click="submitChange">确认</el-button>
逻辑
import { ref, reactive } from 'vue'
const checkedForm = ref()
let openApiTypeList = reactive()
/**
* 获取选项数据与选项类别
*/
const handleOptionsData = async () => {
const checkedListRes = await getCheckedList() // 选中的选项id list
const typeListRes = await getTypeList(); // 分类list
const allApiListRes = await getAllApiList(); // 可选项list
checkedForm.value = checkedListRes.data
// 类别
openApiTypeList = reactive(typeListRes.data.map((item) => {
return {
text: item.dictLabel, // 类别名称
value: item.dictValue, // 类别值
subApi: [], // 该类的可选项
checkedApi: [], // 该类已选项
allCheck: false, // 该类全选状态
isIndeterminate: true // 多选不确定状态
}
}))
if (allApiListRes.data.length) {
allApiListRes.data.forEach((apiItem) => {
openApiTypeList.forEach((typeItem) => {
if (apiItem.apiType === Number(typeItem.value)) {
// 类别字段一致
typeItem.subApi.push(apiItem)
if (checkedForm.value.includes(apiItem.openApiId))
// 该类别的某个接口是否被选中
typeItem.checkedApi.push(apiItem.openApiId)
}
})
})
}
},
/** 该类接口全选状态变更 */
const changeOpenApiAllCheck = (typeItem) => {
typeItem.checkedApi = typeItem.allCheck ? typeItem.subApi.map((api) => api.openApiId) : []
typeItem.isIndeterminate = false
}
/** 具体接口勾选状态更改 */
const handleCheckedApiChange = (typeItem) => {
let checkedCount = typeItem.checkedApi.length
typeItem.allCheck = checkedCount === typeItem.subApi.length
typeItem.isIndeterminate = checkedCount > 0 && checkedCount < typeItem.subApi.length
}
/**
* 提交修改
*/
const submitChange = () => {
let combineApi = []
// 合并
openApiTypeList.forEach((apiType) => {
combineApi = [...combineApi, ...apiType.checkedApi]
})
checkedForm.value = combineApi // 最终所有所选的id
console.log(checkedForm.value)
}
应用
测试数据
// 选中的选项id list
const checkedListRes = { data: [11, 33, 77, 22, 99] }
// 分类list
const typeListRes = {
data: [
{ dictLabel: '分类1', dictValue: '1' },
{ dictLabel: '分类2', dictValue: '2' },
{ dictLabel: '分类3', dictValue: '3' },
{ dictLabel: '分类4', dictValue: '4' },
]
}
// 可选项list
const allApiListRes = {
data: [
{ openApiId: 11, apiName: '选项1', apiType: 1 },
{ openApiId: 22, apiName: '选项2', apiType: 1 },
{ openApiId: 33, apiName: '选项3', apiType: 2 },
{ openApiId: 44, apiName: '选项4', apiType: 3 },
{ openApiId: 55, apiName: '选项5', apiType: 1 },
{ openApiId: 66, apiName: '选项6', apiType: 1 },
{ openApiId: 77, apiName: '选项7', apiType: 3 },
{ openApiId: 88, apiName: '选项8', apiType: 3 },
{ openApiId: 99, apiName: '选项9', apiType: 1 },
{ openApiId: 100, apiName: '选项10', apiType: 2 },
{ openApiId: 101, apiName: '选项11', apiType: 4 },
]
}
效果
- 初始页面效果 分类1
- 初始页面效果 分类3
- 分类1全选后,点击确认效果