|
|
@@ -0,0 +1,322 @@
|
|
|
+import React, { useContext } from "react"
|
|
|
+import { arrayMove, SortableContainer, SortableElement, SortableHandle } from "react-sortable-hoc";
|
|
|
+import { ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, PlusOutlined, BorderRightOutlined } from "@ant-design/icons";
|
|
|
+import { Button, Tooltip } from "antd";
|
|
|
+import { ReactComponent as EditSvg } from '../../../../public/svg/edit.svg'
|
|
|
+import { ReactComponent as ImgSvg } from '../../../../public/svg/img.svg'
|
|
|
+import { ReactComponent as TextSvg } from '../../../../public/svg/text.svg'
|
|
|
+import { ReactComponent as WxAutoSvg } from '../../../../public/svg/wxAutoSvg.svg'
|
|
|
+import { DispatchMiniPageCreate } from "./drawerMini";
|
|
|
+import { useDrop } from "ahooks";
|
|
|
+import { floatButtonContent, imgContent, qrCodeContent, txtContent } from "./const";
|
|
|
+import UploadLoad from "../../components/materialMould/uploadLoad";
|
|
|
+
|
|
|
+const DragHandle = SortableHandle(() => <Button style={{ cursor: 'grab', fontSize: 14 }} className="handle" onClick={(e) => { e.stopPropagation() }} icon={<BorderRightOutlined />}></Button>);
|
|
|
+
|
|
|
+const ComptEdit = (props: {
|
|
|
+ data: any,
|
|
|
+ pureImageUrl?: string,
|
|
|
+ handleBtn: (type: string, index: number) => void,
|
|
|
+ del: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void,
|
|
|
+ onChange?: (v: any) => void
|
|
|
+}) => {
|
|
|
+ const { data, handleBtn, del, pureImageUrl, onChange } = props
|
|
|
+ return <>
|
|
|
+ <section className="comptEditTrBtns">
|
|
|
+ <div className="comptEditTrBtnsInner">
|
|
|
+ {data.index > 1 && <a onClick={(e) => { e.stopPropagation(); handleBtn('upper', data.index) }}><ArrowUpOutlined /></a>}
|
|
|
+ {data.length !== data.index + 1 && <a onClick={(e) => { e.stopPropagation(); handleBtn('lower', data.index) }}><ArrowDownOutlined /></a>}
|
|
|
+ <Tooltip placement="topRight" color="#FFF" title={<div className="assBts">
|
|
|
+ <div onClick={(e) => { e.stopPropagation(); handleBtn('IMAGE', data.index) }}><ImgSvg /></div>
|
|
|
+ <div onClick={(e) => { e.stopPropagation(); handleBtn('TEXT', data.index) }}><TextSvg /></div>
|
|
|
+ </div>}>
|
|
|
+ <a><PlusOutlined /></a>
|
|
|
+ </Tooltip>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ <section className={'comptEditBtns'}>
|
|
|
+ <div className={'comptEditBtnsInner'}>
|
|
|
+ {pureImageUrl && <UploadLoad
|
|
|
+ type='image'
|
|
|
+ uploadButton={<Button icon={<EditSvg />} />}
|
|
|
+ onChange={(value) => {
|
|
|
+ onChange?.(value)
|
|
|
+ }}
|
|
|
+ />}
|
|
|
+ <DragHandle />
|
|
|
+ <Button onClick={(e) => { del(e) }} style={{ fontSize: 14 }} icon={<DeleteOutlined />}></Button>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </>
|
|
|
+}
|
|
|
+
|
|
|
+interface ContainerProps {
|
|
|
+ children: React.ReactNode;
|
|
|
+ isFloatButton?: boolean
|
|
|
+}
|
|
|
+
|
|
|
+const SortableList = SortableContainer<ContainerProps>(({ children, isFloatButton }) => (<div className="page-0" style={isFloatButton ? { paddingBottom: 90, minHeight: 510 } : {}}>{children}</div>));
|
|
|
+
|
|
|
+interface ElementProps {
|
|
|
+ data: { length: number, index: number }
|
|
|
+ item: TASK_MINI_PAGE_CREATE.ComptItem
|
|
|
+ click: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
|
|
|
+ del: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
|
|
|
+ handleBtn: (type: string, index: number) => void
|
|
|
+ pageBackColor?: string,
|
|
|
+ onChange?: (v: any) => void
|
|
|
+}
|
|
|
+
|
|
|
+/** 内容文本 */
|
|
|
+const SortableItemText = SortableElement<ElementProps>(({ item, click, del, pageBackColor, handleBtn, data }) => {
|
|
|
+ let { fontSize, color, textAlign, text, fontWeight, paddingTop, paddingBottom } = item as TASK_MINI_PAGE_CREATE.Text
|
|
|
+ return <div className={`compt componentType1 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
|
|
|
+ <div className={'componentWrap'}>
|
|
|
+ <div className={'componentContent'} style={{ backgroundColor: pageBackColor }}>
|
|
|
+ <div className={'text'} style={{ fontSize: fontSize, color, textAlign, fontWeight, maxWidth: '100%', display: 'block', marginLeft: 24, marginRight: 24, marginTop: paddingTop + 'px', marginBottom: paddingBottom + 'px' }}>
|
|
|
+ <div>{text ?
|
|
|
+ text?.split(/[\r\n]/g)?.map((item: any, index: number) => {
|
|
|
+ if (item) {
|
|
|
+ return <div key={`item${index}`}>
|
|
|
+ {item?.split(' ')?.map((item1: any, ind: number) => {
|
|
|
+ if (item1) {
|
|
|
+ return <span key={`item1${ind}`}>{item1}</span>
|
|
|
+ } else {
|
|
|
+ return <span key={`item1${ind}`}> </span>
|
|
|
+ }
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ } else {
|
|
|
+ return <div key={`item${index}`}> </div>
|
|
|
+ }
|
|
|
+ })
|
|
|
+ : '请输入文本内容'}</div>
|
|
|
+ </div>
|
|
|
+ <div className={'textAreaDiv'} style={{ fontSize: fontSize, margin: '10px 24px', marginLeft: 24, marginRight: 24, marginTop: paddingTop + 'px', marginBottom: paddingBottom + 'px' }}>
|
|
|
+ <textarea readOnly value={text} className={'textarea'} placeholder={item.comptActive ? `请在右侧输入文本内容` : '请输入文本内容'} style={{ color, fontWeight, textAlign, backgroundColor: pageBackColor }}></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
|
|
|
+ </div>
|
|
|
+});
|
|
|
+
|
|
|
+/** 内容图片 */
|
|
|
+const SortableItemImg = SortableElement<ElementProps>(({ item, click, del, data, handleBtn, onChange }) => {
|
|
|
+ const { url, paddingTop, paddingBottom } = item
|
|
|
+
|
|
|
+ return <div className={`compt componentType41 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
|
|
|
+ <div className={'componentWrap'}>
|
|
|
+ <div className={'componentContent'}>
|
|
|
+ {url ? <img src={url} style={{ display: 'block', width: '100%', margin: 0, marginTop: paddingTop + 'px', marginBottom: paddingBottom + 'px' }} /> : <div className={'default'} style={{ width: 375, height: 222, margin: 0, marginTop: paddingTop + 'px', marginBottom: paddingBottom + 'px' }}>
|
|
|
+ <div className={'defaultIcon'} style={{ marginTop: 44 }}>
|
|
|
+ <ImgSvg />
|
|
|
+ </div>
|
|
|
+ </div>}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {!url && <div className={'comptUpload'} style={{ margin: 0, marginTop: paddingTop, marginBottom: paddingBottom }}>
|
|
|
+ <UploadLoad
|
|
|
+ type='image'
|
|
|
+ uploadButton={<button style={{ marginTop: 114 }} className={'comptEditButton'}>上传图片</button>}
|
|
|
+ onChange={(value) => {
|
|
|
+ onChange?.(value)
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>}
|
|
|
+ <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} pureImageUrl={url} onChange={onChange} />
|
|
|
+ </div>
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/** 添加企微 */
|
|
|
+const SortableItemWxAuto = SortableElement<ElementProps>(({ item, click, del, data, handleBtn, onChange }) => {
|
|
|
+ const { imageList, paddingTop, paddingBottom, paddingLeft, paddingRight } = item
|
|
|
+
|
|
|
+ return <div className={`compt componentType41 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
|
|
|
+ <div className={'componentWrap'}>
|
|
|
+ <div className={'componentContent'}>
|
|
|
+ {imageList?.[0] ? <img src={imageList[0]} style={{ display: 'block', width: '100%', margin: 0, marginTop: paddingTop + 'px', marginBottom: paddingBottom + 'px', paddingLeft: paddingLeft + 'px', paddingRight: paddingRight + 'px', boxSizing: 'border-box' }} /> : <div className={'default'} style={{ width: 375, height: 222, margin: 0, marginTop: paddingTop + 'px', marginBottom: paddingBottom + 'px', paddingLeft: paddingLeft + 'px', paddingRight: paddingRight + 'px', boxSizing: 'border-box' }}>
|
|
|
+ <div className={'defaultIcon'} style={{ marginTop: 44 }}>
|
|
|
+ <WxAutoSvg />
|
|
|
+ </div>
|
|
|
+ </div>}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {!imageList?.[0] && <div className={'comptUpload'} style={{ margin: 0, marginTop: paddingTop, marginBottom: paddingBottom, paddingLeft: paddingLeft + 'px', paddingRight: paddingRight + 'px', boxSizing: 'border-box' }}>
|
|
|
+ <UploadLoad
|
|
|
+ type='image'
|
|
|
+ uploadButton={<button style={{ marginTop: 114 }} className={'comptEditButton'}>上传企微客服二维码图片</button>}
|
|
|
+ onChange={(value) => {
|
|
|
+ onChange?.(value)
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>}
|
|
|
+ <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} pureImageUrl={imageList?.[0]} onChange={onChange} />
|
|
|
+ </div>
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+/** 悬浮组件 */
|
|
|
+const SortableItemFloatbutton = SortableElement<{
|
|
|
+ item: TASK_MINI_PAGE_CREATE.FloatButton
|
|
|
+ click: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
|
|
|
+ del: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
|
|
|
+}>(({ item, click, del }) => {
|
|
|
+ const { qrCodeFloatList, title } = item as TASK_MINI_PAGE_CREATE.FloatButton
|
|
|
+ return <div className={`compt componentType134 comptFixedBottom ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
|
|
|
+ <div className={'componentWrap'}>
|
|
|
+ <div className="componentContent">
|
|
|
+ {qrCodeFloatList?.length > 0 ? <img src={qrCodeFloatList[0]} style={{ width: '100%', display: 'block' }} /> : <div className="floatButtonWrapper">
|
|
|
+ <div className="floatButton">
|
|
|
+ <div className="floatButtonAvatarPlaceholder"></div>
|
|
|
+ <div className="floatButtonTexts">
|
|
|
+ <div className="floatButtonTitle" style={{ color: 'rgb(23, 23, 23)' }}>{title || '标题'}</div>
|
|
|
+ <div className="floatButtonDesc" style={{ color: 'rgb(76, 76, 76)' }}>{'联系客服'}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <section className={'comptEditBtns'}>
|
|
|
+ <div className={'comptEditBtnsInner'}>
|
|
|
+ <Button onClick={(e) => { del(e) }} icon={<DeleteOutlined />} style={{ fontSize: 14 }}></Button>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+})
|
|
|
+
|
|
|
+
|
|
|
+const CenterCompt: React.FC = () => {
|
|
|
+
|
|
|
+ /**************************************/
|
|
|
+ const { pageSpecs, setPageSpecs, draggingCon, installActive, setCompt } = useContext(DispatchMiniPageCreate)!;
|
|
|
+ const { pageElementsSpecList, bgColor, globalElementsSpecList } = pageSpecs
|
|
|
+ /**************************************/
|
|
|
+
|
|
|
+ const [dropProps, { isHovering }] = useDrop({
|
|
|
+ onDom: (key) => {
|
|
|
+ onDom(key, 999);
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const onDom = (key: string, index) => { // 内容
|
|
|
+ const newPageElementsSpecList = pageElementsSpecList?.map(item => ({ ...item, comptActive: false }))
|
|
|
+ const newGlobalElementsSpecList = globalElementsSpecList?.map(item => ({ ...item, comptActive: false })) || []
|
|
|
+ if (key === 'TEXT') {
|
|
|
+ if (index === 999) {
|
|
|
+ newPageElementsSpecList.push({ ...txtContent, comptActive: true })
|
|
|
+ } else {
|
|
|
+ newPageElementsSpecList.splice(index, 0, { ...txtContent, comptActive: true })
|
|
|
+ }
|
|
|
+ } else if (key === 'IMAGE') {
|
|
|
+ if (index === 999) {
|
|
|
+ newPageElementsSpecList.push({ ...imgContent, comptActive: true })
|
|
|
+ } else {
|
|
|
+ newPageElementsSpecList.splice(index, 0, { ...imgContent, comptActive: true })
|
|
|
+ }
|
|
|
+ } else if (key === 'QR_CODE') {
|
|
|
+ if (index === 999) {
|
|
|
+ newPageElementsSpecList.push({ ...qrCodeContent, comptActive: true, id: Date.now() })
|
|
|
+ } else {
|
|
|
+ newPageElementsSpecList.splice(index, 0, { ...qrCodeContent, comptActive: true, id: Date.now() })
|
|
|
+ }
|
|
|
+ } else if (key === 'FLOAT_BUTTON') {
|
|
|
+ newGlobalElementsSpecList.push({ ...floatButtonContent, comptActive: true, id: Date.now() })
|
|
|
+ } else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: newPageElementsSpecList, globalElementsSpecList: newGlobalElementsSpecList })
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 内容删除 */
|
|
|
+ const delComptSpec = (e, index: number) => {
|
|
|
+ e.stopPropagation(); e.preventDefault();
|
|
|
+ if (index === 99999) { // 删除悬浮组件
|
|
|
+ let newGlobalElementsSpecList = JSON.parse(JSON.stringify(globalElementsSpecList))
|
|
|
+ newGlobalElementsSpecList = newGlobalElementsSpecList?.filter(item => item.elementType !== 'FLOAT_BUTTON')
|
|
|
+ setPageSpecs({ ...pageSpecs, globalElementsSpecList: newGlobalElementsSpecList })
|
|
|
+ } else {
|
|
|
+ const newPageElementsSpecList = JSON.parse(JSON.stringify(pageElementsSpecList || []))
|
|
|
+ newPageElementsSpecList.splice(index, 1)
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: newPageElementsSpecList })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 内容功能按钮区 */
|
|
|
+ const handleBtn = (type: string, index: number) => {
|
|
|
+ const newPageElementsSpecList = JSON.parse(JSON.stringify(pageElementsSpecList))
|
|
|
+ switch (type) {
|
|
|
+ case 'lower': // 下移动
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: arrayMove(pageElementsSpecList, index, index + 1) })
|
|
|
+ break;
|
|
|
+ case 'upper': // 上移动
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: arrayMove(pageElementsSpecList, index, index - 1) })
|
|
|
+ break;
|
|
|
+ case 'IMAGE': // 图片
|
|
|
+ newPageElementsSpecList.splice(index, 0, { ...imgContent });
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: newPageElementsSpecList })
|
|
|
+ break;
|
|
|
+ case 'TEXT': // 文本
|
|
|
+ newPageElementsSpecList.splice(index, 0, { ...txtContent });
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: newPageElementsSpecList })
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
|
|
|
+ setPageSpecs({ ...pageSpecs, pageElementsSpecList: arrayMove(pageElementsSpecList, oldIndex, newIndex) })
|
|
|
+ }
|
|
|
+
|
|
|
+ const floatButton = globalElementsSpecList?.find(item => item.elementType === 'FLOAT_BUTTON')
|
|
|
+
|
|
|
+ if (pageElementsSpecList?.length > 0) {
|
|
|
+ return <SortableList axis='y' onSortEnd={onSortEnd} useDragHandle isFloatButton={!!floatButton}>
|
|
|
+ {pageElementsSpecList.map((item: TASK_MINI_PAGE_CREATE.ComptItem, index: number) => {
|
|
|
+ switch (item.elementType) {
|
|
|
+ case "IMAGE":
|
|
|
+ return <SortableItemImg
|
|
|
+ key={`item-${item.elementType}-${index}`}
|
|
|
+ index={index} data={{ length: pageElementsSpecList?.length, index }}
|
|
|
+ item={item}
|
|
|
+ click={(e) => { installActive(e, index) }}
|
|
|
+ del={(e) => { delComptSpec(e, index) }}
|
|
|
+ handleBtn={handleBtn}
|
|
|
+ onChange={(url) => setCompt('url', url)}
|
|
|
+ />
|
|
|
+ case "TEXT":
|
|
|
+ return <SortableItemText
|
|
|
+ key={`item-${item.elementType}-${index}`}
|
|
|
+ index={index}
|
|
|
+ data={{ length: pageElementsSpecList?.length, index }}
|
|
|
+ item={item}
|
|
|
+ click={(e) => { installActive(e, index) }}
|
|
|
+ del={(e) => { delComptSpec(e, index) }}
|
|
|
+ pageBackColor={bgColor}
|
|
|
+ handleBtn={handleBtn}
|
|
|
+ />
|
|
|
+ case "QR_CODE":
|
|
|
+ return <SortableItemWxAuto
|
|
|
+ key={`item-${item.elementType}-${index}`}
|
|
|
+ index={index} data={{ length: pageElementsSpecList?.length, index }}
|
|
|
+ item={item}
|
|
|
+ click={(e) => { installActive(e, index) }}
|
|
|
+ del={(e) => { delComptSpec(e, index) }}
|
|
|
+ handleBtn={handleBtn}
|
|
|
+ onChange={(url) => setCompt('imageList', [url])}
|
|
|
+ />
|
|
|
+ default:
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ })}
|
|
|
+ <div className={`comptCon ${isHovering && 'hovering'} ${draggingCon && 'draggingCon'}`} {...dropProps}>
|
|
|
+ {(isHovering || draggingCon) && '请拖至此处'}
|
|
|
+ </div>
|
|
|
+ {floatButton && <SortableItemFloatbutton index={99999} item={floatButton} click={(e: any) => { installActive(e, 99999) }} del={(e: any) => { delComptSpec(e, 99999) }} />}
|
|
|
+ </SortableList>
|
|
|
+ }
|
|
|
+ return null
|
|
|
+}
|
|
|
+
|
|
|
+export default React.memo(CenterCompt)
|