Merge pull request #17 from MrXujiang/yehuozhiliwork

connect components
This commit is contained in:
yehuozhili 2020-09-04 16:39:26 +08:00 committed by GitHub
commit af51d7e259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1828 additions and 984 deletions

View File

@ -78,6 +78,8 @@
},
"license": "MIT",
"devDependencies": {
"@types/classnames": "^2.2.10",
"@types/react-color": "^3.0.4",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"babel-eslint": "10.x",

View File

@ -1,6 +1,6 @@
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' },

View File

@ -17,81 +17,87 @@ export default function Calibration(props: CalibrationTypes) {
const calibrationRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let calibration = calibrationRef.current.getBoundingClientRect();
setCalibration({ width: calibration.width, height: calibration.height });
let length = direction === 'up' ? calibration.width : calibration.height;
for (let i = 0; i < length / 5; i++) {
if (i % 10 === 0) {
generateElement(true, i);
} else {
generateElement();
if (calibrationRef.current) {
let calibration = calibrationRef.current.getBoundingClientRect();
setCalibration({ width: calibration.width, height: calibration.height });
let length = direction === 'up' ? calibration.width : calibration.height;
for (let i = 0; i < length / 5; i++) {
if (i % 10 === 0) {
generateElement(true, i);
} else {
generateElement();
}
}
}
}, [direction]);
const generateElement = (item?: boolean, num?: number) => {
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 (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') {
createSpan.style.height = '12px';
createSpanContent.style.transform = 'translate3d(-4px, 20px, 0px)';
createSpan.style.transform = 'translateY(0px)';
calibrationRef.current.style.marginLeft = '50px';
createSpan.style.width = '1px';
createSpan.style.height = '6px';
createSpan.style.display = 'inline-block';
} else {
createSpan.style.width = '12px';
createSpanContent.style.paddingLeft = '20px';
calibrationRef.current.style.flexDirection = 'column';
createSpan.style.height = '1px';
createSpan.style.width = '6px';
}
createSpanContent.style.display = 'block';
createSpanContent.className = 'calibrationNumber';
createSpanContent.innerHTML = num * 5 + '';
createSpan.appendChild(createSpanContent);
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);
}
calibrationRef.current.appendChild(createSpan);
};
useEffect(() => {
let width = calibrationLength.width
? calibrationLength.width
: calibrationRef.current.getBoundingClientRect().width;
let height = calibrationLength.height
? calibrationLength.height
: calibrationRef.current.getBoundingClientRect().height;
let arr = [...calibrationRef.current.querySelectorAll('.calibrationLine')];
if (arr.length) {
if (direction === 'up') {
calibrationRef.current.style.width = parseFloat(multiple.toFixed(1)) * width + 'px';
arr.forEach(el => {
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
if (dom) {
dom.style.transform = `translate3d(-4px, 16px, 0px) scale(${(multiple + 0.1).toFixed(
1,
)})`;
}
});
} else {
calibrationRef.current.style.height = parseFloat(multiple.toFixed(1)) * height + 'px';
arr.forEach(el => {
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
if (dom) {
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(multiple + 0.1).toFixed(
1,
)})`;
}
});
if (calibrationRef.current) {
let width = calibrationLength.width
? calibrationLength.width
: calibrationRef.current.getBoundingClientRect().width;
let height = calibrationLength.height
? calibrationLength.height
: calibrationRef.current.getBoundingClientRect().height;
let arr = [...calibrationRef.current.querySelectorAll('.calibrationLine')];
if (arr.length) {
if (direction === 'up') {
calibrationRef.current.style.width = parseFloat(multiple.toFixed(1)) * width + 'px';
arr.forEach(el => {
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
if (dom) {
dom.style.transform = `translate3d(-4px, 16px, 0px) scale(${(multiple + 0.1).toFixed(
1,
)})`;
}
});
} else {
calibrationRef.current.style.height = parseFloat(multiple.toFixed(1)) * height + 'px';
arr.forEach(el => {
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
if (dom) {
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(multiple + 0.1).toFixed(
1,
)})`;
}
});
}
}
}
}, [calibrationLength.height, calibrationLength.width, direction, multiple]);

View File

