diff --git a/src/components/Calibration的副本 2/index.less b/src/components/Calibration的副本 2/index.less new file mode 100644 index 0000000..6b8dc61 --- /dev/null +++ b/src/components/Calibration的副本 2/index.less @@ -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; + } +} diff --git a/src/components/Calibration的副本 2/index.tsx b/src/components/Calibration的副本 2/index.tsx new file mode 100644 index 0000000..dfa4f0b --- /dev/null +++ b/src/components/Calibration的副本 2/index.tsx @@ -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({ + width: 0, + height: 0 + }); + const calibrationRef = useRef(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
; +} diff --git a/src/components/Calibration的副本/index.less b/src/components/Calibration的副本/index.less new file mode 100644 index 0000000..6b8dc61 --- /dev/null +++ b/src/components/Calibration的副本/index.less @@ -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; + } +} diff --git a/src/components/Calibration的副本/index.tsx b/src/components/Calibration的副本/index.tsx new file mode 100644 index 0000000..dfa4f0b --- /dev/null +++ b/src/components/Calibration的副本/index.tsx @@ -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({ + width: 0, + height: 0 + }); + const calibrationRef = useRef(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
; +} diff --git a/src/components/ErrorBundaries的副本 2/index.tsx b/src/components/ErrorBundaries的副本 2/index.tsx new file mode 100644 index 0000000..febfa22 --- /dev/null +++ b/src/components/ErrorBundaries的副本 2/index.tsx @@ -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

Something went wrong.

; + } + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/components/ErrorBundaries的副本/index.tsx b/src/components/ErrorBundaries的副本/index.tsx new file mode 100644 index 0000000..febfa22 --- /dev/null +++ b/src/components/ErrorBundaries的副本/index.tsx @@ -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

Something went wrong.

; + } + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/components/FormComponents的副本 2/CardPicker/index.less b/src/components/FormComponents的副本 2/CardPicker/index.less new file mode 100644 index 0000000..c67b749 --- /dev/null +++ b/src/components/FormComponents的副本 2/CardPicker/index.less @@ -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; + } + } +} diff --git a/src/components/FormComponents的副本 2/CardPicker/index.tsx b/src/components/FormComponents的副本 2/CardPicker/index.tsx new file mode 100644 index 0000000..6939a4c --- /dev/null +++ b/src/components/FormComponents的副本 2/CardPicker/index.tsx @@ -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, "type" | "key" | "name"> { + onChange?: (v: string) => void; + type: IconTypes; +} + +export default memo((props: CardPickerType) => { + const { type, icons, onChange } = props; + const [selected, setSelected] = useState(type); + + const handlePicker = (v: IconTypes) => { + if (onChange) { + onChange(v); + return; + } + setSelected(v); + }; + + useEffect(() => { + setSelected(type); + }, [type]); + + return ( +
+ {icons.map((item, i) => { + return ( + handlePicker(item)} + key={i} + > + + + ); + })} +
+ ); +}); diff --git a/src/components/FormComponents的副本 2/Color/index.tsx b/src/components/FormComponents的副本 2/Color/index.tsx new file mode 100644 index 0000000..97df2f5 --- /dev/null +++ b/src/components/FormComponents的副本 2/Color/index.tsx @@ -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 { + 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 ( +
+
+
+
+ {this.state.displayColorPicker ? ( + +
+ +
+
+ + ) : null} +
+ ); + } +} + +export default colorPicker; diff --git a/src/components/FormComponents的副本 2/DataList/editorModal.tsx b/src/components/FormComponents的副本 2/DataList/editorModal.tsx new file mode 100644 index 0000000..e562b18 --- /dev/null +++ b/src/components/FormComponents的副本 2/DataList/editorModal.tsx @@ -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) => void) + | undefined; + item?: TDataListDefaultTypeItem; + onSave: Function; + cropRate: number; +}; + +const EditorModal: FC = 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 && ( + handleOk()}> + 确定 + + } + > +
+ + + + + + + + + + {!!window["currentCates"] && ( + + + + )} + + + + +
+
+ )} + + ); +}; + +export default memo(EditorModal); diff --git a/src/components/FormComponents的副本 2/DataList/index.less b/src/components/FormComponents的副本 2/DataList/index.less new file mode 100644 index 0000000..c6e60b5 --- /dev/null +++ b/src/components/FormComponents的副本 2/DataList/index.less @@ -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; + } + } + } +} diff --git a/src/components/FormComponents的副本 2/DataList/index.tsx b/src/components/FormComponents的副本 2/DataList/index.tsx new file mode 100644 index 0000000..b54af01 --- /dev/null +++ b/src/components/FormComponents的副本 2/DataList/index.tsx @@ -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( + // 整个列表项作为跟随拖动的影像 +
+
{title}
+
{desc}
+
+ onEdit()}> + + + onDel()}> + + + {connectDragSource( + + + + ) // 拖动图标作为 Drag 对象 + } +
+
+ ) + ); +} + +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 = { + // 拖动开始时,返回描述 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 = { + 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(); + + 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( +
+ {!!(list && list.length) && + list.map((item, i) => ( + handleDel(item.id)} + onEdit={() => handleEdit(item)} + key={i} + id={`${item.id}`} + find={find} + move={move} + /> + ))} +
+ +
+ +
+ ); +}; + +const DndList = DropTarget(type, {}, connect => ({ + connectDropTarget: connect.dropTarget() +}))(List); + +// 将 HTMLBackend 作为参数传给 DragDropContext +export default memo((props: DataListMemo) => { + return ( + + + + ); +}); diff --git a/src/components/FormComponents的副本 2/FormItems/EditorModal.tsx b/src/components/FormComponents的副本 2/FormItems/EditorModal.tsx new file mode 100644 index 0000000..f8928d7 --- /dev/null +++ b/src/components/FormComponents的副本 2/FormItems/EditorModal.tsx @@ -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 = 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 && ( + + +
+ } + forceRender + visible={visible} + onOk={handleOk} + closable={false} + > +
+ { + + } + {!!item.label && ( + + + + )} + {!!item.fontSize && ( + + + + )} + {!!item.color && ( + + + + )} + {!!item.placeholder && ( + + + + )} + {!!item.options && ( + + + + )} +
+ + )} + + ); +}; + +export default memo(EditorModal); diff --git a/src/components/FormComponents的副本 2/FormItems/FormItems.tsx b/src/components/FormComponents的副本 2/FormItems/FormItems.tsx new file mode 100644 index 0000000..b2afd06 --- /dev/null +++ b/src/components/FormComponents的副本 2/FormItems/FormItems.tsx @@ -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; +} + +const FormItems = (props: FormItemsProps) => { + const { formList, onChange, rightPannelRef } = props; + const [formData, setFormData] = useState( + formList || [] + ); + const [visible, setVisible] = useState(false); + const [curItem, setCurItem] = useState(); + 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 ( +
+
表单控件
+
+ {formData.map((item: baseFormUnion, i: number) => { + let FormItem = BaseForm[item.type]; + return ( +
+
+ +
+
+ handleDelItem(item)} + > + + +
+
+ handleEditItem(item)} + > + + +
+
+ ); + })} +
+ +
+ {formTpl.map((item, i) => { + let FormItem = BasePopoverForm[item.type]; + return ( +
handleAddItem(item)} + > +
+ +
+
+ ); + })} +
+ {/* setFormTplVisible(false)}>Close */} + + } + directions={"LB"} + innerConstDomStyle={{ display: "block" }} + constDomStyle={{ display: "block" }} + callback={callback} + > + +
+
+
+ + +
+ ); +}; + +export default memo(FormItems); diff --git a/src/components/FormComponents的副本 2/FormItems/formItems.less b/src/components/FormComponents的副本 2/FormItems/formItems.less new file mode 100644 index 0000000..39af471 --- /dev/null +++ b/src/components/FormComponents的副本 2/FormItems/formItems.less @@ -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; + } + } + } + } +} diff --git a/src/components/FormComponents的副本 2/FormItems/index.tsx b/src/components/FormComponents的副本 2/FormItems/index.tsx new file mode 100644 index 0000000..cfb6c41 --- /dev/null +++ b/src/components/FormComponents的副本 2/FormItems/index.tsx @@ -0,0 +1,2 @@ +import FormItems from "./FormItems"; +export default FormItems; diff --git a/src/components/FormComponents的副本 2/MutiText/index.less b/src/components/FormComponents的副本 2/MutiText/index.less new file mode 100644 index 0000000..b2d6716 --- /dev/null +++ b/src/components/FormComponents的副本 2/MutiText/index.less @@ -0,0 +1,12 @@ +.mutiText { + .iptWrap { + margin-bottom: 12px; + display: flex; + .delBtn { + // font-size: 14px; + margin-left: 12px; + cursor: pointer; + align-self: center; + } + } +} diff --git a/src/components/FormComponents的副本 2/MutiText/index.tsx b/src/components/FormComponents的副本 2/MutiText/index.tsx new file mode 100644 index 0000000..48810d0 --- /dev/null +++ b/src/components/FormComponents的副本 2/MutiText/index.tsx @@ -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 + ) => { + 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 ( +
+ {value && value.length ? ( + value!.map((item, i) => { + return ( +
+ handleChange(i, e)} /> + handleDel(i)} + placement="leftTop" + okText="确定" + cancelText="取消" + > + + + + +
+ ); + }) + ) : ( +
+ +
+ )} + {value && value.length < 3 && ( +
+ +
+ )} +
+ ); +}); diff --git a/src/components/FormComponents的副本 2/Pos/index.less b/src/components/FormComponents的副本 2/Pos/index.less new file mode 100644 index 0000000..504c546 --- /dev/null +++ b/src/components/FormComponents的副本 2/Pos/index.less @@ -0,0 +1,11 @@ +.posIpt { + display: flex; + justify-content: flex-end; + margin-right: -10px; + .posItem { + margin-right: 10px; + span { + margin-right: 3px; + } + } +} diff --git a/src/components/FormComponents的副本 2/Pos/index.tsx b/src/components/FormComponents的副本 2/Pos/index.tsx new file mode 100644 index 0000000..222de19 --- /dev/null +++ b/src/components/FormComponents的副本 2/Pos/index.tsx @@ -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 ( +
+
+ x: + +
+
+ y: + +
+
+ ); +}); diff --git a/src/components/FormComponents的副本 2/Table/index.less b/src/components/FormComponents的副本 2/Table/index.less new file mode 100644 index 0000000..02aa16b --- /dev/null +++ b/src/components/FormComponents的副本 2/Table/index.less @@ -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; + } +} diff --git a/src/components/FormComponents的副本 2/Table/index.tsx b/src/components/FormComponents的副本 2/Table/index.tsx new file mode 100644 index 0000000..fdc7274 --- /dev/null +++ b/src/components/FormComponents的副本 2/Table/index.tsx @@ -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(null); + +interface Item { + key: string; + name: string; + age: string; + address: string; +} + +interface EditableRowProps { + index: number; +} + +const EditableRow: React.FC = ({ index, ...props }) => { + const [form] = Form.useForm(); + return ( +
+ + + +
+ ); +}; + +interface EditableCellProps { + title: React.ReactNode; + editable: boolean; + children: React.ReactNode; + dataIndex: string; + record: any; + handleSave: (record: Item) => void; +} + +const EditableCell: React.FC = ({ + title, + editable, + children, + dataIndex, + record, + handleSave, + ...restProps +}) => { + const [editing, setEditing] = useState(false); + const inputRef = useRef(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 ? ( + + RefObject} + onPressEnter={save} + onBlur={save} + /> + + ) : ( +
+ {children} +
+ ); + } + + return {childNode}; +}; + +class EditableTable extends React.Component { + 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 ? ( + this.handleDelete(record.key)} + > + + + ) : 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) => { + this.setState({ + visible: false + }); + }; + + handleCancel = (e: React.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 = 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) { + // 解析并提取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 ( +
+ + + + + + + + "editable-row"} + bordered + dataSource={dataSource} + columns={columns} + pagination={{ pageSize: 50 }} + scroll={{ y: 240 }} + /> + + +
+
+ this.handleApiField("api", e.target.value)} + /> +
+
+ this.handleApiField("header", e.target.value)} + /> +
+
+ +
+ {this.state.apiResult && ( + <> +
+ +
+
+ + this.handleApiField("dataField", e.target.value) + } + /> +

+ 数据源字段是接口返回的图表数据对应的字段, 必填, + 否则无法正确导入数据 +

+
+ + )} +
+
+ + ); + } +} + +export default memo(EditableTable); diff --git a/src/components/FormComponents的副本 2/Upload/index.less b/src/components/FormComponents的副本 2/Upload/index.less new file mode 100644 index 0000000..8f00bfc --- /dev/null +++ b/src/components/FormComponents的副本 2/Upload/index.less @@ -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); + } + } +} diff --git a/src/components/FormComponents的副本 2/Upload/index.tsx b/src/components/FormComponents的副本 2/Upload/index.tsx new file mode 100644 index 0000000..ed5f4f1 --- /dev/null +++ b/src/components/FormComponents的副本 2/Upload/index.tsx @@ -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((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[]; + action?: string; + headers?: any; + withCredentials?: boolean; + maxLen?: number; + onChange?: (v: any) => void; + cropRate?: number | boolean; + isCrop?: boolean; +} + +class PicturesWall extends React.Component { + 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) => { + 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>) => { + 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 = ( +
+ +
上传
+
+ ); + + const cates = Object.keys(imgBed); + + return ( + <> + {isCrop ? ( + + + {fileList.length >= maxLen ? null : uploadButton} + + + ) : ( + + {fileList.length >= maxLen ? null : uploadButton} + + )} +
+ 图片库 +
+ + 预览图片 + + + + {cates.map((item, i) => { + return ( + +
+ {(imgBed as any)[item] && + (imgBed as any)[item].map((item: string, i: number) => { + return ( +
this.handleImgSelected(item)} + > + 趣谈前端-h5-dooring + + + +
+ ); + })} +
+
+ ); + })} + + + +
+
+ + ); + } +} + +export default PicturesWall; diff --git a/src/components/FormComponents的副本 2/XEditor/index.less b/src/components/FormComponents的副本 2/XEditor/index.less new file mode 100644 index 0000000..4783014 --- /dev/null +++ b/src/components/FormComponents的副本 2/XEditor/index.less @@ -0,0 +1,4 @@ +.avatarUploader > :global(.ant-upload) { + width: 128px; + height: 128px; +} diff --git a/src/components/FormComponents的副本 2/XEditor/index.tsx b/src/components/FormComponents的副本 2/XEditor/index.tsx new file mode 100644 index 0000000..c97c31a --- /dev/null +++ b/src/components/FormComponents的副本 2/XEditor/index.tsx @@ -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: 加粗 + }, + "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 ( + + ); +}); diff --git a/src/components/FormComponents的副本 2/types.ts b/src/components/FormComponents的副本 2/types.ts new file mode 100644 index 0000000..a9e9c5e --- /dev/null +++ b/src/components/FormComponents的副本 2/types.ts @@ -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; + +//////////////////// +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; + +///////////////////////////////// +export interface ISelectConfigType { + key: string; + name: string; + type: "Select"; + range: Array<{ + key: KeyType; + text: string; + }>; +} +export type TSelectDefaultType = KeyType; + +///////////////////////// +export interface IRadioConfigType { + key: string; + name: string; + type: "Radio"; + range: Array<{ + key: KeyType; + text: string; + }>; +} +export type TRadioDefaultType = KeyType; + +/////////////// + +export interface ISwitchConfigType { + key: string; + name: string; + type: "Switch"; +} +export type TSwitchDefaultType = boolean; + +///////////////////////////// +export interface ICardPickerConfigType { + key: string; + name: string; + type: "CardPicker"; + icons: Array; +} +export type TCardPickerDefaultType = 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; diff --git a/src/components/FormComponents的副本/CardPicker/index.less b/src/components/FormComponents的副本/CardPicker/index.less new file mode 100644 index 0000000..c67b749 --- /dev/null +++ b/src/components/FormComponents的副本/CardPicker/index.less @@ -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; + } + } +} diff --git a/src/components/FormComponents的副本/CardPicker/index.tsx b/src/components/FormComponents的副本/CardPicker/index.tsx new file mode 100644 index 0000000..6939a4c --- /dev/null +++ b/src/components/FormComponents的副本/CardPicker/index.tsx @@ -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, "type" | "key" | "name"> { + onChange?: (v: string) => void; + type: IconTypes; +} + +export default memo((props: CardPickerType) => { + const { type, icons, onChange } = props; + const [selected, setSelected] = useState(type); + + const handlePicker = (v: IconTypes) => { + if (onChange) { + onChange(v); + return; + } + setSelected(v); + }; + + useEffect(() => { + setSelected(type); + }, [type]); + + return ( +
+ {icons.map((item, i) => { + return ( + handlePicker(item)} + key={i} + > + + + ); + })} +
+ ); +}); diff --git a/src/components/FormComponents的副本/Color/index.tsx b/src/components/FormComponents的副本/Color/index.tsx new file mode 100644 index 0000000..97df2f5 --- /dev/null +++ b/src/components/FormComponents的副本/Color/index.tsx @@ -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 { + 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 ( +
+
+
+
+ {this.state.displayColorPicker ? ( + +
+ +
+
+ + ) : null} +
+ ); + } +} + +export default colorPicker; diff --git a/src/components/FormComponents的副本/DataList/editorModal.tsx b/src/components/FormComponents的副本/DataList/editorModal.tsx new file mode 100644 index 0000000..e562b18 --- /dev/null +++ b/src/components/FormComponents的副本/DataList/editorModal.tsx @@ -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) => void) + | undefined; + item?: TDataListDefaultTypeItem; + onSave: Function; + cropRate: number; +}; + +const EditorModal: FC = 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 && ( + handleOk()}> + 确定 + + } + > +
+ + + + + + + + + + {!!window["currentCates"] && ( + + + + )} + + + + + +
+ )} + + ); +}; + +export default memo(EditorModal); diff --git a/src/components/FormComponents的副本/DataList/index.less b/src/components/FormComponents的副本/DataList/index.less new file mode 100644 index 0000000..c6e60b5 --- /dev/null +++ b/src/components/FormComponents的副本/DataList/index.less @@ -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; + } + } + } +} diff --git a/src/components/FormComponents的副本/DataList/index.tsx b/src/components/FormComponents的副本/DataList/index.tsx new file mode 100644 index 0000000..b54af01 --- /dev/null +++ b/src/components/FormComponents的副本/DataList/index.tsx @@ -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( + // 整个列表项作为跟随拖动的影像 +
+
{title}
+
{desc}
+
+ onEdit()}> + + + onDel()}> + + + {connectDragSource( + + + + ) // 拖动图标作为 Drag 对象 + } +
+
+ ) + ); +} + +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 = { + // 拖动开始时,返回描述 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 = { + 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(); + + 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( +
+ {!!(list && list.length) && + list.map((item, i) => ( + handleDel(item.id)} + onEdit={() => handleEdit(item)} + key={i} + id={`${item.id}`} + find={find} + move={move} + /> + ))} +
+ +
+ +
+ ); +}; + +const DndList = DropTarget(type, {}, connect => ({ + connectDropTarget: connect.dropTarget() +}))(List); + +// 将 HTMLBackend 作为参数传给 DragDropContext +export default memo((props: DataListMemo) => { + return ( + + + + ); +}); diff --git a/src/components/FormComponents的副本/FormItems/EditorModal.tsx b/src/components/FormComponents的副本/FormItems/EditorModal.tsx new file mode 100644 index 0000000..f8928d7 --- /dev/null +++ b/src/components/FormComponents的副本/FormItems/EditorModal.tsx @@ -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 = 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 && ( + + +
+ } + forceRender + visible={visible} + onOk={handleOk} + closable={false} + > +
+ { + + } + {!!item.label && ( + + + + )} + {!!item.fontSize && ( + + + + )} + {!!item.color && ( + + + + )} + {!!item.placeholder && ( + + + + )} + {!!item.options && ( + + + + )} + + + )} + + ); +}; + +export default memo(EditorModal); diff --git a/src/components/FormComponents的副本/FormItems/FormItems.tsx b/src/components/FormComponents的副本/FormItems/FormItems.tsx new file mode 100644 index 0000000..b2afd06 --- /dev/null +++ b/src/components/FormComponents的副本/FormItems/FormItems.tsx @@ -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; +} + +const FormItems = (props: FormItemsProps) => { + const { formList, onChange, rightPannelRef } = props; + const [formData, setFormData] = useState( + formList || [] + ); + const [visible, setVisible] = useState(false); + const [curItem, setCurItem] = useState(); + 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 ( +
+
表单控件
+
+ {formData.map((item: baseFormUnion, i: number) => { + let FormItem = BaseForm[item.type]; + return ( +
+
+ +
+
+ handleDelItem(item)} + > + + +
+
+ handleEditItem(item)} + > + + +
+
+ ); + })} +
+ +
+ {formTpl.map((item, i) => { + let FormItem = BasePopoverForm[item.type]; + return ( +
handleAddItem(item)} + > +
+ +
+
+ ); + })} +
+ {/* setFormTplVisible(false)}>Close */} + + } + directions={"LB"} + innerConstDomStyle={{ display: "block" }} + constDomStyle={{ display: "block" }} + callback={callback} + > + +
+
+
+ + +
+ ); +}; + +export default memo(FormItems); diff --git a/src/components/FormComponents的副本/FormItems/formItems.less b/src/components/FormComponents的副本/FormItems/formItems.less new file mode 100644 index 0000000..39af471 --- /dev/null +++ b/src/components/FormComponents的副本/FormItems/formItems.less @@ -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; + } + } + } + } +} diff --git a/src/components/FormComponents的副本/FormItems/index.tsx b/src/components/FormComponents的副本/FormItems/index.tsx new file mode 100644 index 0000000..cfb6c41 --- /dev/null +++ b/src/components/FormComponents的副本/FormItems/index.tsx @@ -0,0 +1,2 @@ +import FormItems from "./FormItems"; +export default FormItems; diff --git a/src/components/FormComponents的副本/MutiText/index.less b/src/components/FormComponents的副本/MutiText/index.less new file mode 100644 index 0000000..b2d6716 --- /dev/null +++ b/src/components/FormComponents的副本/MutiText/index.less @@ -0,0 +1,12 @@ +.mutiText { + .iptWrap { + margin-bottom: 12px; + display: flex; + .delBtn { + // font-size: 14px; + margin-left: 12px; + cursor: pointer; + align-self: center; + } + } +} diff --git a/src/components/FormComponents的副本/MutiText/index.tsx b/src/components/FormComponents的副本/MutiText/index.tsx new file mode 100644 index 0000000..48810d0 --- /dev/null +++ b/src/components/FormComponents的副本/MutiText/index.tsx @@ -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 + ) => { + 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 ( +
+ {value && value.length ? ( + value!.map((item, i) => { + return ( +
+ handleChange(i, e)} /> + handleDel(i)} + placement="leftTop" + okText="确定" + cancelText="取消" + > + + + + +
+ ); + }) + ) : ( +
+ +
+ )} + {value && value.length < 3 && ( +
+ +
+ )} +
+ ); +}); diff --git a/src/components/FormComponents的副本/Pos/index.less b/src/components/FormComponents的副本/Pos/index.less new file mode 100644 index 0000000..504c546 --- /dev/null +++ b/src/components/FormComponents的副本/Pos/index.less @@ -0,0 +1,11 @@ +.posIpt { + display: flex; + justify-content: flex-end; + margin-right: -10px; + .posItem { + margin-right: 10px; + span { + margin-right: 3px; + } + } +} diff --git a/src/components/FormComponents的副本/Pos/index.tsx b/src/components/FormComponents的副本/Pos/index.tsx new file mode 100644 index 0000000..222de19 --- /dev/null +++ b/src/components/FormComponents的副本/Pos/index.tsx @@ -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 ( +
+
+ x: + +
+
+ y: + +
+
+ ); +}); diff --git a/src/components/FormComponents的副本/Table/index.less b/src/components/FormComponents的副本/Table/index.less new file mode 100644 index 0000000..02aa16b --- /dev/null +++ b/src/components/FormComponents的副本/Table/index.less @@ -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; + } +} diff --git a/src/components/FormComponents的副本/Table/index.tsx b/src/components/FormComponents的副本/Table/index.tsx new file mode 100644 index 0000000..fdc7274 --- /dev/null +++ b/src/components/FormComponents的副本/Table/index.tsx @@ -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(null); + +interface Item { + key: string; + name: string; + age: string; + address: string; +} + +interface EditableRowProps { + index: number; +} + +const EditableRow: React.FC = ({ index, ...props }) => { + const [form] = Form.useForm(); + return ( +
+ +
+ + + ); +}; + +interface EditableCellProps { + title: React.ReactNode; + editable: boolean; + children: React.ReactNode; + dataIndex: string; + record: any; + handleSave: (record: Item) => void; +} + +const EditableCell: React.FC = ({ + title, + editable, + children, + dataIndex, + record, + handleSave, + ...restProps +}) => { + const [editing, setEditing] = useState(false); + const inputRef = useRef(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 ? ( + + RefObject} + onPressEnter={save} + onBlur={save} + /> + + ) : ( +
+ {children} +
+ ); + } + + return
; +}; + +class EditableTable extends React.Component { + 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 ? ( + this.handleDelete(record.key)} + > + + + ) : 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) => { + this.setState({ + visible: false + }); + }; + + handleCancel = (e: React.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 = 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) { + // 解析并提取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 ( +
+ + + + + + + +
{childNode}
"editable-row"} + bordered + dataSource={dataSource} + columns={columns} + pagination={{ pageSize: 50 }} + scroll={{ y: 240 }} + /> + + +
+
+ this.handleApiField("api", e.target.value)} + /> +
+
+ this.handleApiField("header", e.target.value)} + /> +
+
+ +
+ {this.state.apiResult && ( + <> +
+ +
+
+ + this.handleApiField("dataField", e.target.value) + } + /> +

+ 数据源字段是接口返回的图表数据对应的字段, 必填, + 否则无法正确导入数据 +

+
+ + )} +
+
+ + ); + } +} + +export default memo(EditableTable); diff --git a/src/components/FormComponents的副本/Upload/index.less b/src/components/FormComponents的副本/Upload/index.less new file mode 100644 index 0000000..8f00bfc --- /dev/null +++ b/src/components/FormComponents的副本/Upload/index.less @@ -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); + } + } +} diff --git a/src/components/FormComponents的副本/Upload/index.tsx b/src/components/FormComponents的副本/Upload/index.tsx new file mode 100644 index 0000000..ed5f4f1 --- /dev/null +++ b/src/components/FormComponents的副本/Upload/index.tsx @@ -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((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[]; + action?: string; + headers?: any; + withCredentials?: boolean; + maxLen?: number; + onChange?: (v: any) => void; + cropRate?: number | boolean; + isCrop?: boolean; +} + +class PicturesWall extends React.Component { + 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) => { + 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>) => { + 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 = ( +
+ +
上传
+
+ ); + + const cates = Object.keys(imgBed); + + return ( + <> + {isCrop ? ( + + + {fileList.length >= maxLen ? null : uploadButton} + + + ) : ( + + {fileList.length >= maxLen ? null : uploadButton} + + )} +
+ 图片库 +
+ + 预览图片 + + + + {cates.map((item, i) => { + return ( + +
+ {(imgBed as any)[item] && + (imgBed as any)[item].map((item: string, i: number) => { + return ( +
this.handleImgSelected(item)} + > + 趣谈前端-h5-dooring + + + +
+ ); + })} +
+
+ ); + })} + + + +
+
+ + ); + } +} + +export default PicturesWall; diff --git a/src/components/FormComponents的副本/XEditor/index.less b/src/components/FormComponents的副本/XEditor/index.less new file mode 100644 index 0000000..4783014 --- /dev/null +++ b/src/components/FormComponents的副本/XEditor/index.less @@ -0,0 +1,4 @@ +.avatarUploader > :global(.ant-upload) { + width: 128px; + height: 128px; +} diff --git a/src/components/FormComponents的副本/XEditor/index.tsx b/src/components/FormComponents的副本/XEditor/index.tsx new file mode 100644 index 0000000..c97c31a --- /dev/null +++ b/src/components/FormComponents的副本/XEditor/index.tsx @@ -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: 加粗 + }, + "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 ( + + ); +}); diff --git a/src/components/FormComponents的副本/types.ts b/src/components/FormComponents的副本/types.ts new file mode 100644 index 0000000..a9e9c5e --- /dev/null +++ b/src/components/FormComponents的副本/types.ts @@ -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; + +//////////////////// +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; + +///////////////////////////////// +export interface ISelectConfigType { + key: string; + name: string; + type: "Select"; + range: Array<{ + key: KeyType; + text: string; + }>; +} +export type TSelectDefaultType = KeyType; + +///////////////////////// +export interface IRadioConfigType { + key: string; + name: string; + type: "Radio"; + range: Array<{ + key: KeyType; + text: string; + }>; +} +export type TRadioDefaultType = KeyType; + +/////////////// + +export interface ISwitchConfigType { + key: string; + name: string; + type: "Switch"; +} +export type TSwitchDefaultType = boolean; + +///////////////////////////// +export interface ICardPickerConfigType { + key: string; + name: string; + type: "CardPicker"; + icons: Array; +} +export type TCardPickerDefaultType = 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; diff --git a/src/components/LoadingCp的副本 2/index.tsx b/src/components/LoadingCp的副本 2/index.tsx new file mode 100644 index 0000000..89389ac --- /dev/null +++ b/src/components/LoadingCp的副本 2/index.tsx @@ -0,0 +1,3 @@ +import React from "react"; + +export default () =>
Dooring
; diff --git a/src/components/LoadingCp的副本/index.tsx b/src/components/LoadingCp的副本/index.tsx new file mode 100644 index 0000000..89389ac --- /dev/null +++ b/src/components/LoadingCp的副本/index.tsx @@ -0,0 +1,3 @@ +import React from "react"; + +export default () =>
Dooring
; diff --git a/src/components/Zan的副本 2/index.less b/src/components/Zan的副本 2/index.less new file mode 100644 index 0000000..a114916 --- /dev/null +++ b/src/components/Zan的副本 2/index.less @@ -0,0 +1,9 @@ +.takeCat { + display: inline-block; +} +.imgWrap { + width: 160px; + img { + width: 100%; + } +} diff --git a/src/components/Zan的副本 2/index.tsx b/src/components/Zan的副本 2/index.tsx new file mode 100644 index 0000000..644809c --- /dev/null +++ b/src/components/Zan的副本 2/index.tsx @@ -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 = ( +
+ sponsorship +
+); + +export default memo(function ZanPao(props: IProps) { + const { + text = ( + + ) + } = props; + return ( +
+ + {text} + +
+ ); +}); diff --git a/src/components/Zan的副本/index.less b/src/components/Zan的副本/index.less new file mode 100644 index 0000000..a114916 --- /dev/null +++ b/src/components/Zan的副本/index.less @@ -0,0 +1,9 @@ +.takeCat { + display: inline-block; +} +.imgWrap { + width: 160px; + img { + width: 100%; + } +} diff --git a/src/components/Zan的副本/index.tsx b/src/components/Zan的副本/index.tsx new file mode 100644 index 0000000..644809c --- /dev/null +++ b/src/components/Zan的副本/index.tsx @@ -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 = ( +
+ sponsorship +
+); + +export default memo(function ZanPao(props: IProps) { + const { + text = ( + + ) + } = props; + return ( +
+ + {text} + +
+ ); +});