sortable.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import { ArrowDownOutlined, ArrowUpOutlined, BorderRightOutlined, DeleteOutlined, DragOutlined, LinkOutlined, PlusOutlined, RetweetOutlined, UserAddOutlined } from "@ant-design/icons";
  2. import React from "react";
  3. import { SortableContainer, SortableElement, SortableHandle } from "react-sortable-hoc";
  4. import { ReactComponent as Img } from '@/assets/img.svg'
  5. import { ReactComponent as EditSvg } from '@/assets/edit.svg'
  6. import { ReactComponent as MyText } from '@/assets/text.svg'
  7. import { ReactComponent as ImgText } from '@/assets/imgText.svg'
  8. import { ReactComponent as FollowAcc } from '@/assets/followAcc.svg'
  9. import { ReactComponent as JumpLink } from '@/assets/jumpLink.svg'
  10. import { ReactComponent as WxAutoSvg } from '@/assets/wxAutoSvg.svg'
  11. import './index1.less'
  12. import { Tooltip } from "antd";
  13. const DragHandle = SortableHandle(() => <button style={{ cursor: 'grab' }} className="handle" onClick={(e) => { e.stopPropagation() }}><BorderRightOutlined /></button>);
  14. const ComptEdit = (props: {
  15. data: any,
  16. pureImageUrl?: string,
  17. handleBtn: (type: string, index: number) => void,
  18. del: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void,
  19. upload?: () => void
  20. }) => {
  21. const { data, handleBtn, del, pureImageUrl, upload } = props
  22. return <>
  23. <section className="comptEditTrBtns">
  24. <div className="comptEditTrBtnsInner">
  25. {data.index > 1 && <a onClick={(e) => { e.stopPropagation(); handleBtn('upper', data.index) }}><ArrowUpOutlined /></a>}
  26. {data.length !== data.index + 1 && <a onClick={(e) => { e.stopPropagation(); handleBtn('lower', data.index) }}><ArrowDownOutlined /></a>}
  27. <Tooltip placement="topRight" color="#FFF" title={<div className="assBts">
  28. <div onClick={(e) => { e.stopPropagation(); handleBtn('IMAGE', data.index) }}><Img /></div>
  29. <div onClick={(e) => { e.stopPropagation(); handleBtn('TEXT', data.index) }}><MyText /></div>
  30. <div onClick={(e) => { e.stopPropagation(); handleBtn('GH', data.index) }}><FollowAcc /></div>
  31. {/* <div onClick={(e) => { e.stopPropagation(); handleBtn('link', data.index) }}><JumpLink /></div>
  32. <div onClick={(e) => { e.stopPropagation(); handleBtn('shelfnew', data.index) }}><ImgText /></div> */}
  33. <div onClick={(e) => { e.stopPropagation(); handleBtn('ENTERPRISE_WX', data.index) }}><WxAutoSvg /></div>
  34. </div>}>
  35. <a><PlusOutlined /></a>
  36. </Tooltip>
  37. </div>
  38. </section>
  39. <section className={'comptEditBtns'}>
  40. <div className={'comptEditBtnsInner'}>
  41. {pureImageUrl && <button onClick={() => { upload && upload() }}><EditSvg /></button>}
  42. <DragHandle />
  43. <button onClick={(e) => { del(e) }}><DeleteOutlined /></button>
  44. </div>
  45. </section>
  46. </>
  47. }
  48. /** 内容文本 */
  49. export const SortableItemText = SortableElement(({ item, click, del, pageBackColor, handleBtn, data }: any) => {
  50. let { fontSize, fontColor, textAlignment, text, fontStyle, paddingTop, paddingBottom } = item
  51. return <div className={`compt componentType1 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  52. <div className={'componentWrap'}>
  53. <div className={'componentContent'} style={{ backgroundColor: pageBackColor }}>
  54. <div className={'text'} style={{ lineHeight: fontSize * 1.5 + 'px', fontSize: fontSize, color: fontColor, textAlign: textAlignment === 0 ? 'left' : textAlignment === 1 ? 'center' : 'right', fontWeight: fontStyle === 0 ? 'normal' : 'bold', maxWidth: '100%', display: 'block', marginLeft: 24, marginRight: 24, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
  55. <div>{text ?
  56. text?.split(/[\r\n]/g)?.map((item: any, index: number) => {
  57. if (item) {
  58. return <div key={`item${index}`}>
  59. {item?.split(' ')?.map((item1: any, ind: number) => {
  60. if (item1) {
  61. return <span key={`item1${ind}`}>{item1}</span>
  62. } else {
  63. return <span key={`item1${ind}`}>&nbsp;</span>
  64. }
  65. })}
  66. </div>
  67. } else {
  68. return <div key={`item${index}`}>&nbsp;</div>
  69. }
  70. })
  71. : '请输入文本内容'}</div>
  72. </div>
  73. <div className={'textAreaDiv'} style={{ lineHeight: fontSize * 1.5 + 'px', fontSize: fontSize, margin: '11px 24px', marginLeft: 24, marginRight: 24, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
  74. <textarea readOnly value={text} className={'textarea'} placeholder={item.comptActive ? `请在右侧输入文本内容` : '请输入文本内容'} style={{ color: fontColor, fontWeight: fontStyle === 0 ? 'normal' : 'bold', textAlign: textAlignment === 0 ? 'left' : textAlignment === 1 ? 'center' : 'right', backgroundColor: pageBackColor }}></textarea>
  75. </div>
  76. </div>
  77. </div>
  78. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
  79. </div>
  80. });
  81. /** 内容图片 */
  82. export const SortableItemImg = SortableElement(({ item, click, del, upload, data, handleBtn }: any) => {
  83. let { imageUrl, paddingTop, paddingBottom } = item
  84. return <div className={`compt componentType41 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  85. <div className={'componentWrap'}>
  86. <div className={'componentContent'}>
  87. {
  88. imageUrl ? <img src={imageUrl} style={{ display: 'block', width: '100%', margin: 0, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }} /> : <div className={'default'} style={{ width: 375, height: 222, margin: 0, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
  89. <div className={'defaultIcon'} style={{ marginTop: 44 }}>
  90. <Img />
  91. </div>
  92. </div>
  93. }
  94. </div>
  95. </div>
  96. {!imageUrl && <div className={'comptUpload'} style={{ margin: 0, marginTop: paddingTop / 2, marginBottom: paddingBottom / 2 }}>
  97. <button style={{ marginTop: 114 }} className={'comptEditButton'} onClick={() => { upload(1) }}>上传图片</button>
  98. </div>}
  99. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} pureImageUrl={imageUrl} upload={() => { upload(1) }} />
  100. </div>
  101. });
  102. /** 内容关注公众号按钮 */
  103. export const SortableItemFollowAcc = SortableElement(({ item, click, del, data, handleBtn }: any) => {
  104. let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = item
  105. return <div className={`compt componentType21 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  106. <div className={'componentWrap'}>
  107. <div className={'componentContent'}>
  108. <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
  109. <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
  110. <a style={{
  111. textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
  112. border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
  113. overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === 0 ? 'normal' : 'bold',
  114. height: 40, lineHeight: 40, width: '100%', fontSize: 15
  115. }}>{useIcon === 1 && <UserAddOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
  116. </div>
  117. </div>
  118. </div>
  119. </div>
  120. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
  121. </div>
  122. })
  123. /** 内容添加商家微信按钮 */
  124. export const SortableItemWxAuto = SortableElement(({ item, click, del, data, handleBtn }: any) => {
  125. let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = item
  126. return <div className={`compt componentType21 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  127. <div className={'componentWrap'}>
  128. <div className={'componentContent'}>
  129. <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
  130. <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
  131. <a style={{
  132. textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
  133. border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
  134. overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === 0 ? 'normal' : 'bold',
  135. height: 40, lineHeight: 40, width: '100%', fontSize: 15
  136. }}>{useIcon === 1 && <UserAddOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
  137. </div>
  138. </div>
  139. </div>
  140. </div>
  141. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
  142. </div>
  143. })
  144. /** 内容跳转链接按钮 */
  145. export const SortableItemJumpLink = SortableElement(({ item, click, del, data, handleBtn }: any) => {
  146. let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = item
  147. return <div className={`compt componentType21 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  148. <div className={'componentWrap'}>
  149. <div className={'componentContent'}>
  150. <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
  151. <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
  152. <a style={{
  153. textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
  154. border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
  155. overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === '0' ? 'normal' : 'bold',
  156. height: 40, lineHeight: 40, width: '100%', fontSize: 15
  157. }}>{useIcon === '1' && <LinkOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
  158. </div>
  159. </div>
  160. </div>
  161. </div>
  162. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
  163. </div>
  164. })
  165. /** 图文 */
  166. export const SortableItemImgText = SortableElement(({ item, click, del, index, data, handleBtn }: any) => {
  167. let { paddingTop, paddingBottom, layoutItems, borderColor, bgColor, type, wxad_align } = item
  168. if (type === '104') {
  169. let componentItem = layoutItems?.componentItem[0]?.layoutItems?.componentItem
  170. let otherData = componentItem[1]?.layoutItems?.componentItem
  171. return <div className={`compt componentType104 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  172. <div className={'componentWrap'}>
  173. <div className={'componentContent'}>
  174. <div className={'shelf listType'} style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px', marginLeft: 20, marginRight: 20 }}>
  175. <div className={'shelfItem'} style={{ border: `1px solid ${borderColor || "#e5e5e5"}`, backgroundColor: bgColor || "#ffffff" }}>
  176. <div className={'shelfItemImg'} style={{ marginLeft: 11.5, marginTop: 11.5 }}>
  177. {componentItem[0]?.pureImageUrl ? <img src={componentItem[0]?.pureImageUrl} style={{ display: 'flex', width: '100%', height: '100%' }} /> : <div className={'default'} style={{ width: '100%', height: '100%' }}>
  178. <div className={'defaultIcon'} style={{ marginTop: 27, width: 36, height: 36 }}>
  179. <Img />
  180. </div>
  181. </div>}
  182. </div>
  183. <div className={'shelfItemContent'} style={{ margin: '12px 20px 0 12px' }}>
  184. <p className={'title'} style={{ color: otherData[0]?.fontColor || "#353535", fontSize: 16 }}>{otherData[0]?.content || otherData[0]?.name}</p>
  185. <p className={'desc'} style={{ color: otherData[1]?.fontColor || "#B2B2B2" }}>{otherData[1]?.content || otherData[1]?.name}</p>
  186. <div
  187. className={'btn'}
  188. style={{
  189. color: otherData[2]?.fontColor || 'rgb(255, 255, 255)',
  190. textDecoration: 'none',
  191. fontWeight: otherData[2]?.btnFontType === '0' ? 'normal' : 'bold',
  192. backgroundColor: otherData[2]?.btnBgColorTheme || "#07C160",
  193. border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(otherData[2]?.btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${otherData[2]?.btnBorderColorTheme}`,
  194. borderRadius: 4
  195. }}
  196. >{otherData[2]?.btnTitle}</div>
  197. </div>
  198. </div>
  199. </div>
  200. </div>
  201. </div>
  202. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
  203. </div>
  204. } else if (type === '103') {
  205. let componentItem = layoutItems?.componentItem
  206. return <div className={`compt componentType103 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  207. <div className={'componentWrap'}>
  208. <div className={'componentContent'}>
  209. <div className='shelf gridType' style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px', marginLeft: 20 }}>
  210. {
  211. componentItem?.map((item: any, index: number) => {
  212. let shelfnewItem = item?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
  213. return <div className='shelfItem-3q' key={index} style={{ borderWidth: 1, borderStyle: 'solid', borderColor: item?.borderColor, backgroundColor: item?.bgColor || 'rgb(255,255,255)', marginLeft: index === 1 ? 11 : 0 }}>
  214. <div className='shelfItemImg' style={{ marginLeft: 5.5, marginTop: 5.5 }}>
  215. {shelfnewItem[0]?.pureImageUrl ? <img src={shelfnewItem[0]?.pureImageUrl} style={{ display: 'flex', width: '100%', height: '100%' }} /> : <div className="default" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  216. <div className={'defaultIcon'} style={{ width: 36, height: 36 }}>
  217. <Img />
  218. </div>
  219. </div>}
  220. </div>
  221. <div className='shelfItemContent' style={{ marginLeft: 12, textAlign: wxad_align === 0 ? 'left' : 'center' }}>
  222. <p className='title' style={{ color: shelfnewItem[1]?.fontColor || 'rgb(53, 53, 53)', fontSize: 16, marginBottom: 4 }}>{shelfnewItem[1]?.content || shelfnewItem[1]?.name}</p>
  223. <p className='desc' style={{ color: shelfnewItem[2]?.fontColor || 'rgb(178, 178, 178)', marginBottom: 14 }}>{shelfnewItem[2]?.content || shelfnewItem[2]?.name}</p>
  224. <p className='btn' style={{
  225. textDecoration: 'none',
  226. fontWeight: shelfnewItem[3]?.btnFontType === '0' ? 400 : 'bold',
  227. color: shelfnewItem[3]?.fontColor || 'rgb(255, 255, 255)',
  228. backgroundColor: shelfnewItem[3]?.btnBgColorTheme || 'rgb(7,193,96)',
  229. borderWidth: shelfnewItem[3]?.borderSize ? Number(shelfnewItem[3]?.borderSize) : 0,
  230. borderStyle: 'solid',
  231. borderColor: shelfnewItem[3]?.btnBorderColorTheme,
  232. borderRadius: 4
  233. }}>{shelfnewItem[3]?.btnTitle}</p>
  234. </div>
  235. </div>
  236. })
  237. }
  238. </div>
  239. </div>
  240. </div>
  241. <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
  242. </div>
  243. } else {
  244. return null
  245. }
  246. })
  247. /** 悬浮组件 */
  248. export const SortableItemFloatbutton = SortableElement(({ item, click, del }: any) => {
  249. let { titleColor, descColor, componentItem, iconUrl, title, desc, wxad_styleType } = item
  250. return <div className={`compt componentType134 comptFixedBottom ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
  251. <div className={'componentWrap'}>
  252. <div className="componentContent">
  253. <div className="floatButtonWrapper">
  254. <div className="floatButton">
  255. {wxad_styleType === '1' && (iconUrl ? <img src={iconUrl} className="floatButtonAvatar"/> : <div className="floatButtonAvatarPlaceholder"></div>)}
  256. <div className="floatButtonTexts">
  257. <div className="floatButtonTitle" style={{ color: titleColor || 'rgb(23, 23, 23)' }}>{title || '标题'}</div>
  258. {(wxad_styleType === '1' || wxad_styleType === '2' ) && <div className="floatButtonDesc" style={{ color: descColor || 'rgb(76, 76, 76)' }}>{desc || '描述'}</div>}
  259. </div>
  260. <div className="floatButtonLink" style={{
  261. color: componentItem?.fontColor || 'rgb(255,255,255)',
  262. fontWeight: componentItem?.btnFontType === '0' ? 'normal' : 'bold',
  263. backgroundColor: componentItem?.btnBgColorTheme || 'rgb(7, 193, 96)',
  264. width: ((componentItem?.layoutWidth || 160) / 2) + 'px',
  265. textAlign: 'center',
  266. overflow: 'hidden',
  267. whiteSpace: 'pre'
  268. }}>{componentItem?.btnTitle}</div>
  269. </div>
  270. </div>
  271. </div>
  272. </div>
  273. <section className={'comptEditBtns'}>
  274. <div className={'comptEditBtnsInner'}>
  275. <button onClick={(e) => { del(e) }}><DeleteOutlined /></button>
  276. </div>
  277. </section>
  278. </div>
  279. })
  280. /** 外层 */
  281. export const SortableList = SortableContainer(({ children, isFloatButton }: { children: any, isFloatButton?: boolean }) => (<div className="page-0" style={isFloatButton ? { paddingBottom: 90, minHeight: 510 } : {}}>{children}</div>));
  282. // 以下是轮播图内容
  283. const DragSliderHandle = SortableHandle(() => <div style={{ cursor: 'grab' }} className="sliderhandle"><DragOutlined /></div>);
  284. // 轮播图有图item
  285. export const SortableItemLi = SortableElement(({ isActive, pureImageUrl, click, setActiveIndex }: any) => {
  286. return <li className={`imageUploadItem ${isActive ? 'active' : ''} imageUploadItemInnerDone`} style={{ zIndex: 2000 }}>
  287. <div className="upload-img-item">
  288. <div className="upload-img-item-inner" style={{ backgroundImage: `url(${pureImageUrl})`, borderStyle: 'solid' }} onClick={setActiveIndex}>
  289. <div className='upload-img-item-action' onClick={click}>
  290. <RetweetOutlined />
  291. </div>
  292. <DragSliderHandle />
  293. </div>
  294. </div>
  295. </li>
  296. })
  297. // 轮播图无图item
  298. export const SortableItemNoLi = SortableElement(({ isActive, click }: any) => {
  299. return <li className={`imageUploadItem ${isActive ? 'active' : ''}`}>
  300. <div className="upload-img-item">
  301. <div className="upload-img-item-inner" style={{ backgroundImage: `url("")` }} onClick={click}><PlusOutlined /></div>
  302. </div>
  303. </li>
  304. })
  305. /** 外层轮播图 */
  306. export const SortableUlList = SortableContainer(({ children }: { children: any }) => (<ul className="imageUpload">{children}</ul>));