wjx 1 hete
szülő
commit
65c7df8581
19 módosított fájl, 1897 hozzáadás és 28 törlés
  1. 1 1
      src/layout/index.less
  2. 1 1
      src/layout/index.tsx
  3. 2 1
      src/pages/weComTask/API/businessPlan/typings.d.ts
  4. 2 1
      src/pages/weComTask/components/newsMould/index.less
  5. 8 4
      src/pages/weComTask/page/groupChat/create/components/groupUser/addGroupObject.tsx
  6. 7 5
      src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/index.tsx
  7. 162 0
      src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/previewStrategy.tsx
  8. 239 0
      src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/settingsSendObject.tsx
  9. 158 9
      src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/settingsStrategy.tsx
  10. 69 0
      src/pages/weComTask/page/groupChatSend/official/create/components/content/index.tsx
  11. 81 0
      src/pages/weComTask/page/groupChatSend/official/create/components/content/previewContent.tsx
  12. 238 0
      src/pages/weComTask/page/groupChatSend/official/create/components/content/settingsContent.tsx
  13. 336 6
      src/pages/weComTask/page/groupChatSend/official/create/index.tsx
  14. 201 0
      src/pages/weComTask/page/groupChatSend/official/create/tableConfig.tsx
  15. 15 0
      src/pages/weComTask/page/groupChatSend/official/taskList/index.less
  16. 198 0
      src/pages/weComTask/page/groupChatSend/official/taskList/index.tsx
  17. 71 0
      src/pages/weComTask/page/groupChatSend/official/taskList/log.tsx
  18. 85 0
      src/pages/weComTask/page/groupChatSend/official/taskList/tableConfig.tsx
  19. 23 0
      src/pages/weComTask/page/groupChatSend/official/typings.d.ts

+ 1 - 1
src/layout/index.less

@@ -73,7 +73,7 @@
   width: 168px;
   display: flex;
   align-items: center;
-  justify-content: center;
+  padding-left: 18px;
   text-overflow:ellipsis;
   white-space: nowrap;
 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
src/layout/index.tsx


+ 2 - 1
src/pages/weComTask/API/businessPlan/typings.d.ts

@@ -30,6 +30,7 @@ declare namespace BUSINES_SPLAN_API {
         pageSize: number,
         projectName?: string
         createTimeMin?: string,
-        createTimeMax?: string
+        createTimeMax?: string,
+        projectType?: number
     }
 }

+ 2 - 1
src/pages/weComTask/components/newsMould/index.less