@ -3,16 +3,17 @@ import classnames from 'classnames';
import Icon from '../Icon';
import styles from './index.less';
import { IconTypes } from '../DynamicEngine/schema';
import React from 'react';
interface CardPickerType {
type?: IconTypes;
type: IconTypes;
icons: Array<IconTypes>;
onChange?: (v: string) => void;
}
export default memo((props: CardPickerType) => {
const { type, icons, onChange } = props;
console.log(type);
const [selected, setSelected] = useState<IconTypes>(type);
const handlePicker = (v: IconTypes) => {
@ -33,7 +34,7 @@ export default memo((props: CardPickerType) => {
return (
<span
className={classnames(styles.picker, selected === item ? styles.selected : '')}
onClick={handlePicker.bind(this, item)}
onClick={() => handlePicker(item)}
key={i}
>
<Icon type={item} size={20} />

View File

@ -9,7 +9,6 @@ interface CarouselTypes extends CarouselConfigType {
const XCarousel = memo((props: PropsWithChildren<CarouselTypes>) => {
const { direction, swipeable, autoPlay, isTpl, imgList, tplImg } = props;
console.log(direction);
const contentRender = () => {
return imgList.map((item, i) => {
return (

View File

@ -1,73 +0,0 @@
import React from 'react'
import { SketchPicker } from 'react-color'
import { rgba2Obj } from '@/utils/tool'
// import styles from './index.less'
class colorPicker extends React.Component {
state = {
displayColorPicker: false,
color: rgba2Obj(this.props.value),
};
handleClick = () => {
this.setState({ displayColorPicker: !this.state.displayColorPicker })
};
handleClose = () => {
this.setState({ displayColorPicker: false })
};
handleChange = (color) => {
this.setState({ color: color.rgb })
this.props.onChange && this.props.onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`)
};
render() {
return (
<div>
<div
style={{
padding: '5px',
background: '#fff',
borderRadius: '1px',
boxShadow: '0 0 0 1px rgba(0,0,0,.1)',
display: 'inline-block',
cursor: 'pointer'
}}
onClick={ this.handleClick }
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
background: `rgba(${ this.state.color.r }, ${ this.state.color.g }, ${ this.state.color.b }, ${ this.state.color.a })`
}} />
</div>
{ this.state.displayColorPicker ?
<div
style={{
position: 'absolute',
zIndex: '2'
}}
>
<div
style={{
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px'
}}
onClick={ this.handleClose }
/>
<SketchPicker color={ this.state.color } onChange={ this.handleChange } />
</div> : null }
</div>
)
}
}
export default colorPicker

View File

@ -0,0 +1,83 @@
import React from 'react';
import { SketchPicker, ColorResult } from 'react-color';
import { rgba2Obj } from '@/utils/tool';
// import styles from './index.less'
interface ColorProps {
value?: string;
id?: string;
onChange?: (v: string) => void;
}
class colorPicker extends React.Component<ColorProps> {
state = {
displayColorPicker: false,
color: rgba2Obj(this.props.value),
};
handleClick = () => {
this.setState({ displayColorPicker: !this.state.displayColorPicker });
};
handleClose = () => {
this.setState({ displayColorPicker: false });
};
handleChange = (color: ColorResult) => {
this.setState({ color: color.rgb });
this.props.onChange &&
this.props.onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`);
};
render() {
return (
<div>
<div
style={{
padding: '5px',
background: '#fff',
borderRadius: '1px',
boxShadow: '0 0 0 1px rgba(0,0,0,.1)',
display: 'inline-block',
cursor: 'pointer',
}}
onClick={this.handleClick}
>
<div
style={{
width: '36px',
height: '14px',
borderRadius: '2px',
background: `rgba(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b}, ${this.state.color.a})`,
}}
/>
</div>
{this.state.displayColorPicker ? (
<React.Fragment>
<div
style={{
position: 'absolute',
zIndex: 2000,
}}
>
<SketchPicker color={this.state.color} onChange={this.handleChange} />
</div>
<div
style={{
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
zIndex: 1000,
}}
onClick={this.handleClose}
/>
</React.Fragment>
) : null}
</div>
);
}
}
export default colorPicker;

View File

@ -1,96 +0,0 @@
import React, { memo, useEffect } from 'react';
import {
Form,
Select,
Input,
Modal
} from 'antd';
import Upload from '@/components/Upload';
// import styles from './index.less';
const normFile = e => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
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={`form_editor_modal`}
{...formItemLayout}
onFinish={onFinish}
initialValues={item}
>
<Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题!' }]}>
<Input />
</Form.Item>
<Form.Item label="描述" name="desc">
<Input />
</Form.Item>
<Form.Item label="链接地址" name="link">
<Input />
</Form.Item>
{
!!window['currentCates'] &&
<Form.Item label="分类" name="type" rules={[{ required: true, message: '请选择分类!' }]}>
<Select placeholder="请选择">
{
window['currentCates'].map((v, i) => {
return <Option value={i} key={i}>{ v }</Option>
})
}
</Select>
</Form.Item>
}
<Form.Item label="上传图片" name="imgUrl" valuePropName="fileList" getValueFromEvent={normFile}>
<Upload />
</Form.Item>
</Form>
</Modal>
)
}
export default memo(EditorModal)

View File

@ -0,0 +1,123 @@
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 { Store } from 'antd/lib/form/interface';
// import styles from './index.less';
const normFile = (e: any) => {
console.log('Upload event:', e);
console.log('ffffffffff'); //待修改?
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
const { Option } = Select;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
export type EditorModalProps = {
visible: boolean;
onCancel: ((e: React.MouseEvent<HTMLElement, MouseEvent>) => void) | undefined;
item?: BasicDataSource;
onSave: Function;
};
const EditorModal: FC<EditorModalProps> = props => {
const { item, onSave, visible, onCancel } = props;
const onFinish = (values: Store) => {
onSave && onSave(values);
};
const handleOk = () => {
form
.validateFields()
.then(values => {
console.log(values);
if (item) {
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={`form_editor_modal`}
{...formItemLayout}
onFinish={onFinish}
initialValues={item}
>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入标题!' }]}
>
<Input />
</Form.Item>
<Form.Item label="描述" name="desc">
<Input />
</Form.Item>
<Form.Item label="链接地址" name="link">
<Input />
</Form.Item>
{!!window['currentCates'] && (
<Form.Item
label="分类"
name="type"
rules={[{ required: true, message: '请选择分类!' }]}
>
<Select placeholder="请选择">
{window['currentCates'].map((v, i) => {
return (
<Option value={i} key={i}>
{v}
</Option>
);
})}
</Select>
</Form.Item>
)}
<Form.Item
label="上传图片"
name="imgUrl"
valuePropName="fileList"
getValueFromEvent={normFile}
>
<Upload />
</Form.Item>
</Form>
</Modal>
)}
</>
);
};
export default memo(EditorModal);

View File

@ -1,187 +0,0 @@
import React, { memo, useState, useEffect, useCallback } from 'react'
import {
EditOutlined,
MinusCircleOutlined,
MenuOutlined
} from '@ant-design/icons';
import { Button } from 'antd';
import { DragSource, DropTarget, DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import EditorModal from './editorModal';
import { uuid } from '@/utils/tool';
import styles from './index.less'
function ListItem(props) {
const { title, desc, link, imgUrl, type, onDel, onEdit,
// 这些 props 由 React DnD注入参考`collect`函数定义
isDragging, connectDragSource, connectDragPreview, connectDropTarget,
// 这些是组件收到的 props
item, style = {}, find, move, change, remove, ...restProps
} = props
const opacity = isDragging ? 0.5 : 1
return connectDropTarget( // 列表项本身作为 Drop 对象
connectDragPreview( // 整个列表项作为跟随拖动的影像
<div className={styles.listItem} {...restProps} style={Object.assign(style, { opacity })}>
<div className={styles.tit}>{ title }</div>
<div className={styles.desc}>{ desc }</div>
<div className={styles.actionBar}>
<span className={styles.action} onClick={onEdit}><EditOutlined /></span>
<span className={styles.action} onClick={onDel}><MinusCircleOutlined /></span>
{
connectDragSource(
<span className={styles.action}><MenuOutlined /></span>
) // 拖动图标作为 Drag 对象
}
</div>
</div>
)
)
}
const type = "item";
const dragSpec = {
// 拖动开始时,返回描述 source 数据。后续通过 monitor.getItem() 获得
beginDrag: props => ({
id: props.id,
originalIndex: props.find(props.id).index
}),
// 拖动停止时,处理 source 数据
endDrag(props, monitor) {
const { id: droppedId, originalIndex } = monitor.getItem();
const didDrop = monitor.didDrop();
// source 是否已经放置在 target
if (!didDrop) {
return props.move(droppedId, originalIndex);
}
}
}
const dragCollect = (connect, monitor) => ({
connectDragSource: connect.dragSource(), // 用于包装需要拖动的组件
connectDragPreview: connect.dragPreview(), // 用于包装需要拖动跟随预览的组件
isDragging: monitor.isDragging() // 用于判断是否处于拖动状态
})
const dropSpec = {
canDrop: () => false, // item 不处理 drop
hover(props, monitor) {
const { id: draggedId } = monitor.getItem();
const { id: overId } = props;
// 如果 source item 与 target item 不同,则交换位置并重新排序
if (draggedId !== overId) {
const { index: overIndex } = props.find(overId);
props.move(draggedId, overIndex);
}
}
}
const dropCollect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget() // 用于包装需接收拖拽的组件
})
const DndItem = DropTarget(type, dropSpec, dropCollect)(
DragSource(type, dragSpec, dragCollect)(ListItem)
)
const List = function(props) {
const { onChange, value, connectDropTarget } = props
const [list, setList] = useState(value)
const [visible, setVisible] = useState(false)
const [curItem, setCurItem] = useState()
const handleDel = (id) => {
let newVal = value.filter(item => id !== item.id)
onChange(newVal)
}
const find = id => {
const item = list.find(c => `${c.id}` === id);
return {
item,
index: list.indexOf(item)
}
}
const move = (id, toIndex) => {
const { item, index } = find(id);
const oldList = [...list];
oldList.splice(index, 1);
oldList.splice(toIndex, 0, item);
if(onChange) {
onChange(oldList)
return
}
setList(oldList)
}
const handleCancel = useCallback(() => {
setVisible(false)
}, [])
const handleEdit = useCallback((item) => {
setVisible(true)
setCurItem(item)
}, [])
const handleSave = useCallback((item) => {
setVisible(false)
console.log(22, list, item)
if(onChange) {
onChange(list.map(p => p.id === item.id ? item : p))
return
}
setList(prev => prev.map(p => p.id === item.id ? item : p))
}, [curItem])
const handleAdd = () => {
const item = {
title: '新增项标题',
desc: '新增项描述',
id: uuid(8, 10),
imgUrl: [],
link: ''
}
if(onChange) {
onChange([...list, item])
return
}
setList([...list, item])
}
useEffect(() => {
setList(value)
}, [value])
return connectDropTarget(
<div className={styles.dataList}>
{
!!(list && list.length) && list.map((item, i) =>
<DndItem
{...item}
onDel={handleDel.bind(this, item.id)}
onEdit={handleEdit.bind(this, item)}
key={i}
id={`${item.id}`}
find={find}
move={move}
/>
)
}
<div style={{marginTop: '10px'}}><Button type="primary" onClick={handleAdd} block>添加</Button></div>
<EditorModal visible={visible} onCancel={handleCancel} item={curItem} onSave={handleSave} />
</div>
)
}
const DndList = DropTarget(type, {}, connect => ({
connectDropTarget: connect.dropTarget()
}))(List)
// 将 HTMLBackend 作为参数传给 DragDropContext
export default memo((props) => {
return <DndProvider backend={HTML5Backend}>
<DndList {...props} />
</DndProvider>
})

View File

@ -0,0 +1,250 @@
import React, { memo, useState, useEffect, useCallback } from 'react';
import { EditOutlined, MinusCircleOutlined, MenuOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import {
DragSource,
DropTarget,
DndProvider,
ConnectDropTarget,
DragSourceSpec,
DropTargetConnector,
DragSourceMonitor,
DragSourceConnector,
DropTargetSpec,
ConnectDragSource,
ConnectDragPreview,
} from 'react-dnd';
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';
type ListItemProps = DndItemProps & {
isDragging: boolean;
connectDragSource: ConnectDragSource;
connectDragPreview: ConnectDragPreview;
connectDropTarget: ConnectDropTarget;
};
function ListItem(props: ListItemProps) {
const {
title,
desc,
link,
imgUrl,
type,
onDel,
onEdit,
// 这些 props 由 React DnD注入参考`collect`函数定义
isDragging,
connectDragSource,
connectDragPreview,
connectDropTarget,
} = props;
const opacity = isDragging ? 0.5 : 1;
return connectDropTarget(
// 列表项本身作为 Drop 对象
connectDragPreview(
// 整个列表项作为跟随拖动的影像
<div className={styles.listItem} style={Object.assign({}, { opacity })}>
<div className={styles.tit}>{title}</div>
<div className={styles.desc}>{desc}</div>
<div className={styles.actionBar}>
<span className={styles.action} onClick={() => onEdit()}>
<EditOutlined />
</span>
<span className={styles.action} onClick={() => onDel()}>
<MinusCircleOutlined />
</span>
{connectDragSource(
<span className={styles.action}>
<MenuOutlined />
</span>,
) // 拖动图标作为 Drag 对象
}
</div>
</div>,
),
);
}
type DndItemProps = BasicDataSource & {
onDel: Function;
onEdit: Function;
key: number;
id: string;
find: Function;
move: Function;
type?: number;
};
const type = 'item';
type DragObject = {
id: string;
originalIndex: number;
};
const dragSpec: DragSourceSpec<DndItemProps, DragObject> = {
// 拖动开始时,返回描述 source 数据。后续通过 monitor.getItem() 获得
beginDrag: props => ({
id: props.id,
originalIndex: props.find(props.id).index,
}),
// 拖动停止时,处理 source 数据
endDrag(props, monitor) {
const { id: droppedId, originalIndex } = monitor.getItem();
const didDrop = monitor.didDrop();
// source 是否已经放置在 target
if (!didDrop) {
return props.move(droppedId, originalIndex);
}
},
};
const dragCollect = (connect: DragSourceConnector, monitor: DragSourceMonitor) => ({
connectDragSource: connect.dragSource(), // 用于包装需要拖动的组件
connectDragPreview: connect.dragPreview(), // 用于包装需要拖动跟随预览的组件
isDragging: monitor.isDragging(), // 用于判断是否处于拖动状态
});
const dropSpec: DropTargetSpec<DndItemProps> = {
canDrop: () => false, // item 不处理 drop
hover(props, monitor) {
const { id: draggedId } = monitor.getItem();
const { id: overId } = props;
// 如果 source item 与 target item 不同,则交换位置并重新排序
if (draggedId !== overId) {
const { index: overIndex } = props.find(overId);
props.move(draggedId, overIndex);
}
},
};
const dropCollect = (connect: DropTargetConnector) => ({
connectDropTarget: connect.dropTarget(), // 用于包装需接收拖拽的组件
});
const DndItem = DropTarget(
type,
dropSpec,
dropCollect,
)(DragSource(type, dragSpec, dragCollect)(ListItem));
export type DataListMemo = {
onChange?: (v: BasicDataSource[]) => void;
value?: BasicDataSource[];
};
export type DataListType = DataListMemo & {
connectDropTarget: ConnectDropTarget;
};
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 handleDel = (id: string) => {
if (value && onChange) {
let newVal = value.filter(item => id !== item.id);
onChange(newVal);
}
};
const find = (id: string) => {
const item = list!.find(c => `${c.id}` === id)!;
return {
item,
index: list!.indexOf(item!),
};
};
const move = (id: string, toIndex: number) => {
const { item, index } = find(id);
const oldList = [...list!];
oldList.splice(index, 1);
oldList.splice(toIndex, 0, item);
if (onChange) {
onChange(oldList);
return;
}
setList(oldList);
};
const handleCancel = useCallback(() => {
setVisible(false);
}, []);
const handleEdit = useCallback((item: BasicDataSource) => {
setVisible(true);
setCurItem(item);
}, []);
const handleSave = useCallback(
(item: BasicDataSource) => {
setVisible(false);
if (onChange) {
onChange(list!.map(p => (p.id === item.id ? item : p)));
return;
}
setList(prev => prev!.map(p => (p.id === item.id ? item : p)));
},
[curItem],
);
const handleAdd = () => {
const item = {
title: '新增项标题',
desc: '新增项描述',
id: uuid(8, 10),
imgUrl: [],
link: '',
};
if (onChange) {
onChange([...list!, item]);
return;
}
setList([...list!, item]);
};
useEffect(() => {
setList(value);
}, [value]);
return connectDropTarget(
<div className={styles.dataList}>
{!!(list && list.length) &&
list.map((item, i) => (
<DndItem
{...item}
onDel={() => handleDel(item.id)}
onEdit={() => handleEdit(item)}
key={i}
id={`${item.id}`}
find={find}
move={move}
/>
))}
<div style={{ marginTop: '10px' }}>
<Button type="primary" onClick={handleAdd} block>
</Button>
</div>
<EditorModal visible={visible} onCancel={handleCancel} item={curItem} onSave={handleSave} />
</div>,
);
};
const DndList = DropTarget(type, {}, connect => ({
connectDropTarget: connect.dropTarget(),
}))(List);
// 将 HTMLBackend 作为参数传给 DragDropContext
export default memo((props: DataListMemo) => {
return (
<DndProvider backend={HTML5Backend}>
<DndList {...props} />
</DndProvider>
);
});

View File

@ -1,137 +0,0 @@
import { memo } from 'react'
import { NoticeBar, Progress } from 'zarm'
import styles from './components.less'
const Header = memo((props) => {
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) => {
const {
align,
text,
fontSize,
color,
lineHeight
} = props
return <div className={styles.textWrap} style={{color, textAlign: align, fontSize, lineHeight}}>
{ text }
</div>
})
const Notice = memo((props) => {
const {
text,
speed,
theme,
link,
isClose = false
} = props
return <NoticeBar theme={theme} closable={isClose} speed={speed}><a style={{color: 'inherit'}}>{ text }</a></NoticeBar>
})
const Qrcode = memo((props) => {
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 Footer = memo((props) => {
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) => {
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) => {
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) => {
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,
Notice,
Qrcode,
Footer,
Image,
List,
XProgress
}

View File

@ -0,0 +1,147 @@
import { memo } from 'react';
import { NoticeBar, Progress } from 'zarm';
import styles from './components.less';
import React from 'react';
import {
HeaderConfigType,
TextConfigType,
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 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, Notice, Qrcode, Footer, Image, List, XProgress };

View File

@ -1,22 +1,25 @@
import { dynamic } from 'umi';
import Loading from '../LoadingCp';
import { useMemo, memo } from 'react';
import { useMemo, memo, FC } from 'react';
import React from 'react';
import { UnionData, AllTemplateType } from './schema';
const needList = ['Tab', 'Carousel', 'Upload', 'Video', 'Icon'];
const DynamicFunc = type =>
const DynamicFunc = (type: AllTemplateType) =>
dynamic({
loader: async function() {
let Component;
let Component: FC<{ isTpl: boolean }>;
if (needList.includes(type)) {
const { default: Graph } = await import(`@/components/${type}`);
Component = Graph;
} else {
const Components = await import(`@/components/DynamicEngine/components`);
const Components = ((await import(`@/components/DynamicEngine/components`)) as unknown) as {
[key: string]: FC;
};
Component = Components[type];
}
return props => {
return (props: DynamicType) => {
const { config, isTpl } = props;
return <Component {...config} isTpl={isTpl} />;
};
@ -28,10 +31,15 @@ const DynamicFunc = type =>
),
});
const DynamicEngine = memo(props => {
type DynamicType = {
isTpl: boolean;
config: UnionData<'config'>;
type: AllTemplateType;
};
const DynamicEngine = memo((props: DynamicType) => {
const { type, config, isTpl } = props;
const Dynamic = useMemo(() => {
return DynamicFunc(type);
return (DynamicFunc(type) as unknown) as FC<DynamicType>;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [type, config]);
return <Dynamic type={type} config={config} isTpl={isTpl} />;

View File

@ -472,6 +472,20 @@ export interface SchemaType extends SchemaImplement {
XProgress: XProgressSchema;
}
export type UnionData<T extends keyof SchemaBasicImplement> =
| CarouselSchema[T]
| TextSchema[T]
| TabSchema[T]
| NoticeSchema[T]
| QRCodeSchema[T]
| FooterSchema[T]
| ImageSchema[T]
| HeaderSchema[T]
| ListSchema[T]
| IconSchema[T]
| VideoSchema[T]
| XProgressSchema[T];
const schema: SchemaType = {
Carousel: {
editData: [

View File

@ -1,15 +1,17 @@
import React, { memo, useState, useEffect } from 'react';
import { Form, Select, InputNumber, Input, Switch, Radio, Button } from 'antd';
import Upload from '@/components/Upload';
import DataList from '@/components/DataList';
import MutiText from '@/components/MutiText';
import Color from '@/components/Color';
import CardPicker from '@/components/CardPicker';
import Upload from '../Upload';
import DataList from '../DataList';
import MutiText from '../MutiText';
import Color from '../Color';
import CardPicker from '../CardPicker';
import { Store } from 'antd/lib/form/interface';
import { UnionData, BasicRangeType, IconSchema } from '../DynamicEngine/schema';
// import styles from './index.less';
const normFile = e => {
const normFile = (e: any) => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
//待修改
return e;
}
return e && e.fileList;
@ -22,10 +24,18 @@ const formItemLayout = {
wrapperCol: { span: 16 },
};
const FormEditor = props => {
const { config, defaultValue, onSave, onDel, uid } = props;
interface FormEditorProps {
uid: string;
onSave: Function;
onDel: Function;
defaultValue: { [key: string]: any };
config: Array<any>;
}
const onFinish = values => {
const FormEditor = (props: FormEditorProps) => {
const { config, defaultValue, onSave, onDel, uid } = props;
console.log(config, defaultValue, uid);
const onFinish = (values: Store) => {
onSave && onSave(values);
};
@ -80,7 +90,7 @@ const FormEditor = props => {
{item.type === 'Select' && (
<Form.Item label={item.name} name={item.key}>
<Select placeholder="请选择">
{item.range.map((v, i) => {
{item.range.map((v: BasicRangeType<string>, i: number) => {
return (
<Option value={v.key} key={i}>
{v.text}
@ -93,7 +103,7 @@ const FormEditor = props => {
{item.type === 'Radio' && (
<Form.Item label={item.name} name={item.key}>
<Radio.Group>
{item.range.map((v, i) => {
{item.range.map((v: BasicRangeType<string>, i: number) => {
return (
<Radio value={v.key} key={i}>
{v.text}
@ -120,7 +130,10 @@ const FormEditor = props => {
)}
{item.type === 'CardPicker' && (
<Form.Item label={item.name} name={item.key} valuePropName="type">
<CardPicker icons={item.icons} />
<CardPicker
icons={item.icons}
type={defaultValue['type'] as IconSchema['config']['type']}
/>
</Form.Item>
)}
</React.Fragment>
@ -130,7 +143,7 @@ const FormEditor = props => {
<Button type="primary" htmlType="submit">
</Button>
<Button type="danger" style={{ marginLeft: '20px' }} onClick={handleDel}>
<Button danger style={{ marginLeft: '20px' }} onClick={handleDel}>
</Button>
</Form.Item>

View File

@ -1,11 +0,0 @@
export default () => (
<div style={{ paddingTop: 100, textAlign: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div className="sk-fold">
<div className="sk-fold-cube"></div>
<div className="sk-fold-cube"></div>
<div className="sk-fold-cube"></div>
<div className="sk-fold-cube"></div>
</div>
</div>
)

View File

@ -0,0 +1,20 @@
import React from 'react';
export default () => (
<div
style={{
paddingTop: 100,
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<div className="sk-fold">
<div className="sk-fold-cube"></div>
<div className="sk-fold-cube"></div>
<div className="sk-fold-cube"></div>
<div className="sk-fold-cube"></div>
</div>
</div>
);

View File

@ -1,70 +0,0 @@
import React, { memo, useState, useEffect } from 'react'
import { Input, Button, Popconfirm } from 'antd'
import { MinusCircleOutlined } from '@ant-design/icons'
import styles from './index.less'
export default memo(function MutiText(props) {
const { value, onChange } = props
const [valueList, setValueList] = useState(value || [])
const handleAdd = () => {
setValueList((prev) => {
return [...prev, '新增项']
})
}
const handleDel = (index) => {
setValueList((prev) => {
let newList = prev.filter((item, i) => i !== index)
onChange && onChange(newList)
return newList
})
}
const handleChange = (index, e) => {
const { value } = e.target
setValueList((prev) => {
let newList = prev.map((item, i) => (i === index ? value : item))
onChange && onChange(newList)
return newList
})
}
useEffect(() => {
// eslint-disable-next-line no-unused-expressions
window['currentCates'] = valueList
return () => {
window['currentCates'] = null
}
}, [valueList])
return <div className={styles.mutiText}>
{
valueList.length ?
valueList.map((item, i) => {
return <div className={styles.iptWrap} key={i}>
<Input defaultValue={item} onChange={handleChange.bind(this, i)} />
<Popconfirm
title="确定要删除吗?"
onConfirm={handleDel.bind(this, i)}
placement="leftTop"
okText="确定"
cancelText="取消"
>
<span className={styles.delBtn}><MinusCircleOutlined /></span>
</Popconfirm>
</div>
}) :
<div className={styles.iptWrap}>
<Input />
</div>
}
{
valueList.length < 3 &&
<div className={styles.iptWrap}>
<Button type="primary" onClick={handleAdd}>添加项目</Button>
</div>
}
</div>
})

View File

@ -0,0 +1,80 @@
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';
type MultiTextProps = {
onChange?: (v: TabConfigType['tabs']) => void;
value?: TabConfigType['tabs'];
};
export default memo(function MutiText(props: MultiTextProps) {
const { value, onChange } = props;
const [valueList, setValueList] = useState(value || []);
const handleAdd = () => {
setValueList(prev => {
return [...prev, '新增项'];
});
};
const handleDel = (index: number) => {
setValueList(prev => {
let newList = prev.filter((_item, i) => i !== index);
onChange && onChange(newList);
return newList;
});
};
const handleChange = (index: number, e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setValueList(prev => {
let newList = prev.map((item, i) => (i === index ? value : item));
onChange && onChange(newList);
return newList;
});
};
useEffect(() => {
window['currentCates'] = valueList;
return () => {
window['currentCates'] = null;
};
}, [valueList]);
return (
<div className={styles.mutiText}>
{valueList.length ? (
valueList.map((item, i) => {
return (
<div className={styles.iptWrap} key={i}>
<Input defaultValue={item} onChange={e => handleChange(i, e)} />
<Popconfirm
title="确定要删除吗?"
onConfirm={() => handleDel(i)}
placement="leftTop"
okText="确定"
cancelText="取消"
>
<span className={styles.delBtn}>
<MinusCircleOutlined />
</span>
</Popconfirm>
</div>
);
})
) : (
<div className={styles.iptWrap}>
<Input />
</div>
)}
{valueList.length < 3 && (
<div className={styles.iptWrap}>
<Button type="primary" onClick={handleAdd}>
</Button>
</div>
)}
</div>
);
});

View File

@ -1,68 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { Tabs } from 'zarm';
import styles from './index.less';
const { Panel } = Tabs;
const XTab = (props) => {
const {
tabs = ['分类一', '分类二'],
activeColor,
color,
fontSize,
sourceData = [
{
"title": "趣谈小课",
"desc": "致力于打造优质小课程",
"link": "xxxxx",
"type": 0,
"imgUrl": "http://io.nainor.com/uploads/01_173e15d3493.png"
},
{
"title": "趣谈小课",
"desc": "致力于打造优质小课程",
"link": "xxxxx",
"type": 1,
"imgUrl": "http://io.nainor.com/uploads/01_173e15d3493.png"
},
{
"title": "趣谈小课",
"desc": "致力于打造优质小课程",
"link": "xxxxx",
"type": 0,
"imgUrl": "http://io.nainor.com/uploads/01_173e15d3493.png"
}
]
} = props
const tabWrapRef = useRef(null)
useEffect(() => {
tabWrapRef.current.querySelector('.za-tabs__line').style.backgroundColor = activeColor
}, [activeColor])
return <div className={styles.tabWrap} ref={tabWrapRef}>
<Tabs canSwipe onChange={(i) => { console.log(i); }}>
{
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

@ -0,0 +1,64 @@
import React, { useEffect, useRef } from 'react';
import { Tabs } from 'zarm';
import styles from './index.less';
import { TabConfigType } from '../DynamicEngine/schema';
const { Panel } = Tabs;
const XTab = (props: TabConfigType) => {
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
canSwipe
onChange={i => {
console.log(i);
}}
>
{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

@ -3,54 +3,69 @@ import { Upload, Modal, message } from 'antd';
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'
const isDev = process.env.NODE_ENV === 'development';
function getBase64(file) {
return new Promise((resolve, reject) => {
function getBase64(file: File | Blob) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onload = () => resolve(reader.result as string);
reader.onerror = error => reject(error);
});
}
interface PicturesWallType {
fileList?: UploadFile<any>[];
action?: string;
headers?: any;
withCredentials?: boolean;
maxLen?: number;
onChange?: (v: any) => void;
cropRate?: boolean;
isCrop?: boolean;
}
class PicturesWall extends React.Component {
class PicturesWall extends React.Component<PicturesWallType> {
state = {
previewVisible: false,
previewImage: '',
previewTitle: '',
fileList: this.props.fileList || []
fileList: this.props.fileList || [],
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = async file => {
handlePreview = async (file: UploadFile<any>) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
file.preview = await getBase64(file.originFileObj!);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
previewTitle: file.name || file.url.substring(file.url.lastIndexOf('/') + 1),
})
}
previewTitle: file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1),
});
};
handleChange = ({ file, fileList }) => {
this.setState({ fileList })
if(file.status === 'done') {
handleChange = ({ file, fileList }: UploadChangeParam<UploadFile<any>>) => {
this.setState({ fileList });
if (file.status === 'done') {
const files = fileList.map(item => {
const { uid, name, status } = item
const url = item.url || item.response.result.url
return { uid, name, status, url }
})
this.props.onChange && this.props.onChange(files)
const { uid, name, status } = item;
const url = item.url || item.response.result.url;
return { uid, name, status, url };
});
this.props.onChange && this.props.onChange(files);
}
}
};
handleBeforeUpload = (file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif';
handleBeforeUpload = (file: RcFile) => {
const isJpgOrPng =
file.type === 'image/jpeg' ||
file.type === 'image/png' ||
file.type === 'image/jpg' ||
file.type === 'image/gif';
if (!isJpgOrPng) {
message.error('只能上传格式为jpeg/png/gif的图片');
}
@ -59,7 +74,7 @@ class PicturesWall extends React.Component {
message.error('图片必须小于2MB!');
}
return isJpgOrPng && isLt2M;
}
};
render() {
const { previewVisible, previewImage, fileList, previewTitle } = this.state;
@ -68,8 +83,8 @@ class PicturesWall extends React.Component {
action = isDev ? 'http://192.168.1.6:3000/api/xxx' : 'http://xxxx',
headers,
withCredentials = true,
maxLen = 1
} = this.props
maxLen = 1,
} = this.props;
const uploadButton = (
<div>
@ -79,7 +94,13 @@ class PicturesWall extends React.Component {
);
return (
<ImgCrop modalTitle="裁剪图片" modalOk="确定" modalCancel="取消" rotate={true} aspect={375/158}>
<ImgCrop
modalTitle="裁剪图片"
modalOk="确定"
modalCancel="取消"
rotate={true}
aspect={375 / 158}
>
<Upload
fileList={fileList}
onPreview={this.handlePreview}
@ -91,8 +112,8 @@ class PicturesWall extends React.Component {
withCredentials={withCredentials}
headers={{
'x-requested-with': localStorage.getItem('user') || '',
'authorization': localStorage.getItem('token') || '',
...headers
authorization: localStorage.getItem('token') || '',
...headers,
}}
beforeUpload={this.handleBeforeUpload}
>
@ -111,4 +132,4 @@ class PicturesWall extends React.Component {
}
}
export default PicturesWall
export default PicturesWall;

View File

@ -1,17 +0,0 @@
import { memo } from 'react'
import { Player, BigPlayButton } from 'video-react'
import './index.css'
const VideoPlayer = memo((props) => {
const {
poster,
url
} = props
return <div>
<Player playsInline poster={poster[0].url} src={url || 'https://gossv.vcg.com/cmsUploadVideo/creative/1移轴/7月移轴.mp4'}>
<BigPlayButton position="center" />
</Player>
</div>
})
export default VideoPlayer

View File

@ -0,0 +1,21 @@
import React, { memo } from 'react';
import { Player, BigPlayButton } from 'video-react';
import './index.css';
import { VideoConfigType } from '../DynamicEngine/schema';
const VideoPlayer = memo((props: VideoConfigType) => {
const { poster, url } = props;
return (
<div>
<Player
playsInline
poster={poster[0].url}
src={url || 'https://gossv.vcg.com/cmsUploadVideo/creative/1移轴/7月移轴.mp4'}
>
<BigPlayButton position="center" />
</Player>
</div>
);
});
export default VideoPlayer;

View File

@ -1,4 +1,4 @@
import { memo } from 'react';
import React, { memo } from 'react';
import { Button, Popover } from 'antd';
import styles from './index.less';

View File

@ -1,150 +0,0 @@
import React, { useState } from "react";
import { DragSource, DropTarget, DragDropContext } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import { faTrashAlt, faArrowsAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import classnames from "classnames";
function Item(props) {
const {
// 这些 props 由 React DnD注入参考`collect`函数定义
isDragging, connectDragSource, connectDragPreview, connectDropTarget,
// 这些是组件收到的 props
item, style = {}, find, move, change, remove, ...restProps
} = props;
const opacity = isDragging ? 0.5 : 1;
const onRemove = event => {
event.stopPropagation();
remove(item);
}
return connectDropTarget( // 列表项本身作为 Drop 对象
connectDragPreview( // 整个列表项作为跟随拖动的影像
<div {...restProps} style={Object.assign(style, { opacity })}>
<p className="title">{item.title || "任务标题"}</p>
<ul className="oper-list">
{
connectDragSource(
<li className="oper-item icon-move">
<FontAwesomeIcon icon={faArrowsAlt} />
</li>
) // 拖动图标作为 Drag 对象
}
<li className="oper-item" onClick={onRemove}>
<FontAwesomeIcon icon={faTrashAlt} />
</li>
</ul>
</div>
)
);
}
const type = "item";
const dragSpec = {
// 拖动开始时,返回描述 source 数据。后续通过 monitor.getItem() 获得
beginDrag: props => ({
id: props.id,
originalIndex: props.find(props.id).index
}),
// 拖动停止时,处理 source 数据
endDrag(props, monitor) {
const { id: droppedId, originalIndex } = monitor.getItem();
const didDrop = monitor.didDrop();
// source 是否已经放置在 target
if (!didDrop) {
return props.move(droppedId, originalIndex);
}
return props.change(droppedId, originalIndex);
}
};
const dragCollect = (connect, monitor) => ({
connectDragSource: connect.dragSource(), // 用于包装需要拖动的组件
connectDragPreview: connect.dragPreview(), // 用于包装需要拖动跟随预览的组件
isDragging: monitor.isDragging() // 用于判断是否处于拖动状态
});
const dropSpec = {
canDrop: () => false, // item 不处理 drop
hover(props, monitor) {
const { id: draggedId } = monitor.getItem();
const { id: overId } = props;
// 如果 source item 与 target item 不同,则交换位置并重新排序
if (draggedId !== overId) {
const { index: overIndex } = props.find(overId);
props.move(draggedId, overIndex);
}
}
};
const dropCollect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget() // 用于包装需接收拖拽的组件
});
const DndItem = DropTarget(type, dropSpec, dropCollect)(
DragSource(type, dragSpec, dragCollect)(Item)
);
function List(props) {
let { list: propsList, activeItem, connectDropTarget } = props;
propsList = propsList.map(item => {
const isActive = activeItem.id === item.id;
item = isActive ? activeItem : item;
item.active = isActive;
return item;
});
const [list, setList] = useState(propsList);
const find = id => {
const item = list.find(c => `${c.id}` === id);
return {
item,
index: list.indexOf(item)
};
};
const move = (id, toIndex) => {
const { item, index } = find(id);
list.splice(index, 1);
list.splice(toIndex, 0, item);
setList([...list]);
};
const change = (id, fromIndex) => {
const { index: toIndex } = find(id);
props.onDropEnd(list, fromIndex, toIndex);
};
const remove = item => {
const newList = list.filter(it => it.id !== item.id);
setList(newList);
props.onDelete(newList);
};
const onClick = event => {
const { id } = event.currentTarget;
const { item } = find(id);
props.onClick(item);
};
return connectDropTarget(
<ul className="list">
{list.map((item, index) => (
<li
className={classnames("item", { active: item.active })}
key={item.id}
>
<div className="index">{index + 1}</div>
<DndItem
className="info"
id={`${item.id}`}
item={item}
find={find}
move={move}
change={change}
remove={remove}
onClick={onClick}
/>
</li>
))}
</ul>
);
}
const DndList = DropTarget(type, {}, connect => ({
connectDropTarget: connect.dropTarget()
}))(List);
// 将 HTMLBackend 作为参数传给 DragDropContext
export default DragDropContext(HTML5Backend)(DndList);

View File

@ -27,7 +27,6 @@ const Container = memo(props => {
const [scaleNum, setScale] = useState(1);
const { pointData, curPoint, dispatch } = props;
// 指定画布的id
let canvasId = 'js_canvas';

View File

@ -1,8 +1,10 @@
import { Form, Input, Button, Checkbox } from 'antd';
import { Form, Input, Button } from 'antd';
import http from '@/utils/req';
import { history } from 'umi';
import styles from './index.less';
import React from 'react';
import { ValidateErrorEntity, Store } from 'rc-field-form/lib/interface';
import { RouteComponentProps } from 'react-router-dom';
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 16 },
@ -11,16 +13,18 @@ const tailLayout = {
wrapperCol: { offset: 6, span: 16 },
};
const Login = (props) => {
const onFinish = values => {
http.post('/login', {...values}).then(res => {
localStorage.setItem('token', res.token)
localStorage.setItem('user', values.username)
history.push('/')
})
const Login = (props: RouteComponentProps) => {
const onFinish = (values: Store) => {
http
.post<Store, { token: string }>('/login', { ...values })
.then(res => {
localStorage.setItem('token', res.token);
localStorage.setItem('user', values.username);
history.push('/');
});
};
const onFinishFailed = errorInfo => {
const onFinishFailed = (errorInfo: ValidateErrorEntity) => {
console.log('Failed:', errorInfo);
};
@ -34,7 +38,10 @@ const Login = (props) => {
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<div className={styles.tit}>Doring开放平台<span style={{marginLeft: '20px',fontSize: '18px',color: '#06c'}}></span></div>
<div className={styles.tit}>
Doring开放平台
<span style={{ marginLeft: '20px', fontSize: '18px', color: '#06c' }}></span>
</div>
<Form.Item
label="用户名"
name="username"
@ -57,14 +64,13 @@ const Login = (props) => {
</Button>
</Form.Item>
<Form.Item {...tailLayout}>
<Button block onClick={() => props.history.push(`/editor?tid=${props.location.query.tid}`)}>
<Button block onClick={() => props.history.push(`/editor?tid=${123456}`)}>
使
</Button>
</Form.Item>
</Form>
</Form>
</div>
);
};
export default Login
export default Login;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Result } from 'antd';
function MobileTip(props) {
function MobileTip() {
return (
<div>
<Result

3
src/typings.d.ts vendored
View File

@ -1,3 +1,6 @@
declare module '*.css';
declare module '*.png';
declare module '*.less';
interface Window {
currentCates: null | Array<string>;
}

View File

@ -1,38 +0,0 @@
// 生成uuid
function uuid(len, radix) {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
let uuid = [], i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
} else {
let r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random()*16;
uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
// 将rgba字符串对象转化为rgba对象
function rgba2Obj(rgba = '') {
let reg = /rgba\((\d+),(\d+),(\d+),(\d+)\)/g
let rgbaObj = {}
rgba.replace(reg, (m, r, g, b, a) => {
rgbaObj = {r, g, b, a}
})
return rgbaObj
}
export {
uuid,
rgba2Obj
}

40
src/utils/tool.ts Normal file
View File

@ -0,0 +1,40 @@
import { RGBColor } from 'react-color';
// 生成uuid
function uuid(len: number, radix: number) {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
let uuid = [],
i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
} else {
let r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16);
uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
// 将rgba字符串对象转化为rgba对象
function rgba2Obj(rgba = '') {
let reg = /rgba\((\d+),(\d+),(\d+),(\d+)\)/g;
let rgbaObj: RGBColor = { r: 0, g: 0, b: 0, a: 0 };
rgba.replace(reg, (_m, r, g, b, a) => {
rgbaObj = { r, g, b, a };
return rgba;
});
return rgbaObj;
}
export { uuid, rgba2Obj };

771
src/video-react.d.ts vendored Normal file
View File

@ -0,0 +1,771 @@
declare module 'video-react' {
type PreloadType = 'auto' | 'metadata' | 'none';
interface PlayerPropsType {
children?: any;
width?: string | number;
height?: string | number;
fluid?: boolean; // = true;
muted?: boolean; // = false;
playsInline?: boolean; // = false;
aspectRatio?: string; // = 'auto';
className?: string;
videoId?: string;
startTime?: number;
loop?: boolean;
autoPlay?: boolean;
src?: string;
poster?: string;
preload?: PreloadType; // = 'auto';
onLoadStart?: ReactEventHandler;
onWaiting?: ReactEventHandler;
onCanPlay?: ReactEventHandler;
onCanPlayThrough?: ReactEventHandler;
onPlaying?: ReactEventHandler;
onEnded?: ReactEventHandler;
onSeeking?: ReactEventHandler;
onSeeked?: ReactEventHandler;
onPlay?: ReactEventHandler;
onPause?: ReactEventHandler;
onProgress?: ReactEventHandler;
onDurationChange?: ReactEventHandler;
onError?: ReactEventHandler;
onSuspend?: ReactEventHandler;
onAbort?: ReactEventHandler;
onEmptied?: ReactEventHandler;
onStalled?: ReactEventHandler;
onLoadedMetadata?: ReactEventHandler;
onLoadedData?: ReactEventHandler;
onTimeUpdate?: ReactEventHandler;
onRateChange?: ReactEventHandler;
onVolumeChange?: ReactEventHandler;
store?: object;
}
class Player extends React.Component<PlayerPropsType> {
readonly video: Video;
getDefaultChildren(originalChildren): Array<React.Component>;
getChildren(props): Array<React.Component>;
setWidthOrHeight(style: object, name: string, value: string | number);
getStyle(): object;
// get redux state
// { player, operation }
getState(): object;
// get playback rate
get playbackRate(): number;
// set playback rate
// speed of video
set playbackRate(rate: number);
get muted(): boolean;
set muted(val: boolean);
get volume(): number;
set volume(val: number);
// video width
get videoWidth(): number;
// video height
get videoHeight(): number;
// play the video
play();
// pause the video
pause();
// Change the video source and re-load the video:
load();
// Add a new text track to the video
addTextTrack(kind: TextTrackKind, label?: string, language?: string): TextTrack;
// Check if your browser can play different types of video:
canPlayType(type: string): CanPlayTypeResult;
// seek video by time
seek(time: number);
// jump forward x seconds
forward(seconds: number);
// jump back x seconds
replay(seconds: number);
// enter or exist full screen
toggleFullscreen();
// subscribe to player state change
subscribeToStateChange(listener: (state: any, prevState: any) => void);
}
interface VideoPropsType {
actions?: object;
player?: object;
children?: any;
startTime?: number;
loop?: boolean;
muted?: boolean;
autoPlay?: boolean;
playsInline?: boolean;
src?: string;
poster?: string;
className?: string;
preload?: PreloadType;
crossOrigin?: string;
onLoadStart?: ReactEventHandler;
onWaiting?: ReactEventHandler;
onCanPlay?: ReactEventHandler;
onCanPlayThrough?: ReactEventHandler;
onPlaying?: ReactEventHandler;
onEnded?: ReactEventHandler;
onSeeking?: ReactEventHandler;
onSeeked?: ReactEventHandler;
onPlay?: ReactEventHandler;
onPause?: ReactEventHandler;
onProgress?: ReactEventHandler;
onDurationChange?: ReactEventHandler;
onError?: ReactEventHandler;
onSuspend?: ReactEventHandler;
onAbort?: ReactEventHandler;
onEmptied?: ReactEventHandler;
onStalled?: ReactEventHandler;
onLoadedMetadata?: ReactEventHandler;
onLoadedData?: ReactEventHandler;
onTimeUpdate?: ReactEventHandler;
onRateChange?: ReactEventHandler;
onVolumeChange?: ReactEventHandler;
onResize?: ReactEventHandler;
}
class Video extends React.Component<VideoPropsType> {
// get all video properties
getProperties(): any;
// get playback rate
get playbackRate(): number;
// set playback rate
// speed of video
set playbackRate(rate: number);
get muted(): boolean;
set muted(val: boolean);
get volume(): number;
set volume(val: number);
// video width
get videoWidth(): number;
// video height
get videoHeight(): number;
// play the video
play();
// pause the video
pause();
// Change the video source and re-load the video:
load();
// Add a new text track to the video
addTextTrack(kind: TextTrackKind, label?: string, language?: string): TextTrack;
// Check if your browser can play different types of video:
canPlayType(type: string): CanPlayTypeResult;
// toggle play
togglePlay();
// seek video by time
seek(time: number);
// jump forward x seconds
forward(seconds: number);
// jump back x seconds
replay(seconds: number);
// enter or exist full screen
toggleFullscreen();
}
interface BigPlayButtonPropsType {
actions?: object;
player?: object;
position?: 'center' | 'left-top'; // = 'left';
className?: string;
}
class BigPlayButton extends React.Component<BigPlayButtonPropsType> {}
interface LoadingSpinnerPropsType {
player?: object;
className?: string;
}
class LoadingSpinner extends React.Component<LoadingSpinnerPropsType> {}
interface PosterImagePropsType {
poster?: string;
player?: object;
actions?: object;
className?: string;
}
class PosterImage extends React.Component<PosterImagePropsType> {}
interface BezelPropsType {
manager?: object;
className?: string;
}
class Bezel extends React.Component<BezelPropsType> {}
interface ShortcutPropsType {
clickable?: boolean; // = true;
dblclickable?: boolean; // = true;
manager?: object;
actions?: object;
player?: object;
shortcuts?: Array<any>;
}
class Shortcut extends React.Component<ShortcutPropsType> {}
interface ControlBarPropsType {
children?: any;
autoHide?: boolean; // = true;
autoHideTime?: number; // used in Player
disableDefaultControls?: boolean;
disableCompletely?: boolean; // = false;
className?: string;
}
class ControlBar extends React.Component<ControlBarPropsType> {}
interface PlayTogglePropsType {
actions?: object;
player?: object;
className?: string;
}
class PlayToggle extends React.Component<PlayTogglePropsType> {}
type ForwardSecondsType = 5 | 10 | 30;
interface ForwardControlPropsType {
actions?: object;
className?: string;
seconds?: ForwardSecondsType; // = 10;
}
class ForwardControl extends React.Component<ForwardControlPropsType> {}
interface ReplayControlPropsType {
actions?: object;
className?: string;
seconds?: ForwardSecondsType; // = 10;
}
class ReplayControl extends React.Component<ReplayControlPropsType> {}
interface FullscreenTogglePropsType {
actions?: object;
player?: object;
className?: string;
}
class FullscreenToggle extends React.Component<FullscreenTogglePropsType> {}
interface ProgressControlPropsType {
player?: object;
className?: string;
}
class ProgressControl extends React.Component<ProgressControlPropsType> {}
interface SeekBarPropsType {
player?: object;
mouseTime?: object;
actions?: object;
className?: string;
}
class SeekBar extends React.Component<SeekBarPropsType> {
/**
* Get percentage of video played
*
* @return {Number} Percentage played
* @method getPercent
*/
getPercent(): number;
}
interface SliderPropsType {
className?: string;
onMouseDown?: ReactEventHandler;
onMouseMove?: ReactEventHandler;
stepForward?: Function;
stepBack?: Function;
sliderActive?: ReactEventHandler;
sliderInactive?: ReactEventHandler;
onMouseUp?: ReactEventHandler;
onFocus?: ReactEventHandler;
onBlur?: ReactEventHandler;
onClick?: ReactEventHandler;
getPercent?: () => number;
vertical?: boolean;
children?: ReactNode;
label?: string;
valuenow?: string;
valuetext?: string;
}
class Slider extends React.Component<SliderPropsType> {}
interface PlayProgressBarPropsType {
currentTime?: number;
duration?: number;
percentage?: string;
className?: string;
}
class PlayProgressBar extends React.Component<PlayProgressBarPropsType> {}
interface LoadProgressBarPropsType {
duration?: number;
buffered?: object;
className?: string;
}
const LoadProgressBar: React.FC<LoadProgressBarPropsType>;
interface MouseTimeDisplayPropsType {
duration?: number;
mouseTime?: {
time: number;
position: number;
};
className?: string;
text?: string;
}
const MouseTimeDisplay: React.FC<MouseTimeDisplayPropsType>;
interface RemainingTimeDisplayPropsType {
player?: {
currentTime: number;
duration: number;
};
className?: string;
}
const RemainingTimeDisplay: React.FC<RemainingTimeDisplayPropsType>;
interface CurrentTimeDisplayPropsType {
player?: {
currentTime: number;
duration: number;
};
className?: string;
}
const CurrentTimeDisplay: React.FC<CurrentTimeDisplayPropsType>;
interface DurationDisplayPropsType {
player?: {
duration: number;
};
className?: string;
}
const DurationDisplay: React.FC<DurationDisplayPropsType>;
interface TimeDividerPropsType {
separator?: string;
className?: string;
}
const TimeDivider: React.FC<TimeDividerPropsType>;
interface VolumeMenuButtonPropsType {
player?: {
volume: number;
muted: boolean;
};
actions?: object;
vertical?: boolean;
className?: string;
alwaysShowVolume?: boolean;
}
class VolumeMenuButton extends React.Component<VolumeMenuButtonPropsType> {
get volumeLevel(): number;
}
interface PlaybackRateMenuButtonPropsType {
player?: object;
actions?: object;
rates?: Array<number>; // = [2, 1.5, 1.25, 1, 0.5, 0.25];
className?: string;
}
class PlaybackRateMenuButton extends React.Component<PlaybackRateMenuButtonPropsType> {}
interface ClosedCaptionButtonPropsType {
player?: object;
actions?: object;
className?: string;
offMenuText?: string; // = 'Off';
showOffMenu?: boolean; // = true;
kinds?: Array<string>; // = ['captions', 'subtitles']; // `kind`s of TextTrack to look for to associate it with this menu.
}
class ClosedCaptionButton extends React.Component<ClosedCaptionButtonPropsType> {}
class PlaybackRate extends React.Component {}
interface MenuButtonPropsType {
inline?: boolean;
items?: Array<any>;
className?: string;
onSelectItem?: ReactEventHandler;
children?: any;
selectedIndex?: number;
}
class MenuButton extends React.Component<MenuButtonPropsType> {}
namespace playerActions {
type OPERATE = 'video-react/OPERATE';
type FULLSCREEN_CHANGE = 'video-react/FULLSCREEN_CHANGE';
type PLAYER_ACTIVATE = 'video-react/PLAYER_ACTIVATE';
type USER_ACTIVATE = 'video-react/USER_ACTIVATE';
function handleFullscreenChange(
isFullscreen: boolean,
): {
type: FULLSCREEN_CHANGE;
isFullscreen;
};
function activate(
activity,
): {
type: PLAYER_ACTIVATE;
activity;
};
function userActivate(
activity,
): {
type: USER_ACTIVATE;
activity;
};
function play(operation: {
action: 'play';
source: string;
}): {
type: OPERATE;
operation;
};
function pause(operation: {
action: 'pause';
source: string;
}): {
type: OPERATE;
operation;
};
function togglePlay(operation?: {
action: 'toggle-play';
source: string;
}): {
type: OPERATE;
operation;
};
// seek video by time
function seek(
time: number,
operation?: {
action: 'seek';
source: string;
},
): {
type: OPERATE;
operation;
};
// jump forward x seconds
function forward(
seconds: number,
operation?: {
action: string;
source: string;
},
): {
type: OPERATE;
operation;
};
// jump back x seconds
function replay(
seconds: number,
operation?: {
action: string;
source: string;
},
): {
type: OPERATE;
operation;
};
function changeRate(
rate: number,
operation?: {
action: 'change-rate';
source: string;
},
): {
type: OPERATE;
operation;
};
function changeVolume(
volume: number,
operation?: {
action: 'change-volume';
source: string;
},
): {
type: OPERATE;
operation;
};
function mute(
muted: boolean,
operation?: {
action: 'muted' | 'unmuted';
source: string;
},
): {
type: OPERATE;
operation;
};
function toggleFullscreen(player): { type: string; [key: string]: any };
}
namespace videoActions {
type LOAD_START = 'video-react/LOAD_START';
type CAN_PLAY = 'video-react/CAN_PLAY';
type WAITING = 'video-react/WAITING';
type CAN_PLAY_THROUGH = 'video-react/CAN_PLAY_THROUGH';
type PLAYING = 'video-react/PLAYING';
type PLAY = 'video-react/PLAY';
type PAUSE = 'video-react/PAUSE';
type END = 'video-react/END';
type SEEKING = 'video-react/SEEKING';
type SEEKED = 'video-react/SEEKED';
type SEEKING_TIME = 'video-react/SEEKING_TIME';
type END_SEEKING = 'video-react/END_SEEKING';
type DURATION_CHANGE = 'video-react/DURATION_CHANGE';
type TIME_UPDATE = 'video-react/TIME_UPDATE';
type VOLUME_CHANGE = 'video-react/VOLUME_CHANGE';
type PROGRESS_CHANGE = 'video-react/PROGRESS_CHANGE';
type RATE_CHANGE = 'video-react/RATE_CHANGE';
type SUSPEND = 'video-react/SUSPEND';
type ABORT = 'video-react/ABORT';
type EMPTIED = 'video-react/EMPTIED';
type STALLED = 'video-react/STALLED';
type LOADED_META_DATA = 'video-react/LOADED_META_DATA';
type LOADED_DATA = 'video-react/LOADED_DATA';
type RESIZE = 'video-react/RESIZE';
type ERROR = 'video-react/ERROR';
type ACTIVATE_TEXT_TRACK = 'video-react/ACTIVATE_TEXT_TRACK';
function handleLoadStart(
videoProps,
): {
type: LOAD_START;
videoProps;
};
function handleCanPlay(
videoProps,
): {
type: CAN_PLAY;
videoProps;
};
function handleWaiting(
videoProps,
): {
type: WAITING;
videoProps;
};
function handleCanPlayThrough(
videoProps,
): {
type: CAN_PLAY_THROUGH;
videoProps;
};
function handlePlaying(
videoProps,
): {
type: PLAYING;
videoProps;
};
function handlePlay(
videoProps,
): {
type: PLAY;
videoProps;
};
function handlePause(
videoProps,
): {
type: PAUSE;
videoProps;
};
function handleEnd(
videoProps,
): {
type: END;
videoProps;
};
function handleSeeking(
videoProps,
): {
type: SEEKING;
videoProps;
};
function handleSeeked(
videoProps,
): {
type: SEEKED;
videoProps;
};
function handleDurationChange(
videoProps,
): {
type: DURATION_CHANGE;
videoProps;
};
function handleTimeUpdate(
videoProps,
): {
type: TIME_UPDATE;
videoProps;
};
function handleVolumeChange(
videoProps,
): {
type: VOLUME_CHANGE;
videoProps;
};
function handleProgressChange(
videoProps,
): {
type: PROGRESS_CHANGE;
videoProps;
};
function handleRateChange(
videoProps,
): {
type: RATE_CHANGE;
videoProps;
};
function handleSuspend(
videoProps,
): {
type: SUSPEND;
videoProps;
};
function handleAbort(
videoProps,
): {
type: ABORT;
videoProps;
};
function handleEmptied(
videoProps,
): {
type: EMPTIED;
videoProps;
};
function handleStalled(
videoProps,
): {
type: STALLED;
videoProps;
};
function handleLoadedMetaData(
videoProps,
): {
type: LOADED_META_DATA;
videoProps;
};
function handleLoadedData(
videoProps,
): {
type: LOADED_DATA;
videoProps;
};
function handleResize(
videoProps,
): {
type: RESIZE;
videoProps;
};
function handleError(
videoProps,
): {
type: ERROR;
videoProps;
};
function handleSeekingTime(
time,
): {
type: SEEKING_TIME;
time;
};
function handleEndSeeking(
time,
): {
type: END_SEEKING;
time;
};
function activateTextTrack(
textTrack,
): {
type: ACTIVATE_TEXT_TRACK;
textTrack;
};
}
function playerReducer(state: any, action: any);
function operationReducer(state: any, action: any);
}

View File

@ -1830,6 +1830,11 @@
dependencies:
"@types/node" "*"
"@types/classnames@^2.2.10":
version "2.2.10"
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999"
integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==
"@types/color-convert@*":
version "1.9.0"
resolved "https://registry.npm.taobao.org/@types/color-convert/download/@types/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d"
@ -2126,6 +2131,14 @@
resolved "https://registry.npm.taobao.org/@types/range-parser/download/@types/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=
"@types/react-color@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.4.tgz#c63daf012ad067ac0127bdd86725f079d02082bd"
integrity sha512-EswbYJDF1kkrx93/YU+BbBtb46CCtDMvTiGmcOa/c5PETnwTiSWoseJ1oSWeRl/4rUXkhME9bVURvvPg0W5YQw==
dependencies:
"@types/react" "*"
"@types/reactcss" "*"
"@types/react-dom@^16.9.8":
version "16.9.8"
resolved "https://registry.npm.taobao.org/@types/react-dom/download/@types/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
@ -2184,6 +2197,13 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/reactcss@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834"
integrity sha512-d2gQQ0IL6hXLnoRfVYZukQNWHuVsE75DzFTLPUuyyEhJS8G2VvlE+qfQQ91SJjaMqlURRCNIsX7Jcsw6cEuJlA==
dependencies:
"@types/react" "*"
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.npm.taobao.org/@types/resolve/download/@types/resolve-1.17.1.tgz?cache=0&sync_timestamp=1596840738717&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fresolve%2Fdownload%2F%40types%2Fresolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"