mirror of
https://github.com/MrXujiang/h5-Dooring.git
synced 2026-04-05 12:10:14 +00:00
fix
This commit is contained in:
parent
81b9f69953
commit
fb1964ecdc
12
src/components/Calibration的副本 2/index.less
Normal file
12
src/components/Calibration的副本 2/index.less
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/components/Calibration的副本 2/index.tsx
Normal file
123
src/components/Calibration的副本 2/index.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
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>;
|
||||||
|
}
|
||||||
12
src/components/Calibration的副本/index.less
Normal file
12
src/components/Calibration的副本/index.less
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/components/Calibration的副本/index.tsx
Normal file
123
src/components/Calibration的副本/index.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
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>;
|
||||||
|
}
|
||||||
32
src/components/ErrorBundaries的副本 2/index.tsx
Normal file
32
src/components/ErrorBundaries的副本 2/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
32
src/components/ErrorBundaries的副本/index.tsx
Normal file
32
src/components/ErrorBundaries的副本/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
16
src/components/FormComponents的副本 2/CardPicker/index.less
Normal file
16
src/components/FormComponents的副本 2/CardPicker/index.less
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/components/FormComponents的副本 2/CardPicker/index.tsx
Normal file
49
src/components/FormComponents的副本 2/CardPicker/index.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
89
src/components/FormComponents的副本 2/Color/index.tsx
Normal file
89
src/components/FormComponents的副本 2/Color/index.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
127
src/components/FormComponents的副本 2/DataList/editorModal.tsx
Normal file
127
src/components/FormComponents的副本 2/DataList/editorModal.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
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);
|
||||||
46
src/components/FormComponents的副本 2/DataList/index.less
Normal file
46
src/components/FormComponents的副本 2/DataList/index.less
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
263
src/components/FormComponents的副本 2/DataList/index.tsx
Normal file
263
src/components/FormComponents的副本 2/DataList/index.tsx
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
137
src/components/FormComponents的副本 2/FormItems/EditorModal.tsx
Normal file
137
src/components/FormComponents的副本 2/FormItems/EditorModal.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
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);
|
||||||
215
src/components/FormComponents的副本 2/FormItems/FormItems.tsx
Normal file
215
src/components/FormComponents的副本 2/FormItems/FormItems.tsx
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
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);
|
||||||
88
src/components/FormComponents的副本 2/FormItems/formItems.less
Normal file
88
src/components/FormComponents的副本 2/FormItems/formItems.less
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/components/FormComponents的副本 2/FormItems/index.tsx
Normal file
2
src/components/FormComponents的副本 2/FormItems/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import FormItems from "./FormItems";
|
||||||
|
export default FormItems;
|
||||||
12
src/components/FormComponents的副本 2/MutiText/index.less
Normal file
12
src/components/FormComponents的副本 2/MutiText/index.less
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.mutiText {
|
||||||
|
.iptWrap {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
.delBtn {
|
||||||
|
// font-size: 14px;
|
||||||
|
margin-left: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/components/FormComponents的副本 2/MutiText/index.tsx
Normal file
74
src/components/FormComponents的副本 2/MutiText/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
11
src/components/FormComponents的副本 2/Pos/index.less
Normal file
11
src/components/FormComponents的副本 2/Pos/index.less
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.posIpt {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-right: -10px;
|
||||||
|
.posItem {
|
||||||
|
margin-right: 10px;
|
||||||
|
span {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/components/FormComponents的副本 2/Pos/index.tsx
Normal file
39
src/components/FormComponents的副本 2/Pos/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
32
src/components/FormComponents的副本 2/Table/index.less
Normal file
32
src/components/FormComponents的副本 2/Table/index.less
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
449
src/components/FormComponents的副本 2/Table/index.tsx
Normal file
449
src/components/FormComponents的副本 2/Table/index.tsx
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
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);
|
||||||
58
src/components/FormComponents的副本 2/Upload/index.less
Normal file
58
src/components/FormComponents的副本 2/Upload/index.less
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
: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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
288
src/components/FormComponents的副本 2/Upload/index.tsx
Normal file
288
src/components/FormComponents的副本 2/Upload/index.tsx
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
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;
|
||||||
4
src/components/FormComponents的副本 2/XEditor/index.less
Normal file
4
src/components/FormComponents的副本 2/XEditor/index.less
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.avatarUploader > :global(.ant-upload) {
|
||||||
|
width: 128px;
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
94
src/components/FormComponents的副本 2/XEditor/index.tsx
Normal file
94
src/components/FormComponents的副本 2/XEditor/index.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
243
src/components/FormComponents的副本 2/types.ts
Normal file
243
src/components/FormComponents的副本 2/types.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
////////////////////
|
||||||
|
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>;
|
||||||
16
src/components/FormComponents的副本/CardPicker/index.less
Normal file
16
src/components/FormComponents的副本/CardPicker/index.less
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/components/FormComponents的副本/CardPicker/index.tsx
Normal file
49
src/components/FormComponents的副本/CardPicker/index.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
89
src/components/FormComponents的副本/Color/index.tsx
Normal file
89
src/components/FormComponents的副本/Color/index.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
127
src/components/FormComponents的副本/DataList/editorModal.tsx
Normal file
127
src/components/FormComponents的副本/DataList/editorModal.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
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);
|
||||||
46
src/components/FormComponents的副本/DataList/index.less
Normal file
46
src/components/FormComponents的副本/DataList/index.less
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
263
src/components/FormComponents的副本/DataList/index.tsx
Normal file
263
src/components/FormComponents的副本/DataList/index.tsx
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
137
src/components/FormComponents的副本/FormItems/EditorModal.tsx
Normal file
137
src/components/FormComponents的副本/FormItems/EditorModal.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
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);
|
||||||
215
src/components/FormComponents的副本/FormItems/FormItems.tsx
Normal file
215
src/components/FormComponents的副本/FormItems/FormItems.tsx
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
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);
|
||||||
88
src/components/FormComponents的副本/FormItems/formItems.less
Normal file
88
src/components/FormComponents的副本/FormItems/formItems.less
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/components/FormComponents的副本/FormItems/index.tsx
Normal file
2
src/components/FormComponents的副本/FormItems/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import FormItems from "./FormItems";
|
||||||
|
export default FormItems;
|
||||||
12
src/components/FormComponents的副本/MutiText/index.less
Normal file
12
src/components/FormComponents的副本/MutiText/index.less
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.mutiText {
|
||||||
|
.iptWrap {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
.delBtn {
|
||||||
|
// font-size: 14px;
|
||||||
|
margin-left: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/components/FormComponents的副本/MutiText/index.tsx
Normal file
74
src/components/FormComponents的副本/MutiText/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
11
src/components/FormComponents的副本/Pos/index.less
Normal file
11
src/components/FormComponents的副本/Pos/index.less
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.posIpt {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-right: -10px;
|
||||||
|
.posItem {
|
||||||
|
margin-right: 10px;
|
||||||
|
span {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/components/FormComponents的副本/Pos/index.tsx
Normal file
39
src/components/FormComponents的副本/Pos/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
32
src/components/FormComponents的副本/Table/index.less
Normal file
32
src/components/FormComponents的副本/Table/index.less
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
449
src/components/FormComponents的副本/Table/index.tsx
Normal file
449
src/components/FormComponents的副本/Table/index.tsx
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
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);
|
||||||
58
src/components/FormComponents的副本/Upload/index.less
Normal file
58
src/components/FormComponents的副本/Upload/index.less
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
: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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
288
src/components/FormComponents的副本/Upload/index.tsx
Normal file
288
src/components/FormComponents的副本/Upload/index.tsx
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
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;
|
||||||
4
src/components/FormComponents的副本/XEditor/index.less
Normal file
4
src/components/FormComponents的副本/XEditor/index.less
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.avatarUploader > :global(.ant-upload) {
|
||||||
|
width: 128px;
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
94
src/components/FormComponents的副本/XEditor/index.tsx
Normal file
94
src/components/FormComponents的副本/XEditor/index.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
243
src/components/FormComponents的副本/types.ts
Normal file
243
src/components/FormComponents的副本/types.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
////////////////////
|
||||||
|
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>;
|
||||||
3
src/components/LoadingCp的副本 2/index.tsx
Normal file
3
src/components/LoadingCp的副本 2/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default () => <div className="rotate-animate">Dooring</div>;
|
||||||
3
src/components/LoadingCp的副本/index.tsx
Normal file
3
src/components/LoadingCp的副本/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default () => <div className="rotate-animate">Dooring</div>;
|
||||||
9
src/components/Zan的副本 2/index.less
Normal file
9
src/components/Zan的副本 2/index.less
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.takeCat {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.imgWrap {
|
||||||
|
width: 160px;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/components/Zan的副本 2/index.tsx
Normal file
39
src/components/Zan的副本 2/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
9
src/components/Zan的副本/index.less
Normal file
9
src/components/Zan的副本/index.less
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.takeCat {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.imgWrap {
|
||||||
|
width: 160px;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/components/Zan的副本/index.tsx
Normal file
39
src/components/Zan的副本/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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