@@ -45,7 +45,8 @@
 }
 
 .miniprogramNews {
-    max-width: 220px;
+    width: 80%;
+    max-width: 220px !important;
     box-sizing: border-box;
 
     &>.imgBack {

+ 8 - 4
src/pages/weComTask/page/groupChat/create/components/groupUser/addGroupObject.tsx

@@ -20,7 +20,7 @@ const AddGroupObject: React.FC<GROUP_CHAT_CREATE.AddGroupObjectProps> = ({ value
     /******************************************/
     const { message } = App.useApp();
     const [visible, setVisible] = useState<boolean>(false)
-    const [initialValues, setInitialValues] = useState<any>(undefined)
+    const [initialValues, setInitialValues] = useState<any>()
     const [copyData, setCopyData] = useState<{ visible?: boolean, data?: any }>()
     const [copyIndex, setCopyIndex] = useState<number>()
     /******************************************/
@@ -32,7 +32,7 @@ const AddGroupObject: React.FC<GROUP_CHAT_CREATE.AddGroupObjectProps> = ({ value
             bookList={bookList}
             bookPlatForm={bookPlatForm}
             handleDelete={(id) => {
-                onChange?.(value.filter((_, index) => index + 1 !== id))
+                onChange?.(value.filter(item => item.id !== id))
             }}
             handleEdit={(record) => {
                 setInitialValues(record)
@@ -48,9 +48,13 @@ const AddGroupObject: React.FC<GROUP_CHAT_CREATE.AddGroupObjectProps> = ({ value
             visible={visible}
             initialValues={initialValues}
             onChange={(values) => {
-                const newValue = JSON.parse(JSON.stringify(value))
+                let newValue = JSON.parse(JSON.stringify(value))
                 if (initialValues?.id) {
-                    newValue[initialValues.id - 1] = values
+                    newValue = newValue.map(item => {
+                        if (item.id === initialValues.id)
+                            return values
+                        return item
+                    })
                 } else {
                     newValue.push(values)
                 }

+ 7 - 5
src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/index.tsx

@@ -1,16 +1,17 @@
 import React, { useContext, useState } from "react";
 import style from '../../../../../businessPlan/create/index.less'
-import { App, Button, Empty } from "antd";
+import { App, Button, Empty, Popconfirm } from "antd";
 import useNewToken from "@/Hook/useNewToken";
 import { DispatchOfficialChatCreate } from "../..";
 import SettingsStrategy from "./settingsStrategy";
+import PreviewStrategy from "./previewStrategy";
 
 const Strategy: React.FC = () => {
 
     /*********************************/
     const { message } = App.useApp()
     const { token } = useNewToken()
-    const { setSettings, settings, onPreviewReset, mpList } = useContext(DispatchOfficialChatCreate)!;
+    const { setSettings, settings, onPreviewReset, mpList, corpList } = useContext(DispatchOfficialChatCreate)!;
     const [newVisible, setNewVisible] = useState<boolean>(false);
     /*********************************/
 
@@ -19,7 +20,7 @@ const Strategy: React.FC = () => {
             <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
                 <div className={style.title}>
                     <span>策略</span>
-                    {/* {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 && <Popconfirm
+                    {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 && <Popconfirm
                         title="确定清空?"
                         onConfirm={() => {
                             setSettings(undefined)
@@ -27,13 +28,13 @@ const Strategy: React.FC = () => {
                         }}
                     >
                         <a style={{ color: 'red' }}>清空</a>
-                    </Popconfirm>} */}
+                    </Popconfirm>}
                 </div>
                 <div className={style.detail}>
                     <div className={style.detail_title}>群发策略配置</div>
                     <div className={style.detail_body}>
                         {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 ? <>
-                            {/* <PreviewStrategy strategyDTO={settings?.strategyDTO} /> */}
+                            <PreviewStrategy strategyDTO={settings?.strategyDTO} mpList={mpList} corpList={corpList} />
                         </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
                     </div>
                 </div>
@@ -48,6 +49,7 @@ const Strategy: React.FC = () => {
             visible={newVisible}
             value={settings?.strategyDTO}
             mpList={mpList}
+            corpList={corpList}
             onClose={() => {
                 setNewVisible(false);
             }}

+ 162 - 0
src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/previewStrategy.tsx

@@ -0,0 +1,162 @@
+import React, { useEffect } from 'react';
+import dayjs from 'dayjs';
+import { Card, Form, Input, Radio, Select } from 'antd';
+import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
+import '../../../../../businessPlan/create/global.less'
+import FilterUser from '@/pages/weComTask/components/filterUser';
+import { DefaultOptionType } from 'antd/es/select';
+
+interface Props {
+    strategyDTO: { [x: string]: any }
+    mpList: DefaultOptionType[]
+    corpList: DefaultOptionType[]
+}
+
+const PreviewStrategy: React.FC<Props> = ({ strategyDTO, mpList, corpList }) => {
+
+    /**************************************/
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form)
+    /**************************************/
+
+    useEffect(() => {
+        if (strategyDTO && Object.keys(strategyDTO).length) {
+            const data = {
+                ...strategyDTO, strategyList: strategyDTO?.strategyList?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        return {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            }
+            form.setFieldsValue(data)
+        }
+    }, [strategyDTO])
+
+    return <Form
+        form={form}
+        name="newGroupChatStrategyShow"
+        labelAlign='left'
+        labelCol={{ span: 5 }}
+        colon={false}
+        preserve={true}
+    >
+        <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
+            <div className='block_tm'>
+                <Form.Item label={<strong>任务名称</strong>} name="groupSendName" rules={[{ required: true, message: '请输入任务名称!' }]}>
+                    <Input placeholder="请输入任务名称" />
+                </Form.Item>
+            </div>
+        </Card>
+        <Form.List name="strategyList">
+            {(fields) => (
+                <>
+                    {fields.map(({ key, name, ...restField }, index) => {
+                        const timeRepeatType = strategyList?.[index]?.timeRepeatType
+                        const sendData = strategyList?.[index]?.sendData
+
+                        return <Card
+                            key={key}
+                            title={<strong>策略{index + 1} 配置</strong>}
+                            style={{ background: '#fff', marginBottom: 10 }}
+                            id={`strategy_${index + 1}`}
+                        >
+                            <div className='block_tm'>
+                                <Form.Item
+                                    {...restField}
+                                    name={[name, 'strategyName']}
+                                    label={<strong>策略名称</strong>}
+                                >
+                                    <Input placeholder='请输入策略名称' />
+                                </Form.Item>
+                                <SendTimeSet isShow active='all' form={form} restField={restField} name={name} timeRepeatType={timeRepeatType} />
+                                <Form.List name={[name, 'sendData']}>
+                                    {(fields) => (
+                                        <>
+                                            {fields.map(({ key, name, ...restField }, i) => {
+                                                return <Card
+                                                    key={i}
+                                                    title={<strong>策略{index + 1} 发送群对象{i + 1} 配置</strong>}
+                                                    style={{ background: '#fff', marginBottom: 10 }}
+                                                >
+                                                    <Form.Item {...restField} label={<strong>发送群对象名称</strong>} name={[name, 'sendGroupName']}>
+                                                        <Input placeholder="请输入发送群对象名称" />
+                                                    </Form.Item>
+                                                    <Form.Item
+                                                        {...restField}
+                                                        label={<strong>群聊关联公众号</strong>}
+                                                        name={[name, 'mpAccountId']}
+                                                    >
+                                                        <Select
+                                                            showSearch
+                                                            allowClear
+                                                            placeholder="选择公众号"
+                                                            filterOption={(input, option) =>
+                                                                (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                            }
+                                                            options={mpList}
+                                                        />
+                                                    </Form.Item>
+                                                    <Form.Item
+                                                        {...restField}
+                                                        label={<strong>群聊主体</strong>}
+                                                        name={[name, 'corpIds']}
+                                                    >
+                                                        <Select
+                                                            showSearch
+                                                            allowClear
+                                                            placeholder="选择群聊主体"
+                                                            filterOption={(input, option) =>
+                                                                (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                            }
+                                                            mode='multiple'
+                                                            options={corpList}
+                                                        />
+                                                    </Form.Item>
+                                                    <Form.Item label={<strong>发送群对象配置</strong>}>
+                                                        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
+                                                            <Form.Item
+                                                                {...restField}
+                                                                name={[name, 'externalUserType']}
+                                                                noStyle
+                                                            >
+                                                                <Radio.Group options={[{ label: '全部', value: 'all' }, { label: '指定', value: 'specify' }]} />
+                                                            </Form.Item>
+                                                            {sendData?.[i]?.externalUserType === 'specify' && <div style={{ marginTop: 8, width: '100%' }}>
+                                                                <Form.Item
+                                                                    {...restField}
+                                                                    name={[name, 'externalUserFilter']}
+                                                                    noStyle
+                                                                >
+                                                                    <FilterUser configType={'GROUP_GROUP'} isSHow/>
+                                                                </Form.Item>
+                                                            </div>}
+                                                        </div>
+                                                    </Form.Item>
+                                                </Card>
+                                            })}
+                                        </>
+                                    )}
+                                </Form.List>
+                            </div>
+                        </Card>
+                    })}
+                </>
+            )}
+        </Form.List>
+    </Form>
+};
+
+export default React.memo(PreviewStrategy);

+ 239 - 0
src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/settingsSendObject.tsx

@@ -0,0 +1,239 @@
+import { App, Button, Empty, Form, Input, Modal, Radio, Select, Space, Table } from 'antd';
+import React, { useState } from 'react';
+import { PlusOutlined } from '@ant-design/icons';
+import FilterUser from '@/pages/weComTask/components/filterUser';
+import { ColumnsType } from 'antd/es/table';
+
+/**
+ * 新增修改发送对象
+ * @param param0 
+ * @returns 
+ */
+const AddSendObject: React.FC<OFFICIAL_CHAT_CREATE.AddSendObjectProps> = ({ mpList, corpList, visible, onChange, onClose, initialValues }) => {
+
+    /**************************************/
+    const { message } = App.useApp();
+    const [form] = Form.useForm();
+    const externalUserType = Form.useWatch('externalUserType', form);
+    /**************************************/
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            if (initialValues?.id) {
+                values.id = initialValues.id
+            } else {
+                values.id = Date.now();
+            }
+            onChange?.(values)
+        }).catch(() => {
+            form.submit()
+        });
+    }
+
+    return <Modal
+        title={<strong>{(initialValues && Object.keys(initialValues).length > 0) ? '修改' : '新建'}群配置</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={600}
+        onOk={handleOk}
+        styles={{ body: { maxHeight: 550, overflowY: 'auto' } }}
+    >
+        <Form
+            form={form}
+            name="AddSendObject"
+            labelAlign='left'
+            labelCol={{ span: 7 }}
+            colon={false}
+            labelWrap
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={(initialValues && Object.keys(initialValues).length > 0) ? initialValues : { externalUserType: 'all' }}
+            preserve={true}
+        >
+            <Form.Item label={<strong>发送群对象名称</strong>} name={'sendGroupName'} rules={[{ required: true, message: '请输入发送群对象名称!' }]}>
+                <Input placeholder="请输入发送群对象名称" style={{ width: 358 }} allowClear />
+            </Form.Item>
+            <Form.Item
+                label={<strong>群聊关联公众号</strong>}
+                name={'weChatAppid'}
+            >
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder="选择公众号"
+                    filterOption={(input, option) =>
+                        (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                    style={{ width: 358 }}
+                    options={mpList}
+                />
+            </Form.Item>
+            <Form.Item
+                label={<strong>群聊主体</strong>}
+                name={'corpId'}
+                rules={[{ required: true, message: '请选择群聊主体!' }]}
+            >
+                <Select
+                    showSearch
+                    allowClear
+                    placeholder="选择群聊主体"
+                    filterOption={(input, option) =>
+                        (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                    style={{ width: 358 }}
+                    options={corpList}
+                />
+            </Form.Item>
+            <Form.Item
+                label={<strong>发送群对象配置</strong>}
+                required
+            >
+                <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
+                    <Form.Item
+                        name={'externalUserType'}
+                        rules={[{ required: true, message: '请选择转移对象!' }]}
+                        noStyle
+                    >
+                        <Radio.Group options={[{ label: '全部', value: 'all' }, { label: '指定', value: 'specify' }]} />
+                    </Form.Item>
+                    {externalUserType === 'specify' && <div style={{ marginTop: 8, width: '100%' }}>
+                        <Form.Item
+                            name={'externalUserFilter'}
+                            rules={[{ required: true, message: '请选择群人群包!' }]}
+                            noStyle
+                        >
+                            <FilterUser configType={'GROUP_GROUP'} />
+                        </Form.Item>
+                    </div>}
+                </div>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+const ShowSendObjectTable: React.FC<OFFICIAL_CHAT_CREATE.ShowSendObjectTable> = ({ mpList, corpList, data, handleDelete, handleEdit, isPreview }) => {
+
+    const columns: ColumnsType<any> = [
+        {
+            title: '发送群对象名称',
+            dataIndex: 'sendGroupName',
+            key: 'sendGroupName',
+            width: 120,
+            ellipsis: true,
+            fixed: 'left'
+        },
+        {
+            title: '群聊关联公众号',
+            dataIndex: 'weChatAppid',
+            key: 'weChatAppid',
+            width: 100,
+            align: 'center',
+            ellipsis: true,
+            render(value) {
+                return mpList?.find(item => item.value === value)?.label || '--'
+            },
+        },
+        {
+            title: '群聊主体',
+            dataIndex: 'corpId',
+            key: 'corpId',
+            width: 100,
+            align: 'center',
+            ellipsis: true,
+            render(value) {
+                return corpList?.find(item => item.value === value)?.label || '--'
+            },
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 100,
+            fixed: 'right',
+            align: 'center',
+            render(_, record) {
+                return <Space>
+                    <a onClick={() => {
+                        handleEdit?.(record)
+                    }}>修改</a>
+                    <a style={{ color: 'red' }} onClick={() => {
+                        handleDelete?.(record.id)
+                    }}>删除</a>
+                </Space>
+            },
+        }
+    ]
+    if (isPreview) {
+        columns.pop()
+    }
+
+    return <Table
+        size='small'
+        bordered
+        rowKey={'id'}
+        dataSource={data}
+        scroll={{ y: 1000 }}
+        columns={columns}
+    />
+}
+
+
+const SettingsSendObject: React.FC<OFFICIAL_CHAT_CREATE.SettingsSendObjectProps> = ({ mpList, corpList, value, onChange }) => {
+
+    /******************************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>()
+    /******************************************/
+
+    return <>
+        {value?.length > 0 ? <ShowSendObjectTable
+            mpList={mpList}
+            corpList={corpList}
+            data={value}
+            handleDelete={(id) => {
+                onChange?.(value.filter(item => item.id !== id))
+            }}
+            handleEdit={(record) => {
+                setInitialValues(record)
+                setVisible(true)
+            }}
+        /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='暂无发送群对象配置' />}
+        <Button type="dashed" style={{ width: '100%' }} block icon={<PlusOutlined />} onClick={() => setVisible(true)}>新建群配置</Button>
+        {visible && <AddSendObject
+            mpList={mpList}
+            corpList={corpList}
+            visible={visible}
+            initialValues={initialValues}
+            onChange={(values) => {
+                console.log(values)
+                const newValue = [] //JSON.parse(JSON.stringify(value || '[]'))
+                // if (initialValues?.id) {
+                //     newValue = newValue.map(item => {
+                //         if (item.id === initialValues.id)
+                //             return values
+                //         return item
+                //     })
+                // } else {
+                //     newValue.push(values)
+                // }
+                newValue.push({a: 1})
+                console.log(newValue)
+                // onChange?.(newValue)
+                // setVisible(false)
+                // setInitialValues(undefined)
+            }}
+            onClose={() => {
+                setVisible(false)
+                setInitialValues(undefined)
+            }}
+        />}
+    </>;
+};
+
+export default React.memo(SettingsSendObject);

+ 158 - 9
src/pages/weComTask/page/groupChatSend/official/create/components/Strategy/settingsStrategy.tsx

@@ -1,36 +1,158 @@
 import { App, Button, Card, Form, Input, Modal, Radio, Select } from "antd";
-import React, { useRef } from "react";
+import React, { useEffect, useRef, useState } from "react";
 import '../../../../../businessPlan/create/global.less'
 import SendTimeSet from "@/pages/weComTask/components/sendTimeSet";
 import { MinusCircleOutlined, PlusOutlined, DeleteOutlined } from "@ant-design/icons";
 import FilterUser from "@/pages/weComTask/components/filterUser";
 import { DefaultOptionType } from "antd/es/select";
+import NewSteps from "@/pages/weComTask/components/newSteps";
+import dayjs from 'dayjs';
 
 interface Props extends GROUP_CHAT_CREATE.FoundationProps<any> {
     mpList: DefaultOptionType[]
+    corpList: DefaultOptionType[]
 }
 /**
  * 官方 群聊群发策略配置
  * @param param0 
  * @returns 
  */
-const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange, mpList }) => {
+const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange, mpList, corpList }) => {
 
     /************************************/
     const { message } = App.useApp()
     const [form] = Form.useForm();
     const ref1 = useRef<HTMLDivElement>(null)
     const strategyList = Form.useWatch('strategyList', form)
+
+    const [stepsList, setStepsList] = useState<any>([
+        { title: '群聊创建配置', description: '任务名称', id: 'basicInfo' },
+        {
+            title: '策略配置',
+            children: [
+                {
+                    title: `策略${1}`, id: 'strategy_1',
+                    description: `名称、执行时间...`,
+                    children: [{
+                        title: `发送群对象${1}`, id: 'strategy_1_sendData_1',
+                        description: `名称、公众号、主体...`,
+                    }]
+                },
+                { title: '完成' }
+            ]
+        },
+        { title: '完成' }
+    ])
     /************************************/
 
+    useEffect(() => {
+        if (value?.strategyList?.length) {
+            const data = {
+                ...value, strategyList: value?.strategyList?.map(item => {
+                    const { sendTime, startTime, endTime, sendDay, timeRepeatType } = item
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        return {
+                            ...item,
+                            timeRepeatType,
+                            sendDay: sendDay ? dayjs(sendDay + ' ' + sendTime) : undefined
+                        }
+                    }
+                    return {
+                        ...item,
+                        timeRepeatType,
+                        sendTime: sendTime ? dayjs('2025-04-25 ' + sendTime) : undefined,
+                        startTime: startTime ? dayjs(startTime) : undefined,
+                        endTime: endTime ? dayjs(endTime) : undefined,
+                        sendDay: sendDay ? dayjs(sendDay) : undefined
+                    }
+                })
+            }
+            filedUpdateChange(data)
+            form.setFieldsValue(data)
+        }
+    }, [value])
+
     const handleOk = () => {
         form.validateFields().then((values) => {
-
+            const data = {
+                ...values,
+                strategyList: values?.strategyList?.map(item => {
+                    const { startTime, endTime, sendDay, sendTime, timeRepeatType, repeatArray, ...rest } = item
+                    const data = { ...rest, timeRepeatType }
+                    if (timeRepeatType === 'TIME_TYPE_SINGLE_PLACE') {
+                        // 定时发送
+                        data.sendDay = dayjs(sendDay).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendDay).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_DAY') {
+                        // 每日循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                    } else if (timeRepeatType === 'TIME_TYPE_REPEAT_WEEK' || timeRepeatType === 'TIME_TYPE_REPEAT_MONTH') {
+                        // 每周循环、每月循环
+                        data.startTime = dayjs(startTime).format('YYYY-MM-DD')
+                        data.sendTime = dayjs(sendTime).format('HH:mm:ss')
+                        if (endTime) {
+                            data.endTime = dayjs(endTime).format('YYYY-MM-DD')
+                        }
+                        data.repeatArray = repeatArray
+                    }
+                    return data
+                })
+            }
+            onChange(data)
         }).catch(() => {
             form.submit()
         });
     };
 
+
+    const filedUpdateChange = ({ groupSendName, strategyList }: any) => {
+        const strategyChildren = strategyList?.map((item, index) => {
+            const { strategyName, timeRepeatType, sendDay, startTime, sendTime, repeatArray, sendData } = item
+            const sendTimeChecked =
+                timeRepeatType === "TIME_TYPE_SINGLE_TIMELY" ||
+                (timeRepeatType === "TIME_TYPE_SINGLE_PLACE" && sendDay) ||
+                (timeRepeatType === "TIME_TYPE_REPEAT_DAY" && startTime && sendTime) ||
+                ((timeRepeatType === "TIME_TYPE_REPEAT_WEEK" || timeRepeatType === "TIME_TYPE_REPEAT_MONTH") && startTime && sendTime && repeatArray)
+
+            const children = sendData?.map((sd, sdIndex) => {
+                const { sendGroupName, mpAccountId, corpId, externalUserType, externalUserFilter } = sd
+                return {
+                    title: `发送群对象${sdIndex + 1}`, id: `strategy_${index + 1}_sendData_${sdIndex + 1}`,
+                    description: `名称、公众号、主体...`,
+                    checked: sendGroupName && mpAccountId && corpId && (externalUserType === 'all' ? true : externalUserFilter && Object.keys(externalUserFilter)?.length > 0)
+                }
+            })
+            return {
+                title: `策略${index + 1}`,
+                description: `名称、执行时间`,
+                id: `strategy_${index + 1}`,
+                children,
+                checked: sendTimeChecked && strategyName && children.some(item => item.checked)
+            }
+        })
+        const stepsData = [
+            { title: '群聊创建配置', description: '任务名称', id: 'basicInfo', checked: groupSendName },
+            {
+                title: '策略配置',
+                children: [
+                    ...strategyChildren,
+                    {
+                        title: '完成', checked: strategyChildren.some(item => item.checked)
+                    }
+                ],
+                checked: strategyChildren.some(item => item.checked),
+            },
+            {
+                title: '完成', checked: strategyChildren.some(item => item.checked)
+            }
+        ]
+        setStepsList(stepsData)
+    }
+
     return <Modal
         title={<strong>群聊群发策略配置  &nbsp;&nbsp;&nbsp;<span style={{ color: 'red' }}>对于执行时间冲突的策略,按照策略的排序执行</span></strong>}
         open={visible}
@@ -40,7 +162,13 @@ const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange,
         className={`settingsModal`}
     >
         <div className={`body_steps`}>
-
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
         </div>
         <div className={`body_content`} ref={ref1}>
             <Form
@@ -61,12 +189,12 @@ const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange,
                     strategyList: [{ id: Date.now(), sendData: [{ externalUserType: 'all', id: Date.now() }] }]
                 }}
                 onFieldsChange={() => {
-                    // filedUpdateChange(form.getFieldsValue())
+                    filedUpdateChange(form.getFieldsValue())
                 }}
                 preserve={true}
             >
                 <Card title={<strong>基础信息配置</strong>} style={{ background: '#fff', marginBottom: 10 }} id='basicInfo'>
-                    <Form.Item label={<strong>任务名称</strong>} name="taskName" rules={[{ required: true, message: '请输入任务名称!' }]}>
+                    <Form.Item label={<strong>任务名称</strong>} name="groupSendName" rules={[{ required: true, message: '请输入任务名称!' }]}>
                         <Input placeholder="请输入任务名称" style={{ width: 358 }} allowClear />
                     </Form.Item>
                 </Card>
@@ -105,12 +233,15 @@ const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange,
                                                         title={<strong>策略{index + 1} 发送群对象{i + 1} 配置</strong>}
                                                         style={{ background: '#fff', marginBottom: 10 }}
                                                         extra={sendData?.length > 1 ? <Button icon={<DeleteOutlined />} type='link' style={{ color: 'red' }} onClick={() => remove(name)}></Button> : null}
-                                                        id={`strategy_${index}_${i}_sendData`}
+                                                        id={`strategy_${index + 1}_sendData_${i + 1}`}
                                                     >
+                                                        <Form.Item {...restField} label={<strong>发送群对象名称</strong>} name={[name, 'sendGroupName']} rules={[{ required: true, message: '请输入发送群对象名称!' }]}>
+                                                            <Input placeholder="请输入发送群对象名称" style={{ width: 358 }} allowClear />
+                                                        </Form.Item>
                                                         <Form.Item
                                                             {...restField}
                                                             label={<strong>群聊关联公众号</strong>}
-                                                            name={[name, 'weChatAppid']}
+                                                            name={[name, 'mpAccountId']}
                                                         >
                                                             <Select
                                                                 showSearch
@@ -123,6 +254,24 @@ const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange,
                                                                 options={mpList}
                                                             />
                                                         </Form.Item>
+                                                        <Form.Item
+                                                            {...restField}
+                                                            label={<strong>群聊主体</strong>}
+                                                            name={[name, 'corpIds']}
+                                                            rules={[{ required: true, message: '请选择群聊主体!' }]}
+                                                        >
+                                                            <Select
+                                                                showSearch
+                                                                allowClear
+                                                                placeholder="选择群聊主体"
+                                                                filterOption={(input, option) =>
+                                                                    (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                                }
+                                                                mode="multiple"
+                                                                style={{ width: 358 }}
+                                                                options={corpList}
+                                                            />
+                                                        </Form.Item>
                                                         <Form.Item
                                                             label={<strong>发送群对象配置</strong>}
                                                             required
@@ -161,7 +310,7 @@ const SettingsStrategy: React.FC<Props> = ({ visible, onClose, value, onChange,
                                 </Card>
                             })}
                             <Form.Item>
-                                <Button type="primary" onClick={() => add({ id: Date.now() })} block icon={<PlusOutlined />}>
+                                <Button type="primary" onClick={() => add({ id: Date.now(), sendData: [{ externalUserType: 'all', id: Date.now() }] })} block icon={<PlusOutlined />}>
                                     新增策略
                                 </Button>
                             </Form.Item>

+ 69 - 0
src/pages/weComTask/page/groupChatSend/official/create/components/content/index.tsx

@@ -0,0 +1,69 @@
+import React, { useContext, useState } from 'react';
+import style from '../../../../../businessPlan/create/index.less'
+import useNewToken from '@/Hook/useNewToken';
+import { DispatchOfficialChatCreate } from '../..';
+import { Button, Empty, message, Popconfirm } from 'antd';
+import SettingsContent from './settingsContent';
+import PreviewContent from './previewContent';
+
+const Content: React.FC = () => {
+
+    /*******************************************/
+    const { token } = useNewToken()
+    const { setSettings, settings, onPreviewReset, mpList, corpList } = useContext(DispatchOfficialChatCreate)!;
+    const [newVisible, setNewVisible] = useState<boolean>(false);
+    /*******************************************/
+
+    return <>
+        <div className={`${style.settingsBody_content_row}`} style={{ width: '65%' }}>
+            <div className={`${style.settingsBody_content_col}`} style={{ width: '100%' }}>
+                <div className={style.title}>
+                    <span>内容</span>
+                    {/* {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 && <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings(undefined)
+                            onPreviewReset();
+                        }}
+                    >
+                        <a style={{ color: 'red' }}>清空</a>
+                    </Popconfirm>} */}
+                </div>
+                <div className={style.detail}>
+                    <div className={style.detail_title}>群发内容配置</div>
+                    <div className={style.detail_body}>
+                        {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0 ? <>
+                            <PreviewContent strategyList={settings?.strategyDTO?.strategyList} />
+                        </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                    </div>
+                </div>
+                <div className={style.detail_footer}>
+                    {settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0
+                        ? <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => setNewVisible(true)}>编辑</Button>
+                        : <Button type="link" style={{ padding: 0, fontSize: 12, color: token.colorPrimary }} size="small" onClick={() => message.error('请先设置策略')}>编辑</Button>
+                    }
+
+                </div>
+            </div>
+        </div>
+
+        {/* 群聊群发内容配置 */}
+        {newVisible && <SettingsContent
+            visible={newVisible}
+            value={settings?.strategyDTO}
+            onClose={() => setNewVisible(false)}
+            onChange={(values) => {
+                setSettings({
+                    ...settings,
+                    strategyDTO: {
+                        ...settings.strategyDTO,
+                        ...values
+                    }
+                })
+                setNewVisible(false)
+            }}
+        />}
+    </>;
+};
+
+export default React.memo(Content);

+ 81 - 0
src/pages/weComTask/page/groupChatSend/official/create/components/content/previewContent.tsx

@@ -0,0 +1,81 @@
+import useNewToken from '@/Hook/useNewToken';
+import LookMsg from '@/pages/weComTask/components/previewMsg/lookMsg';
+import { getMsgData } from '@/pages/weComTask/page/businessPlan/create/components/massSending/showContent';
+import { welcomeContentData } from '@/pages/weComTask/page/businessPlan/create/const';
+import { Empty, Tabs, Typography } from 'antd';
+import React, { useState } from 'react';
+const { Text } = Typography;
+
+interface PreviewContentProps {
+    strategyList: { [x: string]: any }[];
+}
+/**
+ * 预览
+ * @returns 
+ */
+const PreviewContent: React.FC<PreviewContentProps> = ({ strategyList }) => {
+
+    /****************************************/
+    const [activeKey, setActiveKey] = useState<string>('1')
+    /****************************************/
+
+    return <Tabs
+        activeKey={activeKey}
+        tabPosition='left'
+        style={{
+            height: '100%',
+            width: '100%'
+        }}
+        onChange={(key) => { setActiveKey(key) }}
+        items={strategyList.map((item, index) => {
+            return {
+                label: <div style={{ maxWidth: 100 }}><Text>{item.strategyName}</Text></div>,
+                key: `${index + 1}`,
+                children: <div style={{ width: '100%', height: 282, overflow: 'hidden', overflowY: 'auto' }}>
+                    <PreviewSendData sendData={item.sendData} />
+                </div>
+            }
+        })}
+    />;
+};
+
+const PreviewSendData: React.FC<{ sendData: { [x: string]: any }[]; }> = ({ sendData }) => {
+
+    /****************************************/
+    const [activeKey, setActiveKey] = useState<string>('1')
+    const { token } = useNewToken()
+    /****************************************/
+
+    return <Tabs
+        activeKey={activeKey}
+        tabPosition='left'
+        style={{
+            height: '100%',
+            width: '100%'
+        }}
+        onChange={(key) => { setActiveKey(key) }}
+        items={sendData.map((item, index) => {
+            return {
+                label: <div style={{ maxWidth: 150 }}><Text>{item.sendGroupName}</Text></div>,
+                key: `${index + 1}`,
+                children: <div style={{ width: '100%', height: 282, overflow: 'hidden', overflowY: 'auto' }}>
+                    {item?.contentDTO?.length > 0 ? <>
+                        <div style={{ backgroundColor: token.colorPrimary, padding: 4, color: '#FFF', fontSize: 12 }}>
+                            <div>
+                                发送模式:{welcomeContentData?.find(i => i.value === item?.sendMode)?.label}
+                            </div>
+                        </div>
+                        {item?.contentDTO?.map((item, index) => {
+                            return <div key={index} style={{ marginTop: 5, backgroundColor: '#f1f1f1', borderRadius: 6, padding: '5px 10px 16px' }}>
+                                <h3 style={{ textAlign: 'center' }}>内容{index + 1}</h3>
+                                <LookMsg data={getMsgData(item, token)} />
+                            </div>
+                        })}
+                    </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
+                </div>
+            }
+        })}
+    />
+}
+
+export default PreviewContent;

+ 238 - 0
src/pages/weComTask/page/groupChatSend/official/create/components/content/settingsContent.tsx

@@ -0,0 +1,238 @@
+import { App, Button, Card, Form, Modal, Select } from 'antd';
+import React, { useEffect, useRef, useState } from 'react';
+import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
+import MaterialNoTextMould from '@/pages/weComTask/page/businessPlan/create/components/massSending/materialNoTextMould';
+import { welcomeContentData } from '@/pages/weComTask/page/businessPlan/create/const';
+import NewSteps from '@/pages/weComTask/components/newSteps';
+
+/**
+ * 官方 群聊群发内容配置
+ * @param param0 
+ * @returns 
+ */
+const SettingsContent: React.FC<GROUP_CHAT_CREATE.FoundationProps<any>> = ({ visible, onClose, value, onChange }) => {
+
+    /***********************************************/
+    const { message } = App.useApp()
+    const [form] = Form.useForm();
+    const strategyList = Form.useWatch('strategyList', form)
+    const ref1 = useRef<HTMLDivElement>(null)
+
+    const [stepsList, setStepsList] = useState<any>([
+        {
+            title: `策略${1}`,
+            id: 'strategy_1',
+            children: [{
+                title: `发送群对象${1}`,
+                id: 'strategy_1_sendData_1'
+            }]
+        },
+        { title: '完成' }
+    ])
+    /***********************************************/
+
+    useEffect(() => {
+        if (value) {
+            console.log(value)
+            const valueS = {
+                ...value,
+                strategyList: value?.strategyList?.map(str => {
+                    return {
+                        ...str,
+                        sendData: str?.sendData?.map(item => {
+                            return {
+                                ...item,
+                                contentDTO: item?.contentDTO || [{}]
+                            }
+                        })
+                    }
+                })
+            }
+            filedUpdateChange(valueS)
+            form.setFieldsValue(valueS)
+        }
+    }, [value])
+
+    const handleOk = () => {
+        form.validateFields().then((values) => {
+            onChange(values)
+        }).catch(() => {
+            form.submit()
+        });
+    };
+
+    const filedUpdateChange = ({ strategyList }: any) => {
+        const strategyChildren = strategyList?.map((item, index) => {
+            const { sendData } = item
+
+            const children = sendData?.map((sd, sdIndex) => {
+
+                const content = sd?.contentDTO?.map((i, i_index) => {
+                    return { 
+                        title: `内容${i_index + 1}`, 
+                        checked: (i?.attachmentList?.length || i?.text?.content), 
+                        id: `strategy_${index + 1}_sendData_${sdIndex + 1}_content_${i_index + 1}`
+                    }
+                })
+
+                return {
+                    title: `发送群对象${sdIndex + 1}`, id: `strategy_${index + 1}_sendData_${sdIndex + 1}`, checked: content.every(item => item.checked) && (sd?.sendMode || sd?.sendMode === 0),
+                    children: [
+                        { title: `内容组发送模式`, id: `strategy_${index + 1}_sendData_${sdIndex + 1}_sendMode`, checked: sd?.sendMode || sd?.sendMode === 0 },
+                        ...content
+                    ]
+                }
+            })
+            return {
+                title: `策略${index + 1}`,
+                id: `strategy_${index + 1}`,
+                children,
+                checked: children.every(item => item.checked)
+            }
+        })
+        const stepsData = [
+            ...strategyChildren,
+            {
+                title: '完成', checked: strategyChildren.every(item => item.checked)
+            }
+        ]
+        setStepsList(stepsData)
+    }
+
+    return <Modal
+        title={<strong>群聊群发内容配置</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={850}
+        onOk={handleOk}
+        className={`settingsModal`}
+    >
+        <div className={`body_steps`}>
+            <NewSteps
+                items={stepsList}
+                onChange={(e) => {
+                    if (e?.id)
+                        ref1.current?.querySelector('#' + e?.id)?.scrollIntoView({ behavior: 'smooth' })
+                }}
+            />
+        </div>
+        <div className={`body_content`} ref={ref1}>
+            <Form
+                form={form}
+                name="newGroupChatSendCnotent"
+                labelAlign='left'
+                labelCol={{ span: 5 }}
+                colon={false}
+                scrollToFirstError={{
+                    behavior: 'smooth',
+                    block: 'center'
+                }}
+                onFinishFailed={({ errorFields }) => {
+                    message.error(errorFields?.[0]?.errors?.[0])
+                }}
+                onFinish={handleOk}
+                onFieldsChange={() => {
+                    filedUpdateChange(form.getFieldsValue())
+                }}
+                preserve={true}
+            >
+                <Form.List name="strategyList">
+                    {(fields) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }, index) => {
+                                const strategyItem = strategyList[index]
+                                return <Card
+                                    key={key}
+                                    title={<strong>策略 {strategyItem.strategyName} 配置</strong>}
+                                    style={{ background: '#fff', marginBottom: 10 }}
+                                    id={`strategy_${index + 1}`}
+                                >
+                                    <Form.List name={[name, 'sendData']} {...restField}>
+                                        {(fields) => (
+                                            <>
+                                                {fields.map(({ key, name, ...restField }, i) => {
+                                                    const sendDataItem = strategyItem?.sendData?.[i];
+
+                                                    return <Card
+                                                        key={i}
+                                                        title={<strong>发送群对象 {sendDataItem.sendGroupName} 配置</strong>}
+                                                        style={{ background: '#fff', marginBottom: 10 }}
+                                                        id={`strategy_${index + 1}_sendData_${i + 1}`}
+                                                    >
+                                                        <div id={`strategy_${index + 1}_sendData_${i + 1}_sendMode`}>
+                                                            <Form.Item
+                                                                {...restField}
+                                                                label={<strong>内容组发送模式</strong>}
+                                                                name={[name, 'sendMode']}
+                                                                rules={[{ required: true, message: '请选择内容组发送模式!' }]}
+                                                            >
+                                                                <Select
+                                                                    showSearch
+                                                                    style={{ width: 358 }}
+                                                                    allowClear
+                                                                    placeholder="请选择内容组发送模式"
+                                                                    filterOption={(input, option) =>
+                                                                        ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                                                                    }
+                                                                    options={welcomeContentData}
+                                                                />
+                                                            </Form.Item>
+                                                        </div>
+                                                        <div id={'clientId' + '_' + index + '_' + i + 'contentDTO'}>
+                                                            <Form.List {...restField} name={[name, 'contentDTO']} >
+                                                                {(fields, { add, remove }) => {
+                                                                    return <>
+                                                                        {fields.map(({ key, name, ...restField }, c_index) => {
+                                                                            return <div key={c_index} id={`strategy_${index + 1}_sendData_${i + 1}_content_${c_index + 1}`}>
+                                                                                <h3 style={{ display: 'flex', justifyContent: 'space-between', margin: 0 }}>内容{index + 1}
+                                                                                    {fields?.length > 1 && <Button
+                                                                                        type="dashed"
+                                                                                        danger
+                                                                                        onClick={() => remove(c_index)}
+                                                                                        icon={<MinusOutlined />}
+                                                                                        size='small'
+                                                                                    >
+                                                                                        移除内容
+                                                                                    </Button>}
+                                                                                </h3>
+                                                                                <Form.Item
+                                                                                    {...restField}
+                                                                                    name={name}
+                                                                                    rules={[{
+                                                                                        validator: (_, value) => {
+                                                                                            if ((value && !(value?.text?.content || value?.attachmentList?.length)) || !value) {
+                                                                                                return Promise.reject('请填写内容或上传附件');
+                                                                                            }
+                                                                                            return Promise.resolve();
+                                                                                        }
+                                                                                    }]}
+                                                                                >
+                                                                                    <MaterialNoTextMould />
+                                                                                </Form.Item>
+                                                                            </div>
+                                                                        })}
+                                                                        <Form.Item noStyle>
+                                                                            <Button type="dashed" onClick={() => add()} style={{ width: '100%' }} icon={<PlusOutlined />}>
+                                                                                新增内容
+                                                                            </Button>
+                                                                        </Form.Item>
+                                                                    </>
+                                                                }}
+                                                            </Form.List>
+                                                        </div>
+                                                    </Card>
+                                                })}
+                                            </>
+                                        )}
+                                    </Form.List>
+                                </Card>
+                            })}
+                        </>
+                    )}
+                </Form.List>
+            </Form>
+        </div>
+    </Modal>;
+};
+
+export default SettingsContent;

+ 336 - 6
src/pages/weComTask/page/groupChatSend/official/create/index.tsx

@@ -1,6 +1,6 @@
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 import style from '../../../businessPlan/create/index.less'
-import { Button, Card, Input, Select, Space, Spin } from 'antd';
+import { App, Button, Card, Empty, Form, Input, Popconfirm, Select, Space, Spin, Table } from 'antd';
 import { toJS } from 'mobx';
 import { inject, observer } from 'mobx-react';
 import { welcomeMsgJobTypeApi } from '@/pages/weComTask/API/weMaterial/weMaterial';
@@ -8,6 +8,14 @@ import { useAjax } from '@/Hook/useAjax';
 import Strategy from './components/Strategy';
 import { getBindMpListApi } from '@/pages/weComTask/API/corpUserAssign';
 import { DefaultOptionType } from 'antd/es/select';
+import { getCorpAllListApi } from '@/API/global';
+import { SaveOutlined, RedoOutlined, SearchOutlined, PlusOutlined } from '@ant-design/icons';
+import Content from './components/content';
+import { removeEmptyValues } from '@/utils/utils';
+import { PreviewColumns } from './tableConfig';
+import SubmitModal from '../../../businessPlan/create/submitModal';
+import { addTaskApi, getCreateDetailsApi, updateTaskApi } from '@/pages/weComTask/API/businessPlan/create';
+import { useNavigate } from 'react-router-dom';
 
 
 export const DispatchOfficialChatCreate = React.createContext<OFFICIAL_CHAT_CREATE.DispatchOfficialChatCreate | null>(null);
@@ -18,6 +26,8 @@ export const DispatchOfficialChatCreate = React.createContext<OFFICIAL_CHAT_CREA
 const OfficialCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
 
     /*******************************************/
+    const navigate = useNavigate();
+    const { message, modal } = App.useApp()
     const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
     const [settings, setSettings] = useState<OFFICIAL_CHAT_CREATE.SettingsProps>();
     const [msgJobTypeList, setMsgJobTypeList] = useState<{ value: string, label: string }[]>([])
@@ -25,16 +35,44 @@ const OfficialCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE
     const [previewData, setPreviewData] = useState<any[]>([])
     const [previewDataOld, setPreviewDataOld] = useState<any[]>([])
     const [mpList, setMpList] = useState<DefaultOptionType[]>([])
+    const [corpList, setCorpList] = useState<DefaultOptionType[]>([])
+    const [previewContent, setPreviewContent] = useState<{ [x: string]: { [x: string]: string } }>({})
+    const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
 
     const welcomeMsgJobType = useAjax(() => welcomeMsgJobTypeApi())//获取业务类型
     const getBindMpList = useAjax(() => getBindMpListApi())
+    const getCorpAllList = useAjax((param) => getCorpAllListApi(param))
+    const addTask = useAjax((params) => addTaskApi(params))
+    const updateTask = useAjax((params) => updateTaskApi(params))
+    const getCreateDetails = useAjax((params) => getCreateDetailsApi(params))
     /*******************************************/
 
+    useEffect(() => {
+        const project = sessionStorage.getItem('PGSD_OFFICIALTASKID')
+        if (project) {
+            const { id, isCopy } = JSON.parse(project)
+            if (!isCopy) {
+                setProjectId(id)
+            }
+
+        } else {
+            const task = localStorage.getItem('TASK_GROUP_CHAT_SEND_CREATE')
+            if (task) {
+                setSettings(JSON.parse(task).settings)
+            }
+        }
+    }, [])
+
     useEffect(() => {
         getBindMpList.run().then(res => {
             if (res?.data)
                 setMpList(res.data.map((item: any) => ({ label: item.name, value: item.id, appId: item.appId })))
         })
+
+        getCorpAllList.run({}).then(res => {
+            if (res?.data)
+                setCorpList(res.data.map((item: any) => ({ label: item.corpName, value: item.corpId })))
+        })
     }, [])
 
     useEffect(() => {
@@ -53,9 +91,207 @@ const OfficialCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE
         setPreviewDataOld([])
     }
 
+    const severBd = () => {
+        localStorage.setItem('TASK_GROUP_CHAT_SEND_CREATE', JSON.stringify({ settings }))
+        message.success('存储成功')
+    }
+
+    // 预览
+    const preview = () => {
+        const { bizType, platform, templateProductId, channel } = settings
+        if (!(settings?.strategyDTO && Object.keys(settings?.strategyDTO).length > 0)) {
+            message.error('请先配置内容')
+            return
+        }
+        if (!bizType) {
+            message.error('请选择业务类型')
+            return
+        }
+        if (!platform) {
+            message.error('请选择书城')
+            return
+        }
+        const list: any[] = []
+        let id = 1
+        if (!settings?.strategyDTO?.strategyList?.every((str, s_index) => {
+            const { sendData, ...sdto } = str
+            return sendData?.every((sd, f_index) => {
+                const { contentDTO, mpAccountId, corpIds, ...cdto } = sd
+                if (contentDTO?.length) {
+                    return contentDTO?.every((cd, c_index) => {
+                        if (cd?.attachmentList?.length || cd?.text?.content) {
+                            const mediaItem = JSON.parse(JSON.stringify(cd?.attachmentList || []))
+                            if (cd?.text?.content) {
+                                mediaItem.push({
+                                    msgType: 'TASK_CONTENT_TEXT',
+                                    textContent: cd?.text?.content
+                                })
+                            }
+                            const linkData = []
+                            const miniProgramData = []
+                            const contentReactNode = mediaItem.map(item => {
+                                switch (item.msgType) {
+                                    case 'TASK_CONTENT_LINK':
+                                        linkData.push(item)
+                                        return `<span style="color: red">链接</span>`
+                                    case 'TASK_STATUS_MINIPROGRAM':
+                                        miniProgramData.push(item)
+                                        return `<span style="color: red">小程序</span>`
+                                    case 'TASK_STATUS_FILE':
+                                        return `<span>文件</span>`
+                                    case 'TASK_STATUS_VIDEO':
+                                        return `<span>视频</span>`
+                                    case 'TASK_CONTENT_IMAGE':
+                                        return `<span>图片</span>`
+                                    case 'TASK_CONTENT_TEXT':
+                                        return `<span>文本</span>`
+                                    default:
+                                        return `<span style="color: red">请联系管理员</span>`
+                                }
+                            })
+                            const weChat = mpList.find(item => item.value === mpAccountId)
+                            list.push({
+                                id,
+                                bizType,
+                                platform,
+                                templateProductId,
+                                channel,
+                                taskName: settings?.strategyDTO?.taskName,
+                                strategyData: sdto,
+                                mpAccountId,
+                                weChatName: weChat?.label,
+                                mpAccountAppid: weChat?.appId,
+                                corpIds,
+                                corpNames: corpIds.map(corpId => corpList.find(item => item.value === corpId)?.label),
+                                sendData: cdto,
+                                contentReactNode,
+                                content: cd,
+                                strategyIndex: s_index,
+                                sendIndex: f_index,
+                                contentIndex: c_index,
+                                linkData,
+                                miniProgramData
+                            })
+                            id++;
+                            return true
+                        } else {
+                            message.error(`策略:${str?.strategyName},发送对象:${sd?.sendGroupName}请填写发送内容`)
+                            return false
+                        }
+                    })
+                } else {
+                    message.error(`策略:${str?.strategyName},发送对象:${sd?.sendGroupName}请填写发送内容`)
+                    return false
+                }
+
+            })
+        })) {
+            return
+        }
+        console.log('=========================>', list)
+        setPreviewData(list)
+        setPreviewDataOld(list)
+        setPreviewContent({})
+    }
+
+    const tableSearch = useCallback((values) => {
+        console.log(values)
+        const obj = removeEmptyValues(values)
+        if (Object.keys(obj).length) {
+            const newPreviewData = previewDataOld.filter(item => {
+                return (obj?.weChatAppids?.length > 0 ? obj.weChatAppids.includes(item?.mpAccountId) : true)
+                    && (obj?.corpIds?.length > 0 ? obj.corpIds.some(corpId => item?.corpIds.includes(corpId)) : true)
+            })
+            setPreviewData(newPreviewData)
+        } else {
+            setPreviewData(previewDataOld)
+        }
+    }, [previewDataOld, previewData])
+
+    const setTaskName = () => {
+        if (previewDataOld.every((item, index) => {
+            if ((item?.linkData?.length > 0 ? previewContent?.[index + 1]?.linkUrl : true) && (item?.miniProgramData?.length > 0 ? (previewContent?.[index + 1]?.miniprogramAppid && previewContent?.[index + 1]?.miniprogramPage) : true)) {
+                return true
+            } else {
+                message.error('请补充图文或者小程序内容')
+                return false
+            }
+        })) {
+            setSubVisible(true)
+        }
+    }
+
+    const onSubmit = (values: any) => {
+        const { bizType, platform, templateProductId, channel, strategyDTO } = settings
+        const params = {
+            ...values,
+            bizType,
+            platform,
+            templateProductId,
+            channel,
+            groupChatSendTaskAddDTO: {
+                ...strategyDTO,
+                strategyList: strategyDTO?.strategyList?.map(item => {
+                    const { sendData, ...its } = item
+                    return {
+                        ...its,
+                        taskDetail: sendData.map(sd => {
+                            const { externalUserType, externalUserFilter, contentDTO, sendGroupName, ...itsd } = sd
+
+                            const detail: { [x: string]: any } = {
+                                ...itsd,
+                                contentDTO,
+                                externalUserFilterName: sendGroupName,
+                            }
+                            if (externalUserType === 'specify') {
+                                detail.externalUserFilter = {
+                                    configName: externalUserFilter.configName,
+                                    ...externalUserFilter.configContent
+                                }
+                            }
+                            return detail
+                        })
+                    }
+                })
+            }
+        }
+        console.log('--->', params)
+        if (projectId) {
+            params.projectId = projectId
+            updateTask.run(params).then(res => {
+                console.log(res)
+                if (res?.data) {
+                    message.success('修改提交成功')
+                    setProjectId(undefined)
+                    sessionStorage.setItem('CAMPCORP_OFFICIAL', values?.projectName)
+                    navigate('/weComTask/groupChatSend/official/taskList')
+                }
+            })
+        } else {
+            addTask.run(params).then(res => {
+                console.log(res)
+                if (res?.data) {
+                    modal.success({
+                        content: '任务提交成功',
+                        styles: { body: { fontWeight: 700 } },
+                        okText: '跳转任务列表',
+                        closable: true,
+                        onOk: () => {
+                            sessionStorage.setItem('CAMPCORP_OFFICIAL', values?.projectName)
+                            navigate('/weComTask/groupChatSend/official/taskList')
+                        },
+                        onCancel: () => {
+                            setSubVisible(false)
+                        }
+                    })
+                }
+            })
+        }
+    }
+
     return <div className={style.create}>
         <Spin spinning={false}>
-            <Card title={<strong>官方群发{projectId ? ' xxxx' + '任务编辑' : ''}配置区</strong>} className={`${style.card} ${style.config}`}>
+            <Card title={<strong>官方群发{projectId ? getCreateDetails?.data?.data?.projectName + '任务编辑' : ''}配置区</strong>} className={`${style.card} ${style.config}`}>
                 <Space wrap>
                     <Space.Compact>
                         <Button>业务类型</Button>
@@ -135,18 +371,112 @@ const OfficialCreate: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE
                                 settings, setSettings,
                                 onPreviewReset,
                                 bookPlatForm, bookList,
-                                mpList
+                                mpList,
+                                corpList
                             }}
                         >
                             {/* 策略配置 */}
                             <Strategy />
-                            {/* 进群对象 */}
-                            {/* <GroupUser /> */}
+                            {/* 群发内容 */}
+                            <Content />
                         </DispatchOfficialChatCreate.Provider>
                     </div>
                 </div>
+                <Space className={style.bts} wrap>
+                    <Button icon={<SaveOutlined />} onClick={severBd}>存为预设</Button>
+                    <Popconfirm
+                        title="确定清空?"
+                        onConfirm={() => {
+                            setSettings(undefined)
+                            onPreviewReset()
+                            localStorage.removeItem('TASK_GROUP_CHAT_CREATE')
+                        }}
+                    >
+                        <Button icon={<RedoOutlined />} danger>清空配置/预设</Button>
+                    </Popconfirm>
+                    <Button type='primary' onClick={preview}><SearchOutlined />预览任务/配置内容</Button>
+                </Space>
             </Card>
         </Spin>
+
+        <Card
+            className={style.card}
+            style={{ marginTop: 10, marginBottom: 10 }}
+            extra={previewDataOld?.length > 0 ? <Button type='primary' icon={<PlusOutlined />} onClick={() => {
+                setTaskName()
+            }}>提交</Button> : undefined}
+        >
+            {previewDataOld?.length > 0 ? <div style={{ minHeight: 300 }}>
+                <Form
+                    layout={'inline'}
+                    onFinish={tableSearch}
+                    colon={false}
+                >
+                    <Form.Item label={<strong>关联公众号</strong>} style={{ marginBottom: 10 }} name="weChatAppids">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 160 }}
+                            maxTagCount={1}
+                            placeholder="公众号"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            mode='multiple'
+                            options={mpList}
+                        />
+                    </Form.Item>
+                    <Form.Item label={<strong>群聊主体</strong>} style={{ marginBottom: 10 }} name="corpIds">
+                        <Select
+                            showSearch
+                            style={{ minWidth: 160 }}
+                            maxTagCount={1}
+                            placeholder="请选择主体"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
+                            }
+                            allowClear
+                            options={corpList}
+                            mode='multiple'
+                        />
+                    </Form.Item>
+
+                    <Form.Item style={{ marginBottom: 10 }}>
+                        <Space>
+                            <Button htmlType="reset">重置</Button>
+                            <Button type="primary" htmlType='submit'>搜索</Button>
+                        </Space>
+                    </Form.Item>
+                </Form>
+                <Table
+                    dataSource={previewData}
+                    columns={PreviewColumns(bookPlatForm, bookList, bookPlatForm.find(item => item.id === Number(settings?.platform)).platformKey, setPreviewContent, previewContent)}
+                    rowKey={'id'}
+                    bordered={true}
+                    scroll={{ y: 550 }}
+                    pagination={{
+                        showTotal(total, range) {
+                            return `共 ${total} 条记录 第 ${range[0]}-${range[1]} 条`
+                        },
+                    }}
+                />
+            </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                <Empty description="请先完成模块配置后,再预览" />
+            </div>}
+        </Card>
+
+        {/* 提交配置 */}
+        {subVisible && <SubmitModal
+            visible={subVisible}
+            loading={addTask.loading || updateTask.loading}
+            projectName={projectId ? getCreateDetails?.data?.data?.projectName : undefined}
+            onChange={(values) => {
+                onSubmit(values)
+            }}
+            onClose={() => {
+                setSubVisible(false)
+            }}
+        />}
     </div>;
 };
 

+ 201 - 0
src/pages/weComTask/page/groupChatSend/official/create/tableConfig.tsx

@@ -0,0 +1,201 @@
+import { Input, Typography } from "antd";
+import { AnyObject } from "antd/es/_util/type"
+import { ColumnsType } from "antd/es/table"
+import { TIME_TYPE, welcomeContentData } from "../../../businessPlan/create/const";
+import FilterUser from "@/pages/weComTask/components/filterUser";
+import ShowContentTable from "../../../businessPlan/create/components/massSending/showContentTable";
+import SelectBookLinkButton from "../../../bookLink/SelectBookLinkButton";
+const { Paragraph, Title } = Typography;
+
+
+export const PreviewColumns = (bookPlatForm: TASK_CREATE.BookPlatFormProps[], bookList: TASK_CREATE.BookListProps[], platform: string, setPreviewContent: React.Dispatch<React.SetStateAction<{ [x: string]: { [x: string]: string; }; }>>, previewContent: { [x: string]: { [x: string]: string } }): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '基础信息',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 130,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>任务名称:{value}</Title>
+                    {record?.bizType && <Paragraph style={{ margin: 0 }}>业务类型:{record?.bizType === 'novel' ? '小说' : record?.bizType === 'game' ? '游戏' : '<空>'}</Paragraph>}
+                    {record?.platform && <Paragraph style={{ margin: 0 }}>书城:{record?.platform ? bookPlatForm?.find(item => item.id === record?.platform)?.platformName : '<空>'}</Paragraph>}
+                    {record?.templateProductId && <Paragraph style={{ margin: 0 }}>适用产品:{record?.templateProductId ? bookList?.find(item => item.id === record?.templateProductId)?.bookName : '<空>'}</Paragraph>}
+                </>
+            }
+        },
+        {
+            title: '策略信息',
+            dataIndex: 'strategyData',
+            key: 'strategyData',
+            width: 200,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>策略{record.strategyIndex + 1}</Title>
+                    <Paragraph style={{ margin: 0 }}>名称:{value?.strategyName || '<空>'}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>执行类型:{TIME_TYPE[value?.timeRepeatType]}</Paragraph>
+                    {value?.sendDay && <Paragraph style={{ margin: 0 }}>执行时间:{value?.sendDay}</Paragraph>}
+                    {value?.startTime && <Paragraph style={{ margin: 0 }}>执行日期:{value?.startTime}~{value?.endTime ? value?.endTime : '长期执行'}</Paragraph>}
+                    {value?.sendTime && <Paragraph style={{ margin: 0 }}>执行时间:{value?.sendTime}</Paragraph>}
+                    {value?.repeatArray && <Paragraph style={{ margin: 0 }}>执行天数:{value?.repeatArray.join('、')}</Paragraph>}
+                </>
+            }
+        },
+        {
+            title: '群聊关注公众号',
+            dataIndex: 'weChatName',
+            key: 'weChatName',
+            width: 120,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            },
+        },
+        {
+            title: '企微主体',
+            dataIndex: 'corpNames',
+            key: 'corpNames',
+            width: 160,
+            ellipsis: true,
+            render(value) {
+                return value ? value.join('、') : '--'
+            },
+        },
+        {
+            title: '发送对象',
+            dataIndex: 'sendData',
+            key: 'sendData',
+            width: 200,
+            render(value, record) {
+                return <>
+                    <Title level={5} style={{ margin: 0 }}>发送对象{record.sendIndex + 1}</Title>
+                    <Paragraph style={{ margin: 0 }} ellipsis>名称:{value?.sendGroupName}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>内容发送模式:{welcomeContentData?.find(i => i.value === value?.sendMode)?.label}</Paragraph>
+                    <Paragraph style={{ margin: 0 }}>类型:{value?.externalUserType === 'all' ? '全部' : '指定'}</Paragraph>
+                    {value?.externalUserType === 'specify' && <FilterUser isSHow value={value?.externalUserFilter} configType='USER_GROUP' />}
+                </>
+            },
+            onCell: (record) => {
+                return { rowSpan: record.sendDataRowSpan }
+            }
+        },
+        {
+            title: '发送内容',
+            dataIndex: 'content',
+            key: 'content',
+            width: 200,
+            render(value, record) {
+                return <>
+                    <ShowContentTable
+                        data={value}
+                        name={<><span>内容{record.contentIndex + 1}:</span><span dangerouslySetInnerHTML={{ __html: record.contentReactNode?.map((item) => item).join('、') }}></span></>}
+                    />
+                </>
+            },
+        },
+        {
+            title: '图文链接',
+            dataIndex: 'linkUrl',
+            key: 'linkUrl',
+            width: 250,
+            render(_, record) {
+                return record?.linkData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入链接"
+                        allowClear
+                        value={previewContent?.[record.id]?.linkUrl}
+                        onChange={e => {
+                            const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                            const c = previewContent?.[record.id] || {}
+                            newPreviewContent[record.id] = { ...c, linkUrl: e.target.value }
+                            setPreviewContent(newPreviewContent)
+                        }}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record.mpAccountId}
+                        linkData={record.linkData}
+                        onChange={(linkStr, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = { linkUrl: linkStr }
+                            if (miniprogramAppid && miniprogramPage && record?.miniProgramData?.length > 0 && !previewContent?.[record.id]?.miniprogramAppid && !previewContent?.[record.id]?.miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                            }
+                            const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                            const c = previewContent?.[record.id] || {}
+                            newPreviewContent[record.id] = { ...c, ...msg }
+                            setPreviewContent(newPreviewContent)
+                        }}
+                    />
+                </div> : '--'
+            },
+        },
+        {
+            title: '小程序APPID',
+            dataIndex: 'miniprogramAppid',
+            key: 'miniprogramAppid',
+            width: 200,
+            render(_, record) {
+                return record?.miniProgramData?.length > 0 ? <Input.TextArea
+                    placeholder="请输入小程序APPID"
+                    allowClear
+                    value={previewContent?.[record.id]?.miniprogramAppid}
+                    onChange={e => {
+                        const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                        const c = previewContent?.[record.id] || {}
+                        newPreviewContent[record.id] = { ...c, miniprogramAppid: e.target.value }
+                        setPreviewContent(newPreviewContent)
+                    }}
+                /> : '--'
+            },
+        },
+        {
+            title: '小程序路径',
+            dataIndex: 'miniprogramPage',
+            key: 'miniprogramPage',
+            width: 250,
+            render(_, record) {
+                return record?.miniProgramData?.length > 0 ? <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end' }}>
+                    <Input.TextArea
+                        placeholder="请输入小程序路径"
+                        allowClear
+                        value={previewContent?.[record.id]?.miniprogramPage}
+                        onChange={e => {
+                            const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                            const c = previewContent?.[record.id] || {}
+                            newPreviewContent[record.id] = { ...c, miniprogramPage: e.target.value }
+                            setPreviewContent(newPreviewContent)
+                        }}
+                    />
+                    <SelectBookLinkButton
+                        bookPlatForm={bookPlatForm}
+                        platform={platform}
+                        mpAccountId={record.mpAccountId}
+                        linkData={[{
+                            msgType: "TASK_CONTENT_LINK",
+                            link: {
+                                title: record.miniProgramData?.[0]?.miniprogram?.title,
+                                picUrl: record.miniProgramData?.[0]?.miniprogram?.picUrl,
+                                desc: ''
+                            }
+                        }]}
+                        onChange={(_, miniprogramAppid, miniprogramPage) => {
+                            const msg: { [x: string]: string } = {}
+                            if (miniprogramAppid && miniprogramPage) {
+                                msg.miniprogramAppid = miniprogramAppid
+                                msg.miniprogramPage = miniprogramPage
+                                const newPreviewContent = JSON.parse(JSON.stringify(previewContent))
+                                const c = previewContent?.[record.id] || {}
+                                newPreviewContent[record.id] = { ...c, ...msg }
+                                setPreviewContent(newPreviewContent)
+                            }
+                        }}
+                    />
+                </div> : '--'
+            }
+        }
+    ]
+
+}

