基于react-vant封装表单基本常用组件
目录
一、基础组件
1. LocationPicker(位置级联)
效果演示:
![](https://img-blog.csdnimg.cn/e8839ee3cf774f5abae017bd3148ab9e.gif#pic_center)
组件代码
import React, {useEffect, useRef, useState} from 'react';
import {Cell, Empty, Loading, Picker, Popup} from "react-vant";
import PropType from "prop-types";
import '@/common/style/common.less'
import emptyPic from '@/common/images/empty.png'
import {_arrayFilterEmpty, _isEmpty} from "../../../common/js/utils";
/**
* 默认值
*/
LocationPicker.defaultProps = {
isLoaded: false,
columns: [],
defaultValues: [],
}
/**
* 类型和必填校验
*/
LocationPicker.propTypes = {
defaultValues: PropType.array.isRequired,
isLoaded: PropType.bool.isRequired
}
function LocationPicker(props) {
//数据源
const columns = props.columns;
//是否必填
const required = props.required;
//数据是否已加载
const isLoaded = props.isLoaded;
//默认展示数据
const defaultValues = props.defaultValues;
//组件数据变化函数
const onLineChange = props.onLineChange;
const [showPopup, setShowPopup] = useState(false);
const [lineValue, setLineValue] = useState('');
const picker = useRef(null);
/**
* 选中值
* @param val
* @returns {*[]}
*/
const handleSelected = (val) => {
if (val.constructor !== Array) {
console.error('选中数据格式错误,请处理')
}
let textList = []
let codeList = []
val.forEach((el) => {
textList.push(el['text'])
codeList.push(el['value'])
})
textList = _arrayFilterEmpty(textList);
codeList = _arrayFilterEmpty(codeList);
//展示数据
setLineValue(textList.join('-'))
//向父组件传递数据
onLineChange(textList, codeList)
}
/**
* 设置默认选中
* @param defaultArr
*/
const setDefaultValues = (defaultArr) => {
picker && picker.current && picker.current.setValues(defaultArr)
};
/**
* 弹出pop
* @returns {Promise<void>}
*/
const showPickerPop = async () => {
await setShowPopup(true)
setDefaultValues(defaultValues)
}
/**
* 隐藏pop
* @returns {Promise<void>}
*/
const hidePickerPop = async () => {
await setShowPopup(false)
}
/**
* 监听默认值变化
*/
useEffect(() => {
setLineValue(defaultValues.join('-'))
}, [defaultValues]);
return (
<div>
<Cell title="地址" isLink size="large" value={lineValue} required={required} onClick={() =>
showPickerPop()
}/>
<Popup
visible={showPopup}
position="bottom"
style={{height: '45%'}}
safeAreaInsetBottom
onClose={() => setShowPopup(false)}
>
{!isLoaded && <Loading type="ball" className='content-loading' vertical>加载中...</Loading>}
{
isLoaded && columns.length <= 0 &&
<Empty
className="custom-image"
image={emptyPic}
description="暂无数据"
/>
}
{
isLoaded && columns.length > 0 &&
<Picker
columns={columns}
ref={picker}
onConfirm={async (val) => {
handleSelected(val);
await hidePickerPop();
}}
onCancel={async (val) => {
handleSelected([]);
await hidePickerPop();
}}
/>
}
</Popup>
</div>
);
}
export default LocationPicker;
页面state 设置:
//线路基础数据
const [columns,setColumns] = useState([]);
const [isLineLoaded, setLineLoaded] = useState(false);
//当前选择的线路lineList 线路编码lineCodeList
const [lineList, setLineList] = useState([]);
const [lineCodeList, setLineCodeList] = useState([]);
useEffect(() => {
setTimeout(async () => {
await setLineLoaded(true)
await setColumns( [
{
"text": '江苏',
"children": [
{
"text": '苏州',
"children": [
{
"text": '姑苏区',
value: "001"
},
{
"text": '吴中区'
}
]
},
{
"text": '扬州',
"children": [
{
"text": '广陵区'
},
{
"text": '邗江区'
}
]
}
]
},
{
"text": '浙江',
value: "002",
"children": [
{
"text": '杭州',
"children": [
{
"text": '西湖区'
},
{
"text": '余杭区'
}
]
},
{
"text": '温州',
"children": [
"",
{
"text": '鹿城区'
},
{
"text": '瓯海区'
}
]
}
]
}
])
}, 5000)
}, [])
组件引用
<LocationPicker
required
columns={columns}
defaultValues={lineList}
isLoaded={isLineLoaded}
onLineChange={(valArr, codeArr) => {
setLineList(valArr);
setLineCodeList(codeArr)
}}/>
2. RadioList(单选)、SingleSelector (表单联动单选popup)
效果演示:
RadioList组件代码
import {Radio, Cell} from "react-vant";
RadioList.defaultProps = {
isCancel: true,
onChange: () => {
}
}
function RadioList(props) {
//list-数据源 isCancel-可取消 onChange-change函数 defaultValue-默认选中值
const {list, isCancel, onChange, value} = props;
/**
* 处理选中--可取消
*/
function handleSelected(selectedVal) {
let val = value === selectedVal && isCancel ? '' : selectedVal;
onChange(val);
}
return (
<Radio.Group value={value}>
<Cell.Group>
{
list.map((_item, _idx, _arr) => {
return <Cell
title={_item.text}
key={_idx}
rightIcon={
<Radio name={_item.value}/>
}
onClick={() => {
handleSelected(_item.value)
}}
/>
})
}
</Cell.Group>
</Radio.Group>
);
}
export default RadioList;
SingleSelector 组件代码
import React, {useEffect, useState} from 'react';
import {Cell, Empty, Loading, Radio, Popup} from "react-vant";
import emptyPic from '@/common/images/empty.png'
import RadioList from "../RadioList";
/**
* 单选popup框
*/
SingleSelector.defaultProps = {
isLoaded: false,
list: [],
defaultValue: '',
label: '单元格',
}
function SingleSelector(props) {
//isLoaded 异步数据是否已相应 list - 数据源头 onChange-回调 defaultValue 默认选中值
const {label, required, isLoaded, list, onChange, value} = props;
const [text, setText] = useState('');
const [show, setShowPopup] = useState(false);
useEffect(() => {
initCell(value);
}, [value])
/**
* 渲染cell
* @param val
*/
function initCell(val) {
setText(getTextByVal(val));
}
function getTextByVal(val) {
let obj = list.find((_item) => {
return _item['value'] === val;
})
return obj ? obj['text'] : '';
}
function showPopup() {
setShowPopup(true);
}
function hidePopup() {
setShowPopup(false);
}
/**
* 获取渲染结点
* @returns {JSX.Element}
*/
function getRender() {
if (!isLoaded) {
return <Loading type="ball" className='content-loading' vertical>加载中...</Loading>
} else if (list.length === 0) {
return <Empty className="custom-image" image={emptyPic} description="暂无数据"/>
} else {
return (
<RadioList
list={list}
value={value}
onChange={(val) => {
const text = getTextByVal(val);
onChange(val, text);
}}/>
)
}
}
return (
<div>
<Cell title={label} isLink size="large" value={text} required={required} onClick={() =>
showPopup()
}/>
<Popup
visible={show}
position="bottom"
style={{height: '45%'}}
safeAreaInsetBottom
onClose={() => hidePopup()
}
>
{getRender()}
</Popup>
</div>
);
}
export default SingleSelector;
页面引用:
import React, {useState} from 'react';
import Header from "@/components/base/Header";
import SingleSelector from "@/components/base/SingleSelector";
function Index(props) {
const headConfig = {
title: '测试页'
}
//调度数据
const [reportList, setReportList] = useState([{text: '南京', value: "00001"}, {
text: '北京',
value: "00002"
}, {text: '上海', value: "00003"}]);
const [isReportLoaded, setReportLoaded] = useState(false);
const [selectedReport, setSelectedReport] = useState('00001');
setTimeout(() => {
setReportLoaded(true);
}, 2000)
return (
<div className='page-container page-background'>
<Header {...headConfig}>
</Header>
<SingleSelector
label='城市'
required
isLoaded={isReportLoaded}
list={reportList}
value={selectedReport}
onChange={(val, text) => {
console.log(val, text)
setSelectedReport(val);
}}
/>
</div>
);
}
export default Index;
3. CheckboxList(多选)、MultipleSelector(表单联动多选Popup)
效果演示:
![](https://img-blog.csdnimg.cn/adb738c585734b6295495e69551d1a6f.gif#pic_center)
组件CheckboxList代码
import {Checkbox, Cell} from "react-vant";
CheckboxList.defaultProps = {
defaultValue: [],
list: [],
onChange: () => {
}
}
function CheckboxList(props) {
//list - 数据源 defaultValue-默认选中值 onChange-父组件回调函数
const {list, value, onChange} = props;
/**
* 选中 与 取消选中处理
* @param name
*/
const toggle = (name) => {
const cellCheck = JSON.parse(JSON.stringify(value));
const newValue = cellCheck.includes(name) ? cellCheck.filter((el) => el !== name) : [...cellCheck, name];
onChange(newValue);
};
return (
<Checkbox.Group value={value}>
<Cell.Group>
{
list.map((_item, _idx) => {
return <Cell
clickable
title={_item['text']}
key={_idx}
onClick={() => {
toggle(_item['value'])
}}
rightIcon={<Checkbox name={_item['value']}/>}
/>
})
}
</Cell.Group>
</Checkbox.Group>
);
}
export default CheckboxList;
组件MultipleSelector代码
import React, {useEffect, useState} from 'react';
import {Cell, Empty, Loading, Radio, Popup} from "react-vant";
import emptyPic from '@/common/images/empty.png'
import CheckboxList from "../CheckboxList";
/**
* 默认值
*/
MultipleSelector.defaultProps = {
isLoaded: false,
list: [],
value: [],
label: '单元格'
}
function MultipleSelector(props) {
//isLoaded 异步数据是否已相应 list - 数据源头 onChange-回调 value 默认选中值
const {label, required, isLoaded, list, onChange, value} = props;
const [selectedText, setSelectedText] = useState('');
const [show, setShowPopup] = useState(false);
useEffect(() => {
initCell(value);
}, [value])
/**
* 渲染cell
* @param val
*/
function initCell(val) {
setSelectedText(getTextByVal(val));
}
function getTextByVal(val) {
let textList = [];
list.forEach((_item) => {
if (val.includes(_item['value'])) {
textList.push(_item['text'])
}
})
return textList.join(',')
}
function showPopup() {
setShowPopup(true);
}
function hidePopup() {
setShowPopup(false);
}
/**
* 获取渲染结点
* @returns {JSX.Element}
*/
function getRender() {
if (!isLoaded) {
return <Loading type="ball" className='content-loading' vertical>加载中...</Loading>
} else if (list.length === 0) {
return <Empty className="custom-image" image={emptyPic} description="暂无数据"/>
} else {
return (
<CheckboxList list={list}
value={value}
onChange={(val) => {
initCell(val)
onChange(val);
}}/>
)
}
}
return (
<div>
<Cell title={label} isLink size="large" value={selectedText} required={required} onClick={() =>
showPopup()
}/>
<Popup
visible={show}
position="bottom"
style={{height: '45%'}}
safeAreaInsetBottom
onClose={() => hidePopup()
}
>
{getRender()}
</Popup>
</div>
);
}
export default MultipleSelector;
页面引用:
import React, {useState} from 'react';
import Header from "@/components/base/Header";
import MultipleSelector from "@/components/base/MultipleSelector";
function Index(props) {
const headConfig = {
title: '测试页'
}
//调度数据
const [majorList, setMajorList] = useState([{text: '计算机', value: "1"}, {text: '软件工程', value: "2"}
, {text: '生物', value: "3"}
, {text: '数学', value: "4"}
])
const [isMajorLoaded, setMajorLoaded] = useState(false);
const [selectedMajor, setSelectedMajor] = useState(['1']);
setTimeout(() => {
setMajorLoaded(true);
}, 2000)
return (
<div className='page-container page-background'>
<Header {...headConfig}>
</Header>
<MultipleSelector
label='专业'
required
isLoaded={isMajorLoaded}
list={majorList}
value={selectedMajor}
onChange={(val) => {
setSelectedMajor(val);
}}
/>
</div>
);
}
export default Index;
4. DatePicker(日期)
效果演示:
![](https://img-blog.csdnimg.cn/8b6d7eb590cc449aa92064012ced1fb1.gif#pic_center)
组件DatePicker代码
import React, {useEffect, useState} from 'react';
import {Cell, Popup, DatetimePicker} from "react-vant";
import {formatDate} from "../../../common/js/utils";
/**
* 单选popup框
*/
DatePicker.defaultProps = {
value: formatDate(new Date()),
dateType: 'datetime',
label: '单元格',
required: true,
onChange: () => {
}
}
function DatePicker(props) {
// onChange-回调 value 选中值
const {label, required, onChange, value, dateType} = props;
const [text, setText] = useState('');
const [show, setShow] = useState(false);
useEffect(() => {
initCell(value);
}, [value])
/**
* 渲染cell
*/
function initCell(val) {
setText(val);
}
function showPopup() {
setShow(true);
}
function hidePopup() {
setShow(false);
}
return (
<div>
<Cell title={label} isLink size="large"
value={text} required={required} onClick={() =>
showPopup()
}/>
<Popup
visible={show}
position="bottom"
style={{height: '45%'}}
safeAreaInsetBottom
round
onClose={() => hidePopup()
}
>
<DatetimePicker
type={dateType}
minDate={new Date(2020, 0, 1)}
value={new Date(value)}
onCancel={() => {
onChange('');
hidePopup()
}}
onConfirm={(value) => {
onChange(formatDate(value));
hidePopup()
}}
/>
</Popup>
</div>
);
}
export default DatePicker;
页面引用
import React, {useState} from 'react';
import Header from "@/components/base/Header";
import DatePicker from "@/components/base/DatePicker";
import {formatDate} from "@/common/js/utils";
function Index(props) {
const headConfig = {
title: '测试页'
}
//调度数据
const [occurTime, setOccurTime] = useState(formatDate(new Date()))
return (
<div className='page-container page-background'>
<Header {...headConfig}>
</Header>
<DatePicker
label='故障发生时间'
value={occurTime}
onChange={(val) => {
setOccurTime(val);
}}
/>
</div>
);
}
export default Index;
5. Header(标题栏)
效果演示:
![](https://img-blog.csdnimg.cn/4bb1733a7baa4f39815baf090c5d46c8.png)
组件Header 代码
import {NavBar} from 'react-vant';
import PropType from 'prop-types'
import {useNavigate} from "react-router-dom";
const Header = (props) => {
const {title, showLeftBtn, leftText, rightText, leftClick, rightClick} = props;
const navigate = useNavigate();
return (
<NavBar
title={title}
leftArrow={showLeftBtn}
leftText={leftText}
fixed
placeholder
safeAreaInsetTop
rightText={rightText}
onClickLeft={() => leftClick ? leftClick() : navigate(-1)}
onClickRight={() => rightClick && rightClick()}
/>
);
}
//props 类型 必填限制
Header.propTypes = {
title: PropType.string.isRequired,
leftClick: PropType.func,
rightClick: PropType.func,
}
//props 默认值
Header.defaultProps = {
title: '标题', //导航标题
showLeftBtn: true, //左侧按钮
leftText: '',
rightText: false,// false 不显示右侧按钮 , 其他ReactNode节点 , 则渲染
}
export default Header;
页面引用
import React, {useState} from 'react';
import Header from "@/components/base/Header";
import {Toast} from "react-vant";
function Index(props) {
const headConfig = {
title: '测试页',
rightText: '筛选',
rightClick:()=>{
Toast('右侧按钮')
}
}
return (
<div className='page-container page-background'>
<Header {...headConfig}>
</Header>
</div>
);
}
export default Index;
总结
每天记录一点,从小小菜鸟变小菜鸟!!!