🆕 新增长文本组件, 长文本组件的typescript类型

This commit is contained in:
xujiang 2020-09-07 23:22:43 +08:00
parent 0b225181be
commit e54228401c
12 changed files with 927 additions and 2 deletions

View File

@ -5,6 +5,7 @@ import React from 'react';
import {
HeaderConfigType,
TextConfigType,
LongTextConfigType,
NoticeConfigType,
QRCodeConfigType,
FooterConfigType,
@ -55,6 +56,18 @@ const Qrcode = memo((props: QRCodeConfigType) => {
);
});
const LongText = memo((props: LongTextConfigType) => {
const { text, fontSize, color, indent, lineHeight } = props;
return (
<div
className={styles.textWrap}
style={{ color, textIndent: indent + 'px', fontSize, lineHeight }}
>
{text}
</div>
);
});
const Footer = memo((props: FooterConfigType) => {
const { bgColor, text, color, align, fontSize, height } = props;
return (
@ -144,4 +157,4 @@ const XProgress = memo((props: XProgressConfigType) => {
);
});
export { Header, Text, Notice, Qrcode, Footer, Image, List, XProgress };
export { Header, Text, LongText, Notice, Qrcode, Footer, Image, List, XProgress };

View File

@ -38,7 +38,6 @@ type DynamicType = {
};
const DynamicEngine = memo((props: DynamicType) => {
const { type, config, isTpl } = props;
console.log(config);
const Dynamic = useMemo(() => {
return (DynamicFunc(type) as unknown) as FC<DynamicType>;
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -9,6 +9,8 @@ export type BasicSchemaType =
| 'Switch'
| 'DataList'
| 'Text'
| 'LongText'
| 'TextArea'
| 'Color'
| 'Number'
| 'Select'
@ -102,6 +104,30 @@ export interface TextSchema extends SchemaBasicImplement {
editData: Array<TextEditItem<TextKeyType>>;
config: TextConfigType;
}
//__________________________________________
//________________LongText________________________
export type LongTextConfigType = {
text: string;
fontSize: number;
color: string;
indent: number;
lineHeight: number;
};
export type LongTextKeyType = keyof LongTextConfigType;
export interface LongTextEditItem<T extends LongTextKeyType> {
key: T;
name: string;
type: BasicSchemaType;
range?: Array<number>;
step?: number;
}
export interface LongTextSchema extends SchemaBasicImplement {
editData: Array<LongTextEditItem<LongTextKeyType>>;
config: LongTextConfigType;
}
//__________________________________________
//________________TAB________________________
@ -401,6 +427,7 @@ export interface XProgressSchema extends SchemaBasicImplement {
export interface SchemaType extends SchemaImplement {
Carousel: CarouselSchema;
Text: TextSchema;
LongText: LongTextSchema;
Tab: TabSchema;
Notice: NoticeSchema;
Qrcode: QRCodeSchema;
@ -534,6 +561,44 @@ const schema: SchemaType = {
lineHeight: 2,
},
},
LongText: {
editData: [
{
key: 'text',
name: '文字',
type: 'TextArea',
},
{
key: 'color',
name: '标题颜色',
type: 'Color',
},
{
key: 'fontSize',
name: '字体大小',
type: 'Number',
},
{
key: 'indent',
name: '首行缩进',
type: 'Number',
range: [0, 100],
},
{
key: 'lineHeight',
name: '行高',
type: 'Number',
step: 0.1,
},
],
config: {
text: '我是长文本有一段故事dooring可视化编辑器无限可能赶快来体验吧骚年们奥利给~',
color: 'rgba(60,60,60,1)',
fontSize: 14,
indent: 20,
lineHeight: 1.8,
},
},
Tab: {
editData: [
{

View File

@ -2,6 +2,7 @@ import { BasicTemplateItem } from './schema';
export type TemplateKeyType =
| 'Text'
| 'LongText'
| 'Carousel'
| 'Tab'
| 'Notice'
@ -19,6 +20,10 @@ const template: TemplateType = [
type: 'Text',
h: 20,
},
{
type: 'LongText',
h: 36,
},
{
type: 'Carousel',
h: 82,

View File

@ -0,0 +1,87 @@
import { Input, Cell, DateSelect, Radio, Select } from 'zarm';
import styles from './baseForm.less';
// 维护表单控件, 提高form渲染性能
const BaseForm = {
Text: props => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<Input clearable type="text" placeholder={placeholder} onChange={onChange} />
</Cell>
);
},
Textarea: props => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<Input
type="text"
rows={3}
autoHeight
showLength
placeholder={placeholder}
onChange={onChange}
/>
</Cell>
);
},
Number: props => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<Input
type="number"
rows={3}
autoHeight
showLength
placeholder={placeholder}
onChange={onChange}
/>
</Cell>
);
},
MyRadio: props => {
const { label, options, onChange } = props;
return (
<div className={styles.radioWrap}>
<div className={styles.radioTitle}>{label}</div>
<Cell>
<Radio.Group onChange={onChange}>
{options.map((item, i) => {
return (
<Radio value={item.value} key={i} className={styles.radioItem}>
{item.label}
</Radio>
);
})}
</Radio.Group>
</Cell>
</div>
);
},
Date: props => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<DateSelect
placeholder={placeholder}
mode="date"
min="1949-05-15"
max="2100-05-15"
onOk={onChange}
/>
</Cell>
);
},
MySelect: props => {
const { label, options, onChange } = props;
return (
<Cell title={label}>
<Select dataSource={options} onOk={onChange} />
</Cell>
);
},
};
export default BaseForm;

View File

@ -0,0 +1,104 @@
import React, { memo, useEffect } from 'react';
import { Form, Select, Input, Modal } from 'antd';
const { Option } = Select;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
const EditorModal = props => {
const { item, onSave, visible, onCancel } = props;
const onFinish = values => {
onSave && onSave(values);
};
const handleOk = () => {
form
.validateFields()
.then(values => {
console.log(values);
values.id = item.id;
onSave && onSave(values);
})
.catch(err => {
console.log(err);
});
};
const [form] = Form.useForm();
useEffect(() => {
return () => {
form.resetFields();
};
}, [item]);
return (
!!item && (
<Modal
title="编辑表单组件"
visible={visible}
onOk={handleOk}
onCancel={onCancel}
okText="确定"
cancelText="取消"
>
<Form
form={form}
name={`formItem_editor_modal`}
{...formItemLayout}
onFinish={onFinish}
initialValues={item}
>
{
<Form.Item label="类型" name="type" hidden>
<Input />
</Form.Item>
}
{!!item.label && (
<Form.Item
label="字段名"
name="label"
rules={[{ required: true, message: '请输入字段名!' }]}
>
<Input />
</Form.Item>
)}
{!!item.placeholder && (
<Form.Item label="提示文本" name="placeholder">
<Input placeholder="请输入提示文本" />
</Form.Item>
)}
{!!item.options && (
<Form.Item
label="选项源"
name="options"
rules={[{ required: true, message: '选项不能为空!' }]}
>
<Select
placeholder="请输入"
mode="tags"
labelInValue
maxTagCount={39}
maxTagTextLength={16}
>
{item.options.map((v, i) => {
return (
<Option value={v.value} key={i}>
{v.label}
</Option>
);
})}
</Select>
</Form.Item>
)}
</Form>
</Modal>
)
);
};
export default memo(EditorModal);

View File

@ -0,0 +1,138 @@
import React, { memo, useState, useEffect } from 'react';
import BaseForm from './BaseForm';
import EditorModal from './EditorModal';
import { EditOutlined, MinusCircleOutlined } from '@ant-design/icons';
// import { Popconfirm } from 'antd';
import styles from './formItems.less';
const formTpl = [
{
id: '1',
type: 'Text',
label: '文本',
placeholder: '请输入文本',
},
{
id: '2',
type: 'Textarea',
label: '长文本',
placeholder: '请输入长文本请输入长文本',
},
{
id: '3',
type: 'Number',
label: '数值',
placeholder: ' 请输入数值',
},
{
id: '4',
type: 'MyRadio',
label: '单选框',
options: [
{ label: '选项一', value: '1' },
{ label: '选项二', value: '2' },
],
},
{
id: '5',
type: 'MySelect',
label: '下拉选择框',
options: [
{ label: '选项一', value: '1' },
{ label: '选项二', value: '2' },
{ label: '选项三', value: '3' },
],
},
{
id: '6',
type: 'Date',
label: '日期框',
},
];
const FormItems = props => {
const { formList, onChange } = props;
const [formData, setFormData] = useState(formList || []);
const [visible, setVisible] = useState(false);
const [curItem, setCurItem] = useState();
const handleAddItem = item => {
let tpl = formTpl.find(v => v.type === item.type);
let newData = [...formData, tpl];
setFormData(newData);
onChange && onChange(newData);
};
const handleEditItem = item => {
setVisible(true);
setCurItem(item);
};
const handleDelItem = item => {
let newData = formData.filter(v => v.type !== item.type);
setFormData(newData);
onChange && onChange(newData);
};
const handleCloseModal = () => {
setVisible(false);
};
const handleSaveItem = data => {
let newData = formData.map(v => (v.type === data.type ? data : v));
setFormData(newData);
onChange && onChange(newData);
setVisible(false);
};
return (
<div className={styles.formItemWrap}>
<div className={styles.editForm}>
{formData.map((item, i) => {
let FormItem = BaseForm[item.type];
return (
<div className={styles.formItem} key={i}>
<div className={styles.disClick}>
<FormItem {...item} />
</div>
<div className={styles.operationWrap}>
<span className={styles.operationBtn} onClick={handleEditItem.bind(this, item)}>
<EditOutlined />
</span>
<span className={styles.operationBtn} onClick={handleDelItem.bind(this, item)}>
<MinusCircleOutlined />
</span>
</div>
</div>
);
})}
</div>
<div className={styles.formTpl}>
<h4>表单模版</h4>
{formTpl.map((item, i) => {
let FormItem = BaseForm[item.type];
return (
<div className={styles.formItem} key={i}>
<div className={styles.disClick}>
<FormItem {...item} />
</div>
<span className={styles.addBtn} onClick={handleAddItem.bind(this, item)}>
添加
</span>
</div>
);
})}
</div>
<EditorModal
item={curItem}
onSave={handleSaveItem}
visible={visible}
onCancel={handleCloseModal}
/>
</div>
);
};
export default memo(FormItems);

View File

@ -0,0 +1,10 @@
.radioWrap {
margin-bottom: 10px;
.radioTitle {
padding: 6px 16px;
font-size: 15px;
}
.radioItem {
margin-top: 10px;
}
}

View File

@ -0,0 +1,56 @@
.formItemWrap {
.editForm {
.formItem {
position: relative;
&:hover {
.operationWrap {
display: inline-block;
}
}
.operationWrap {
position: absolute;
display: none;
right: 0;
top: 16px;
box-shadow: 0 0 20px #fff;
background-color: #fff;
.operationBtn {
margin-right: 15px;
display: inline-block;
cursor: pointer;
}
}
}
}
.formTpl {
margin-top: 12px;
border-top: 1px dashed #ccc;
padding-top: 16px;
.formItem {
position: relative;
border: 1px solid #ccc;
margin-bottom: 2px;
.disClick {
pointer-events: none;
}
&:hover {
border-color: #2f54eb;
.addBtn {
display: inline-block;
}
}
.addBtn {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
display: none;
padding: 3px 6px;
color: #fff;
border-radius: 3px;
background-color: #2f54eb;
cursor: pointer;
}
}
}
}

View File

@ -0,0 +1,70 @@
import React, { memo, useState, useEffect } from 'react';
import { Button } from 'zarm';
import BaseForm from './BaseForm';
import req from 'utils/req';
import styles from './index.less';
function unParams(params = '?a=1&b=2&c=3') {
let obj = {};
params &&
params.replace(/((\w*)=([\.a-z0-9A-Z]*)?)?/g, (m, a, b, c) => {
if (b || c) obj[b] = c;
});
return obj;
}
const FormComponent = props => {
const { title, bgColor, fontSize, titColor, btnColor, btnTextColor, api, formControls } = props;
const formData = {};
const handleChange = (item, v) => {
if (item.options) {
formData[item.label] = v[0].label;
return;
}
formData[item.label] = v;
};
const handleSubmit = () => {
if (api) {
fetch(api, {
body: JSON.stringify(formData),
cache: 'no-cache',
headers: {
'content-type': 'application/json',
},
method: 'POST',
mode: 'cors',
});
}
};
return (
<div className={styles.formWrap} style={{ backgroundColor: bgColor }}>
{title && (
<div className={styles.title} style={{ fontSize, color: titColor }}>
{title}
</div>
)}
<div className={styles.formContent}>
{formControls.map(item => {
const FormItem = BaseForm[item.type];
return <FormItem onChange={handleChange.bind(this, item)} {...item} key={item.id} />;
})}
<div style={{ textAlign: 'center', padding: '16px 0' }}>
<Button
theme="primary"
size="sm"
block
onClick={handleSubmit}
style={{ backgroundColor: btnColor, borderColor: btnColor, color: btnTextColor }}
>
提交
</Button>
</div>
</div>
</div>
);
};
export default memo(FormComponent);

View File

@ -0,0 +1,14 @@
.formWrap {
margin: 10px;
padding: 20px 16px;
border-radius: 6px;
background-color: #fff;
box-shadow: 0 2px 6px #f0f0f0;
.title {
padding-bottom: 20px;
text-align: center;
font-size: 18px;
}
.formContent {
}
}

View File

@ -0,0 +1,364 @@
export type AntdIconType =
| 'max'
| 'required'
| 'default'
| 'high'
| 'low'
| 'disabled'
| 'start'
| 'open'
| 'media'
| 'hidden'
| 'cite'
| 'data'
| 'dir'
| 'form'
| 'label'
| 'slot'
| 'span'
| 'style'
| 'summary'
| 'title'
| 'pattern'
| 'async'
| 'defer'
| 'manifest'
| 'color'
| 'content'
| 'size'
| 'wrap'
| 'multiple'
| 'height'
| 'rotate'
| 'translate'
| 'width'
| 'prefix'
| 'src'
| 'children'
| 'key'
| 'list'
| 'step'
| 'aria-label'
| 'spin'
| 'accept'
| 'acceptCharset'
| 'action'
| 'allowFullScreen'
| 'allowTransparency'
| 'alt'
| 'as'
| 'autoComplete'
| 'autoFocus'
| 'autoPlay'
| 'capture'
| 'cellPadding'
| 'cellSpacing'
| 'charSet'
| 'challenge'
| 'checked'
| 'classID'
| 'cols'
| 'colSpan'
| 'controls'
| 'coords'
| 'crossOrigin'
| 'dateTime'
| 'download'
| 'encType'
| 'formAction'
| 'formEncType'
| 'formMethod'
| 'formNoValidate'
| 'formTarget'
| 'frameBorder'
| 'headers'
| 'href'
| 'hrefLang'
| 'htmlFor'
| 'httpEquiv'
| 'integrity'
| 'keyParams'
| 'keyType'
| 'kind'
| 'loop'
| 'marginHeight'
| 'marginWidth'
| 'maxLength'
| 'mediaGroup'
| 'method'
| 'min'
| 'minLength'
| 'muted'
| 'name'
| 'nonce'
| 'noValidate'
| 'optimum'
| 'placeholder'
| 'playsInline'
| 'poster'
| 'preload'
| 'readOnly'
| 'rel'
| 'reversed'
| 'rows'
| 'rowSpan'
| 'sandbox'
| 'scope'
| 'scoped'
| 'scrolling'
| 'seamless'
| 'selected'
| 'shape'
| 'sizes'
| 'srcDoc'
| 'srcLang'
| 'srcSet'
| 'target'
| 'type'
| 'useMap'
| 'value'
| 'wmode'
| 'defaultChecked'
| 'defaultValue'
| 'suppressContentEditableWarning'
| 'suppressHydrationWarning'
| 'accessKey'
| 'className'
| 'contentEditable'
| 'contextMenu'
| 'draggable'
| 'id'
| 'lang'
| 'spellCheck'
| 'tabIndex'
| 'radioGroup'
| 'role'
| 'about'
| 'datatype'
| 'inlist'
| 'property'
| 'resource'
| 'typeof'
| 'vocab'
| 'autoCapitalize'
| 'autoCorrect'
| 'autoSave'
| 'itemProp'
| 'itemScope'
| 'itemType'
| 'itemID'
| 'itemRef'
| 'results'
| 'security'
| 'unselectable'
| 'inputMode'
| 'is'
| 'aria-activedescendant'
| 'aria-atomic'
| 'aria-autocomplete'
| 'aria-busy'
| 'aria-checked'
| 'aria-colcount'
| 'aria-colindex'
| 'aria-colspan'
| 'aria-controls'
| 'aria-current'
| 'aria-describedby'
| 'aria-details'
| 'aria-disabled'
| 'aria-dropeffect'
| 'aria-errormessage'
| 'aria-expanded'
| 'aria-flowto'
| 'aria-grabbed'
| 'aria-haspopup'
| 'aria-hidden'
| 'aria-invalid'
| 'aria-keyshortcuts'
| 'aria-labelledby'
| 'aria-level'
| 'aria-live'
| 'aria-modal'
| 'aria-multiline'
| 'aria-multiselectable'
| 'aria-orientation'
| 'aria-owns'
| 'aria-placeholder'
| 'aria-posinset'
| 'aria-pressed'
| 'aria-readonly'
| 'aria-relevant'
| 'aria-required'
| 'aria-roledescription'
| 'aria-rowcount'
| 'aria-rowindex'
| 'aria-rowspan'
| 'aria-selected'
| 'aria-setsize'
| 'aria-sort'
| 'aria-valuemax'
| 'aria-valuemin'
| 'aria-valuenow'
| 'aria-valuetext'
| 'dangerouslySetInnerHTML'
| 'onCopy'
| 'onCopyCapture'
| 'onCut'
| 'onCutCapture'
| 'onPaste'
| 'onPasteCapture'
| 'onCompositionEnd'
| 'onCompositionEndCapture'
| 'onCompositionStart'
| 'onCompositionStartCapture'
| 'onCompositionUpdate'
| 'onCompositionUpdateCapture'
| 'onFocus'
| 'onFocusCapture'
| 'onBlur'
| 'onBlurCapture'
| 'onChange'
| 'onChangeCapture'
| 'onBeforeInput'
| 'onBeforeInputCapture'
| 'onInput'
| 'onInputCapture'
| 'onReset'
| 'onResetCapture'
| 'onSubmit'
| 'onSubmitCapture'
| 'onInvalid'
| 'onInvalidCapture'
| 'onLoad'
| 'onLoadCapture'
| 'onError'
| 'onErrorCapture'
| 'onKeyDown'
| 'onKeyDownCapture'
| 'onKeyPress'
| 'onKeyPressCapture'
| 'onKeyUp'
| 'onKeyUpCapture'
| 'onAbort'
| 'onAbortCapture'
| 'onCanPlay'
| 'onCanPlayCapture'
| 'onCanPlayThrough'
| 'onCanPlayThroughCapture'
| 'onDurationChange'
| 'onDurationChangeCapture'
| 'onEmptied'
| 'onEmptiedCapture'
| 'onEncrypted'
| 'onEncryptedCapture'
| 'onEnded'
| 'onEndedCapture'
| 'onLoadedData'
| 'onLoadedDataCapture'
| 'onLoadedMetadata'
| 'onLoadedMetadataCapture'
| 'onLoadStart'
| 'onLoadStartCapture'
| 'onPause'
| 'onPauseCapture'
| 'onPlay'
| 'onPlayCapture'
| 'onPlaying'
| 'onPlayingCapture'
| 'onProgress'
| 'onProgressCapture'
| 'onRateChange'
| 'onRateChangeCapture'
| 'onSeeked'
| 'onSeekedCapture'
| 'onSeeking'
| 'onSeekingCapture'
| 'onStalled'
| 'onStalledCapture'
| 'onSuspend'
| 'onSuspendCapture'
| 'onTimeUpdate'
| 'onTimeUpdateCapture'
| 'onVolumeChange'
| 'onVolumeChangeCapture'
| 'onWaiting'
| 'onWaitingCapture'
| 'onAuxClick'
| 'onAuxClickCapture'
| 'onClick'
| 'onClickCapture'
| 'onContextMenu'
| 'onContextMenuCapture'
| 'onDoubleClick'
| 'onDoubleClickCapture'
| 'onDrag'
| 'onDragCapture'
| 'onDragEnd'
| 'onDragEndCapture'
| 'onDragEnter'
| 'onDragEnterCapture'
| 'onDragExit'
| 'onDragExitCapture'
| 'onDragLeave'
| 'onDragLeaveCapture'
| 'onDragOver'
| 'onDragOverCapture'
| 'onDragStart'
| 'onDragStartCapture'
| 'onDrop'
| 'onDropCapture'
| 'onMouseDown'
| 'onMouseDownCapture'
| 'onMouseEnter'
| 'onMouseLeave'
| 'onMouseMove'
| 'onMouseMoveCapture'
| 'onMouseOut'
| 'onMouseOutCapture'
| 'onMouseOver'
| 'onMouseOverCapture'
| 'onMouseUp'
| 'onMouseUpCapture'
| 'onSelect'
| 'onSelectCapture'
| 'onTouchCancel'
| 'onTouchCancelCapture'
| 'onTouchEnd'
| 'onTouchEndCapture'
| 'onTouchMove'
| 'onTouchMoveCapture'
| 'onTouchStart'
| 'onTouchStartCapture'
| 'onPointerDown'
| 'onPointerDownCapture'
| 'onPointerMove'
| 'onPointerMoveCapture'
| 'onPointerUp'
| 'onPointerUpCapture'
| 'onPointerCancel'
| 'onPointerCancelCapture'
| 'onPointerEnter'
| 'onPointerEnterCapture'
| 'onPointerLeave'
| 'onPointerLeaveCapture'
| 'onPointerOver'
| 'onPointerOverCapture'
| 'onPointerOut'
| 'onPointerOutCapture'
| 'onGotPointerCapture'
| 'onGotPointerCaptureCapture'
| 'onLostPointerCapture'
| 'onLostPointerCaptureCapture'
| 'onScroll'
| 'onScrollCapture'
| 'onWheel'
| 'onWheelCapture'
| 'onAnimationStart'
| 'onAnimationStartCapture'
| 'onAnimationEnd'
| 'onAnimationEndCapture'
| 'onAnimationIteration'
| 'onAnimationIterationCapture'
| 'onTransitionEnd'
| 'onTransitionEndCapture'
| 'twoToneColor';