index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import { Button, Checkbox, Input, Popconfirm, Space } from 'antd'
  2. import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
  3. import { useModel } from 'umi'
  4. import moment from 'moment'
  5. import style from './index.less'
  6. import filePng from '../../../public/file.png'
  7. /**
  8. * 模板消息 模板预览
  9. * 高级群发 新增预览 编辑预览 详情预览
  10. */
  11. type Props = {
  12. data: any,
  13. del?: boolean,
  14. newsDispatch?: (type: any) => void,
  15. isShowEdit?: boolean,
  16. showDrawer?: (props?: any) => void,//图文素材弹窗事件
  17. isShowEditBtn?: boolean,//是否开启编辑内容按钮
  18. actionId?: number,
  19. linsFn?: (params: any) => void
  20. }
  21. interface State {
  22. mediaIds: number[],
  23. checked: boolean,
  24. indeterminate: boolean,
  25. links?: string[],//当前图文中的原文链接数组
  26. linksSR?: any
  27. }
  28. interface Action {
  29. type: 'pushData' | 'checked' | 'delData' | 'returnData' | 'setLinks' | 'setLinksSR',
  30. params?: any
  31. }
  32. function reducer(state: State, action: Action) {
  33. let { type, params } = action
  34. switch (type) {
  35. case 'pushData':
  36. return { ...state, mediaIds: [...state?.mediaIds, ...params?.mediaIds] }
  37. case 'delData':
  38. let mediaIds = [...state?.mediaIds]
  39. mediaIds = mediaIds.filter((mediaId: number) => {
  40. if (params?.mediaIds?.every((id: number) => id !== mediaId)) {
  41. return mediaId
  42. }
  43. return
  44. })
  45. return { ...state, mediaIds }
  46. case 'returnData':
  47. return { ...state, mediaIds: params?.mediaIds }
  48. case 'checked':
  49. return { ...state, checked: params?.checked, indeterminate: params?.indeterminate }
  50. case 'setLinks':
  51. console.log('setLinks')
  52. return { ...state, links: params?.links }
  53. case 'setLinksSR':
  54. let newlinksSR = { ...state.linksSR }
  55. Object.keys(params?.links).forEach(key => {
  56. newlinksSR[key] = params?.links[key]
  57. })
  58. return { ...state, linksSR: newlinksSR }
  59. default:
  60. return { ...state }
  61. }
  62. }
  63. const initState = {
  64. mediaIds: [],
  65. checked: false,
  66. indeterminate: false,
  67. links: [],
  68. linksSR: {}
  69. }
  70. const WxDetailsBox = React.memo((props: Props) => {
  71. const { data, del = false, newsDispatch, isShowEdit = false, showDrawer, isShowEditBtn = true, actionId, linsFn } = props
  72. const [state, dispatch] = useReducer(reducer, initState)
  73. const { mediaIds, checked, indeterminate, links, linksSR } = state
  74. const [isEdit, setIsEdit] = useState<boolean>(false)
  75. const num = useMemo(() => { return data?.content?.news?.length }, [data])
  76. const { dispatch: dDispatch } = useModel('useOperating.useMaterialDrawer')//素材内容管理器
  77. // const { getWXInfo } = useModel('useOperating.useMaterialContent')//获取微信素材详情
  78. // const { get } = useModel('useOperating.useBdMediaPup')//获取本地素材详情
  79. const { delMsgGroup, getDetail } = useModel('useOperating.useNews', model => ({ delMsgGroup: model.delMsgGroup, getDetail: model.getDetail }))
  80. /**手动点击添加 */
  81. const handleAdd = useCallback((mediaId: number) => {
  82. dispatch({ type: 'pushData', params: { mediaIds: [mediaId] } })
  83. }, [num])
  84. /**手动点击删除 */
  85. const handleDel = useCallback((mediaId: number) => {
  86. dispatch({ type: 'delData', params: { mediaIds: [mediaId] } })
  87. }, [num])
  88. /**反选 */
  89. const handelReturn = useCallback(() => {
  90. let articles = data?.content?.news
  91. let newMediaIds = articles?.filter((item: { id: number, deleted: boolean }) => {
  92. if (mediaIds.every((mediaId: number) => mediaId !== item.id) && !item.deleted) {
  93. return item.id
  94. }
  95. return
  96. })
  97. let newArr: number[] = []
  98. newMediaIds.forEach((item: { id: number }) => {
  99. newArr.push(item.id)
  100. })
  101. dispatch({ type: 'returnData', params: { mediaIds: newArr } })
  102. }, [data, mediaIds])
  103. /**全选 */
  104. const handleAll = useCallback(() => {
  105. if (mediaIds?.length === num) {
  106. dispatch({ type: 'returnData', params: { mediaIds: [] } })
  107. } else {
  108. let newArr: number[] = []
  109. data?.content?.news?.forEach((item: { id: number, deleted: boolean }) => {
  110. if (!item.deleted) {
  111. newArr.push(item.id)
  112. }
  113. })
  114. dispatch({ type: 'returnData', params: { mediaIds: newArr } })
  115. }
  116. }, [data, mediaIds, num])
  117. /**删除 */
  118. const deleteNews = useCallback(() => {
  119. if (mediaIds?.length > 0) {
  120. delMsgGroup.run({ id: data.id, list: mediaIds }).then((res) => {
  121. if (res) {
  122. getDetail.refresh().then((res: any) => {
  123. let { mediaInfo, msgType, mediaId } = res
  124. let { name, url, title, } = mediaInfo
  125. let theEdit = {
  126. url: url,
  127. title: name || title,
  128. msgType,
  129. mediaId,
  130. content: mediaInfo,//NEWS使用
  131. }
  132. if (res) {
  133. console.log(res)
  134. newsDispatch && newsDispatch({ type: 'setDefaultData', params: { defaultData: { ...res, theEdit } } })
  135. dispatch({ type: 'pushData', params: { mediaIds: [] } })
  136. dispatch({ type: 'checked', params: { checked: false, indeterminate: false } })
  137. }
  138. })
  139. }
  140. })
  141. }
  142. }, [mediaIds, data])
  143. useEffect(() => {
  144. let len = mediaIds?.length
  145. if (len < num && len !== 0) {
  146. dispatch({ type: 'checked', params: { checked: true, indeterminate: true } })
  147. }
  148. if (len === num) {
  149. dispatch({ type: 'checked', params: { checked: true, indeterminate: false } })
  150. }
  151. if (len === 0) {
  152. dispatch({ type: 'checked', params: { checked: false, indeterminate: false } })
  153. }
  154. }, [mediaIds, num])
  155. //编辑链接
  156. const editLinks = useCallback(() => {
  157. if (data && data?.msgType === 'mpnews') {
  158. let links: string[] = []
  159. data?.content?.news?.forEach((item: { contentSourceUrl: string }) => {
  160. links.push(item?.contentSourceUrl || '')
  161. })
  162. dispatch({ type: 'setLinks', params: { links } })
  163. }
  164. }, [data])
  165. //编辑内容
  166. const editContent = useCallback(() => {
  167. dDispatch({ type: 'actionData', params: { data: { dataArr: data?.content?.news, actionId: 1 } } })//设置编辑器的内容
  168. showDrawer && showDrawer({ isEdit: true, editId: data?.id })//开启新建素材弹窗
  169. }, [showDrawer, data])
  170. //保存链接
  171. const saveLink = useCallback(() => {
  172. if (!actionId) {
  173. //替换文章中的链接
  174. let news = data?.content?.news?.map((item: { contentSourceUrl: string }, index: number) => {
  175. if (item.contentSourceUrl !== links[index]) {
  176. item.contentSourceUrl = links[index]
  177. }
  178. return item
  179. })
  180. setIsEdit(false)
  181. }else{
  182. setIsEdit(false)
  183. }
  184. }, [links, data, actionId, linksSR])
  185. //默认全选
  186. useEffect(() => {
  187. handleAll()
  188. }, [])
  189. const Box = useCallback(() => {
  190. switch (data?.msgType) {
  191. case 'mpnews':
  192. return <>
  193. {
  194. del && <div className={style.details_header}>
  195. <Space className={style.btn}>
  196. <Checkbox indeterminate={indeterminate} onChange={handleAll} checked={checked}>全选</Checkbox>
  197. <Button onClick={handelReturn} type='dashed' size='small'>反选</Button>
  198. </Space>
  199. <Popconfirm
  200. title='确定要删除吗?'
  201. onConfirm={deleteNews}
  202. placement='left'
  203. >
  204. <Button type='primary' size='small'>删除</Button>
  205. </Popconfirm>
  206. </div>
  207. }
  208. <div className={style.box_card}>
  209. {
  210. data?.content?.news?.map((list: any, index: number) => {
  211. let isAction = mediaIds?.length > 0 ? mediaIds.some((mediaId: number) => mediaId === list.id) : false
  212. let isOne = data?.content?.news?.length === 1
  213. // if (list.deleted) {
  214. return (
  215. <div
  216. className={`${style.img_ScwxBox} ${del && isAction ? style.action : ''} `}
  217. key={(list.thumbMediaId || list.sysMediaId || list.id) + index + (list.menuId || '')}
  218. onClick={() => {
  219. if (!list.deleted) {
  220. if (isAction) {
  221. handleDel(list.id)
  222. } else {
  223. handleAdd(list.id)
  224. }
  225. }
  226. }
  227. }>
  228. {!isOne && <span><span>{list.title}</span></span>}
  229. <img src={list?.thumbMediaUrl || list?.thumbUrl} />
  230. {isOne && <div className={style.one}>
  231. <p>{list.title}</p>
  232. <p>{list.digest}</p>
  233. </div>}
  234. {
  235. list.deleted && <div className={style.deleted}>已删除</div>
  236. }
  237. </div>
  238. )
  239. // }
  240. return
  241. })
  242. }
  243. </div>
  244. </>
  245. case 'news':
  246. return <div className={style.box_card}>
  247. {
  248. data?.newsList?.map((list: any, index: number) => {
  249. let isOne = data?.newsList?.length === 1
  250. return <div className={`${style.img_ScwxBox} `} key={index}>
  251. {!isOne && <span>{list?.title}</span>}
  252. <img src={list?.knewsThumbUrl || (list?.knewsThumbId ? filePng : 'https://s.weituibao.com/static/1552098829922/bigfm.png')} />
  253. {isOne && <div className={style.one}>
  254. <p>{list?.title}</p>
  255. <p>{list?.description}</p>
  256. </div>}
  257. </div>
  258. })
  259. }
  260. </div>
  261. case 'mpvideo':
  262. return <div className={style.mpvideo}>
  263. <img src={require('../../../public/image/video.png')} />
  264. <p>{data?.name || data?.title}</p>
  265. </div>
  266. case 'video':
  267. return <div className={style.mpvideo}>
  268. <img src={require('../../../public/image/video.png')} />
  269. <p>{data?.name || data?.title}</p>
  270. </div>
  271. case 'voice':
  272. return <div className={style.voice}>
  273. <p>{data?.name || data?.title}</p>
  274. <img src={require('../../../public/image/voice.png')} />
  275. </div>
  276. case 'image':
  277. return <div className={style.image}>
  278. <img src='https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png' />
  279. <img src={data?.url || data?.thumbUrl || data?.imageUrl} />
  280. </div>
  281. case 'text':
  282. let str = ''
  283. if (Array.isArray(data?.textContent)) {
  284. data?.textContent?.forEach((key: string, index: number) => {
  285. // if (key) {
  286. str += `${key}${index !== data?.textContent.length - 1 ? '<br/>' : ''}`
  287. // }
  288. })
  289. } else {
  290. str = data?.textContent
  291. }
  292. return <div className={style.text}>
  293. <img src='https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png' />
  294. <p dangerouslySetInnerHTML={{ __html: str }}></p>
  295. </div>
  296. case 'tem':
  297. let { title, dataTem } = data?.data
  298. let temStr = dataTem?.replace(/\n/g, '<br>')
  299. return <>
  300. {
  301. dataTem ? <div className={style.temCon}>
  302. <div className={style.title}>{title}</div>
  303. <div className={style.rq}>{moment(new Date()).format('MM月DD日')}</div>
  304. <p dangerouslySetInnerHTML={{ __html: temStr }}></p>
  305. </div> :
  306. <div></div>
  307. }
  308. </>
  309. case 'miniprogrampage':
  310. return <div className={style.miniprogrampage}>
  311. <span className={style.miniprogrampage_title}>{data?.title}</span>
  312. <img className={style.miniprogrampage_img} src={data?.imageUrl} />
  313. <span className={style.miniprogrampage_bottom}>小程序</span>
  314. </div>
  315. }
  316. return <></>
  317. }, [data, del, mediaIds, indeterminate, checked])
  318. return <div className={style.box}>
  319. <div className={style.top} />
  320. <div className={style.content} >
  321. {
  322. isEdit ? actionId ? <div>
  323. {//存在actionID表示私人定制多选批量
  324. (linksSR[actionId] || links)?.map((url: string, index: number) => {//存在编辑过的链接使用编辑过的,没有就用默认
  325. return <div key={index} style={{ marginBottom: 15 }}>
  326. <span>第{index + 1}篇:</span>
  327. <Input.TextArea value={url} onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
  328. let newLinks = JSON.parse(JSON.stringify(linksSR[actionId] || links))
  329. newLinks[index] = event.target.value
  330. let newlinksSR = { ...state.linksSR }
  331. newlinksSR[actionId] = newLinks
  332. console.log()
  333. linsFn && linsFn(newlinksSR)
  334. dispatch({ type: 'setLinksSR', params: { links: { [actionId]: newLinks } } })
  335. }} disabled={!isShowEdit}/>
  336. </div>
  337. })
  338. }
  339. </div> : <div>
  340. {//常规操作没有批量
  341. links?.map((url: string, index: number) => {
  342. return <div key={index} style={{ marginBottom: 15 }}>
  343. <span>第{index + 1}篇:</span>
  344. <Input.TextArea value={url} onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
  345. let newLinks = JSON.parse(JSON.stringify(links))
  346. newLinks[index] = event.target.value
  347. console.log(3333, event.target.value)
  348. dispatch({ type: 'setLinks', params: { links: newLinks } })
  349. }} disabled={!isShowEdit}/>
  350. </div>
  351. })
  352. }
  353. </div> : <Box />
  354. }
  355. {
  356. data?.msgType === 'mpnews' && <Space style={{ width: '100%', display: 'flex', justifyContent: 'center' }}>
  357. {
  358. isEdit ?
  359. <>
  360. {isShowEdit && <Button onClick={saveLink}>保存</Button>}
  361. <Button onClick={() => { setIsEdit(false) }}>取消</Button>
  362. </>
  363. :
  364. <>
  365. <Button onClick={() => { setIsEdit(true); editLinks() }}>{isShowEdit ? '编辑链接':'查看连接'}</Button>
  366. {isShowEditBtn && <Button onClick={editContent}>编辑内容</Button>}
  367. </>
  368. }
  369. </Space>
  370. }
  371. </div>
  372. </div>
  373. })
  374. export default WxDetailsBox