基于naiveUI 实现一个form+table的效果,附源码
技术栈:vue3+naive-ui
基于项目的需求,需要实现如下效果:
组件库并没有直接提供该效果的实现,所以去网上找了一下,也没有,就自己实现了一下,分享给大家,那我们开始吧。
基础的表单校验是这样的效果:
它的校验规则是这样写的:
{
name: {
required: true,
trigger: ['blur', 'input'],
message: '请输入姓名'
},
age: {
required: true,
trigger: ['blur', 'input'],
message: '请输入年龄'
}
}
每一个输入框对应一个校验规则的 key,就是每一条数据录入都需要对应一条校验规则。 而我们的table的数据是一个数组对象。
const data = [
{
age: null,
describe: "",
graduate: 1,
name: "",
school:undefined
}
]
我需要把表格的每一个单元格对应的数据也对应到相关的校验对象上,这样数据输入的时候才能正确的触发对应的校验。
一个输入框-->对应一个值-->对应一个校验对象
那应该怎么把他们串起来呢?
实际上表格的表头配置已经告诉了我们答案,表格的表头会设置 这一列用取哪个字段,而第几行就取第一个数组的。 它通过 行+列 来定位 对应的单元格和数据。
第一步:我们定义表格的数据,比如这样:
const data = [
{
age:0,
name:'dws'
},
{
age:0,
name:'dim'
}
]
第二步:建立对应的map数据 ,比如这样:
这个 map 数据对象 ,可以帮我们快速的找到对应的值,以及我们需要的校验对象的值
通过这个函数
//column.title + index
export const getKeyIndex=(key,index)=>{
return key+"_"+index
}
export const disposeArrayToMap = (array: any, keys: Array<string>,idKey?:string) => {
const map = new Map();
//把数组的数据转换成Map
array.forEach((item:any, index:number) => {
keys.forEach(key => {
const key_index = getKeyIndex(key, index)
map.set(key_index, item[key])
})
})
return map;
}
我们得到以下的map数据
const map = {
age_0:dws,
name_0:dws,
age_1:dim,
name_1:dim
}
第三步:根据map ,建立对应的校验对象
首先我们定义需要校验的对象
我们可以设计一个闭包的函数,让它返回对应的校验对象,在这个校验对象的函数里面保存它对应的key和map,用于校验指定的数据
const age = (map:Map<string,string>, key:string) => {
return {
required: true,
trigger: ["blur", "input"],
type: "string",
validator: async () => {
const value = map.get(key)
const {add, validate} = useValidateUtils();
add({rule: "isEmpty", errMsg: "请输入年龄"});
return await validate(value);
},
}
}
const name = (map:Map<string,string>, key:string) => {
return {
required: true,
trigger: ["blur", "input"],
type: "string",
validator: async () => {
const value = map.get(key)
const {add, validate} = useValidateUtils();
add({rule: "isEmpty", errMsg: "请输入姓名"});
return await validate(value);
},
}
}
const formObj: any = {
age,
name,
}
通过这个函数 我们把map对应的key 对应的校验对象生产出来
/**
*
* 通过遍历map的key值 拿到数组拼接以后存储到map中的key 也就是 key+"_"+index
* 把key 对应的校验规则函数找到 利用闭包的特性,把key_index传入进去 ,在触发表单校验的时候,可以通过key_index
* 从 Map 中拿到对应的输入框的值 就可以正确的进行数据校验了
* @param map
*/
export const getFormRules = (map:Map<string,string>)=>{
const formRules:FormRules = {}
for(const [key_index,value] of map){
const splitKey = key_index.split('_')[0]
const formItem = formObj[splitKey]
if(formItem){
formRules[key_index] = formItem(map,key_index)
}
}
return formRules
}
最后得到我们的校验对象
const map = {
age_0: {...},
name_0:{...},
age_1: {...},
name_1:{...},
}
好了 ,现在核心的逻辑已经完成了。
剩下我们看看 输入框应该如何实现呢?
我们在数据输入的时候 同步把数据存储到table 对应的数组中以及我们传下来的map中。 让data和map的数据保持同步,这样当我们删除data中的数据的时候,我们只需要直接把data直接重新生成map就行。 不需要去进行其他的操作。
/**
* input 输入框
*/
import {NFormItem,NInput} from "naive-ui";
import {getKeyIndex} from "@utils";
export default function inputView({rowData, index, element, map}) {
const key_index = getKeyIndex(element.key,index)
const onUpdateValue = (value:string)=>{
rowData[element.key] = value
map?.set(key_index,value)
}
const value = map?.get(key_index)
return (
<NFormItem path={key_index} >
<NInput id={key_index} value={value} onUpdateValue={onUpdateValue} placeholder="请输入" />
</NFormItem>
)
}
form+table的view
<template>
<div class="table_container">
<div class="flex_row">
<NSpace>
<NButton @click="add" >新增</NButton>
<NButton @click="validate" type="info">保存</NButton>
</NSpace>
</div>
<NConfigProvider :theme-overrides="config">
<NForm
ref="formRef"
:model="formValue"
:rules="formRules"
label-placement="left"
>
<STable
:columns="columns"
:data="dataRef"
:map="dataMapRef"
/>
</NForm>
</NConfigProvider>
</div>
</template>
表格的封装 是基于策略模式的表格封装
<script lang="tsx" setup>
import { Config, tableColumn} from './types'
type TProps = {
columns: Array<tableColumn>,
searchValue?: string,
map?: Map<string, string>,
}
const props = defineProps<TProps>()
const getDisposeColumns = () => {
return props.columns.map((element: tableColumn) => {
if (!element.render) {
element.render = (rowData: any, index: number) => {
if (element.renderType) {
return Config[element.renderType]({ map: props.map, rowData, index, element, searchValue: props.searchValue })
}
return Config.defaultView({ rowData, index, element, searchValue: props.searchValue })
}
}
return element;
});
}
</script>
<template>
<NDataTable
:columns="getDisposeColumns()"
:single-line="true"
>
</NDataTable>
</template>
最后完成了,我们看效果吧!
转载自:https://juejin.cn/post/7369113568573423628