mirror of
https://github.com/MrXujiang/h5-Dooring.git
synced 2026-04-04 19:50:11 +00:00
fix
This commit is contained in:
parent
fb1964ecdc
commit
8f64938639
@ -1,12 +0,0 @@
|
|||||||
.calibration {
|
|
||||||
width: calc(200% - 50px);
|
|
||||||
height: 200%;
|
|
||||||
position: relative;
|
|
||||||
white-space: nowrap;
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
|
||||||
:global(.calibrationNumber) {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
||||||
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
export interface calibrationTypes {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
export type CalibrationTypes = {
|
|
||||||
direction: "up" | "left" | "right";
|
|
||||||
multiple: number;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Calibration(props: CalibrationTypes) {
|
|
||||||
const { direction, multiple } = props;
|
|
||||||
const [calibrationLength, setCalibration] = useState<calibrationTypes>({
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
});
|
|
||||||
const calibrationRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const generateElement = useCallback(
|
|
||||||
(item?: boolean, num?: number) => {
|
|
||||||
if (calibrationRef.current) {
|
|
||||||
let createSpan = document.createElement("div");
|
|
||||||
createSpan.className = "calibrationLine";
|
|
||||||
createSpan.style.backgroundColor = "#ccc";
|
|
||||||
calibrationRef.current.style.display = "flex";
|
|
||||||
calibrationRef.current.style.justifyContent = "space-between";
|
|
||||||
if (direction === "up") {
|
|
||||||
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") {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[direction]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (calibrationRef.current) {
|
|
||||||
let calibration = calibrationRef.current.getBoundingClientRect();
|
|
||||||
setCalibration({ width: calibration.width, height: calibration.height });
|
|
||||||
let length = direction === "up" ? calibration.width : calibration.height;
|
|
||||||
for (let i = 0; i < length / 5; i++) {
|
|
||||||
if (i % 10 === 0) {
|
|
||||||
generateElement(true, i);
|
|
||||||
} else {
|
|
||||||
generateElement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [direction, generateElement]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (calibrationRef.current) {
|
|
||||||
let width = calibrationLength.width
|
|
||||||
? calibrationLength.width
|
|
||||||
: calibrationRef.current.getBoundingClientRect().width;
|
|
||||||
let height = calibrationLength.height
|
|
||||||
? calibrationLength.height
|
|
||||||
: calibrationRef.current.getBoundingClientRect().height;
|
|
||||||
let arr = [
|
|
||||||
...Array.from(
|
|
||||||
calibrationRef.current.querySelectorAll(".calibrationLine")
|
|
||||||
)
|
|
||||||
];
|
|
||||||
if (arr.length) {
|
|
||||||
if (direction === "up") {
|
|
||||||
calibrationRef.current.style.width =
|
|
||||||
parseFloat(multiple.toFixed(1)) * width + "px";
|
|
||||||
arr.forEach(el => {
|
|
||||||
let dom = [
|
|
||||||
...Array.from(el.querySelectorAll(".calibrationNumber"))
|
|
||||||
][0] as HTMLElement;
|
|
||||||
if (dom) {
|
|
||||||
dom.style.transform = `translate3d(-4px, 16px, 0px) scale(${(
|
|
||||||
multiple + 0.1
|
|
||||||
).toFixed(1)})`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
calibrationRef.current.style.height =
|
|
||||||
parseFloat(multiple.toFixed(1)) * height + "px";
|
|
||||||
arr.forEach(el => {
|
|
||||||
let dom = [
|
|
||||||
...Array.from(el.querySelectorAll(".calibrationNumber"))
|
|
||||||
][0] as HTMLElement;
|
|
||||||
if (dom) {
|
|
||||||
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(
|
|
||||||
multiple + 0.1
|
|
||||||
).toFixed(1)})`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [calibrationLength.height, calibrationLength.width, direction, multiple]);
|
|
||||||
|
|
||||||
return <div className={styles.calibration} ref={calibrationRef}></div>;
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.calibration {
|
|
||||||
width: calc(200% - 50px);
|
|
||||||
height: 200%;
|
|
||||||
position: relative;
|
|
||||||
white-space: nowrap;
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
|
||||||
:global(.calibrationNumber) {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
||||||
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
export interface calibrationTypes {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
export type CalibrationTypes = {
|
|
||||||
direction: "up" | "left" | "right";
|
|
||||||
multiple: number;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Calibration(props: CalibrationTypes) {
|
|
||||||
const { direction, multiple } = props;
|
|
||||||
const [calibrationLength, setCalibration] = useState<calibrationTypes>({
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
});
|
|
||||||
const calibrationRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const generateElement = useCallback(
|
|
||||||
(item?: boolean, num?: number) => {
|
|
||||||
if (calibrationRef.current) {
|
|
||||||
let createSpan = document.createElement("div");
|
|
||||||
createSpan.className = "calibrationLine";
|
|
||||||
createSpan.style.backgroundColor = "#ccc";
|
|
||||||
calibrationRef.current.style.display = "flex";
|
|
||||||
calibrationRef.current.style.justifyContent = "space-between";
|
|
||||||
if (direction === "up") {
|
|
||||||
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") {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[direction]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (calibrationRef.current) {
|
|
||||||
let calibration = calibrationRef.current.getBoundingClientRect();
|
|
||||||
setCalibration({ width: calibration.width, height: calibration.height });
|
|
||||||
let length = direction === "up" ? calibration.width : calibration.height;
|
|
||||||
for (let i = 0; i < length / 5; i++) {
|
|
||||||
if (i % 10 === 0) {
|
|
||||||
generateElement(true, i);
|
|
||||||
} else {
|
|
||||||
generateElement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [direction, generateElement]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (calibrationRef.current) {
|
|
||||||
let width = calibrationLength.width
|
|
||||||
? calibrationLength.width
|
|
||||||
: calibrationRef.current.getBoundingClientRect().width;
|
|
||||||
let height = calibrationLength.height
|
|
||||||
? calibrationLength.height
|
|
||||||
: calibrationRef.current.getBoundingClientRect().height;
|
|
||||||
let arr = [
|
|
||||||
...Array.from(
|
|
||||||
calibrationRef.current.querySelectorAll(".calibrationLine")
|
|
||||||
)
|
|
||||||
];
|
|
||||||
if (arr.length) {
|
|
||||||
if (direction === "up") {
|
|
||||||
calibrationRef.current.style.width =
|
|
||||||
parseFloat(multiple.toFixed(1)) * width + "px";
|
|
||||||
arr.forEach(el => {
|
|
||||||
let dom = [
|
|
||||||
...Array.from(el.querySelectorAll(".calibrationNumber"))
|
|
||||||
][0] as HTMLElement;
|
|
||||||
if (dom) {
|
|
||||||
dom.style.transform = `translate3d(-4px, 16px, 0px) scale(${(
|
|
||||||
multiple + 0.1
|
|
||||||
).toFixed(1)})`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
calibrationRef.current.style.height =
|
|
||||||
parseFloat(multiple.toFixed(1)) * height + "px";
|
|
||||||
arr.forEach(el => {
|
|
||||||
let dom = [
|
|
||||||
...Array.from(el.querySelectorAll(".calibrationNumber"))
|
|
||||||
][0] as HTMLElement;
|
|
||||||
if (dom) {
|
|
||||||
dom.style.transform = `translate3d(-4px, -8px, 0px) scale(${(
|
|
||||||
multiple + 0.1
|
|
||||||
).toFixed(1)})`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [calibrationLength.height, calibrationLength.width, direction, multiple]);
|
|
||||||
|
|
||||||
return <div className={styles.calibration} ref={calibrationRef}></div>;
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import React, { ErrorInfo, PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
interface ErrorBoundaryState {
|
|
||||||
hasError: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component<
|
|
||||||
PropsWithChildren<{}>,
|
|
||||||
ErrorBoundaryState
|
|
||||||
> {
|
|
||||||
constructor(props: PropsWithChildren<{}>) {
|
|
||||||
super(props);
|
|
||||||
this.state = { hasError: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(_error: Error, _info: ErrorInfo) {
|
|
||||||
// Display fallback UI
|
|
||||||
this.setState({ hasError: true });
|
|
||||||
// You can also log the error to an error reporting service
|
|
||||||
//logErrorToMyService(error, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) {
|
|
||||||
// You can render any custom fallback UI
|
|
||||||
return <h1>Something went wrong.</h1>;
|
|
||||||
}
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorBoundary;
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import React, { ErrorInfo, PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
interface ErrorBoundaryState {
|
|
||||||
hasError: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component<
|
|
||||||
PropsWithChildren<{}>,
|
|
||||||
ErrorBoundaryState
|
|
||||||
> {
|
|
||||||
constructor(props: PropsWithChildren<{}>) {
|
|
||||||
super(props);
|
|
||||||
this.state = { hasError: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(_error: Error, _info: ErrorInfo) {
|
|
||||||
// Display fallback UI
|
|
||||||
this.setState({ hasError: true });
|
|
||||||
// You can also log the error to an error reporting service
|
|
||||||
//logErrorToMyService(error, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) {
|
|
||||||
// You can render any custom fallback UI
|
|
||||||
return <h1>Something went wrong.</h1>;
|
|
||||||
}
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorBoundary;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
.pickerWrap {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
.picker {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
border-color: #4091f7;
|
|
||||||
}
|
|
||||||
&.selected {
|
|
||||||
border-color: #4091f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { useState, useEffect, memo } from "react";
|
|
||||||
import classnames from "classnames";
|
|
||||||
import Icon from "@/materials/base/Icon";
|
|
||||||
import styles from "./index.less";
|
|
||||||
import React from "react";
|
|
||||||
import { IconTypes } from "@/materials/base/Icon/schema";
|
|
||||||
import { ICardPickerConfigType } from "../types";
|
|
||||||
|
|
||||||
interface CardPickerType
|
|
||||||
extends Omit<ICardPickerConfigType<IconTypes>, "type" | "key" | "name"> {
|
|
||||||
onChange?: (v: string) => void;
|
|
||||||
type: IconTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo((props: CardPickerType) => {
|
|
||||||
const { type, icons, onChange } = props;
|
|
||||||
const [selected, setSelected] = useState<IconTypes>(type);
|
|
||||||
|
|
||||||
const handlePicker = (v: IconTypes) => {
|
|
||||||
if (onChange) {
|
|
||||||
onChange(v);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSelected(v);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelected(type);
|
|
||||||
}, [type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.pickerWrap}>
|
|
||||||
{icons.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={classnames(
|
|
||||||
styles.picker,
|
|
||||||
selected === item ? styles.selected : ""
|
|
||||||
)}
|
|
||||||
onClick={() => handlePicker(item)}
|
|
||||||
key={i}
|
|
||||||
>
|
|
||||||
<Icon type={item} size={20} color={"#4091f7"} spin={false} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { SketchPicker, ColorResult } from "react-color";
|
|
||||||
import { rgba2Obj } from "@/utils/tool";
|
|
||||||
|
|
||||||
export type ColorConfigType = string;
|
|
||||||
|
|
||||||
//value 初始值传来,onchange item给的回调
|
|
||||||
interface ColorProps {
|
|
||||||
value?: ColorConfigType;
|
|
||||||
onChange?: (v: ColorConfigType) => 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: "20px",
|
|
||||||
height: "20px",
|
|
||||||
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,127 +0,0 @@
|
|||||||
import React, { memo, useEffect, FC } from "react";
|
|
||||||
import { Form, Select, Input, Modal, Button } from "antd";
|
|
||||||
import Upload from "../Upload";
|
|
||||||
import { Store } from "antd/lib/form/interface";
|
|
||||||
import { TDataListDefaultTypeItem } from "../FormEditor/types";
|
|
||||||
// import styles from './index.less';
|
|
||||||
const normFile = (e: any) => {
|
|
||||||
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?: TDataListDefaultTypeItem;
|
|
||||||
onSave: Function;
|
|
||||||
cropRate: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditorModal: FC<EditorModalProps> = props => {
|
|
||||||
const { item, onSave, visible, onCancel, cropRate } = props;
|
|
||||||
const onFinish = (values: Store) => {
|
|
||||||
console.log(values);
|
|
||||||
onSave && onSave(values);
|
|
||||||
};
|
|
||||||
const handleOk = () => {
|
|
||||||
form
|
|
||||||
.validateFields()
|
|
||||||
.then(values => {
|
|
||||||
if (item) {
|
|
||||||
values.id = item.id;
|
|
||||||
onSave && onSave(values);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (form && item && visible) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
}, [form, item, visible]);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!!item && (
|
|
||||||
<Modal
|
|
||||||
title="编辑数据源"
|
|
||||||
closable={false}
|
|
||||||
visible={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
okText="确定"
|
|
||||||
forceRender
|
|
||||||
footer={
|
|
||||||
<Button type={"primary"} onClick={() => handleOk()}>
|
|
||||||
确定
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<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 cropRate={cropRate} isCrop />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(EditorModal);
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
.dataList {
|
|
||||||
padding: 6px 10px;
|
|
||||||
border: 1px solid #f0f0f0;
|
|
||||||
text-align: justify;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
.listItem {
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
&:hover {
|
|
||||||
.actionBar {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.tit {
|
|
||||||
font-weight: bold;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
.actionBar {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: none;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: -20px 0 10px 10px #fff;
|
|
||||||
.action {
|
|
||||||
margin-right: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
&:last-child {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,263 +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,
|
|
||||||
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 { TDataListDefaultType, TDataListDefaultTypeItem } from "../types";
|
|
||||||
|
|
||||||
type ListItemProps = DndItemProps & {
|
|
||||||
isDragging: boolean;
|
|
||||||
connectDragSource: ConnectDragSource;
|
|
||||||
connectDragPreview: ConnectDragPreview;
|
|
||||||
connectDropTarget: ConnectDropTarget;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ListItem(props: ListItemProps) {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
desc,
|
|
||||||
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 = TDataListDefaultTypeItem & {
|
|
||||||
onDel: Function;
|
|
||||||
onEdit: Function;
|
|
||||||
key: number;
|
|
||||||
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: TDataListDefaultType) => void;
|
|
||||||
value?: TDataListDefaultType;
|
|
||||||
cropRate: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DataListType = DataListMemo & {
|
|
||||||
connectDropTarget: ConnectDropTarget;
|
|
||||||
};
|
|
||||||
|
|
||||||
const List = function(props: DataListType) {
|
|
||||||
const { onChange, value, connectDropTarget, cropRate } = props;
|
|
||||||
const [list, setList] = useState(value);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [curItem, setCurItem] = useState<TDataListDefaultTypeItem>();
|
|
||||||
|
|
||||||
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(() => {
|
|
||||||
console.log("a");
|
|
||||||
setVisible(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEdit = useCallback((item: TDataListDefaultTypeItem) => {
|
|
||||||
console.log("b");
|
|
||||||
setVisible(true);
|
|
||||||
setCurItem(item);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSave = useCallback(
|
|
||||||
(item: TDataListDefaultTypeItem) => {
|
|
||||||
console.log("c");
|
|
||||||
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)));
|
|
||||||
},
|
|
||||||
[list, onChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
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 onClick={handleAdd} block>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<EditorModal
|
|
||||||
visible={visible}
|
|
||||||
onCancel={handleCancel}
|
|
||||||
item={curItem}
|
|
||||||
onSave={handleSave}
|
|
||||||
cropRate={cropRate}
|
|
||||||
/>
|
|
||||||
</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 React, { FC, memo, useEffect } from "react";
|
|
||||||
import { Form, Select, Input, Modal, Button, InputNumber } from "antd";
|
|
||||||
import { baseFormOptionsType } from "../types";
|
|
||||||
import Color from "../Color";
|
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const formItemLayout = {
|
|
||||||
labelCol: { span: 6 },
|
|
||||||
wrapperCol: { span: 14 }
|
|
||||||
};
|
|
||||||
|
|
||||||
interface EditorModalProps {
|
|
||||||
item: any;
|
|
||||||
onSave: (data: any) => void;
|
|
||||||
visible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditorModal: FC<EditorModalProps> = props => {
|
|
||||||
const { item, onSave, visible } = props;
|
|
||||||
|
|
||||||
const onFinish = (values: any) => {
|
|
||||||
onSave && onSave(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOk = () => {
|
|
||||||
form
|
|
||||||
.validateFields()
|
|
||||||
.then(values => {
|
|
||||||
values.id = item.id;
|
|
||||||
onSave && onSave(values);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (form && item && visible) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
}, [form, item, visible]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!!item && (
|
|
||||||
<Modal
|
|
||||||
title="编辑表单组件"
|
|
||||||
footer={
|
|
||||||
<div>
|
|
||||||
<Button type="primary" onClick={() => handleOk()}>
|
|
||||||
确定
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
forceRender
|
|
||||||
visible={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
closable={false}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
name={`formItem_editor_modal`}
|
|
||||||
{...formItemLayout}
|
|
||||||
onFinish={onFinish}
|
|
||||||
initialValues={item}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
<Form.Item label="类型" name="type" hidden>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
}
|
|
||||||
{!!item.label && (
|
|
||||||
<Form.Item
|
|
||||||
label="字段名"
|
|
||||||
name="label"
|
|
||||||
rules={[{ required: true, message: "请输入字段名!" }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.fontSize && (
|
|
||||||
<Form.Item
|
|
||||||
label="字体大小"
|
|
||||||
name="fontSize"
|
|
||||||
rules={[{ required: true, message: "请输入字体大小!" }]}
|
|
||||||
>
|
|
||||||
<InputNumber min={12} max={30} defaultValue={14} />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.color && (
|
|
||||||
<Form.Item
|
|
||||||
label="文字颜色"
|
|
||||||
name="color"
|
|
||||||
rules={[{ required: true, message: "请输入文字颜色!" }]}
|
|
||||||
>
|
|
||||||
<Color />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.placeholder && (
|
|
||||||
<Form.Item label="提示文本" name="placeholder">
|
|
||||||
<Input placeholder="请输入提示文本" />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.options && (
|
|
||||||
<Form.Item
|
|
||||||
label="选项源"
|
|
||||||
name="options"
|
|
||||||
rules={[{ required: true, message: "选项不能为空!" }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder="请输入"
|
|
||||||
mode="tags"
|
|
||||||
labelInValue
|
|
||||||
maxTagCount={39}
|
|
||||||
maxTagTextLength={16}
|
|
||||||
>
|
|
||||||
{item.options.map((v: baseFormOptionsType, i: number) => {
|
|
||||||
return (
|
|
||||||
<Option value={v.value} key={i}>
|
|
||||||
{v.label}
|
|
||||||
</Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(EditorModal);
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
import React, {
|
|
||||||
memo,
|
|
||||||
RefObject,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState
|
|
||||||
} from "react";
|
|
||||||
import BaseForm from "@/materials/base/Form/BaseForm";
|
|
||||||
import BasePopoverForm from "@/materials/base/Form/BasePopoverForm";
|
|
||||||
import EditorModal from "./EditorModal";
|
|
||||||
import { MinusCircleFilled, EditFilled, PlusOutlined } from "@ant-design/icons";
|
|
||||||
import styles from "./formItems.less";
|
|
||||||
import { baseFormUnion, TFormItemsDefaultType } from "../types";
|
|
||||||
import { uuid } from "@/utils/tool";
|
|
||||||
import { Button } from "antd";
|
|
||||||
import MyPopover from "yh-react-popover";
|
|
||||||
// import { Popconfirm } from 'antd';
|
|
||||||
|
|
||||||
const formTpl: TFormItemsDefaultType = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
type: "Text",
|
|
||||||
label: "文本框",
|
|
||||||
placeholder: "请输入文本"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
type: "Textarea",
|
|
||||||
label: "长文本框",
|
|
||||||
placeholder: "请输入长文本请输入长文本"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
type: "Number",
|
|
||||||
label: "数值",
|
|
||||||
placeholder: " 请输入数值"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
type: "MyRadio",
|
|
||||||
label: "单选框",
|
|
||||||
options: [
|
|
||||||
{ label: "选项一", value: "1" },
|
|
||||||
{ label: "选项二", value: "2" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
type: "MySelect",
|
|
||||||
label: "下拉选择框",
|
|
||||||
options: [
|
|
||||||
{ label: "选项一", value: "1" },
|
|
||||||
{ label: "选项二", value: "2" },
|
|
||||||
{ label: "选项三", value: "3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
type: "Date",
|
|
||||||
label: "日期框",
|
|
||||||
placeholder: ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "7",
|
|
||||||
type: "MyTextTip",
|
|
||||||
label: "纯文本",
|
|
||||||
fontSize: 12,
|
|
||||||
color: "rgba(0,0,0,1)"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
interface FormItemsProps {
|
|
||||||
formList?: TFormItemsDefaultType;
|
|
||||||
onChange?: (v: TFormItemsDefaultType) => void;
|
|
||||||
data: any;
|
|
||||||
rightPannelRef: RefObject<HTMLDivElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormItems = (props: FormItemsProps) => {
|
|
||||||
const { formList, onChange, rightPannelRef } = props;
|
|
||||||
const [formData, setFormData] = useState<TFormItemsDefaultType>(
|
|
||||||
formList || []
|
|
||||||
);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [curItem, setCurItem] = useState<baseFormUnion>();
|
|
||||||
const [force, setforce] = useState<{ force: Function }>({
|
|
||||||
force: () => {}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAddItem = (item: baseFormUnion) => {
|
|
||||||
let tpl = formTpl.find(v => v.type === item.type);
|
|
||||||
let newData = [...formData, { ...tpl!, id: uuid(6, 10) }];
|
|
||||||
setFormData(newData);
|
|
||||||
onChange && onChange(newData);
|
|
||||||
force.force();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditItem = (item: baseFormUnion) => {
|
|
||||||
setVisible(true);
|
|
||||||
setCurItem(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelItem = (item: baseFormUnion) => {
|
|
||||||
let newData = formData.filter(v => v.id !== item.id);
|
|
||||||
setFormData(newData);
|
|
||||||
onChange && onChange(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveItem = (data: baseFormUnion) => {
|
|
||||||
let newData = formData.map(v => (v.id === data.id ? data : v));
|
|
||||||
setFormData(newData);
|
|
||||||
onChange && onChange(newData);
|
|
||||||
setVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const callback = useCallback((v: Function) => {
|
|
||||||
console.log(v);
|
|
||||||
setforce({ force: v });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let listenner: (e: Event) => void;
|
|
||||||
if (rightPannelRef.current) {
|
|
||||||
listenner = () => {
|
|
||||||
force.force();
|
|
||||||
};
|
|
||||||
rightPannelRef.current.addEventListener("scroll", listenner);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (rightPannelRef.current) {
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
rightPannelRef.current.removeEventListener("scroll", listenner);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [force, rightPannelRef]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.formItemWrap}>
|
|
||||||
<div className={styles.formTitle}>表单控件</div>
|
|
||||||
<div className={styles.editForm}>
|
|
||||||
{formData.map((item: baseFormUnion, i: number) => {
|
|
||||||
let FormItem = BaseForm[item.type];
|
|
||||||
return (
|
|
||||||
<div className={styles.formItem} key={i}>
|
|
||||||
<div className={styles.disClick}>
|
|
||||||
<FormItem {...item} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.deleteWrap}>
|
|
||||||
<span
|
|
||||||
className={styles.operationBtn}
|
|
||||||
onClick={() => handleDelItem(item)}
|
|
||||||
>
|
|
||||||
<MinusCircleFilled />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.editWrap}>
|
|
||||||
<span
|
|
||||||
className={styles.operationBtn}
|
|
||||||
onClick={() => handleEditItem(item)}
|
|
||||||
>
|
|
||||||
<EditFilled />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className={styles.formAddWrap}>
|
|
||||||
<MyPopover
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<div className={styles.formTpl} style={{ color: "red" }}>
|
|
||||||
{formTpl.map((item, i) => {
|
|
||||||
let FormItem = BasePopoverForm[item.type];
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.formItem}
|
|
||||||
key={i}
|
|
||||||
onClick={() => handleAddItem(item)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={styles.disClick}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
overflow: "row",
|
|
||||||
marginTop: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormItem {...item} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{/* <a style={{color: 'red'}} onClick={() => setFormTplVisible(false)}>Close</a> */}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
directions={"LB"}
|
|
||||||
innerConstDomStyle={{ display: "block" }}
|
|
||||||
constDomStyle={{ display: "block" }}
|
|
||||||
callback={callback}
|
|
||||||
>
|
|
||||||
<Button style={{ width: "100%" }} block icon={<PlusOutlined />}>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</MyPopover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<EditorModal item={curItem} onSave={handleSaveItem} visible={visible} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(FormItems);
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
.formItemWrap {
|
|
||||||
.formTitle {
|
|
||||||
width: 56px;
|
|
||||||
height: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: PingFangSC-Medium, PingFang SC;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #000000;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
.editForm {
|
|
||||||
text-align: left;
|
|
||||||
width: 251px;
|
|
||||||
.formItem {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 2px;
|
|
||||||
.common {
|
|
||||||
position: absolute;
|
|
||||||
top: 19px;
|
|
||||||
box-shadow: 0 0 20px #fff;
|
|
||||||
.operationBtn {
|
|
||||||
margin-right: 15px;
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.deleteWrap {
|
|
||||||
.common;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.editWrap {
|
|
||||||
.common;
|
|
||||||
right: -18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.formAddWrap {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #4a4a4a;
|
|
||||||
line-height: 20px;
|
|
||||||
background-color: #2f54eb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.formAddWrap {
|
|
||||||
.formTpl {
|
|
||||||
margin-top: 12px;
|
|
||||||
border-top: 1px dashed #ccc;
|
|
||||||
padding-top: 16px;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
.formItem {
|
|
||||||
button,
|
|
||||||
[type="button"] {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 4px 0px 0px 0px;
|
|
||||||
}
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
cursor: pointer;
|
|
||||||
.disClick {
|
|
||||||
pointer-events: none;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
border-color: #2f54eb;
|
|
||||||
.addBtn {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.addBtn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: none;
|
|
||||||
padding: 3px 6px;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #2f54eb;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import FormItems from "./FormItems";
|
|
||||||
export default FormItems;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.mutiText {
|
|
||||||
.iptWrap {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
display: flex;
|
|
||||||
.delBtn {
|
|
||||||
// font-size: 14px;
|
|
||||||
margin-left: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import React, { memo, useEffect } from "react";
|
|
||||||
import { Input, Button, Popconfirm } from "antd";
|
|
||||||
import { MinusCircleFilled } from "@ant-design/icons";
|
|
||||||
import styles from "./index.less";
|
|
||||||
import { TMutiTextDefaultType } from "../types";
|
|
||||||
|
|
||||||
type MultiTextProps = {
|
|
||||||
onChange?: (v: TMutiTextDefaultType) => void;
|
|
||||||
value?: TMutiTextDefaultType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(function MutiText(props: MultiTextProps) {
|
|
||||||
const { value, onChange } = props;
|
|
||||||
const handleAdd = () => {
|
|
||||||
onChange && onChange([...value!, "新增项目"]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDel = (index: number) => {
|
|
||||||
let newList = value!.filter((_item, i) => i !== index);
|
|
||||||
onChange && onChange(newList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (
|
|
||||||
index: number,
|
|
||||||
e: React.ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
let newList = value!.map((item, i) =>
|
|
||||||
i === index ? e.target.value : item
|
|
||||||
);
|
|
||||||
onChange && onChange(newList);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
window["currentCates"] = value!;
|
|
||||||
return () => {
|
|
||||||
window["currentCates"] = null;
|
|
||||||
};
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.mutiText}>
|
|
||||||
{value && value.length ? (
|
|
||||||
value!.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}>
|
|
||||||
<MinusCircleFilled />
|
|
||||||
</span>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className={styles.iptWrap}>
|
|
||||||
<Input />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{value && value.length < 3 && (
|
|
||||||
<div className={styles.iptWrap}>
|
|
||||||
<Button block onClick={handleAdd}>
|
|
||||||
添加项目
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.posIpt {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-right: -10px;
|
|
||||||
.posItem {
|
|
||||||
margin-right: 10px;
|
|
||||||
span {
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import React, { memo, useState, useEffect } from "react";
|
|
||||||
import { InputNumber } from "antd";
|
|
||||||
import styles from "./index.less";
|
|
||||||
import { TPosDefaultType, TPosItem } from "../types";
|
|
||||||
|
|
||||||
type PosProps = {
|
|
||||||
value?: TPosDefaultType;
|
|
||||||
onChange?: (v: TPosItem | string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(function Pos(props: PosProps) {
|
|
||||||
const { value, onChange } = props;
|
|
||||||
let _this: typeof Pos = Pos;
|
|
||||||
|
|
||||||
const handleChange = (index: number, v: TPosItem | string) => {
|
|
||||||
let arr: any = value || [];
|
|
||||||
arr[index] = v;
|
|
||||||
onChange && onChange(arr);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.posIpt}>
|
|
||||||
<div className={styles.posItem}>
|
|
||||||
<span>x: </span>
|
|
||||||
<InputNumber
|
|
||||||
defaultValue={value && value[0]}
|
|
||||||
onChange={handleChange.bind(_this, 0)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.posItem}>
|
|
||||||
<span>y: </span>
|
|
||||||
<InputNumber
|
|
||||||
defaultValue={value && value[1]}
|
|
||||||
onChange={handleChange.bind(_this, 1)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
:global(.editable-cell) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
padding: 5px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.editable-row) {
|
|
||||||
&:hover :global(.editable-cell-value-wrap) {
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 11px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global([data-theme="dark"]) {
|
|
||||||
:global(.editable-row) {
|
|
||||||
&:hover {
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
border: 1px solid #434343;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.apiForm {
|
|
||||||
.formItem {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,449 +0,0 @@
|
|||||||
import React, {
|
|
||||||
useContext,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
memo,
|
|
||||||
RefObject
|
|
||||||
} from "react";
|
|
||||||
import { Table, Input, Button, Popconfirm, Form, Modal, Upload } from "antd";
|
|
||||||
import { ColumnsType } from "antd/lib/table";
|
|
||||||
import { uuid } from "@/utils/tool";
|
|
||||||
import XLSX from "xlsx";
|
|
||||||
// 下方样式主要为全局样式,暂时不可删
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
const EditableContext = React.createContext<any>(null);
|
|
||||||
|
|
||||||
interface Item {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
age: string;
|
|
||||||
address: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditableRowProps {
|
|
||||||
index: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
return (
|
|
||||||
<Form form={form} component={false}>
|
|
||||||
<EditableContext.Provider value={form}>
|
|
||||||
<tr {...props} />
|
|
||||||
</EditableContext.Provider>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface EditableCellProps {
|
|
||||||
title: React.ReactNode;
|
|
||||||
editable: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
dataIndex: string;
|
|
||||||
record: any;
|
|
||||||
handleSave: (record: Item) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditableCell: React.FC<EditableCellProps> = ({
|
|
||||||
title,
|
|
||||||
editable,
|
|
||||||
children,
|
|
||||||
dataIndex,
|
|
||||||
record,
|
|
||||||
handleSave,
|
|
||||||
...restProps
|
|
||||||
}) => {
|
|
||||||
const [editing, setEditing] = useState(false);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const form = useContext(EditableContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (editing) {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}
|
|
||||||
}, [editing]);
|
|
||||||
|
|
||||||
const toggleEdit = () => {
|
|
||||||
setEditing(!editing);
|
|
||||||
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
|
|
||||||
};
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
toggleEdit();
|
|
||||||
handleSave({ ...record, ...values });
|
|
||||||
} catch (errInfo) {
|
|
||||||
console.log("Save failed:", errInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let childNode = children;
|
|
||||||
|
|
||||||
if (editable) {
|
|
||||||
childNode = editing ? (
|
|
||||||
<Form.Item
|
|
||||||
style={{ margin: 0 }}
|
|
||||||
name={dataIndex}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: `${title} 是必填的.`
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
ref={(inputRef as unknown) as () => RefObject<HTMLInputElement>}
|
|
||||||
onPressEnter={save}
|
|
||||||
onBlur={save}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className="editable-cell-value-wrap"
|
|
||||||
style={{ paddingRight: 24 }}
|
|
||||||
onClick={toggleEdit}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <td {...restProps}>{childNode}</td>;
|
|
||||||
};
|
|
||||||
|
|
||||||
class EditableTable extends React.Component<any, any> {
|
|
||||||
columns: (
|
|
||||||
| {
|
|
||||||
title: string;
|
|
||||||
dataIndex: string;
|
|
||||||
width: string;
|
|
||||||
editable: boolean;
|
|
||||||
render?: undefined;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
title: string;
|
|
||||||
dataIndex: string;
|
|
||||||
render: (text: string, record: any) => JSX.Element | null;
|
|
||||||
width?: undefined;
|
|
||||||
editable?: undefined;
|
|
||||||
}
|
|
||||||
)[];
|
|
||||||
apiForm: {
|
|
||||||
api: string;
|
|
||||||
header: string;
|
|
||||||
dataField: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
this.columns = [
|
|
||||||
{
|
|
||||||
title: "名字",
|
|
||||||
dataIndex: "name",
|
|
||||||
width: "180px",
|
|
||||||
editable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "值",
|
|
||||||
dataIndex: "value",
|
|
||||||
width: "120px",
|
|
||||||
editable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "操作",
|
|
||||||
dataIndex: "operation",
|
|
||||||
render: (text: string, record) =>
|
|
||||||
this.state.dataSource.length >= 1 ? (
|
|
||||||
<Popconfirm
|
|
||||||
title="Sure to delete?"
|
|
||||||
onConfirm={() => this.handleDelete(record.key)}
|
|
||||||
>
|
|
||||||
<Button type="link">删除</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.apiForm = {
|
|
||||||
api: "",
|
|
||||||
header: "",
|
|
||||||
dataField: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataSource =
|
|
||||||
props.data &&
|
|
||||||
props.data.map((item: any, i: number) => ({ key: i + "", ...item }));
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
dataSource: dataSource,
|
|
||||||
visible: false,
|
|
||||||
apiVisible: false,
|
|
||||||
apiResult: ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDelete = (key: string) => {
|
|
||||||
const dataSource = [...this.state.dataSource];
|
|
||||||
const newDataSource = dataSource.filter(item => item.key !== key);
|
|
||||||
this.setState({ dataSource: newDataSource });
|
|
||||||
this.props.onChange && this.props.onChange(newDataSource);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAdd = () => {
|
|
||||||
const { dataSource } = this.state;
|
|
||||||
const uid = uuid(8, 10);
|
|
||||||
const newData = {
|
|
||||||
key: uid,
|
|
||||||
name: `dooring ${dataSource.length + 1}`,
|
|
||||||
value: 32
|
|
||||||
};
|
|
||||||
const newDataSource = [...dataSource, newData];
|
|
||||||
this.setState({
|
|
||||||
dataSource: newDataSource
|
|
||||||
});
|
|
||||||
this.props.onChange && this.props.onChange(newDataSource);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSave = (row: any) => {
|
|
||||||
const newData = [...this.state.dataSource];
|
|
||||||
const index = newData.findIndex(item => row.key === item.key);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
...row
|
|
||||||
});
|
|
||||||
this.setState({ dataSource: newData });
|
|
||||||
this.props.onChange && this.props.onChange(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
showModal = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOk = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
|
||||||
this.setState({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCancel = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
|
||||||
this.setState({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
showApiModal = () => {
|
|
||||||
this.setState({
|
|
||||||
apiVisible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAPIOk = () => {
|
|
||||||
const { dataField } = this.apiForm;
|
|
||||||
if (dataField) {
|
|
||||||
let data = this.state.apiResult[dataField];
|
|
||||||
if (data && data instanceof Array) {
|
|
||||||
data = data.map((item, i) => ({ key: i + "", ...item }));
|
|
||||||
this.setState({
|
|
||||||
dataSource: data
|
|
||||||
});
|
|
||||||
this.props.onChange && this.props.onChange(data);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
apiVisible: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAPICancel = () => {
|
|
||||||
this.setState({
|
|
||||||
apiVisible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleApiField = (type: "api" | "header" | "dataField", v: string) => {
|
|
||||||
this.apiForm[type] = v;
|
|
||||||
};
|
|
||||||
|
|
||||||
getApiFn = () => {
|
|
||||||
console.log(this.apiForm);
|
|
||||||
const { api, header } = this.apiForm;
|
|
||||||
fetch(api, {
|
|
||||||
cache: "no-cache",
|
|
||||||
headers: Object.assign(
|
|
||||||
{ "content-type": "application/json" },
|
|
||||||
header ? JSON.parse(header) : {}
|
|
||||||
),
|
|
||||||
method: "GET",
|
|
||||||
mode: "cors"
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
this.setState({
|
|
||||||
apiResult: res
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { dataSource } = this.state;
|
|
||||||
const components = {
|
|
||||||
body: {
|
|
||||||
row: EditableRow,
|
|
||||||
cell: EditableCell
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const columns: ColumnsType<any> = this.columns.map(col => {
|
|
||||||
if (!col.editable) {
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...col,
|
|
||||||
onCell: record => ({
|
|
||||||
record,
|
|
||||||
editable: col.editable,
|
|
||||||
dataIndex: col.dataIndex,
|
|
||||||
title: col.title,
|
|
||||||
handleSave: this.handleSave
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const _this = this;
|
|
||||||
const props = {
|
|
||||||
name: "file",
|
|
||||||
// action: '',
|
|
||||||
showUploadList: false,
|
|
||||||
beforeUpload(file: File, fileList: Array<File>) {
|
|
||||||
// 解析并提取excel数据
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = function(e: any) {
|
|
||||||
let data = e.target.result;
|
|
||||||
let workbook = XLSX.read(data, { type: "binary" });
|
|
||||||
let sheetNames = workbook.SheetNames; // 工作表名称集合
|
|
||||||
let draftArr: any = {};
|
|
||||||
sheetNames.forEach(name => {
|
|
||||||
let worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
|
|
||||||
for (let key in worksheet) {
|
|
||||||
// v是读取单元格的原始值
|
|
||||||
if (key[0] !== "!") {
|
|
||||||
if (draftArr[key[0]]) {
|
|
||||||
draftArr[key[0]].push(worksheet[key].v);
|
|
||||||
} else {
|
|
||||||
draftArr[key[0]] = [worksheet[key].v];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let sourceData = Object.values(draftArr).map((item: any, i) => ({
|
|
||||||
key: i + "",
|
|
||||||
name: item[0],
|
|
||||||
value: item[1]
|
|
||||||
}));
|
|
||||||
_this.setState({
|
|
||||||
dataSource: sourceData
|
|
||||||
});
|
|
||||||
_this.props.onChange && _this.props.onChange(sourceData);
|
|
||||||
};
|
|
||||||
reader.readAsBinaryString(file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button type="primary" onClick={this.showModal}>
|
|
||||||
编辑数据源
|
|
||||||
</Button>
|
|
||||||
<Modal
|
|
||||||
title="编辑数据源"
|
|
||||||
visible={this.state.visible}
|
|
||||||
onOk={this.handleOk}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={this.handleAdd}
|
|
||||||
type="primary"
|
|
||||||
style={{ marginBottom: 16, marginRight: 16 }}
|
|
||||||
>
|
|
||||||
添加行
|
|
||||||
</Button>
|
|
||||||
<Upload {...props}>
|
|
||||||
<Button type="primary" ghost style={{ marginRight: 16 }}>
|
|
||||||
导入Excel
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
<Button type="primary" ghost onClick={this.showApiModal}>
|
|
||||||
第三方API
|
|
||||||
</Button>
|
|
||||||
<Table
|
|
||||||
components={components}
|
|
||||||
rowClassName={() => "editable-row"}
|
|
||||||
bordered
|
|
||||||
dataSource={dataSource}
|
|
||||||
columns={columns}
|
|
||||||
pagination={{ pageSize: 50 }}
|
|
||||||
scroll={{ y: 240 }}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
title="配置api"
|
|
||||||
visible={this.state.apiVisible}
|
|
||||||
onOk={this.handleAPIOk}
|
|
||||||
onCancel={this.handleAPICancel}
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<div className={styles.apiForm}>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input
|
|
||||||
placeholder="请输入api地址"
|
|
||||||
onChange={e => this.handleApiField("api", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input.TextArea
|
|
||||||
placeholder="请输入头信息, 如{token: 123456}, 格式必须为json对象"
|
|
||||||
rows={4}
|
|
||||||
onChange={e => this.handleApiField("header", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Button type="primary" onClick={this.getApiFn}>
|
|
||||||
发送请求
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{this.state.apiResult && (
|
|
||||||
<>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input.TextArea
|
|
||||||
rows={6}
|
|
||||||
value={JSON.stringify(this.state.apiResult, null, 4)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input
|
|
||||||
placeholder="设置数据源字段"
|
|
||||||
onChange={e =>
|
|
||||||
this.handleApiField("dataField", e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<p style={{ color: "red" }}>
|
|
||||||
数据源字段是接口返回的图表数据对应的字段, 必填,
|
|
||||||
否则无法正确导入数据
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(EditableTable);
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
:global(.ant-upload-select-picture-card i) {
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.ant-upload-select-picture-card .ant-upload-text) {
|
|
||||||
margin-top: 8px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatarUploader {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallBtn {
|
|
||||||
position: absolute;
|
|
||||||
left: 140px;
|
|
||||||
bottom: 56px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #2f54eb;
|
|
||||||
cursor: pointer;
|
|
||||||
border-bottom: 1px solid #2f54eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imgBox {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-height: 520px;
|
|
||||||
overflow: auto;
|
|
||||||
.imgItem {
|
|
||||||
position: relative;
|
|
||||||
margin-right: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
width: 320px;
|
|
||||||
max-height: 220px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&.seleted {
|
|
||||||
.iconBtn {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconBtn {
|
|
||||||
position: absolute;
|
|
||||||
visibility: hidden;
|
|
||||||
top: 6px;
|
|
||||||
right: 10px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: rgb(8, 156, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,288 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Upload, Modal, message, Tabs, Result } from "antd";
|
|
||||||
import { PlusOutlined, CheckCircleFilled } from "@ant-design/icons";
|
|
||||||
import ImgCrop from "antd-img-crop";
|
|
||||||
import classnames from "classnames";
|
|
||||||
import {
|
|
||||||
UploadFile,
|
|
||||||
UploadChangeParam,
|
|
||||||
RcFile
|
|
||||||
} from "antd/lib/upload/interface";
|
|
||||||
import { isDev, unParams, uuid } from "@/utils/tool";
|
|
||||||
import req from "@/utils/req";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
// 维护图片分类映射
|
|
||||||
const wallCateName: any = {
|
|
||||||
photo: "照片",
|
|
||||||
bg: "背景",
|
|
||||||
chahua: "插画"
|
|
||||||
};
|
|
||||||
|
|
||||||
function getBase64(file: File | Blob) {
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => resolve(reader.result as string);
|
|
||||||
reader.onerror = error => reject(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PicturesWallType {
|
|
||||||
fileList?: UploadFile<any>[];
|
|
||||||
action?: string;
|
|
||||||
headers?: any;
|
|
||||||
withCredentials?: boolean;
|
|
||||||
maxLen?: number;
|
|
||||||
onChange?: (v: any) => void;
|
|
||||||
cropRate?: number | boolean;
|
|
||||||
isCrop?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PicturesWall extends React.Component<PicturesWallType> {
|
|
||||||
state = {
|
|
||||||
previewVisible: false,
|
|
||||||
previewImage: "",
|
|
||||||
wallModalVisible: false,
|
|
||||||
previewTitle: "",
|
|
||||||
imgBed: {
|
|
||||||
photo: [],
|
|
||||||
bg: [],
|
|
||||||
chahua: []
|
|
||||||
},
|
|
||||||
curSelectedImg: "",
|
|
||||||
fileList: this.props.fileList || []
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCancel = () => this.setState({ previewVisible: false });
|
|
||||||
|
|
||||||
handleModalCancel = () => this.setState({ wallModalVisible: false });
|
|
||||||
|
|
||||||
handlePreview = async (file: UploadFile<any>) => {
|
|
||||||
if (!file.url && !file.preview) {
|
|
||||||
file.preview = await getBase64(file.originFileObj!);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
previewImage: file.url || file.preview,
|
|
||||||
previewVisible: true,
|
|
||||||
previewTitle:
|
|
||||||
file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleWallSelect = (url: string) => {
|
|
||||||
this.setState({
|
|
||||||
wallModalVisible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleImgSelected = (url: string) => {
|
|
||||||
this.setState({
|
|
||||||
curSelectedImg: url
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleWallShow = () => {
|
|
||||||
this.setState({
|
|
||||||
wallModalVisible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleModalOk = () => {
|
|
||||||
const fileList = [
|
|
||||||
{
|
|
||||||
uid: uuid(8, 16),
|
|
||||||
name: "h5-dooring图片库",
|
|
||||||
status: "done",
|
|
||||||
url: this.state.curSelectedImg
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.props.onChange && this.props.onChange(fileList);
|
|
||||||
this.setState({ fileList, wallModalVisible: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = ({ file, fileList }: UploadChangeParam<UploadFile<any>>) => {
|
|
||||||
this.setState({ fileList });
|
|
||||||
if (file.status === "done") {
|
|
||||||
const files = fileList.map(item => {
|
|
||||||
const { uid, name, status } = item;
|
|
||||||
const url = item.url || item.response.result.url;
|
|
||||||
return { uid, name, status, url };
|
|
||||||
});
|
|
||||||
this.props.onChange && this.props.onChange(files);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBeforeUpload = (file: RcFile) => {
|
|
||||||
const isJpgOrPng =
|
|
||||||
file.type === "image/jpeg" ||
|
|
||||||
file.type === "image/png" ||
|
|
||||||
file.type === "image/jpg" ||
|
|
||||||
file.type === "image/gif";
|
|
||||||
if (!isJpgOrPng) {
|
|
||||||
message.error("只能上传格式为jpeg/png/gif的图片");
|
|
||||||
}
|
|
||||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
|
||||||
if (!isLt2M) {
|
|
||||||
message.error("图片必须小于2MB!");
|
|
||||||
}
|
|
||||||
return isJpgOrPng && isLt2M;
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
// req.get(`/visible/bed/get?tid=${unParams(location.search)!.tid}`).then(res => {
|
|
||||||
// res &&
|
|
||||||
// this.setState({
|
|
||||||
// imgBed: res,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
previewVisible,
|
|
||||||
previewImage,
|
|
||||||
fileList,
|
|
||||||
previewTitle,
|
|
||||||
wallModalVisible,
|
|
||||||
imgBed,
|
|
||||||
curSelectedImg
|
|
||||||
} = this.state;
|
|
||||||
const {
|
|
||||||
action = isDev
|
|
||||||
? "http://192.168.1.8:3000/api/v0/files/upload/free"
|
|
||||||
: "你的服务器地址",
|
|
||||||
headers,
|
|
||||||
withCredentials = true,
|
|
||||||
maxLen = 1,
|
|
||||||
cropRate = 375 / 158,
|
|
||||||
isCrop
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const uploadButton = (
|
|
||||||
<div>
|
|
||||||
<PlusOutlined />
|
|
||||||
<div className="ant-upload-text">上传</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const cates = Object.keys(imgBed);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isCrop ? (
|
|
||||||
<ImgCrop
|
|
||||||
modalTitle="裁剪图片"
|
|
||||||
modalOk="确定"
|
|
||||||
modalCancel="取消"
|
|
||||||
rotate={true}
|
|
||||||
aspect={cropRate}
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
fileList={fileList}
|
|
||||||
onPreview={this.handlePreview}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
name="file"
|
|
||||||
listType="picture-card"
|
|
||||||
className={styles.avatarUploader}
|
|
||||||
action={action}
|
|
||||||
withCredentials={withCredentials}
|
|
||||||
headers={{
|
|
||||||
"x-requested-with": localStorage.getItem("user") || "",
|
|
||||||
authorization: localStorage.getItem("token") || "",
|
|
||||||
...headers
|
|
||||||
}}
|
|
||||||
beforeUpload={this.handleBeforeUpload}
|
|
||||||
>
|
|
||||||
{fileList.length >= maxLen ? null : uploadButton}
|
|
||||||
</Upload>
|
|
||||||
</ImgCrop>
|
|
||||||
) : (
|
|
||||||
<Upload
|
|
||||||
fileList={fileList}
|
|
||||||
onPreview={this.handlePreview}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
name="file"
|
|
||||||
listType="picture-card"
|
|
||||||
className={styles.avatarUploader}
|
|
||||||
action={action}
|
|
||||||
withCredentials={withCredentials}
|
|
||||||
headers={{
|
|
||||||
"x-requested-with": localStorage.getItem("user") || "",
|
|
||||||
authorization: localStorage.getItem("token") || "",
|
|
||||||
...headers
|
|
||||||
}}
|
|
||||||
beforeUpload={this.handleBeforeUpload}
|
|
||||||
>
|
|
||||||
{fileList.length >= maxLen ? null : uploadButton}
|
|
||||||
</Upload>
|
|
||||||
)}
|
|
||||||
<div className={styles.wallBtn} onClick={this.handleWallShow}>
|
|
||||||
图片库
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
visible={previewVisible}
|
|
||||||
title={previewTitle}
|
|
||||||
footer={null}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
>
|
|
||||||
<img alt="预览图片" style={{ width: "100%" }} src={previewImage} />
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
visible={wallModalVisible}
|
|
||||||
title="图片库"
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
width={860}
|
|
||||||
onCancel={this.handleModalCancel}
|
|
||||||
onOk={this.handleModalOk}
|
|
||||||
>
|
|
||||||
<Tabs
|
|
||||||
defaultActiveKey={cates[0]}
|
|
||||||
tabPosition="left"
|
|
||||||
style={{ height: 520 }}
|
|
||||||
>
|
|
||||||
{cates.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<TabPane tab={wallCateName[item]} key={item}>
|
|
||||||
<div className={styles.imgBox}>
|
|
||||||
{(imgBed as any)[item] &&
|
|
||||||
(imgBed as any)[item].map((item: string, i: number) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
styles.imgItem,
|
|
||||||
curSelectedImg === item ? styles.seleted : ""
|
|
||||||
)}
|
|
||||||
key={i}
|
|
||||||
onClick={() => this.handleImgSelected(item)}
|
|
||||||
>
|
|
||||||
<img src={item} alt="趣谈前端-h5-dooring" />
|
|
||||||
<span className={styles.iconBtn}>
|
|
||||||
<CheckCircleFilled />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</TabPane>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<TabPane tab="更多" key="more">
|
|
||||||
<Result
|
|
||||||
status="500"
|
|
||||||
title="Dooring温馨提示"
|
|
||||||
subTitle="更多素材, 正在筹备中..."
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PicturesWall;
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
.avatarUploader > :global(.ant-upload) {
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import React, { useState, useEffect, memo } from "react";
|
|
||||||
import req from "@/utils/req";
|
|
||||||
import BraftEditor from "braft-editor";
|
|
||||||
import "braft-editor/dist/index.css";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
const controls = [
|
|
||||||
{
|
|
||||||
key: "bold",
|
|
||||||
text: <b>加粗</b>
|
|
||||||
},
|
|
||||||
"undo",
|
|
||||||
"redo",
|
|
||||||
"emoji",
|
|
||||||
"list-ul",
|
|
||||||
"list-ol",
|
|
||||||
"blockquote",
|
|
||||||
"text-align",
|
|
||||||
"font-size",
|
|
||||||
"line-height",
|
|
||||||
"letter-spacing",
|
|
||||||
"text-color",
|
|
||||||
"italic",
|
|
||||||
"underline",
|
|
||||||
"link",
|
|
||||||
"media"
|
|
||||||
];
|
|
||||||
|
|
||||||
export default memo(function XEditor(props: any) {
|
|
||||||
const { value, onChange } = props;
|
|
||||||
const [editorState, setEditorState] = useState(
|
|
||||||
BraftEditor.createEditorState(value)
|
|
||||||
);
|
|
||||||
|
|
||||||
const myUploadFn = (param: any) => {
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append("file", param.file);
|
|
||||||
|
|
||||||
req
|
|
||||||
.post("xxxx", fd, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data"
|
|
||||||
},
|
|
||||||
onUploadProgress: function(event) {
|
|
||||||
// 上传进度发生变化时调用param.progress
|
|
||||||
console.log((event.loaded / event.total) * 100);
|
|
||||||
param.progress((event.loaded / event.total) * 100);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
// 上传成功后调用param.success并传入上传后的文件地址
|
|
||||||
param.success({
|
|
||||||
url: res.url,
|
|
||||||
meta: {
|
|
||||||
id: Date.now(),
|
|
||||||
title: res.filename,
|
|
||||||
alt: "趣谈前端"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
param.error({
|
|
||||||
msg: "上传失败."
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitContent = () => {
|
|
||||||
const htmlContent = editorState.toHTML();
|
|
||||||
onChange && onChange(htmlContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditorChange = editorState => {
|
|
||||||
setEditorState(editorState);
|
|
||||||
if (onChange) {
|
|
||||||
const htmlContent = editorState.toHTML();
|
|
||||||
onChange(htmlContent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const htmlContent = value || "";
|
|
||||||
setEditorState(BraftEditor.createEditorState(htmlContent));
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<BraftEditor
|
|
||||||
value={editorState}
|
|
||||||
controls={controls}
|
|
||||||
onChange={handleEditorChange}
|
|
||||||
onSave={submitContent}
|
|
||||||
media={{ uploadFn: myUploadFn }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,243 +0,0 @@
|
|||||||
////////////////////
|
|
||||||
export interface IUploadConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Upload";
|
|
||||||
isCrop?: boolean;
|
|
||||||
cropRate?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TUploadDefaultType = Array<{
|
|
||||||
uid: string;
|
|
||||||
name: string;
|
|
||||||
status: string;
|
|
||||||
url: string;
|
|
||||||
}>;
|
|
||||||
/////////////////
|
|
||||||
export interface ITextConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Text";
|
|
||||||
}
|
|
||||||
export type TTextDefaultType = string;
|
|
||||||
////////////////////////
|
|
||||||
export interface ITextAreaConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "TextArea";
|
|
||||||
}
|
|
||||||
export type TTextAreaDefaultType = string;
|
|
||||||
////////////////////////////
|
|
||||||
export interface INumberConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Number";
|
|
||||||
range?: [number, number];
|
|
||||||
step?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TNumberDefaultType = number;
|
|
||||||
|
|
||||||
///////////////////
|
|
||||||
export interface IDataListConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "DataList";
|
|
||||||
cropRate: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TDataListDefaultTypeItem = {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
desc: string;
|
|
||||||
link: string;
|
|
||||||
type?: number;
|
|
||||||
imgUrl: TUploadDefaultType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TDataListDefaultType = Array<TDataListDefaultTypeItem>;
|
|
||||||
|
|
||||||
////////////////////
|
|
||||||
export interface IColorConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Color";
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TColorDefaultType = string;
|
|
||||||
|
|
||||||
/////////////////
|
|
||||||
export interface IRichTextConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "RichText";
|
|
||||||
}
|
|
||||||
export type TRichTextDefaultType = string;
|
|
||||||
|
|
||||||
export interface IMutiTextConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "MutiText";
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TMutiTextDefaultType = Array<string>;
|
|
||||||
|
|
||||||
/////////////////////////////////
|
|
||||||
export interface ISelectConfigType<KeyType> {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Select";
|
|
||||||
range: Array<{
|
|
||||||
key: KeyType;
|
|
||||||
text: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
export type TSelectDefaultType<KeyType> = KeyType;
|
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
export interface IRadioConfigType<KeyType> {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Radio";
|
|
||||||
range: Array<{
|
|
||||||
key: KeyType;
|
|
||||||
text: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
export type TRadioDefaultType<KeyType> = KeyType;
|
|
||||||
|
|
||||||
///////////////
|
|
||||||
|
|
||||||
export interface ISwitchConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Switch";
|
|
||||||
}
|
|
||||||
export type TSwitchDefaultType = boolean;
|
|
||||||
|
|
||||||
/////////////////////////////
|
|
||||||
export interface ICardPickerConfigType<T> {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "CardPicker";
|
|
||||||
icons: Array<T>;
|
|
||||||
}
|
|
||||||
export type TCardPickerDefaultType<T> = T;
|
|
||||||
|
|
||||||
/////////////
|
|
||||||
|
|
||||||
export interface ITableConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Table";
|
|
||||||
}
|
|
||||||
export type TTableDefaultType = Array<{
|
|
||||||
name: string;
|
|
||||||
value: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// position input control
|
|
||||||
export interface IPosConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Pos";
|
|
||||||
placeObj: {
|
|
||||||
text: string;
|
|
||||||
link: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TPosItem = number | undefined;
|
|
||||||
|
|
||||||
export type TPosDefaultType = [TPosItem, TPosItem];
|
|
||||||
|
|
||||||
//////////////////
|
|
||||||
export interface IFormItemsConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "FormItems";
|
|
||||||
}
|
|
||||||
|
|
||||||
//0---------baseform
|
|
||||||
export type baseFormOptionsType = {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormTextTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Text";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormTextTipTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MyTextTip";
|
|
||||||
label: string;
|
|
||||||
color: string;
|
|
||||||
fontSize: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormNumberTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Number";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormTextAreaTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Textarea";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormMyRadioTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MyRadio";
|
|
||||||
label: string;
|
|
||||||
options: baseFormOptionsType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormMyCheckboxTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MyCheckbox";
|
|
||||||
label: string;
|
|
||||||
options: baseFormOptionsType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormMySelectTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MySelect";
|
|
||||||
label: string;
|
|
||||||
options: baseFormOptionsType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormDateTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Date";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormUnion =
|
|
||||||
| baseFormTextTpl
|
|
||||||
| baseFormTextTipTpl
|
|
||||||
| baseFormNumberTpl
|
|
||||||
| baseFormTextAreaTpl
|
|
||||||
| baseFormMyRadioTpl
|
|
||||||
| baseFormMyCheckboxTpl
|
|
||||||
| baseFormMySelectTpl
|
|
||||||
| baseFormDateTpl;
|
|
||||||
export type baseFormUnionType =
|
|
||||||
| baseFormTextTpl["type"]
|
|
||||||
| baseFormTextTipTpl["type"]
|
|
||||||
| baseFormNumberTpl["type"]
|
|
||||||
| baseFormTextAreaTpl["type"]
|
|
||||||
| baseFormMyRadioTpl["type"]
|
|
||||||
| baseFormMyCheckboxTpl["type"]
|
|
||||||
| baseFormMySelectTpl["type"]
|
|
||||||
| baseFormDateTpl["type"];
|
|
||||||
|
|
||||||
export type TFormItemsDefaultType = Array<baseFormUnion>;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
.pickerWrap {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
.picker {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
border-color: #4091f7;
|
|
||||||
}
|
|
||||||
&.selected {
|
|
||||||
border-color: #4091f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { useState, useEffect, memo } from "react";
|
|
||||||
import classnames from "classnames";
|
|
||||||
import Icon from "@/materials/base/Icon";
|
|
||||||
import styles from "./index.less";
|
|
||||||
import React from "react";
|
|
||||||
import { IconTypes } from "@/materials/base/Icon/schema";
|
|
||||||
import { ICardPickerConfigType } from "../types";
|
|
||||||
|
|
||||||
interface CardPickerType
|
|
||||||
extends Omit<ICardPickerConfigType<IconTypes>, "type" | "key" | "name"> {
|
|
||||||
onChange?: (v: string) => void;
|
|
||||||
type: IconTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo((props: CardPickerType) => {
|
|
||||||
const { type, icons, onChange } = props;
|
|
||||||
const [selected, setSelected] = useState<IconTypes>(type);
|
|
||||||
|
|
||||||
const handlePicker = (v: IconTypes) => {
|
|
||||||
if (onChange) {
|
|
||||||
onChange(v);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSelected(v);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelected(type);
|
|
||||||
}, [type]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.pickerWrap}>
|
|
||||||
{icons.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={classnames(
|
|
||||||
styles.picker,
|
|
||||||
selected === item ? styles.selected : ""
|
|
||||||
)}
|
|
||||||
onClick={() => handlePicker(item)}
|
|
||||||
key={i}
|
|
||||||
>
|
|
||||||
<Icon type={item} size={20} color={"#4091f7"} spin={false} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { SketchPicker, ColorResult } from "react-color";
|
|
||||||
import { rgba2Obj } from "@/utils/tool";
|
|
||||||
|
|
||||||
export type ColorConfigType = string;
|
|
||||||
|
|
||||||
//value 初始值传来,onchange item给的回调
|
|
||||||
interface ColorProps {
|
|
||||||
value?: ColorConfigType;
|
|
||||||
onChange?: (v: ColorConfigType) => 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: "20px",
|
|
||||||
height: "20px",
|
|
||||||
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,127 +0,0 @@
|
|||||||
import React, { memo, useEffect, FC } from "react";
|
|
||||||
import { Form, Select, Input, Modal, Button } from "antd";
|
|
||||||
import Upload from "../Upload";
|
|
||||||
import { Store } from "antd/lib/form/interface";
|
|
||||||
import { TDataListDefaultTypeItem } from "../FormEditor/types";
|
|
||||||
// import styles from './index.less';
|
|
||||||
const normFile = (e: any) => {
|
|
||||||
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?: TDataListDefaultTypeItem;
|
|
||||||
onSave: Function;
|
|
||||||
cropRate: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const EditorModal: FC<EditorModalProps> = props => {
|
|
||||||
const { item, onSave, visible, onCancel, cropRate } = props;
|
|
||||||
const onFinish = (values: Store) => {
|
|
||||||
console.log(values);
|
|
||||||
onSave && onSave(values);
|
|
||||||
};
|
|
||||||
const handleOk = () => {
|
|
||||||
form
|
|
||||||
.validateFields()
|
|
||||||
.then(values => {
|
|
||||||
if (item) {
|
|
||||||
values.id = item.id;
|
|
||||||
onSave && onSave(values);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (form && item && visible) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
}, [form, item, visible]);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!!item && (
|
|
||||||
<Modal
|
|
||||||
title="编辑数据源"
|
|
||||||
closable={false}
|
|
||||||
visible={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
okText="确定"
|
|
||||||
forceRender
|
|
||||||
footer={
|
|
||||||
<Button type={"primary"} onClick={() => handleOk()}>
|
|
||||||
确定
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<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 cropRate={cropRate} isCrop />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(EditorModal);
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
.dataList {
|
|
||||||
padding: 6px 10px;
|
|
||||||
border: 1px solid #f0f0f0;
|
|
||||||
text-align: justify;
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
.listItem {
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
&:hover {
|
|
||||||
.actionBar {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.tit {
|
|
||||||
font-weight: bold;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
.actionBar {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: none;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: -20px 0 10px 10px #fff;
|
|
||||||
.action {
|
|
||||||
margin-right: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
&:last-child {
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,263 +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,
|
|
||||||
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 { TDataListDefaultType, TDataListDefaultTypeItem } from "../types";
|
|
||||||
|
|
||||||
type ListItemProps = DndItemProps & {
|
|
||||||
isDragging: boolean;
|
|
||||||
connectDragSource: ConnectDragSource;
|
|
||||||
connectDragPreview: ConnectDragPreview;
|
|
||||||
connectDropTarget: ConnectDropTarget;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ListItem(props: ListItemProps) {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
desc,
|
|
||||||
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 = TDataListDefaultTypeItem & {
|
|
||||||
onDel: Function;
|
|
||||||
onEdit: Function;
|
|
||||||
key: number;
|
|
||||||
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: TDataListDefaultType) => void;
|
|
||||||
value?: TDataListDefaultType;
|
|
||||||
cropRate: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DataListType = DataListMemo & {
|
|
||||||
connectDropTarget: ConnectDropTarget;
|
|
||||||
};
|
|
||||||
|
|
||||||
const List = function(props: DataListType) {
|
|
||||||
const { onChange, value, connectDropTarget, cropRate } = props;
|
|
||||||
const [list, setList] = useState(value);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [curItem, setCurItem] = useState<TDataListDefaultTypeItem>();
|
|
||||||
|
|
||||||
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(() => {
|
|
||||||
console.log("a");
|
|
||||||
setVisible(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEdit = useCallback((item: TDataListDefaultTypeItem) => {
|
|
||||||
console.log("b");
|
|
||||||
setVisible(true);
|
|
||||||
setCurItem(item);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSave = useCallback(
|
|
||||||
(item: TDataListDefaultTypeItem) => {
|
|
||||||
console.log("c");
|
|
||||||
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)));
|
|
||||||
},
|
|
||||||
[list, onChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
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 onClick={handleAdd} block>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<EditorModal
|
|
||||||
visible={visible}
|
|
||||||
onCancel={handleCancel}
|
|
||||||
item={curItem}
|
|
||||||
onSave={handleSave}
|
|
||||||
cropRate={cropRate}
|
|
||||||
/>
|
|
||||||
</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 React, { FC, memo, useEffect } from "react";
|
|
||||||
import { Form, Select, Input, Modal, Button, InputNumber } from "antd";
|
|
||||||
import { baseFormOptionsType } from "../types";
|
|
||||||
import Color from "../Color";
|
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const formItemLayout = {
|
|
||||||
labelCol: { span: 6 },
|
|
||||||
wrapperCol: { span: 14 }
|
|
||||||
};
|
|
||||||
|
|
||||||
interface EditorModalProps {
|
|
||||||
item: any;
|
|
||||||
onSave: (data: any) => void;
|
|
||||||
visible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditorModal: FC<EditorModalProps> = props => {
|
|
||||||
const { item, onSave, visible } = props;
|
|
||||||
|
|
||||||
const onFinish = (values: any) => {
|
|
||||||
onSave && onSave(values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOk = () => {
|
|
||||||
form
|
|
||||||
.validateFields()
|
|
||||||
.then(values => {
|
|
||||||
values.id = item.id;
|
|
||||||
onSave && onSave(values);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (form && item && visible) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
}, [form, item, visible]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!!item && (
|
|
||||||
<Modal
|
|
||||||
title="编辑表单组件"
|
|
||||||
footer={
|
|
||||||
<div>
|
|
||||||
<Button type="primary" onClick={() => handleOk()}>
|
|
||||||
确定
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
forceRender
|
|
||||||
visible={visible}
|
|
||||||
onOk={handleOk}
|
|
||||||
closable={false}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
name={`formItem_editor_modal`}
|
|
||||||
{...formItemLayout}
|
|
||||||
onFinish={onFinish}
|
|
||||||
initialValues={item}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
<Form.Item label="类型" name="type" hidden>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
}
|
|
||||||
{!!item.label && (
|
|
||||||
<Form.Item
|
|
||||||
label="字段名"
|
|
||||||
name="label"
|
|
||||||
rules={[{ required: true, message: "请输入字段名!" }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.fontSize && (
|
|
||||||
<Form.Item
|
|
||||||
label="字体大小"
|
|
||||||
name="fontSize"
|
|
||||||
rules={[{ required: true, message: "请输入字体大小!" }]}
|
|
||||||
>
|
|
||||||
<InputNumber min={12} max={30} defaultValue={14} />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.color && (
|
|
||||||
<Form.Item
|
|
||||||
label="文字颜色"
|
|
||||||
name="color"
|
|
||||||
rules={[{ required: true, message: "请输入文字颜色!" }]}
|
|
||||||
>
|
|
||||||
<Color />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.placeholder && (
|
|
||||||
<Form.Item label="提示文本" name="placeholder">
|
|
||||||
<Input placeholder="请输入提示文本" />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
{!!item.options && (
|
|
||||||
<Form.Item
|
|
||||||
label="选项源"
|
|
||||||
name="options"
|
|
||||||
rules={[{ required: true, message: "选项不能为空!" }]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder="请输入"
|
|
||||||
mode="tags"
|
|
||||||
labelInValue
|
|
||||||
maxTagCount={39}
|
|
||||||
maxTagTextLength={16}
|
|
||||||
>
|
|
||||||
{item.options.map((v: baseFormOptionsType, i: number) => {
|
|
||||||
return (
|
|
||||||
<Option value={v.value} key={i}>
|
|
||||||
{v.label}
|
|
||||||
</Option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(EditorModal);
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
import React, {
|
|
||||||
memo,
|
|
||||||
RefObject,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState
|
|
||||||
} from "react";
|
|
||||||
import BaseForm from "@/materials/base/Form/BaseForm";
|
|
||||||
import BasePopoverForm from "@/materials/base/Form/BasePopoverForm";
|
|
||||||
import EditorModal from "./EditorModal";
|
|
||||||
import { MinusCircleFilled, EditFilled, PlusOutlined } from "@ant-design/icons";
|
|
||||||
import styles from "./formItems.less";
|
|
||||||
import { baseFormUnion, TFormItemsDefaultType } from "../types";
|
|
||||||
import { uuid } from "@/utils/tool";
|
|
||||||
import { Button } from "antd";
|
|
||||||
import MyPopover from "yh-react-popover";
|
|
||||||
// import { Popconfirm } from 'antd';
|
|
||||||
|
|
||||||
const formTpl: TFormItemsDefaultType = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
type: "Text",
|
|
||||||
label: "文本框",
|
|
||||||
placeholder: "请输入文本"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
type: "Textarea",
|
|
||||||
label: "长文本框",
|
|
||||||
placeholder: "请输入长文本请输入长文本"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
type: "Number",
|
|
||||||
label: "数值",
|
|
||||||
placeholder: " 请输入数值"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
type: "MyRadio",
|
|
||||||
label: "单选框",
|
|
||||||
options: [
|
|
||||||
{ label: "选项一", value: "1" },
|
|
||||||
{ label: "选项二", value: "2" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
type: "MySelect",
|
|
||||||
label: "下拉选择框",
|
|
||||||
options: [
|
|
||||||
{ label: "选项一", value: "1" },
|
|
||||||
{ label: "选项二", value: "2" },
|
|
||||||
{ label: "选项三", value: "3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
type: "Date",
|
|
||||||
label: "日期框",
|
|
||||||
placeholder: ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "7",
|
|
||||||
type: "MyTextTip",
|
|
||||||
label: "纯文本",
|
|
||||||
fontSize: 12,
|
|
||||||
color: "rgba(0,0,0,1)"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
interface FormItemsProps {
|
|
||||||
formList?: TFormItemsDefaultType;
|
|
||||||
onChange?: (v: TFormItemsDefaultType) => void;
|
|
||||||
data: any;
|
|
||||||
rightPannelRef: RefObject<HTMLDivElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormItems = (props: FormItemsProps) => {
|
|
||||||
const { formList, onChange, rightPannelRef } = props;
|
|
||||||
const [formData, setFormData] = useState<TFormItemsDefaultType>(
|
|
||||||
formList || []
|
|
||||||
);
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [curItem, setCurItem] = useState<baseFormUnion>();
|
|
||||||
const [force, setforce] = useState<{ force: Function }>({
|
|
||||||
force: () => {}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAddItem = (item: baseFormUnion) => {
|
|
||||||
let tpl = formTpl.find(v => v.type === item.type);
|
|
||||||
let newData = [...formData, { ...tpl!, id: uuid(6, 10) }];
|
|
||||||
setFormData(newData);
|
|
||||||
onChange && onChange(newData);
|
|
||||||
force.force();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditItem = (item: baseFormUnion) => {
|
|
||||||
setVisible(true);
|
|
||||||
setCurItem(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelItem = (item: baseFormUnion) => {
|
|
||||||
let newData = formData.filter(v => v.id !== item.id);
|
|
||||||
setFormData(newData);
|
|
||||||
onChange && onChange(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveItem = (data: baseFormUnion) => {
|
|
||||||
let newData = formData.map(v => (v.id === data.id ? data : v));
|
|
||||||
setFormData(newData);
|
|
||||||
onChange && onChange(newData);
|
|
||||||
setVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const callback = useCallback((v: Function) => {
|
|
||||||
console.log(v);
|
|
||||||
setforce({ force: v });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let listenner: (e: Event) => void;
|
|
||||||
if (rightPannelRef.current) {
|
|
||||||
listenner = () => {
|
|
||||||
force.force();
|
|
||||||
};
|
|
||||||
rightPannelRef.current.addEventListener("scroll", listenner);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (rightPannelRef.current) {
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
rightPannelRef.current.removeEventListener("scroll", listenner);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [force, rightPannelRef]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.formItemWrap}>
|
|
||||||
<div className={styles.formTitle}>表单控件</div>
|
|
||||||
<div className={styles.editForm}>
|
|
||||||
{formData.map((item: baseFormUnion, i: number) => {
|
|
||||||
let FormItem = BaseForm[item.type];
|
|
||||||
return (
|
|
||||||
<div className={styles.formItem} key={i}>
|
|
||||||
<div className={styles.disClick}>
|
|
||||||
<FormItem {...item} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.deleteWrap}>
|
|
||||||
<span
|
|
||||||
className={styles.operationBtn}
|
|
||||||
onClick={() => handleDelItem(item)}
|
|
||||||
>
|
|
||||||
<MinusCircleFilled />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.editWrap}>
|
|
||||||
<span
|
|
||||||
className={styles.operationBtn}
|
|
||||||
onClick={() => handleEditItem(item)}
|
|
||||||
>
|
|
||||||
<EditFilled />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className={styles.formAddWrap}>
|
|
||||||
<MyPopover
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<div className={styles.formTpl} style={{ color: "red" }}>
|
|
||||||
{formTpl.map((item, i) => {
|
|
||||||
let FormItem = BasePopoverForm[item.type];
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.formItem}
|
|
||||||
key={i}
|
|
||||||
onClick={() => handleAddItem(item)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={styles.disClick}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
overflow: "row",
|
|
||||||
marginTop: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormItem {...item} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{/* <a style={{color: 'red'}} onClick={() => setFormTplVisible(false)}>Close</a> */}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
directions={"LB"}
|
|
||||||
innerConstDomStyle={{ display: "block" }}
|
|
||||||
constDomStyle={{ display: "block" }}
|
|
||||||
callback={callback}
|
|
||||||
>
|
|
||||||
<Button style={{ width: "100%" }} block icon={<PlusOutlined />}>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</MyPopover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<EditorModal item={curItem} onSave={handleSaveItem} visible={visible} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(FormItems);
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
.formItemWrap {
|
|
||||||
.formTitle {
|
|
||||||
width: 56px;
|
|
||||||
height: 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: PingFangSC-Medium, PingFang SC;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #000000;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
.editForm {
|
|
||||||
text-align: left;
|
|
||||||
width: 251px;
|
|
||||||
.formItem {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 2px;
|
|
||||||
.common {
|
|
||||||
position: absolute;
|
|
||||||
top: 19px;
|
|
||||||
box-shadow: 0 0 20px #fff;
|
|
||||||
.operationBtn {
|
|
||||||
margin-right: 15px;
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.deleteWrap {
|
|
||||||
.common;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.editWrap {
|
|
||||||
.common;
|
|
||||||
right: -18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.formAddWrap {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #4a4a4a;
|
|
||||||
line-height: 20px;
|
|
||||||
background-color: #2f54eb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.formAddWrap {
|
|
||||||
.formTpl {
|
|
||||||
margin-top: 12px;
|
|
||||||
border-top: 1px dashed #ccc;
|
|
||||||
padding-top: 16px;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
.formItem {
|
|
||||||
button,
|
|
||||||
[type="button"] {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 4px 0px 0px 0px;
|
|
||||||
}
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
background-color: #4a4a4a;
|
|
||||||
cursor: pointer;
|
|
||||||
.disClick {
|
|
||||||
pointer-events: none;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
border-color: #2f54eb;
|
|
||||||
.addBtn {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.addBtn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
display: none;
|
|
||||||
padding: 3px 6px;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #2f54eb;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
import FormItems from "./FormItems";
|
|
||||||
export default FormItems;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.mutiText {
|
|
||||||
.iptWrap {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
display: flex;
|
|
||||||
.delBtn {
|
|
||||||
// font-size: 14px;
|
|
||||||
margin-left: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import React, { memo, useEffect } from "react";
|
|
||||||
import { Input, Button, Popconfirm } from "antd";
|
|
||||||
import { MinusCircleFilled } from "@ant-design/icons";
|
|
||||||
import styles from "./index.less";
|
|
||||||
import { TMutiTextDefaultType } from "../types";
|
|
||||||
|
|
||||||
type MultiTextProps = {
|
|
||||||
onChange?: (v: TMutiTextDefaultType) => void;
|
|
||||||
value?: TMutiTextDefaultType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(function MutiText(props: MultiTextProps) {
|
|
||||||
const { value, onChange } = props;
|
|
||||||
const handleAdd = () => {
|
|
||||||
onChange && onChange([...value!, "新增项目"]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDel = (index: number) => {
|
|
||||||
let newList = value!.filter((_item, i) => i !== index);
|
|
||||||
onChange && onChange(newList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (
|
|
||||||
index: number,
|
|
||||||
e: React.ChangeEvent<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
let newList = value!.map((item, i) =>
|
|
||||||
i === index ? e.target.value : item
|
|
||||||
);
|
|
||||||
onChange && onChange(newList);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
window["currentCates"] = value!;
|
|
||||||
return () => {
|
|
||||||
window["currentCates"] = null;
|
|
||||||
};
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.mutiText}>
|
|
||||||
{value && value.length ? (
|
|
||||||
value!.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}>
|
|
||||||
<MinusCircleFilled />
|
|
||||||
</span>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div className={styles.iptWrap}>
|
|
||||||
<Input />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{value && value.length < 3 && (
|
|
||||||
<div className={styles.iptWrap}>
|
|
||||||
<Button block onClick={handleAdd}>
|
|
||||||
添加项目
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.posIpt {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-right: -10px;
|
|
||||||
.posItem {
|
|
||||||
margin-right: 10px;
|
|
||||||
span {
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import React, { memo, useState, useEffect } from "react";
|
|
||||||
import { InputNumber } from "antd";
|
|
||||||
import styles from "./index.less";
|
|
||||||
import { TPosDefaultType, TPosItem } from "../types";
|
|
||||||
|
|
||||||
type PosProps = {
|
|
||||||
value?: TPosDefaultType;
|
|
||||||
onChange?: (v: TPosItem | string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(function Pos(props: PosProps) {
|
|
||||||
const { value, onChange } = props;
|
|
||||||
let _this: typeof Pos = Pos;
|
|
||||||
|
|
||||||
const handleChange = (index: number, v: TPosItem | string) => {
|
|
||||||
let arr: any = value || [];
|
|
||||||
arr[index] = v;
|
|
||||||
onChange && onChange(arr);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.posIpt}>
|
|
||||||
<div className={styles.posItem}>
|
|
||||||
<span>x: </span>
|
|
||||||
<InputNumber
|
|
||||||
defaultValue={value && value[0]}
|
|
||||||
onChange={handleChange.bind(_this, 0)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.posItem}>
|
|
||||||
<span>y: </span>
|
|
||||||
<InputNumber
|
|
||||||
defaultValue={value && value[1]}
|
|
||||||
onChange={handleChange.bind(_this, 1)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
:global(.editable-cell) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
padding: 5px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.editable-row) {
|
|
||||||
&:hover :global(.editable-cell-value-wrap) {
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 11px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:global([data-theme="dark"]) {
|
|
||||||
:global(.editable-row) {
|
|
||||||
&:hover {
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
border: 1px solid #434343;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.apiForm {
|
|
||||||
.formItem {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,449 +0,0 @@
|
|||||||
import React, {
|
|
||||||
useContext,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
memo,
|
|
||||||
RefObject
|
|
||||||
} from "react";
|
|
||||||
import { Table, Input, Button, Popconfirm, Form, Modal, Upload } from "antd";
|
|
||||||
import { ColumnsType } from "antd/lib/table";
|
|
||||||
import { uuid } from "@/utils/tool";
|
|
||||||
import XLSX from "xlsx";
|
|
||||||
// 下方样式主要为全局样式,暂时不可删
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
const EditableContext = React.createContext<any>(null);
|
|
||||||
|
|
||||||
interface Item {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
age: string;
|
|
||||||
address: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditableRowProps {
|
|
||||||
index: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
return (
|
|
||||||
<Form form={form} component={false}>
|
|
||||||
<EditableContext.Provider value={form}>
|
|
||||||
<tr {...props} />
|
|
||||||
</EditableContext.Provider>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface EditableCellProps {
|
|
||||||
title: React.ReactNode;
|
|
||||||
editable: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
dataIndex: string;
|
|
||||||
record: any;
|
|
||||||
handleSave: (record: Item) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditableCell: React.FC<EditableCellProps> = ({
|
|
||||||
title,
|
|
||||||
editable,
|
|
||||||
children,
|
|
||||||
dataIndex,
|
|
||||||
record,
|
|
||||||
handleSave,
|
|
||||||
...restProps
|
|
||||||
}) => {
|
|
||||||
const [editing, setEditing] = useState(false);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const form = useContext(EditableContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (editing) {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}
|
|
||||||
}, [editing]);
|
|
||||||
|
|
||||||
const toggleEdit = () => {
|
|
||||||
setEditing(!editing);
|
|
||||||
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
|
|
||||||
};
|
|
||||||
|
|
||||||
const save = async () => {
|
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
toggleEdit();
|
|
||||||
handleSave({ ...record, ...values });
|
|
||||||
} catch (errInfo) {
|
|
||||||
console.log("Save failed:", errInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let childNode = children;
|
|
||||||
|
|
||||||
if (editable) {
|
|
||||||
childNode = editing ? (
|
|
||||||
<Form.Item
|
|
||||||
style={{ margin: 0 }}
|
|
||||||
name={dataIndex}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: `${title} 是必填的.`
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
ref={(inputRef as unknown) as () => RefObject<HTMLInputElement>}
|
|
||||||
onPressEnter={save}
|
|
||||||
onBlur={save}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className="editable-cell-value-wrap"
|
|
||||||
style={{ paddingRight: 24 }}
|
|
||||||
onClick={toggleEdit}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <td {...restProps}>{childNode}</td>;
|
|
||||||
};
|
|
||||||
|
|
||||||
class EditableTable extends React.Component<any, any> {
|
|
||||||
columns: (
|
|
||||||
| {
|
|
||||||
title: string;
|
|
||||||
dataIndex: string;
|
|
||||||
width: string;
|
|
||||||
editable: boolean;
|
|
||||||
render?: undefined;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
title: string;
|
|
||||||
dataIndex: string;
|
|
||||||
render: (text: string, record: any) => JSX.Element | null;
|
|
||||||
width?: undefined;
|
|
||||||
editable?: undefined;
|
|
||||||
}
|
|
||||||
)[];
|
|
||||||
apiForm: {
|
|
||||||
api: string;
|
|
||||||
header: string;
|
|
||||||
dataField: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: any) {
|
|
||||||
super(props);
|
|
||||||
this.columns = [
|
|
||||||
{
|
|
||||||
title: "名字",
|
|
||||||
dataIndex: "name",
|
|
||||||
width: "180px",
|
|
||||||
editable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "值",
|
|
||||||
dataIndex: "value",
|
|
||||||
width: "120px",
|
|
||||||
editable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "操作",
|
|
||||||
dataIndex: "operation",
|
|
||||||
render: (text: string, record) =>
|
|
||||||
this.state.dataSource.length >= 1 ? (
|
|
||||||
<Popconfirm
|
|
||||||
title="Sure to delete?"
|
|
||||||
onConfirm={() => this.handleDelete(record.key)}
|
|
||||||
>
|
|
||||||
<Button type="link">删除</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.apiForm = {
|
|
||||||
api: "",
|
|
||||||
header: "",
|
|
||||||
dataField: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataSource =
|
|
||||||
props.data &&
|
|
||||||
props.data.map((item: any, i: number) => ({ key: i + "", ...item }));
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
dataSource: dataSource,
|
|
||||||
visible: false,
|
|
||||||
apiVisible: false,
|
|
||||||
apiResult: ""
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDelete = (key: string) => {
|
|
||||||
const dataSource = [...this.state.dataSource];
|
|
||||||
const newDataSource = dataSource.filter(item => item.key !== key);
|
|
||||||
this.setState({ dataSource: newDataSource });
|
|
||||||
this.props.onChange && this.props.onChange(newDataSource);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAdd = () => {
|
|
||||||
const { dataSource } = this.state;
|
|
||||||
const uid = uuid(8, 10);
|
|
||||||
const newData = {
|
|
||||||
key: uid,
|
|
||||||
name: `dooring ${dataSource.length + 1}`,
|
|
||||||
value: 32
|
|
||||||
};
|
|
||||||
const newDataSource = [...dataSource, newData];
|
|
||||||
this.setState({
|
|
||||||
dataSource: newDataSource
|
|
||||||
});
|
|
||||||
this.props.onChange && this.props.onChange(newDataSource);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSave = (row: any) => {
|
|
||||||
const newData = [...this.state.dataSource];
|
|
||||||
const index = newData.findIndex(item => row.key === item.key);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
...row
|
|
||||||
});
|
|
||||||
this.setState({ dataSource: newData });
|
|
||||||
this.props.onChange && this.props.onChange(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
showModal = () => {
|
|
||||||
this.setState({
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOk = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
|
||||||
this.setState({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCancel = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
|
||||||
this.setState({
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
showApiModal = () => {
|
|
||||||
this.setState({
|
|
||||||
apiVisible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAPIOk = () => {
|
|
||||||
const { dataField } = this.apiForm;
|
|
||||||
if (dataField) {
|
|
||||||
let data = this.state.apiResult[dataField];
|
|
||||||
if (data && data instanceof Array) {
|
|
||||||
data = data.map((item, i) => ({ key: i + "", ...item }));
|
|
||||||
this.setState({
|
|
||||||
dataSource: data
|
|
||||||
});
|
|
||||||
this.props.onChange && this.props.onChange(data);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
apiVisible: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAPICancel = () => {
|
|
||||||
this.setState({
|
|
||||||
apiVisible: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleApiField = (type: "api" | "header" | "dataField", v: string) => {
|
|
||||||
this.apiForm[type] = v;
|
|
||||||
};
|
|
||||||
|
|
||||||
getApiFn = () => {
|
|
||||||
console.log(this.apiForm);
|
|
||||||
const { api, header } = this.apiForm;
|
|
||||||
fetch(api, {
|
|
||||||
cache: "no-cache",
|
|
||||||
headers: Object.assign(
|
|
||||||
{ "content-type": "application/json" },
|
|
||||||
header ? JSON.parse(header) : {}
|
|
||||||
),
|
|
||||||
method: "GET",
|
|
||||||
mode: "cors"
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
this.setState({
|
|
||||||
apiResult: res
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { dataSource } = this.state;
|
|
||||||
const components = {
|
|
||||||
body: {
|
|
||||||
row: EditableRow,
|
|
||||||
cell: EditableCell
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const columns: ColumnsType<any> = this.columns.map(col => {
|
|
||||||
if (!col.editable) {
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...col,
|
|
||||||
onCell: record => ({
|
|
||||||
record,
|
|
||||||
editable: col.editable,
|
|
||||||
dataIndex: col.dataIndex,
|
|
||||||
title: col.title,
|
|
||||||
handleSave: this.handleSave
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const _this = this;
|
|
||||||
const props = {
|
|
||||||
name: "file",
|
|
||||||
// action: '',
|
|
||||||
showUploadList: false,
|
|
||||||
beforeUpload(file: File, fileList: Array<File>) {
|
|
||||||
// 解析并提取excel数据
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = function(e: any) {
|
|
||||||
let data = e.target.result;
|
|
||||||
let workbook = XLSX.read(data, { type: "binary" });
|
|
||||||
let sheetNames = workbook.SheetNames; // 工作表名称集合
|
|
||||||
let draftArr: any = {};
|
|
||||||
sheetNames.forEach(name => {
|
|
||||||
let worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
|
|
||||||
for (let key in worksheet) {
|
|
||||||
// v是读取单元格的原始值
|
|
||||||
if (key[0] !== "!") {
|
|
||||||
if (draftArr[key[0]]) {
|
|
||||||
draftArr[key[0]].push(worksheet[key].v);
|
|
||||||
} else {
|
|
||||||
draftArr[key[0]] = [worksheet[key].v];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let sourceData = Object.values(draftArr).map((item: any, i) => ({
|
|
||||||
key: i + "",
|
|
||||||
name: item[0],
|
|
||||||
value: item[1]
|
|
||||||
}));
|
|
||||||
_this.setState({
|
|
||||||
dataSource: sourceData
|
|
||||||
});
|
|
||||||
_this.props.onChange && _this.props.onChange(sourceData);
|
|
||||||
};
|
|
||||||
reader.readAsBinaryString(file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Button type="primary" onClick={this.showModal}>
|
|
||||||
编辑数据源
|
|
||||||
</Button>
|
|
||||||
<Modal
|
|
||||||
title="编辑数据源"
|
|
||||||
visible={this.state.visible}
|
|
||||||
onOk={this.handleOk}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={this.handleAdd}
|
|
||||||
type="primary"
|
|
||||||
style={{ marginBottom: 16, marginRight: 16 }}
|
|
||||||
>
|
|
||||||
添加行
|
|
||||||
</Button>
|
|
||||||
<Upload {...props}>
|
|
||||||
<Button type="primary" ghost style={{ marginRight: 16 }}>
|
|
||||||
导入Excel
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
<Button type="primary" ghost onClick={this.showApiModal}>
|
|
||||||
第三方API
|
|
||||||
</Button>
|
|
||||||
<Table
|
|
||||||
components={components}
|
|
||||||
rowClassName={() => "editable-row"}
|
|
||||||
bordered
|
|
||||||
dataSource={dataSource}
|
|
||||||
columns={columns}
|
|
||||||
pagination={{ pageSize: 50 }}
|
|
||||||
scroll={{ y: 240 }}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
title="配置api"
|
|
||||||
visible={this.state.apiVisible}
|
|
||||||
onOk={this.handleAPIOk}
|
|
||||||
onCancel={this.handleAPICancel}
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<div className={styles.apiForm}>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input
|
|
||||||
placeholder="请输入api地址"
|
|
||||||
onChange={e => this.handleApiField("api", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input.TextArea
|
|
||||||
placeholder="请输入头信息, 如{token: 123456}, 格式必须为json对象"
|
|
||||||
rows={4}
|
|
||||||
onChange={e => this.handleApiField("header", e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Button type="primary" onClick={this.getApiFn}>
|
|
||||||
发送请求
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{this.state.apiResult && (
|
|
||||||
<>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input.TextArea
|
|
||||||
rows={6}
|
|
||||||
value={JSON.stringify(this.state.apiResult, null, 4)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formItem}>
|
|
||||||
<Input
|
|
||||||
placeholder="设置数据源字段"
|
|
||||||
onChange={e =>
|
|
||||||
this.handleApiField("dataField", e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<p style={{ color: "red" }}>
|
|
||||||
数据源字段是接口返回的图表数据对应的字段, 必填,
|
|
||||||
否则无法正确导入数据
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(EditableTable);
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
:global(.ant-upload-select-picture-card i) {
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.ant-upload-select-picture-card .ant-upload-text) {
|
|
||||||
margin-top: 8px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatarUploader {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallBtn {
|
|
||||||
position: absolute;
|
|
||||||
left: 140px;
|
|
||||||
bottom: 56px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #2f54eb;
|
|
||||||
cursor: pointer;
|
|
||||||
border-bottom: 1px solid #2f54eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imgBox {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-height: 520px;
|
|
||||||
overflow: auto;
|
|
||||||
.imgItem {
|
|
||||||
position: relative;
|
|
||||||
margin-right: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
width: 320px;
|
|
||||||
max-height: 220px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
&:hover,
|
|
||||||
&.seleted {
|
|
||||||
.iconBtn {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconBtn {
|
|
||||||
position: absolute;
|
|
||||||
visibility: hidden;
|
|
||||||
top: 6px;
|
|
||||||
right: 10px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: rgb(8, 156, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,288 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Upload, Modal, message, Tabs, Result } from "antd";
|
|
||||||
import { PlusOutlined, CheckCircleFilled } from "@ant-design/icons";
|
|
||||||
import ImgCrop from "antd-img-crop";
|
|
||||||
import classnames from "classnames";
|
|
||||||
import {
|
|
||||||
UploadFile,
|
|
||||||
UploadChangeParam,
|
|
||||||
RcFile
|
|
||||||
} from "antd/lib/upload/interface";
|
|
||||||
import { isDev, unParams, uuid } from "@/utils/tool";
|
|
||||||
import req from "@/utils/req";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
// 维护图片分类映射
|
|
||||||
const wallCateName: any = {
|
|
||||||
photo: "照片",
|
|
||||||
bg: "背景",
|
|
||||||
chahua: "插画"
|
|
||||||
};
|
|
||||||
|
|
||||||
function getBase64(file: File | Blob) {
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => resolve(reader.result as string);
|
|
||||||
reader.onerror = error => reject(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PicturesWallType {
|
|
||||||
fileList?: UploadFile<any>[];
|
|
||||||
action?: string;
|
|
||||||
headers?: any;
|
|
||||||
withCredentials?: boolean;
|
|
||||||
maxLen?: number;
|
|
||||||
onChange?: (v: any) => void;
|
|
||||||
cropRate?: number | boolean;
|
|
||||||
isCrop?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PicturesWall extends React.Component<PicturesWallType> {
|
|
||||||
state = {
|
|
||||||
previewVisible: false,
|
|
||||||
previewImage: "",
|
|
||||||
wallModalVisible: false,
|
|
||||||
previewTitle: "",
|
|
||||||
imgBed: {
|
|
||||||
photo: [],
|
|
||||||
bg: [],
|
|
||||||
chahua: []
|
|
||||||
},
|
|
||||||
curSelectedImg: "",
|
|
||||||
fileList: this.props.fileList || []
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCancel = () => this.setState({ previewVisible: false });
|
|
||||||
|
|
||||||
handleModalCancel = () => this.setState({ wallModalVisible: false });
|
|
||||||
|
|
||||||
handlePreview = async (file: UploadFile<any>) => {
|
|
||||||
if (!file.url && !file.preview) {
|
|
||||||
file.preview = await getBase64(file.originFileObj!);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
previewImage: file.url || file.preview,
|
|
||||||
previewVisible: true,
|
|
||||||
previewTitle:
|
|
||||||
file.name || file.url!.substring(file.url!.lastIndexOf("/") + 1)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleWallSelect = (url: string) => {
|
|
||||||
this.setState({
|
|
||||||
wallModalVisible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleImgSelected = (url: string) => {
|
|
||||||
this.setState({
|
|
||||||
curSelectedImg: url
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleWallShow = () => {
|
|
||||||
this.setState({
|
|
||||||
wallModalVisible: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleModalOk = () => {
|
|
||||||
const fileList = [
|
|
||||||
{
|
|
||||||
uid: uuid(8, 16),
|
|
||||||
name: "h5-dooring图片库",
|
|
||||||
status: "done",
|
|
||||||
url: this.state.curSelectedImg
|
|
||||||
}
|
|
||||||
];
|
|
||||||
this.props.onChange && this.props.onChange(fileList);
|
|
||||||
this.setState({ fileList, wallModalVisible: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = ({ file, fileList }: UploadChangeParam<UploadFile<any>>) => {
|
|
||||||
this.setState({ fileList });
|
|
||||||
if (file.status === "done") {
|
|
||||||
const files = fileList.map(item => {
|
|
||||||
const { uid, name, status } = item;
|
|
||||||
const url = item.url || item.response.result.url;
|
|
||||||
return { uid, name, status, url };
|
|
||||||
});
|
|
||||||
this.props.onChange && this.props.onChange(files);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBeforeUpload = (file: RcFile) => {
|
|
||||||
const isJpgOrPng =
|
|
||||||
file.type === "image/jpeg" ||
|
|
||||||
file.type === "image/png" ||
|
|
||||||
file.type === "image/jpg" ||
|
|
||||||
file.type === "image/gif";
|
|
||||||
if (!isJpgOrPng) {
|
|
||||||
message.error("只能上传格式为jpeg/png/gif的图片");
|
|
||||||
}
|
|
||||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
|
||||||
if (!isLt2M) {
|
|
||||||
message.error("图片必须小于2MB!");
|
|
||||||
}
|
|
||||||
return isJpgOrPng && isLt2M;
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
// req.get(`/visible/bed/get?tid=${unParams(location.search)!.tid}`).then(res => {
|
|
||||||
// res &&
|
|
||||||
// this.setState({
|
|
||||||
// imgBed: res,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
previewVisible,
|
|
||||||
previewImage,
|
|
||||||
fileList,
|
|
||||||
previewTitle,
|
|
||||||
wallModalVisible,
|
|
||||||
imgBed,
|
|
||||||
curSelectedImg
|
|
||||||
} = this.state;
|
|
||||||
const {
|
|
||||||
action = isDev
|
|
||||||
? "http://192.168.1.8:3000/api/v0/files/upload/free"
|
|
||||||
: "你的服务器地址",
|
|
||||||
headers,
|
|
||||||
withCredentials = true,
|
|
||||||
maxLen = 1,
|
|
||||||
cropRate = 375 / 158,
|
|
||||||
isCrop
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const uploadButton = (
|
|
||||||
<div>
|
|
||||||
<PlusOutlined />
|
|
||||||
<div className="ant-upload-text">上传</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const cates = Object.keys(imgBed);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isCrop ? (
|
|
||||||
<ImgCrop
|
|
||||||
modalTitle="裁剪图片"
|
|
||||||
modalOk="确定"
|
|
||||||
modalCancel="取消"
|
|
||||||
rotate={true}
|
|
||||||
aspect={cropRate}
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
fileList={fileList}
|
|
||||||
onPreview={this.handlePreview}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
name="file"
|
|
||||||
listType="picture-card"
|
|
||||||
className={styles.avatarUploader}
|
|
||||||
action={action}
|
|
||||||
withCredentials={withCredentials}
|
|
||||||
headers={{
|
|
||||||
"x-requested-with": localStorage.getItem("user") || "",
|
|
||||||
authorization: localStorage.getItem("token") || "",
|
|
||||||
...headers
|
|
||||||
}}
|
|
||||||
beforeUpload={this.handleBeforeUpload}
|
|
||||||
>
|
|
||||||
{fileList.length >= maxLen ? null : uploadButton}
|
|
||||||
</Upload>
|
|
||||||
</ImgCrop>
|
|
||||||
) : (
|
|
||||||
<Upload
|
|
||||||
fileList={fileList}
|
|
||||||
onPreview={this.handlePreview}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
name="file"
|
|
||||||
listType="picture-card"
|
|
||||||
className={styles.avatarUploader}
|
|
||||||
action={action}
|
|
||||||
withCredentials={withCredentials}
|
|
||||||
headers={{
|
|
||||||
"x-requested-with": localStorage.getItem("user") || "",
|
|
||||||
authorization: localStorage.getItem("token") || "",
|
|
||||||
...headers
|
|
||||||
}}
|
|
||||||
beforeUpload={this.handleBeforeUpload}
|
|
||||||
>
|
|
||||||
{fileList.length >= maxLen ? null : uploadButton}
|
|
||||||
</Upload>
|
|
||||||
)}
|
|
||||||
<div className={styles.wallBtn} onClick={this.handleWallShow}>
|
|
||||||
图片库
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
visible={previewVisible}
|
|
||||||
title={previewTitle}
|
|
||||||
footer={null}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
>
|
|
||||||
<img alt="预览图片" style={{ width: "100%" }} src={previewImage} />
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
visible={wallModalVisible}
|
|
||||||
title="图片库"
|
|
||||||
okText="确定"
|
|
||||||
cancelText="取消"
|
|
||||||
width={860}
|
|
||||||
onCancel={this.handleModalCancel}
|
|
||||||
onOk={this.handleModalOk}
|
|
||||||
>
|
|
||||||
<Tabs
|
|
||||||
defaultActiveKey={cates[0]}
|
|
||||||
tabPosition="left"
|
|
||||||
style={{ height: 520 }}
|
|
||||||
>
|
|
||||||
{cates.map((item, i) => {
|
|
||||||
return (
|
|
||||||
<TabPane tab={wallCateName[item]} key={item}>
|
|
||||||
<div className={styles.imgBox}>
|
|
||||||
{(imgBed as any)[item] &&
|
|
||||||
(imgBed as any)[item].map((item: string, i: number) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
styles.imgItem,
|
|
||||||
curSelectedImg === item ? styles.seleted : ""
|
|
||||||
)}
|
|
||||||
key={i}
|
|
||||||
onClick={() => this.handleImgSelected(item)}
|
|
||||||
>
|
|
||||||
<img src={item} alt="趣谈前端-h5-dooring" />
|
|
||||||
<span className={styles.iconBtn}>
|
|
||||||
<CheckCircleFilled />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</TabPane>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<TabPane tab="更多" key="more">
|
|
||||||
<Result
|
|
||||||
status="500"
|
|
||||||
title="Dooring温馨提示"
|
|
||||||
subTitle="更多素材, 正在筹备中..."
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PicturesWall;
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
.avatarUploader > :global(.ant-upload) {
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import React, { useState, useEffect, memo } from "react";
|
|
||||||
import req from "@/utils/req";
|
|
||||||
import BraftEditor from "braft-editor";
|
|
||||||
import "braft-editor/dist/index.css";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
const controls = [
|
|
||||||
{
|
|
||||||
key: "bold",
|
|
||||||
text: <b>加粗</b>
|
|
||||||
},
|
|
||||||
"undo",
|
|
||||||
"redo",
|
|
||||||
"emoji",
|
|
||||||
"list-ul",
|
|
||||||
"list-ol",
|
|
||||||
"blockquote",
|
|
||||||
"text-align",
|
|
||||||
"font-size",
|
|
||||||
"line-height",
|
|
||||||
"letter-spacing",
|
|
||||||
"text-color",
|
|
||||||
"italic",
|
|
||||||
"underline",
|
|
||||||
"link",
|
|
||||||
"media"
|
|
||||||
];
|
|
||||||
|
|
||||||
export default memo(function XEditor(props: any) {
|
|
||||||
const { value, onChange } = props;
|
|
||||||
const [editorState, setEditorState] = useState(
|
|
||||||
BraftEditor.createEditorState(value)
|
|
||||||
);
|
|
||||||
|
|
||||||
const myUploadFn = (param: any) => {
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append("file", param.file);
|
|
||||||
|
|
||||||
req
|
|
||||||
.post("xxxx", fd, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data"
|
|
||||||
},
|
|
||||||
onUploadProgress: function(event) {
|
|
||||||
// 上传进度发生变化时调用param.progress
|
|
||||||
console.log((event.loaded / event.total) * 100);
|
|
||||||
param.progress((event.loaded / event.total) * 100);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res: any) => {
|
|
||||||
// 上传成功后调用param.success并传入上传后的文件地址
|
|
||||||
param.success({
|
|
||||||
url: res.url,
|
|
||||||
meta: {
|
|
||||||
id: Date.now(),
|
|
||||||
title: res.filename,
|
|
||||||
alt: "趣谈前端"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
param.error({
|
|
||||||
msg: "上传失败."
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitContent = () => {
|
|
||||||
const htmlContent = editorState.toHTML();
|
|
||||||
onChange && onChange(htmlContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditorChange = editorState => {
|
|
||||||
setEditorState(editorState);
|
|
||||||
if (onChange) {
|
|
||||||
const htmlContent = editorState.toHTML();
|
|
||||||
onChange(htmlContent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const htmlContent = value || "";
|
|
||||||
setEditorState(BraftEditor.createEditorState(htmlContent));
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<BraftEditor
|
|
||||||
value={editorState}
|
|
||||||
controls={controls}
|
|
||||||
onChange={handleEditorChange}
|
|
||||||
onSave={submitContent}
|
|
||||||
media={{ uploadFn: myUploadFn }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,243 +0,0 @@
|
|||||||
////////////////////
|
|
||||||
export interface IUploadConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Upload";
|
|
||||||
isCrop?: boolean;
|
|
||||||
cropRate?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TUploadDefaultType = Array<{
|
|
||||||
uid: string;
|
|
||||||
name: string;
|
|
||||||
status: string;
|
|
||||||
url: string;
|
|
||||||
}>;
|
|
||||||
/////////////////
|
|
||||||
export interface ITextConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Text";
|
|
||||||
}
|
|
||||||
export type TTextDefaultType = string;
|
|
||||||
////////////////////////
|
|
||||||
export interface ITextAreaConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "TextArea";
|
|
||||||
}
|
|
||||||
export type TTextAreaDefaultType = string;
|
|
||||||
////////////////////////////
|
|
||||||
export interface INumberConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Number";
|
|
||||||
range?: [number, number];
|
|
||||||
step?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TNumberDefaultType = number;
|
|
||||||
|
|
||||||
///////////////////
|
|
||||||
export interface IDataListConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "DataList";
|
|
||||||
cropRate: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TDataListDefaultTypeItem = {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
desc: string;
|
|
||||||
link: string;
|
|
||||||
type?: number;
|
|
||||||
imgUrl: TUploadDefaultType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TDataListDefaultType = Array<TDataListDefaultTypeItem>;
|
|
||||||
|
|
||||||
////////////////////
|
|
||||||
export interface IColorConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Color";
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TColorDefaultType = string;
|
|
||||||
|
|
||||||
/////////////////
|
|
||||||
export interface IRichTextConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "RichText";
|
|
||||||
}
|
|
||||||
export type TRichTextDefaultType = string;
|
|
||||||
|
|
||||||
export interface IMutiTextConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "MutiText";
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TMutiTextDefaultType = Array<string>;
|
|
||||||
|
|
||||||
/////////////////////////////////
|
|
||||||
export interface ISelectConfigType<KeyType> {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Select";
|
|
||||||
range: Array<{
|
|
||||||
key: KeyType;
|
|
||||||
text: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
export type TSelectDefaultType<KeyType> = KeyType;
|
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
export interface IRadioConfigType<KeyType> {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Radio";
|
|
||||||
range: Array<{
|
|
||||||
key: KeyType;
|
|
||||||
text: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
export type TRadioDefaultType<KeyType> = KeyType;
|
|
||||||
|
|
||||||
///////////////
|
|
||||||
|
|
||||||
export interface ISwitchConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Switch";
|
|
||||||
}
|
|
||||||
export type TSwitchDefaultType = boolean;
|
|
||||||
|
|
||||||
/////////////////////////////
|
|
||||||
export interface ICardPickerConfigType<T> {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "CardPicker";
|
|
||||||
icons: Array<T>;
|
|
||||||
}
|
|
||||||
export type TCardPickerDefaultType<T> = T;
|
|
||||||
|
|
||||||
/////////////
|
|
||||||
|
|
||||||
export interface ITableConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Table";
|
|
||||||
}
|
|
||||||
export type TTableDefaultType = Array<{
|
|
||||||
name: string;
|
|
||||||
value: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// position input control
|
|
||||||
export interface IPosConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "Pos";
|
|
||||||
placeObj: {
|
|
||||||
text: string;
|
|
||||||
link: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TPosItem = number | undefined;
|
|
||||||
|
|
||||||
export type TPosDefaultType = [TPosItem, TPosItem];
|
|
||||||
|
|
||||||
//////////////////
|
|
||||||
export interface IFormItemsConfigType {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "FormItems";
|
|
||||||
}
|
|
||||||
|
|
||||||
//0---------baseform
|
|
||||||
export type baseFormOptionsType = {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormTextTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Text";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormTextTipTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MyTextTip";
|
|
||||||
label: string;
|
|
||||||
color: string;
|
|
||||||
fontSize: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormNumberTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Number";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormTextAreaTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Textarea";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormMyRadioTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MyRadio";
|
|
||||||
label: string;
|
|
||||||
options: baseFormOptionsType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormMyCheckboxTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MyCheckbox";
|
|
||||||
label: string;
|
|
||||||
options: baseFormOptionsType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormMySelectTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "MySelect";
|
|
||||||
label: string;
|
|
||||||
options: baseFormOptionsType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormDateTpl = {
|
|
||||||
id: string;
|
|
||||||
type: "Date";
|
|
||||||
label: string;
|
|
||||||
placeholder: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type baseFormUnion =
|
|
||||||
| baseFormTextTpl
|
|
||||||
| baseFormTextTipTpl
|
|
||||||
| baseFormNumberTpl
|
|
||||||
| baseFormTextAreaTpl
|
|
||||||
| baseFormMyRadioTpl
|
|
||||||
| baseFormMyCheckboxTpl
|
|
||||||
| baseFormMySelectTpl
|
|
||||||
| baseFormDateTpl;
|
|
||||||
export type baseFormUnionType =
|
|
||||||
| baseFormTextTpl["type"]
|
|
||||||
| baseFormTextTipTpl["type"]
|
|
||||||
| baseFormNumberTpl["type"]
|
|
||||||
| baseFormTextAreaTpl["type"]
|
|
||||||
| baseFormMyRadioTpl["type"]
|
|
||||||
| baseFormMyCheckboxTpl["type"]
|
|
||||||
| baseFormMySelectTpl["type"]
|
|
||||||
| baseFormDateTpl["type"];
|
|
||||||
|
|
||||||
export type TFormItemsDefaultType = Array<baseFormUnion>;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default () => <div className="rotate-animate">Dooring</div>;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
export default () => <div className="rotate-animate">Dooring</div>;
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
.takeCat {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.imgWrap {
|
|
||||||
width: 160px;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import React, { memo } from "react";
|
|
||||||
import { Button, Popover } from "antd";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
text: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
///这组件写的有问题 popover会重定位
|
|
||||||
const content = (
|
|
||||||
<div className={styles.imgWrap}>
|
|
||||||
<img
|
|
||||||
src={`http://h5.dooring.cn/uploads/WechatIMG2_17969ccfe40.jpeg`}
|
|
||||||
alt="sponsorship"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default memo(function ZanPao(props: IProps) {
|
|
||||||
const {
|
|
||||||
text = (
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
danger
|
|
||||||
style={{ background: "red !important" }}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
支持开源, 请作者喝茶~
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
} = props;
|
|
||||||
return (
|
|
||||||
<div className={styles.takeCat}>
|
|
||||||
<Popover placement="top" title={null} content={content} trigger="hover">
|
|
||||||
{text}
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
.takeCat {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.imgWrap {
|
|
||||||
width: 160px;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import React, { memo } from "react";
|
|
||||||
import { Button, Popover } from "antd";
|
|
||||||
import styles from "./index.less";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
text: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
///这组件写的有问题 popover会重定位
|
|
||||||
const content = (
|
|
||||||
<div className={styles.imgWrap}>
|
|
||||||
<img
|
|
||||||
src={`http://h5.dooring.cn/uploads/WechatIMG2_17969ccfe40.jpeg`}
|
|
||||||
alt="sponsorship"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default memo(function ZanPao(props: IProps) {
|
|
||||||
const {
|
|
||||||
text = (
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
danger
|
|
||||||
style={{ background: "red !important" }}
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
支持开源, 请作者喝茶~
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
} = props;
|
|
||||||
return (
|
|
||||||
<div className={styles.takeCat}>
|
|
||||||
<Popover placement="top" title={null} content={content} trigger="hover">
|
|
||||||
{text}
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Loading…
x
Reference in New Issue
Block a user