🔨 代码部分重构,渲染层和数据层分离,核心组件放在core文件夹

This commit is contained in:
xujiang 2020-12-13 18:09:24 +08:00
parent a1516cfffa
commit 5a8d62d98a
59 changed files with 157 additions and 1249 deletions

View File

@ -1,36 +0,0 @@
import { memo } from 'react';
import { BackToTop, Icon } from 'zarm';
import React from 'react';
const themeObj = {
simple: { bgColor: '#fff', color: '#999' },
black: { bgColor: '#000', color: '#fff' },
danger: { bgColor: '#ff5050', color: '#fff' },
primary: { bgColor: '#00bc71', color: '#fff' },
blue: { bgColor: '#06c', color: '#fff' },
};
const BackTop = memo((props: { theme: keyof typeof themeObj }) => {
const { theme = 'simple' } = props;
return (
<BackToTop>
<div
style={{
width: 48,
height: 48,
lineHeight: '48px',
textAlign: 'center',
backgroundColor: themeObj[theme].bgColor,
color: themeObj[theme].color,
fontSize: 20,
borderRadius: 30,
boxShadow: '0 2px 10px 0 rgba(0, 0, 0, 0.2)',
cursor: 'pointer',
}}
>
<Icon type="arrow-top" />
</div>
</BackToTop>
);
});
export default BackTop;

View File

@ -7,7 +7,7 @@ import {
TDataListDefaultType,
TNumberDefaultType,
TSelectDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
} from '@/core/FormComponents/types';
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
export type TListSelectKeyType = '60' | '80' | '100' | '120' | '150';
export type TListEditData = Array<

View File

@ -7,7 +7,7 @@ import {
TNumberDefaultType,
TSelectDefaultType,
TTextAreaDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
} from '@/core/FormComponents/types';
export type TLongTextSelectKeyType = 'left' | 'center' | 'right';
export type TLongTextEditData = Array<

View File

@ -7,7 +7,7 @@ import {
TNumberDefaultType,
TSelectDefaultType,
TTextDefaultType,
} from '@/components/PanelComponents/FormEditor/types';
} from '@/core/FormComponents/types';
export type TTextSelectKeyType = 'left' | 'right' | 'center';
export type TTextEditData = Array<

View File

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

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

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

View File

@ -1,27 +0,0 @@
.tabWrap {
padding-top: 16px;
padding-bottom: 16px;
.content {
display: flex;
flex-wrap: wrap;
.item {
padding: 20px 20px 0;
width: 50%;
text-align: center;
justify-content: center;
.imgWrap {
display: inline-block;
width: 80%;
img {
border-radius: 6px;
width: 120px;
height: 120px;
object-fit: cover;
}
.title {
line-height: 2.4;
}
}
}
}
}

View File

@ -1,59 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { Tabs } from 'zarm';
import styles from './index.less';
import { ITabConfig } from './schema';
const { Panel } = Tabs;
const XTab = (props: ITabConfig) => {
const { tabs = ['分类一', '分类二'], activeColor, color, fontSize, sourceData } = props;
const tabWrapRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (tabWrapRef.current) {
let res = tabWrapRef.current.querySelector('.za-tabs__line') as HTMLElement;
if (res) {
res.style.backgroundColor = activeColor;
}
}
}, [activeColor]);
return (
<div className={styles.tabWrap} ref={tabWrapRef}>
<Tabs scrollThreshold={3}>
{tabs.map((item, i) => {
return (
<Panel title={item} key={i}>
<div className={styles.content}>
{sourceData
.filter(item => item.type === i)
.map((item, i) => {
return (
<div className={styles.item} key={i}>
<a className={styles.imgWrap} href={item.link} title={item.desc}>
<img
src={
item.imgUrl[0]
? item.imgUrl[0].url
: 'http://io.nainor.com/uploads/01_173e15d3493.png'
}
alt={item.title}
/>
<div className={styles.title} style={{ fontSize, color }}>
{item.title}
</div>
</a>
</div>
);
})}
</div>
</Panel>
);
})}
</Tabs>
</div>
);
};
export default XTab;

