Explorar o código

Merge branch 'develop' of http://git.zanxiangnet.com/wjx/ad-manage

wjx hai 5 meses
pai
achega
91cd8f8e54
Modificáronse 25 ficheiros con 875 adicións e 148 borrados
  1. 6 0
      config/routerConfig.ts
  2. 1 0
      src/pages/launchSystemV3/adqv3/ad/autoAcquisitionSet.tsx
  3. 11 5
      src/pages/launchSystemV3/adqv3/ad/index.tsx
  4. 4 3
      src/pages/launchSystemV3/adqv3/ad/tableConfig.tsx
  5. 4 3
      src/pages/launchSystemV3/adqv3/ad/updateAd3.tsx
  6. 1 1
      src/pages/launchSystemV3/adqv3/config.ts
  7. 7 0
      src/pages/launchSystemV3/adqv3/const.tsx
  8. 14 14
      src/pages/launchSystemV3/components/ConversionSelect/index.tsx
  9. 1 1
      src/pages/launchSystemV3/components/ConversionSelect/tableConfig.tsx
  10. 6 0
      src/pages/launchSystemV3/material/cloudNew/selectGroupCloudNew.tsx
  11. 15 1
      src/pages/launchSystemV3/material/cloudNew/selectGroupSearch.tsx
  12. 93 0
      src/pages/launchSystemV3/material/cloudNew/syncCloudSc.tsx
  13. 42 4
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/addSubAccount.tsx
  14. 11 3
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/index.tsx
  15. 1 0
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/modifyAccountGroup.tsx
  16. 10 8
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/subAccount.tsx
  17. 1 1
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfig.tsx
  18. 24 13
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfigSub.tsx
  19. 95 0
      src/pages/launchSystemV3/tencenTasset/copyWriting/index.tsx
  20. 81 0
      src/pages/launchSystemV3/tencenTasset/copyWriting/modifyCopyWriting.tsx
  21. 160 0
      src/pages/launchSystemV3/tencenTasset/copyWriting/selectCopyWriting.tsx
  22. 100 0
      src/pages/launchSystemV3/tencenTasset/copyWriting/tableConfig.tsx
  23. 23 0
      src/pages/launchSystemV3/tencentAdPutIn/const.ts
  24. 102 89
      src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx
  25. 62 2
      src/services/adqV3/global.ts

+ 6 - 0
config/routerConfig.ts

@@ -179,6 +179,12 @@ const launchSystemV3 = {
                     path: '/launchSystemV3/tencenTasset/accountAssetSharing',
                     access: 'accountAssetSharing',
                     component: './launchSystemV3/tencenTasset/accountAssetSharing',
+                },
+                {
+                    name: '文案库',
+                    path: '/launchSystemV3/tencenTasset/copyWriting',
+                    access: 'copyWriting',
+                    component: './launchSystemV3/tencenTasset/copyWriting',
                 }
             ],
         },

+ 1 - 0
src/pages/launchSystemV3/adqv3/ad/autoAcquisitionSet.tsx

@@ -80,6 +80,7 @@ const AutoAcquisitionSet: React.FC<Props> = ({ selectAdList, visible, onChange,
                     size="small"
                     scroll={{ x: 400, y: 450 }}
                     bordered
+                    rowKey={'adgroupId'}
                     pagination={false}
                     columns={[
                         {

+ 11 - 5
src/pages/launchSystemV3/adqv3/ad/index.tsx

@@ -263,7 +263,7 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                         }}
                         value={handleType}
                         dropdownMatchSelectWidth={false}
-                        options={[{ label: '广告操作', value: 1 }, { label: '创意操作', value: 2 }, { label: '修改深度优化期望ROI', value: 3 }]}
+                        options={[{ label: '广告操作', value: 1 }, { label: '创意操作', value: 2 }, { label: '修改首日付费 ROI', value: 3 }, { label: '修改首日变现 ROI', value: 4 }]}
                     /></Col>
                     <Col>
                         <Button type='dashed' onClick={() => { setCzjlShow(true) }}>操作记录</Button>
@@ -357,7 +357,11 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                     </> : handleType === 3 ? <>
                         <Col><Button type='primary' disabled={selectedRows.length === 0} onClick={() => {
                             setUpdateDate({ visible: true, type: '深度优化ROI' })
-                        }}>修改深度优化期望ROI</Button></Col>
+                        }}>修改首日付费 ROI</Button></Col>
+                    </> : handleType === 4 ? <>
+                        <Col><Button type='primary' disabled={selectedRows.length === 0} onClick={() => {
+                            setUpdateDate({ visible: true, type: '深度优化ROI' })
+                        }}>修改首日变现 ROI</Button></Col>
                     </> : null}
                 </Row>
             </Space>}
@@ -380,8 +384,10 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                         }
                     } else {
                         return {
-                            disabled: handleType === 3 ? record.isDeleted || !(record?.deepConversionSpec?.deepConversionWorthSpec?.goal === 'GOAL_1DAY_PURCHASE_ROAS')
-                                : record.isDeleted
+                            disabled:
+                                handleType === 3 ? record.isDeleted || !(record?.deepConversionSpec?.deepConversionWorthSpec?.goal === 'GOAL_1DAY_PURCHASE_ROAS') :
+                                    handleType === 4 ? record.isDeleted || !(record?.deepConversionSpec?.deepConversionWorthSpec?.goal === 'GOAL_1DAY_MONETIZATION_ROAS')
+                                        : record.isDeleted
                         }
                     }
 
@@ -512,7 +518,7 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
         />}
 
         {/* 批量一键起量 */}
