Merge pull request #27 from MrXujiang/yehuozhiliwork

Yehuozhiliwork
This commit is contained in:
yehuozhili 2020-09-17 21:14:07 +08:00 committed by GitHub
commit d5c4c0d59c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 16873 additions and 16358 deletions

View File

@ -88,8 +88,8 @@
"@types/node": "^14.6.2",
"@types/react-color": "^3.0.4",
"@types/redux-logger": "^3.0.8",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"@typescript-eslint/eslint-plugin": "4.1.1",
"@typescript-eslint/parser": "4.1.1",
"babel-eslint": "10.x",
"babel-plugin-import": "^1.13.0",
"eslint": "6.x",

View File

@ -1,9 +1,9 @@
import React, { memo, PropsWithChildren } from 'react';
import { Carousel } from 'zarm';
import styles from './index.less';
import { CarouselConfigType } from '../DynamicEngine/schema';
import { ICarouselConfig } from './schema';
interface CarouselTypes extends CarouselConfigType {
interface CarouselTypes extends ICarouselConfig {
isTpl: boolean;
}
@ -18,7 +18,6 @@ const XCarousel = memo((props: PropsWithChildren<CarouselTypes>) => {
);
});
};
return (
<div style={{ width: '100%', overflow: 'hidden' }}>
{isTpl ? (

View File

@ -0,0 +1,98 @@
import {
IDataListConfigType,
IRadioConfigType,
ISwitchConfigType,
TDataListDefaultType,
TRadioDefaultType,
TSwitchDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type CarouselDirectionKeyType = 'down' | 'left';
export type TCarouselEditData = Array<
IRadioConfigType<CarouselDirectionKeyType> | ISwitchConfigType | IDataListConfigType
>;
export interface ICarouselConfig {
direction: TRadioDefaultType<CarouselDirectionKeyType>;
swipeable: TSwitchDefaultType;
autoPlay: TSwitchDefaultType;
imgList: TDataListDefaultType;
tplImg: string;
}
export interface ICarouselSchema {
editData: TCarouselEditData;
config: ICarouselConfig;
}
const Carousel: ICarouselSchema = {
editData: [
{
key: 'direction',
name: '方向',
type: 'Radio',
range: [
{
key: 'down',
text: '从上到下',
},
{
key: 'left',
text: '从左到右',
},
],
},
{
key: 'swipeable',
name: '是否可拖拽',
type: 'Switch',
},
{
key: 'autoPlay',
name: '是否自动播放',
type: 'Switch',
},
{
key: 'imgList',
name: '图片列表',
type: 'DataList',
},
],
config: {
direction: 'left',
swipeable: false,
autoPlay: false,
imgList: [
{
id: '1',
title: '趣谈小课1',
desc: '致力于打造优质小课程',
link: 'xxxxx',
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/1_1740bd7c3dc.png',
},
],
},
{
id: '2',
title: '趣谈小课1',
desc: '致力于打造优质小课程',
link: 'xxxxx',
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/2_1740bd8d525.png',
},
],
},
],
tplImg: 'http://io.nainor.com/uploads/carousal_17442e1420f.png',
},
};
export default Carousel;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Carousel',
h: 82,
};
export default template;

View File

@ -0,0 +1,21 @@
import React, { memo } from 'react';
import { IFooterConfig } from './schema';
const Footer = memo((props: IFooterConfig) => {
const { bgColor, text, color, align, fontSize, height } = props;
return (
<footer
style={{
backgroundColor: bgColor,
color,
fontSize,
textAlign: align,
height,
lineHeight: height + 'px',
}}
>
{text}
</footer>
);
});
export default Footer;

View File

@ -0,0 +1,87 @@
import {
IColorConfigType,
INumberConfigType,
ISelectConfigType,
ITextConfigType,
TColorDefaultType,
TNumberDefaultType,
TSelectDefaultType,
TTextDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TfooterSelectKeyType = 'left' | 'center' | 'right';
export type TFooterEditData = Array<
IColorConfigType | INumberConfigType | ITextConfigType | ISelectConfigType<TfooterSelectKeyType>
>;
export interface IFooterConfig {
bgColor: TColorDefaultType;
text: TTextDefaultType;
color: TColorDefaultType;
align: TSelectDefaultType<TfooterSelectKeyType>;
fontSize: TNumberDefaultType;
height: TNumberDefaultType;
}
export interface IFooterSchema {
editData: TFooterEditData;
config: IFooterConfig;
}
const Footer: IFooterSchema = {
editData: [
{
key: 'bgColor',
name: '背景色',
type: 'Color',
},
{
key: 'height',
name: '高度',
type: 'Number',
},
{
key: 'text',
name: '文字',
type: 'Text',
},
{
key: 'fontSize',
name: '字体大小',
type: 'Number',
},
{
key: 'color',
name: '文字颜色',
type: 'Color',
},
{
key: 'align',
name: '对齐方式',
type: 'Select',
range: [
{
key: 'left',
text: '左对齐',
},
{
key: 'center',
text: '居中对齐',
},
{
key: 'right',
text: '右对齐',
},
],
},
],
config: {
bgColor: 'rgba(0,0,0,1)',
text: '页脚Footer',
color: 'rgba(255,255,255,1)',
align: 'center',
fontSize: 16,
height: 48,
},
};
export default Footer;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Footer',
h: 24,
};
export default template;

View File

