1.通用性:可以适用于多种表格展示场景,样式设计更灵活
2.可配置性:提供丰富的配置选项
3.易用性:使用方式简单直观
4.可维护性:代码结构清晰,逻辑分明
5.可扩展性:自定义校验规则,样式覆盖等
import React from 'react';
import PropTypes from 'prop-types';
import { message, Icon } from 'antd';
import 'src/css/basicModal.css';
class BasicModal extends React.Component {
constructor(props) {
super(props);
this.state = {
formData: props.initialValues || {},
errors: {},
visible: props.visible || false
};
}
componentDidUpdate(prevProps) {
if (prevProps.visible !== this.props.visible) {
this.setState({ visible: this.props.visible });
}
if (prevProps.initialValues !== this.props.initialValues) {
this.setState({ formData: this.props.initialValues || {} });
}
}
// 关闭模态框
handleClose = () => {
const { onClose } = this.props;
this.setState({ visible: false });
if (onClose) {
onClose();
}
}
// 处理输入框的值
handleInputChange = (fieldName, value) => {
const { formFields } = this.props;
const field = formFields.find(f => f.name === fieldName);
// 根据字段类型转换值的格式
let formattedValue = value;
if (field && field.dataType) {
switch (field.dataType) {
case 'number':
// 转换为数字,处理空字符串和0的情况
formattedValue = value === '' ? '' : Number(value);
break;
case 'float':
// 转换为浮点数并保留特定小数位,处理空字符串和0的情况
if (value === '') {
formattedValue = '';
} else {
const numValue = Number(value);
// 确保0能够正确处理
formattedValue = Number(parseFloat(numValue).toFixed(field.precision || 2));
}
break;
case 'integer':
// 转换为整数,处理空字符串和0的情况
if (value === '') {
formattedValue = '';
} else {
formattedValue = Math.floor(Number(value));
}
break;
case 'boolean':
// 对于select类型且dataType为boolean时的特殊处理
if (field.type === 'select') {
// 把 "true" 和 "false" 字符串转换为布尔值
if (value === 'true') formattedValue = true;
if (value === 'false') formattedValue = false;
} else {
// 其他情况
formattedValue = Boolean(value);
}
break;
case 'date':
// 处理日期格式
// 这里可以添加日期格式化的逻辑
break;
default:
// 默认不做处理
break;
}
}
this.setState(prevState => ({
formData: {
...prevState.formData,
[fieldName]: formattedValue
},
errors: {
...prevState.errors,
[fieldName]: null // 清除错误信息
}
}));
}
// 验证字段
validateField = (fieldName, value) => {
const { formFields } = this.props;
const field = formFields.find(f => f.name === fieldName);
if (!field || !field.rules) return null;
for (const rule of field.rules) {
// 对必填字段的判断,特殊处理数字类型
if (rule.required) {
// 对数字类型字段,0是有效值,不应视为空
if (field.dataType === 'number' || field.dataType === 'float' || field.dataType === 'integer') {
if (value === undefined || value === null || value === '') {
return rule.message || `${field.label}不能为空`;
}
} else if (!value || (typeof value === 'string' && !value.trim())) {
// 对于非数字类型,保持原有逻辑
return rule.message || `${field.label}不能为空`;
}
}
if (rule.pattern && !rule.pattern.test(value)) {
return rule.message || `${field.label}格式不正确`;
}
if (rule.validator) {
const validationResult = rule.validator(value, this.state.formData);
if (validationResult) {
return validationResult;
}
}
}
return null;
}
// 验证表单
validateForm = () => {
const { formFields } = this.props;
const { formData } = this.state;
const errors = {};
let hasError = false;
formFields.forEach(field => {
const error = this.validateField(field.name, formData[field.name]);
if (error) {
errors[field.name] = error;
hasError = true;
}
});
this.setState({ errors });
return !hasError;
}
// 提交表单
handleSubmit = () => {
const isValid = this.validateForm();
if (isValid) {
const { onSubmit } = this.props;
if (onSubmit) {
onSubmit(this.state.formData);
}
if (this.props.closeOnSubmit !== false) {
this.handleClose();
}
} else {
message.error('请检查表单填写是否正确');
}
}
// 渲染提示图标
renderTooltip = (tooltip, placement = 'top') => {
if (!tooltip) return null;
// 确保放置方向是有效的
const validPlacements = ['top', 'right', 'bottom', 'left'];
const tooltipPlacement = validPlacements.includes(placement) ? placement : 'top';
return (
<span className="form-modal-info-icon">
<Icon type="info-circle" />
<span className={`form-modal-tooltip form-modal-tooltip-${tooltipPlacement}`}>
{tooltip}
</span>
</span>
);
}
// 渲染表单字段
renderFormField = (field) => {
const { formData, errors } = this.state;
const value = formData[field.name] !== undefined ? formData[field.name] : '';
const error = errors[field.name];
let inputElement;
// 根据字段类型决定输入方式
const inputType = field.dataType === 'number' || field.dataType === 'float' || field.dataType === 'integer'
? 'number'
: field.type || 'text';
// 根据字段数据类型设置特殊的输入属性
let inputProps = {
className: "form-modal-input",
value: value,
onChange: (e) => this.handleInputChange(field.name, e.target.value),
placeholder: field.placeholder,
disabled: field.disabled
};
// 为数字类型输入添加特殊属性
if (field.dataType === 'float' || field.dataType === 'number') {
inputProps.step = field.step || (field.dataType === 'float' ? '0.001' : '1');
if (field.min !== undefined) inputProps.min = field.min;
if (field.max !== undefined) inputProps.max = field.max;
}
switch (field.type) {
case 'textarea':
inputElement = (
<textarea
className="form-modal-textarea"
value={value}
onChange={(e) => this.handleInputChange(field.name, e.target.value)}
placeholder={field.placeholder}
disabled={field.disabled}
/>
);
break;
case 'select':
inputElement = (
<select
className="form-modal-select"
value={typeof value === 'boolean' ? String(value) : value}
onChange={(e) => this.handleInputChange(field.name, e.target.value)}
disabled={field.disabled}
>
{field.options && field.options.map((option, idx) => (
<option key={idx} value={typeof option.value === 'boolean' ? String(option.value) : option.value}>
{option.label}
</option>
))}
</select>
);
break;
case 'checkbox':
inputElement = (
<input
type="checkbox"
className="form-modal-checkbox"
checked={!!value}
onChange={(e) => this.handleInputChange(field.name, e.target.checked)}
disabled={field.disabled}
/>
);
break;
default: // 默认文本输入
inputElement = (
<input
type={inputType}
{...inputProps}
/>
);
}
return (
<div key={field.name} className="form-modal-field">
<label className="form-modal-label">
{field.required && <span className="form-modal-required-mark">*</span>}
{field.label}
{this.renderTooltip(field.tooltip, field.tooltipPlacement)}
</label>
<div className="form-modal-input-wrap">
{inputElement}
{error && <div className="form-modal-error">{error}</div>}
</div>
</div>
);
}
// 渲染模态框
render() {
const { title, formFields, submitText, cancelText, width, placement, style } = this.props;
const { visible } = this.state;
if (!visible) return null;
// 根据position属性计算模态框位置样式
let positionStyle = {};
// 自定义样式优先级高于placement
if (style) {
positionStyle = style
} else {
switch (placement) {
case 'left':
// 左上角定位
positionStyle = {
top: '20px',
left: '20px',
transform: 'none'
};
break;
case 'right':
// 右上角定位
positionStyle = {
top: '20px',
right: '20px',
transform: 'none'
};
break;
case 'center':
default:
// 默认水平垂直居中
positionStyle = {
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
};
}
}
const modalStyle = {
width: width || '500px',
maxWidth: '90%',
...positionStyle,
};
return (
<div className="form-modal-overlay">
<div className="form-modal-content" style={modalStyle}>
<div className="form-modal-header">
<h3>{title || '表单'}</h3>
<button className="form-modal-close-button" onClick={this.handleClose}>×</button>
</div>
<div className="form-modal-body">
<div className="form-modal-container">
{formFields && formFields.map(field => this.renderFormField(field))}
</div>
</div>
<div className="form-modal-footer">
<button
className="form-modal-btn form-modal-btn-default"
onClick={this.handleClose}
>
{cancelText || '取消'}
</button>
<button
className="form-modal-btn form-modal-btn-primary"
onClick={this.handleSubmit}
>
{submitText || '确定'}
</button>
</div>
</div>
</div>
);
}
}
BasicModal.propTypes = {
formFields: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
type: PropTypes.string,
dataType: PropTypes.oneOf(['string', 'number', 'float', 'integer', 'boolean', 'date']),
precision: PropTypes.number, // 小数位数
min: PropTypes.number, // 最小值
max: PropTypes.number, // 最大值
step: PropTypes.string, // 步长
placeholder: PropTypes.string,
disabled: PropTypes.bool,
required: PropTypes.bool,
tooltip: PropTypes.string, // 提示信息
tooltipPlacement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), // 提示位置
options: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.any.isRequired
})),
rules: PropTypes.arrayOf(PropTypes.shape({
required: PropTypes.bool,
pattern: PropTypes.instanceOf(RegExp),
validator: PropTypes.func,
message: PropTypes.string
})),
})),
initialValues: PropTypes.object,
visible: PropTypes.bool,
onClose: PropTypes.func,
onSubmit: PropTypes.func,
title: PropTypes.string,
submitText: PropTypes.string,
cancelText: PropTypes.string,
closeOnSubmit: PropTypes.bool,
width: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
placement: PropTypes.oneOf(['left', 'right', 'center']),
style: PropTypes.object, // 添加自定义样式属性
};
BasicModal.defaultProps = {
formFields: [],
initialValues: {},
visible: false,
closeOnSubmit: true,
placement: 'center'
};
export default BasicModal;
/* 模态框覆盖层 */
.form-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: not-allowed;
z-index: 1000;
}
/* 模态框内容 */
.form-modal-content {
position: absolute;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 90vh;
}
/* 模态框头部 */
.form-modal-header {
background-color: #eaeaea;
border-bottom: 1px solid #e8e8e8;
line-height: 33px;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-modal-header::before {
content: '';
position: absolute;
top: 9px;
left: 10px;
height: 14px;
border-left: 2px solid #f95e34;
}
.form-modal-header h3 {
margin: 0;
font-size: 12px;
font-family: Microsoft Yahei;
color: rgba(0, 0, 0, 0.85);
margin-left: 15px;
}
.form-modal-close-button {
background: none !important;
border: none;
font-size: 20px;
color: #999;
cursor: pointer;
outline: none;
}
.form-modal-close-button:hover {
color: #666;
}
/* 模态框主体 */
.form-modal-body {
padding: 16px;
overflow-y: auto;
overflow-x: hidden;
flex: 1;
}
/* 表单容器 */
.form-modal-container {
display: flex;
flex-direction: column;
gap: 16px;
}
/* 表单字段 - 修改为水平布局 */
.form-modal-field {
display: flex;
flex-direction: row;
align-items: center;
}
/* 字段标签 - 修改宽度和对齐方式 */
.form-modal-label {
font-size: 12px;
color: #333;
width: 40%; /* 固定宽度 */
padding-right: 10px;
text-align: left;
line-height: 32px; /* 垂直居中对齐 */
flex-shrink: 0;
position: relative; /* 为提示图标定位 */
}
.form-modal-required-mark {
color: #f5222d;
margin-right: 4px;
}
/* 字段输入区域 */
.form-modal-input-wrap {
position: relative;
flex: 6; /* 修改为flex: 6,与label的40%形成4/6比例 */
width: 60%; /* 添加宽度60%确保比例 */
}
/* 表单控件通用样式 */
.form-modal-input,
.form-modal-textarea,
.form-modal-select {
width: 100%;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
outline: none;
transition: all 0.3s;
}
.form-modal-input:focus,
.form-modal-textarea:focus,
.form-modal-select:focus {
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 禁用状态的输入框样式 */
.form-modal-input:disabled,
.form-modal-textarea:disabled,
.form-modal-select:disabled {
background-color: #f5f5f5;
color: #bfbfbf;
cursor: not-allowed;
border-color: #d9d9d9;
}
.form-modal-textarea {
min-height: 80px;
resize: vertical;
}
.form-modal-checkbox {
margin-top: 8px; /* 对齐调整 */
}
/* 错误提示 */
.form-modal-error {
color: #f5222d;
font-size: 12px;
margin-top: 4px;
}
/* 提示图标样式 */
.form-modal-info-icon {
display: inline-block;
margin-left: 4px;
color: #1890ff;
font-size: 14px;
cursor: pointer;
position: relative;
}
/* 提示内容样式 */
.form-modal-tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.75);
color: #fff;
padding: 6px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
z-index: 100;
max-width: 250px;
}
/* 显示提示 */
.form-modal-info-icon:hover .form-modal-tooltip {
visibility: visible;
opacity: 1;
}
/* 提示方向 - 上 */
.form-modal-tooltip-top {
bottom: 100%;
left: 50%;
transform: translateX(-50%);
margin-bottom: 8px;
}
.form-modal-tooltip-top:after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: rgba(0, 0, 0, 0.75) transparent transparent transparent;
}
/* 提示方向 - 右 */
.form-modal-tooltip-right {
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 8px;
}
.form-modal-tooltip-right:after {
content: '';
position: absolute;
right: 100%;
top: 50%;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent rgba(0, 0, 0, 0.75) transparent transparent;
}
/* 提示方向 - 下 */
.form-modal-tooltip-bottom {
top: 100%;
left: 50%;
transform: translateX(-50%);
margin-top: 8px;
}
.form-modal-tooltip-bottom:after {
content: '';
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent rgba(0, 0, 0, 0.75) transparent;
}
/* 提示方向 - 左 */
.form-modal-tooltip-left {
right: 100%;
top: 50%;
transform: translateY(-50%);
margin-right: 8px;
}
.form-modal-tooltip-left:after {
content: '';
position: absolute;
left: 100%;
top: 50%;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent transparent rgba(0, 0, 0, 0.75);
}
/* 模态框底部 */
.form-modal-footer {
padding: 16px;
border-top: 1px solid #e8e8e8;
display: flex;
justify-content: flex-end;
gap: 8px;
}
/* 按钮样式 */
.form-modal-btn {
min-width: 80px;
padding: 0 15px;
font-size: 14px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
cursor: pointer;
font-weight: 400;
white-space: nowrap;
text-align: center;
touch-action: manipulation;
user-select: none;
}
/* 取消按钮 */
.form-modal-btn-default {
color: #ff6232 !important;
background-color: #fff !important;
border: 1px solid #ff6232 !important;
}
/* 确认按钮 */
.form-modal-btn-primary {
color: #fff !important;
background-color: #ff6232 !important;
border: 1px solid #ff6232 !important;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12) !important;
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045) !important;
}
BasicFormModal 是一个Form模态框组件,类型转换、命名控件样式隔离、支持表单验证、位置自定义、自定义布局、提示信息info等功能。它适用于需要在模态框中收集用户输入数据的场景。
import BasicFormModal from './basicFormModal';
const formFields = [
{
name: 'username',
label: '用户名',
type: 'text',
placeholder: '请输入用户名',
required: true,
rules: [
{
required: true,
message: '用户名不能为空'
}
]
},
{
name: 'password',
label: '密码',
type: 'password',
placeholder: '请输入密码',
required: true,
rules: [
{
required: true,
message: '密码不能为空'
},
{
pattern: /^.{6,}$/,
message: '密码长度至少6位'
}
]
}
];
function MyComponent() {
return (
{}}
onSubmit={(values) => console.log(values)}
/>
);
}
// 设置模态框宽度
// 设置模态框位置
// 自定义样式
const initialValues = {
username: 'admin',
remember: true
};
const formFields = [
{
name: 'username',
label: '用户名',
type: 'text',
placeholder: '请输入用户名'
},
{
name: 'description',
label: '描述',
type: 'textarea',
placeholder: '请输入描述信息'
},
{
name: 'role',
label: '角色',
type: 'select',
options: [
{ label: '管理员', value: 'admin' },
{ label: '用户', value: 'user' },
{ label: '访客', value: 'guest' }
]
},
{
name: 'remember',
label: '记住我',
type: 'checkbox'
}
];
组件支持多种验证规则,包括必填、正则表达式匹配、自定义验证函数等。
const formFields = [
{
name: 'email',
label: '邮箱',
type: 'text',
required: true,
rules: [
{
required: true,
message: '邮箱不能为空'
},
{
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: '邮箱格式不正确'
}
]
},
{
name: 'password',
label: '密码',
type: 'password',
required: true,
rules: [
{
required: true,
message: '密码不能为空'
}
]
},
{
name: 'confirmPassword',
label: '确认密码',
type: 'password',
required: true,
rules: [
{
required: true,
message: '请确认密码'
},
{
// 自定义验证函数
validator: (value, formData) => {
if (value !== formData.password) {
return '两次输入的密码不一致';
}
return null;
}
}
]
}
];
组件支持为表单字段添加提示信息,帮助用户理解字段的作用和要求。
const formFields = [
{
name: 'username',
label: '用户名',
type: 'text',
required: true,
tooltip: '用户名只能包含字母、数字和下划线',
tooltipPlacement: 'right' // 可选: 'top', 'right', 'bottom', 'left'
},
{
name: 'apiKey',
label: 'API密钥',
type: 'text',
tooltip: '您可以在个人设置页面获取API密钥'
}
];
const formFields = [
{
name: 'username',
label: '用户名',
type: 'text',
disabled: true,
value: 'admin'
}
];
组件内置了以下样式特性:
参数 | 说明 | 类型 | 必填 | 默认值 |
---|---|---|---|---|
formFields | 表单字段配置 | Array | 是 | [] |
initialValues | 表单初始值 | Object | 否 | {} |
visible | 是否显示模态框 | boolean | 否 | false |
onClose | 关闭模态框的回调函数 | function | 否 | - |
onSubmit | 提交表单的回调函数 | function(formData) | 否 | - |
title | 模态框标题 | string | 否 | ‘表单’ |
submitText | 提交按钮文本 | string | 否 | ‘确定’ |
cancelText | 取消按钮文本 | string | 否 | ‘取消’ |
closeOnSubmit | 提交后是否自动关闭模态框 | boolean | 否 | true |
width | 模态框宽度 | number/string | 否 | ‘500px’ |
placement | 模态框位置 | string | 否 | ‘center’ |
style | 自定义样式对象 | object | 否 | - |
参数 | 说明 | 类型 | 必填 | 默认值 |
---|---|---|---|---|
name | 字段名称 | string | 是 | - |
label | 字段标签 | string | 是 | - |
type | 字段类型 | string | 否 | ‘text’ |
dataType | 数据类型 | string | 否 | ‘string’ |
precision | 小数位数(用于float类型) | number | 否 | 2 |
min | 最小值(用于数值类型) | number | 否 | - |
max | 最大值(用于数值类型) | number | 否 | - |
step | 步长(用于数值类型) | string | 否 | ‘1’/‘0.001’ |
placeholder | 占位文本 | string | 否 | - |
disabled | 是否禁用 | boolean | 否 | false |
required | 是否必填 | boolean | 否 | false |
tooltip | 提示信息 | string | 否 | - |
tooltipPlacement | 提示位置 | string | 否 | ‘top’ |
options | 选择项(用于select类型) | Array | 否 | - |
rules | 验证规则 | Array | 否 | - |
值 | 说明 |
---|---|
string | 字符串类型(默认) |
number | 数字类型,支持小数 |
float | 浮点数类型,可指定精度 |
integer | 整数类型 |
boolean | 布尔类型 |
date | 日期类型 |
参数 | 说明 | 类型 | 必填 |
---|---|---|---|
required | 是否必填 | boolean | 否 |
message | 错误提示信息 | string | 否 |
pattern | 正则表达式 | RegExp | 否 |
validator | 自定义验证函数 | function(value, formData) | 否 |
参数 | 说明 | 类型 | 必填 |
---|---|---|---|
label | 选项文本 | string | 是 |
value | 选项值 | any | 是 |
const formFields = [
{
name: 'price',
label: '价格',
type: 'text', // 输入框类型仍为text
dataType: 'float', // 数据类型为浮点数
precision: 2, // 保留2位小数
min: 0, // 最小值为0
max: 9999, // 最大值为9999
step: '0.01', // 步长为0.01
placeholder: '请输入价格'
},
{
name: 'quantity',
label: '数量',
type: 'text',
dataType: 'integer', // 数据类型为整数
min: 1,
placeholder: '请输入数量'
}
];
const formFields = [
{
name: 'isActive',
label: '是否激活',
type: 'checkbox',
dataType: 'boolean'
},
{
name: 'status',
label: '状态',
type: 'select',
dataType: 'boolean',
options: [
{ label: '启用', value: true },
{ label: '禁用', value: false }
]
}
];