Merge pull request #39 from MrXujiang/yehuozhiliwork

add pc mode
This commit is contained in:
yehuozhili 2020-09-28 18:25:03 +08:00 committed by GitHub
commit b9219c9ebc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 607 additions and 180 deletions

View File

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

View File

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

View File

@ -1,23 +1,32 @@
import { dynamic } from 'umi';
import Loading from '../LoadingCp';
import { useMemo, memo, FC } from 'react';
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';
const DynamicFunc = (type: string, componentsType: string) =>
dynamic({
const DynamicFunc = (type: string, componentsType: string, context: dooringContextType) => {
const prefix = context === 'pc' ? 'Pc' : '';
return dynamic({
loader: async function() {
let Component: FC<{ isTpl: boolean }>;
if (componentsType === 'base') {
const { default: Graph } = await import(`@/components/BasicShop/BasicComponents/${type}`);
const { default: Graph } = await import(
`@/components/Basic${prefix}Shop/BasicComponents/${type}`
);
Component = Graph;
} else if (componentsType === 'media') {
const { default: Graph } = await import(`@/components/BasicShop/MediaComponents/${type}`);
const { default: Graph } = await import(
`@/components/Basic${prefix}Shop/MediaComponents/${type}`
);
Component = Graph;
} else {
const { default: Graph } = await import(`@/components/BasicShop/VisualComponents/${type}`);
const { default: Graph } = await import(
`@/components/Basic${prefix}Shop/VisualComponents/${type}`
);
Component = Graph;
}
return (props: DynamicType) => {
@ -31,6 +40,7 @@ const DynamicFunc = (type: string, componentsType: string) =>
</div>
),
});
};
type DynamicType = {
isTpl: boolean;
@ -41,10 +51,11 @@ type DynamicType = {
};
const DynamicEngine = memo((props: DynamicType) => {
const { type, config, category } = props;
const context = useContext(dooringContext);
const Dynamic = useMemo(() => {
return (DynamicFunc(type, category) as unknown) as FC<DynamicType>;
return (DynamicFunc(type, category, context.theme) as unknown) as FC<DynamicType>;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config]);
}, [config, context.theme]);
return <Dynamic {...props} />;
});

View File

@ -1,8 +1,9 @@
import React, { useCallback, useState } from 'react';
import React, { createContext, useCallback, useState } from 'react';
import { library, generateRespones, RenderList, useRegister } from 'chatbot-antd';
import { IRouteComponentProps } from 'umi';
import { Button } from 'antd';
import { CustomerServiceOutlined } from '@ant-design/icons';
import { stat } from 'fs/promises';
library.push(
//语料库push进去也可以不用
@ -21,6 +22,17 @@ library.push(
},
);
export type dooringContextType = 'h5' | 'pc';
export interface IdooringContextType {
theme: dooringContextType;
setTheme: Function;
}
export const dooringContext = createContext<IdooringContextType>({
theme: 'h5',
setTheme: () => {},
});
export default function Layout({ children }: IRouteComponentProps) {
const [modalOpen, setModalOpen] = useState(false);
const callb = useCallback((v: RenderList) => {
@ -46,7 +58,16 @@ export default function Layout({ children }: IRouteComponentProps) {
{},
<div>welcome!使h5-Dooring</div>,
);
const [state, setState] = useState<dooringContextType>('h5');
return (
<dooringContext.Provider
value={{
theme: state,
setTheme: setState,
}}
>
<div style={{ height: '100%', width: '100%' }}>
<div
style={{
@ -64,5 +85,6 @@ export default function Layout({ children }: IRouteComponentProps) {
{render}
{children}
</div>
</dooringContext.Provider>
);
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useEffect, useMemo, useContext } from 'react';
import { Slider, Result, Tabs, Alert } from 'antd';
import {
PieChartOutlined,
@ -15,24 +15,82 @@ import TargetBox from './TargetBox';
import Calibration from 'components/Calibration';
import DynamicEngine, { componentsType } from 'components/DynamicEngine';
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 template1 from 'components/BasicShop/BasicComponents/template';
import template2 from 'components/BasicPcShop/BasicComponents/template';
import mediaTpl1 from 'components/BasicShop/MediaComponents/template';
import mediaTpl2 from 'components/BasicPcShop/MediaComponents/template';
import graphTpl1 from 'components/BasicShop/VisualComponents/template';
import graphTpl2 from 'components/BasicPcShop/VisualComponents/template';
import schema1 from 'components/BasicShop/schema';
import schema2 from 'components/BasicPcShop/schema';
import { ActionCreators } from 'redux-undo';
import { StateWithHistory } from 'redux-undo';
import styles from './index.less';
import { useGetBall } from 'react-draggable-ball';
import { useAnimation } from '@/utils/tool';
import { dooringContext } from '@/layouts';
const { TabPane } = Tabs;
const Container = (props: { history?: any; location?: any; pstate?: any; dispatch?: any }) => {
const Container = (props: {
history?: any;
location?: any;
pstate?: any;
cstate?: any;
dispatch?: any;
}) => {
const [scaleNum, setScale] = useState(1);
const [collapsed, setCollapsed] = useState(false);
const [collapsed, setCollapsed] = useState(true);
const { pstate, cstate, dispatch } = props;
const pointData = pstate ? pstate.pointData : [];
const cpointData = cstate ? cstate.pointData : [];
const context = useContext(dooringContext);
const curPoint = useMemo(() => {
if (context.theme === 'h5') {
return pstate ? pstate.curPoint : {};
} else {
return cstate ? cstate.curPoint : {};
}
}, [context.theme, cstate, pstate]);
const schema = useMemo(() => {
if (context.theme === 'h5') {
return schema1;
} else {
console.log(schema2);
return schema2;
}
}, [context.theme]);
const template = useMemo(() => {
if (context.theme === 'h5') {
return template1;
} else {
return template2;
}
}, [context.theme]);
const mediaTpl = useMemo(() => {
if (context.theme === 'h5') {
return mediaTpl1;
} else {
return mediaTpl2;
}
}, [context.theme]);
const graphTpl = useMemo(() => {
if (context.theme === 'h5') {
return graphTpl1;
} else {
return graphTpl2;
}
}, [context.theme]);
const { pstate, dispatch } = props;
const pointData = pstate ? pstate.pointData : {};
const curPoint = pstate ? pstate.curPoint : {};
// 指定画布的id
let canvasId = 'js_canvas';
@ -41,6 +99,7 @@ const Container = (props: { history?: any; location?: any; pstate?: any; dispatc
};
const toggleCollapsed = (checked: boolean) => {
console.log(checked);
setCollapsed(checked);
};
@ -69,23 +128,51 @@ const Container = (props: { history?: any; location?: any; pstate?: any; dispatc
setScale(prev => (prev <= 0.5 ? 0.5 : prev - 0.1));
}
};
const handleFormSave = (data: any) => {
const handleFormSave = useMemo(() => {
if (context.theme === 'h5') {
return (data: any) => {
dispatch({
type: 'editorModal/modPointData',
payload: { ...curPoint, item: { ...curPoint.item, config: data } },
});
};
const clearData = () => {
dispatch({ type: 'editorModal/clearAll' });
} else {
return (data: any) => {
dispatch({
type: 'editorPcModal/modPointData',
payload: { ...curPoint, item: { ...curPoint.item, config: data } },
});
};
}
}, [context.theme, curPoint, dispatch]);
const handleDel = (id: any) => {
const clearData = useMemo(() => {
if (context.theme === 'h5') {
dispatch({ type: 'editorModal/clearAll' });
} else {
dispatch({ type: 'editorPcModal/clearAll' });
}
}, [context.theme, 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]);
const redohandler = () => {
dispatch(ActionCreators.redo());
};
@ -120,18 +207,81 @@ const Container = (props: { history?: any; location?: any; pstate?: any; dispatc
intervalDelay: 5,
});
const [display] = useAnimation(collapsed, 500);
const renderRight = useMemo(() => {
if (context.theme === 'h5') {
return (
<div className={styles.attrSetting}>
{pointData.length && curPoint ? (
<>
<div className={styles.tit}></div>
<FormEditor
config={curPoint.item.editableEl}
uid={curPoint.id}
defaultValue={curPoint.item.config}
onSave={handleFormSave}
onDel={handleDel}
/>
</>
) : (
<div style={{ paddingTop: '100px' }}>
<Result
status="404"
title="还没有数据哦"
subTitle="赶快拖拽组件来生成你的H5页面吧"
/>
</div>
)}
</div>
);
} else {
return (
<div className={styles.attrSetting}>
{cpointData.length && curPoint ? (
<>
<div className={styles.tit}></div>
<FormEditor
config={curPoint.item.editableEl}
uid={curPoint.id}
defaultValue={curPoint.item.config}
onSave={handleFormSave}
onDel={handleDel}
/>
</>
) : (
<div style={{ paddingTop: '100px' }}>
<Result
status="404"
title="还没有数据哦"
subTitle="赶快拖拽组件来生成你的H5页面吧"
/>
</div>
)}
</div>
);
}
}, [context.theme, cpointData.length, curPoint, handleDel, handleFormSave, pointData.length]);
return (
<div className={styles.editorWrap}>
<HeaderComponent
redohandler={redohandler}
undohandler={undohandler}
pointData={pointData}
pointData={context.theme === 'h5' ? pointData : cpointData}
clearData={clearData}
location={props.location}
toggleCollapsed={toggleCollapsed}
/>
<div className={styles.container}>
<div className={!collapsed ? styles.list : styles.collapsed}>
<div
className={styles.list}
style={{
transform: collapsed ? 'translate(0,0)' : 'translate(-100%,0)',
transition: 'all ease-in-out 0.5s',
display: display ? 'none' : 'block',
}}
>
<div className={styles.searchBar}>
<Alert
banner
@ -228,33 +378,12 @@ const Container = (props: { history?: any; location?: any; pstate?: any; dispatc
<ExpandOutlined onClick={backSize} />
</div>
</div>
<div className={styles.attrSetting}>
{pointData.length && curPoint ? (
<>
<div className={styles.tit}></div>
<FormEditor
config={curPoint.item.editableEl}
uid={curPoint.id}
defaultValue={curPoint.item.config}
onSave={handleFormSave}
onDel={handleDel}
/>
</>
) : (
<div style={{ paddingTop: '100px' }}>
<Result
status="404"
title="还没有数据哦"
subTitle="赶快拖拽组件来生成你的H5页面吧"
/>
</div>
)}
</div>
{renderRight}
</div>
</div>
);
};
export default connect((state: StateWithHistory<any>) => {
return { pstate: state.present.editorModal };
return { pstate: state.present.editorModal, cstate: state.present.editorPcModal };
})(Container);

View File

@ -1,4 +1,4 @@
import React, { memo, useEffect, useState } from 'react';
import React, { memo, 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';
@ -9,8 +9,10 @@ import styles from './index.less';
import { uuid } from '@/utils/tool';
import { Dispatch } from 'umi';
import { StateWithHistory } from 'redux-undo';
import { dooringContext } from '@/layouts';
interface SourceBoxProps {
pstate: { pointData: { id: string; item: any; point: any }[] };
pstate: { pointData: { id: string; item: any; point: any }[]; curPoint: any };
cstate: { pointData: { id: string; item: any; point: any }[]; curPoint: any };
scaleNum: number;
canvasId: string;
allType: string[];
@ -25,14 +27,17 @@ interface SourceBoxProps {
}
const SourceBox = memo((props: SourceBoxProps) => {
const { pstate, scaleNum, canvasId, allType, dispatch, dragState, setDragState } = props;
const { pstate, scaleNum, canvasId, allType, dispatch, dragState, setDragState, cstate } = props;
const context = useContext(dooringContext);
const pointData = pstate ? pstate.pointData : [];
const cpointData = cstate ? cstate.pointData : [];
const [canvasRect, setCanvasRect] = useState<number[]>([]);
const [isShowTip, setIsShowTip] = useState(true);
const [{ isOver }, drop] = useDrop({
accept: allType,
drop: (item: { h: number; type: string }, monitor) => {
drop: (item: { h: number; type: string; x: number }, monitor) => {
let parentDiv = document.getElementById(canvasId),
pointRect = parentDiv!.getBoundingClientRect(),
top = pointRect.top,
@ -43,6 +48,7 @@ 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: {
@ -51,6 +57,24 @@ const SourceBox = memo((props: SourceBoxProps) => {
point: { i: `x-${pointData.length}`, x: 0, y: gridY, w, h: item.h, isBounded: true },
},
});
} else {
console.log(item.x);
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,
},
},
});
}
},
collect: monitor => ({
isOver: monitor.isOver(),
@ -59,34 +83,65 @@ const SourceBox = memo((props: SourceBoxProps) => {
}),
});
const dragStop: ItemCallback = (layout, oldItem, newItem, placeholder, e, element) => {
const dragStop: ItemCallback = useMemo(() => {
return (layout, oldItem, newItem, placeholder, e, element) => {
if (context.theme === 'h5') {
const curPointData = pointData.filter(item => item.id === newItem.i)[0];
dispatch({
type: 'editorModal/modPointData',
payload: { ...curPointData, point: newItem },
});
} else {
const curPointData = cpointData.filter(item => item.id === newItem.i)[0];
dispatch({
type: 'editorPcModal/modPointData',
payload: { ...curPointData, point: newItem },
});
}
};
}, [context.theme, cpointData, dispatch, pointData]);
const onDragStart: ItemCallback = (layout, oldItem, newItem, placeholder, e, element) => {
const onDragStart: ItemCallback = useMemo(() => {
return (layout, oldItem, newItem, placeholder, e, element) => {
if (context.theme === 'h5') {
const curPointData = pointData.filter(item => item.id === newItem.i)[0];
dispatch({
type: 'editorModal/modPointData',
payload: { ...curPointData },
});
} else {
const curPointData = cpointData.filter(item => item.id === newItem.i)[0];
dispatch({
type: 'editorPcModal/modPointData',
payload: { ...curPointData },
});
}
};
}, [context.theme, cpointData, dispatch, pointData]);
const onResizeStop: ItemCallback = (layout, oldItem, newItem, placeholder, e, element) => {
const onResizeStop: ItemCallback = useMemo(() => {
return (layout, oldItem, newItem, placeholder, e, element) => {
if (context.theme === 'h5') {
const curPointData = pointData.filter(item => item.id === newItem.i)[0];
dispatch({
type: 'editorModal/modPointData',
payload: { ...curPointData, point: newItem },
});
} else {
const curPointData = cpointData.filter(item => item.id === newItem.i)[0];
dispatch({
type: 'editorPcModal/modPointData',
payload: { ...curPointData, point: newItem },
});
}
};
}, [context.theme, cpointData, dispatch, pointData]);
useEffect(() => {
let { width, height } = document.getElementById(canvasId)!.getBoundingClientRect();
console.log(width, height);
setCanvasRect([width, height]);
}, [canvasId]);
}, [canvasId, context.theme]);
useEffect(() => {
let timer = window.setTimeout(() => {
@ -99,6 +154,8 @@ const SourceBox = memo((props: SourceBoxProps) => {
const opacity = isOver ? 0.7 : 1;
// const backgroundColor = isOver ? 1 : 0.7;
const render = useMemo(() => {
if (context.theme === 'h5') {
return (
<Draggable
position={dragState}
@ -164,8 +221,95 @@ const SourceBox = memo((props: SourceBoxProps) => {
</div>
</Draggable>
);
} else {
//pc可能要传递宽度
return (
<Draggable
position={dragState}
handle=".js_box"
onStop={(e: DraggableEvent, data: DraggableData) => {
setDragState({ x: data.x, y: data.y });
}}
>
<div className={styles.canvasBox2}>
<div
style={{
transform: `scale(${scaleNum - 0.2})`,
position: 'relative',
width: '100%',
height: '100%',
}}
>
<div
id={canvasId}
className={styles.canvas2}
style={{
opacity,
}}
ref={drop}
>
<Tooltip placement="right" title="鼠标按住此处拖拽画布" visible={isShowTip}>
<div
className="js_box"
style={{
width: '10px',
height: '100%',
position: 'absolute',
borderRadius: '0 6px 6px 0',
backgroundColor: '#2f54eb',
right: '-10px',
top: '0',
color: '#fff',
cursor: 'move',
}}
/>
</Tooltip>
{cpointData.length > 0 ? (
<GridLayout
className={styles.layout}
cols={24}
rowHeight={2}
width={canvasRect[0] || 0}
margin={[0, 0]}
onDragStop={dragStop}
onDragStart={onDragStart}
onResizeStop={onResizeStop}
>
{cpointData.map(value => (
<div className={styles.dragItem} key={value.id} data-grid={value.point}>
<DynamicEngine {...value.item} isTpl={false} />
</div>
))}
</GridLayout>
) : null}
</div>
</div>
</div>
</Draggable>
);
}
}, [
canvasId,
canvasRect,
context.theme,
cpointData,
dragState,
dragStop,
drop,
isShowTip,
onDragStart,
onResizeStop,
opacity,
pointData,
scaleNum,
setDragState,
]);
return <>{render}</>;
});
export default connect((state: StateWithHistory<any>) => ({ pstate: state.present.editorModal }))(
SourceBox,
);
export default connect((state: StateWithHistory<any>) => ({
pstate: state.present.editorModal,
cstate: state.present.editorPcModal,
}))(SourceBox);

View File

@ -18,6 +18,7 @@ const TargetBox = memo((props: TargetBoxProps) => {
h: item.h,
editableEl: schema[item.type as keyof typeof schema].editData,
category: item.category,
x: item.x || 0,
},
collect: monitor => ({
isDragging: monitor.isDragging(),

View File

@ -1,5 +1,5 @@
import React, { useRef, memo } from 'react';
import { Button, Input, Popover, Modal, Switch } from 'antd';
import React, { useRef, memo, useContext } from 'react';
import { Button, Input, Popover, Modal, Switch, Select } from 'antd';
import {
ArrowLeftOutlined,
MobileOutlined,
@ -17,6 +17,7 @@ import Zan from 'components/Zan';
import req from '@/utils/req';
import Code from '@/assets/code.png';
import styles from './index.less';
import { dooringContext } from '@/layouts';
const { confirm } = Modal;
@ -128,7 +129,7 @@ const HeaderComponent = memo((props: HeaderComponentProps) => {
req.post('/visible/preview', { tid, tpl: pointData });
};
const handleSaveCode = () => {};
const { setTheme } = useContext(dooringContext);
return (
<div className={styles.header}>
<div className={styles.logoArea}>
@ -136,7 +137,7 @@ const HeaderComponent = memo((props: HeaderComponentProps) => {
<ArrowLeftOutlined />
</div>
<div className={styles.logo}>Dooring</div>
{/* <Switch onChange={toggleCollapsed} style={{ marginLeft: '100px' }} />暂时隐藏 TODO */}
<Switch defaultChecked onChange={toggleCollapsed} style={{ marginLeft: '100px' }} />
</div>
<div className={styles.controlArea}>
<Button type="primary" style={{ marginRight: '9px' }} onClick={useTemplate}>
@ -211,6 +212,16 @@ const HeaderComponent = memo((props: HeaderComponentProps) => {
</Button>
</div>
<Select
defaultValue="h5"
style={{ width: 120 }}
onChange={e => {
setTheme(e);
}}
>
<Select.Option value="h5">h5模式</Select.Option>
<Select.Option value="pc">pc模式</Select.Option>
</Select>
<div className={styles.btnArea}>
<Zan />
</div>

View File

@ -18,10 +18,10 @@
width: 100%;
}
.container {
width: 100%; //写vw产生滚动条会出现横向滚动条
// height: 100vh;
height: calc(100% - 56px);
display: flex;
.list {
width: 350px;
height: 100%;
@ -56,10 +56,10 @@
}
}
.tickMark {
width: calc(100% - 750px);
overflow: hidden;
height: 100%;
position: relative;
flex: 1;
.tickMarkTop {
width: 100%;
height: 50px;
@ -108,6 +108,40 @@
}
}
}
.canvasBox2 {
width: 1200px;
min-height: 800px;
height: 800px;
left: 15%;
position: relative;
margin-left: -200px;
top: 20px;
.canvas2 {
position: relative;
width: 1200px;
min-height: 800px;
background-color: #fff;
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
position: absolute;
left: 0;
top: 0;
transition: transform 300ms ease-out;
.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;
}
}
}
}
.resetBall {
position: absolute;
right: 24px;
@ -152,6 +186,7 @@
height: 100%;
box-shadow: -2px 0px 4px 0px rgba(0, 0, 0, 0.1);
overflow: auto;
.tit {
margin-bottom: 16px;
font-size: 18px;
@ -205,10 +240,6 @@
}
}
}
.collapsed {
.list;
height: calc(56px);
}
}
}

View File

@ -0,0 +1,58 @@
const pointData = localStorage.getItem('userPcData') || '[]';
function overSave(name: string, data: any) {
localStorage.setItem(name, JSON.stringify(data));
}
export default {
namespace: 'editorPcModal',
state: {
pointData: JSON.parse(pointData),
curPoint: null,
},
reducers: {
addPointData(state: any, { payload }: any) {
let pointData = [...state.pointData, payload];
overSave('userPcData', pointData);
return {
...state,
pointData,
curPoint: payload,
};
},
modPointData(state: any, { payload }: any) {
const { id } = payload;
const pointData = state.pointData.map((item: any) => {
if (item.id === id) {
return payload;
}
return { ...item };
});
overSave('userPcData', pointData);
return {
...state,
pointData,
curPoint: payload,
};
},
delPointData(state: any, { payload }: any) {
const { id } = payload;
const pointData = state.pointData.filter((item: any) => item.id !== id);
overSave('userPcData', pointData);
return {
...state,
pointData,
curPoint: null,
};
},
clearAll(state: any) {
overSave('userPcData', []);
return {
...state,
pointData: [],
curPoint: null,
};
},
},
effects: {},
};

View File

@ -63,3 +63,21 @@ export function useGetScrollBarWidth(ref: RefObject<HTMLElement>) {
}, [ref]);
return width;
}
export function useAnimation(state: boolean, delay: number) {
const [display, setDisplay] = useState(false);
useEffect(() => {
let timer: number;
if (state && display === true) {
setDisplay(false);
} else if (!state && display === false) {
timer = window.setTimeout(() => {
setDisplay(true);
}, delay);
}
return () => {
window.clearTimeout(timer);
};
}, [delay, display, state]);
return [display, setDisplay];
}