mirror of
https://github.com/MrXujiang/h5-Dooring.git
synced 2026-01-07 04:08:10 +00:00
Merge pull request #17 from MrXujiang/yehuozhiliwork
connect components
This commit is contained in:
commit
af51d7e259
@ -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",
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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
|
||||
83
src/components/Color/index.tsx
Normal file
83
src/components/Color/index.tsx
Normal 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;
|
||||
@ -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)
|
||||
123
src/components/DataList/editorModal.tsx
Normal file
123
src/components/DataList/editorModal.tsx
Normal 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);
|
||||
@ -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>
|
||||
})
|
||||
250
src/components/DataList/index.tsx
Normal file
250
src/components/DataList/index.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
@ -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
|
||||
}
|
||||
147
src/components/DynamicEngine/components.tsx
Normal file
147
src/components/DynamicEngine/components.tsx
Normal 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 };
|
||||
@ -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} />;
|
||||
@ -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: [
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
)
|
||||
20
src/components/LoadingCp/index.tsx
Normal file
20
src/components/LoadingCp/index.tsx
Normal 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>
|
||||
);
|
||||
@ -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>
|
||||
})
|
||||
80
src/components/MutiText/index.tsx
Normal file
80
src/components/MutiText/index.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
@ -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;
|
||||
64
src/components/Tab/index.tsx
Normal file
64
src/components/Tab/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
@ -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
|
||||
21
src/components/Video/index.tsx
Normal file
21
src/components/Video/index.tsx
Normal 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;
|
||||
@ -1,4 +1,4 @@
|
||||
import { memo } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { Button, Popover } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
@ -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);
|
||||
@ -27,7 +27,6 @@ const Container = memo(props => {
|
||||
const [scaleNum, setScale] = useState(1);
|
||||
|
||||
const { pointData, curPoint, dispatch } = props;
|
||||
|
||||
// 指定画布的id
|
||||
let canvasId = 'js_canvas';
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
3
src/typings.d.ts
vendored
@ -1,3 +1,6 @@
|
||||
declare module '*.css';
|
||||
declare module '*.png';
|
||||
declare module '*.less';
|
||||
interface Window {
|
||||
currentCates: null | Array<string>;
|
||||
}
|
||||
|
||||
@ -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
40
src/utils/tool.ts
Normal 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
771
src/video-react.d.ts
vendored
Normal 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);
|
||||
}
|
||||
20
yarn.lock
20
yarn.lock
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user