View File

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

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

View File

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

@ -1,32 +0,0 @@
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,10 +0,0 @@
.carousel__item__pic {
display: inline-block;
width: 100%;
max-height: 220px;
overflow: hidden;
vertical-align: top;
img {
width: 100%;
}
}

View File

@ -1,44 +0,0 @@
import React, { memo, PropsWithChildren } from 'react';
import { Carousel } from 'zarm';
import styles from './index.less';
import { ICarouselConfig } from './schema';
interface CarouselTypes extends ICarouselConfig {
isTpl: boolean;
}
const XCarousel = memo((props: PropsWithChildren<CarouselTypes>) => {
const { direction, swipeable, autoPlay, isTpl, imgList, tplImg } = props;
const contentRender = () => {
return imgList.map((item, i) => {
return (
<div className={styles.carousel__item__pic} key={+i}>
<img src={item.imgUrl.length > 0 ? item.imgUrl[0].url : ''} alt="" />
</div>
);
});
};
return (
<div style={{ width: '100%', overflow: 'hidden' }}>
{isTpl ? (
<div className={styles.carousel__item__pic}>
<img src={tplImg} alt="" />
</div>
) : (
<Carousel
onChange={index => {
// console.log(`onChange: ${index}`);
}}
direction={direction}
swipeable={swipeable}
autoPlay={autoPlay}
loop
>
{contentRender()}
</Carousel>
)}
</div>
);
});
export default XCarousel;

View File

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

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

View File

@ -1,96 +0,0 @@
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渲染性能
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}>
<Input clearable type="text" placeholder={placeholder} onChange={onChange} />
</Cell>
);
},
Textarea: (props: baseFormTextAreaTpl & { onChange: (v: string | undefined) => void }) => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<Input
type="text"
rows={3}
autoHeight
showLength
placeholder={placeholder}
onChange={onChange}
/>
</Cell>
);
},
Number: (props: baseFormNumberTpl & { onChange: (v: string | undefined | number) => void }) => {
const { label, placeholder, onChange } = props;
return (
<Cell title={label}>
<Input type="number" placeholder={placeholder} onChange={onChange} />
</Cell>
);
},
MyRadio: (props: baseFormMyRadioTpl & { onChange: (v: string | undefined | number) => void }) => {
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: baseFormDateTpl & { onChange: (v: Date) => void }) => {
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: baseFormMySelectTpl & { onChange: ((v: Record<string, any>) => void) | undefined },
) => {
const { label, options, onChange } = props;
return (
<Cell title={label}>
<Select dataSource={options} onOk={onChange} />
</Cell>
);
},
};
export default BaseForm;

View File

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

View File

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