+ 15 - 0
src/pages/weComTask/page/groupChatSend/official/taskList/index.less

@@ -0,0 +1,15 @@
+.nameBox {
+    display: flex;
+    align-items: center;
+
+    >div {
+        flex: 1 0;
+        overflow: hidden;
+    }
+
+    >a {
+        margin: 0 3px;
+        color: black;
+        cursor: help;
+    }
+}

+ 198 - 0
src/pages/weComTask/page/groupChatSend/official/taskList/index.tsx

@@ -0,0 +1,198 @@
+import { useAjax } from '@/Hook/useAjax';
+import { cancelProjectApi, delProjectApi, getProjectListApi } from '@/pages/weComTask/API/businessPlan/create';
+import { App, Button, Card, DatePicker, Input, Popconfirm, Space, Table } from 'antd';
+import React, { useEffect, useState } from 'react';
+import taskListColumns from './tableConfig';
+import dayjs from 'dayjs';
+import { SearchOutlined, DeleteOutlined, PauseCircleOutlined } from '@ant-design/icons';
+import Log from './log';
+import { inject, observer } from 'mobx-react';
+import { toJS } from 'mobx';
+
+/**
+ * 任务列表
+ * @returns 
+ */
+const TaskList: React.FC<{ weComTaskStore: { data: { bookList: TASK_CREATE.BookListProps[], bookPlatForm: TASK_CREATE.BookPlatFormProps[] } } }> = ({ weComTaskStore }) => {
+
+    /***********************************************/
+    const projectName = sessionStorage.getItem('CAMPCORP')
+    const { message } = App.useApp();
+    const { bookList, bookPlatForm } = toJS(weComTaskStore.data)
+    const [queryForm, setQueryForm] = useState<BUSINES_SPLAN_API.GetProjectListProps>({ pageNum: 1, pageSize: 20, projectName, projectType: 1 })
+    const [queryFormNew, setQueryFormNew] = useState<BUSINES_SPLAN_API.GetProjectListProps>({ pageNum: 1, pageSize: 20, projectName, projectType: 1 })
+    const [logOpenData, setLogOpenData] = useState<{ visible: boolean, data: any }>({ visible: false, data: {} })
+    const [selectedRows, setselectedRows] = useState<any[]>([])
+
+    const getProjectList = useAjax((params) => getProjectListApi(params))
+    const delProject = useAjax((params) => delProjectApi(params))
+    const cancelProject = useAjax((params) => cancelProjectApi(params))
+    /***********************************************/
+
+    useEffect(() => {
+        const projectName = sessionStorage.getItem('CAMPCORP_OFFICIAL')
+        if (projectName) {
+            sessionStorage.removeItem('CAMPCORP_OFFICIAL')
+        }
+    }, [])
+
+    useEffect(() => {
+        getProjectList.run(queryFormNew)
+    }, [queryFormNew])
+
+    // 日志
+    const handleLog = (data: any) => {
+        setLogOpenData({ visible: true, data })
+    }
+
+    // 复制
+    const handleCopy = (data: any, isCopy: boolean) => {
+        sessionStorage.setItem('OFFICIALTASKID', JSON.stringify({ id: data.id, isCopy }))
+        sessionStorage.setItem('oldPath', '/weComTask/businessPlan/create')
+        window.location.href = '/weComTask#/weComTask/businessPlan/create'
+    }
+
+    // 删除
+    const handleDel = (data: { projectIds: number[] }, type: 'del' | 'cancel') => {
+        const hide = message.loading(type === 'del' ? '正在删除...' : '正在取消...', 0)
+        switch (type) {
+            case 'del':
+                delProject.run(data).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success('删除成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error('删除失败')
+                    }
+                }).catch(() => hide())
+                break
+            case 'cancel':
+                cancelProject.run(data).then(res => {
+                    hide()
+                    setselectedRows([])
+                    if (res?.data) {
+                        message.success('取消成功')
+                        getProjectList.refresh()
+                    } else {
+                        message.error('取消失败')
+                    }
+                }).catch(() => hide())
+                break
+        }
+
+    }
+
+    return <Card>
+        <div style={{ display: 'flex', gap: 10, marginBottom: 10 }}>
+            <Space.Compact>
+                <Button>任务名称</Button>
+                <Input placeholder='请输入任务名称' allowClear value={queryForm?.projectName} onChange={(e) => setQueryForm({ ...queryForm, projectName: e.target.value })} />
+            </Space.Compact>
+            <Space.Compact>
+                <Button>创建时间</Button>
+                <DatePicker.RangePicker
+                    placeholder={["创建时间开始", "创建时间结束"]}
+                    value={queryForm?.createTimeMin ? [dayjs(queryForm?.createTimeMin), dayjs(queryForm?.createTimeMax)] : undefined}
+                    onChange={(_, options) => {
+                        const newQueryForm = { ...queryForm }
+                        if (options?.[0]) {
+                            newQueryForm.createTimeMin = options?.[0] + ' 00:00:00'
+                            newQueryForm.createTimeMax = options?.[1] + ' 23:59:59'
+                        } else {
+                            delete newQueryForm?.createTimeMin
+                            delete newQueryForm?.createTimeMax
+                        }
+                        setQueryForm(newQueryForm)
+                    }}
+                />
+            </Space.Compact>
+            <Button
+                type='primary'
+                onClick={() => {
+                    setQueryFormNew({ ...queryForm, pageNum: 1 })
+                }}
+                icon={<SearchOutlined />}
+            >搜索</Button>
+            <Popconfirm
+                title="确定删除?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'del') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' danger icon={<DeleteOutlined />} loading={delProject.loading} disabled={selectedRows.length === 0}>删除</Button>
+            </Popconfirm>
+            <Popconfirm
+                title="确定取消?"
+                onConfirm={() => { handleDel({ projectIds: selectedRows.map(i => i.id) }, 'cancel') }}
+                disabled={selectedRows.length === 0}
+            >
+                <Button type='primary' style={{ backgroundColor: 'orange', borderColor: 'orange' }} loading={cancelProject.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0}>取消任务</Button>
+            </Popconfirm>
+        </div>
+        <Table
+            dataSource={getProjectList?.data?.data?.records}
+            columns={taskListColumns(bookPlatForm, bookList, handleLog, handleCopy, handleDel)}
+            rowKey={'id'}
+            bordered={true}
+            size='small'
+            scroll={{ y: 600, x: 1200 }}
+            pagination={{
+                current: getProjectList?.data?.data?.current,
+                pageSize: getProjectList?.data?.data?.size,
+                total: getProjectList?.data?.data?.total,
+                showSizeChanger: true,
+                onChange: (page, pageSize) => {
+                    setQueryFormNew({ ...queryFormNew, pageNum: page, pageSize })
+                    setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                }
+            }}
+            rowSelection={{
+                selectedRowKeys: selectedRows?.map((item: any) => item?.id),
+                onSelect: (record: { id: string }, selected: boolean) => {
+                    let newData = JSON.parse(JSON.stringify(selectedRows))
+                    if (selected) {
+                        newData.push({ ...record })
+                    } else {
+                        newData = newData.filter((item: { id: string }) => item.id !== record.id)
+                    }
+                    setselectedRows(newData)
+                },
+                onSelectAll: (selected: boolean, _: { id: string }[], changeRows: { id: string }[]) => {
+                    let newData = JSON.parse(JSON.stringify(selectedRows || '[]'))
+                    if (selected) {
+                        changeRows.forEach((item: { id: string }) => {
+                            let index = newData.findIndex((ite: { id: string }) => ite.id === item.id)
+                            if (index === -1) {
+                                newData.push(item)
+                            }
+                        })
+                    } else {
+                        let newSelectAccData = newData.filter((item: { id: string }) => {
+                            let index = changeRows.findIndex((ite: { id: string }) => ite.id === item.id)
+                            if (index !== -1) {
+                                return false
+                            } else {
+                                return true
+                            }
+                        })
+                        newData = newSelectAccData
+                    }
+                    setselectedRows(newData)
+                }
+            }}
+        />
+
+        {/* 日志 */}
+        {logOpenData.visible && <Log
+            {...logOpenData}
+            bookPlatForm={bookPlatForm}
+            bookList={bookList}
+            onClose={() => {
+                setLogOpenData({ visible: false, data: undefined })
+            }}
+        />}
+    </Card>
+};
+
+export default inject('store')(observer((props: any) => TaskList(props.store)));

