addGroupObject.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. import { App, Badge, Button, Card, Empty, Form, Input, InputNumber, Modal, Popover, Radio, Select, Space, Table, Typography } from 'antd';
  2. import React, { useEffect, useState } from 'react';
  3. import { PlusOutlined, QuestionCircleFilled } from '@ant-design/icons';
  4. import FilterUser from '@/pages/weComTask/components/filterUser';
  5. import MindTags from '@/pages/weComTask/components/mindTags';
  6. import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
  7. import { useAjax } from '@/Hook/useAjax';
  8. import FilterUserTooltip from '@/pages/weComTask/components/filterUser/filterUserTooltip';
  9. import { businessPlanData } from '@/pages/weComTask/page/businessPlan/create/const';
  10. import { ColumnsType } from 'antd/es/table';
  11. const { Text, Paragraph } = Typography;
  12. /**
  13. * 进群对象新建组件
  14. * @param param0
  15. * @returns
  16. */
  17. const AddGroupObject: React.FC<GROUP_CHAT_CREATE.AddGroupObjectProps> = ({ value = [], onChange, onCopy, bookPlatForm, index, strategyList, bookList }) => {
  18. /******************************************/
  19. const { message } = App.useApp();
  20. const [visible, setVisible] = useState<boolean>(false)
  21. const [initialValues, setInitialValues] = useState<any>(undefined)
  22. const [copyData, setCopyData] = useState<{ visible?: boolean, data?: any }>()
  23. const [copyIndex, setCopyIndex] = useState<number>()
  24. /******************************************/
  25. return <>
  26. {value?.length > 0 ? <ShowGroupUserTable
  27. value={value}
  28. bookList={bookList}
  29. bookPlatForm={bookPlatForm}
  30. handleDelete={(id) => {
  31. onChange?.(value.filter((_, index) => index + 1 !== id))
  32. }}
  33. handleEdit={(record) => {
  34. setInitialValues(record)
  35. setVisible(true)
  36. }}
  37. handleCopy={(record) => {
  38. const newValue = { ...record, id: Date.now(), groupObjectName: `copy策略${index + 1}_` + record.groupObjectName }
  39. setCopyData({ visible: true, data: newValue })
  40. }}
  41. /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='暂无群配置' />}
  42. <Button type="dashed" style={{ width: '100%' }} block icon={<PlusOutlined />} onClick={() => setVisible(true)}>新建群配置</Button>
  43. {visible && <AddGroupObjectModal
  44. visible={visible}
  45. initialValues={initialValues}
  46. onChange={(values) => {
  47. const newValue = JSON.parse(JSON.stringify(value))
  48. if (initialValues?.id) {
  49. newValue[initialValues.id - 1] = values
  50. } else {
  51. newValue.push(values)
  52. }
  53. onChange?.(newValue)
  54. setVisible(false)
  55. setInitialValues(undefined)
  56. }}
  57. onClose={() => {
  58. setVisible(false)
  59. setInitialValues(undefined)
  60. }}
  61. />}
  62. {copyData?.visible && <Modal
  63. title={<strong>复制至</strong>}
  64. open={copyData?.visible}
  65. onCancel={() => setCopyData(undefined)}
  66. onOk={() => {
  67. if (copyIndex) {
  68. onCopy?.(copyData.data, copyIndex)
  69. setCopyData(undefined)
  70. setCopyIndex(undefined)
  71. setVisible(false)
  72. } else {
  73. message.error('清选择复制到哪')
  74. }
  75. }}
  76. >
  77. <Select
  78. style={{ width: '100%' }}
  79. onChange={(e) => {
  80. setCopyIndex(e)
  81. }}
  82. placeholder="请选择复制到哪"
  83. value={copyIndex}
  84. options={strategyList.map((item, index) => ({ value: index + 1, label: item.strategyName }))}
  85. />
  86. </Modal>}
  87. </>
  88. };
  89. export const ShowGroupUserTable: React.FC<GROUP_CHAT_CREATE.ShowGroupUserTableProps> = React.memo(({ bookList, bookPlatForm, value, handleEdit, handleDelete, handleCopy, isPreview }) => {
  90. const columns: ColumnsType<any> = [
  91. {
  92. title: '群名称',
  93. dataIndex: 'groupObjectName',
  94. key: 'groupObjectName',
  95. width: 120,
  96. ellipsis: true,
  97. fixed: 'left'
  98. },
  99. {
  100. title: '是否开启已有旧群聊补缺',
  101. dataIndex: 'isRepair',
  102. key: 'isRepair',
  103. width: 150,
  104. ellipsis: true,
  105. render(value, record, index) {
  106. return value ? <Space>
  107. <Badge status="success" text="开启" />
  108. <span>近{record.repairTimes}天</span>
  109. </Space> : <Badge status="error" text="关闭" />
  110. },
  111. },
  112. {
  113. title: '进群对象',
  114. dataIndex: 'externalUserFilter',
  115. key: 'externalUserFilter',
  116. width: 90,
  117. align: 'center',
  118. render(value) {
  119. return value ? <div style={{ display: 'flex', alignItems: 'center' }}>
  120. <div style={{ flex: '1 0', overflow: 'hidden' }}>
  121. <Text ellipsis>指定</Text>
  122. </div>
  123. <Popover
  124. placement="right"
  125. styles={{ body: { maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' } }}
  126. mouseEnterDelay={0.5}
  127. content={<FilterUserTooltip
  128. bookCityList={bookPlatForm?.map(item => ({ label: item.platformName, value: item.platformKey }))}
  129. configName={value?.configName}
  130. data={value?.configContent}
  131. />}
  132. >
  133. <a style={{ color: '#000' }}><QuestionCircleFilled /></a>
  134. </Popover>
  135. </div> : '全部'
  136. },
  137. },
  138. {
  139. title: '群递增起始编号',
  140. dataIndex: 'groupIndex',
  141. key: 'groupIndex',
  142. width: 60,
  143. align: 'center'
  144. },
  145. {
  146. title: '群固定人数',
  147. dataIndex: 'groupUserCount',
  148. key: 'groupUserCount',
  149. width: 50,
  150. align: 'center'
  151. },
  152. {
  153. title: '邀请客户进群完毕后客服号是否退群',
  154. dataIndex: 'autoOutGroup',
  155. key: 'autoOutGroup',
  156. width: 85,
  157. align: 'center',
  158. render(value) {
  159. return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
  160. },
  161. },
  162. {
  163. title: '是否排除已在群的客户',
  164. dataIndex: 'excludeInGroup',
  165. key: 'excludeInGroup',
  166. width: 60,
  167. align: 'center',
  168. render(value) {
  169. return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
  170. },
  171. },
  172. {
  173. title: '拉群完成后自动删除拉群标签',
  174. dataIndex: 'excludeInGroup',
  175. key: 'excludeInGroup',
  176. width: 70,
  177. align: 'center',
  178. render(value) {
  179. return value ? <span style={{ color: '#1890ff' }}>是</span> : <span style={{ color: 'red' }}>否</span>
  180. },
  181. },
  182. {
  183. title: '群聊关联公众号',
  184. dataIndex: 'weChatAppid',
  185. key: 'weChatAppid',
  186. width: 100,
  187. align: 'center',
  188. ellipsis: true,
  189. render(value) {
  190. return value || '--'
  191. },
  192. },
  193. {
  194. title: '拉群完成后群聊备注',
  195. dataIndex: 'remark',
  196. key: 'remark',
  197. width: 140,
  198. ellipsis: true,
  199. render(value) {
  200. return value || '--'
  201. },
  202. },
  203. {
  204. title: '拉群完成后群聊智能标签',
  205. dataIndex: 'tagDTO',
  206. key: 'tagDTO',
  207. width: 200,
  208. ellipsis: true,
  209. render(value) {
  210. return value && Object.keys(value)?.length > 0 ?
  211. <Paragraph ellipsis style={{ margin: 0 }}>{Object.keys(value).map(key => {
  212. if (key === 'business' && value[key]) {
  213. return `业务(来源渠道):${businessPlanData.find(i => i.value === value.business)?.label || '<空>'}`
  214. } else if (key === 'bookCity' && value[key]) {
  215. return `书城(来源渠道):${bookPlatForm.find(i => i.id === value.bookCity)?.platformName || '<空>'}`
  216. } else if (key === 'product' && value[key]) {
  217. return `产品(来源渠道):${bookList.find(i => i.id === value.product)?.bookName || '<空>'}`
  218. }
  219. return ''
  220. }).join('、')}</Paragraph> : '--'
  221. },
  222. },
  223. {
  224. title: '建群成功发送内容',
  225. dataIndex: 'groupSendMsg',
  226. key: 'groupSendMsg',
  227. width: 140,
  228. ellipsis: true
  229. },
  230. {
  231. title: '操作',
  232. dataIndex: 'cz',
  233. key: 'cz',
  234. width: 100,
  235. fixed: 'right',
  236. align: 'center',
  237. render(_, record) {
  238. return <Space>
  239. <a onClick={() => {
  240. handleCopy?.(record)
  241. }}>复制</a>
  242. <a onClick={() => {
  243. handleEdit?.(record)
  244. }}>修改</a>
  245. <a style={{ color: 'red' }} onClick={() => {
  246. handleDelete?.(record.id)
  247. }}>删除</a>
  248. </Space>
  249. },
  250. }
  251. ]
  252. if (isPreview) {
  253. columns.pop()
  254. }
  255. return <Table
  256. size='small'
  257. bordered
  258. rowKey={'id'}
  259. dataSource={value}
  260. scroll={{ y: 1000 }}
  261. columns={columns}
  262. />
  263. })
  264. const AddGroupObjectModal: React.FC<GROUP_CHAT_CREATE.AddGroupObjectModalProps> = React.memo(({ visible, onChange, onClose, initialValues }) => {
  265. /******************************************/
  266. const { message } = App.useApp();
  267. const [form] = Form.useForm();
  268. const externalUserType = Form.useWatch('externalUserType', form);
  269. const isRepair = Form.useWatch('isRepair', form);
  270. const getBindMpList = useAjax(() => getBindMpListApi())
  271. /******************************************/
  272. useEffect(() => {
  273. getBindMpList.run()
  274. }, [])
  275. const handleOk = () => {
  276. form.validateFields().then((values) => {
  277. if (initialValues?.id) {
  278. values.id = initialValues.id
  279. } else {
  280. values.id = Date.now();
  281. }
  282. console.log(values)
  283. onChange?.(values)
  284. }).catch(() => {
  285. form.submit()
  286. });
  287. }
  288. return <Modal
  289. title={<strong>{(initialValues && Object.keys(initialValues).length > 0) ? '修改' : '新建'}群配置</strong>}
  290. open={visible}
  291. onCancel={onClose}
  292. width={600}
  293. onOk={handleOk}
  294. styles={{ body: { maxHeight: 550, overflowY: 'auto' } }}
  295. >
  296. <Form
  297. form={form}
  298. name="addGroupUser"
  299. labelAlign='left'
  300. labelCol={{ span: 9 }}
  301. colon={false}
  302. labelWrap
  303. scrollToFirstError={{
  304. behavior: 'smooth',
  305. block: 'center'
  306. }}
  307. onFinishFailed={({ errorFields }) => {
  308. message.error(errorFields?.[0]?.errors?.[0])
  309. }}
  310. onFinish={handleOk}
  311. initialValues={(initialValues && Object.keys(initialValues).length > 0) ? initialValues : { externalUserType: 'all', isRepair: false, groupIndex: 1, groupUserCount: 37, autoOutGroup: false, excludeInGroup: true, deleteGroupTag: true }}
  312. preserve={true}
  313. >
  314. <Form.Item
  315. name={'groupObjectName'}
  316. label={<strong>群名称</strong>}
  317. rules={[{ required: true, message: '请输入群名称!' }]}
  318. >
  319. <Input placeholder='请输入群名称' allowClear />
  320. </Form.Item>
  321. <Form.Item
  322. name={'isRepair'}
  323. label={<strong>是否开启已有旧群聊补缺</strong>}
  324. tooltip={{ title: '优先将客户补充进已有旧群聊的空缺位置,待旧群满员后再创建新群容纳剩余客户。', placement: 'top' }}
  325. rules={[{ required: true, message: '请选择是否开启群补人!' }]}
  326. >
  327. <Radio.Group options={[{ label: '开启', value: true }, { label: '关闭', value: false }]} />
  328. </Form.Item>
  329. {isRepair && <Form.Item
  330. name={'repairTimes'}
  331. label={<strong>补缺群聊范围</strong>}
  332. rules={[{ required: true, message: '请选择补缺群聊范围!' }]}
  333. tooltip={{ title: '可补缺的群聊范围:在本建群策略内创建的群聊', placement: 'top' }}
  334. >
  335. <Select
  336. showSearch
  337. allowClear
  338. placeholder="请选择补缺群聊范围"
  339. filterOption={(input, option) =>
  340. (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
  341. }
  342. options={Array(30).fill('').map((_, index) => ({ label: `近${index + 1}天`, value: index + 1 }))}
  343. />
  344. </Form.Item>}
  345. <Form.Item
  346. label={<strong>进群对象配置</strong>}
  347. required
  348. >
  349. <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
  350. <Form.Item
  351. name={'externalUserType'}
  352. rules={[{ required: true, message: '请选择转移对象!' }]}
  353. noStyle
  354. >
  355. <Radio.Group options={[{ label: '全部', value: 'all' }, { label: '指定', value: 'specify' }]} />
  356. </Form.Item>
  357. {externalUserType === 'specify' && <div style={{ marginTop: 8, width: '100%' }}>
  358. <Form.Item
  359. name={'externalUserFilter'}
  360. rules={[{ required: true, message: '请选择人群包!' }]}
  361. noStyle
  362. >
  363. <FilterUser configType={'USER_GROUP'} />
  364. </Form.Item>
  365. </div>}
  366. </div>
  367. </Form.Item>
  368. <Form.Item
  369. name={'groupIndex'}
  370. tooltip={{ title: `例如:起始编号为'1'群名为'测试群',再建群后群名为'测试群1'超出群人数的用户将分配到'测试群2'依次递增`, placement: 'top' }}
  371. label={<strong>群递增起始编号</strong>}
  372. rules={[{ required: true, message: '请输入群递增起始编号!' }]}
  373. >
  374. <InputNumber placeholder='请输入群递增起始编号' min={1} style={{ width: '100%' }} />
  375. </Form.Item>
  376. <Form.Item
  377. name={'groupUserCount'}
  378. tooltip={{ title: `不包括建群人和固定企微号,最大上限数量38`, placement: 'top' }}
  379. label={<strong>群固定人数</strong>}
  380. rules={[{ required: true, message: '请输入群固定人数!' }]}
  381. >
  382. <InputNumber placeholder='请输入群固定人数' max={38} min={1} style={{ width: '100%' }} />
  383. </Form.Item>
  384. <Form.Item
  385. name={'autoOutGroup'}
  386. label={<strong>邀请客户进群完毕后客服号是否退群</strong>}
  387. rules={[{ required: true, message: '请选择是否退群!' }]}
  388. >
  389. <Radio.Group>
  390. <Radio value={true}>是</Radio>
  391. <Radio value={false}>否</Radio>
  392. </Radio.Group>
  393. </Form.Item>
  394. <Form.Item
  395. name={'excludeInGroup'}
  396. label={<strong>是否排除已在群的客户</strong>}
  397. rules={[{ required: true, message: '请选择是否排除已在群的客户!' }]}
  398. >
  399. <Radio.Group>
  400. <Radio value={true}>是</Radio>
  401. </Radio.Group>
  402. </Form.Item>
  403. <Form.Item
  404. name={'deleteGroupTag'}
  405. label={<strong>拉群完成后自动删除拉群标签</strong>}
  406. rules={[{ required: true, message: '请选择拉群完成后自动删除拉群标签!' }]}
  407. >
  408. <Radio.Group>
  409. <Radio value={true}>是</Radio>
  410. </Radio.Group>
  411. </Form.Item>
  412. <Form.Item
  413. name={'weChatAppid'}
  414. label={<strong>群聊关联公众号</strong>}
  415. >
  416. <Select
  417. showSearch
  418. allowClear
  419. placeholder="选择公众号"
  420. filterOption={(input, option) =>
  421. (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
  422. }
  423. options={getBindMpList?.data?.data?.map(item => ({ label: item.name, value: item.name + '_' + item.id }))}
  424. />
  425. </Form.Item>
  426. <Form.Item
  427. name={'remark'}
  428. label={<strong>拉群完成后群聊备注</strong>}
  429. >
  430. <Input.TextArea placeholder="请输入群聊备注" />
  431. </Form.Item>
  432. <Card title={<strong>拉群完成后群聊智能标签</strong>} style={{ background: '#fff', marginBottom: 10 }} styles={{ body: { padding: '6px 0 6px 16px' } }}>
  433. <Form.Item
  434. name={'tagDTO'}
  435. style={{ marginBottom: 0 }}
  436. >
  437. <MindTags />
  438. </Form.Item>
  439. </Card>
  440. <Form.Item
  441. name={'groupSendMsg'}
  442. label={<strong>建群成功发送内容</strong>}
  443. rules={[{ required: true, message: '请输入建群成功发送内容!' }]}
  444. >
  445. <Input.TextArea placeholder="请输入建群成功发送内容" />
  446. </Form.Item>
  447. </Form>
  448. </Modal>
  449. })
  450. export default React.memo(AddGroupObject);