import React, { useState, useEffect } from 'react';
|
import { Space, InputItem, Select, Switch, DatePicker, TextareaItem, Toast, Picker, Upload } from 'dingtalk-design-mobile';
|
import { RightArrow2Outlined } from 'dd-icons';
|
import FileShow from '../FileShow';
|
import DingUpload from '../DingUpload';
|
import * as $$ from '../../utils/utility';
|
import './index.less';
|
|
/**
|
* 自定义表单组件,无需使用Form组件
|
* @param {Array} formConfig - 表单配置数组
|
* @param {Object} formData - 表单数据对象
|
* @param {Function} onChange - 表单数据变更回调
|
* @param {Function} onSelectPerson - 选择人员回调
|
* @param {Function} onUpload - 文件上传回调
|
* @param {Boolean} disabled - 是否禁用表单
|
*/
|
const CustomFormView = ({ formConfig = [], formData = {}, onChange, onSelectPerson, onUpload, disabled = false }) => {
|
const [formValues, setFormValues] = useState({});
|
|
// 初始化表单数据
|
useEffect(() => {
|
setFormValues(formData);
|
}, [formData]);
|
|
// 处理表单项值变更
|
const handleChange = (name, value) => {
|
const newValues = { ...formValues, [name]: value };
|
setFormValues(newValues);
|
|
if (onChange) {
|
onChange(newValues, name);
|
}
|
};
|
|
// 处理文件上传/删除
|
const handleFileChange = (items, type) => {
|
let fileList = formValues.fileList || [];
|
|
if (type === 'add') {
|
fileList = fileList.concat(items);
|
} else {
|
fileList = fileList.filter((item) => item.id !== items.id);
|
}
|
|
const newValues = { ...formValues, fileList };
|
setFormValues(newValues);
|
|
if (onChange) {
|
onChange(newValues, 'fileList');
|
}
|
};
|
|
// 渲染表单项
|
const renderFormItem = (item, index) => {
|
const {
|
type,
|
name,
|
label,
|
placeholder,
|
format,
|
list = [],
|
desc,
|
require,
|
disabled,
|
className = '',
|
itemStyle = {},
|
tabs,
|
cols = 2,
|
subTitle,
|
url,
|
ownerType,
|
ownerId,
|
mainId,
|
onClick,
|
} = item;
|
|
const isFieldDisabled = disabled;
|
|
// 表单项的值
|
const fieldValue = formValues[name] !== undefined ? formValues[name] : '';
|
|
// 渲染不同类型的表单项
|
switch (type) {
|
case 'headerTitle':
|
return (
|
<div key={`header-${index}`} className="custom-form-header" style={itemStyle}>
|
{label}
|
</div>
|
);
|
|
case 'textBrief':
|
case 'text':
|
case 'password':
|
case 'phone':
|
case 'idcard':
|
case 'digit':
|
return (
|
<div key={`text-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-label">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control">
|
<InputItem
|
className="custom-form-input"
|
type={type === 'textBrief' ? 'text' : type}
|
value={formValues[name] || ''}
|
onChange={(value) => handleChange(name, value)}
|
disabled={isFieldDisabled}
|
placeholder={placeholder}
|
/>
|
</div>
|
</div>
|
);
|
|
case 'textarea':
|
return (
|
<div key={`textarea-${name}-${index}`} className={`custom-form-item ${tabs > 1 ? 'custom-form-item-tabs' : ''}`} style={itemStyle}>
|
<div className="custom-form-label" style={{ marginTop: '8px' }}>
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control">
|
<TextareaItem
|
className="custom-form-textarea"
|
value={formValues[name] || ''}
|
onChange={(value) => handleChange(name, value)}
|
disabled={isFieldDisabled}
|
placeholder={placeholder}
|
rows={tabs || 2}
|
maxLength={500}
|
/>
|
</div>
|
</div>
|
);
|
|
case 'select':
|
// 优化选项处理逻辑,支持多种传入格式
|
let pickerOptions = [];
|
|
try {
|
if (Array.isArray(list)) {
|
if (list.length > 0) {
|
if (typeof list[0] === 'object' && list[0] !== null) {
|
// 处理标准对象数组格式 [{label/title/name: '', value: ''}]
|
pickerOptions = list.map((item) => {
|
if (!item) return { label: '', value: '' };
|
return {
|
label: item.label || item.title || item.name || String(item.value || '') || '',
|
value: item.value !== undefined ? item.value : item.id !== undefined ? item.id : item.key !== undefined ? item.key : '',
|
};
|
});
|
} else if (typeof list[0] === 'string' || typeof list[0] === 'number') {
|
// 处理简单数组格式 ['选项1', '选项2']
|
pickerOptions = list.map((item) => ({
|
label: String(item || ''),
|
value: item,
|
}));
|
}
|
}
|
} else if (typeof list === 'object' && list !== null) {
|
// 处理对象格式 {key1: value1, key2: value2}
|
pickerOptions = Object.entries(list).map(([key, value]) => ({
|
label: String(value || ''),
|
value: key,
|
}));
|
} else if (typeof list === 'string') {
|
// 尝试将字符串解析为JSON
|
try {
|
const parsedList = JSON.parse(list);
|
if (Array.isArray(parsedList)) {
|
if (parsedList.length > 0 && typeof parsedList[0] === 'object') {
|
pickerOptions = parsedList.map((item) => ({
|
label: item.label || item.title || item.name || String(item.value || '') || '',
|
value: item.value || item.id || item.key || '',
|
}));
|
} else {
|
pickerOptions = parsedList.map((item) => ({
|
label: String(item || ''),
|
value: item,
|
}));
|
}
|
}
|
} catch (e) {
|
console.error('尝试解析字符串失败:', e);
|
// 如果解析失败,将整个字符串作为单个选项
|
pickerOptions = [{ label: list, value: list }];
|
}
|
}
|
|
// 如果转换后的选项为空,创建一个空占位选项避免Picker组件报错
|
if (pickerOptions.length === 0) {
|
pickerOptions = [{ label: placeholder, value: '' }];
|
}
|
} catch (error) {
|
console.error('处理select选项时出错:', error);
|
pickerOptions = [{ label: placeholder, value: '' }];
|
}
|
|
// 安全地将fieldValue转换为字符串进行比较
|
const fieldValueStr = fieldValue !== undefined && fieldValue !== null ? String(fieldValue) : '';
|
|
// 查找当前选中项的标签文本
|
const selectedOption = pickerOptions.find((item) => {
|
if (item.value === undefined || item.value === null) return false;
|
|
// 处理数字和字符串类型转换的比较
|
const itemValueStr = String(item.value);
|
return itemValueStr === fieldValueStr;
|
});
|
|
const selectedLabel = selectedOption ? selectedOption.label : '';
|
|
return (
|
<div key={`select-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row custom-form-value-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-select">
|
<Picker
|
value={fieldValue ? [fieldValue] : []}
|
okText="确定"
|
cancelText="取消"
|
onChange={(value) => {
|
console.log('Picker选择的值:', value);
|
// 如果提供了desc参数,同时更新对应的中文名称
|
if (desc) {
|
const selectedOption = pickerOptions.find((item) => String(item.value) === String(value[0]));
|
// 创建一个新的对象,同时更新两个字段
|
const newValues = {
|
...formValues,
|
[name]: value[0],
|
[desc]: selectedOption ? selectedOption.label : '',
|
};
|
setFormValues(newValues);
|
if (onChange) {
|
onChange(newValues, name);
|
}
|
} else {
|
handleChange(name, value[0]);
|
}
|
}}
|
disabled={isFieldDisabled}
|
cols={1}
|
data={list}
|
>
|
<div className="custom-form-value-display">
|
<span className={selectedLabel ? 'custom-form-value-select-text' : 'custom-form-value-select-text-placeholder'}>
|
{selectedLabel || placeholder}
|
</span>
|
<RightArrow2Outlined className="custom-form-value-arrow" />
|
</div>
|
</Picker>
|
</div>
|
</div>
|
</div>
|
);
|
|
case 'date':
|
return (
|
<div key={`date-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control">
|
<DatePicker
|
value={fieldValue ? fieldValue : ''}
|
onChange={(date) => {
|
console.log('date', date);
|
handleChange(name, date);
|
}}
|
disabled={isFieldDisabled}
|
adaptForm
|
// mode="time"
|
extra={placeholder || '选择'}
|
format={format || 'YYYY-MM-DD'}
|
/>
|
</div>
|
</div>
|
</div>
|
);
|
|
case 'switch':
|
return (
|
<div key={`switch-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control custom-form-switch">
|
<Switch
|
checked={fieldValue === '1' || fieldValue === true || fieldValue === 1}
|
onChange={(checked) => handleChange(name, checked ? '1' : '0')}
|
disabled={isFieldDisabled}
|
/>
|
<span className="custom-form-switch-text">
|
{list && list.length > 1 ? (fieldValue === '1' || fieldValue === true || fieldValue === 1 ? list[1] : list[0]) : ''}
|
</span>
|
</div>
|
</div>
|
</div>
|
);
|
|
case 'number':
|
return (
|
<div key={`number-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-label">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control">
|
<InputItem
|
className="custom-form-input"
|
type="number"
|
value={fieldValue}
|
onChange={(value) => handleChange(name, value.replace(/\s+/g, ''))}
|
disabled={isFieldDisabled}
|
placeholder={placeholder}
|
clear
|
maxLength={10}
|
/>
|
</div>
|
</div>
|
);
|
|
case 'selectPerson':
|
return (
|
<div key={`person-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control">
|
<div className="custom-form-select-person" onClick={() => !isFieldDisabled && onSelectPerson && onSelectPerson(name, url)}>
|
{fieldValue ? (
|
<div className="custom-form-select-value">{fieldValue}</div>
|
) : (
|
<div className="custom-form-select-placeholder">{placeholder}</div>
|
)}
|
{!isFieldDisabled && (
|
<div className="custom-form-select-arrow">
|
<RightArrow2Outlined />
|
</div>
|
)}
|
</div>
|
</div>
|
</div>
|
{subTitle && <div className="custom-form-subtitle">{subTitle}</div>}
|
</div>
|
);
|
|
case 'dingUpload':
|
return (
|
<div key={`dingUpload-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-upload">
|
<Upload
|
fileList={formValues[name] || []}
|
label={
|
<div className="ding-upload-label">
|
{require && <span className="custom-form-required">*</span>}
|
<div className="ding-upload-title">{label}</div>
|
{subTitle && <div className="ding-upload-subtitle">{subTitle}</div>}
|
</div>
|
// <div className="custom-form-title-row">
|
// <span className="custom-form-label-upload">{label}</span>
|
|
// </div>
|
}
|
type="form-upload"
|
accept="image/*,.pdf,.doc,.docx,.zip,.rar"
|
previewContent="预览"
|
action={`${$$.appUrl.fileUrl}/${$$.appUrl.sys}/api/web/fileInfo/upload?mainId=${mainId || ''}&ownerId=${ownerId || ''}&ownerType=${
|
ownerType || ''
|
}`}
|
headers={{ Authorization: $$.getSessionStorage('customerSystemToken') }}
|
onChange={(info) => {
|
handleChange(name, info.fileList);
|
if (onUpload) {
|
onUpload(name, info.fileList);
|
}
|
}}
|
disabled={isFieldDisabled}
|
uploadLabel="支持扩展名:.jpg .pdf .doc .docx .zip .rar,30M以内"
|
uploadBtnLabel="上传文件"
|
className="identity-document-upload"
|
/>
|
</div>
|
{/* {subTitle && <div className="custom-form-subtitle">{subTitle}</div>} */}
|
</div>
|
);
|
|
case 'checkboxDiy':
|
return (
|
<div key={`checkbox-${name}-${index}`} className="custom-form-item custom-form-checkbox" style={itemStyle}>
|
<div className="custom-form-checkbox-list">
|
{list.map((option, optIndex) => (
|
<div
|
key={`option-${option.value}-${optIndex}`}
|
className={`custom-form-checkbox-option ${fieldValue === option.value ? 'custom-form-checkbox-selected' : ''}`}
|
onClick={() => !isFieldDisabled && handleChange(name, option.value)}
|
>
|
<div className="custom-form-checkbox-title">{option.title}</div>
|
{option.subTitle && <div className="custom-form-checkbox-subtitle">{option.subTitle}</div>}
|
</div>
|
))}
|
</div>
|
</div>
|
);
|
|
case 'district':
|
return (
|
<div key={`district-${name}-${index}`} className={`custom-form-item ${tabs > 1 ? 'custom-form-item-tabs' : ''}`} style={itemStyle}>
|
<div className="custom-form-select-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-control">
|
<div className="custom-form-district" onClick={() => !isFieldDisabled && onSelectPerson && onSelectPerson('district', name)}>
|
{fieldValue ? (
|
<div className="custom-form-select-value">{fieldValue}</div>
|
) : (
|
<div className="custom-form-select-placeholder">{placeholder || '请选择地区'}</div>
|
)}
|
{!isFieldDisabled && (
|
<div className="custom-form-select-arrow">
|
<RightArrow2Outlined />
|
</div>
|
)}
|
</div>
|
</div>
|
</div>
|
</div>
|
);
|
|
case 'radio':
|
return (
|
<div key={`radio-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-radio">
|
<Space direction="horizontal" size="large">
|
{list.map((option, optIndex) => (
|
<label key={`radio-option-${optIndex}`} className="custom-form-radio-option">
|
<input
|
type="radio"
|
name={name}
|
value={option.value}
|
checked={fieldValue === option.value}
|
onChange={(e) => handleChange(name, e.target.value)}
|
disabled={isFieldDisabled}
|
/>
|
<span className="custom-form-radio-label">{option.label}</span>
|
</label>
|
))}
|
</Space>
|
</div>
|
</div>
|
</div>
|
);
|
|
case 'cascader':
|
return (
|
<div key={`cascader-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row custom-form-value-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-select">
|
<Picker
|
value={Array.isArray(fieldValue) ? fieldValue : []}
|
cascade={true}
|
okText="确定"
|
cancelText="取消"
|
onChange={(value) => {
|
handleChange(name, value);
|
}}
|
disabled={isFieldDisabled}
|
cols={cols}
|
data={Array.isArray(list) ? list : []}
|
>
|
<div className="custom-form-value-display">
|
<span
|
className={
|
Array.isArray(fieldValue) && fieldValue.length > 0
|
? 'custom-form-value-select-text'
|
: 'custom-form-value-select-text-placeholder'
|
}
|
>
|
{Array.isArray(fieldValue) && fieldValue.length > 0
|
? (() => {
|
// 尝试从列表中找出对应的标签文本
|
try {
|
const labels = [];
|
let currentData = list;
|
fieldValue.forEach((value, index) => {
|
const found = currentData.find((item) => item.value === value);
|
if (found) {
|
labels.push(found.label);
|
currentData = found.children || [];
|
}
|
});
|
return labels.length > 0 ? labels.join('/') : fieldValue.join('/');
|
} catch (e) {
|
// 如果解析出错,直接返回原始值
|
return fieldValue.join('/');
|
}
|
})()
|
: placeholder}
|
</span>
|
<RightArrow2Outlined className="custom-form-value-arrow" />
|
</div>
|
</Picker>
|
</div>
|
</div>
|
</div>
|
);
|
|
case 'navigation':
|
return (
|
<div key={`navigation-${name}-${index}`} className="custom-form-item" style={itemStyle}>
|
<div className="custom-form-select-row custom-form-value-row">
|
<div className="custom-form-label custom-form-label-inline">
|
{require && <span className="custom-form-required">*</span>}
|
{label}
|
</div>
|
<div className="custom-form-select">
|
<div className="custom-form-value-display" onClick={() => !isFieldDisabled && onClick && onClick()}>
|
<span className={fieldValue ? 'custom-form-value-select-text' : 'custom-form-value-select-text-placeholder'}>
|
{fieldValue || placeholder}
|
</span>
|
<RightArrow2Outlined className="custom-form-value-arrow" />
|
</div>
|
</div>
|
</div>
|
</div>
|
);
|
|
default:
|
return null;
|
}
|
};
|
|
// 渲染表单分组
|
const renderFormGroup = (group, groupIndex) => {
|
const { title, titleTab, list = [] } = group;
|
|
return (
|
<div key={`group-${groupIndex}`} className="custom-form-group">
|
{(title || titleTab) && <div className="custom-form-group-title">{title || titleTab}</div>}
|
<div className="custom-form-group-content">{list.map((item, itemIndex) => renderFormItem(item, itemIndex))}</div>
|
</div>
|
);
|
};
|
|
return <div className="custom-form-container">{formConfig.map((group, index) => renderFormGroup(group, index))}</div>;
|
};
|
|
export default CustomFormView;
|