+ 71 - 0
src/pages/weComTask/page/groupChatSend/official/taskList/log.tsx

@@ -0,0 +1,71 @@
+import { useAjax } from '@/Hook/useAjax';
+import { getProjectLogListApi } from '@/pages/weComTask/API/businessPlan/create';
+import { Drawer, Spin, Tabs } from 'antd';
+import React, { useEffect, useState } from 'react';
+
+
+interface Props {
+    data: any,
+    bookPlatForm: TASK_CREATE.BookPlatFormProps[]
+    bookList: TASK_CREATE.BookListProps[]
+    visible?: boolean,
+    onClose?: () => void,
+}
+
+export const DispatchTaskDetails = React.createContext<TASK_CREATE.DispatchTaskDetails | null>(null);
+/**
+ * 任务日志
+ * @param param0 
+ * @returns 
+ */
+const Log: React.FC<Props> = ({ data, bookPlatForm, bookList, visible, onClose }) => {
+
+    /******************************************************/
+    const [previewData, setPreviewData] = useState<{ welcomeMsgTemplateVO?: any, groupSendTaskVOList?: any[], externalUserTransferTasksVOList?: any[] }>({})
+    const getProjectLogList = useAjax((params) => getProjectLogListApi(params))
+    /******************************************************/
+
+    useEffect(() => {
+        console.log(data.id)
+        getProjectLogList.run(data.id).then(res => {
+            if (res?.data) {
+                setPreviewData(res.data)
+            }
+        })
+    }, [])
+
+    return <Drawer
+        title={<strong>{data.projectName} 任务详情</strong>}
+        onClose={onClose}
+        open={visible}
+        width={1400}
+        styles={{ body: { paddingTop: 5 } }}
+    >
+
+        <Spin spinning={getProjectLogList.loading}>
+            {/* <DispatchTaskDetails.Provider
+                value={{ bookPlatForm, bookList }}
+            >
+                <Tabs
+                    items={Object.keys(previewData).filter(key => key === 'welcomeMsgTemplateVO' ? previewData[key] : previewData[key]?.length).map(key => ({
+                        key: key,
+                        label: { 'externalUserTransferTasksVOList': '客户继承', 'groupSendTaskVOList': '客户群发', 'welcomeMsgTemplateVO': '欢迎语', 'messageSendTaskVOS': '高级群发', 'momentsTaskVOList': '朋友圈' }[key],
+                        children: key === 'externalUserTransferTasksVOList' ? <>
+                            <ExternalUserTransferTask externalUserTransferTasksVOList={previewData[key]} />
+                        </> : key === 'groupSendTaskVOList' ? <>
+                            <GroupTask groupSendTaskVOList={previewData[key]} />
+                        </> : key === 'welcomeMsgTemplateVO' ? <>
+                            <WelcomeTask welcomeMsgTemplateVO={previewData[key]} />
+                        </> : key === 'messageSendTaskVOS' ? <>
+                            <HighGroupTask groupSendTaskVOList={previewData[key]} />
+                        </> : key === 'momentsTaskVOList' ? <>
+                            <MomentTask momentTaskVOList={previewData[key]} />
+                        </> : undefined
+                    }))}
+                />
+            </DispatchTaskDetails.Provider> */}
+        </Spin>
+    </Drawer>
+};
+
+export default Log;