-        {autoAcqVisible && <AutoAcquisitionSet 
+        {autoAcqVisible && <AutoAcquisitionSet
             selectAdList={selectedRows}
             visible={autoAcqVisible}
             onClose={() => {

+ 4 - 3
src/pages/launchSystemV3/adqv3/ad/tableConfig.tsx

@@ -3,7 +3,7 @@ import React from 'react'
 import { Badge, Space, Tag } from 'antd'
 import '../index.less'
 import { copy } from '@/utils/utils'
-import { ADGROUP_STATUS, AUTO_ACQUISTION_STATUS } from '../const'
+import { ADGROUP_STATUS, AUTO_ACQUISTION_STATUS, GOAL_ENUM } from '../const'
 import SwitchStatus from './switchStatus'
 import TimeSeriesLook from '@/pages/launchSystemNew/adq/ad/timeSeriesLook'
 import CreativePreview from '../../adMonitorListV3/CreativePreview'
@@ -133,9 +133,10 @@ function tableConfig(onChange: () => void, creativeHandle?: (id: number) => void
             dataIndex: 'deepConversionBehaviorBid',
             key: 'deepConversionBehaviorBid',
             width: 140,
-            align: 'center',
+            align: 'right',
+            ellipsis: true,
             render: (a: string, b: any) => {
-                return b?.deepConversionSpec?.deepConversionWorthSpec?.expectedRoi || '--'
+                return b?.deepConversionSpec?.deepConversionWorthSpec?.expectedRoi ? b?.deepConversionSpec?.deepConversionWorthSpec?.expectedRoi + '/' + GOAL_ENUM[b?.deepConversionSpec?.deepConversionWorthSpec?.goal as keyof typeof GOAL_ENUM] : '--'
             }
         },
         {

+ 4 - 3
src/pages/launchSystemV3/adqv3/ad/updateAd3.tsx

@@ -27,6 +27,7 @@ const UpdateAd3: React.FC<Props> = ({ visible, type, onClose, onChange, updateDa
     const [form] = Form.useForm();
     const timeSeriesType = Form.useWatch('timeSeriesType', form)
     const updateType = Form.useWatch('updateType', form)
+    const goal = Form.useWatch(['deepConversionSpec', 'deepConversionWorthSpec', 'goal'], form)
 
     const [failIdList, setFailIdList] = useState<{ adgroupId: number, code: number, message: string, messageCn: string }[]>([])
     const [failVisible, setFailVisible] = useState<boolean>(false)
@@ -271,7 +272,7 @@ const UpdateAd3: React.FC<Props> = ({ visible, type, onClose, onChange, updateDa
                         </Form.Item>
                         <Form.Item label={<strong>深度优化目标</strong>} name={['deepConversionSpec', 'deepConversionWorthSpec', 'goal']} rules={[{ required: true, message: '请选择深度优化目标' }]}>
                             <Select style={{ width: 480 }} placeholder='请选择'>
-                                <Select.Option value={'GOAL_1DAY_PURCHASE_ROAS'}>首日付费ROI</Select.Option>
+                                {goal === 'GOAL_1DAY_PURCHASE_ROAS' ? <Select.Option value={'GOAL_1DAY_PURCHASE_ROAS'}>首日付费ROI</Select.Option> : goal === 'GOAL_1DAY_MONETIZATION_ROAS' ? <Select.Option value={'GOAL_1DAY_MONETIZATION_ROAS'}>首日变现 ROI</Select.Option> : null}
                             </Select>
                         </Form.Item>
                         <Form.Item
@@ -342,7 +343,7 @@ const UpdateAd3: React.FC<Props> = ({ visible, type, onClose, onChange, updateDa
             <Table
                 size="small"
                 bordered
-                rowKey={'adgroupId'}
+                rowKey={'id'}
                 columns={[{
                     title: '广告ID',
                     dataIndex: 'adgroupId',
@@ -362,7 +363,7 @@ const UpdateAd3: React.FC<Props> = ({ visible, type, onClose, onChange, updateDa
                     key: 'messageCn',
                     render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
                 }]}
-                dataSource={failIdList}
+                dataSource={failIdList.map((item, index) => ({...item, id: index}))}
             />
         </Modal>}
     </>

+ 1 - 1
src/pages/launchSystemV3/adqv3/config.ts

@@ -14,7 +14,7 @@ const txAdConfig = [
             { title: '投放时间', dataIndex: 'timeSeries', label: '广告详情', default: 9, width: 55 },
             { title: '首日开始投放时间', dataIndex: 'firstDayBeginTime', label: '广告详情', default: 10, width: 70 },
             { title: '出价', dataIndex: 'bidAmount', label: '广告详情', default: 11, width: 140 },
-            { title: '深度优化行为出价', dataIndex: 'deepConversionBehaviorBid', label: '广告详情', default: 12, width: 70 },
+            { title: '深度优化行为出价', dataIndex: 'deepConversionBehaviorBid', label: '广告详情', default: 12, width: 120 },
             { title: '出价类型', dataIndex: 'smartBidType', label: '广告详情', default: 13, width: 80 },
             { title: '出价策略', dataIndex: 'bidStrategy', label: '广告详情', default: 14, width: 80 },
             { title: '一键起量', dataIndex: 'autoAcquisitionEnabled', label: '广告详情', default: 15, width: 70 },

+ 7 - 0
src/pages/launchSystemV3/adqv3/const.tsx

@@ -56,4 +56,11 @@ export enum AUTO_ACQUISTION_STATUS {
     AUTO_ACQUISTION_STATUS_SUSPEND_ON_LEARNING_FAIL="起量中止(探索过程中,因广告起量情况太差,从而探索中止)",
     AUTO_ACQUISTION_STATUS_SUSPEND_ON_PLAYING_FAIL="起量中止(探索过程中,因广告无法播放,从而起量中止(包括广告主动或被动下线或 timeset 不连续))",
     AUTO_ACQUISTION_STATUS_ADVERTISER_CLOSED="广告主主动关闭一键起量功能"
+}
+
+/** 广告深度优化转化目标类型 */
+export enum GOAL_ENUM {
+    GOAL_1DAY_PURCHASE_ROAS="首日付费ROI",
+    GOAL_1DAY_MONETIZATION_ROAS="首日付费ROI",
+    GOAL_30DAY_ORDER_ROAS="下单ROI"
 }

+ 14 - 14
src/pages/launchSystemV3/components/ConversionSelect/index.tsx

@@ -50,9 +50,9 @@ const ConversionSelect: React.FC<Props> = (props) => {
         let { optimizationGoal, deepConversionSpec, siteSet, marketingSubGoal, marketingGoal } = adgroups
         let params: { [x: string]: any } = {}
         if (putInType === 'NOVEL') {
-            params = { accountId, pageNum: 1, pageSize: 100, conversionName, createSourceType: 'SELF_CREATED', optimizationGoal, deepWorthOptimizationGoal: deepConversionSpec?.deepConversionWorthSpec?.goal, siteSet, taskType: putInType }
+            params = { accountId, pageNum: 1, pageSize: 200, conversionName, createSourceType: 'SELF_CREATED', optimizationGoal, deepWorthOptimizationGoal: deepConversionSpec?.deepConversionWorthSpec?.goal, siteSet, taskType: putInType }
         } else {
-            params = { accountId, pageNum: 1, pageSize: 100, marketingGoal, marketingSubGoal, conversionName, createSourceType: 'SELF_CREATED', optimizationGoal, deepWorthOptimizationGoal: deepConversionSpec?.deepConversionWorthSpec?.goal, siteSet, taskType: putInType }
+            params = { accountId, pageNum: 1, pageSize: 200, marketingGoal, marketingSubGoal, conversionName, createSourceType: 'SELF_CREATED', optimizationGoal, deepWorthOptimizationGoal: deepConversionSpec?.deepConversionWorthSpec?.goal, siteSet, taskType: putInType }
         }
         getConversionInfo.run(params).then(res => {
             setTableData(res?.records || [])
@@ -176,18 +176,18 @@ const ConversionSelect: React.FC<Props> = (props) => {
                             }
                         },
                     }}
-                    onRow={(record) => ({
-                        onClick: () => {
-                            let newDatas = JSON.parse(JSON.stringify(data))
-                            let oldData = newDatas[selectAdz - 1]?.newConversionList || []
-                            const selected = oldData?.some((item: any) => item?.conversionId === record.conversionId);
-                            const newSelectedRows = selected
-                                ? oldData?.filter((item: any) => item?.conversionId !== record.conversionId)
-                                : [...oldData, record];
-                            newDatas[selectAdz - 1]['newConversionList'] = newSelectedRows;
-                            setData([...newDatas])
-                        },
-                    })}
+                    // onRow={(record) => ({
+                    //     onClick: () => {
+                    //         let newDatas = JSON.parse(JSON.stringify(data))
+                    //         let oldData = newDatas[selectAdz - 1]?.newConversionList || []
+                    //         const selected = oldData?.some((item: any) => item?.conversionId === record.conversionId);
+                    //         const newSelectedRows = selected
+                    //             ? oldData?.filter((item: any) => item?.conversionId !== record.conversionId)
+                    //             : [...oldData, record];
+                    //         newDatas[selectAdz - 1]['newConversionList'] = newSelectedRows;
+                    //         setData([...newDatas])
+                    //     },
+                    // })}
                 />
             </div>
         </div>

+ 1 - 1
src/pages/launchSystemV3/components/ConversionSelect/tableConfig.tsx

@@ -18,7 +18,7 @@ let columns = (): TableProps<any>['columns'] => [
         width: 100,
         align: 'center',
         ellipsis: true,
-        render: (a: any, b: any) => {
+        render: (a: any) => {
             return <span style={{ fontSize: "12px" }}>{OPTIMIZATIONGOAL_ENUM[a as keyof typeof OPTIMIZATIONGOAL_ENUM]}</span>
         }
     },

+ 6 - 0
src/pages/launchSystemV3/material/cloudNew/selectGroupCloudNew.tsx

@@ -13,6 +13,7 @@ import { EyeOutlined, SortAscendingOutlined } from "@ant-design/icons"
 import PlayVideo from "./playVideo"
 import Lazyimg from "react-lazyimg-component"
 import { useLocalStorageState } from "ahooks"
+import SyncCloudSc from "./syncCloudSc"
 
 const { Text, Paragraph } = Typography;
 
@@ -128,6 +129,7 @@ const SelectGroupCloudNew: React.FC<CLOUDNEW.SelectGroupCloudNewProps> = ({ num,
             {ownerAccountId && ownerAccountId !== -1 ? <>
                 {/* 搜索 */}
                 <SelectGroupSearch
+                    type={defaultParams.materialType}
                     onSearch={(value) => { setSearchParams(value) }}
                 />
                 <Card
@@ -172,6 +174,10 @@ const SelectGroupCloudNew: React.FC<CLOUDNEW.SelectGroupCloudNewProps> = ({ num,
                                     <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
                                     {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
                                     {sortData?.sortField && <Text>「排序-{showField1List.find(item => item.value === sortData.sortField)?.label}-{sortData.sortType ? '正序' : '倒序'}」</Text>}
+                                    {/* 同步素材 */}
+                                    <SyncCloudSc 
+                                        accountId={ownerAccountId}
+                                    />
                                 </div>
                                 <div className={style.left_bts}>
                                     <Popover

+ 15 - 1
src/pages/launchSystemV3/material/cloudNew/selectGroupSearch.tsx

@@ -4,13 +4,15 @@ import { getUserAllApi } from "@/services/operating/account";
 import { useAjax } from "@/Hook/useAjax";
 import { SearchOutlined } from "@ant-design/icons";
 import moment from "moment";
+import { SOURCE_TYPE_IMAGE_ENUM, SOURCE_TYPE_VIDEO_ENUM } from "../../tencentAdPutIn/const";
 
 interface Props {
+    type: 'image' | 'video'
     onSearch?: (value: Partial<CLOUDNEW.GetMaterialListProps>) => void
 }
 
 // 选择素材搜索
-const SelectGroupSearch: React.FC<Props> = ({ onSearch }) => {
+const SelectGroupSearch: React.FC<Props> = ({ type, onSearch }) => {
 
     /**********************************/
     const [form] = Form.useForm();
@@ -112,6 +114,18 @@ const SelectGroupSearch: React.FC<Props> = ({ onSearch }) => {
                         options={[{ label: '横板', value: 1 }, { label: '竖版', value: 2 }, { label: '方图', value: 3 }]}
                     />
                 </Form.Item></Col>
+                <Col><Form.Item name={'sourceType'}>
+                    <Select
+                        placeholder="来源"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ width: 100 }}
+                        allowClear
+                        dropdownMatchSelectWidth={false}
+                        options={type === 'image' ? Object.keys(SOURCE_TYPE_IMAGE_ENUM).map(key => ({ label: SOURCE_TYPE_IMAGE_ENUM[key as keyof typeof SOURCE_TYPE_IMAGE_ENUM], value: key })) : Object.keys(SOURCE_TYPE_VIDEO_ENUM).map(key => ({ label: SOURCE_TYPE_VIDEO_ENUM[key as keyof typeof SOURCE_TYPE_VIDEO_ENUM], value: key })) as any}
+                    />
+                </Form.Item></Col>
                 <Col><Form.Item name={'uploadTime'}>
                     <DatePicker.RangePicker style={{ width: 260 }} placeholder={['上传时间开始', '上传时间结束']} />
                 </Form.Item></Col>

+ 93 - 0
src/pages/launchSystemV3/material/cloudNew/syncCloudSc.tsx

@@ -0,0 +1,93 @@
+import { useAjax } from "@/Hook/useAjax"
+import { syncMediaImageApi } from "@/services/adqV3/global"
+import { DatePicker, Form, message, Modal, Radio } from "antd"
+import { RangePickerProps } from "antd/lib/date-picker"
+import React, { useState } from "react"
+import moment from "moment"
+import '../../tencentAdPutIn/index.less'
+
+/**
+ * 同步素材
+ * @returns 
+ */
+const SyncCloudSc: React.FC<{ onChange?: () => void, accountId: number }> = ({ onChange, accountId }) => {
+
+    /********************************************/
+    const [form] = Form.useForm()
+
+    const syncMediaImage = useAjax((params) => syncMediaImageApi(params))
+    const [visible, setVisible] = useState<boolean>(false)
+    /********************************************/
+
+    const handleOk = () => {
+        form.validateFields().then(valid => {
+            console.log(valid)
+            const { createTime, ...params } = valid
+            syncMediaImage.run({ ...params, accountIds: [accountId], createTimeMin: moment(createTime[0]).format('YYYY-MM-DD') + ' 00:00:00', createTimeMax: moment(createTime[1]).format('YYYY-MM-DD') + ' 23:59:59' }).then(res => {
+                if (res) {
+                    message.success('同步成功')
+                    onChange?.()
+                    setVisible(false)
+                }
+            })
+        })
+    }
+
+    const disabledDate: RangePickerProps['disabledDate'] = current => {
+        // 禁止选择当前日期之前的日期  
+        return current && current > moment().endOf('day');
+    };
+
+    return <>
+        <a onClick={() => setVisible(true)}>同步素材</a>
+        {visible && <Modal
+            title={<strong>同步素材</strong>}
+            open={visible}
+            onCancel={() => {
+                setVisible(false)
+            }}
+            onOk={handleOk}
+            className="modalResetCss"
+            confirmLoading={syncMediaImage.loading}
+            okText={'同步'}
+        >
+            <Form
+                name="basicSyncCloudSc"
+                form={form}
+                layout='vertical'
+                autoComplete="off"
+                initialValues={{ type: 'image', createTime: [moment(), moment()] }}
+            >
+                <Form.Item label={<strong>素材类型</strong>} name="type" rules={[{ required: true, message: '请选择素材类型!' }]}>
+                    <Radio.Group buttonStyle="solid">
+                        <Radio.Button value="image">图片</Radio.Button>
+                        <Radio.Button value="video">视频</Radio.Button>
+                    </Radio.Group>
+                </Form.Item>
+                <Form.Item
+                    label={<strong>创建时间</strong>}
+                    name="createTime"
+                    rules={[
+                        { required: true, message: '请选择创建时间' },
+                        () => ({
+                            validator(_, value) {
+                                if (value) {
+                                    const [startDate, endDate] = value;
+                                    // 检查选择的日期范围是否超过30天  
+                                    if (endDate.diff(startDate, 'days') >= 30) {
+                                        return Promise.reject(new Error('选择的日期范围不能超过30天!'));
+                                    }
+                                }
+                                return Promise.resolve();
+                            },
+                        })
+                    ]}
+                >
+                    <DatePicker.RangePicker disabledDate={disabledDate} />
+                </Form.Item>
+            </Form>
+        </Modal>}
+    </>
+}
+
+export default React.memo(SyncCloudSc)

+ 42 - 4
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/addSubAccount.tsx

@@ -1,11 +1,13 @@
-import { Form, message, Modal, Transfer } from "antd"
+import { Form, message, Modal, Select, Transfer } from "antd"
 import React, { useEffect, useState } from "react"
 import '../../tencentAdPutIn/index.less'
 import { getAccountAllListApi } from "@/services/launchAdq/adAuthorize"
 import { useAjax } from "@/Hook/useAjax"
-import { authAccountAssetsGroupAccountApi } from "@/services/adqV3/global"
+import { authAccountAssetsGroupAccountApi, getConversionInfoApi } from "@/services/adqV3/global"
+import { GOAL_ROAS_ENUM, OPTIMIZATIONGOAL_ENUM } from "../../tencentAdPutIn/const"
 
 interface Props {
+    authType: 'material' | 'conversion'
     // 主账户
     authMainAccountId: string
     // 主账户ID
@@ -20,16 +22,34 @@ interface Props {
     onClose?: () => void
     onChange?: () => void
 }
-const AddSubAccount: React.FC<Props> = ({ authMainAccountId, accountAssetsGroupId, selectedAccountIdList = [], corporationName, corporationLicence, visible, onClose, onChange }) => {
+const AddSubAccount: React.FC<Props> = ({ authType, authMainAccountId, accountAssetsGroupId, selectedAccountIdList = [], corporationName, corporationLicence, visible, onClose, onChange }) => {
 
     /************************************/
     const [form] = Form.useForm()
     const [errorDataList, setErrorDataList] = useState<any[]>([])
+    const [tableData, setTableData] = useState<any[]>([])//table数据
 
     const getAccountAllList = useAjax(() => getAccountAllListApi())
     const authAccountAssetsGroupAccount = useAjax((params) => authAccountAssetsGroupAccountApi(params))
+    const getConversionInfo = useAjax((params) => getConversionInfoApi(params))
     /************************************/
 
+    useEffect(() => {
+        if (authType === 'conversion') {
+            getConversionInfo.run({ accountId: authMainAccountId, pageNum: 1, pageSize: 1000 }).then(res => {
+                setTableData(res?.records?.filter((item: { ownerId: string }) => item.ownerId === authMainAccountId)?.map((item: { conversionName: string; optimizationGoal: string; deepBehaviorOptimizationGoal: string; deepWorthOptimizationGoal: string, conversionId: number, createSourceType: string }) => {
+                    const deepGoal = item?.deepBehaviorOptimizationGoal ? OPTIMIZATIONGOAL_ENUM[item?.deepBehaviorOptimizationGoal as keyof typeof OPTIMIZATIONGOAL_ENUM] : item?.deepWorthOptimizationGoal ? GOAL_ROAS_ENUM[item?.deepWorthOptimizationGoal as keyof typeof GOAL_ROAS_ENUM] : null
+                    const cType = item?.createSourceType === 'SELF_CREATED' ? '自建转化' : item?.createSourceType === 'PLATFORM' ? '平台类转化' : ''
+                    return {
+                        label: item.conversionName + `(优化目标:${OPTIMIZATIONGOAL_ENUM[item?.optimizationGoal as keyof typeof OPTIMIZATIONGOAL_ENUM]}${deepGoal ? '/深度优化目标:' + deepGoal : ''}/上报类型:${cType})`, //+ '_' + (item?.deepBehaviorOptimizationGoal ? OPTIMIZATIONGOAL_ENUM[item?.deepBehaviorOptimizationGoal as keyof typeof OPTIMIZATIONGOAL_ENUM] : item?.deepWorthOptimizationGoal ? GOAL_ROAS_ENUM[item?.deepWorthOptimizationGoal as keyof typeof GOAL_ROAS_ENUM] : null) + '_' + item?.createSourceType === 'SELF_CREATED' ? '自建转化' : item?.createSourceType === 'PLATFORM' ? '平台类转化' : '',
+                        value: item.conversionId,
+                        name: item.conversionName
+                    }
+                }) || [])
+            })
+        }
+    }, [authType])
+
     useEffect(() => {
         getAccountAllList.run()
     }, [])
@@ -37,7 +57,12 @@ const AddSubAccount: React.FC<Props> = ({ authMainAccountId, accountAssetsGroupI
     const handleOk = () => {
         form.validateFields().then(valid => {
             console.log(valid)
-            authAccountAssetsGroupAccount.run({ ...valid, accountAssetsGroupId: accountAssetsGroupId }).then(res => {
+            let params = { ...valid, accountAssetsGroupId: accountAssetsGroupId }
+            if (valid.assetId) {
+                const assetName = tableData.find(item => item.value === valid.assetId).name
+                params.assetName = assetName
+            }
+            authAccountAssetsGroupAccount.run(params).then(res => {
                 console.log(res)
                 if (res) {
                     message.success('授权成功')
@@ -61,6 +86,19 @@ const AddSubAccount: React.FC<Props> = ({ authMainAccountId, accountAssetsGroupI
             layout='vertical'
             autoComplete="off"
         >
+            {authType === 'conversion' && <Form.Item label={<strong>授权资产</strong>} name="assetId" rules={[{ required: true, message: '请授权资产!' }]}>
+                <Select
+                    placeholder="请选择授权资产"
+                    allowClear
+                    showSearch
+                    filterOption={(input, option) =>
+                        (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }
+                    loading={getConversionInfo.loading}
+                    options={tableData}
+                    dropdownMatchSelectWidth={false}
+                />
+            </Form.Item>}
             <Form.Item label={<strong>请选择账户</strong>} name="accountId" rules={[{ required: true, message: '请选择账户!' }]} valuePropName="targetKeys">
                 <Transfer
                     showSearch

+ 11 - 3
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/index.tsx

@@ -1,7 +1,7 @@
 import { useAjax } from "@/Hook/useAjax"
 import { delAuthAccountAssetsGroupApi, getAccountAssetsGroupListApi } from "@/services/adqV3/global"
 import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
-import { Button, Card, Input, message, Table } from "antd"
+import { Button, Card, Input, message, Radio, Select, Table } from "antd"
 import React, { useEffect, useState } from "react"
 import '../../tencentAdPutIn/index.less'
 import ModifyAccountGroup from "./modifyAccountGroup"
@@ -16,8 +16,8 @@ import SubAccount from "./subAccount"
 const AccountAssetSharing: React.FC = () => {
 
     /****************************************/
-    const [queryForm, setQueryForm] = useState<{ accountGroupName?: string, authMainAccountIds?: number[], pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
-    const [queryFormNew, setQueryFormNew] = useState<{ accountGroupName?: string, authMainAccountIds?: number[], pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryForm, setQueryForm] = useState<{ accountGroupName?: string, authMainAccountIds?: number[], authType?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ accountGroupName?: string, authMainAccountIds?: number[], authType?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
     const [visible, setVisible] = useState<boolean>(false)
     const [initialValues, setInitialValues] = useState<any>(undefined)
     const [subAccountData, setSubAccountData] = useState<{ visible: boolean, data: any }>({ visible: false, data: undefined })
@@ -52,6 +52,14 @@ const AccountAssetSharing: React.FC = () => {
     return <Card
         className="cardResetCss"
         title={<div className="flexStart" style={{ gap: 8 }}>
+            <Select
+                value={queryForm?.authType}
+                allowClear
+                onChange={(e) => setQueryForm({ ...queryForm, authType: e, pageNum: 1 })}
+                placeholder="授权类型"
+                style={{ width: 110 }}
+                options={[{ label: '素材', value: 'material' }, { label: '转化归因', value: 'conversion' }]}
+            />
             <Input style={{ width: 200 }} placeholder="请输入账户组名称" value={queryForm?.accountGroupName} allowClear onChange={(e) => setQueryForm({ ...queryForm, accountGroupName: e.target.value, pageNum: 1 })} />
             <Input.TextArea
                 style={{ width: 300 }}

+ 1 - 0
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/modifyAccountGroup.tsx

@@ -77,6 +77,7 @@ const ModifyAccountGroup: React.FC<Props> = ({ initialValues, visible, onChange,
             <Form.Item label={<strong>授权类型</strong>} name="authType" rules={[{ required: true, message: '请选择授权内容!' }]}>
                 <Radio.Group buttonStyle="solid" disabled={initialValues}>
                     <Radio.Button value="material">素材</Radio.Button>
+                    <Radio.Button value="conversion">转化归因</Radio.Button>
                 </Radio.Group>
             </Form.Item>
 

+ 10 - 8
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/subAccount.tsx

@@ -43,20 +43,21 @@ const SubAccount: React.FC<Props> = ({ data, visible, onClose }) => {
     const handleSearch = (data: any) => {
         let accountids = queryForm?.accountids || []
         if (accountids?.length) {
-            setDataSource(data?.filter((accountId: string) => accountids?.some(val => accountId?.toString().toLowerCase()?.includes(val)))?.map((accountId: any) => ({ accountId })))
+            setDataSource(data?.filter((item: { accountId: number }) => accountids?.some(val => item.accountId?.toString().toLowerCase()?.includes(val))))
         } else {
-            setDataSource(data?.map((accountId: any) => ({ accountId })))
+            setDataSource(data)
         }
     }
 
-    const del = (accountId: number[]) => {
-        revokeAuthAccountAssetsGroup.run({ accountAssetsGroupId: data.id, accountId }).then(res => {
-            console.log('----------->', res)
+    const del = (accountId: number[], assetId?: number) => {
+        const hide = message.loading('取消授权中。。。')
+        revokeAuthAccountAssetsGroup.run({ accountAssetsGroupId: data.id, accountId, assetId }).then(res => {
+            hide()
             if (res) {
                 message.success('取消成功')
                 getList()
             }
-        })
+        }).catch(() => hide())
     }
 
     return <Modal
@@ -84,7 +85,7 @@ const SubAccount: React.FC<Props> = ({ data, visible, onClose }) => {
                         setQueryForm({ ...queryForm, accountids: arr })
                     }}
                 />
-                <Button type="primary" icon={<SearchOutlined />} onClick={() => handleSearch(getAccountAssetsGroupAccountList?.data || [])}>搜索</Button>
+                <Button type="primary" icon={<SearchOutlined />} onClick={() => getList()}>搜索</Button>
                 <Button type="primary" icon={<PlusOutlined />} onClick={() => { setVisibleS(true) }}>子账户授权</Button>
             </div>
 
@@ -95,11 +96,12 @@ const SubAccount: React.FC<Props> = ({ data, visible, onClose }) => {
                 loading={getAccountAssetsGroupAccountList?.loading}
                 scroll={{ y: 600 }}
                 bordered
-                rowKey={'accountId'}
+                rowKey={'id'}
             />
         </Space>
 
         {visibleS && <AddSubAccount
+            authType={data.authType}
             corporationName={data.corporationName}
             corporationLicence={data.corporationLicence}
             authMainAccountId={data.authMainAccountId}

+ 1 - 1
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfig.tsx

@@ -54,7 +54,7 @@ const columns = (del: (id: number) => void, update: (data: any) => void, handleS
             width: 90,
             align: 'center',
             render: (a) => {
-                return a === 'material' ? <Tag color="#f50">素材</Tag> : '--'
+                return a === 'material' ? <Tag color="#f50">素材</Tag> : a === 'conversion' ? <Tag color="#2db7f5">转化归因</Tag> : '--'
             }
         },
         {

+ 24 - 13
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfigSub.tsx

@@ -2,30 +2,21 @@ import { Space, Popconfirm, TableProps } from "antd"
 import React from "react"
 
 
-const columns = (del: (id: number[]) => void): TableProps<any>['columns'] => {
+const columns = (del: (id: number[], assetId?: number) => void): TableProps<any>['columns'] => {
 
 
     const data: TableProps<any>['columns'] = [
-        {
-            title: '子账户',
-            dataIndex: 'accountId',
-            key: 'accountId',
-            ellipsis: true,
-            width: 110,
-            align: 'center',
-            render: (a) => {
-                return <span style={{ fontSize: "12px" }}>{a}</span>
-            }
-        },
         {
             title: '操作',
             dataIndex: 'cz',
             key: 'cz',
+            width: 70,
+            align: 'center',
             render: (_, b) => {
                 return <Space>
                     <Popconfirm
                         title="确定取消授权?"
-                        onConfirm={() => del([b.accountId])}
+                        onConfirm={() => del([b.accountId], b?.assetId)}
                         okText="是"
                         cancelText="否"
                     >
@@ -33,6 +24,26 @@ const columns = (del: (id: number[]) => void): TableProps<any>['columns'] => {
                     </Popconfirm>
                 </Space>
             }
+        },
+        {
+            title: '子账户',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            ellipsis: true,
+            width: 110,
+            align: 'center',
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '资产',
+            dataIndex: 'assetId',
+            key: 'assetId',
+            ellipsis: true,
+            render: (a, b) => {
+                return a ? a + (b?.assetName ? `(${b?.assetName})` : '') : '--'
+            }
         }
     ]
 

+ 95 - 0
src/pages/launchSystemV3/tencenTasset/copyWriting/index.tsx

@@ -0,0 +1,95 @@
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import { Button, Card, Input, message, Table } from "antd"
+import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
+import { useAjax } from "@/Hook/useAjax"
+import { delCopyWritingApi, getCopyWritingListApi } from "@/services/adqV3/global"
+import ModifyCopyWriting from "./modifyCopyWriting"
+import columns from "./tableConfig"
+
+/**
+ * 文案库
+ * @returns 
+ */
+const CopyWriting: React.FC = () => {
+
+    /*************************************/
+    const [queryForm, setQueryForm] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [initialValues, setInitialValues] = useState<any>()
+    const [visible, setVisible] = useState<boolean>(false)
+
+
+    const getCopyWritingList = useAjax((params) => getCopyWritingListApi(params))
+    const delCopyWriting = useAjax((params) => delCopyWritingApi(params))
+    /*************************************/
+
+    useEffect(() => {
+        getCopyWritingList.run(queryFormNew)
+    }, [queryFormNew])
+
+    const del = (id: number) => {
+        delCopyWriting.run(id).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getCopyWritingList.refresh()
+            }
+        })
+    }
+
+    const update = (data: any) => {
+        setInitialValues(data)
+        setVisible(true)
+    }
+
+    return <Card
+        className="cardResetCss"
+        title={<div className="flexStart" style={{ gap: 8 }}>
+            <Input style={{ width: 200 }} placeholder="文案分类" value={queryForm?.category} allowClear onChange={(e) => setQueryForm({ ...queryForm, category: e.target.value, pageNum: 1 })} />
+            <Input style={{ width: 200 }} placeholder="关键字" value={queryForm?.content} allowClear onChange={(e) => setQueryForm({ ...queryForm, content: e.target.value, pageNum: 1 })} />
+
+            <Button type="primary" icon={<SearchOutlined />} onClick={() => setQueryFormNew({ ...queryForm })}>搜索</Button>
+            <Button type="primary" icon={<PlusOutlined />} onClick={() => { setVisible(true) }}>新增文案</Button>
+        </div>}
+    >
+
+        <Table
+            columns={columns(del, update)}
+            dataSource={getCopyWritingList.data?.records}
+            size="small"
+            loading={getCopyWritingList?.loading}
+            scroll={{ y: 600, x: 1100 }}
+            bordered
+            rowKey={'id'}
+            pagination={{
+                defaultPageSize: 20,
+                current: getCopyWritingList.data?.current || 1,
+                pageSize: getCopyWritingList.data?.size || 10,
+                total: getCopyWritingList.data?.total || 0
+            }}
+            onChange={(pagination) => {
+                const { current, pageSize } = pagination
+                setQueryForm({ ...queryForm, pageNum: current || 1, pageSize: pageSize || 10 })
+                setQueryFormNew({ ...queryForm, pageNum: current || 1, pageSize: pageSize || 10 })
+            }}
+        />
+
+
+        {/* 新增文案修改文案 */}
+        {visible && <ModifyCopyWriting
+            visible={visible}
+            initialValues={initialValues}
+            onChange={() => {
+                setInitialValues(undefined)
+                setVisible(false)
+                getCopyWritingList.refresh()
+            }}
+            onClose={() => {
+                setInitialValues(undefined)
+                setVisible(false)
+            }}
+        />}
+    </Card>
+}
+
+export default CopyWriting

+ 81 - 0
src/pages/launchSystemV3/tencenTasset/copyWriting/modifyCopyWriting.tsx

@@ -0,0 +1,81 @@
+import { useAjax } from "@/Hook/useAjax"
+import { copyWritingApi } from "@/services/adqV3/global"
+import { Form, Input, message, Modal } from "antd"
+import React from "react"
+import '../../tencentAdPutIn/index.less'
+
+
+interface Props {
+    initialValues?: any
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+
+/**
+ * 文案操作
+ * @param param0 
+ * @returns 
+ */
+const ModifyCopyWriting: React.FC<Props> = ({ visible, initialValues, onClose, onChange }) => {
+
+    /******************************/
+    const [form] = Form.useForm()
+
+    const copyWriting = useAjax((params) => copyWritingApi(params))
+    /******************************/
+
+    const handleOk = () => {
+        form.validateFields().then(valid => {
+            console.log(valid)
+            let params = JSON.parse(JSON.stringify(valid))
+            if (params?.contentList) {
+                params.contentList = params?.contentList?.split(/[,,\n\s]+/ig).filter((item: any) => item)
+            }
+            if (initialValues?.id) {
+                params.id = initialValues.id
+            }
+            copyWriting.run(params).then(res => {
+                if (res) {
+                    message.success(initialValues?.id ? '修改成功' : '新增成功')
+                    onChange?.()
+                }
+            })
+        })
+    }
+
+    return <Modal
+        title={<strong>{initialValues?.id ? '修改文案' : '新增文案'}</strong>}
+        open={visible}
+        onCancel={onClose}
+        className="modalResetCss"
+        onOk={handleOk}
+        confirmLoading={copyWriting.loading}
+    >
+        <Form
+            name="basicCopyWriting"
+            form={form}
+            layout='vertical'
+            autoComplete="off"
+            initialValues={initialValues || {
+
+            }}
+        >
+            <Form.Item label={<strong>分类</strong>} name="category" rules={[{ required: true, message: '请输入文案分类!' }]}>
+                <Input placeholder="请输入文案分类" allowClear />
+            </Form.Item>
+
+            {initialValues?.id ? <Form.Item label={<strong>文案</strong>} name="content" rules={[{ required: true, message: '请输入文案!' }]}>
+                <Input allowClear placeholder="请输入文案" />
+            </Form.Item> : <Form.Item label={<strong>文案</strong>} name="contentList" rules={[{ required: true, message: '请输入文案!' }]}>
+                <Input.TextArea
+                    placeholder="请输入文案(多个,,空格换行)"
+                    allowClear
+                    rows={6}
+                />
+            </Form.Item>}
+        </Form>
+    </Modal>
+}
+
+export default React.memo(ModifyCopyWriting)

+ 160 - 0
src/pages/launchSystemV3/tencenTasset/copyWriting/selectCopyWriting.tsx

@@ -0,0 +1,160 @@
+import { useAjax } from "@/Hook/useAjax"
+import { delCopyWritingApi, getCopyWritingListApi } from "@/services/adqV3/global"
+import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
+import { Button, Input, message, Modal, Space, Table } from "antd"
+import React, { useEffect, useState } from "react"
+import ModifyCopyWriting from "./modifyCopyWriting"
+import columns from "./tableConfig"
+import '../../tencentAdPutIn/index.less'
+
+interface Props {
+    onChange?: (value: string[]) => void
+    onClick?: React.MouseEventHandler<HTMLElement>
+}
+
+const SelectCopyWriting: React.FC<Props> = ({ onChange, onClick }) => {
+
+    /**********************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    const [queryForm, setQueryForm] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [initialValues, setInitialValues] = useState<any>()
+    const [mvisible, setMVisible] = useState<boolean>(false)
+    const [selectedRows, setSelectedRows] = useState<any[]>([])
+
+    const getCopyWritingList = useAjax((params) => getCopyWritingListApi(params))
+    const delCopyWriting = useAjax((params) => delCopyWritingApi(params))
+    /**********************************/
+
+    useEffect(() => {
+        if (visible)
+            getCopyWritingList.run(queryFormNew)
+    }, [queryFormNew, visible])
+
+    const del = (id: number) => {
+        delCopyWriting.run(id).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getCopyWritingList.refresh()
+            }
+        })
+    }
+
+    const update = (data: any) => {
+        setInitialValues(data)
+        setMVisible(true)
+    }
+
+    const handleOk = () => {
+        if (selectedRows.length > 0) {
+            onChange?.(selectedRows.map(item => item.content))
+            setSelectedRows([])
+            setVisible(false)
+        } else {
+            message.error('请选择文案')
+        }
+    }
+
+    return <>
+        <Button type="link" style={{ padding: 0, fontSize: 12 }} onClick={(e) => {
+            setVisible(true)
+            onClick?.(e)
+        }}>选择文案</Button>
+        {visible && <Modal
+            title={<strong>选择文案</strong>}
+            open={visible}
+            onCancel={() => {
+                setVisible(false)
+                setSelectedRows([])
+            }}
+            onOk={handleOk}
+            className="modalResetCss"
+            width={1000}
+        >
+            <Space style={{ width: '100%' }} direction="vertical">
+                <Space>
+                    <Input style={{ width: 200 }} placeholder="文案分类" value={queryForm?.category} allowClear onChange={(e) => setQueryForm({ ...queryForm, category: e.target.value, pageNum: 1 })} />
+                    <Input style={{ width: 200 }} placeholder="关键字" value={queryForm?.content} allowClear onChange={(e) => setQueryForm({ ...queryForm, content: e.target.value, pageNum: 1 })} />
+
+                    <Button type="primary" icon={<SearchOutlined />} onClick={() => setQueryFormNew({ ...queryForm })}>搜索</Button>
+                    <Button type="primary" icon={<PlusOutlined />} onClick={() => { setMVisible(true) }}>新增文案</Button>
+                </Space>
+
+                <Table
+                    columns={columns(del, update)}
+                    dataSource={getCopyWritingList.data?.records}
+                    size="small"
+                    loading={getCopyWritingList?.loading}
+                    scroll={{ y: 600, x: 850 }}
+                    bordered
+                    rowKey={'id'}
+                    pagination={{
+                        defaultPageSize: 20,
+                        current: getCopyWritingList.data?.current || 1,
+                        pageSize: getCopyWritingList.data?.size || 10,
+                        total: getCopyWritingList.data?.total || 0
+                    }}
+                    onChange={(pagination) => {
+                        const { current, pageSize } = pagination
+                        setQueryForm({ ...queryForm, pageNum: current || 1, pageSize: pageSize || 10 })
+                        setQueryFormNew({ ...queryForm, pageNum: current || 1, pageSize: pageSize || 10 })
+                    }}
+                    rowSelection={{
+                        selectedRowKeys: selectedRows.map(item => item.id.toString()),
+                        onSelect: (record: { id: number, mpName: string }, selected: boolean) => {
+                            if (selected) {
+                                selectedRows.push({ ...record })
+                                setSelectedRows([...selectedRows])
+                            } else {
+                                let newSelectAccData = selectedRows.filter((item: { id: number }) => item.id !== record.id)
+                                setSelectedRows([...newSelectAccData])
+                            }
+                        },
+                        onSelectAll: (selected: boolean, selectedRowss: { id: number }[], changeRows: { id: number }[]) => {
+                            if (selected) {
+                                let newSelectAccData = [...selectedRows]
+                                changeRows.forEach((item: { id: number }) => {
+                                    let index = newSelectAccData.findIndex((ite: { id: number }) => ite.id === item.id)
+                                    if (index === -1) {
+                                        let data: any = { ...item }
+                                        newSelectAccData.push(data)
+                                    }
+                                })
+                                setSelectedRows([...newSelectAccData])
+                            } else {
+                                let newSelectAccData = selectedRows.filter((item: { id: number }) => {
+                                    let index = changeRows.findIndex((ite: { id: number }) => ite.id === item.id)
+                                    if (index !== -1) {
+                                        return false
+                                    } else {
+                                        return true
+                                    }
+                                })
+                                setSelectedRows([...newSelectAccData])
+                            }
+                        }
+                    }}
+                />
+
+            </Space>
+        </Modal>}
+
+        {/* 新增文案修改文案 */}
+        {mvisible && <ModifyCopyWriting
+            visible={mvisible}
+            initialValues={initialValues}
+            onChange={() => {
+                setInitialValues(undefined)
+                setMVisible(false)
+                getCopyWritingList.refresh()
+            }}
+            onClose={() => {
+                setInitialValues(undefined)
+                setMVisible(false)
+            }}
+        />}
+    </>
+}
+
+
+export default React.memo(SelectCopyWriting)

+ 100 - 0
src/pages/launchSystemV3/tencenTasset/copyWriting/tableConfig.tsx

@@ -0,0 +1,100 @@
+import { Space, Popconfirm, TableProps } from "antd"
+import React from "react"
+
+
+const columns = (del: (id: number) => void, update: (data: any) => void): TableProps<any>['columns'] => {
+
+
+    const data: TableProps<any>['columns'] = [
+        {
+            title: '文案',
+            dataIndex: 'content',
+            key: 'content',
+            ellipsis: true,
+            width: 400,
+            fixed: 'left',
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '分类',
+            dataIndex: 'category',
+            key: 'category',
+            ellipsis: true,
+            width: 90,
+            align: 'center',
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '创建人',
+            dataIndex: 'createByName',
+            key: 'createByName',
+            align: 'center',
+            width: 75,
+            ellipsis: true,
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a || '--'}</span>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 140,
+            ellipsis: true,
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '更新人',
+            dataIndex: 'createByName',
+            key: 'createByName',
+            align: 'center',
+            width: 75,
+            ellipsis: true,
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a || '--'}</span>
+            }
+        },
+        {
+            title: '更新时间',
+            dataIndex: 'updateTime',
+            key: 'updateTime',
+            align: 'center',
+            width: 140,
+            ellipsis: true,
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 100,
+            fixed: 'right',
+            render: (_, b) => {
+                return <Space>
+                    <a style={{ fontSize: 12 }} onClick={() => { update(b) }}>修改</a>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => del(b.id)}
+                        okText="是"
+                        cancelText="否"
+                    >
+                        <a style={{ color: 'red', fontSize: 12 }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            }
+        }
+    ]
+
+    return data
+}
+
+export default columns

+ 23 - 0
src/pages/launchSystemV3/tencentAdPutIn/const.ts

@@ -798,4 +798,27 @@ export enum LABEL_TYPE_ENUM {
 	LABEL_TYPE_CUSTOMIZETEXT = '自定义标签',
 	LABEL_TYPE_ICON = '角标',
 	LABEL_TYPE_DYNAMIC = '动态标签'
+}
+
+/** 图片来源 */
+export enum SOURCE_TYPE_IMAGE_ENUM {
+	SOURCE_TYPE_UNSUPPORTED = '其他上传方式',
+	SOURCE_TYPE_LOCAL = '通过投放端本地自行上传',
+	SOURCE_TYPE_MUSE = '妙思智能制图工具',
+	SOURCE_TYPE_API = '通过 Marketing API 上传',
+	SOURCE_TYPE_QUICK_DRAW = '快速制图工具',
+	SOURCE_TYPE_VIDEO_SNAPSHOTS = '视频截图',
+	SOURCE_TYPE_TCC = '腾讯创意订制平台制作'
+}
+
+/** 视频来源 */
+export enum SOURCE_TYPE_VIDEO_ENUM {
+	SOURCE_TYPE_LOCAL = '本地自行上传',
+	SOURCE_TYPE_API = 'API 上传',
+	SOURCE_TYPE_VIDEO_MAKER_XSJ = '小视界视频制作工具',
+	SOURCE_TYPE_TCC = '腾讯创意定制平台制作',
+	SOURCE_TYPE_DERIVE = '创意衍生',
+	SOURCE_TYPE_DERIVATION = '视频派生工具',
+	SOURCE_TYPE_HUXUAN = '互选',
+	SOURCE_TYPE_HUXUAN_DERIVE = '互选二创'
 }

+ 102 - 89
src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx

@@ -8,6 +8,7 @@ import AddTextS from "./addTextS"
 import style from '../index.less'
 import styles from '../Material/index.less'
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews"
+import SelectCopyWriting from "@/pages/launchSystemV3/tencenTasset/copyWriting/selectCopyWriting"
 
 interface Props {
     textData: any,
@@ -126,6 +127,98 @@ const NewText: React.FC<Props> = ({ visible, onClose, onChange, value, textData,
         return null
     }
 
+    const setText = (valList: string[]) => {
+        let fieldData = textList.find(item => item.label === addSTitle)
+        let count = dynamicMaterialDTos.dynamicGroup.length
+        if (type === 1 || type === 0) {
+            let len = 0
+            const newTextDto = textDto.map((item: { [x: string]: any }) => {
+                if (fieldData?.value && (item?.[fieldData?.value]?.length === 0 || !item?.[fieldData?.value]?.every((t: string) => t)) && valList.length >= len + 1) {
+                    return {
+                        ...item,
+                        [fieldData.value]: (item?.[fieldData?.value]?.length === 0 ? [''] : item?.[fieldData?.value]).map((t: string) => {
+                            if (t) {
+                                return t
+                            } else {
+                                return valList[len++]
+                            }
+                        })
+                    }
+                }
+                return item
+            })
+            form.setFieldsValue({
+                textDto: newTextDto
+            })
+        } else if (type === 2) {
+            let len = 0
+            const newTextDto = textDto.map((item: { [x: string]: any }) => {
+                if (fieldData?.value && (item?.[fieldData?.value]?.length === 0 || !item?.[fieldData?.value]?.every((t: string) => t)) && valList.length >= len + 1) {
+                    return {
+                        ...item,
+                        [fieldData.value]: (item?.[fieldData?.value]?.length === 0 ? [''] : item?.[fieldData?.value]).map((t: string) => {
+                            if (t) {
+                                return t
+                            } else {
+                                return valList[len++]
+                            }
+                        })
+                    }
+                }
+                return item
+            })
+            let diffTextDto: any[] = []
+            if (newTextDto.length < count && len < valList.length) {
+                let diffCount = count - newTextDto.length
+                let diffTextCount = valList.length - len
+                let diff = 0
+                if (diffCount >= diffTextCount) {
+                    diff = diffTextCount
+                } else {
+                    diff = diffCount
+                }
+                diffTextDto = Array(diff).fill('').map((item: { [x: string]: any }) => {
+                    if (fieldData?.value) {
+                        return { ...item, [fieldData.value]: [valList[len++]] }
+                    }
+                    return item
+                })
+            }
+            form.setFieldsValue({
+                textDto: [...newTextDto, ...diffTextDto]
+            })
+        } else if ([3, 4].includes(type)) {
+            let len = 0
+            const newTextDto = textDto.map((item: { [x: string]: any }) => {
+                if (fieldData?.value && (item?.[fieldData?.value]?.length === 0 || !item?.[fieldData?.value]?.every((t: string) => t)) && valList.length >= len + 1) {
+                    return {
+                        ...item, [fieldData.value]: (item?.[fieldData?.value]?.length === 0 ? [''] : item?.[fieldData?.value]).map((t: string) => {
+                            if (t) {
+                                return t
+                            } else {
+                                return valList[len++]
+                            }
+                        })
+                    }
+                }
+                return item
+            })
+            let diffTextDto: any[] = []
+            if (len < valList.length) {
+                let diff = valList.length - len
+                diffTextDto = Array(diff).fill('').map((item: { [x: string]: any }) => {
+                    if (fieldData?.value) {
+                        return { ...item, [fieldData.value]: [valList[len++]] }
+                    }
+                    return item
+                })
+            }
+            form.setFieldsValue({
+                textDto: [...newTextDto, ...diffTextDto]
+            })
+        }
+    }
+
     return <Modal
         title={<Space>
             <strong style={{ fontSize: 20 }}>创意文案</strong>
@@ -133,6 +226,14 @@ const NewText: React.FC<Props> = ({ visible, onClose, onChange, value, textData,
                 {textList.some(item => item.value === "description") && <a style={{ fontSize: 12 }} onClick={() => { setDesVisible(true); setAddStitle(textList.find(item => item.value === "description")?.label) }}>批量添加{textList.find(item => item.value === "description")?.label}</a>}
                 {textList.some(item => item.value === "title") && <a style={{ fontSize: 12 }} onClick={() => { setDesVisible(true); setAddStitle(textList.find(item => item.value === "description")?.label) }}>批量添加{textList.find(item => item.value === "description")?.label}</a>}
             </>}
+            {textList.some(item => item.value === "description") && <SelectCopyWriting
+                onClick={() => {
+                    setAddStitle(textList.find(item => item.value === "description")?.label)
+                }}
+                onChange={(valList) => {
+                    setText(valList)
+                }}
+            />}
         </Space>}
         open={visible}
         onCancel={onClose}
@@ -285,95 +386,7 @@ const NewText: React.FC<Props> = ({ visible, onClose, onChange, value, textData,
                         .split(/\r?\n/)        // 按换行符分割字符串
                         .map(line => line.trim()) // 去除每行的首尾空白
                         .filter(line => line !== ''); // 过滤掉空行
-                    let fieldData = textList.find(item => item.label === addSTitle)
-                    let count = dynamicMaterialDTos.dynamicGroup.length
-                    if (type === 1) {
-                        let len = 0
-                        const newTextDto = textDto.map((item: { [x: string]: any }) => {
-                            if (fieldData?.value && (item?.[fieldData?.value]?.length === 0 || !item?.[fieldData?.value]?.every((t: string) => t)) && valList.length >= len + 1) {
-                                return {
-                                    ...item,
-                                    [fieldData.value]: (item?.[fieldData?.value]?.length === 0 ? [''] : item?.[fieldData?.value]).map((t: string) => {
-                                        if (t) {
-                                            return t
-                                        } else {
-                                            return valList[len++]
-                                        }
-                                    })
-                                }
-                            }
-                            return item
-                        })
-                        form.setFieldsValue({
-                            textDto: newTextDto
-                        })
-                    } else if (type === 2) {
-                        let len = 0
-                        const newTextDto = textDto.map((item: { [x: string]: any }) => {
-                            if (fieldData?.value && (item?.[fieldData?.value]?.length === 0 || !item?.[fieldData?.value]?.every((t: string) => t)) && valList.length >= len + 1) {
-                                return {
-                                    ...item,
-                                    [fieldData.value]: (item?.[fieldData?.value]?.length === 0 ? [''] : item?.[fieldData?.value]).map((t: string) => {
-                                        if (t) {
-                                            return t
-                                        } else {
-                                            return valList[len++]
-                                        }
-                                    })
-                                }
-                            }
-                            return item
-                        })
-                        let diffTextDto: any[] = []
-                        if (newTextDto.length < count && len < valList.length) {
-                            let diffCount = count - newTextDto.length
-                            let diffTextCount = valList.length - len
-                            let diff = 0
-                            if (diffCount >= diffTextCount) {
-                                diff = diffTextCount
-                            } else {
-                                diff = diffCount
-                            }
-                            diffTextDto = Array(diff).fill('').map((item: { [x: string]: any }) => {
-                                if (fieldData?.value) {
-                                    return { ...item, [fieldData.value]: [valList[len++]] }
-                                }
-                                return item
-                            })
-                        }
-                        form.setFieldsValue({
-                            textDto: [...newTextDto, ...diffTextDto]
-                        })
-                    } else if ([3, 4].includes(type)) {
-                        let len = 0
-                        const newTextDto = textDto.map((item: { [x: string]: any }) => {
-                            if (fieldData?.value && (item?.[fieldData?.value]?.length === 0 || !item?.[fieldData?.value]?.every((t: string) => t)) && valList.length >= len + 1) {
-                                return {
-                                    ...item, [fieldData.value]: (item?.[fieldData?.value]?.length === 0 ? [''] : item?.[fieldData?.value]).map((t: string) => {
-                                        if (t) {
-                                            return t
-                                        } else {
-                                            return valList[len++]
-                                        }
-                                    })
-                                }
-                            }
-                            return item
-                        })
-                        let diffTextDto: any[] = []
-                        if (len < valList.length) {
-                            let diff = valList.length - len
-                            diffTextDto = Array(diff).fill('').map((item: { [x: string]: any }) => {
-                                if (fieldData?.value) {
-                                    return { ...item, [fieldData.value]: [valList[len++]] }
-                                }
-                                return item
-                            })
-                        }
-                        form.setFieldsValue({
-                            textDto: [...newTextDto, ...diffTextDto]
-                        })
-                    }
+                    setText(valList)
                 }
                 setDesVisible(false)
                 setAddStitle(undefined)

+ 62 - 2
src/services/adqV3/global.ts

@@ -714,7 +714,7 @@ export async function getBarrageRecommendListApi(params: { adAccountId?: number,
  * @param params 
  * @returns 
  */
-export async function getAccountAssetsGroupListApi(data: { accountGroupName?: string, authMainAccountIds?: number[], pageNum: number, pageSize: number }) {
+export async function getAccountAssetsGroupListApi(data: { accountGroupName?: string, authMainAccountIds?: number[], authType?: string, pageNum: number, pageSize: number }) {
     return request(api + `/adq/v3/marketingAssets/accountAssetsGroup/pageList`, {
         method: 'POST',
         data
@@ -751,7 +751,7 @@ export async function updateAccountAssetsGroupApi(data: { accountGroupId: number
  * @param data 
  * @returns 
  */
-export async function revokeAuthAccountAssetsGroupApi(data: { accountAssetsGroupId: number, accountId: number[] }) {
+export async function revokeAuthAccountAssetsGroupApi(data: { accountAssetsGroupId: number, accountId: number[], assetId?: number }) {
     return request(api + `/adq/v3/marketingAssets/accountAssetsGroup/revokeAuth`, {
         method: 'POST',
         data
@@ -804,4 +804,64 @@ export async function authAccountAssetsGroupAccountApi(data: { accountAssetsGrou
         method: 'POST',
         data
     })
+}
+
+/**
+ * 同步素材
+ * @param data 
+ * @returns 
+ */
+export async function syncMediaImageApi({ type, ...data }: { accountIds: number[], createTimeMin: string, createTimeMax: string, type: 'image' | 'video' }) {
+    if (type === 'image') {
+        return request(api + `/adq/v3/mediaImage/syncImage`, {
+            method: 'POST',
+            data
+        })
+    }
+    return request(api + `/adq/v3/mediaVideo/syncVideo`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 获取文案库列表
+ * @param data 
+ * @returns 
+ */
+export async function getCopyWritingListApi(data: { category?: string, content?: string, pageNum: number, pageSize: number }) {
+    return request(api + `/adq/copyWriting/pageList`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 新增修改文案
+ * @param data 
+ * @returns 
+ */
+export async function copyWritingApi(data: { category: string, contentList?: string[], content?: string, id?: number }) {
+    if (data?.id) {
+        return request(api + `/adq/copyWriting/update`, {
+            method: 'POST',
+            data
+        })
+    }
+    return request(api + `/adq/copyWriting/addBatch`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 删除
+ * @param id 
+ * @returns 
+ */
+export async function delCopyWritingApi(id: number) {
+    return request(api + `/adq/copyWriting/delById/${id}`, {
+        method: 'DELETE',
+    })
 }