@ -1,61 +0,0 @@
import React, { memo, useCallback } from 'react';
import { Button } from 'zarm';
import BaseForm from './BaseForm';
import styles from './index.less';
import { IFormConfig } from './schema';
const FormComponent = (props: IFormConfig) => {
const { title, bgColor, fontSize, titColor, btnColor, btnTextColor, api, formControls } = props;
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, {
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

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

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

View File

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

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

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

Binary file not shown.

View File

@ -1,9 +1,8 @@
import { dynamic } from 'umi';
import Loading from '../LoadingCp';
import Loading from '../components/LoadingCp';
import { useMemo, memo, FC, useContext } from 'react';
import React from 'react';
import { dooringContext, dooringContextType } from '@/layouts';
// import { AllTemplateType } from './schema';
export type componentsType = 'media' | 'base' | 'visible';

View File

@ -1,10 +1,10 @@
import { useState, useEffect, memo } from 'react';
import classnames from 'classnames';
import Icon from '../../BasicShop/BasicComponents/Icon';
import Icon from '@/components/BasicShop/BasicComponents/Icon';
import styles from './index.less';
import React from 'react';
import { IconTypes } from '@/components/BasicShop/BasicComponents/Icon/schema';
import { ICardPickerConfigType } from '../FormEditor/types';
import { ICardPickerConfigType } from '../types';
interface CardPickerType extends Omit<ICardPickerConfigType<IconTypes>, 'type' | 'key' | 'name'> {
onChange?: (v: string) => void;

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 { TDataListDefaultType, TDataListDefaultTypeItem } from '../FormEditor/types';
import { TDataListDefaultType, TDataListDefaultTypeItem } from '../types';
type ListItemProps = DndItemProps & {
isDragging: boolean;

View File

@ -1,6 +1,6 @@
import React, { FC, memo, useEffect } from 'react';
import { Form, Select, Input, Modal, Button, InputNumber } from 'antd';
import { baseFormOptionsType } from '../FormEditor/types';
import { baseFormOptionsType } from '../types';
import Color from '../Color';
const { Option } = Select;

View File

@ -1,10 +1,10 @@
import React, { memo, RefObject, useCallback, useEffect, useState } from 'react';
import BaseForm from '../../BasicShop/BasicComponents/Form/BaseForm';
import BasePopoverForm from '../../BasicShop/BasicComponents/Form/BasePopoverForm';
import BaseForm from '@/components/BasicShop/BasicComponents/Form/BaseForm';
import BasePopoverForm from '@/components/BasicShop/BasicComponents/Form/BasePopoverForm';
import EditorModal from './EditorModal';
import { MinusCircleFilled, EditFilled, PlusOutlined } from '@ant-design/icons';
import styles from './formItems.less';
import { baseFormUnion, TFormItemsDefaultType } from '../FormEditor/types';
import { baseFormUnion, TFormItemsDefaultType } from '../types';
import { uuid } from '@/utils/tool';
import { Button } from 'antd';
import MyPopover from 'yh-react-popover';

View File

@ -2,7 +2,7 @@ import React, { memo, useEffect } from 'react';
import { Input, Button, Popconfirm } from 'antd';
import { MinusCircleFilled } from '@ant-design/icons';
import styles from './index.less';
import { TMutiTextDefaultType } from '../FormEditor/types';
import { TMutiTextDefaultType } from '../types';
type MultiTextProps = {
onChange?: (v: TMutiTextDefaultType) => void;

View File

@ -1,7 +1,7 @@
import React, { memo, useState, useEffect } from 'react';
import { InputNumber } from 'antd';
import styles from './index.less';
import { TPosDefaultType, TPosItem } from '../FormEditor/types';
import { TPosDefaultType, TPosItem } from '../types';
type PosProps = {
value?: TPosDefaultType;

View File

@ -27,11 +27,10 @@ const controls = [
];
export default memo(function XEditor(props: any) {
const [editorState, setEditorState] = useState(BraftEditor.createEditorState());
const { value, onChange } = props;
const [editorState, setEditorState] = useState(BraftEditor.createEditorState(value));
const myUploadFn = param => {
const myUploadFn = (param: any) => {
const fd = new FormData();
fd.append('file', param.file);
@ -46,8 +45,7 @@ export default memo(function XEditor(props: any) {
param.progress((event.loaded / event.total) * 100);
},
})
.then(res => {
console.log(res);
.then((res: any) => {
// 上传成功后调用param.success并传入上传后的文件地址
param.success({
url: res.url,

View File

@ -1,16 +1,15 @@
import React, { memo, RefObject, useEffect } from 'react';
import { Form, Select, InputNumber, Input, Switch, Radio, Button } from 'antd';
import Upload from '../Upload';
import DataList from '../DataList';
import MutiText from '../MutiText';
import Color from '../Color';
import CardPicker from '../CardPicker';
import Table from '../Table';
import Pos from '../Pos';
import { Form, Select, InputNumber, Input, Switch, Radio } from 'antd';
import Upload from './FormComponents/Upload';
import DataList from './FormComponents/DataList';
import MutiText from './FormComponents/MutiText';
import Color from './FormComponents/Color';
import CardPicker from './FormComponents/CardPicker';
import Table from './FormComponents/Table';
import Pos from './FormComponents/Pos';
import { Store } from 'antd/lib/form/interface';
import RichText from '../XEditor';
import FormItems from '../FormItems';
// import styles from './index.less';
import RichText from './FormComponents/XEditor';
import FormItems from './FormComponents/FormItems';
const normFile = (e: any) => {
console.log('Upload event:', e);
if (Array.isArray(e)) {

50
src/core/ViewRender.tsx Normal file
View File

@ -0,0 +1,50 @@
import React, { memo } from 'react';
import GridLayout, { ItemCallback } from 'react-grid-layout';
import DynamicEngine from '@/core/DynamicEngine';
import styles from './viewRender.less';
interface PointDataItem {
id: string;
item: Record<string, any>;
point: Record<string, any>;
}
interface ViewProps {
pointData: Array<PointDataItem>;
pageData?: any;
width?: number;
dragStop?: ItemCallback;
onDragStart?: ItemCallback;
onResizeStop?: ItemCallback;
}
const ViewRender = memo((props: ViewProps) => {
const { pointData, pageData, width, dragStop, onDragStart, onResizeStop } = props;
return (
<GridLayout
cols={24}
rowHeight={2}
width={width}
margin={[0, 0]}
onDragStop={dragStop}
onDragStart={onDragStart}
onResizeStop={onResizeStop}
style={{
backgroundColor: pageData && pageData.bgColor,
backgroundImage:
pageData && pageData.bgImage ? `url(${pageData.bgImage[0].url})` : 'initial',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
}}
>
{pointData.map((value: PointDataItem) => (
<div key={value.id} data-grid={value.point} className={styles.dragItem}>
<DynamicEngine {...(value.item as any)} isTpl={false} />
</div>
))}
</GridLayout>
);
});
export default ViewRender;

14
src/core/viewRender.less Normal file
View File

@ -0,0 +1,14 @@
.dragItem {
display: inline-block;
position: absolute;
z-index: 2;
border: 2px solid transparent;
cursor: move;
&:hover {
border: 2px solid #06c;
}
:global(a) {
display: block;
pointer-events: none;
}
}

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo, useCallback, useContext, useRef } from 'react';
import { Result, Tabs, Button } from 'antd';
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { Result, Tabs } from 'antd';
import {
PieChartOutlined,
PlayCircleOutlined,
@ -13,8 +13,8 @@ import CanvasControl from './components/CanvasControl';
import SourceBox from './SourceBox';
import TargetBox from './TargetBox';
import Calibration from 'components/Calibration';
import DynamicEngine, { componentsType } from 'components/DynamicEngine';
import FormEditor from 'components/PanelComponents/FormEditor';
import DynamicEngine, { componentsType } from '@/core/DynamicEngine';
import FormRender from '@/core/FormRender';
import template from 'components/BasicShop/BasicComponents/template';
import mediaTpl from 'components/BasicShop/MediaComponents/template';
@ -22,7 +22,6 @@ import graphTpl from 'components/BasicShop/VisualComponents/template';
import schemaH5 from 'components/BasicShop/schema';
import { ActionCreators, StateWithHistory } from 'redux-undo';
import { dooringContext } from '@/layouts';
import { throttle } from '@/utils/tool';
import styles from './index.less';
@ -53,7 +52,6 @@ const Container = (props: {
setRightColla(c);
};
}, []);
const context = useContext(dooringContext);
const curPoint = pstate ? pstate.curPoint : {};
// 指定画布的id
@ -91,48 +89,26 @@ const Container = (props: {
}, []);
const handleFormSave = useMemo(() => {
if (context.theme === 'h5') {
return (data: any) => {
dispatch({
type: 'editorModal/modPointData',
payload: { ...curPoint, item: { ...curPoint.item, config: data } },
});
};
} else {
return (data: any) => {
dispatch({
type: 'editorPcModal/modPointData',
payload: { ...curPoint, item: { ...curPoint.item, config: data } },
});
};
}
}, [context.theme, curPoint, dispatch]);
return (data: any) => {
dispatch({
type: 'editorModal/modPointData',
payload: { ...curPoint, item: { ...curPoint.item, config: data } },
});
};
}, [curPoint, dispatch]);
const clearData = useCallback(() => {
if (context.theme === 'h5') {
dispatch({ type: 'editorModal/clearAll' });
} else {
dispatch({ type: 'editorPcModal/clearAll' });
}
}, [context.theme, dispatch]);
dispatch({ type: 'editorModal/clearAll' });
}, [dispatch]);
const handleDel = useMemo(() => {
if (context.theme === 'h5') {
return (id: any) => {
dispatch({
type: 'editorModal/delPointData',
payload: { id },
});
};
} else {
return (id: any) => {
dispatch({
type: 'editorPcModal/delPointData',
payload: { id },
});
};
}
}, [context.theme, dispatch]);
return (id: any) => {
dispatch({
type: 'editorModal/delPointData',
payload: { id },
});
};
}, [dispatch]);
const redohandler = useMemo(() => {
return () => {
@ -184,81 +160,35 @@ const Container = (props: {
const ref = useRef<HTMLDivElement>(null);
const renderRight = useMemo(() => {
if (context.theme === 'h5') {
return (
<div
ref={ref}
className={styles.attrSetting}
style={{
transition: 'all ease-in-out 0.5s',
transform: rightColla ? 'translate(100%,0)' : 'translate(0,0)',
}}
>
{pointData.length && curPoint ? (
<>
<div className={styles.tit}></div>
<FormEditor
config={curPoint.item.editableEl}
uid={curPoint.id}
defaultValue={curPoint.item.config}
onSave={handleFormSave}
onDel={handleDel}
rightPannelRef={ref}
/>
</>
) : (
<div style={{ paddingTop: '100px' }}>
<Result
status="404"
title="还没有数据哦"
subTitle="赶快拖拽组件来生成你的H5页面吧"
/>
</div>
)}
</div>
);
} else {
return (
<div
className={styles.attrSetting}
style={{
transition: 'all ease-in-out 0.5s',
transform: rightColla ? 'translate(100%,0)' : 'translate(0,0)',
}}
>
{cpointData.length && curPoint ? (
<>
<div className={styles.tit}></div>
<FormEditor
config={curPoint.item.editableEl}
uid={curPoint.id}
defaultValue={curPoint.item.config}
onSave={handleFormSave}
onDel={handleDel}
rightPannelRef={ref}
/>
</>
) : (
<div style={{ paddingTop: '100px' }}>
<Result
status="404"
title="还没有数据哦"
subTitle="赶快拖拽组件来生成你的H5页面吧"
/>
</div>
)}
</div>
);
}
}, [
context.theme,
cpointData.length,
curPoint,
handleDel,
handleFormSave,
pointData.length,
rightColla,
]);
return (
<div
ref={ref}
className={styles.attrSetting}
style={{
transition: 'all ease-in-out 0.5s',
transform: rightColla ? 'translate(100%,0)' : 'translate(0,0)',
}}
>
{pointData.length && curPoint ? (
<>
<div className={styles.tit}></div>
<FormRender
config={curPoint.item.editableEl}
uid={curPoint.id}
defaultValue={curPoint.item.config}
onSave={handleFormSave}
onDel={handleDel}
rightPannelRef={ref}
/>
</>
) : (
<div style={{ paddingTop: '100px' }}>
<Result status="404" title="还没有数据哦" subTitle="赶快拖拽组件来生成你的H5页面吧" />
</div>
)}
</div>
);
}, [cpointData.length, curPoint, handleDel, handleFormSave, pointData.length, rightColla]);
const tabRender = useMemo(() => {
if (collapsed) {

View File

@ -1,16 +1,15 @@
import React, { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useDrop } from 'react-dnd';
import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
import GridLayout, { ItemCallback } from 'react-grid-layout';
import { ItemCallback } from 'react-grid-layout';
import { connect } from 'dva';
import DynamicEngine from 'components/DynamicEngine';
import ViewRender from '@/core/ViewRender';
import styles from './index.less';
import { uuid } from '@/utils/tool';
import { Dispatch } from 'umi';
import { StateWithHistory } from 'redux-undo';
import { Menu, Item, MenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';
import { dooringContext } from '@/layouts';
interface SourceBoxProps {
pstate: { pointData: { id: string; item: any; point: any; isMenu?: any }[]; curPoint: any };
cstate: { pointData: { id: string; item: any; point: any }[]; curPoint: any };
@ -29,15 +28,12 @@ interface SourceBoxProps {
const SourceBox = memo((props: SourceBoxProps) => {
const { pstate, scaleNum, canvasId, allType, dispatch, dragState, setDragState, cstate } = props;
const context = useContext(dooringContext);
let pointData = pstate ? pstate.pointData : [];
const cpointData = cstate ? cstate.pointData : [];
const [canvasRect, setCanvasRect] = useState<number[]>([]);
const [isShowTip, setIsShowTip] = useState(true);
// const [clonePointData, setPointData] = useState(pointData);
// const [isMenu, setIsMenu] = useState(false);
const [{ isOver }, drop] = useDrop({
accept: allType,
drop: (item: { h: number; type: string; x: number }, monitor) => {
@ -51,34 +47,15 @@ const SourceBox = memo((props: SourceBoxProps) => {
w = item.type === 'Icon' ? 3 : col;
// 转换成网格规则的坐标和大小
let gridY = Math.ceil(y / cellHeight);
if (context.theme === 'h5') {
dispatch({
type: 'editorModal/addPointData',
payload: {
id: uuid(6, 10),
item,
point: { i: `x-${pointData.length}`, x: 0, y: gridY, w, h: item.h, isBounded: true },
status: 'inToCanvas',
},
});
} else {
dispatch({
type: 'editorPcModal/addPointData',
payload: {
id: uuid(6, 10),
item,
point: {
i: `x-${cpointData.length}`,
x: item.x || 0,
y: gridY,
w,
h: item.h,
isBounded: true,
},
status: 'inToCanvas',
},
});
}
dispatch({
type: 'editorModal/addPointData',
payload: {
id: uuid(6, 10),
item,
point: { i: `x-${pointData.length}`, x: 0, y: gridY, w, h: item.h, isBounded: true },
status: 'inToCanvas',
},
});
},
collect: monitor => ({
isOver: monitor.isOver(),
@ -94,21 +71,8 @@ const SourceBox = memo((props: SourceBoxProps) => {
type: 'editorModal/modPointData',
payload: { ...curPointData, point: newItem, status: 'inToCanvas' },
});
// if (context.theme === 'h5') {
// const curPointData = pointData.filter(item => item.id === newItem.i)[0];
// dispatch({
// type: 'editorModal/modPointData',
// payload: { ...curPointData, point: newItem, status: 'inToCanvas' },
// });
// } else {
// const curPointData = cpointData.filter(item => item.id === newItem.i)[0];
// dispatch({
// type: 'editorPcModal/modPointData',
// payload: { ...curPointData, point: newItem, status: 'inToCanvas' },
// });
// }
};
}, [context.theme, cpointData, dispatch, pointData]);
}, [cpointData, dispatch, pointData]);
const onDragStart: ItemCallback = useMemo(() => {
return (layout, oldItem, newItem, placeholder, e, element) => {
@ -117,19 +81,6 @@ const SourceBox = memo((props: SourceBoxProps) => {
type: 'editorModal/modPointData',
payload: { ...curPointData, status: 'inToCanvas' },
});
// if (context.theme === 'h5') {
// const curPointData = pointData.filter(item => item.id === newItem.i)[0];
// dispatch({
// type: 'editorModal/modPointData',
// payload: { ...curPointData, status: 'inToCanvas' },
// });
// } else {
// const curPointData = cpointData.filter(item => item.id === newItem.i)[0];
// dispatch({
// type: 'editorPcModal/modPointData',
// payload: { ...curPointData, status: 'inToCanvas' },
// });
// }
};
}, [dispatch, pointData]);
@ -140,32 +91,9 @@ const SourceBox = memo((props: SourceBoxProps) => {
type: 'editorModal/modPointData',
payload: { ...curPointData, point: newItem, status: 'inToCanvas' },
});
// if (context.theme === 'h5') {
// const curPointData = pointData.filter(item => item.id === newItem.i)[0];
// dispatch({
// type: 'editorModal/modPointData',
// payload: { ...curPointData, point: newItem, status: 'inToCanvas' },
// });
// } else {
// const curPointData = cpointData.filter(item => item.id === newItem.i)[0];
// dispatch({
// type: 'editorPcModal/modPointData',
// payload: { ...curPointData, point: newItem, status: 'inToCanvas' },
// });
// }
};
}, [dispatch, pointData]);
const initSelect: any = (data: any = []) => {
return (
data &&
data.map((itemData: any) => ({
...itemData,
isMenu: false,
}))
);
};
const handleContextMenuDel = () => {
if (pstate.curPoint) {
dispatch({
@ -245,26 +173,13 @@ const SourceBox = memo((props: SourceBoxProps) => {
ref={drop}
>
{pointData.length > 0 ? (
<GridLayout
className={styles.layout}
cols={24}
rowHeight={2}
<ViewRender
pointData={pointData}
width={canvasRect[0] || 0}
margin={[0, 0]}
onDragStop={dragStop}
dragStop={dragStop}
onDragStart={onDragStart}
onResizeStop={onResizeStop}
>
{pointData.map(value => (
<div
className={value.isMenu ? styles.selected : styles.dragItem}
key={value.id}
data-grid={value.point}
>
<DynamicEngine {...value.item} isTpl={false} />
</div>
))}
</GridLayout>
/>
) : null}
</div>
</div>

View File

@ -132,7 +132,6 @@
display: inline-block;
position: absolute;
z-index: 2;
border: 2px solid transparent;
cursor: move;
:global(a) {
display: block;

View File

@ -1,9 +1,7 @@
import React, { CSSProperties, memo, useEffect, useMemo, useRef, useState } from 'react';
import GridLayout from 'react-grid-layout';
import DynamicEngine from 'components/DynamicEngine';
import ViewRender from '@/core/ViewRender';
import domtoimage from 'dom-to-image';
import req from '@/utils/req';
import styles from './index.less';
import { useGetScrollBarWidth } from '@/utils/tool';
import { LocationDescriptorObject } from 'history-with-query';
@ -123,25 +121,7 @@ const PreviewPage = memo((props: PreviewPageProps) => {
}
>
<div ref={refImgDom}>
<GridLayout
className={styles.layout}
cols={24}
rowHeight={2}
width={vw > 800 ? 375 : vw}
margin={[0, 0]}
style={{
backgroundColor: pageData.bgColor,
backgroundImage: pageData.bgImage ? `url(${pageData.bgImage[0].url})` : 'initial',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
}}
>
{pointData.map((value: PointDataItem) => (
<div className={styles.dragItem} key={value.id} data-grid={value.point}>
<DynamicEngine {...(value.item as any)} />
</div>
))}
</GridLayout>
<ViewRender pageData={pageData} pointData={pointData} width={vw > 800 ? 375 : vw} />
</div>
</div>