index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import React, { useCallback, useEffect, useState } from 'react';
  2. import style from '../../businessPlan/create/index.less'
  3. import { useAjax } from '@/Hook/useAjax';
  4. import { welcomeMsgJobTypeApi } from '@/pages/weComTask/API/weMaterial/weMaterial';
  5. import { addTaskApi, getCreateDetailsApi, updateTaskApi } from '@/pages/weComTask/API/businessPlan/create';
  6. import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
  7. import { App, Button, Card, Empty, Form, Input, Popconfirm, Select, Space, Spin, Table } from 'antd';
  8. import SelectCorpUserGroup from '../../corpUserManage/selectCorpUserGroup';
  9. import { useNavigate } from 'react-router-dom';
  10. import { toJS } from 'mobx';
  11. import { inject, observer } from 'mobx-react';
  12. import Strategy from './components/Strategy';
  13. import Content from './components/content';
  14. import { SaveOutlined, RedoOutlined, SearchOutlined, PlusOutlined } from '@ant-design/icons';
  15. import SelectCorpUser from '../../corpUserManage/selectCorpUser';
  16. import { monmentsColumns } from './tableConfig';
  17. import SubmitModal from '../../businessPlan/create/submitModal';
  18. import { groupBy } from '@/utils/utils';
  19. export const DispatchMomentsTaskCreate = React.createContext<TASK_MOMENTS_CREATE.DispatchMomentsTaskCreate | null>(null);
  20. /**
  21. * 朋友圈创建
  22. * @param param0
  23. * @returns
  24. */
  25. const MomentsCreatePage: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
  26. /*************************************/
  27. const navigate = useNavigate();
  28. const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
  29. const { message, modal } = App.useApp()
  30. const [settings, setSettings] = useState<TASK_MOMENTS_CREATE.SettingsProps>();
  31. const [msgJobTypeList, setMsgJobTypeList] = useState<{ value: string, label: string }[]>([])
  32. const [previewData, setPreviewData] = useState<TASK_MOMENTS_CREATE.previewDataProps>([])
  33. const [previewDataOld, setPreviewDataOld] = useState<TASK_MOMENTS_CREATE.previewDataProps>([])
  34. const [mpList, setMplist] = useState<{ label: string, value: number }[]>([])
  35. const [previewContent, setPreviewContent] = useState<{ [x: string]: any }>({})
  36. const [projectId, setProjectId] = useState<number>()
  37. const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
  38. const welcomeMsgJobType = useAjax(() => welcomeMsgJobTypeApi())//获取欢迎语类型
  39. const addTask = useAjax((params) => addTaskApi(params))
  40. const updateTask = useAjax((params) => updateTaskApi(params))
  41. const getCreateDetails = useAjax((params) => getCreateDetailsApi(params))
  42. const getBindMpList = useAjax(() => getBindMpListApi())
  43. /*************************************/
  44. useEffect(() => {
  45. const project = sessionStorage.getItem('MOMENTSTASKID')
  46. if (project) {
  47. const { id, isCopy } = JSON.parse(project)
  48. if (!isCopy) {
  49. setProjectId(id)
  50. }
  51. getCreateDetails.run(id).then(res => {
  52. sessionStorage.removeItem('MOMENTSTASKID')
  53. if (res?.data) {
  54. const { corpUsers, bizType, platform, templateProductId, momentCreateDTO } = res.data
  55. const corpUserGroups = corpUsers.map(item => {
  56. return {
  57. corpUsers: item.corpUserList.map(({ corpUserId, name, corpId, mpAccountId, mpAccountInfo, id }) => {
  58. return { id: id || (corpId + '_' + corpUserId), corpUserId, name, corpName: item.corpName, corpId, mpAccountId, mpAccountName: mpAccountInfo?.name }
  59. })
  60. }
  61. })
  62. const newSettings: TASK_MOMENTS_CREATE.SettingsProps = {
  63. bizType,
  64. platform: Number(platform) as any,
  65. templateProductId,
  66. corpUserGroups,
  67. massSendingStrategy: {
  68. momentSendName: momentCreateDTO?.momentSendName || '',
  69. strategyList: momentCreateDTO?.strategyList || []
  70. }
  71. }
  72. setSettings(newSettings)
  73. let id = 1;
  74. const newPreviewContent: { [x: string]: any } = {};
  75. corpUsers.forEach((cu) => {
  76. const momentSendContent = cu?.momentSendContent;
  77. (momentCreateDTO?.strategyList || []).forEach((str, s_index) => {
  78. const { taskDetail } = str
  79. taskDetail?.contentDTO?.forEach((_, c_index) => {
  80. const content = momentSendContent?.[s_index]?.[c_index] || {}
  81. newPreviewContent[id] = content
  82. id++;
  83. })
  84. });
  85. })
  86. setPreviewContent(newPreviewContent)
  87. }
  88. })
  89. } else {
  90. const task = localStorage.getItem('TASK_MOMENTS_CREATE')
  91. if (task) {
  92. setSettings(JSON.parse(task).settings)
  93. }
  94. }
  95. }, [])
  96. useEffect(() => {
  97. welcomeMsgJobType.run().then(res => {
  98. if (res?.data) {
  99. setMsgJobTypeList(Object.keys(res.data).map(key => ({ value: key, label: res.data[key] })))
  100. }
  101. })
  102. getBindMpList.run().then(res => {
  103. setMplist(res?.data?.map((item: any) => ({ label: item.name, value: item.id })))
  104. })
  105. }, [])
  106. // 预览
  107. const preview = () => {
  108. const { bizType, platform, templateProductId, channel, massSendingStrategy, corpUserGroups } = settings
  109. if (!corpUserGroups || corpUserGroups?.length === 0) {
  110. message.error('请先选择客服号')
  111. return
  112. }
  113. if (!bizType) {
  114. message.error('请选择业务类型')
  115. return
  116. }
  117. if (!platform) {
  118. message.error('请选择书城')
  119. return
  120. }
  121. const list: any[] = []
  122. if (!massSendingStrategy?.strategyList?.every((str, s_index) => {
  123. const { taskDetail, ...sdto } = str
  124. if (taskDetail?.contentDTO?.length) {
  125. return taskDetail?.contentDTO?.every((cd, c_index, row) => {
  126. if (cd?.attachmentList?.length || cd?.text?.content) {
  127. const mediaItem = JSON.parse(JSON.stringify(cd?.attachmentList || []))
  128. if (cd?.text?.content) {
  129. mediaItem.push({
  130. msgType: 'TASK_CONTENT_TEXT',
  131. textContent: cd?.text?.content
  132. })
  133. }
  134. const linkData = []
  135. const miniProgramData = []
  136. const contentReactNode = mediaItem.map(item => {
  137. switch (item.msgType) {
  138. case 'TASK_CONTENT_LINK':
  139. linkData.push(item)
  140. return `<span style="color: red">链接</span>`
  141. case 'TASK_STATUS_MINIPROGRAM':
  142. miniProgramData.push(item)
  143. return `<span style="color: red">小程序</span>`
  144. case 'TASK_STATUS_FILE':
  145. return `<span>文件</span>`
  146. case 'TASK_STATUS_VIDEO':
  147. return `<span>视频</span>`
  148. case 'TASK_CONTENT_IMAGE':
  149. return `<span>图片</span>`
  150. case 'TASK_CONTENT_TEXT':
  151. return `<span>文本</span>`
  152. default:
  153. return `<span style="color: red">请联系管理员</span>`
  154. }
  155. })
  156. list.push({
  157. bizType,
  158. platform,
  159. templateProductId,
  160. channel,
  161. taskName: settings?.massSendingStrategy?.momentSendName,
  162. strategyData: sdto,
  163. contentReactNode,
  164. content: cd,
  165. strategyIndex: s_index,
  166. contentIndex: c_index,
  167. linkData,
  168. miniProgramData,
  169. strategyRowSpan: c_index === 0 ? row.length : 0,
  170. })
  171. return true
  172. } else {
  173. message.error(`策略:${str?.strategyName}请填写发送内容`)
  174. return false
  175. }
  176. })
  177. } else {
  178. message.error(`策略:${str?.strategyName}请填写发送内容`)
  179. return false
  180. }
  181. })) {
  182. return
  183. }
  184. let id = 1
  185. const newPreviewData = corpUserGroups.reduce((pre, cur, index) => {
  186. return pre.concat(...list.map((item, i) => {
  187. return {
  188. ...item,
  189. id: id++,
  190. corpUserGroupName: `客服组${index + 1}`,
  191. corpUsers: cur.corpUsers,
  192. mpAccountId: cur.corpUsers?.[0]?.mpAccountId,
  193. mpAccountName: cur.corpUsers?.[0]?.mpAccountName,
  194. corpUserStrList: cur.corpUsers?.map(item => item.corpId + '_' + item.corpUserId),
  195. groupIndex: index,
  196. groupRowSpan: i === 0 ? list.length : 0, // 用于表格合并
  197. }
  198. }))
  199. }, [])
  200. console.log(newPreviewData)
  201. setPreviewData(newPreviewData)
  202. setPreviewDataOld(newPreviewData)
  203. }
  204. const setTaskName = () => {
  205. if (previewDataOld.every((item, index) => {
  206. if ((item?.linkData?.length > 0 ? previewContent?.[index + 1]?.linkUrl : true) && (item?.miniProgramData?.length > 0 ? (previewContent?.[index + 1]?.miniprogramAppid && previewContent?.[index + 1]?.miniprogramPage) : true)) {
  207. return true
  208. } else {
  209. message.error('请补充图文或者小程序内容')
  210. return false
  211. }
  212. })) {
  213. setSubVisible(true)
  214. }
  215. }
  216. const onSubmit = (values: any) => {
  217. const groupData = groupBy(previewDataOld, (item) => item['groupIndex'], true);
  218. const { bizType, platform, templateProductId, corpUserGroups, massSendingStrategy } = settings
  219. const params = {
  220. ...values,
  221. bizType,
  222. platform,
  223. templateProductId,
  224. corpUsers: corpUserGroups?.map((item, index) => {
  225. const params: { [x: string]: any } = {
  226. corpId: item.corpUsers[0].corpId,
  227. corpUserIds: item.corpUsers.map(item => item.corpUserId)
  228. }
  229. const data = groupData[index] || []
  230. const momentSendContent: any[] = []
  231. data.forEach(d => {
  232. const { strategyIndex, contentIndex, id } = d
  233. if (!momentSendContent[strategyIndex]) momentSendContent[strategyIndex] = [];
  234. momentSendContent[strategyIndex][contentIndex] = previewContent?.[id] || {};
  235. })
  236. params.momentSendContent = momentSendContent
  237. return params
  238. })
  239. }
  240. params.momentCreateDTO = {
  241. momentSendName: massSendingStrategy.momentSendName,
  242. strategyList: massSendingStrategy?.strategyList
  243. }
  244. if (projectId) {
  245. params.projectId = projectId
  246. updateTask.run(params).then(res => {
  247. if (res?.data) {
  248. message.success('修改提交成功')
  249. setProjectId(undefined)
  250. sessionStorage.setItem('CAMPCORP', values?.projectName)
  251. navigate('/weComTask/moments/taskList')
  252. }
  253. })
  254. } else {
  255. addTask.run(params).then(res => {
  256. if (res?.data) {
  257. modal.success({
  258. content: '任务提交成功',
  259. styles: { body: { fontWeight: 700 } },
  260. okText: '跳转任务列表',
  261. closable: true,
  262. onOk: () => {
  263. sessionStorage.setItem('CAMPCORP', values?.projectName)
  264. navigate('/weComTask/moments/taskList')
  265. },
  266. onCancel: () => {
  267. setSubVisible(false)
  268. }
  269. })
  270. }
  271. })
  272. }
  273. }
  274. // 重置表格
  275. const onPreviewReset = () => {
  276. setPreviewData([])
  277. setPreviewDataOld([])
  278. setPreviewContent({})
  279. }
  280. const severBd = () => {
  281. localStorage.setItem('TASK_MOMENTS_CREATE', JSON.stringify({ settings }))
  282. message.success('存储成功')
  283. }
  284. const tableSearch = useCallback((values) => {
  285. console.log(values)
  286. if (values?.mpAccountIds?.length > 0 || values?.corpUserIds?.length > 0) {
  287. let newPreviewData: TASK_MOMENTS_CREATE.previewDataProps = []
  288. const corpUserStrList = values?.corpUserIds?.map(item => item.corpId + '_' + item.corpUserId)
  289. if (previewDataOld?.length > 0) {
  290. newPreviewData = previewDataOld.filter(item => (
  291. (values?.mpAccountIds?.length > 0 ? values.mpAccountIds.includes(item?.mpAccountId) : true)
  292. &&
  293. (corpUserStrList?.length > 0 ? item.corpUserStrList.some(str => corpUserStrList.includes(str)) : true)
  294. )).map(item => ({ ...item, userRowSpan: 1, strategyRowSpan: 1, sendDataRowSpan: 1 }))
  295. }
  296. setPreviewData(newPreviewData)
  297. } else {
  298. setPreviewData(previewDataOld)
  299. }
  300. }, [previewDataOld, previewData])
  301. return <div className={style.create}>
  302. <Spin spinning={getCreateDetails.loading}>
  303. <Card title={<strong>{projectId ? getCreateDetails?.data?.data?.projectName + '任务编辑' : ''}配置区</strong>} className={`${style.card} ${style.config}`}>
  304. <Space wrap>
  305. <Space.Compact>
  306. <Button>客服组</Button>
  307. <SelectCorpUserGroup
  308. value={settings?.corpUserGroups}
  309. onChange={(corpUserGroups) => {
  310. setSettings({
  311. ...settings, corpUserGroups: corpUserGroups.map(item => {
  312. return {
  313. ...item, corpUsers: item.corpUsers.map((item: any) => {
  314. const { corpUserId, name, corpName, corpId, mpAccountId, mpAccountInfo, id } = item
  315. return { corpUserId, name, corpName, corpId, mpAccountId, mpAccountName: mpAccountInfo?.name || item?.mpAccountName, id }
  316. })
  317. }
  318. })
  319. })
  320. onPreviewReset()
  321. }}
  322. />
  323. </Space.Compact>
  324. <Space.Compact>
  325. <Button>业务类型</Button>
  326. <Select
  327. showSearch
  328. style={{ width: 120 }}
  329. allowClear
  330. placeholder="请选择类型"
  331. filterOption={(input, option) =>
  332. ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
  333. }
  334. value={settings?.bizType}
  335. onChange={(e) => {
  336. setSettings({ ...settings, bizType: e })
  337. onPreviewReset()
  338. }}
  339. options={msgJobTypeList.filter(item => item.value === 'novel')}
  340. />
  341. </Space.Compact>
  342. {settings?.bizType === 'novel' ? <>
  343. <Space.Compact>
  344. <Button>书城</Button>
  345. <Select
  346. showSearch
  347. allowClear
  348. placeholder="请选择书城"
  349. style={{ width: 120 }}
  350. filterOption={(input, option) =>
  351. ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
  352. }
  353. value={settings?.platform}
  354. onChange={(e) => {
  355. setSettings({ ...settings, platform: e, templateProductId: undefined })
  356. onPreviewReset()
  357. }}
  358. options={bookPlatForm.map(item => ({ value: item.id, label: item.platformName }))}
  359. />
  360. </Space.Compact>
  361. <Space.Compact>
  362. <Button>适用产品</Button>
  363. <Select
  364. showSearch
  365. style={{ width: 150 }}
  366. allowClear
  367. placeholder="请选择模板适用产品"
  368. filterOption={(input, option) =>
  369. ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
  370. }
  371. value={settings?.templateProductId}
  372. onChange={(e) => {
  373. setSettings({ ...settings, templateProductId: e })
  374. onPreviewReset()
  375. }}
  376. options={bookList.filter(item => settings?.platform ? item.platformId === settings?.platform : true).map(item => ({ value: item.id, label: item.bookName }))}
  377. />
  378. </Space.Compact>
  379. </> : settings?.bizType === 'game' ? <Space.Compact>
  380. <Button>游戏渠道</Button>
  381. <Input
  382. style={{ width: 200 }}
  383. allowClear
  384. placeholder="请输入游戏渠道"
  385. value={settings.channel}
  386. onChange={(e) => {
  387. setSettings({ ...settings, channel: e.target.value })
  388. onPreviewReset()
  389. }}
  390. />
  391. </Space.Compact> : undefined}
  392. </Space>
  393. <div className={style.settingsBody}>
  394. <div className={style.settingsBody_content}>
  395. <DispatchMomentsTaskCreate.Provider
  396. value={{
  397. settings, setSettings,
  398. bookPlatForm,
  399. bookList,
  400. msgJobTypeList,
  401. onPreviewReset
  402. }}
  403. >
  404. {/* 朋友圈策略 */}
  405. <Strategy />
  406. {/* 朋友圈内容 */}
  407. <Content />
  408. </DispatchMomentsTaskCreate.Provider>
  409. </div>
  410. </div>
  411. <Space className={style.bts} wrap>
  412. <Button icon={<SaveOutlined />} onClick={severBd}>存为预设</Button>
  413. <Popconfirm
  414. title="确定清空?"
  415. onConfirm={() => {
  416. setSettings(undefined)
  417. setPreviewData([])
  418. setPreviewDataOld([])
  419. localStorage.removeItem('TASK_MOMENTS_CREATE')
  420. }}
  421. >
  422. <Button icon={<RedoOutlined />} danger>清空配置/预设</Button>
  423. </Popconfirm>
  424. <Button type='primary' onClick={preview}><SearchOutlined />预览任务/配置内容</Button>
  425. </Space>
  426. </Card>
  427. </Spin>
  428. <Card
  429. className={style.card}
  430. style={{ marginTop: 10, marginBottom: 10 }}
  431. extra={previewDataOld?.length > 0 ? <Button type='primary' icon={<PlusOutlined />} onClick={() => {
  432. setTaskName()
  433. }}>提交</Button> : undefined}
  434. >
  435. {previewDataOld?.length > 0 ? <div style={{ minHeight: 300 }}>
  436. <Form
  437. layout={'inline'}
  438. onFinish={tableSearch}
  439. style={{ marginBottom: 10 }}
  440. >
  441. <Form.Item label={<strong>公众号</strong>} name="mpAccountIds">
  442. <Select
  443. showSearch
  444. style={{ minWidth: 160 }}
  445. maxTagCount={1}
  446. placeholder="公众号"
  447. filterOption={(input, option) =>
  448. ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
  449. }
  450. allowClear
  451. mode='multiple'
  452. options={mpList}
  453. />
  454. </Form.Item>
  455. <Space.Compact>
  456. <Button>客服组</Button>
  457. <Form.Item name="corpUserIds">
  458. <SelectCorpUser placeholder="请选择客服号" />
  459. </Form.Item>
  460. </Space.Compact>
  461. <Form.Item>
  462. <Space>
  463. <Button htmlType="reset">重置</Button>
  464. <Button type="primary" htmlType='submit'>搜索</Button>
  465. </Space>
  466. </Form.Item>
  467. </Form>
  468. <Table
  469. dataSource={previewData}
  470. columns={monmentsColumns(bookPlatForm, bookList, bookPlatForm.find(item => item.id === Number(settings?.platform)).platformKey, previewContent, setPreviewContent)}
  471. rowKey={'id'}
  472. bordered={true}
  473. scroll={{ y: 550 }}
  474. pagination={false}
  475. />
  476. </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  477. <Empty description="请先完成模块配置后,再预览" />
  478. </div>}
  479. </Card>
  480. {/* 提交配置 */}
  481. {subVisible && <SubmitModal
  482. visible={subVisible}
  483. loading={addTask.loading || updateTask.loading}
  484. projectName={projectId ? getCreateDetails?.data?.data?.projectName : undefined}
  485. onChange={(values) => {
  486. onSubmit(values)
  487. }}
  488. onClose={() => {
  489. setSubVisible(false)
  490. }}
  491. />}
  492. </div>;
  493. };
  494. export default inject('store')(observer((props: any) => MomentsCreatePage(props.store)))