+ 85 - 0
src/pages/weComTask/page/groupChatSend/official/taskList/tableConfig.tsx

@@ -0,0 +1,85 @@
+import { Flex, Popconfirm, Popover, Space, Typography } from "antd";
+import { AnyObject } from "antd/es/_util/type";
+import { ColumnsType } from "antd/es/table";
+import style from './index.less'
+import { QuestionCircleFilled } from '@ant-design/icons';
+
+const { Text, Paragraph } = Typography;
+const taskListColumns = (
+    bookPlatForm: any[],
+    bookList: any[],
+    handleLog: (data: any) => void,
+    handleCopy: (data: any, isCopy: boolean) => void,
+    handleDel: (data: any, type: 'del' | 'cancel') => void,
+): ColumnsType<AnyObject> => {
+
+    return [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 160,
+            render(_, record) {
+                return <Space>
+                    <Popconfirm
+                        title="确定取消?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'cancel') }}
+                    >
+                        <a style={{ color: 'orange' }}>取消任务</a>
+                    </Popconfirm>
+                    <a onClick={() => handleCopy(record, true)}>复制</a>
+                    <a onClick={() => handleCopy(record, false)}>编辑</a>
+                    <a onClick={() => handleLog(record)}>详情</a>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { handleDel({ projectIds: [record.id] }, 'del') }}
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            },
+        },
+        {
+            title: '任务名称',
+            dataIndex: 'projectName',
+            key: 'projectName',
+            width: 120,
+            ellipsis: true
+        },
+        {
+            title: '基础信息',
+            dataIndex: 'bizType',
+            key: 'bizType',
+            width: 180,
+            render: (_, record) => {
+                return <Paragraph style={{ margin: 0 }} ellipsis={{ tooltip: true }}>
+                    业务类型:{record?.bizType === 'novel' ? '小说' : '<空>'}-书城:{record?.platformName || '<空>'}-适用产品:{record?.templateProductName || '<空>'}
+                </Paragraph>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 125,
+            ellipsis: true
+        },
+        {
+            title: '任务数量',
+            dataIndex: 'taskCount',
+            key: 'taskCount',
+            width: 260,
+            render(value, record) {
+                return <Flex gap={6}>
+                    <Text type={record.welcomeMsgCount > 0 ? 'success' : 'danger'}>欢迎语:{record.welcomeMsgCount}</Text>
+                    <Text type={record.groupSendTaskCount > 0 ? 'success' : 'danger'}>群发:{record.groupSendTaskCount}</Text>
+                    <Text type={record.externalUserTransferTaskCount > 0 ? 'success' : 'danger'}>客户继承:{record.externalUserTransferTaskCount}</Text>
+                </Flex>
+            },
+        },
+    ]
+}
+
+
+export default taskListColumns

