mirror of
https://github.com/MrXujiang/h5-Dooring.git
synced 2026-01-25 08:58:13 +00:00
Merge pull request #17 from MrXujiang/yehuozhiliwork
connect components
This commit is contained in:
commit
af51d7e259
@ -78,6 +78,8 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/classnames": "^2.2.10",
|
||||||
|
"@types/react-color": "^3.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "2.x",
|
"@typescript-eslint/eslint-plugin": "2.x",
|
||||||
"@typescript-eslint/parser": "2.x",
|
"@typescript-eslint/parser": "2.x",
|
||||||
"babel-eslint": "10.x",
|
"babel-eslint": "10.x",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { BackToTop, Icon } from 'zarm';
|
import { BackToTop, Icon } from 'zarm';
|
||||||
|
import React from 'react';
|
||||||
const themeObj = {
|
const themeObj = {
|
||||||
simple: { bgColor: '#fff', color: '#999' },
|
simple: { bgColor: '#fff', color: '#999' },
|
||||||
black: { bgColor: '#000', color: '#fff' },
|
black: { bgColor: '#000', color: '#fff' },
|
||||||
|
|||||||
@ -17,81 +17,87 @@ export default function Calibration(props: CalibrationTypes) {
|
|||||||
const calibrationRef = useRef<HTMLDivElement>(null);
|
const calibrationRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let calibration = calibrationRef.current.getBoundingClientRect();
|
if (calibrationRef.current) {
|
||||||
setCalibration({ width: calibration.width, height: calibration.height });
|
let calibration = calibrationRef.current.getBoundingClientRect();
|
||||||
let length = direction === 'up' ? calibration.width : calibration.height;
|
setCalibration({ width: calibration.width, height: calibration.height });
|
||||||
for (let i = 0; i < length / 5; i++) {
|
let length = direction === 'up' ? calibration.width : calibration.height;
|
||||||
if (i % 10 === 0) {
|
for (let i = 0; i < length / 5; i++) {
|
||||||
generateElement(true, i);
|
if (i % 10 === 0) {
|
||||||
} else {
|
generateElement(true, i);
|
||||||
generateElement();
|
} else {
|
||||||
|
generateElement();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [direction]);
|
}, [direction]);
|
||||||
|
|
||||||
const generateElement = (item?: boolean, num?: number) => {
|
const generateElement = (item?: boolean, num?: number) => {
|
||||||
let createSpan = document.createElement('div');
|
if (calibrationRef.current) {
|
||||||
createSpan.className = 'calibrationLine';
|
let createSpan = document.createElement('div');
|
||||||
createSpan.style.backgroundColor = '#ccc';
|
createSpan.className = 'calibrationLine';
|
||||||
calibrationRef.current.style.display = 'flex';
|
createSpan.style.backgroundColor = '#ccc';
|
||||||
calibrationRef.current.style.justifyContent = 'space-between';
|
calibrationRef.current.style.display = 'flex';
|
||||||
if (direction === 'up') {
|
calibrationRef.current.style.justifyContent = 'space-between';
|
||||||
calibrationRef.current.style.marginLeft = '50px';
|
|
||||||
createSpan.style.width = '1px';
|
|
||||||
createSpan.style.height = '6px';
|
|
||||||
createSpan.style.display = 'inline-block';
|
|
||||||
} else {
|
|
||||||
calibrationRef.current.style.flexDirection = 'column';
|
|
||||||
createSpan.style.height = '1px';
|
|
||||||
createSpan.style.width = '6px';
|
|
||||||
}
|
|
||||||
if (item) {
|
|
||||||
let createSpanContent = document.createElement('span');
|
|
||||||
if (direction === 'up') {
|
if (direction === 'up') {
|
||||||
createSpan.style.height = '12px';
|
calibrationRef.current.style.marginLeft = '50px';
|
||||||
createSpanContent.style.transform = 'translate3d(-4px, 20px, 0px)';
|
createSpan.style.width = '1px';
|
||||||
createSpan.style.transform = 'translateY(0px)';
|
createSpan.style.height = '6px';
|
||||||
|
createSpan.style.display = 'inline-block';
|
||||||
} else {
|
} else {
|
||||||
createSpan.style.width = '12px';
|
calibrationRef.current.style.flexDirection = 'column';
|
||||||
createSpanContent.style.paddingLeft = '20px';
|
createSpan.style.height = '1px';
|
||||||
|
createSpan.style.width = '6px';
|
||||||
}
|
}
|
||||||
createSpanContent.style.display = 'block';
|
if (item) {
|
||||||
createSpanContent.className = 'calibrationNumber';
|
let createSpanContent = document.createElement('span');
|
||||||
createSpanContent.innerHTML = num * 5 + '';
|
if (direction === 'up') {
|
||||||
createSpan.appendChild(createSpanContent);
|
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(() => {
|
useEffect(() => {
|
||||||
let width = calibrationLength.width
|
if (calibrationRef.current) {
|
||||||
? calibrationLength.width
|
let width = calibrationLength.width
|
||||||
: calibrationRef.current.getBoundingClientRect().width;
|
? calibrationLength.width
|
||||||
let height = calibrationLength.height
|
: calibrationRef.current.getBoundingClientRect().width;
|
||||||
? calibrationLength.height
|
let height = calibrationLength.height
|
||||||
: calibrationRef.current.getBoundingClientRect().height;
|
? calibrationLength.height
|
||||||
let arr = [...calibrationRef.current.querySelectorAll('.calibrationLine')];
|
: calibrationRef.current.getBoundingClientRect().height;
|
||||||
if (arr.length) {
|
let arr = [...calibrationRef.current.querySelectorAll('.calibrationLine')];
|
||||||
if (direction === 'up') {
|
if (arr.length) {
|
||||||
calibrationRef.current.style.width = parseFloat(multiple.toFixed(1)) * width + 'px';
|
if (direction === 'up') {
|
||||||
arr.forEach(el => {
|
calibrationRef.current.style.width = parseFloat(multiple.toFixed(1)) * width + 'px';
|
||||||
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
|
arr.forEach(el => {
|
||||||
if (dom) {
|
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
|
||||||
dom.style.transform = `translate3d(-4px, 16px, 0px) scale(${(multiple + 0.1).toFixed(
|
if (dom) {
|
||||||
1,
|
dom.style.transform = `translate3d(-4px, 16px, 0px) scale(${(multiple + 0.1).toFixed(
|
||||||
)})`;
|
1,
|
||||||
}
|
)})`;
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
calibrationRef.current.style.height = parseFloat(multiple.toFixed(1)) * height + 'px';
|
} else {
|
||||||
arr.forEach(el => {
|
calibrationRef.current.style.height = parseFloat(multiple.toFixed(1)) * height + 'px';
|
||||||
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
|
arr.forEach(el => {
|
||||||
if (dom) {
|
let dom = [...el.querySelectorAll('.calibrationNumber')][0] as HTMLElement;
|
||||||
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(multiple + 0.1).toFixed(
|
if (dom) {
|
||||||
1,
|
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(multiple + 0.1).toFixed(
|
||||||
)})`;
|
1,
|
||||||
}
|
)})`;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [calibrationLength.height, calibrationLength.width, direction, multiple]);
|
}, [calibrationLength.height, calibrationLength.width, direction, multiple]);
|
||||||
|
|||||||
@ -3,16 +3,17 @@ import classnames from 'classnames';
|
|||||||
import Icon from '../Icon';
|
import Icon from '../Icon';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { IconTypes } from '../DynamicEngine/schema';
|
import { IconTypes } from '../DynamicEngine/schema';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
interface CardPickerType {
|
interface CardPickerType {
|
||||||
type?: IconTypes;
|
type: IconTypes;
|
||||||
icons: Array<IconTypes>;
|
icons: Array<IconTypes>;
|
||||||
onChange?: (v: string) => void;
|
onChange?: (v: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo((props: CardPickerType) => {
|
export default memo((props: CardPickerType) => {
|
||||||
const { type, icons, onChange } = props;
|
const { type, icons, onChange } = props;
|
||||||
|
console.log(type);
|
||||||
const [selected, setSelected] = useState<IconTypes>(type);
|
const [selected, setSelected] = useState<IconTypes>(type);
|
||||||
|
|
||||||
const handlePicker = (v: IconTypes) => {
|
const handlePicker = (v: IconTypes) => {
|
||||||
@ -33,7 +34,7 @@ export default memo((props: CardPickerType) => {
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={classnames(styles.picker, selected === item ? styles.selected : '')}
|
className={classnames(styles.picker, selected === item ? styles.selected : '')}
|
||||||
onClick={handlePicker.bind(this, item)}
|
onClick={() => handlePicker(item)}
|
||||||
key={i}
|
key={i}
|
||||||
>
|
>
|
||||||
<Icon type={item} size={20} />
|
<Icon type={item} size={20} />
|
||||||
|
|||||||
@ -9,7 +9,6 @@ interface CarouselTypes extends CarouselConfigType {
|
|||||||
|
|
||||||
const XCarousel = memo((props: PropsWithChildren<CarouselTypes>) => {
|
const XCarousel = memo((props: PropsWithChildren<CarouselTypes>) => {
|
||||||
const { direction, swipeable, autoPlay, isTpl, imgList, tplImg } = props;
|
const { direction, swipeable, autoPlay, isTpl, imgList, tplImg } = props;
|
||||||
console.log(direction);
|
|
||||||
const contentRender = () => {
|
const contentRender = () => {
|
||||||
return imgList.map((item, i) => {
|
return imgList.map((item, i) => {
|
||||||
return (
|
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 { dynamic } from 'umi';
|
||||||
import Loading from '../LoadingCp';
|
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 needList = ['Tab', 'Carousel', 'Upload', 'Video', 'Icon'];
|
||||||
|
|
||||||
const DynamicFunc = type =>
|
const DynamicFunc = (type: AllTemplateType) =>
|
||||||
dynamic({
|
dynamic({
|
||||||
loader: async function() {
|
loader: async function() {
|
||||||
let Component;
|
let Component: FC<{ isTpl: boolean }>;
|
||||||
if (needList.includes(type)) {
|
if (needList.includes(type)) {
|
||||||
const { default: Graph } = await import(`@/components/${type}`);
|
const { default: Graph } = await import(`@/components/${type}`);
|
||||||
Component = Graph;
|
Component = Graph;
|
||||||
} else {
|
} else {
|
||||||
const Components = await import(`@/components/DynamicEngine/components`);
|
const Components = ((await import(`@/components/DynamicEngine/components`)) as unknown) as {
|
||||||
|
[key: string]: FC;
|
||||||
|
};
|
||||||
Component = Components[type];
|
Component = Components[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
return props => {
|
return (props: DynamicType) => {
|
||||||
const { config, isTpl } = props;
|
const { config, isTpl } = props;
|
||||||
return <Component {...config} isTpl={isTpl} />;
|
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 { type, config, isTpl } = props;
|
||||||
const Dynamic = useMemo(() => {
|
const Dynamic = useMemo(() => {
|
||||||
return DynamicFunc(type);
|
return (DynamicFunc(type) as unknown) as FC<DynamicType>;
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [type, config]);
|
}, [type, config]);
|
||||||
return <Dynamic type={type} config={config} isTpl={isTpl} />;
|
return <Dynamic type={type} config={config} isTpl={isTpl} />;
|
||||||
@ -472,6 +472,20 @@ export interface SchemaType extends SchemaImplement {
|
|||||||
XProgress: XProgressSchema;
|
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 = {
|
const schema: SchemaType = {
|
||||||
Carousel: {
|
Carousel: {
|
||||||
editData: [
|
editData: [
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import React, { memo, useState, useEffect } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import { Form, Select, InputNumber, Input, Switch, Radio, Button } from 'antd';
|
import { Form, Select, InputNumber, Input, Switch, Radio, Button } from 'antd';
|
||||||
import Upload from '@/components/Upload';
|
import Upload from '../Upload';
|
||||||
import DataList from '@/components/DataList';
|
import DataList from '../DataList';
|
||||||
import MutiText from '@/components/MutiText';
|
import MutiText from '../MutiText';
|
||||||
import Color from '@/components/Color';
|
import Color from '../Color';
|
||||||
import CardPicker from '@/components/CardPicker';
|
import CardPicker from '../CardPicker';
|
||||||
|
import { Store } from 'antd/lib/form/interface';
|
||||||
|
import { UnionData, BasicRangeType, IconSchema } from '../DynamicEngine/schema';
|
||||||
// import styles from './index.less';
|
// import styles from './index.less';
|
||||||
const normFile = e => {
|
const normFile = (e: any) => {
|
||||||
console.log('Upload event:', e);
|
console.log('Upload event:', e);
|
||||||
if (Array.isArray(e)) {
|
if (Array.isArray(e)) {
|
||||||
|
//待修改
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
return e && e.fileList;
|
return e && e.fileList;
|
||||||
@ -22,10 +24,18 @@ const formItemLayout = {
|
|||||||
wrapperCol: { span: 16 },
|
wrapperCol: { span: 16 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormEditor = props => {
|
interface FormEditorProps {
|
||||||
const { config, defaultValue, onSave, onDel, uid } = props;
|
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);
|
onSave && onSave(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,7 +90,7 @@ const FormEditor = props => {
|
|||||||
{item.type === 'Select' && (
|
{item.type === 'Select' && (
|
||||||
<Form.Item label={item.name} name={item.key}>
|
<Form.Item label={item.name} name={item.key}>
|
||||||
<Select placeholder="请选择">
|
<Select placeholder="请选择">
|
||||||
{item.range.map((v, i) => {
|
{item.range.map((v: BasicRangeType<string>, i: number) => {
|
||||||
return (
|
return (
|
||||||
<Option value={v.key} key={i}>
|
<Option value={v.key} key={i}>
|
||||||
{v.text}
|
{v.text}
|
||||||
@ -93,7 +103,7 @@ const FormEditor = props => {
|
|||||||
{item.type === 'Radio' && (
|
{item.type === 'Radio' && (
|
||||||
<Form.Item label={item.name} name={item.key}>
|
<Form.Item label={item.name} name={item.key}>
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
{item.range.map((v, i) => {
|
{item.range.map((v: BasicRangeType<string>, i: number) => {
|
||||||
return (
|
return (
|
||||||
<Radio value={v.key} key={i}>
|
<Radio value={v.key} key={i}>
|
||||||
{v.text}
|
{v.text}
|
||||||
@ -120,7 +130,10 @@ const FormEditor = props => {
|
|||||||
)}
|
)}
|
||||||
{item.type === 'CardPicker' && (
|
{item.type === 'CardPicker' && (
|
||||||
<Form.Item label={item.name} name={item.key} valuePropName="type">
|
<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>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@ -130,7 +143,7 @@ const FormEditor = props => {
|
|||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="danger" style={{ marginLeft: '20px' }} onClick={handleDel}>
|
<Button danger style={{ marginLeft: '20px' }} onClick={handleDel}>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</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 { PlusOutlined } from '@ant-design/icons';
|
||||||
import ImgCrop from 'antd-img-crop';
|
import ImgCrop from 'antd-img-crop';
|
||||||
import styles from './index.less';
|
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) {
|
function getBase64(file: File | Blob) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
reader.onload = () => resolve(reader.result);
|
reader.onload = () => resolve(reader.result as string);
|
||||||
reader.onerror = error => reject(error);
|
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 = {
|
state = {
|
||||||
previewVisible: false,
|
previewVisible: false,
|
||||||
previewImage: '',
|
previewImage: '',
|
||||||
previewTitle: '',
|
previewTitle: '',
|
||||||
fileList: this.props.fileList || []
|
fileList: this.props.fileList || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCancel = () => this.setState({ previewVisible: false });
|
handleCancel = () => this.setState({ previewVisible: false });
|
||||||
|
|
||||||
handlePreview = async file => {
|
handlePreview = async (file: UploadFile<any>) => {
|
||||||
if (!file.url && !file.preview) {
|
if (!file.url && !file.preview) {
|
||||||
file.preview = await getBase64(file.originFileObj);
|
file.preview = await getBase64(file.originFileObj!);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
previewImage: file.url || file.preview,
|
previewImage: file.url || file.preview,
|
||||||
previewVisible: true,
|
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 }) => {
|
handleChange = ({ file, fileList }: UploadChangeParam<UploadFile<any>>) => {
|
||||||
this.setState({ fileList })
|
this.setState({ fileList });
|
||||||
if(file.status === 'done') {
|
if (file.status === 'done') {
|
||||||
const files = fileList.map(item => {
|
const files = fileList.map(item => {
|
||||||
const { uid, name, status } = item
|
const { uid, name, status } = item;
|
||||||
const url = item.url || item.response.result.url
|
const url = item.url || item.response.result.url;
|
||||||
return { uid, name, status, url }
|
return { uid, name, status, url };
|
||||||
})
|
});
|
||||||
this.props.onChange && this.props.onChange(files)
|
this.props.onChange && this.props.onChange(files);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
handleBeforeUpload = (file) => {
|
handleBeforeUpload = (file: RcFile) => {
|
||||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif';
|
const isJpgOrPng =
|
||||||
|
file.type === 'image/jpeg' ||
|
||||||
|
file.type === 'image/png' ||
|
||||||
|
file.type === 'image/jpg' ||
|
||||||
|
file.type === 'image/gif';
|
||||||
if (!isJpgOrPng) {
|
if (!isJpgOrPng) {
|
||||||
message.error('只能上传格式为jpeg/png/gif的图片');
|
message.error('只能上传格式为jpeg/png/gif的图片');
|
||||||
}
|
}
|
||||||
@ -59,7 +74,7 @@ class PicturesWall extends React.Component {
|
|||||||
message.error('图片必须小于2MB!');
|
message.error('图片必须小于2MB!');
|
||||||
}
|
}
|
||||||
return isJpgOrPng && isLt2M;
|
return isJpgOrPng && isLt2M;
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { previewVisible, previewImage, fileList, previewTitle } = this.state;
|
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',
|
action = isDev ? 'http://192.168.1.6:3000/api/xxx' : 'http://xxxx',
|
||||||
headers,
|
headers,
|
||||||
withCredentials = true,
|
withCredentials = true,
|
||||||
maxLen = 1
|
maxLen = 1,
|
||||||
} = this.props
|
} = this.props;
|
||||||
|
|
||||||
const uploadButton = (
|
const uploadButton = (
|
||||||
<div>
|
<div>
|
||||||
@ -79,7 +94,13 @@ class PicturesWall extends React.Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImgCrop modalTitle="裁剪图片" modalOk="确定" modalCancel="取消" rotate={true} aspect={375/158}>
|
<ImgCrop
|
||||||
|
modalTitle="裁剪图片"
|
||||||
|
modalOk="确定"
|
||||||
|
modalCancel="取消"
|
||||||
|
rotate={true}
|
||||||
|
aspect={375 / 158}
|
||||||
|
>
|
||||||
<Upload
|
<Upload
|
||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
onPreview={this.handlePreview}
|
onPreview={this.handlePreview}
|
||||||
@ -91,8 +112,8 @@ class PicturesWall extends React.Component {
|
|||||||
withCredentials={withCredentials}
|
withCredentials={withCredentials}
|
||||||
headers={{
|
headers={{
|
||||||
'x-requested-with': localStorage.getItem('user') || '',
|
'x-requested-with': localStorage.getItem('user') || '',
|
||||||
'authorization': localStorage.getItem('token') || '',
|
authorization: localStorage.getItem('token') || '',
|
||||||
...headers
|
...headers,
|
||||||
}}
|
}}
|
||||||
beforeUpload={this.handleBeforeUpload}
|
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 { Button, Popover } from 'antd';
|
||||||
import styles from './index.less';
|
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 [scaleNum, setScale] = useState(1);
|
||||||
|
|
||||||
const { pointData, curPoint, dispatch } = props;
|
const { pointData, curPoint, dispatch } = props;
|
||||||
|
|
||||||
// 指定画布的id
|
// 指定画布的id
|
||||||
let canvasId = 'js_canvas';
|
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 http from '@/utils/req';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
import styles from './index.less';
|
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 = {
|
const layout = {
|
||||||
labelCol: { span: 6 },
|
labelCol: { span: 6 },
|
||||||
wrapperCol: { span: 16 },
|
wrapperCol: { span: 16 },
|
||||||
@ -11,16 +13,18 @@ const tailLayout = {
|
|||||||
wrapperCol: { offset: 6, span: 16 },
|
wrapperCol: { offset: 6, span: 16 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const Login = (props) => {
|
const Login = (props: RouteComponentProps) => {
|
||||||
const onFinish = values => {
|
const onFinish = (values: Store) => {
|
||||||
http.post('/login', {...values}).then(res => {
|
http
|
||||||
localStorage.setItem('token', res.token)
|
.post<Store, { token: string }>('/login', { ...values })
|
||||||
localStorage.setItem('user', values.username)
|
.then(res => {
|
||||||
history.push('/')
|
localStorage.setItem('token', res.token);
|
||||||
})
|
localStorage.setItem('user', values.username);
|
||||||
|
history.push('/');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFinishFailed = errorInfo => {
|
const onFinishFailed = (errorInfo: ValidateErrorEntity) => {
|
||||||
console.log('Failed:', errorInfo);
|
console.log('Failed:', errorInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -34,7 +38,10 @@ const Login = (props) => {
|
|||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
onFinishFailed={onFinishFailed}
|
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
|
<Form.Item
|
||||||
label="用户名"
|
label="用户名"
|
||||||
name="username"
|
name="username"
|
||||||
@ -57,14 +64,13 @@ const Login = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item {...tailLayout}>
|
<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>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Login
|
export default Login;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Result } from 'antd';
|
import { Result } from 'antd';
|
||||||
|
|
||||||
function MobileTip(props) {
|
function MobileTip() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Result
|
<Result
|
||||||
|
|||||||
3
src/typings.d.ts
vendored
3
src/typings.d.ts
vendored
@ -1,3 +1,6 @@
|
|||||||
declare module '*.css';
|
declare module '*.css';
|
||||||
declare module '*.png';
|
declare module '*.png';
|
||||||
declare module '*.less';
|
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:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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@*":
|
"@types/color-convert@*":
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.npm.taobao.org/@types/color-convert/download/@types/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d"
|
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"
|
resolved "https://registry.npm.taobao.org/@types/range-parser/download/@types/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||||
integrity sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=
|
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":
|
"@types/react-dom@^16.9.8":
|
||||||
version "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"
|
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" "*"
|
"@types/prop-types" "*"
|
||||||
csstype "^3.0.2"
|
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":
|
"@types/resolve@1.17.1":
|
||||||
version "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"
|
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