mirror of
https://github.com/MrXujiang/h5-Dooring.git
synced 2025-12-10 16:52:49 +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