@ -1,9 +1,23 @@
import { Input, Cell, DateSelect, Radio, Select } from 'zarm';
import styles from './baseForm.less';
import React from 'react';
import {
baseFormDateTpl,
baseFormMyRadioTpl,
baseFormMySelectTpl,
baseFormNumberTpl,
baseFormTextAreaTpl,
baseFormTextTpl,
baseFormUnionType,
} from '@/components/PanelComponents/FormEditor/types';
// 维护表单控件, 提高form渲染性能
const BaseForm = {
Text: props => {
type TBaseForm = {
[key in baseFormUnionType]: any;
};
const BaseForm: TBaseForm = {
Text: (props: baseFormTextTpl & { onChange: (v: string | undefined) => void }) => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
@ -11,7 +25,7 @@ const BaseForm = {
</Cell>
);
},
Textarea: props => {
Textarea: (props: baseFormTextAreaTpl & { onChange: (v: string | undefined) => void }) => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
@ -26,22 +40,15 @@ const BaseForm = {
</Cell>
);
},
Number: props => {
Number: (props: baseFormNumberTpl & { onChange: (v: string | undefined | number) => void }) => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<Input
type="number"
rows={3}
autoHeight
showLength
placeholder={placeholder}
onChange={onChange}
/>
<Input type="number" placeholder={placeholder} onChange={onChange} />
</Cell>
);
},
MyRadio: props => {
MyRadio: (props: baseFormMyRadioTpl & { onChange: (v: string | undefined | number) => void }) => {
const { label, options, onChange } = props;
return (
<div className={styles.radioWrap}>
@ -60,7 +67,7 @@ const BaseForm = {
</div>
);
},
Date: props => {
Date: (props: baseFormDateTpl & { onChange: (v: Date) => void }) => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
@ -74,7 +81,9 @@ const BaseForm = {
</Cell>
);
},
MySelect: props => {
MySelect: (
props: baseFormMySelectTpl & { onChange: ((v: Record<string, any>) => void) | undefined },
) => {
const { label, options, onChange } = props;
return (
<Cell title={label}>

View File

@ -1,30 +1,22 @@
import React, { memo, useState, useEffect } from 'react';
import React, { memo, useCallback } from 'react';
import { Button } from 'zarm';
import BaseForm from './BaseForm';
import req from 'utils/req';
import styles from './index.less';
import { IFormConfig } from './schema';
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 FormComponent = (props: IFormConfig) => {
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 formData: Record<string, any> = {};
const handleChange = useCallback(
(item, v) => {
if (item.options) {
formData[item.label] = v[0].label;
return;
}
formData[item.label] = v;
},
[formData],
);
const handleSubmit = () => {
if (api) {
fetch(api, {
@ -38,7 +30,6 @@ const FormComponent = props => {
});
}
};
return (
<div className={styles.formWrap} style={{ backgroundColor: bgColor }}>
{title && (

View File

@ -0,0 +1,108 @@
import {
IColorConfigType,
IFormItemsConfigType,
INumberConfigType,
ITextConfigType,
TColorDefaultType,
TFormItemsDefaultType,
TNumberDefaultType,
TTextDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TFormEditData = Array<
ITextConfigType | INumberConfigType | IColorConfigType | ITextConfigType | IFormItemsConfigType
>;
export interface IFormConfig {
title: TTextDefaultType;
fontSize: TNumberDefaultType;
titColor: TColorDefaultType;
bgColor: TColorDefaultType;
btnColor: TColorDefaultType;
btnTextColor: TColorDefaultType;
api: TTextDefaultType;
formControls: TFormItemsDefaultType;
}
export interface IFormSchema {
editData: TFormEditData;
config: IFormConfig;
}
const Form: IFormSchema = {
editData: [
{
key: 'title',
name: '标题',
type: 'Text',
},
{
key: 'fontSize',
name: '标题大小',
type: 'Number',
},
{
key: 'titColor',
name: '标题颜色',
type: 'Color',
},
{
key: 'bgColor',
name: '背景色',
type: 'Color',
},
{
key: 'btnColor',
name: '按钮颜色',
type: 'Color',
},
{
key: 'btnTextColor',
name: '按钮文本颜色',
type: 'Color',
},
{
key: 'api',
name: '表单Api地址',
type: 'Text',
},
{
key: 'formControls',
name: '表单控件',
type: 'FormItems',
},
],
config: {
title: '表单定制组件',
fontSize: 18,
titColor: 'rgba(60,60,60,1)',
bgColor: 'rgba(255,255,255,1)',
btnColor: 'rgba(129,173,173,1)',
btnTextColor: 'rgba(255,255,255,1)',
api: '',
formControls: [
{
id: '1',
type: 'Text',
label: '姓名',
placeholder: '请输入姓名',
},
{
id: '2',
type: 'Number',
label: '年龄',
placeholder: ' 请输入年龄',
},
{
id: '4',
type: 'MySelect',
label: '爱好',
options: [
{ label: '选项一', value: '1' },
{ label: '选项二', value: '2' },
{ label: '选项三', value: '3' },
],
},
],
},
};
export default Form;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Form',
h: 172,
};
export default template;

View File

@ -21,23 +21,3 @@
color: #fff;
}
}
.list {
margin: 20px auto;
width: 94%;
.sourceList {
.sourceItem {
display: flex;
align-items: center;
margin-bottom: 16px;
.imgWrap {
}
.content {
margin-left: 12px;
.tit {
line-height: 2;
}
}
}
}
}

View File

@ -0,0 +1,20 @@
import { memo } from 'react';
import styles from './index.less';
import React from 'react';
import { IHeaderConfig } from './schema';
const Header = memo((props: IHeaderConfig) => {
const { bgColor, logo, logoText, fontSize, color } = props;
return (
<header className={styles.header} style={{ backgroundColor: bgColor }}>
<div className={styles.logo}>
<img src={logo && logo[0].url} alt={logoText} />
</div>
<div className={styles.title} style={{ fontSize, color }}>
{logoText}
</div>
</header>
);
});
export default Header;

View File

@ -0,0 +1,81 @@
import {
IColorConfigType,
INumberConfigType,
ITextConfigType,
IUploadConfigType,
TColorDefaultType,
TNumberDefaultType,
TTextDefaultType,
TUploadDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type THeaderEditData = Array<
IColorConfigType | INumberConfigType | IUploadConfigType | ITextConfigType
>;
export interface IHeaderConfig {
bgColor: TColorDefaultType;
logo: TUploadDefaultType;
logoText: TTextDefaultType;
fontSize: TNumberDefaultType;
color: TColorDefaultType;
height: TNumberDefaultType;
}
export interface IHeaderSchema {
editData: THeaderEditData;
config: IHeaderConfig;
}
const Header: IHeaderSchema = {
editData: [
{
key: 'bgColor',
name: '背景色',
type: 'Color',
},
{
key: 'height',
name: '高度',
type: 'Number',
},
{
key: 'logo',
name: 'logo',
type: 'Upload',
isCrop: true,
cropRate: 1000 / 618,
},
{
key: 'logoText',
name: 'logo文字',
type: 'Text',
},
{
key: 'color',
name: '文字颜色',
type: 'Color',
},
{
key: 'fontSize',
name: '文字大小',
type: 'Number',
},
],
config: {
bgColor: 'rgba(0,0,0,1)',
logo: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/3_1740be8a482.png',
},
],
logoText: '页头Header',
fontSize: 20,
color: 'rgba(255,255,255,1)',
height: 50,
},
};
export default Header;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Header',
h: 28,
};
export default template;

View File

@ -2,13 +2,11 @@ import React, { memo } from 'react';
import * as Icon from '@ant-design/icons';
import IconImg from 'assets/icon.png';
import { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon';
import { IconConfigType } from '../DynamicEngine/schema';
import { AntdIconType } from './icon';
import { IIconConfig } from './schema';
interface IconType extends Omit<IconConfigType, 'spin' | 'color'> {
interface IconType extends IIconConfig {
isTpl?: boolean;
spin?: IconConfigType['spin'];
color?: IconConfigType['color'];
}
const XIcon = memo((props: IconType) => {
const { color, size, type, spin, isTpl } = props;

View File

@ -0,0 +1,144 @@
import {
ICardPickerConfigType,
IColorConfigType,
INumberConfigType,
ISwitchConfigType,
TCardPickerDefaultType,
TColorDefaultType,
TNumberDefaultType,
TSwitchDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TIconEditData = Array<
IColorConfigType | INumberConfigType | ISwitchConfigType | ICardPickerConfigType<IconTypes>
>;
export interface IIconConfig {
color: TColorDefaultType;
size: TNumberDefaultType;
spin: TSwitchDefaultType;
type: TCardPickerDefaultType<IconTypes>;
}
export interface IIconSchema {
editData: TIconEditData;
config: IIconConfig;
}
export type IconTypes =
| 'AccountBookTwoTone'
| 'AlertTwoTone'
| 'ApiTwoTone'
| 'AppstoreTwoTone'
| 'AudioTwoTone'
| 'BankTwoTone'
| 'BellTwoTone'
| 'BookTwoTone'
| 'BugTwoTone'
| 'BuildTwoTone'
| 'BulbTwoTone'
| 'CalculatorTwoTone'
| 'CalendarTwoTone'
| 'CameraTwoTone'
| 'CarTwoTone'
| 'CarryOutTwoTone'
| 'CiCircleTwoTone'
| 'CloudTwoTone'
| 'CodeTwoTone'
| 'CrownTwoTone'
| 'CustomerServiceTwoTone'
| 'DollarCircleTwoTone'
| 'EnvironmentTwoTone'
| 'ExperimentTwoTone'
| 'FireTwoTone'
| 'GiftTwoTone'
| 'InsuranceTwoTone'
| 'LikeTwoTone'
| 'LockTwoTone'
| 'MailTwoTone'
| 'MessageTwoTone'
| 'PhoneTwoTone'
| 'PictureTwoTone'
| 'PlaySquareTwoTone'
| 'RedEnvelopeTwoTone'
| 'ShopTwoTone'
| 'TrademarkCircleTwoTone'
| 'StarTwoTone'
| 'SafetyCertificateTwoTone'
| 'SettingTwoTone'
| 'RocketTwoTone';
const Icon: IIconSchema = {
editData: [
{
key: 'color',
name: '颜色',
type: 'Color',
},
{
key: 'size',
name: '大小',
type: 'Number',
},
{
key: 'spin',
name: '旋转动画',
type: 'Switch',
},
{
key: 'type',
name: '图标类型',
type: 'CardPicker',
icons: [
'AccountBookTwoTone',
'AlertTwoTone',
'ApiTwoTone',
'AppstoreTwoTone',
'AudioTwoTone',
'BankTwoTone',
'BellTwoTone',
'BookTwoTone',
'BugTwoTone',
'BuildTwoTone',
'BulbTwoTone',
'CalculatorTwoTone',
'CalendarTwoTone',
'CameraTwoTone',
'CarTwoTone',
'CarryOutTwoTone',
'CiCircleTwoTone',
'CloudTwoTone',
'CodeTwoTone',
'CrownTwoTone',
'CustomerServiceTwoTone',
'DollarCircleTwoTone',
'EnvironmentTwoTone',
'ExperimentTwoTone',
'FireTwoTone',
'GiftTwoTone',
'InsuranceTwoTone',
'LikeTwoTone',
'LockTwoTone',
'MailTwoTone',
'MessageTwoTone',
'PhoneTwoTone',
'PictureTwoTone',
'PlaySquareTwoTone',
'RedEnvelopeTwoTone',
'ShopTwoTone',
'TrademarkCircleTwoTone',
'StarTwoTone',
'SafetyCertificateTwoTone',
'SettingTwoTone',
'RocketTwoTone',
],
},
],
config: {
color: 'rgba(74,144,226,1)',
size: 36,
spin: false,
type: 'CarTwoTone',
},
};
export default Icon;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Icon',
h: 23,
};
export default template;

View File

@ -0,0 +1,12 @@
import React, { memo } from 'react';
import { IImageConfig } from './schema';
const Image = memo((props: IImageConfig) => {
const { imgUrl, round = 0 } = props;
return (
<div style={{ borderRadius: round, width: '100%', textAlign: 'center', overflow: 'hidden' }}>
<img src={imgUrl && imgUrl[0].url} alt="" style={{ width: '100%' }} />
</div>
);
});
export default Image;

View File

@ -0,0 +1,46 @@
import {
INumberConfigType,
IUploadConfigType,
TNumberDefaultType,
TUploadDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TImageEditData = Array<IUploadConfigType | INumberConfigType>;
export interface IImageConfig {
imgUrl: TUploadDefaultType;
round: TNumberDefaultType;
}
export interface IImageSchema {
editData: TImageEditData;
config: IImageConfig;
}
const Image: IImageSchema = {
editData: [
{
key: 'imgUrl',
name: '上传',
type: 'Upload',
isCrop: false,
},
{
key: 'round',
name: '圆角',
type: 'Number',
},
],
config: {
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/4_1740bf4535c.png',
},
],
round: 0,
},
};
export default Image;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Image',
h: 188,
};
export default template;

View File

@ -0,0 +1,19 @@
.list {
margin: 20px auto;
width: 94%;
.sourceList {
.sourceItem {
display: flex;
align-items: center;
margin-bottom: 16px;
.imgWrap {
}
.content {
margin-left: 12px;
.tit {
line-height: 2;
}
}
}
}
}

View File

@ -0,0 +1,51 @@
import React, { memo } from 'react';
import styles from './index.less';
import { IListConfig } from './schema';
const List = memo((props: IListConfig) => {
const { round, sourceData, imgSize, fontSize, color } = props;
return (
<div className={styles.list}>
<div className={styles.sourceList}>
{sourceData.map((item, i) => {
return (
<div className={styles.sourceItem} key={i}>
<div className={styles.imgWrap}>
<img
src={
item.imgUrl[0]
? item.imgUrl[0].url
: 'http://io.nainor.com/uploads/01_173e15d3493.png'
}
alt={item.desc}
style={{
width: parseFloat(imgSize),
height: imgSize,
objectFit: 'cover',
borderRadius: round,
}}
/>
</div>
<div className={styles.content}>
<a
className={styles.tit}
style={{ fontSize, color }}
href={item.link ? item.link : '#'}
>
{item.title}
<div
className={styles.desc}
style={{ fontSize: fontSize * 0.8, color: 'rgba(0,0,0, .3)' }}
>
{item.desc}
</div>
</a>
</div>
</div>
);
})}
</div>
</div>
);
});
export default List;

View File

@ -0,0 +1,116 @@
import {
IColorConfigType,
IDataListConfigType,
INumberConfigType,
ISelectConfigType,
TColorDefaultType,
TDataListDefaultType,
TNumberDefaultType,
TSelectDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TListSelectKeyType = '60' | '80' | '100' | '120' | '150';
export type TListEditData = Array<
IColorConfigType | IDataListConfigType | INumberConfigType | ISelectConfigType<TListSelectKeyType>
>;
export interface IListConfig {
sourceData: TDataListDefaultType;
round: TNumberDefaultType;
imgSize: TSelectDefaultType<TListSelectKeyType>;
fontSize: TNumberDefaultType;
color: TColorDefaultType;
}
export interface IListSchema {
editData: TListEditData;
config: IListConfig;
}
const List: IListSchema = {
editData: [
{
key: 'sourceData',
name: '数据源',
type: 'DataList',
},
{
key: 'round',
name: '圆角',
type: 'Number',
},
{
key: 'imgSize',
name: '图片大小',
type: 'Select',
range: [
{
key: '60',
text: '60 x 60',
},
{
key: '80',
text: '80 x 80',
},
{
key: '100',
text: '100 x 100',
},
{
key: '120',
text: '120 x 120',
},
{
key: '150',
text: '150 x 150',
},
],
},
{
key: 'fontSize',
name: '文字大小',
type: 'Number',
},
{
key: 'color',
name: '文字颜色',
type: 'Color',
},
],
config: {
sourceData: [
{
id: '1',
title: '趣谈小课',
desc: '致力于打造优质小课程',
link: 'xxxxx',
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
},
],
},
{
id: '2',
title: '趣谈小课',
desc: '致力于打造优质小课程',
link: 'xxxxx',
imgUrl: [
{
uid: '002',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
},
],
},
],
round: 0,
imgSize: '80',
fontSize: 16,
color: 'rgba(153,153,153,1)',
},
};
export default List;

View File

@ -0,0 +1,5 @@
const template = {
type: 'List',
h: 110,
};
export default template;

View File

@ -0,0 +1,15 @@
import React, { memo } from 'react';
import styles from './index.less';
import { ILongTextConfig } from './schema';
const LongText = memo((props: ILongTextConfig) => {
const { text, fontSize, color, indent, lineHeight, textAlign } = props;
return (
<div
className={styles.textWrap}
style={{ color, textIndent: indent + 'px', fontSize, lineHeight, textAlign }}
>
{text}
</div>
);
});
export default LongText;

View File

@ -0,0 +1,92 @@
import {
IColorConfigType,
INumberConfigType,
ISelectConfigType,
ITextAreaConfigType,
TColorDefaultType,
TNumberDefaultType,
TSelectDefaultType,
TTextAreaDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TLongTextSelectKeyType = 'left' | 'center' | 'right';
export type TLongTextEditData = Array<
| ITextAreaConfigType
| IColorConfigType
| INumberConfigType
| ISelectConfigType<TLongTextSelectKeyType>
>;
export interface ILongTextConfig {
text: TTextAreaDefaultType;
color: TColorDefaultType;
fontSize: TNumberDefaultType;
indent: TNumberDefaultType;
lineHeight: TNumberDefaultType;
textAlign: TSelectDefaultType<TLongTextSelectKeyType>;
}
export interface ILongTextSchema {
editData: TLongTextEditData;
config: ILongTextConfig;
}
const LongText: ILongTextSchema = {
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: 'textAlign',
name: '对齐方式',
type: 'Select',
range: [
{
key: 'left',
text: '左对齐',
},
{
key: 'center',
text: '居中对齐',
},
{
key: 'right',
text: '右对齐',
},
],
},
{
key: 'lineHeight',
name: '行高',
type: 'Number',
step: 0.1,
},
],
config: {
text: '我是长文本有一段故事dooring可视化编辑器无限可能赶快来体验吧骚年们奥利给~',
color: 'rgba(60,60,60,1)',
fontSize: 14,
indent: 20,
lineHeight: 1.8,
textAlign: 'left',
},
};
export default LongText;

View File

@ -0,0 +1,5 @@
const template = {
type: 'LongText',
h: 36,
};
export default template;

View File

@ -0,0 +1,13 @@
import { NoticeBar } from 'zarm';
import React, { memo } from 'react';
import { INoticeConfig } from './schema';
const Notice = memo((props: INoticeConfig) => {
const { text, speed, theme, isClose = false } = props;
return (
<NoticeBar theme={theme === 'default' ? undefined : theme} closable={isClose} speed={speed}>
<span style={{ color: 'inherit' }}>{text}</span>
</NoticeBar>
);
});
export default Notice;

View File

@ -0,0 +1,81 @@
import {
INumberConfigType,
ISelectConfigType,
ISwitchConfigType,
ITextConfigType,
TNumberDefaultType,
TSelectDefaultType,
TSwitchDefaultType,
TTextDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TNoticeSelectKeyType = 'default' | 'warning' | 'primary' | 'success' | 'danger';
export type TNoticeEditData = Array<
ITextConfigType | INumberConfigType | ISelectConfigType<TNoticeSelectKeyType> | ISwitchConfigType
>;
export interface INoticeConfig {
text: TTextDefaultType;
speed: TNumberDefaultType;
theme: TSelectDefaultType<TNoticeSelectKeyType>;
isClose: TSwitchDefaultType;
}
export interface INoticeSchema {
editData: TNoticeEditData;
config: INoticeConfig;
}
const Notice: INoticeSchema = {
editData: [
{
key: 'text',
name: '文本',
type: 'Text',
},
{
key: 'speed',
name: '滚动速度',
type: 'Number',
},
{
key: 'theme',
name: '主题',
type: 'Select',
range: [
{
key: 'default',
text: '默认',
},
{
key: 'warning',
text: '警告',
},
{
key: 'primary',
text: '主要',
},
{
key: 'success',
text: '成功',
},
{
key: 'danger',
text: '危险',
},
],
},
{
key: 'isClose',
name: '是否可关闭',
type: 'Switch',
},
],
config: {
text: '通知栏: 趣谈前端上线啦',
speed: 50,
theme: 'warning',
isClose: false,
},
};
export default Notice;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Notice',
h: 20,
};
export default template;

View File

@ -0,0 +1,14 @@
import React, { memo } from 'react';
import { IQrcodeConfig } from './schema';
const Qrcode = memo((props: IQrcodeConfig) => {
const { qrcode, text, color, fontSize = 14 } = props;
return (
<div style={{ width: '240px', margin: '16px auto' }}>
<img src={qrcode && qrcode[0].url} alt={text} style={{ width: '100%' }} />
<div style={{ textAlign: 'center', color, fontSize, paddingTop: '8px' }}>{text}</div>
</div>
);
});
export default Qrcode;

View File

@ -0,0 +1,67 @@
import {
IColorConfigType,
INumberConfigType,
ITextConfigType,
IUploadConfigType,
TColorDefaultType,
TNumberDefaultType,
TTextDefaultType,
TUploadDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TQrcodeEditData = Array<
IUploadConfigType | ITextConfigType | IColorConfigType | INumberConfigType
>;
export interface IQrcodeConfig {
qrcode: TUploadDefaultType;
text: TTextDefaultType;
color: TColorDefaultType;
fontSize: TNumberDefaultType;
}
export interface IQrcodeSchema {
editData: TQrcodeEditData;
config: IQrcodeConfig;
}
const Qrcode: IQrcodeSchema = {
editData: [
{
key: 'qrcode',
name: '二维码',
type: 'Upload',
isCrop: true,
cropRate: 1,
},
{
key: 'text',
name: '文字',
type: 'Text',
},
{
key: 'color',
name: '文字颜色',
type: 'Color',
},
{
key: 'fontSize',
name: '文字大小',
type: 'Number',
},
],
config: {
qrcode: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/code_173e1705e0c.png',
},
],
text: '二维码',
color: 'rgba(153,153,153,1)',
fontSize: 14,
},
};
export default Qrcode;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Qrcode',
h: 150,
};
export default template;

View File

@ -1,11 +1,11 @@
import React, { useEffect, useRef } from 'react';
import { Tabs } from 'zarm';
import styles from './index.less';
import { TabConfigType } from '../DynamicEngine/schema';
import { ITabConfig } from './schema';
const { Panel } = Tabs;
const XTab = (props: TabConfigType) => {
const XTab = (props: ITabConfig) => {
const { tabs = ['分类一', '分类二'], activeColor, color, fontSize, sourceData } = props;
const tabWrapRef = useRef<HTMLDivElement>(null);
@ -22,7 +22,7 @@ const XTab = (props: TabConfigType) => {
return (
<div className={styles.tabWrap} ref={tabWrapRef}>
<Tabs
canSwipe
scrollThreshold={3}
onChange={i => {
console.log(i);
}}

View File

@ -0,0 +1,118 @@
import {
IColorConfigType,
IDataListConfigType,
IMutiTextConfigType,
INumberConfigType,
TColorDefaultType,
TDataListDefaultType,
TMutiTextDefaultType,
TNumberDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TTabEditData = Array<
IMutiTextConfigType | IColorConfigType | INumberConfigType | IDataListConfigType
>;
export interface ITabConfig {
tabs: TMutiTextDefaultType;
color: TColorDefaultType;
activeColor: TColorDefaultType;
fontSize: TNumberDefaultType;
imgSize: TNumberDefaultType;
sourceData: TDataListDefaultType;
}
export interface ITabSchema {
editData: TTabEditData;
config: ITabConfig;
}
const Tab: ITabSchema = {
editData: [
{
key: 'tabs',
name: '项目类别',
type: 'MutiText',
},
{
key: 'activeColor',
name: '激活颜色',
type: 'Color',
},
{
key: 'color',
name: '文字颜色',
type: 'Color',
},
{
key: 'fontSize',
name: '文字大小',
type: 'Number',
},
{
key: 'imgSize',
name: '图片大小',
type: 'Number',
},
{
key: 'sourceData',
name: '数据源',
type: 'DataList',
},
],
config: {
tabs: ['类别一', '类别二'],
color: 'rgba(153,153,153,1)',
activeColor: 'rgba(0,102,204,1)',
fontSize: 16,
imgSize: 100,
sourceData: [
{
id: '1',
title: '趣谈小课1',
desc: '致力于打造优质小课程',
link: 'xxxxx',
type: 0,
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
},
],
},
{
id: '2',
title: '趣谈小课2',
desc: '致力于打造优质小课程',
link: 'xxxxx',
type: 0,
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/2_1740c7033a9.png',
},
],
},
{
id: '3',
title: '趣谈小课3',
desc: '致力于打造优质小课程',
link: 'xxxxx',
type: 1,
imgUrl: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
},
],
},
],
},
};
export default Tab;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Tab',
h: 130,
};
export default template;

View File

@ -0,0 +1,13 @@
import React, { memo } from 'react';
import styles from './index.less';
import { ITextConfig } from './schema';
const Text = memo((props: ITextConfig) => {
const { align, text, fontSize, color, lineHeight } = props;
return (
<div className={styles.textWrap} style={{ color, textAlign: align, fontSize, lineHeight }}>
{text}
</div>
);
});
export default Text;

View File

@ -0,0 +1,78 @@
import {
IColorConfigType,
INumberConfigType,
ISelectConfigType,
ITextConfigType,
TColorDefaultType,
TNumberDefaultType,
TSelectDefaultType,
TTextDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TTextSelectKeyType = 'left' | 'right' | 'center';
export type TTextEditData = Array<
ITextConfigType | IColorConfigType | INumberConfigType | ISelectConfigType<TTextSelectKeyType>
>;
export interface ITextConfig {
text: TTextDefaultType;
color: TColorDefaultType;
fontSize: TNumberDefaultType;
align: TSelectDefaultType<TTextSelectKeyType>;
lineHeight: TNumberDefaultType;
}
export interface ITextSchema {
editData: TTextEditData;
config: ITextConfig;
}
const Text: ITextSchema = {
editData: [
{
key: 'text',
name: '文字',
type: 'Text',
},
{
key: 'color',
name: '标题颜色',
type: 'Color',
},
{
key: 'fontSize',
name: '字体大小',
type: 'Number',
},
{
key: 'align',
name: '对齐方式',
type: 'Select',
range: [
{
key: 'left',
text: '左对齐',
},
{
key: 'center',
text: '居中对齐',
},
{
key: 'right',
text: '右对齐',
},
],
},
{
key: 'lineHeight',
name: '行高',
type: 'Number',
},
],
config: {
text: '我是文本',
color: 'rgba(60,60,60,1)',
fontSize: 18,
align: 'center',
lineHeight: 2,
},
};
export default Text;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Text',
h: 20,
};
export default template;

View File

@ -0,0 +1,28 @@
import Carousel from './Carousel/schema';
import Footer from './Footer/schema';
import Form from './Form/schema';
import Header from './Header/schema';
import Icon from './Icon/schema';
import Image from './Image/schema';
import List from './List/schema';
import LongText from './LongText/schema';
import Notice from './Notice/schema';
import Qrcode from './Qrcode/schema';
import Tab from './Tab/schema';
import Text from './Text/schema';
const basicSchema = {
Carousel,
Footer,
Form,
Header,
Icon,
Image,
List,
LongText,
Notice,
Qrcode,
Tab,
Text,
};
export default basicSchema;

View File

@ -0,0 +1,32 @@
import Carousel from './Carousel/template';
import Footer from './Footer/template';
import Form from './Form/template';
import Header from './Header/template';
import Icon from './Icon/template';
import Image from './Image/template';
import List from './List/template';
import LongText from './LongText/template';
import Notice from './Notice/template';
import Qrcode from './Qrcode/template';
import Tab from './Tab/template';
import Text from './Text/template';
const basicTemplate = [
Carousel,
Footer,
Form,
Header,
Icon,
Image,
List,
LongText,
Notice,
Qrcode,
Tab,
Text,
];
const BasicTemplate = basicTemplate.map(v => {
return { ...v, category: 'base' };
});
export default BasicTemplate;

View File

@ -1,9 +1,9 @@
import React, { memo } from 'react';
import { Player, BigPlayButton } from 'video-react';
import './index.css';
import { VideoConfigType } from '../DynamicEngine/schema';
import { IVideoConfig } from './schema';
const VideoPlayer = memo((props: VideoConfigType) => {
const VideoPlayer = memo((props: IVideoConfig) => {
const { poster, url } = props;
return (
<div>

View File

@ -0,0 +1,45 @@
import {
ITextConfigType,
IUploadConfigType,
TTextDefaultType,
TUploadDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TVideoEditData = Array<IUploadConfigType | ITextConfigType>;
export interface IVideoConfig {
poster: TUploadDefaultType;
url: TTextDefaultType;
}
export interface IVideoSchema {
editData: TVideoEditData;
config: IVideoConfig;
}
const Video: IVideoSchema = {
editData: [
{
key: 'poster',
name: '视频封面',
type: 'Upload',
},
{
key: 'url',
name: '视频链接',
type: 'Text',
},
],
config: {
poster: [
{
uid: '001',
name: 'image.png',
status: 'done',
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
},
],
url: '',
},
};
export default Video;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Video',
h: 107,
};
export default template;

View File

@ -0,0 +1,6 @@
import Video from './Video/schema';
const mediaSchema = {
Video,
};
export default mediaSchema;

View File

@ -0,0 +1,9 @@
import Video from './Video/template';
const mediaTemplate = [Video];
const MediaTemplate = mediaTemplate.map(v => {
return { ...v, category: 'media' };
});
export default MediaTemplate;

View File

@ -1,25 +1,16 @@
import { Chart } from '@antv/f2';
import React, { memo, PropsWithChildren, useEffect, useRef } from 'react';
import React, { memo, useEffect, useRef } from 'react';
// import { uuid } from 'utils/tool';
import ChartImg from '@/assets/chart.png';
import styles from './index.less';
import { IChartConfig } from './schema';
type DataItem = {
name: string;
value: number;
};
interface XChartProps {
interface XChartProps extends IChartConfig {
isTpl: boolean;
title: string;
color: string;
size: number;
paddingTop: number;
data: Array<DataItem>;
}
const XChart = (props: PropsWithChildren<XChartProps>) => {
const XChart = (props: XChartProps) => {
const { isTpl, data, color, size, paddingTop, title } = props;
const chartRef = useRef(null);
useEffect(() => {
@ -44,7 +35,7 @@ const XChart = (props: PropsWithChildren<XChartProps>) => {
// Step 4: 渲染图表
chart.render();
}
}, []);
}, [data, isTpl]);
return (
<div className={styles.chartWrap}>
<div className={styles.chartTitle} style={{ color, fontSize: size, paddingTop }}>

View File

@ -0,0 +1,78 @@
import {
IColorConfigType,
INumberConfigType,
ITableConfigType,
ITextConfigType,
TColorDefaultType,
TNumberDefaultType,
TTableDefaultType,
TTextDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TChartEditData = Array<
ITextConfigType | INumberConfigType | IColorConfigType | ITableConfigType
>;
export interface IChartConfig {
title: TTextDefaultType;
size: TNumberDefaultType;
color: TColorDefaultType;
paddingTop: TNumberDefaultType;
data: TTableDefaultType;
}
export interface IChartSchema {
editData: TChartEditData;
config: IChartConfig;
}
const Chart: IChartSchema = {
editData: [
{
key: 'title',
name: '标题',
type: 'Text',
},
{
key: 'size',
name: '标题大小',
type: 'Number',
},
{
key: 'color',
name: '标题颜色',
type: 'Color',
},
{
key: 'paddingTop',
name: '上边距',
type: 'Number',
},
{
key: 'data',
name: '数据源',
type: 'Table',
},
],
config: {
title: '柱状图',
size: 14,
color: 'rgba(0,0,0,1)',
paddingTop: 10,
data: [
{
name: 'A',
value: 20,
},
{
name: 'B',
value: 60,
},
{
name: 'C',
value: 20,
},
],
},
};
export default Chart;

View File

@ -0,0 +1,5 @@
const template = {
type: 'Chart',
h: 102,
};
export default template;

View File

@ -0,0 +1,21 @@
import React, { memo } from 'react';
import { Progress } from 'zarm';
import styles from './index.less';
import { IXProgressConfig } from './schema';
const XProgress = memo((props: IXProgressConfig) => {
const { theme, size, shape, percent, strokeWidth } = props;
return (
<div className={styles.textWrap} style={{ textAlign: 'center' }}>
<Progress
shape={shape}
size={size}
percent={percent}
theme={theme}
strokeWidth={strokeWidth}
/>
</div>
);
});
export default XProgress;

View File

@ -0,0 +1,95 @@
import {
INumberConfigType,
IRadioConfigType,
ISelectConfigType,
TNumberDefaultType,
TRadioDefaultType,
TSelectDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
export type TXProgressSelectKeyType = 'success' | 'warning' | 'danger';
export type TXProgressRadiotKeyType = 'circle' | 'line' | 'semi-circle';
export type TXProgressEditData = Array<
| ISelectConfigType<TXProgressSelectKeyType>
| IRadioConfigType<TXProgressRadiotKeyType>
| INumberConfigType
>;
export interface IXProgressConfig {
theme: TSelectDefaultType<TXProgressSelectKeyType>;
shape: TRadioDefaultType<TXProgressRadiotKeyType>;
size: TNumberDefaultType;
percent: TNumberDefaultType;
strokeWidth: TNumberDefaultType;
}
export interface IXProgressSchema {
editData: TXProgressEditData;
config: IXProgressConfig;
}
const XProgress: IXProgressSchema = {
editData: [
{
key: 'theme',
name: '主题',
type: 'Select',
range: [
{
key: 'success',
text: '成功',
},
{
key: 'warning',
text: '警告',
},
{
key: 'danger',
text: '危险',
},
],
},
{
key: 'shape',
name: '形状',
type: 'Radio',
range: [
{
key: 'circle',
text: '圆形',
},
{
key: 'line',
text: '线形',
},
{
key: 'semi-circle',
text: '半圆形',
},
],
},
{
key: 'size',
name: '大小',
type: 'Number',
},
{
key: 'percent',
name: '进度值',
type: 'Number',
range: [0, 100],
},
{
key: 'strokeWidth',
name: '线条粗细',
type: 'Number',
},
],
config: {
theme: 'success',
shape: 'circle',
size: 200,
percent: 30,
strokeWidth: 10,
},
};
export default XProgress;

View File

@ -0,0 +1,5 @@
const template = {
type: 'XProgress',
h: 102,
};
export default template;

View File

@ -0,0 +1,8 @@
import Chart from './Chart/schema';
import XProgress from './XProgress/schema';
const visualSchema = {
Chart,
XProgress,
};
export default visualSchema;

View File

@ -0,0 +1,9 @@
import Chart from './Chart/template';
import XProgress from './XProgress/template';
const visualTemplate = [Chart, XProgress];
const VisualTemplate = visualTemplate.map(v => {
return { ...v, category: 'visual' };
});
export default VisualTemplate;

View File

@ -0,0 +1,11 @@
import BasicSchema from './BasicComponents/schema';
import MediaSchema from './MediaComponents/schema';
import VisualSchema from './VisualComponents/schema';
const schema = {
...BasicSchema,
...MediaSchema,
...VisualSchema,
};
export default schema;

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import styles from './index.less';
@ -16,6 +16,45 @@ export default function Calibration(props: CalibrationTypes) {
const [calibrationLength, setCalibration] = useState<calibrationTypes>({ width: 0, height: 0 });
const calibrationRef = useRef<HTMLDivElement>(null);
const generateElement = useCallback(
(item?: boolean, num?: number) => {
if (calibrationRef.current) {
let createSpan = document.createElement('div');
createSpan.className = 'calibrationLine';
createSpan.style.backgroundColor = '#ccc';
calibrationRef.current.style.display = 'flex';
calibrationRef.current.style.justifyContent = 'space-between';
if (direction === 'up') {
calibrationRef.current.style.marginLeft = '50px';
createSpan.style.width = '1px';
createSpan.style.height = '6px';
createSpan.style.display = 'inline-block';
} else {
calibrationRef.current.style.flexDirection = 'column';
createSpan.style.height = '1px';
createSpan.style.width = '6px';
}
if (item) {
let createSpanContent = document.createElement('span');
if (direction === 'up') {
createSpan.style.height = '12px';
createSpanContent.style.transform = 'translate3d(-4px, 20px, 0px)';
createSpan.style.transform = 'translateY(0px)';
} else {
createSpan.style.width = '12px';
createSpanContent.style.paddingLeft = '20px';
}
createSpanContent.style.display = 'block';
createSpanContent.className = 'calibrationNumber';
createSpanContent.innerHTML = num! * 5 + '';
createSpan.appendChild(createSpanContent);
}
calibrationRef.current.appendChild(createSpan);
}
},
[direction],
);
useEffect(() => {
if (calibrationRef.current) {
let calibration = calibrationRef.current.getBoundingClientRect();
@ -29,43 +68,7 @@ export default function Calibration(props: CalibrationTypes) {
}
}
}
}, [direction]);
const generateElement = (item?: boolean, num?: number) => {
if (calibrationRef.current) {
let createSpan = document.createElement('div');
createSpan.className = 'calibrationLine';
createSpan.style.backgroundColor = '#ccc';
calibrationRef.current.style.display = 'flex';
calibrationRef.current.style.justifyContent = 'space-between';
if (direction === 'up') {
calibrationRef.current.style.marginLeft = '50px';
createSpan.style.width = '1px';
createSpan.style.height = '6px';
createSpan.style.display = 'inline-block';
} else {
calibrationRef.current.style.flexDirection = 'column';
createSpan.style.height = '1px';
createSpan.style.width = '6px';
}
if (item) {
let createSpanContent = document.createElement('span');
if (direction === 'up') {
createSpan.style.height = '12px';
createSpanContent.style.transform = 'translate3d(-4px, 20px, 0px)';
createSpan.style.transform = 'translateY(0px)';
} else {
createSpan.style.width = '12px';
createSpanContent.style.paddingLeft = '20px';
}
createSpanContent.style.display = 'block';
createSpanContent.className = 'calibrationNumber';
createSpanContent.innerHTML = num! * 5 + '';
createSpan.appendChild(createSpanContent);
}
calibrationRef.current.appendChild(createSpan);
}
};
}, [direction, generateElement]);
useEffect(() => {
if (calibrationRef.current) {

View File

@ -1,160 +0,0 @@
import { memo } from 'react';
import { NoticeBar, Progress } from 'zarm';
import styles from './components.less';
import React from 'react';
import {
HeaderConfigType,
TextConfigType,
LongTextConfigType,
NoticeConfigType,
QRCodeConfigType,
FooterConfigType,
ImageConfigType,
ListConfigType,
XProgressConfigType,
} from './schema';
const Header = memo((props: HeaderConfigType) => {
const { bgColor, logo, logoText, fontSize, color } = props;
return (
<header className={styles.header} style={{ backgroundColor: bgColor }}>
<div className={styles.logo}>
<img src={logo && logo[0].url} alt={logoText} />
</div>
<div className={styles.title} style={{ fontSize, color }}>
{logoText}
</div>
</header>
);
});
const Text = memo((props: TextConfigType) => {
const { align, text, fontSize, color, lineHeight } = props;
return (
<div className={styles.textWrap} style={{ color, textAlign: align, fontSize, lineHeight }}>
{text}
</div>
);
});
const Notice = memo((props: NoticeConfigType) => {
const { text, speed, theme, link, isClose = false } = props;
return (
<NoticeBar theme={theme === 'default' ? undefined : theme} closable={isClose} speed={speed}>
<a style={{ color: 'inherit' }}>{text}</a>
</NoticeBar>
);
});
const Qrcode = memo((props: QRCodeConfigType) => {
const { qrcode, text, color, fontSize = 14 } = props;
return (
<div style={{ width: '240px', margin: '16px auto' }}>
<img src={qrcode && qrcode[0].url} alt={text} style={{ width: '100%' }} />
<div style={{ textAlign: 'center', color, fontSize, paddingTop: '8px' }}>{text}</div>
</div>
);
});
const LongText = memo((props: LongTextConfigType) => {
const { text, fontSize, color, indent, lineHeight, textAlign } = props;
return (
<div
className={styles.textWrap}
style={{ color, textIndent: indent + 'px', fontSize, lineHeight, textAlign }}
>
{text}
</div>
);
});
const Footer = memo((props: FooterConfigType) => {
const { bgColor, text, color, align, fontSize, height } = props;
return (
<footer
style={{
backgroundColor: bgColor,
color,
fontSize,
textAlign: align,
height,
lineHeight: height + 'px',
}}
>
{text}
</footer>
);
});
const Image = memo((props: ImageConfigType) => {
const { imgUrl, round = 0 } = props;
return (
<div style={{ borderRadius: round, width: '100%', textAlign: 'center', overflow: 'hidden' }}>
<img src={imgUrl && imgUrl[0].url} alt="" style={{ width: '100%' }} />
</div>
);
});
const List = memo((props: ListConfigType) => {
const { round, sourceData, imgSize, fontSize, color } = props;
return (
<div className={styles.list}>
<div className={styles.sourceList}>
{sourceData.map((item, i) => {
return (
<div className={styles.sourceItem} key={i}>
<div className={styles.imgWrap}>
<img
src={
item.imgUrl[0]
? item.imgUrl[0].url
: 'http://io.nainor.com/uploads/01_173e15d3493.png'
}
alt={item.desc}
style={{
width: imgSize,
height: imgSize,
objectFit: 'cover',
borderRadius: round,
}}
/>
</div>
<div className={styles.content}>
<a
className={styles.tit}
style={{ fontSize, color }}
href={item.link ? item.link : '#'}
>
{item.title}
<div
className={styles.desc}
style={{ fontSize: fontSize * 0.8, color: 'rgba(0,0,0, .3)' }}
>
{item.desc}
</div>
</a>
</div>
</div>
);
})}
</div>
</div>
);
});
const XProgress = memo((props: XProgressConfigType) => {
const { theme, size, shape, percent, strokeWidth } = props;
return (
<div className={styles.textWrap} style={{ textAlign: 'center' }}>
<Progress
shape={shape}
size={size}
percent={percent}
theme={theme}
strokeWidth={strokeWidth}
/>
</div>
);
});
export { Header, Text, LongText, Notice, Qrcode, Footer, Image, List, XProgress };

View File

@ -1,17 +0,0 @@
import { BasicTemplateItem } from './schema';
export type GraphTplKeyType = 'XProgress' | 'Chart';
export type GraphTplType = Array<BasicTemplateItem<GraphTplKeyType>>;
const graphTpl: GraphTplType = [
{
type: 'XProgress',
h: 102,
},
{
type: 'Chart',
h: 102,
},
];
export default graphTpl;

View File

@ -3,22 +3,23 @@ import Loading from '../LoadingCp';
import { useMemo, memo, FC } from 'react';
import React from 'react';
import { AllTemplateType } from './schema';
const needList = ['Tab', 'Carousel', 'Upload', 'Video', 'Icon', 'Chart'];
const DynamicFunc = (type: AllTemplateType) =>
export type componentsType = 'media' | 'base' | 'visible';
const DynamicFunc = (type: AllTemplateType, componentsType: componentsType) =>
dynamic({
loader: async function() {
let Component: FC<{ isTpl: boolean }>;
if (needList.includes(type)) {
const { default: Graph } = await import(`@/components/${type}`);
if (componentsType === 'base') {
const { default: Graph } = await import(`@/components/BasicShop/BasicComponents/${type}`);
Component = Graph;
} else if (componentsType === 'media') {
const { default: Graph } = await import(`@/components/BasicShop/MediaComponents/${type}`);
Component = Graph;
} else {
const Components = ((await import(`@/components/DynamicEngine/components`)) as unknown) as {
[key: string]: FC;
};
Component = Components[type];
const { default: Graph } = await import(`@/components/BasicShop/VisualComponents/${type}`);
Component = Graph;
}
return (props: DynamicType) => {
const { config, isTpl } = props;
return <Component {...config} isTpl={isTpl} />;
@ -35,14 +36,17 @@ type DynamicType = {
isTpl: boolean;
config: { [key: string]: any };
type: AllTemplateType;
componentsType: componentsType;
category: componentsType;
};
const DynamicEngine = memo((props: DynamicType) => {
const { type, config, isTpl } = props;
const { type, config, category } = props;
const Dynamic = useMemo(() => {
return (DynamicFunc(type) as unknown) as FC<DynamicType>;
return (DynamicFunc(type, category) as unknown) as FC<DynamicType>;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [type, config]);
return <Dynamic type={type} config={config} isTpl={isTpl} />;
return <Dynamic {...props} />;
});
export default DynamicEngine;

View File

@ -1,13 +0,0 @@
import { BasicTemplateItem } from './schema';
export type MediaTplKeyType = 'Video';
export type MediaTplType = Array<BasicTemplateItem<MediaTplKeyType>>;
const mediaTpl: MediaTplType = [
{
type: 'Video',
h: 107,
},
];
export default mediaTpl;

File diff suppressed because it is too large Load Diff

View File

@ -1,65 +0,0 @@
import { BasicTemplateItem } from './schema';
export type TemplateKeyType =
| 'Text'
| 'LongText'
| 'Carousel'
| 'Tab'
| 'Notice'
| 'Qrcode'
| 'Icon'
| 'Image'
| 'Header'
| 'List'
| 'Footer';
export type TemplateType = Array<BasicTemplateItem<TemplateKeyType>>;
const template: TemplateType = [
{
type: 'Text',
h: 20,
},
{
type: 'LongText',
h: 36,
},
{
type: 'Carousel',
h: 82,
},
{
type: 'Tab',
h: 130,
},
{
type: 'Notice',
h: 20,
},
{
type: 'Qrcode',
h: 150,
},
{
type: 'Icon',
h: 23,
},
{
type: 'Image',
h: 188,
},
{
type: 'Header',
h: 28,
},
{
type: 'List',
h: 110,
},
{
type: 'Footer',
h: 24,
},
];
export default template;

View File

@ -1,12 +1,16 @@
import React from 'react';
import React, { ErrorInfo, PropsWithChildren } from 'react';
class ErrorBoundary extends React.Component<any, any> {
constructor(props: any) {
interface ErrorBoundaryState {
hasError: boolean;
}
class ErrorBoundary extends React.Component<PropsWithChildren<{}>, ErrorBoundaryState> {
constructor(props: PropsWithChildren<{}>) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error: any, info: any) {
componentDidCatch(_error: Error, _info: ErrorInfo) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service

View File

@ -1,104 +0,0 @@
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

@ -1,19 +1,18 @@
import { useState, useEffect, memo } from 'react';
import classnames from 'classnames';
import Icon from '../Icon/';
import Icon from '../../BasicShop/BasicComponents/Icon';
import styles from './index.less';
import { IconTypes } from '../DynamicEngine/schema';
import React from 'react';
import { IconTypes } from '@/components/BasicShop/BasicComponents/Icon/schema';
import { ICardPickerConfigType } from '../FormEditor/types';
interface CardPickerType {
type: IconTypes;
icons: Array<IconTypes>;
interface CardPickerType extends Omit<ICardPickerConfigType<IconTypes>, 'type' | 'key' | 'name'> {
onChange?: (v: string) => void;
type: IconTypes;
}
export default memo((props: CardPickerType) => {
const { type, icons, onChange } = props;
console.log(type);
const [selected, setSelected] = useState<IconTypes>(type);
const handlePicker = (v: IconTypes) => {
@ -37,7 +36,7 @@ export default memo((props: CardPickerType) => {
onClick={() => handlePicker(item)}
key={i}
>
<Icon type={item} size={20} />
<Icon type={item} size={20} color={'#4091f7'} spin={false} />
</span>
);
})}

View File

@ -3,10 +3,12 @@ import { SketchPicker, ColorResult } from 'react-color';
import { rgba2Obj } from '@/utils/tool';
// import styles from './index.less'
export type ColorConfigType = string;
//value 初始值传来onchange item给的回调
interface ColorProps {
value?: string;
id?: string;
onChange?: (v: string) => void;
value?: ColorConfigType;
onChange?: (v: ColorConfigType) => void;
}
class colorPicker extends React.Component<ColorProps> {

View File

@ -1,13 +1,10 @@
import React, { memo, useEffect, FC } from 'react';
import { Form, Select, Input, Modal } from 'antd';
import Upload from '@/components/Upload';
import { BasicDataSource } from '../DynamicEngine/schema';
import Upload from '../Upload';
import { Store } from 'antd/lib/form/interface';
import { TDataListDefaultTypeItem } from '../FormEditor/types';
// import styles from './index.less';
const normFile = (e: any) => {
console.log('Upload event:', e);
console.log('ffffffffff'); //待修改?
if (Array.isArray(e)) {
return e;
}
@ -24,7 +21,7 @@ const formItemLayout = {
export type EditorModalProps = {
visible: boolean;
onCancel: ((e: React.MouseEvent<HTMLElement, MouseEvent>) => void) | undefined;
item?: BasicDataSource;
item?: TDataListDefaultTypeItem;
onSave: Function;
};

View File

@ -18,7 +18,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import EditorModal from './editorModal';
import { uuid } from '@/utils/tool';
import styles from './index.less';
import { BasicDataSource } from '../DynamicEngine/schema';
import { TDataListDefaultType, TDataListDefaultTypeItem } from '../FormEditor/types';
type ListItemProps = DndItemProps & {
isDragging: boolean;
@ -31,9 +31,6 @@ function ListItem(props: ListItemProps) {
const {
title,
desc,
link,
imgUrl,
type,
onDel,
onEdit,
// 这些 props 由 React DnD注入参考`collect`函数定义
@ -69,11 +66,10 @@ function ListItem(props: ListItemProps) {
);
}
type DndItemProps = BasicDataSource & {
type DndItemProps = TDataListDefaultTypeItem & {
onDel: Function;
onEdit: Function;
key: number;
id: string;
find: Function;
move: Function;
type?: number;
@ -131,8 +127,8 @@ const DndItem = DropTarget(
)(DragSource(type, dragSpec, dragCollect)(ListItem));
export type DataListMemo = {
onChange?: (v: BasicDataSource[]) => void;
value?: BasicDataSource[];
onChange?: (v: TDataListDefaultType) => void;
value?: TDataListDefaultType;
};
export type DataListType = DataListMemo & {
@ -143,7 +139,7 @@ const List = function(props: DataListType) {
const { onChange, value, connectDropTarget } = props;
const [list, setList] = useState(value);
const [visible, setVisible] = useState(false);
const [curItem, setCurItem] = useState<BasicDataSource>();
const [curItem, setCurItem] = useState<TDataListDefaultTypeItem>();
const handleDel = (id: string) => {
if (value && onChange) {
@ -176,13 +172,13 @@ const List = function(props: DataListType) {
setVisible(false);
}, []);
const handleEdit = useCallback((item: BasicDataSource) => {
const handleEdit = useCallback((item: TDataListDefaultTypeItem) => {
setVisible(true);
setCurItem(item);
}, []);
const handleSave = useCallback(
(item: BasicDataSource) => {
(item: TDataListDefaultTypeItem) => {
setVisible(false);
if (onChange) {
onChange(list!.map(p => (p.id === item.id ? item : p)));

View File

@ -1,4 +1,4 @@
import React, { memo, useState, useEffect } from 'react';
import React, { memo, useEffect } from 'react';
import { Form, Select, InputNumber, Input, Switch, Radio, Button } from 'antd';
import Upload from '../Upload';
import DataList from '../DataList';
@ -7,7 +7,7 @@ import Color from '../Color';
import CardPicker from '../CardPicker';
import Table from '../Table';
import { Store } from 'antd/lib/form/interface';
import { BasicRangeType, IconSchema } from '../DynamicEngine/schema';
import FormItems from '../FormItems';
// import styles from './index.less';
const normFile = (e: any) => {
console.log('Upload event:', e);
@ -36,6 +36,7 @@ interface FormEditorProps {
const FormEditor = (props: FormEditorProps) => {
const { config, defaultValue, onSave, onDel, uid } = props;
console.log(defaultValue, config);
const onFinish = (values: Store) => {
onSave && onSave(values);
};
@ -50,8 +51,7 @@ const FormEditor = (props: FormEditorProps) => {
return () => {
form.resetFields();
};
}, [defaultValue]);
}, [defaultValue, form]);
return (
<Form
form={form}
@ -96,7 +96,7 @@ const FormEditor = (props: FormEditorProps) => {
{item.type === 'Select' && (
<Form.Item label={item.name} name={item.key}>
<Select placeholder="请选择">
{item.range.map((v: BasicRangeType<string>, i: number) => {
{item.range.map((v: any, i: number) => {
return (
<Option value={v.key} key={i}>
{v.text}
@ -109,7 +109,7 @@ const FormEditor = (props: FormEditorProps) => {
{item.type === 'Radio' && (
<Form.Item label={item.name} name={item.key}>
<Radio.Group>
{item.range.map((v: BasicRangeType<string>, i: number) => {
{item.range.map((v: any, i: number) => {
return (
<Radio value={v.key} key={i}>
{v.text}
@ -136,10 +136,7 @@ const FormEditor = (props: FormEditorProps) => {
)}
{item.type === 'CardPicker' && (
<Form.Item label={item.name} name={item.key} valuePropName="type">
<CardPicker
icons={item.icons}
type={defaultValue['type'] as IconSchema['config']['type']}
/>
<CardPicker icons={item.icons} type={defaultValue['type']} />
</Form.Item>
)}
{item.type === 'Table' && (
@ -147,6 +144,11 @@ const FormEditor = (props: FormEditorProps) => {
<Table data={item.data} />
</Form.Item>
)}
{item.type === 'FormItems' && (
<Form.Item label={item.name} name={item.key} valuePropName="formList">
<FormItems data={item.data} />
</Form.Item>
)}
</React.Fragment>
);
})}

View File

@ -0,0 +1,199 @@
////////////////////
export interface IUploadConfigType {
key: string;
name: string;
type: 'Upload';
isCrop?: boolean;
cropRate?: number;
}
export type TUploadDefaultType = Array<{
uid: string;
name: string;
status: string;
url: string;
}>;
/////////////////
export interface ITextConfigType {
key: string;
name: string;
type: 'Text';
}
export type TTextDefaultType = string;
////////////////////////
export interface ITextAreaConfigType {
key: string;
name: string;
type: 'TextArea';
}
export type TTextAreaDefaultType = string;
////////////////////////////
export interface INumberConfigType {
key: string;
name: string;
type: 'Number';
range?: [number, number];
step?: number;
}
export type TNumberDefaultType = number;
///////////////////
export interface IDataListConfigType {
key: string;
name: string;
type: 'DataList';
}
export type TDataListDefaultTypeItem = {
id: string;
title: string;
desc: string;
link: string;
type?: number;
imgUrl: TUploadDefaultType;
};
export type TDataListDefaultType = Array<TDataListDefaultTypeItem>;
////////////////////
export interface IColorConfigType {
key: string;
name: string;
type: 'Color';
}
export type TColorDefaultType = string;
export interface IMutiTextConfigType {
key: string;
name: string;
type: 'MutiText';
}
export type TMutiTextDefaultType = Array<string>;
/////////////////////////////////
export interface ISelectConfigType<KeyType> {
key: string;
name: string;
type: 'Select';
range: Array<{
key: KeyType;
text: string;
}>;
}
export type TSelectDefaultType<KeyType> = KeyType;
/////////////////////////
export interface IRadioConfigType<KeyType> {
key: string;
name: string;
type: 'Radio';
range: Array<{
key: KeyType;
text: string;
}>;
}
export type TRadioDefaultType<KeyType> = KeyType;
///////////////
export interface ISwitchConfigType {
key: string;
name: string;
type: 'Switch';
}
export type TSwitchDefaultType = boolean;
/////////////////////////////
export interface ICardPickerConfigType<T> {
key: string;
name: string;
type: 'CardPicker';
icons: Array<T>;
}
export type TCardPickerDefaultType<T> = T;
/////////////
export interface ITableConfigType {
key: string;
name: string;
type: 'Table';
}
export type TTableDefaultType = Array<{
name: string;
value: number;
}>;
//////////////////
export interface IFormItemsConfigType {
key: string;
name: string;
type: 'FormItems';
}
//0---------baseform
export type baseFormOptionsType = {
label: string;
value: string;
};
export type baseFormTextTpl = {
id: string;
type: 'Text';
label: string;
placeholder: string;
};
export type baseFormNumberTpl = {
id: string;
type: 'Number';
label: string;
placeholder: string;
};
export type baseFormTextAreaTpl = {
id: string;
type: 'Textarea';
label: string;
placeholder: string;
};
export type baseFormMyRadioTpl = {
id: string;
type: 'MyRadio';
label: string;
options: baseFormOptionsType[];
};
export type baseFormMySelectTpl = {
id: string;
type: 'MySelect';
label: string;
options: baseFormOptionsType[];
};
export type baseFormDateTpl = {
id: string;
type: 'Date';
label: string;
placeholder: string;
};
export type baseFormUnion =
| baseFormTextTpl
| baseFormNumberTpl
| baseFormTextAreaTpl
| baseFormMyRadioTpl
| baseFormMySelectTpl
| baseFormDateTpl;
export type baseFormUnionType =
| baseFormTextTpl['type']
| baseFormNumberTpl['type']
| baseFormTextAreaTpl['type']
| baseFormMyRadioTpl['type']
| baseFormMySelectTpl['type']
| baseFormDateTpl['type'];
export type TFormItemsDefaultType = Array<baseFormUnion>;

View File

@ -0,0 +1,114 @@
import React, { FC, memo, useEffect } from 'react';
import { Form, Select, Input, Modal } from 'antd';
import { baseFormOptionsType, baseFormUnion } from '../FormEditor/types';
import { Store } from 'antd/lib/form/interface';
const { Option } = Select;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
interface EditorModalProps {
item: any;
onSave: (data: any) => void;
visible: boolean;
onCancel: () => void;
}
const EditorModal: FC<EditorModalProps> = props => {
const { item, onSave, visible, onCancel } = props;
const onFinish = (values: any) => {
onSave && onSave(values);
};
const handleOk = () => {
form
.validateFields()
.then(values => {
values.id = item.id;
onSave && onSave(values);
})
.catch(err => {
console.log(err);
});
};
const [form] = Form.useForm();
useEffect(() => {
return () => {
form.resetFields();
};
}, [form, 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: baseFormOptionsType, i: number) => {
return (
<Option value={v.value} key={i}>
{v.label}
</Option>
);
})}
</Select>
</Form.Item>
)}
</Form>
</Modal>
)}
</>
);
};
export default memo(EditorModal);

View File

@ -1,14 +1,13 @@
import React, { memo, useState, useEffect } from 'react';
import BaseForm from './BaseForm';
import React, { memo, useState } from 'react';
import BaseForm from '../../BasicShop/BasicComponents/Form/BaseForm';
import EditorModal from './EditorModal';
import { EditOutlined, MinusCircleOutlined } from '@ant-design/icons';
import styles from './formItems.less';
import { baseFormUnion, TFormItemsDefaultType } from '../FormEditor/types';
// import { Popconfirm } from 'antd';
import styles from './formItems.less';
const formTpl = [
const formTpl: TFormItemsDefaultType = [
{
id: '1',
type: 'Text',
@ -50,28 +49,35 @@ const formTpl = [
id: '6',
type: 'Date',
label: '日期框',
placeholder: '',
},
];
const FormItems = props => {
const { formList, onChange } = props;
const [formData, setFormData] = useState(formList || []);
const [visible, setVisible] = useState(false);
const [curItem, setCurItem] = useState();
interface FormItemsProps {
formList?: TFormItemsDefaultType;
onChange?: (v: TFormItemsDefaultType) => void;
data: any;
}
const handleAddItem = item => {
const FormItems = (props: FormItemsProps) => {
const { formList, onChange } = props;
const [formData, setFormData] = useState<TFormItemsDefaultType>(formList || []);
const [visible, setVisible] = useState(false);
const [curItem, setCurItem] = useState<baseFormUnion>();
const handleAddItem = (item: baseFormUnion) => {
let tpl = formTpl.find(v => v.type === item.type);
let newData = [...formData, tpl];
let newData = [...formData, tpl!];
setFormData(newData);
onChange && onChange(newData);
};
const handleEditItem = item => {
const handleEditItem = (item: baseFormUnion) => {
setVisible(true);
setCurItem(item);
};
const handleDelItem = item => {
const handleDelItem = (item: baseFormUnion) => {
let newData = formData.filter(v => v.type !== item.type);
setFormData(newData);
onChange && onChange(newData);
@ -81,7 +87,7 @@ const FormItems = props => {
setVisible(false);
};
const handleSaveItem = data => {
const handleSaveItem = (data: baseFormUnion) => {
let newData = formData.map(v => (v.type === data.type ? data : v));
setFormData(newData);
onChange && onChange(newData);
@ -90,7 +96,7 @@ const FormItems = props => {
return (
<div className={styles.formItemWrap}>
<div className={styles.editForm}>
{formData.map((item, i) => {
{formData.map((item: baseFormUnion, i: number) => {
let FormItem = BaseForm[item.type];
return (
<div className={styles.formItem} key={i}>

View File

@ -0,0 +1,2 @@
import FormItems from './FormItems';
export default FormItems;

View File

@ -2,11 +2,11 @@ import React, { memo, useState, useEffect } from 'react';
import { Input, Button, Popconfirm } from 'antd';
import { MinusCircleOutlined } from '@ant-design/icons';
import styles from './index.less';
import { TabConfigType } from '../DynamicEngine/schema';
import { TMutiTextDefaultType } from '../FormEditor/types';
type MultiTextProps = {
onChange?: (v: TabConfigType['tabs']) => void;
value?: TabConfigType['tabs'];
onChange?: (v: TMutiTextDefaultType) => void;
value?: TMutiTextDefaultType;
};
export default memo(function MutiText(props: MultiTextProps) {

View File

@ -1,9 +1,10 @@
import React, { useContext, useState, useEffect, useRef, memo } from 'react';
import React, { useContext, useState, useEffect, useRef, memo, RefObject } from 'react';
import { Table, Input, Button, Popconfirm, Form, Modal } from 'antd';
// 下方样式主要为全局样式,暂时不可删
import styles from './index.less';
import { ColumnsType } from 'antd/lib/table';
const EditableContext = React.createContext<any>();
const EditableContext = React.createContext<any>(null);
interface Item {
key: string;
@ -32,7 +33,7 @@ interface EditableCellProps {
editable: boolean;
children: React.ReactNode;
dataIndex: string;
record: Item;
record: any;
handleSave: (record: Item) => void;
}
@ -46,12 +47,12 @@ const EditableCell: React.FC<EditableCellProps> = ({
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef();
const inputRef = useRef<HTMLInputElement>(null);
const form = useContext(EditableContext);
useEffect(() => {
if (editing) {
inputRef.current.focus();
inputRef.current?.focus();
}
}, [editing]);
@ -60,10 +61,9 @@ const EditableCell: React.FC<EditableCellProps> = ({
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async e => {
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
@ -85,7 +85,11 @@ const EditableCell: React.FC<EditableCellProps> = ({
},
]}
>
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
<Input
ref={(inputRef as unknown) as () => RefObject<HTMLInputElement>}
onPressEnter={save}
onBlur={save}
/>
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
@ -97,8 +101,19 @@ const EditableCell: React.FC<EditableCellProps> = ({
return <td {...restProps}>{childNode}</td>;
};
class EditableTable extends React.Component {
constructor(props) {
class EditableTable extends React.Component<any, any> {
columns: (
| { title: string; dataIndex: string; width: string; editable: boolean; render?: undefined }
| {
title: string;
dataIndex: string;
render: (text: string, record: any) => JSX.Element | null;
width?: undefined;
editable?: undefined;
}
)[];
constructor(props: any) {
super(props);
this.columns = [
{
@ -119,13 +134,14 @@ class EditableTable extends React.Component {
render: (text: string, record) =>
this.state.dataSource.length >= 1 ? (
<Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
<a></a>
<Button type="link"></Button>
</Popconfirm>
) : null,
},
];
const dataSource = props.data.map((item, i: number) => ({ key: i + '', ...item }));
const dataSource =
props.data && props.data.map((item: any, i: number) => ({ key: i + '', ...item }));
this.state = {
dataSource: dataSource,
@ -156,7 +172,7 @@ class EditableTable extends React.Component {
this.props.onChange && this.props.onChange(newDataSource);
};
handleSave = row => {
handleSave = (row: any) => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
@ -174,14 +190,14 @@ class EditableTable extends React.Component {
});
};
handleOk = e => {
handleOk = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
console.log(e);
this.setState({
visible: false,
});
};
handleCancel = e => {
handleCancel = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
console.log(e);
this.setState({
visible: false,
@ -196,7 +212,7 @@ class EditableTable extends React.Component {
cell: EditableCell,
},
};
const columns = this.columns.map(col => {
const columns: ColumnsType<any> = this.columns.map(col => {
if (!col.editable) {
return col;
}

View File

@ -6,4 +6,4 @@
:global(.ant-upload-select-picture-card .ant-upload-text) {
margin-top: 8px;
color: #666;
}
}

View File

@ -4,9 +4,7 @@ import { PlusOutlined } from '@ant-design/icons';
import ImgCrop from 'antd-img-crop';
import styles from './index.less';
import { UploadFile, UploadChangeParam, RcFile } from 'antd/lib/upload/interface';
const isDev = process.env.NODE_ENV === 'development';
import { isDev } from '@/utils/tool';
function getBase64(file: File | Blob) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();

View File

@ -2,9 +2,10 @@ import React, { memo } from 'react';
import { Button, Popover } from 'antd';
import styles from './index.less';
///这组件写的有问题 popover会重定位
const content = (
<div className={styles.imgWrap}>
<img src="http://io.nainor.com/uploads/WechatIMG2_1742b586c3d.jpeg" />
<img src="http://io.nainor.com/uploads/WechatIMG2_1742b586c3d.jpeg" alt="sponsorship" />
</div>
);

View File

@ -14,7 +14,7 @@ library.push(
text: (
<div>
<a href="https://github.com/MrXujiang">@徐小夕</a>
<a href="https://github.com/yehuozhili/learnsinglespa">@yehuozhili</a>
<a href="https://github.com/yehuozhili">@yehuozhili</a>
</div>
),
useReg: /(.*?)作者是谁(.*?)/,

View File

@ -13,11 +13,11 @@ import SourceBox from './SourceBox';
import TargetBox from './TargetBox';
import Calibration from 'components/Calibration';
import DynamicEngine from 'components/DynamicEngine';
import FormEditor from 'components/FormEditor';
import template from 'components/DynamicEngine/template';
import mediaTpl from 'components/DynamicEngine/mediaTpl';
import graphTpl from 'components/DynamicEngine/graphTpl';
import schema from 'components/DynamicEngine/schema';
import FormEditor from 'components/PanelComponents/FormEditor';
import template from 'components/BasicShop/BasicComponents/template';
import mediaTpl from 'components/BasicShop/MediaComponents/template';
import graphTpl from 'components/BasicShop/VisualComponents/template';
import schema from 'components/BasicShop/schema';
import { ActionCreators } from 'redux-undo';
import styles from './index.less';
@ -63,7 +63,6 @@ const Container = props => {
setScale(prev => (prev <= 0.5 ? 0.5 : prev - 0.1));
}
};
const handleFormSave = data => {
dispatch({
type: 'editorModal/modPointData',
@ -90,7 +89,7 @@ const Container = props => {
useEffect(() => {
if (window.innerWidth < 1024) {
props.history.push('/mobileTip');
}
} //待修改
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -135,23 +134,40 @@ const Container = props => {
<div className={styles.componentList}>
<Tabs defaultActiveKey="1">
<TabPane tab={generateHeader('base', '基础组件')} key="1">
{template.map((value, i) => (
<TargetBox item={value} key={i} canvasId={canvasId}>
<DynamicEngine {...value} config={schema[value.type].config} isTpl={true} />
</TargetBox>
))}
{template.map((value, i) => {
return (
<TargetBox item={value} key={i} canvasId={canvasId}>
<DynamicEngine
{...value}
config={schema[value.type].config}
componentsType="base"
isTpl={true}
/>
</TargetBox>
);
})}
</TabPane>
<TabPane tab={generateHeader('media', '媒体组件')} key="2">
{mediaTpl.map((value, i) => (
<TargetBox item={value} key={i} canvasId={canvasId}>
<DynamicEngine {...value} config={schema[value.type].config} isTpl={true} />
<DynamicEngine
{...value}
config={schema[value.type].config}
componentsType="media"
isTpl={true}
/>
</TargetBox>
))}
</TabPane>
<TabPane tab={generateHeader('visible', '可视化组件')} key="3">
{graphTpl.map((value, i) => (
<TargetBox item={value} key={i} canvasId={canvasId}>
<DynamicEngine {...value} config={schema[value.type].config} isTpl={true} />
<DynamicEngine
{...value}
config={schema[value.type].config}
componentsType="visible"
isTpl={true}
/>
</TargetBox>
))}
</TabPane>

View File

@ -79,7 +79,6 @@ const SourceBox = memo(props => {
window.clearTimeout(timer);
};
}, []);
const opacity = isOver ? 0.7 : 1;
const backgroundColor = isOver ? 1 : 0.7;
return (

View File

@ -1,7 +1,6 @@
import React, { useMemo, memo } from 'react';
import { useDrag } from 'react-dnd';
import schema from 'components/DynamicEngine/schema';
import schema from 'components/BasicShop/schema';
import styles from './index.less';
const TargetBox = memo(props => {
@ -12,6 +11,7 @@ const TargetBox = memo(props => {
config: schema[item.type].config,
h: item.h,
editableEl: schema[item.type].editData,
category: item.category,
},
collect: monitor => ({
isDragging: monitor.isDragging(),

Some files were not shown because too many files have changed in this diff Show More