+ 23 - 0
src/pages/weComTask/page/groupChatSend/official/typings.d.ts

@@ -6,6 +6,7 @@ declare namespace OFFICIAL_CHAT_CREATE {
         bookPlatForm: TASK_CREATE.BookPlatFormProps[]
         bookList: TASK_CREATE.BookListProps[]
         mpList: DefaultOptionType[]
+        corpList: DefaultOptionType[]
     }
     // 官方群发创建参数配置
     interface SettingsProps {
@@ -15,4 +16,26 @@ declare namespace OFFICIAL_CHAT_CREATE {
         templateProductId?: string; // 适用产品
         strategyDTO?: { [x: string]: any };
     }
+    interface SettingsSendObjectProps {
+        mpList: DefaultOptionType[]
+        corpList: DefaultOptionType[]
+        value?: { [x: string]: any }[];
+        onChange?: (value: { [x: string]: any }[]) => void;
+    }
+    interface AddSendObjectProps {
+        mpList: DefaultOptionType[]
+        corpList: DefaultOptionType[]
+        initialValues?: any;
+        onChange?: (value: { [x: string]: any }) => void;
+        visible?: boolean;
+        onClose?: () => void;
+    }
+    interface ShowSendObjectTable {
+        mpList: DefaultOptionType[]
+        corpList: DefaultOptionType[]
+        data: { [x: string]: any }[]
+        isPreview?: boolean
+        handleEdit?: (data: { [x: string]: any }) => void
+        handleDelete?: (id: number) => void
+    }
 }

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott