wjx 6 månader sedan
förälder
incheckning
8669c7ebc7
32 ändrade filer med 2131 tillägg och 228 borttagningar
  1. 3 2
      src/pages/launchSystemNew/components/newsModal/videoNews.tsx
  2. 5 3
      src/pages/launchSystemNew/launchManage/createAd/selector.tsx
  3. 196 0
      src/pages/launchSystemV3/adqv3/ad/autoAcquisitionSet.tsx
  4. 48 0
      src/pages/launchSystemV3/adqv3/ad/index.less
  5. 22 0
      src/pages/launchSystemV3/adqv3/ad/index.tsx
  6. 28 2
      src/pages/launchSystemV3/adqv3/ad/tableConfig.tsx
  7. 15 12
      src/pages/launchSystemV3/adqv3/config.ts
  8. 12 0
      src/pages/launchSystemV3/adqv3/const.tsx
  9. 4 1
      src/pages/launchSystemV3/adqv3/typings.d.ts
  10. 27 0
      src/pages/launchSystemV3/material/cloudNew/global.less
  11. 34 14
      src/pages/launchSystemV3/material/cloudNew/selectCloudNew.tsx
  12. 359 0
      src/pages/launchSystemV3/material/cloudNew/selectGroupCloudNew.tsx
  13. 118 0
      src/pages/launchSystemV3/material/cloudNew/selectGroupSearch.tsx
  14. 51 11
      src/pages/launchSystemV3/material/typings.d.ts
  15. 86 0
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/addSubAccount.tsx
  16. 51 15
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/index.tsx
  17. 65 16
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/modifyAccountGroup.tsx
  18. 120 0
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/subAccount.tsx
  19. 128 0
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfig.tsx
  20. 42 0
      src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfigSub.tsx
  21. 41 18
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx
  22. 4 3
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.tsx
  23. 3 3
      src/pages/launchSystemV3/tencentAdPutIn/create/Save/PreviewImg.tsx
  24. 97 0
      src/pages/launchSystemV3/tencentAdPutIn/create/SelectAccount/index.less
  25. 483 0
      src/pages/launchSystemV3/tencentAdPutIn/create/SelectAccount/index.tsx
  26. 37 121
      src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx
  27. 3 3
      src/pages/launchSystemV3/tencentAdPutIn/create/tableConfig.tsx
  28. 3 1
      src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts
  29. 19 0
      src/services/adqV3/cloudNew.ts
  30. 13 2
      src/services/adqV3/global.ts
  31. 10 0
      src/services/launchAdq/adAuthorize.ts
  32. 4 1
      src/utils/utils.ts

+ 3 - 2
src/pages/launchSystemNew/components/newsModal/videoNews.tsx

@@ -7,11 +7,12 @@ import { CloseOutlined } from '@ant-design/icons';
 import './index1.less'
 
 interface Props extends ImageProps {
+    keyFrameImageUrl?: string
     maskBodyStyle?: React.CSSProperties
     maskImgStyle?: React.CSSProperties
 }
 
-const VideoNews: React.FC<Props> = ({ preview = false, src, maskBodyStyle, maskImgStyle, ...data }) => {
+const VideoNews: React.FC<Props> = ({ preview = false, src, maskBodyStyle, maskImgStyle, keyFrameImageUrl, ...data }) => {
 
     /*****************************/
     const [toPlay, setToPlay] = useState<boolean>(false)
@@ -19,7 +20,7 @@ const VideoNews: React.FC<Props> = ({ preview = false, src, maskBodyStyle, maskI
 
     return <>
         <div className={`${style.imgNews} imgNews`}>
-            <Image src={src ? getVideoImgUrl(src) : 'error'} preview={false} {...data} className={style.img}/>
+            <Image src={keyFrameImageUrl || (src ? getVideoImgUrl(src) : 'error')} preview={false} {...data} className={style.img}/>
             <div className={style.mask} style={maskBodyStyle}>
                 <img src={play} style={maskImgStyle} onClick={(e) => { e.stopPropagation(); e.preventDefault(); setToPlay(true) }} />
             </div>

+ 5 - 3
src/pages/launchSystemNew/launchManage/createAd/selector.tsx

@@ -4,15 +4,17 @@ import './index.less'
 interface Props {
     label: string,
     children: React.ReactNode;
+    style?: React.CSSProperties
+    titleStyle?: React.CSSProperties
 }
 const Selector: React.FC<Props> = (props) => {
 
     /****************/
-    const { label, children } = props
+    const { label, children, style, titleStyle } = props
     /****************/
 
-    return <div className="selector">
-        <span className="selectorLabel">{label}</span>{children}
+    return <div className="selector" style={style}>
+        <span className="selectorLabel" style={titleStyle}>{label}</span>{children}
     </div>
 }
 

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

@@ -0,0 +1,196 @@
+import { InputNumber, message, Modal, Radio, Space, Switch, Table, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import style from './index.less'
+import { InfoCircleFilled, QuestionCircleOutlined } from "@ant-design/icons"
+import { updateBatchAdgroupInfoApi } from "@/services/launchAdq/adqv3"
+import { useAjax } from "@/Hook/useAjax"
+
+interface Props {
+    selectAdList: any[]
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+
+/**
+ * 批量一键起量
+ * @param param0 
+ * @returns 
+ */
+const AutoAcquisitionSet: React.FC<Props> = ({ selectAdList, visible, onChange, onClose }) => {
+
+    /****************************************/
+    const [autoAcquisitionData, setAutoAcquisitionData] = useState<{ autoAcquisitionEnabled: boolean, autoAcquisitionBudget?: number, autoAcquisitionBudgetPercent?: number }>({ autoAcquisitionEnabled: false })
+    const [isPercent, setIsPercent] = useState<boolean>(false)
+    const [addType, setAddType] = useState<'fixed' | 'percent'>('fixed')
+    const updateBatchAdgroupInfo = useAjax((params) => updateBatchAdgroupInfoApi(params)) // 名称
+    const [failIdList, setFailIdList] = useState<{ adgroupId: number, code: number, message: string, messageCn: string }[]>([])
+    const [failVisible, setFailVisible] = useState<boolean>(false)
+    /****************************************/
+
+    useEffect(() => {
+        if (selectAdList.every(item => item.autoAcquisitionEnabled)) {
+            setIsPercent(true)
+        } else {
+            setIsPercent(false)
+        }
+    }, [selectAdList])
+
+    const handleOk = () => {
+        let params = { ...autoAcquisitionData }
+        if (params?.autoAcquisitionEnabled) {
+            if (addType === 'fixed' && !params?.autoAcquisitionBudget) {
+                message.error('请填写起量预算')
+                return
+            }
+            if (addType === 'percent' && !params?.autoAcquisitionBudgetPercent) {
+                message.error('请填写起量预算百分比')
+                return
+            }
+        }
+
+        if (params?.autoAcquisitionBudgetPercent !== null && params?.autoAcquisitionBudgetPercent !== undefined) params.autoAcquisitionBudgetPercent = params?.autoAcquisitionBudgetPercent / 100
+        let accountAdgroupMaps = [...new Set(selectAdList?.map(item => item.accountId + ',' + item.adgroupId))]
+        updateBatchAdgroupInfo.run({ accountAdgroupMaps, ...params }).then(res => {
+            if (res?.failIdList?.length === 0) {
+                message.success(`修改操作完成!`)
+                onChange?.()
+            } else {
+                setFailIdList(res?.list || [])
+                setFailVisible(true)
+            }
+        })
+        console.log(params)
+    }
+
+    return <Modal
+        title={<strong>批量修改一键起量</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        className='modalResetCss'
+        width={750}
+        confirmLoading={updateBatchAdgroupInfo.loading}
+    >
+        <div className={style.autoAcquisitionSet}>
+            <div className={style.left}>
+                <Table
+                    dataSource={selectAdList}
+                    size="small"
+                    scroll={{ x: 400, y: 450 }}
+                    bordered
+                    pagination={false}
+                    columns={[
+                        {
+                            title: '广告名称',
+                            dataIndex: 'adgroupName',
+                            key: 'adgroupName',
+                            width: 200,
+                            render(value) {
+                                return <span style={{ wordBreak: 'break-all' }}>{value}</span>
+                            },
+                        },
+                        {
+                            title: '原设置',
+                            dataIndex: 'beforeSet',
+                            key: 'beforeSet',
+                            width: 120,
+                            render(value, record) {
+                                if (!record?.autoAcquisitionEnabled) {
+                                    return '未开启'
+                                }
+                                return `一键起量中,起量预算:${record?.autoAcquisitionBudget} 元`
+                            },
+                        }
+                    ]}
+                />
+            </div>
+            <div className={style.right}>
+                <div className={style.header}>
+                    <Space>
+                        <span>修改一键起量</span>
+                        <Tooltip title={<div>
+                            <p>1. 一键起量期间产生的消耗不赔付,但转化计入赔付门槛判断;</p>
+                            <p>2. 一键起量可能导致转化成本高于预期,且起量结束后不一定能持续消耗。</p>
+                        </div>}>
+                            <QuestionCircleOutlined />
+                        </Tooltip>
+                    </Space>
+                </div>
+                <div className={style.edit}>
+                    <div>
+                        <div className={style.tips}>
+                            <InfoCircleFilled style={{ color: '#296BEF', marginTop: 2 }} />
+                            {
+                                autoAcquisitionData?.autoAcquisitionEnabled ?
+                                    // '对于状态为“一键起量中”、“一键起量完成”、“一键起量中止”的广告,将会自动关闭一键起量,同时按照新的起量预算重新开启一键起量'
+                                    '开启一键起量'
+                                    :
+                                    '当前开关为关闭状态,点击「确定」将默认关闭已选广告的一键起量功能'
+                            }
+                        </div>
+                        <div>
+                            <Switch
+                                checkedChildren="开启"
+                                unCheckedChildren="未开启"
+                                onChange={(e) => {
+                                    setAutoAcquisitionData({ ...autoAcquisitionData, autoAcquisitionEnabled: e })
+                                }}
+                                checked={autoAcquisitionData?.autoAcquisitionEnabled}
+                            />
+                        </div>
+                        {autoAcquisitionData?.autoAcquisitionEnabled && <>
+                            <Radio.Group buttonStyle="solid" value={addType} onChange={(e) => setAddType(e.target.value)}>
+                                <Radio.Button value="fixed">固定值</Radio.Button>
+                                <Radio.Button value="percent" disabled={!isPercent}>百分比添加</Radio.Button>
+                            </Radio.Group>
+                            {addType === 'fixed' ?
+                                <InputNumber placeholder="起量预算,建议设置为出价的10倍" min={200} max={100000} style={{ width: '100%' }} value={autoAcquisitionData?.autoAcquisitionBudget} onChange={(e) => setAutoAcquisitionData({ autoAcquisitionEnabled: true, autoAcquisitionBudget: e || 0 })} />
+                                :
+                                <InputNumber placeholder="起量预算,原有基础上下调百分比" style={{ width: '100%' }} addonAfter="%" value={autoAcquisitionData?.autoAcquisitionBudgetPercent} onChange={(e) => setAutoAcquisitionData({ autoAcquisitionEnabled: true, autoAcquisitionBudgetPercent: e || 0 })} />
+                            }
+                        </>}
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        {failVisible && <Modal
+            title={<strong>报错信息</strong>}
+            open={failVisible}
+            className='modalResetCss'
+            width={650}
+            onCancel={() => { setFailVisible(false); setFailIdList([]) }}
+            footer={null}
+        >
+            <Table
+                size="small"
+                bordered
+                rowKey={'adgroupId'}
+                columns={[{
+                    title: '广告ID',
+                    dataIndex: 'adgroupId',
+                    key: 'adgroupId',
+                    width: 110,
+                    render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
+                }, {
+                    title: 'code',
+                    dataIndex: 'code',
+                    key: 'code',
+                    width: 70,
+                    align: 'center',
+                    render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
+                }, {
+                    title: '错误信息',
+                    dataIndex: 'messageCn',
+                    key: 'messageCn',
+                    render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
+                }]}
+                dataSource={failIdList}
+            />
+        </Modal>}
+    </Modal>
+}
+
+export default React.memo(AutoAcquisitionSet)

+ 48 - 0
src/pages/launchSystemV3/adqv3/ad/index.less

@@ -0,0 +1,48 @@
+.autoAcquisitionSet {
+    display: flex;
+
+    .left {
+        width: 60%;
+    }
+
+    .right {
+        width: 40%;
+        border: 1px solid #f0f0f0;
+        display: flex;
+        flex-direction: column;
+
+        .header {
+            padding: 8px;
+            font-size: 14px;
+            font-weight: 600;
+            border-bottom: 1px solid #f0f0f0;
+            background: #fafafa;
+        }
+
+        .edit {
+            flex: 1;
+            display: flex;
+            align-items: center;
+            padding: 8px;
+            box-sizing: border-box;
+            width: 100%;
+
+            >div {
+                display: flex;
+                flex-direction: column;
+                gap: 10px;
+                width: 100%;
+            }
+        }
+
+        .tips {
+            border: 1px solid #D4E1FC;
+            border-radius: 18px;
+            display: flex;
+            gap: 8px;
+            padding: 7px 12px;
+            background-color: #f5f8ff;
+            margin-bottom: 10px;
+        }
+    }
+}

+ 22 - 0
src/pages/launchSystemV3/adqv3/ad/index.tsx

@@ -16,6 +16,7 @@ import '../../tencentAdPutIn/index.less'
 import UserTactics from "../../tencentAdPutIn/create/TacticsS/userTactics";
 import UpdateAd3 from "./updateAd3";
 import { useLocalStorageState } from "ahooks";
+import AutoAcquisitionSet from "./autoAcquisitionSet";
 const { Text } = Typography;
 
 const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
@@ -31,6 +32,7 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
     const [handleType, setHandleType] = useState<number>(1)
     const [czjlShow, setCzjlShow] = useState(false)
     const [updateData, setUpdateDate] = useState<{ visible: boolean, type: '修改出价' | '修改名称' | '修改日限额' | '修改投放时间' | '删除' | '深度优化ROI' | '修改投放首日开始时间' }>({ visible: false, type: '修改出价' })
+    const [autoAcqVisible, setAutoAcqVisible] = useState<boolean>(false)
 
     const syncBatch = useAjax((params) => syncBatchApi(params))
     const modifyStatusBatch = useAjax((params) => modifyStatusBatchApi(params))
@@ -304,6 +306,12 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                                         key: '5',
                                         disabled: selectedRows.length === 0,
                                         onClick: () => { setUpdateDate({ visible: true, type: '修改投放首日开始时间' }) }
+                                    },
+                                    {
+                                        label: '一键起量',
+                                        key: '6',
+                                        disabled: selectedRows.length === 0,
+                                        onClick: () => { setAutoAcqVisible(true) }
                                     }
                                 ]
                             }}
@@ -502,6 +510,20 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                 }
             }}
         />}
+
+        {/* 批量一键起量 */}
+        {autoAcqVisible && <AutoAcquisitionSet 
+            selectAdList={selectedRows}
+            visible={autoAcqVisible}
+            onClose={() => {
+                setAutoAcqVisible(false)
+            }}
+            onChange={() => {
+                setAutoAcqVisible(false)
+                getAdqV3AdList.refresh()
+                setSelectedRows([])
+            }}
+        />}
     </div>
 }
 

+ 28 - 2
src/pages/launchSystemV3/adqv3/ad/tableConfig.tsx

@@ -1,9 +1,9 @@
 import { BidStrategyEnum } from '@/services/launchAdq/enum'
 import React from 'react'
-import { Badge, Space } from 'antd'
+import { Badge, Space, Tag } from 'antd'
 import '../index.less'
 import { copy } from '@/utils/utils'
-import { ADGROUP_STATUS } from '../const'
+import { ADGROUP_STATUS, AUTO_ACQUISTION_STATUS } from '../const'
 import SwitchStatus from './switchStatus'
 import TimeSeriesLook from '@/pages/launchSystemNew/adq/ad/timeSeriesLook'
 import CreativePreview from '../../adMonitorListV3/CreativePreview'
@@ -163,6 +163,32 @@ function tableConfig(onChange: () => void, creativeHandle?: (id: number) => void
                 return BidStrategyEnum[a as keyof typeof BidStrategyEnum]
             }
         },
+        {
+            title: '一键起量',
+            dataIndex: 'autoAcquisitionEnabled',
+            key: 'autoAcquisitionEnabled',
+            align: 'center',
+            width: 70,
+            render: (a: boolean) => {
+                return a ? <Tag color="success">开启</Tag> : <Tag color="error">关闭</Tag>
+            }
+        },
+        {
+            title: '一键起量状态',
+            dataIndex: 'autoAcquisitionStatus',
+            key: 'autoAcquisitionStatus',
+            align: 'center',
+            width: 120,
+            ellipsis: true,
+            render: (a: string) => AUTO_ACQUISTION_STATUS[a as keyof typeof AUTO_ACQUISTION_STATUS]
+        },
+        {
+            title: '一键起量预算',
+            dataIndex: 'autoAcquisitionBudget',
+            key: 'autoAcquisitionBudget',
+            align: 'right',
+            width: 70
+        },
         {
             title: '广告组日预算(元)',
             dataIndex: 'dailyBudget',

+ 15 - 12
src/pages/launchSystemV3/adqv3/config.ts

@@ -17,18 +17,21 @@ const txAdConfig = [
             { title: '深度优化行为出价', dataIndex: 'deepConversionBehaviorBid', label: '广告详情', default: 12, width: 70 },
             { title: '出价类型', dataIndex: 'smartBidType', label: '广告详情', default: 13, width: 80 },
             { title: '出价策略', dataIndex: 'bidStrategy', label: '广告详情', default: 14, width: 80 },
-            { title: '广告组日预算(元)', dataIndex: 'dailyBudget', label: '广告详情', default: 15, width: 70 },
-            { title: '营销目的', dataIndex: 'marketingGoal', label: '广告详情', default: 16, width: 80 },
-            { title: '推广产品类型', dataIndex: 'marketingTargetType', label: '广告详情', default: 17, width: 90 },
-            { title: '营销载体类型', dataIndex: 'marketingCarrierType', label: '广告详情', default: 18, width: 90 },
-            { title: '是否开启自动版位功能', dataIndex: 'automaticSiteEnabled', label: '广告详情', default: 19, width: 80 },
-            { title: '版位选择', dataIndex: 'siteSet', label: '广告详情', default: 20, width: 100 },
-            { title: '定向条件描述', dataIndex: 'targetingTranslation', label: '广告详情', default: 21, width: 80 },
-            { title: '创建时间', dataIndex: 'createdTime', label: '广告详情', default: 22, width: 140 },
-            { title: '是否已删除', dataIndex: 'isDeleted', label: '广告详情', default: 23, width: 60 },
-            { title: '广告状态', dataIndex: 'systemStatus', label: '广告详情', default: 24, width: 80 },
-            { title: '创意预览', dataIndex: 'dynamicCreativeList', label: '广告详情', default: 25, width: 150 },
-            { title: '操作', dataIndex: 'cz', label: '广告详情', default: 26, width: 65 },
+            { title: '一键起量', dataIndex: 'autoAcquisitionEnabled', label: '广告详情', default: 15, width: 70 },
+            { title: '一键起量状态', dataIndex: 'autoAcquisitionStatus', label: '广告详情', default: 16, width: 120 },
+            { title: '一键起量预算', dataIndex: 'autoAcquisitionBudget', label: '广告详情', default: 17, width: 70 },
+            { title: '广告组日预算(元)', dataIndex: 'dailyBudget', label: '广告详情', default: 18, width: 70 },
+            { title: '营销目的', dataIndex: 'marketingGoal', label: '广告详情', default: 19, width: 80 },
+            { title: '推广产品类型', dataIndex: 'marketingTargetType', label: '广告详情', default: 20, width: 90 },
+            { title: '营销载体类型', dataIndex: 'marketingCarrierType', label: '广告详情', default: 21, width: 90 },
+            { title: '是否开启自动版位功能', dataIndex: 'automaticSiteEnabled', label: '广告详情', default: 22, width: 80 },
+            { title: '版位选择', dataIndex: 'siteSet', label: '广告详情', default: 23, width: 100 },
+            { title: '定向条件描述', dataIndex: 'targetingTranslation', label: '广告详情', default: 24, width: 80 },
+            { title: '创建时间', dataIndex: 'createdTime', label: '广告详情', default: 25, width: 140 },
+            { title: '是否已删除', dataIndex: 'isDeleted', label: '广告详情', default: 26, width: 60 },
+            { title: '广告状态', dataIndex: 'systemStatus', label: '广告详情', default: 27, width: 80 },
+            { title: '创意预览', dataIndex: 'dynamicCreativeList', label: '广告详情', default: 28, width: 150 },
+            { title: '操作', dataIndex: 'cz', label: '广告详情', default: 29, width: 65 },
         ]
     }
 ]

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

@@ -44,4 +44,16 @@ export enum DYNAMIC_CREATIVE_STATUS {
 export const AD_STATUS = {
     AD_STATUS_NORMAL: <Badge status="success" text='有效' />,
     AD_STATUS_SUSPEND: <Badge status="warning" text='暂停' />,
+}
+
+/** 一键起量 */
+export enum AUTO_ACQUISTION_STATUS {
+    AUTO_ACQUISTION_STATUS_UNKNOW="未开启一键起量功能",
+    AUTO_ACQUISTION_STATUS_PENDING="开启一键起量,探索中",
+    AUTO_ACQUISTION_STATUS_END_LESS_THAN_24H="起量完成(探索结束,预算花完,功能开启后未满 24h)",
+    AUTO_ACQUISTION_STATUS_END_MORE_THAN_24H="起量完成(探索结束,预算花完,功能开启后已满 24h)",
+    AUTO_ACQUISTION_STATUS_COMPLETED="起量结束(探索结束,距离广告开启已满 6h,但预算未花完(实际花费<起量预算*90%))",
+    AUTO_ACQUISTION_STATUS_SUSPEND_ON_LEARNING_FAIL="起量中止(探索过程中,因广告起量情况太差,从而探索中止)",
+    AUTO_ACQUISTION_STATUS_SUSPEND_ON_PLAYING_FAIL="起量中止(探索过程中,因广告无法播放,从而起量中止(包括广告主动或被动下线或 timeset 不连续))",
+    AUTO_ACQUISTION_STATUS_ADVERTISER_CLOSED="广告主主动关闭一键起量功能"
 }

+ 4 - 1
src/pages/launchSystemV3/adqv3/typings.d.ts

@@ -47,7 +47,10 @@ declare namespace ADQV3 {
         timeSeries: string
     }
     interface UpdateBatchAdgroupInfoProps extends AccountAdgroupMapsProps {
-        adgroupName: string
+        adgroupName?: string
+        autoAcquisitionEnabled?: boolean
+        autoAcquisitionBudget?: number
+        autoAcquisitionBudgetPercent?: number
     }
     interface ModifyDailyBudgetBatchProps extends AccountAdgroupMapsProps {
         bidAmount: number

+ 27 - 0
src/pages/launchSystemV3/material/cloudNew/global.less

@@ -18,4 +18,31 @@
     opacity: 1;
     transform: scale(1);
     transition: opacity 400ms, transform 400ms;
+}
+
+.SCK .ant-modal-header {
+    padding: 0 8px;
+
+    .SCK_header {
+        display: flex;
+
+        >div {
+            padding: 8px 8px;
+            font-weight: bold;
+            box-sizing: border-box;
+            cursor: pointer;
+        }
+
+        .selected {
+            color: #1890ff;
+            border-bottom: 2px solid #1890ff;
+        }
+    }
+}
+
+.select_group_spin {
+    height: 100%;
+    >.ant-spin-container {
+        height: 100%;
+    }
 }

+ 34 - 14
src/pages/launchSystemV3/material/cloudNew/selectCloudNew.tsx

@@ -16,6 +16,7 @@ import { DeleteOutlined, PlayCircleOutlined, SortAscendingOutlined } from "@ant-
 import PlayVideo from "./playVideo"
 import { showFieldList } from "./const"
 import SelectFolder from "./selectFolder"
+import SelectGroupCloudNew from "./selectGroupCloudNew"
 
 
 const { Text, Paragraph } = Typography;
@@ -25,7 +26,7 @@ const { Text, Paragraph } = Typography;
  * @param param0 
  * @returns 
  */
-const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defaultParams, num, sliderImgContent, isGroup, onChange, onClose }) => {
+const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defaultParams, num, sliderImgContent, isGroup, onChange, onClose, accountCreateLogs }) => {
 
     /************************************/
     const { initialState } = useModel('@@initialState');
@@ -35,6 +36,7 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
     const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
     const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
     const [rowNum, setRowNum] = useState<number>(0)
+    const [SCKType, setSCKType] = useState<'1' | '2'>('1')
 
     const [queryParams, setQueryParams] = useState<CLOUDNEW.GetMaterialDataListProps>({ pageNum: 1, pageSize: 20 })
     const [searchParams, setSearchParams] = useState<Partial<CLOUDNEW.GetMaterialDataListProps>>({})
@@ -49,7 +51,7 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
         if (sliderImgContent && sliderImgContent?.length > 0) {
             setCheckedFolderList(sliderImgContent as any || [])
         }
-    }, [sliderImgContent])
+    }, [])
 
     useEffect(() => {
         let params = { ...searchParams, ...defaultParams, ...queryParams }
@@ -99,7 +101,7 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
     const onCheckboxChange = (checked: boolean, item: any) => {
         let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
         if (checked) { // 选中
-            newCheckedFolderList.push(item)
+            newCheckedFolderList.push({ ...item, materialType: 0 })
         } else { // 取消
             newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== item.id)
         }
@@ -114,12 +116,19 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
         setUploadsVisible(true)
     });
 
-
     return <Modal
-        title={<Space>
-            <strong>素材库</strong>
-            <a onClick={() => setUploadVisible(true)}>上传素材</a>
-        </Space>}
+        title={<div className={'SCK_header'}>
+            {[{ label: '素材库', value: '1' }, { label: '账户组媒体素材', value: '2' }].map(item => <div
+                className={item.value === SCKType ? 'selected' : ''}
+                key={item.value}
+                onClick={() => {
+                    setSCKType(item.value as any)
+                    // setCheckedFolderList([])
+                }}>
+                {item.label}
+            </div>)}
+            {SCKType === '1' && <div><a style={{ marginLeft: 20, fontWeight: 'normal' }} onClick={() => setUploadVisible(true)}>上传素材</a></div>}
+        </div>}
         open={visible}
         onCancel={onClose}
         width={1400}
@@ -133,7 +142,7 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
                                 {item.material_type === 'video' && <PlayVideo videoUrl={item.oss_url}>{(onPlay) => <a onClick={onPlay}><PlayCircleOutlined /></a>}</PlayVideo>}
                                 <a style={{ color: 'red' }} onClick={() => setCheckedFolderList(data => data.filter(i => i.id !== item.id))}><DeleteOutlined /></a>
                             </div>
-                            <img src={item.material_type === 'image' ? item.oss_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
+                            <img src={item.material_type === 'image' ? item.oss_url : item.materialType === 1 ? item.key_frame_image_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
                         </div>)}
                     </div>
                 </div>
@@ -144,10 +153,10 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
                 <Button type="primary" onClick={handleOk} disabled={checkedFolderList.length === 0}>确定</Button>
             </Space>
         </div>}
-        className="modalResetCss selectModal"
+        className={`modalResetCss selectModal SCK`}
         bodyStyle={{ backgroundColor: '#f1f4fc', height: 700, overflow: 'hidden', padding: '10px' }}
     >
-        <div className={style.select_cloudNew_layout}>
+        {SCKType === '1' ? <div className={style.select_cloudNew_layout}>
             {/* 搜索 */}
             <SelectSearch
                 onSearch={(value) => { setSearchParams(value) }}
@@ -176,9 +185,9 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
                                             const remainDataLength = remainData.length
                                             const remainNum = num - newCheckedFolderList.length
                                             if (remainNum > remainDataLength) {
-                                                newCheckedFolderList.push(...data)
+                                                newCheckedFolderList.push(...remainData.map(item => ({ ...item, materialType: 0 })))
                                             } else {
-                                                newCheckedFolderList.push(...remainData.splice(0, remainNum))
+                                                newCheckedFolderList.push(...remainData.splice(0, remainNum).map(item => ({ ...item, materialType: 0 })))
                                             }
                                             newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
                                         } else { // 取消全选
@@ -344,7 +353,18 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
                     </div>
                 </div>
             </Card>
-        </div>
+        </div> : <SelectGroupCloudNew
+            num={num}
+            defaultParams={defaultParams}
+            checkedFolderList={checkedFolderList}
+            setCheckedFolderList={setCheckedFolderList}
+            showField={showField}
+            setShowField={setShowField}
+            sortData={sortData}
+            setSortData={setSortData}
+            accountCreateLogs={accountCreateLogs}
+        />}
+
 
         {/* 上传素材 */}
         {uploadVisible && <UploadFile

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

@@ -0,0 +1,359 @@
+import React, { useEffect, useRef, useState } from "react"
+import style from './index.less'
+import { Button, Card, Checkbox, Divider, Empty, Form, message, Pagination, Popover, Radio, Result, Select, Space, Spin, Typography, Image } from "antd"
+import { useAjax } from "@/Hook/useAjax"
+import { getPageRemoteImageDataListApi } from "@/services/adqV3/cloudNew"
+import { useModel } from "umi"
+import { formatBytes, formatSecondsToTime, groupBy } from "@/utils/utils"
+import SelectGroupSearch from "./selectGroupSearch"
+import './global.less'
+import '../../tencentAdPutIn/index.less'
+import { showFieldList } from "./const"
+import { EyeOutlined, SortAscendingOutlined } from "@ant-design/icons"
+import { useSize } from "ahooks"
+import PlayVideo from "./playVideo"
+import Lazyimg from "react-lazyimg-component"
+
+const { Text, Paragraph } = Typography;
+
+/**
+ * 选择账户组媒体素材
+ * @param param0 
+ * @returns 
+ */
+const SelectGroupCloudNew: React.FC<CLOUDNEW.SelectGroupCloudNewProps> = ({ num, defaultParams, checkedFolderList, setCheckedFolderList, sortData, setSortData, showField, setShowField, accountCreateLogs }) => {
+
+    /*****************************************/
+    const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
+    const ref = useRef<HTMLDivElement>(null);
+    const size = useSize(ref);
+    const [rowNum, setRowNum] = useState<number>(0)
+
+    const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
+    const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
+
+    const [queryParams, setQueryParams] = useState<Omit<CLOUDNEW.GetPageRemoteImageDataListProps, 'materialType'>>({ pageNum: 1, pageSize: 20 })
+    const [searchParams, setSearchParams] = useState<Partial<Omit<CLOUDNEW.GetPageRemoteImageDataListProps, 'materialType'>>>({})
+    const [ownerAccountId, setOwnerAccountId] = useState<number>()
+    const [previewData, setPreviewData] = useState<{ visible: boolean, url?: string }>({ visible: false })
+
+    const getPageRemoteImageDataList = useAjax((params) => getPageRemoteImageDataListApi(params))
+    /*****************************************/
+
+    // 根据内容宽度计算列数
+    useEffect(() => {
+        if (size?.width) {
+            let rowNum = Math.floor((size?.width - 26) / 220)
+            setRowNum(rowNum || 1)
+        }
+    }, [size?.width])
+
+    // 处理全选按钮状态
+    useEffect(() => {
+        let data: any[] = getPageRemoteImageDataList?.data?.records || []
+        let dataIds = data.map(item => item.image_id || item.video_id)
+        let selectIds = checkedFolderList.map(item => item.id)
+        if (selectIds.length === 0 || dataIds.length === 0) {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(false);
+        } else if (dataIds.every((element) => selectIds.includes(element))) {
+            setCheckFolderAll(true)
+            setIndeterminateFolder(false);
+        } else if (dataIds.some((element) => selectIds.includes(element))) {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(true);
+        } else {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(false);
+        }
+    }, [checkedFolderList, getPageRemoteImageDataList?.data?.records])
+
+    useEffect(() => {
+        if (accountCreateLogs?.length) {
+            getAllUserAccount.run().then(res => {
+                if (res?.data?.length) {
+                    const list = accountCreateLogs.map(item => item.accountId)
+                    const selectAccountList = res.data.filter((item: { accountId: any }) => list.includes(item.accountId))
+                    const groupAccount = groupBy(selectAccountList, (item) => item.groupId, true)
+                    const GrounpArray = Object.keys(groupAccount).map(function (group) {
+                        return groupAccount[group];
+                    })
+                    if (GrounpArray.length === 1) {
+                        setOwnerAccountId(GrounpArray?.[0]?.[0]?.authMainAccountId || GrounpArray?.[0]?.[0]?.accountId)
+                    } else if (GrounpArray.length === 2 && groupAccount?.['undefined']?.length === 1) {
+                        const undefinedAccount = groupAccount?.['undefined'][0].accountId
+                        let authMainAccountId: any
+                        Object.keys(groupAccount).forEach(key => {
+                            if (key !== 'undefined') {
+                                authMainAccountId = groupAccount[key][0].authMainAccountId
+                            }
+                        })
+                        if (undefinedAccount === authMainAccountId) {
+                            setOwnerAccountId(authMainAccountId)
+                        } else {
+                            setOwnerAccountId(-1)
+                        }
+                    } else {
+                        setOwnerAccountId(-1)
+                    }
+                }
+            })
+        }
+    }, [accountCreateLogs])
+
+    useEffect(() => {
+        if (ownerAccountId) {
+            let params = { ...searchParams, ...defaultParams, ...queryParams, ownerAccountId }
+            if (sortData?.sortField) {
+                params = { ...params, ...sortData }
+            }
+            getPageRemoteImageDataList.run(params)
+        }
+    }, [queryParams, defaultParams, searchParams, sortData, showField, ownerAccountId])
+
+    // 选择
+    const onCheckboxChange = (checked: boolean, item: any) => {
+        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+        if (checked) { // 选中
+            newCheckedFolderList.push({ ...item, id: item.image_id || item.video_id, material_type: defaultParams.materialType, oss_url: item.preview_url, materialType: 1 })
+        } else { // 取消
+            let id = item.image_id || item.video_id
+            newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== id)
+        }
+        setCheckedFolderList(newCheckedFolderList)
+    };
+
+    return <Spin spinning={getAllUserAccount.loading} wrapperClassName={'select_group_spin'}>
+        <div className={style.select_cloudNew_layout}>
+            {ownerAccountId ? <>
+                {/* 搜索 */}
+                <SelectGroupSearch
+                    onSearch={(value) => { setSearchParams(value) }}
+                />
+                <Card
+                    style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
+                    bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
+                    className="cardResetCss buttonResetCss"
+                    bordered
+                >
+                    <div className={style.cloudNew}>
+                        <div className={style.material} style={{ height: '100%' }}>
+                            <div className={style.operates}>
+                                <div className={style.left_bts}>
+                                    <Checkbox
+                                        onChange={(e) => {
+                                            let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+                                            const data: any[] = getPageRemoteImageDataList?.data?.records
+
+                                            if (e.target.checked) { // 全选
+                                                const selectIds = newCheckedFolderList.map(item => item.id)
+                                                const remainData = data.filter(item => {
+                                                    const id = item.image_id || item.video_id
+                                                    return !selectIds.includes(id)
+                                                })
+                                                const remainDataLength = remainData.length
+                                                const remainNum = num - newCheckedFolderList.length
+                                                if (remainNum > remainDataLength) {
+                                                    newCheckedFolderList.push(...remainData.map(i => ({ ...i,  material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
+                                                } else {
+                                                    newCheckedFolderList.push(...remainData.splice(0, remainNum).map(i => ({ ...i,  material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
+                                                }
+                                                newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
+                                            } else { // 取消全选
+                                                const dataIds = data.map(item => item.image_id || item.video_id)
+                                                newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
+                                            }
+                                            setCheckedFolderList(newCheckedFolderList)
+                                        }}
+                                        disabled={checkedFolderList?.length >= num}
+                                        indeterminate={indeterminateFolder}
+                                        checked={checkFolderAll}
+                                    >全选</Checkbox>
+                                    <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
+                                    {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
+                                    {sortData?.sortField && <Text>「排序-{showFieldList.find(item => item.value === sortData.sortField)?.label}-{sortData.sortType ? '正序' : '倒序'}」</Text>}
+                                </div>
+                                <div className={style.left_bts}>
+                                    <Popover
+                                        content={<div style={{ width: 320 }}>
+                                            <Form
+                                                labelCol={{ span: 5 }}
+                                                labelAlign="left"
+                                                colon={false}
+                                            >
+                                                <Form.Item label={<strong>展示指标</strong>}>
+                                                    <Select
+                                                        placeholder="选择展示指标"
+                                                        showSearch
+                                                        filterOption={(input, option) =>
+                                                            (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                        }
+                                                        mode="multiple"
+                                                        options={showFieldList}
+                                                        value={showField}
+                                                        onChange={(value) => {
+                                                            if (value.length > 6) {
+                                                                message.warn('最多只能选择6个指标')
+                                                                return
+                                                            }
+                                                            if (value.length < 1) {
+                                                                message.warn('最少选择1个指标')
+                                                                return
+                                                            }
+                                                            setSortData({ ...sortData, sortField: undefined })
+                                                            setShowField(value as any)
+                                                        }}
+                                                    />
+                                                </Form.Item>
+                                                <Form.Item label={<strong>排序</strong>}>
+                                                    <Space>
+                                                        <Select
+                                                            style={{ width: 125 }}
+                                                            placeholder="选择排序指标"
+                                                            showSearch
+                                                            filterOption={(input, option) =>
+                                                                (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                            }
+                                                            options={showFieldList.filter(item => showField.includes(item.value) && item.value !== 'description')}
+                                                            value={sortData.sortField}
+                                                            allowClear
+                                                            onChange={(value) => {
+                                                                setSortData({ ...sortData, sortField: value as any })
+                                                            }}
+                                                        />
+                                                        <Radio.Group value={sortData.sortType} onChange={(e) => setSortData({ ...sortData, sortType: e.target.value })} buttonStyle="solid">
+                                                            <Radio.Button value={true}>正序</Radio.Button>
+                                                            <Radio.Button value={false}>倒序</Radio.Button>
+                                                        </Radio.Group>
+                                                    </Space>
+                                                </Form.Item>
+                                            </Form>
+                                        </div>}
+                                        trigger={['click']}
+                                        title={<strong>自定义展示指标与排序</strong>}
+                                        placement="bottomRight"
+                                    >
+                                        <Button icon={<SortAscendingOutlined />}>自定义展示指标与排序</Button>
+                                    </Popover>
+                                </div>
+                            </div>
+                            <div className={`${style.content} content_global`}>
+                                <Spin spinning={getPageRemoteImageDataList.loading}>
+                                    <div className={style.content_scroll} ref={ref}>
+                                        {getPageRemoteImageDataList?.data?.records?.length > 0 ? <Checkbox.Group value={checkedFolderList?.map(item => item.id)} style={{ width: '100%' }}>
+                                            <div className={style.content_scroll_div}>
+                                                {getPageRemoteImageDataList?.data?.records.map((item: any) => {
+                                                    let id = defaultParams.materialType === 'image' ? item.image_id : item.video_id
+                                                    const isSelect = checkedFolderList?.map(item => item.id).includes(id)
+                                                    const disabled = checkedFolderList.length >= num && !isSelect
+                                                    return <div key={id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 220 }}>
+                                                        <Card
+                                                            hoverable
+                                                            bodyStyle={{ padding: 0 }}
+                                                            style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
+                                                            className={`${style.content_col}`}
+                                                            cover={<div style={{ height: 120, padding: 0 }} className={style.content_cover}>
+                                                                <div className={style.checkbox}><Checkbox disabled={disabled} value={id} onChange={(e) => onCheckboxChange(e.target.checked, item)} /></div>
+                                                                {defaultParams.materialType === 'video' ? <div className={style.playr}>
+                                                                    <PlayVideo videoUrl={item.preview_url}>{(onPlay) => <img onClick={(e) => {
+                                                                        e.stopPropagation(); e.preventDefault()
+                                                                        onPlay()
+                                                                    }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
+                                                                </div> : <div className={style.imgPreview} onClick={(e) => {
+                                                                    e.stopPropagation()
+                                                                    setPreviewData({ visible: true, url: item.preview_url })
+                                                                }}>
+                                                                    <Space><EyeOutlined /><span>预览</span></Space>
+                                                                </div>}
+                                                                <div className={style.file_info}>
+                                                                    <div>{item.width}*{item.height}</div>
+                                                                    {defaultParams.materialType === 'video' && item.image_duration_millisecond && <div>{formatSecondsToTime(Math.floor(item.image_duration_millisecond / 1000))}</div>}
+                                                                </div>
+                                                                <Lazyimg
+                                                                    animateType="transition"
+                                                                    src={defaultParams.materialType === 'image' ? item.preview_url : item?.key_frame_image_url}
+                                                                    className={`${style.coverImg} lazy`}
+                                                                    animateClassName={['transition-enter', 'transition-enter-active']}
+                                                                />
+                                                            </div>}
+                                                            onClick={() => !disabled && onCheckboxChange(!isSelect, item)}
+                                                        >
+                                                            <div className={style.body}>
+                                                                <Text ellipsis strong style={{ flex: '1 0' }}>{item?.material_name}</Text>
+                                                                <Text style={{ fontSize: 12 }}>{formatBytes(item?.file_size)}</Text>
+                                                            </div>
+                                                            <Divider style={{ margin: '0 0 4px 0' }} />
+                                                            <div style={{ padding: '0 10px 6px' }}>
+                                                                {showField.map(field => {
+                                                                    switch (field) {
+                                                                        case 'material.create_time':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创建时间:{item?.create_time}</Paragraph>
+                                                                        case 'material_data_day.cost':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>消耗:{(item?.cost === null || item?.cost === undefined) ? '--' : item?.cost}</Paragraph>
+                                                                        case 'material_data_day.order_pv':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单次数:{(item?.order_pv === null || item?.order_pv === undefined) ? '--' : item?.order_pv}</Paragraph>
+                                                                        case 'material_data_day.order_cost':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单成本:{(item?.order_cost === null || item?.order_cost === undefined) ? '--' : item?.order_cost}</Paragraph>
+                                                                        case 'material_data_day.ctr':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>点击率:{(item?.ctr === null || item?.ctr === undefined) ? '--' : (item?.ctr * 100).toFixed(2) + '%'}</Paragraph>
+                                                                        case 'material_data_day.conversions_rate':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>目标转化率:{(item?.conversions_rate === null || item?.conversions_rate === undefined) ? '--' : (item?.conversions_rate * 100).toFixed(2) + '%'}</Paragraph>
+                                                                        case 'material_data_day.adgroup_count':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>广告关联数:{(item?.adgroup_count === null || item?.adgroup_count === undefined) ? '--' : item?.adgroup_count}</Paragraph>
+                                                                        case 'material_data_day.dynamic_creative_count':
+                                                                            return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创意关联数:{(item?.dynamic_creative_count === null || item?.dynamic_creative_count === undefined) ? '--' : item?.dynamic_creative_count}</Paragraph>
+                                                                        default:
+                                                                            return null
+                                                                    }
+                                                                })}
+                                                                {showField.includes('description') && <Paragraph style={{ fontSize: 12, marginBottom: 1 }} ellipsis={{ tooltip: true }}>备注:{item.description || '--'}</Paragraph>}
+                                                            </div>
+                                                        </Card>
+                                                    </div>
+                                                })}
+                                            </div>
+                                        </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
+                                    </div>
+                                </Spin>
+                            </div>
+                            <div className={style.fotter}>
+                                <Pagination
+                                    size="small"
+                                    total={getPageRemoteImageDataList?.data?.total || 0}
+                                    showSizeChanger
+                                    showQuickJumper
+                                    pageSize={getPageRemoteImageDataList?.data?.size || 20}
+                                    current={getPageRemoteImageDataList?.data?.current || 1}
+                                    onChange={(page: number, pageSize: number) => {
+                                        setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                                    }}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </Card>
+            </> : ownerAccountId === -1 ? <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
+                <Result
+                    title="错误"
+                    status="500"
+                    subTitle="选择的账户不在一个账户分组中."
+                />
+            </div> : undefined}
+        </div>
+
+        {/* 预览 */}
+        {previewData.visible && <Image
+            width={200}
+            style={{ display: 'none' }}
+            preview={{
+                visible: previewData.visible,
+                src: previewData.url,
+                onVisibleChange: value => {
+                    setPreviewData({ visible: false })
+                },
+            }}
+        />}
+    </Spin>
+}
+
+export default React.memo(SelectGroupCloudNew)

+ 118 - 0
src/pages/launchSystemV3/material/cloudNew/selectGroupSearch.tsx

@@ -0,0 +1,118 @@
+import { Button, Card, Col, DatePicker, Form, Input, InputNumber, Row, Select, Space } from "antd"
+import React, { useEffect } from "react"
+import { getUserAllApi } from "@/services/operating/account";
+import { useAjax } from "@/Hook/useAjax";
+import { SearchOutlined } from "@ant-design/icons";
+import moment from "moment";
+
+interface Props {
+    onSearch?: (value: Partial<CLOUDNEW.GetMaterialListProps>) => void
+}
+
+// 选择素材搜索
+const SelectGroupSearch: React.FC<Props> = ({ onSearch }) => {
+
+    /**********************************/
+    const [form] = Form.useForm();
+
+    const getUserAll = useAjax(() => getUserAllApi())
+    /**********************************/
+
+    useEffect(() => {
+        getUserAll.run()
+    }, [])
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        let params: any = []
+        Object.keys(values).forEach(key => {
+            let value = values[key]
+            if (['accountIds', 'adgroupIds', 'dynamicCreativeIds', 'tencentMaterialId'].includes(key) && value) {
+                let value1 = value.replace(/[,,\s]/g, ',')
+                params[key] = value1.split(',').filter((a: any) => a)
+            } else if ('uploadTime' === key && value?.length === 2) {
+                params.uploadTimeMin = moment(value?.[0]).format('YYYY-MM-DD')
+                params.uploadTimeMax = moment(value?.[1]).format('YYYY-MM-DD')
+            } else if ('dataTime' === key && value?.length === 2) {
+                params.dataTimeMin = moment(value?.[0]).format('YYYY-MM-DD')
+                params.dataTimeMax = moment(value?.[1]).format('YYYY-MM-DD')
+            } else {
+                params[key] = value
+            }
+        })
+        onSearch?.(params)
+    }
+
+    return <Card
+        bodyStyle={{ padding: '5px 10px', overflow: 'auto hidden', display: 'flex', gap: 6, flexWrap: 'wrap' }}
+        className="cardResetCss buttonResetCss"
+        bordered
+    >
+        <Form
+            layout="inline"
+            name="basicSelectSearch"
+            colon={false}
+            form={form}
+            onFinish={handleOk}
+        >
+            <Row gutter={[0, 6]}>
+                <Col><Form.Item name={'description'}>
+                    <Input style={{ width: 190 }} allowClear placeholder="请输入备注关键词" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'sysUserIds'}>
+                    <Select
+                        placeholder="投手"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ width: 190 }}
+                        maxTagCount={1}
+                        mode="multiple"
+                        allowClear
+                        options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'minCost'}>
+                    <InputNumber style={{ width: 80 }} placeholder="最小消耗" />
+                </Form.Item></Col>
+                {/* <Col><Form.Item name={'accountIds'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="广告账号(多个逗号、换行等隔开)" />
+                </Form.Item></Col> */}
+                <Col><Form.Item name={'adgroupIds'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="广告ID(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'dynamicCreativeIds'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="创意ID(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'tencentMaterialId'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="腾讯素材ID(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'useStatus'}>
+                    <Select
+                        placeholder="使用情况"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ minWidth: 120 }}
+                        allowClear
+                        options={[{ label: '未使用', value: 0 }, { label: '无消耗', value: 1 }, { label: '有消耗', value: 2 }]}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'uploadTime'}>
+                    <DatePicker.RangePicker style={{ width: 260 }} placeholder={['上传时间开始', '上传时间结束']} />
+                </Form.Item></Col>
+                <Col><Form.Item name={'dataTime'}>
+                    <DatePicker.RangePicker style={{ width: 260 }} placeholder={['数据时间开始', '数据时间结束']} />
+                </Form.Item></Col>
+                <Col><Form.Item>
+                    <Space>
+                        <Button onClick={() => form.resetFields()}>重置</Button>
+                        <Button type="primary" htmlType="submit" icon={<SearchOutlined />} className="modalResetCss">搜索</Button>
+                    </Space>
+                </Form.Item></Col>
+            </Row>
+        </Form>
+    </Card>
+}
+
+export default React.memo(SelectGroupSearch)

+ 51 - 11
src/pages/launchSystemV3/material/typings.d.ts

@@ -80,6 +80,26 @@ declare namespace CLOUDNEW {
         useStatus?: number            // 使用情况(0未使用 1无消耗 2有消耗)
         fileSize?: number
     }
+    interface GetPageRemoteImageDataListProps {
+        materialType: 'image' | 'video'   // 素材类型
+        pageNum: number,
+        pageSize: number,
+        columns?: string[]              // 列
+        accountIds?: number[]          // 广告账号
+        adgroupIds?: number[]          // 广告id
+        dynamicCreativeIds?: number[]  // 创意id
+        sysUserIds?: number[]    // 投手id
+        mediaType?: string
+        minCost?: number,       // 最小消耗
+        sizeQueries?: { width?: number, height?: number, relation: string }[]
+        sortField?: string,
+        sortType?: boolean,
+        tencentMaterialId?: number[]  // 腾讯素材id
+        uploadTimeMin?: string        // 图片上传时间
+        uploadTimeMax?: string
+        useStatus?: number            // 使用情况(0未使用 1无消耗 2有消耗)
+        fileSize?: number
+    }
     interface AddMaterialProps {
         designerId?: number,    // 设计师
         fileMime?: string,
@@ -114,25 +134,45 @@ declare namespace CLOUDNEW {
         sortField?: string,
         sortType?: boolean
     }
-
+    type DefaultParams = {
+        sizeQueries?: {
+            width?: number,
+            height?: number,
+            relation: string
+        }[],
+        materialType: 'image' | 'video'
+        fileSize: number
+    }
     interface SelectCloudNewProps {
-        defaultParams: {
-            sizeQueries?: {
-                width?: number,
-                height?: number,
-                relation: string
-            }[],
-            materialType: 'image' | 'video'
-            fileSize: number
-        }
+        defaultParams: DefaultParams
         num: number
         sliderImgContent?: { oss_url: string, id: number, width?: number, height?: number }[]
         isGroup?: boolean
         visible?: boolean
         onClose?: () => void
         onChange?: (data: any[]) => void
+        accountCreateLogs?: PULLIN.AccountCreateLogsProps[]
+    }
+    interface SelectGroupCloudNewProps {
+        num: number
+        defaultParams: DefaultParams
+        checkedFolderList: any[]
+        setCheckedFolderList: React.Dispatch<React.SetStateAction<any[]>>
+        showField: string[]
+        setShowField: (value?: string[] | IFuncUpdater<string[]> | undefined) => void
+        sortData: {
+            sortField: string | undefined;
+            sortType: boolean;
+        }
+        setSortData: (value?: {
+            sortField: string | undefined;
+            sortType: boolean;
+        } | IFuncUpdater<{
+            sortField: string | undefined;
+            sortType: boolean;
+        }> | undefined) => void
+        accountCreateLogs?: PULLIN.AccountCreateLogsProps[]
     }
-
     interface GetRemoteMaterialListProps {
         pageNum: number,
         pageSize: number,

+ 86 - 0
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/addSubAccount.tsx

@@ -0,0 +1,86 @@
+import { Form, message, Modal, 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"
+
+interface Props {
+    // 主账户
+    authMainAccountId: string
+    // 主账户ID
+    accountAssetsGroupId: number
+    // 已选账户
+    selectedAccountIdList: string[]
+    // 主体名称
+    corporationName: string,
+    // 主体编号
+    corporationLicence: string
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+const AddSubAccount: React.FC<Props> = ({ authMainAccountId, accountAssetsGroupId, selectedAccountIdList = [], corporationName, corporationLicence, visible, onClose, onChange }) => {
+
+    /************************************/
+    const [form] = Form.useForm()
+    const [errorDataList, setErrorDataList] = useState<any[]>([])
+
+    const getAccountAllList = useAjax(() => getAccountAllListApi())
+    const authAccountAssetsGroupAccount = useAjax((params) => authAccountAssetsGroupAccountApi(params))
+    /************************************/
+
+    useEffect(() => {
+        getAccountAllList.run()
+    }, [])
+
+    const handleOk = () => {
+        form.validateFields().then(valid => {
+            console.log(valid)
+            authAccountAssetsGroupAccount.run({ ...valid, accountAssetsGroupId: accountAssetsGroupId }).then(res => {
+                console.log(res)
+                if (res) {
+                    message.success('授权成功')
+                    onChange?.()
+                }
+            })
+        })
+    }
+
+    return <Modal
+        title={<strong>{authMainAccountId} 授权子账户(<span style={{ color: 'red', fontSize: 12 }}>主体:{corporationName}</span>)</strong>}
+        open={visible}
+        onCancel={onClose}
+        className="modalResetCss"
+        onOk={handleOk}
+        confirmLoading={authAccountAssetsGroupAccount.loading}
+    >
+        <Form
+            name="basicAddSub"
+            form={form}
+            layout='vertical'
+            autoComplete="off"
+        >
+            <Form.Item label={<strong>请选择账户</strong>} name="accountId" rules={[{ required: true, message: '请选择账户!' }]} valuePropName="targetKeys">
+                <Transfer
+                    showSearch
+                    filterOption={(inputValue: string, option: any) => {
+                        let newInput: string[] = inputValue ? inputValue?.split(/[,,\n\s]+/ig).filter((item: any) => item) : []
+                        return newInput?.some(val => option!.title?.toString().toLowerCase()?.includes(val))
+                    }}
+                    dataSource={getAccountAllList?.data?.filter((item: { accountId: string, corporationLicence: string }) => item.accountId != authMainAccountId && item.corporationLicence === corporationLicence)?.map((item: { accountId: any }) => ({ title: item.accountId, key: item.accountId, disabled: selectedAccountIdList?.includes(item.accountId) }))}
+                    titles={['所有', '已选']}
+                    oneWay
+                    listStyle={{
+                        height: 450,
+                        width: 222
+                    }}
+                    render={item => <span>{item.title}</span>}
+                    pagination
+                />
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(AddSubAccount)

+ 51 - 15
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/index.tsx

@@ -1,9 +1,12 @@
 import { useAjax } from "@/Hook/useAjax"
-import { getAccountAssetsGroupListApi } from "@/services/adqV3/global"
+import { delAuthAccountAssetsGroupApi, getAccountAssetsGroupListApi } from "@/services/adqV3/global"
 import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
-import { Button, Card, Input } from "antd"
+import { Button, Card, Input, message, Table } from "antd"
 import React, { useEffect, useState } from "react"
 import '../../tencentAdPutIn/index.less'
+import ModifyAccountGroup from "./modifyAccountGroup"
+import columns from "./tableConfig"
+import SubAccount from "./subAccount"
 
 
 /**
@@ -16,14 +19,36 @@ 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 [visible, setVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>(undefined)
+    const [subAccountData, setSubAccountData] = useState<{ visible: boolean, data: any }>({ visible: false, data: undefined })
 
     const getAccountAssetsGroupList = useAjax((params) => getAccountAssetsGroupListApi(params))
+    const delAuthAccountAssetsGroup = useAjax((params) => delAuthAccountAssetsGroupApi(params))
     /****************************************/
 
     useEffect(() => {
         getAccountAssetsGroupList.run(queryFormNew)
     }, [queryFormNew])
 
+
+    const del = (id: number) => {
+        delAuthAccountAssetsGroup.run({ id }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getAccountAssetsGroupList.refresh()
+            }
+        })
+    }
+
+    const update = (data: any) => {
+        setInitialValues(data)
+        setVisible(true)
+    }
+
+    const handleSubAccount = (data: any) => {
+        setSubAccountData({ visible: true, data })
+    }
+
     return <Card
         className="cardResetCss"
         title={<div className="flexStart" style={{ gap: 8 }}>
@@ -44,42 +69,53 @@ const AccountAssetSharing: React.FC = () => {
                 }}
             />
             <Button type="primary" icon={<SearchOutlined />} onClick={() => setQueryFormNew({ ...queryForm })}>搜索</Button>
-            <Button type="primary" icon={<PlusOutlined />} onClick={() => { setVisible(true) }}>新增账户组</Button>
+            <Button type="primary" icon={<PlusOutlined />} onClick={() => { setVisible(true) }}>新增账户资产共享组</Button>
         </div>}
     >
 
-        {/* <Table
-            columns={columns(del)}
-            dataSource={getGameLibraryList.data?.records}
+        <Table
+            columns={columns(del, update, handleSubAccount)}
+            dataSource={getAccountAssetsGroupList.data?.records}
             size="small"
-            loading={getGameLibraryList?.loading}
-            scroll={{ y: 600 }}
+            loading={getAccountAssetsGroupList?.loading}
+            scroll={{ y: 600, x: 1100 }}
             bordered
             rowKey={'id'}
             pagination={{
                 defaultPageSize: 20,
-                current: getGameLibraryList.data?.current || 1,
-                pageSize: getGameLibraryList.data?.size || 10,
-                total: getGameLibraryList.data?.total || 0
+                current: getAccountAssetsGroupList.data?.current || 1,
+                pageSize: getAccountAssetsGroupList.data?.size || 10,
+                total: getAccountAssetsGroupList.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 && <Modify
+        {visible && <ModifyAccountGroup
+            initialValues={initialValues}
             visible={visible}
             onClose={() => {
                 setVisible(false)
+                setInitialValues(undefined)
             }}
             onChange={() => {
                 setVisible(false)
-                getGameLibraryList.refresh()
+                setInitialValues(undefined)
+                getAccountAssetsGroupList.refresh()
+            }}
+        />}
+
+        {/* 授权子账户 */}
+        {subAccountData?.visible && <SubAccount
+            {...subAccountData}
+            onClose={() => {
+                setSubAccountData({ visible: false, data: undefined })
             }}
-        />} */}
+        />}
     </Card>
 }
 

+ 65 - 16
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/modifyAccountGroup.tsx

@@ -1,8 +1,12 @@
-import { Form, Input, Modal } from "antd"
-import React from "react"
+import { Form, Input, message, Modal, Radio, Select } from "antd"
+import React, { useEffect } from "react"
 import '../../tencentAdPutIn/index.less'
+import { useAjax } from "@/Hook/useAjax"
+import { getAccountAllListApi } from "@/services/launchAdq/adAuthorize"
+import { createAccountAssetsGroupApi, updateAccountAssetsGroupApi } from "@/services/adqV3/global"
 
 interface Props {
+    initialValues?: any
     visible?: boolean
     onClose?: () => void
     onChange?: () => void
@@ -12,41 +16,86 @@ interface Props {
  * 新增修改账户组
  * @returns 
  */
-const ModifyAccountGroup: React.FC<Props> = ({ visible, onChange, onClose }) => {
+const ModifyAccountGroup: React.FC<Props> = ({ initialValues, visible, onChange, onClose }) => {
 
     /******************************************/
     const [form] = Form.useForm()
+
+    const getAccountAllList = useAjax(() => getAccountAllListApi())
+    const createAccountAssetsGroup = useAjax((params) => createAccountAssetsGroupApi(params))
+    const updateAccountAssetsGroup = useAjax((params) => updateAccountAssetsGroupApi(params))
     /******************************************/
 
+    useEffect(() => {
+        getAccountAllList.run()
+    }, [])
+
+
+    const handleOk = () => {
+        form.validateFields().then(valid => {
+            console.log(valid)
+            if (initialValues) {
+                updateAccountAssetsGroup.run({ ...valid, accountGroupId: initialValues.id }).then(res => {
+                    if (res) {
+                        message.success('新增成功')
+                        onChange?.()
+                    }
+                })
+            } else {
+                createAccountAssetsGroup.run(valid).then(res => {
+                    if (res) {
+                        message.success('新增成功')
+                        onChange?.()
+                    }
+                })
+            }
+
+        })
+    }
+
     return <Modal
-        title={<strong>账户资产共享组新增</strong>}
+        title={<strong>{initialValues ? '账户资产共享组修改' : '账户资产共享组新增'}</strong>}
         open={visible}
         onCancel={onClose}
         className="modalResetCss"
+        onOk={handleOk}
+        confirmLoading={createAccountAssetsGroup.loading || updateAccountAssetsGroup.loading}
     >
         <Form
-            name="basicMiniProgramWechat"
+            name="basicModifyAccountGroup"
             form={form}
             layout='vertical'
             autoComplete="off"
-            initialValues={{
-
+            initialValues={initialValues || {
+                authType: 'material'
             }}
         >
             <Form.Item label={<strong>账户资产共享组名称</strong>} name="accountGroupName" rules={[{ required: true, message: '请输入账户资产共享组名称!' }]}>
-                <Input placeholder="请输入游戏名称" allowClear />
+                <Input placeholder="请输入账户资产共享组名称" allowClear />
             </Form.Item>
-            <Form.Item label={<strong>授权</strong>} name="gameName" rules={[{ required: true, message: '请输入游戏名称!' }]}>
-                <Input placeholder="请输入游戏名称" allowClear />
+
+            <Form.Item label={<strong>授权类型</strong>} name="authType" rules={[{ required: true, message: '请选择授权内容!' }]}>
+                <Radio.Group buttonStyle="solid" disabled={initialValues}>
+                    <Radio.Button value="material">素材</Radio.Button>
+                </Radio.Group>
             </Form.Item>
+
             <Form.Item
-                label={<strong>appId</strong>}
-                name="appId"
-                rules={[
-                    { required: true, message: '请输入appId!' }
-                ]}
+                label={<strong>授权主账户</strong>}
+                name="authMainAccountId"
+                rules={[{ required: true, message: '请选择授权主账户!' }]}
+                tooltip="授权主户代表该账户组中的资产账户,主户中的相关资产会自动授权到所有子户中,会自动获取主户中的资产,可允许该账户组内的所有账户使用"
+                help={<span style={{ fontSize: 12, color: 'red' }}>账户组生效依赖主账户必须至少存在1个视频1个图片,若主账户不满足条件请先主动上传。</span>}
             >
-                <Input placeholder="请输入appId" allowClear />
+                <Select
+                    disabled={initialValues}
+                    filterOption={(input: any, option: any) => {
+                        return option!.value?.toString().toLowerCase().includes(input.toLowerCase())
+                    }}
+                    showSearch
+                    placeholder="请选择员工"
+                    options={getAccountAllList?.data?.map((item: { accountId: any }) => ({ label: item.accountId, value: item.accountId }))}
+                />
             </Form.Item>
         </Form>
     </Modal>

+ 120 - 0
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/subAccount.tsx

@@ -0,0 +1,120 @@
+import { Button, Input, message, Modal, Space, Table } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
+import { useAjax } from "@/Hook/useAjax"
+import { getAccountAssetsGroupAccountListApi, revokeAuthAccountAssetsGroupApi } from "@/services/adqV3/global"
+import AddSubAccount from "./addSubAccount"
+import columns from "./tableConfigSub"
+
+interface Props {
+    data?: any
+    visible?: boolean
+    onClose?: () => void
+}
+
+/**
+ * 授权子账户
+ * @param param0 
+ * @returns 
+ */
+const SubAccount: React.FC<Props> = ({ data, visible, onClose }) => {
+
+    /********************************************/
+    const [queryForm, setQueryForm] = useState<{ accountids?: string[] }>()
+    const [visibleS, setVisibleS] = useState<boolean>(false)
+    const [dataSource, setDataSource] = useState<any[]>([])
+
+    const getAccountAssetsGroupAccountList = useAjax((params) => getAccountAssetsGroupAccountListApi(params))
+    const revokeAuthAccountAssetsGroup = useAjax((params) => revokeAuthAccountAssetsGroupApi(params))
+    /********************************************/
+
+    useEffect(() => {
+        getList()
+    }, [data])
+
+    const getList = () => {
+        getAccountAssetsGroupAccountList.run({ groupId: data.id }).then(res => {
+            handleSearch(res || [])
+        })
+    }
+
+
+    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 })))
+        } else {
+            setDataSource(data?.map((accountId: any) => ({ accountId })))
+        }
+    }
+
+    const del = (accountId: number[]) => {
+        revokeAuthAccountAssetsGroup.run({ accountAssetsGroupId: data.id, accountId }).then(res => {
+            console.log('----------->', res)
+            if (res) {
+                message.success('取消成功')
+                getList()
+            }
+        })
+    }
+
+    return <Modal
+        title={<span><strong>{data.authMainAccountId} 授权子账户</strong><span style={{ fontSize: 12 }}>(主体:{data.corporationName})</span></span>}
+        open={visible}
+        className="modalResetCss"
+        width={660}
+        onCancel={onClose}
+        footer={null}
+    >
+        <Space style={{ width: '100%' }} direction="vertical">
+            <div className="flexStart" style={{ gap: 8 }}>
+                <Input.TextArea
+                    style={{ width: 300 }}
+                    placeholder="请输入主账户ID列表(多个逗号隔开)"
+                    allowClear
+                    rows={1}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        let arr: any[] = []
+                        if (value) {
+                            value = value.replace(/[,,\s]/g, ',')
+                            arr = value.split(',').filter((a: any) => a)
+                        }
+                        setQueryForm({ ...queryForm, accountids: arr })
+                    }}
+                />
+                <Button type="primary" icon={<SearchOutlined />} onClick={() => handleSearch(getAccountAssetsGroupAccountList?.data || [])}>搜索</Button>
+                <Button type="primary" icon={<PlusOutlined />} onClick={() => { setVisibleS(true) }}>子账户授权</Button>
+            </div>
+
+            <Table
+                columns={columns(del)}
+                dataSource={dataSource}
+                size="small"
+                loading={getAccountAssetsGroupAccountList?.loading}
+                scroll={{ y: 600 }}
+                bordered
+                rowKey={'accountId'}
+            />
+        </Space>
+
+        {visibleS && <AddSubAccount
+            corporationName={data.corporationName}
+            corporationLicence={data.corporationLicence}
+            authMainAccountId={data.authMainAccountId}
+            accountAssetsGroupId={data.id}
+            selectedAccountIdList={getAccountAssetsGroupAccountList?.data || []}
+            visible={visibleS}
+            onClose={() => {
+                setVisibleS(false)
+            }}
+            onChange={() => {
+                setVisibleS(false)
+                getList()
+            }}
+        />}
+    </Modal>
+}
+
+export default React.memo(SubAccount)

+ 128 - 0
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfig.tsx

@@ -0,0 +1,128 @@
+import { Space, Popconfirm, TableProps, Tag } from "antd"
+import React from "react"
+
+
+const columns = (del: (id: number) => void, update: (data: any) => void, handleSubAccount: (data: any) => void): TableProps<any>['columns'] => {
+
+
+    const data: TableProps<any>['columns'] = [
+        {
+            title: 'ID',
+            dataIndex: 'id',
+            key: 'id',
+            width: 50,
+            align: 'center',
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '账户资产共享组名称',
+            dataIndex: 'accountGroupName',
+            key: 'accountGroupName',
+            ellipsis: true,
+            width: 180,
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '授权主账户',
+            dataIndex: 'authMainAccountId',
+            key: 'authMainAccountId',
+            ellipsis: true,
+            width: 85,
+            align: 'center',
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '企业名称',
+            dataIndex: 'corporationName',
+            key: 'corporationName',
+            ellipsis: true,
+            width: 180,
+            render: (a) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '授权类型',
+            dataIndex: 'authType',
+            key: 'authType',
+            width: 90,
+            align: 'center',
+            render: (a) => {
+                return a === 'material' ? <Tag color="#f50">素材</Tag> : '--'
+            }
+        },
+        {
+            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',
+            render: (_, b) => {
+                return <Space>
+                    <a style={{ fontSize: 12 }} onClick={() => { handleSubAccount(b) }}>授权子户</a>
+                    <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

+ 42 - 0
src/pages/launchSystemV3/tencenTasset/accountAssetSharing/tableConfigSub.tsx

@@ -0,0 +1,42 @@
+import { Space, Popconfirm, TableProps } from "antd"
+import React from "react"
+
+
+const columns = (del: (id: 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',
+            render: (_, b) => {
+                return <Space>
+                    <Popconfirm
+                        title="确定取消授权?"
+                        onConfirm={() => del([b.accountId])}
+                        okText="是"
+                        cancelText="否"
+                    >
+                        <a style={{ color: 'red', fontSize: 12 }}>取消授权</a>
+                    </Popconfirm>
+                </Space>
+            }
+        }
+    ]
+
+    return data
+}
+
+export default columns

+ 41 - 18
src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx

@@ -13,13 +13,15 @@ interface Props {
     creativeTemplateId: number
     materialData: any
     deliveryMode: string,
+    isSelectRemote?: boolean
     value?: any,
     visible?: boolean
     onClose?: () => void
     onChange?: (value: any) => void
+    accountCreateLogs?: PULLIN.AccountCreateLogsProps[]
 }
 
-const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, deliveryMode, visible, value, onChange, onClose, adLength }) => {
+const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, deliveryMode, visible, isSelectRemote, value, onChange, onClose, adLength, accountCreateLogs }) => {
 
     /*********************************/
     const [form] = Form.useForm();
@@ -170,6 +172,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
         })
         return `${imageCount}张图片,${videoCount}个视频,${arrayImgCount}个组图`
     }
+    console.log('dynamicGroup---->', dynamicGroup)
 
     return <Modal
         title={<Space>
@@ -435,7 +438,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                                     }, 100)
                                                 }}>
                                                     <div className={styles.p}>
-                                                        {dynamicGroup?.length > 0 && dynamicGroup[num] && Object.keys(dynamicGroup[num])?.includes(item.name) ? <VideoNews src={dynamicGroup[num][item.name]['url']} style={{ display: 'block', width: 'auto', margin: 0, height: '100%' }} maskImgStyle={{ position: 'absolute', top: '50%', left: '50%', width: 40, height: 40, transform: 'translate(-50%, -50%)', zIndex: 10 }} /> : <>
+                                                        {dynamicGroup?.length > 0 && dynamicGroup[num] && Object.keys(dynamicGroup[num])?.includes(item.name) ? <VideoNews keyFrameImageUrl={dynamicGroup[num][item.name]['keyFrameImageUrl']} src={dynamicGroup[num][item.name]['url']} style={{ display: 'block', width: 'auto', margin: 0, height: '100%' }} maskImgStyle={{ position: 'absolute', top: '50%', left: '50%', width: 40, height: 40, transform: 'translate(-50%, -50%)', zIndex: 10 }} /> : <>
                                                             <span>{`推荐尺寸(${creativeTemplateId === 1708 ? 1280 : item.restriction.videoRestriction.minWidth} x ${creativeTemplateId === 1708 ? 720 : item.restriction.videoRestriction.minHeight})`}</span>
                                                             <span>{`${item.restriction.videoRestriction.fileFormat?.map((str: any) => str?.replace('MEDIA_TYPE_', ''))};< ${item.restriction.videoRestriction.fileSize / 1024}M;时长 ≥ ${item.restriction.videoRestriction.minDuration}s,≤ ${item.restriction.videoRestriction.maxDuration}s,必须带有声音`}</span>
                                                         </>}
@@ -572,10 +575,10 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                                         </div>
                                                         <div className={styles.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
                                                     </div>
-                                                } else if (item?.url?.includes('mp4')) {
+                                                } else if (item?.url?.includes('mp4') || item?.keyFrameImageUrl) {
                                                     return <div className={styles.boxList_body_item} key={index}>
                                                         <div className={styles.content}>
-                                                            <VideoNews src={item?.url} style={{ width: 100, height: 100 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
+                                                            <VideoNews src={item?.url} keyFrameImageUrl={item?.keyFrameImageUrl} style={{ width: 100, height: 100 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
                                                         </div>
                                                         <div className={styles.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
                                                     </div>
@@ -612,12 +615,13 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
         {/* 选择素材 */}
         {(selectVideoVisible && selectCloudData) && <SelectCloudNew
             {...selectCloudData}
+            accountCreateLogs={accountCreateLogs}
             visible={selectVideoVisible}
             isGroup={materialConfig?.isGroup}
             sliderImgContent={materialConfig.index === 99999 ? undefined :
-                materialConfig.type === 'element_story' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('element_story')) ? dynamicGroup[materialConfig.index]['element_story']?.map((item: any) => ({ oss_url: item.url, id: item.id })) : undefined :
-                    materialConfig.type === 'image_list' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('image_list')) ? dynamicGroup[materialConfig.index]['image_list']?.map((item: any) => ({ oss_url: item.url, id: item.id })) : undefined :
-                        (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes(materialConfig.type)) ? [{ oss_url: dynamicGroup[materialConfig.index][materialConfig.type]['url'], id: dynamicGroup[materialConfig.index][materialConfig.type]['id'] }] : undefined
+                materialConfig.type === 'element_story' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('element_story')) ? dynamicGroup[materialConfig.index]['element_story']?.map((item: any) => ({ oss_url: item.url, id: item.id, materialType: item?.materialType, material_type: selectCloudData.defaultParams.materialType, })) : undefined :
+                    materialConfig.type === 'image_list' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('image_list')) ? dynamicGroup[materialConfig.index]['image_list']?.map((item: any) => ({ oss_url: item.url, id: item.id, materialType: item?.materialType, material_type: selectCloudData.defaultParams.materialType, })) : undefined :
+                        (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes(materialConfig.type)) ? [{ oss_url: dynamicGroup[materialConfig.index][materialConfig.type]['url'], id: dynamicGroup[materialConfig.index][materialConfig.type]['id'], material_type: selectCloudData.defaultParams.materialType, materialType: dynamicGroup[materialConfig.index][materialConfig.type]?.['materialType'], key_frame_image_url: dynamicGroup[materialConfig.index][materialConfig.type]?.['keyFrameImageUrl'] }] : undefined
             }
             onClose={() => {
                 setSelectVideoVisible(false)
@@ -630,7 +634,12 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                             if (materialConfig.isGroup) {
 
                             } else {
-                                const aContent = content.map((m: any) => ({ id: m?.id, url: m?.oss_url, materialType: 0 }))
+                                const aContent = content.map((m: any) => {
+                                    if (selectCloudData?.defaultParams?.materialType === 'video' && m?.materialType === 1) {
+                                        return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.account_id }
+                                    }
+                                    return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, accountId: m?.account_id }
+                                })
                                 let newDynamicGroup = dynamicGroup?.map((item: any) => {
                                     let oldList = item?.list || []
                                     if (oldList.length < mCount) {
@@ -654,10 +663,20 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                 if (materialConfig.index === index) {
                                     let oldList = item?.list || []
                                     if (materialConfig.isGroup) {
-                                        oldList = oldList.concat([content.map((m: any) => ({ id: m?.id, url: m?.oss_url, materialType: 0 }))])
+                                        oldList = oldList.concat([content.map((m: any) => {
+                                            if (selectCloudData?.defaultParams?.materialType === 'video' && m?.materialType === 1) {
+                                                return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.account_id }
+                                            }
+                                            return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, accountId: m?.account_id }
+                                        })])
                                         return { list: oldList }
                                     } else {
-                                        oldList = oldList.concat(content.map((m: any) => ({ id: m?.id, url: m?.oss_url, materialType: 0 })))
+                                        oldList = oldList.concat(content.map((m: any) => {
+                                            if (selectCloudData?.defaultParams?.materialType === 'video' && m?.materialType === 1) {
+                                                return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.account_id }
+                                            }
+                                            return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, accountId: m?.account_id }
+                                        }))
                                         return { list: oldList }
                                     }
                                 }
@@ -668,7 +687,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                     } else { // 自定义创意
                         if (materialConfig.index === 99999) {
                             if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
-                                let urls = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 }))
+                                let urls = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: item?.materialType || 0, accountId: item?.account_id }))
                                 let max = materialConfig.max
                                 let materialsNew = dynamicGroup.map((item: any) => {
                                     let newItem = item || {}
@@ -716,9 +735,9 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                             } else {
                                 let newMaterials = content?.map((item: any) => {
                                     if (["short_video1", 'video_id'].includes(materialConfig.type) && mLength === 2) {
-                                        return { [materialConfig.type]: { url: item?.oss_url, id: item?.id, materialType: 0 }, cover_id: { url: getVideoImgUrl(item?.oss_url), id: null, materialType: 0 } }
+                                        return { [materialConfig.type]: { url: item?.oss_url, keyFrameImageUrl: item.key_frame_image_url, id: item?.id, materialType: item?.materialType || 0, accountId: item?.account_id }, cover_id: { url: item?.materialType === 1 ? item?.key_frame_image_url : getVideoImgUrl(item?.oss_url), id: null, materialType: item?.materialType || 0, accountId: item?.account_id } }
                                     }
-                                    return { [materialConfig.type]: { url: item?.oss_url, id: item?.id, materialType: 0 } }
+                                    return { [materialConfig.type]: { url: item?.oss_url, id: item?.id, materialType: item?.materialType || 0, accountId: item?.account_id } }
                                 })
                                 if (newMaterials.length > 0) {
                                     if (dynamicGroup?.every((item: any) => !item)) { // 没设置过
@@ -749,20 +768,24 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                 if (materialConfig.index === index) {
                                     if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
                                         if (item) {
-                                            item[materialConfig.type] = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 }))
+                                            item[materialConfig.type] = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: item?.materialType || 0, accountId: item?.account_id }))
                                             return { ...item }
                                         } else {
-                                            return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 })) }
+                                            return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: item?.materialType || 0, accountId: item?.account_id })) }
                                         }
                                     } else {
                                         if (item) {
-                                            item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 }
+                                            let d: any = { id: content[0]?.id, url: content[0]?.oss_url, materialType: content[0]?.materialType || 0, accountId: content[0]?.account_id }
+                                            if (["short_video1", 'video_id'].includes(materialConfig.type) && content[0]?.materialType === 1) {
+                                                d.keyFrameImageUrl = content[0]?.key_frame_image_url
+                                            }
+                                            item[materialConfig.type] = d
                                             return { ...item }
                                         } else {
                                             if (["short_video1", 'video_id'].includes(materialConfig.type) && mLength === 2) {
-                                                return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 }, cover_id: { id: null, url: getVideoImgUrl(content[0]?.oss_url), materialType: 0 } }
+                                                return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, keyFrameImageUrl: content[0]?.key_frame_image_url, materialType: content[0]?.materialType || 0, accountId: content[0]?.account_id }, cover_id: { id: null, url: content[0]?.materialType === 1 ? content[0]?.key_frame_image_url : getVideoImgUrl(content[0]?.oss_url), materialType: content[0]?.materialType || 0, accountId: content[0]?.account_id } }
                                             }
-                                            return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 } }
+                                            return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, materialType: content[0]?.materialType || 0, accountId: content[0]?.account_id } }
                                         }
                                     }
                                 }

+ 4 - 3
src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.tsx

@@ -97,7 +97,7 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                                     return <div key={index} style={{ width: deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' ? [641, 642, 643].includes(creativeTemplateId) ? '100%' : [720, 721, 722, 1529, 618].includes(creativeTemplateId) ? '50%' : '25%' : '100%' }}>
                                         <Title level={5} style={{ fontSize: 12 }}>创意组{index + 1}</Title>
                                         {mType ? ['short_video', 'video'].includes(mType) ? <div className={style.video}>
-                                            <VideoNews src={item?.video_id?.url || item?.short_video1?.url} />
+                                            <VideoNews src={item?.video_id?.url || item?.short_video1?.url} keyFrameImageUrl={item?.video_id?.keyFrameImageUrl || item?.short_video1?.keyFrameImageUrl}/>
                                             {item?.cover_id && <div className={style.cover_image}>
                                                 <img src={item?.cover_id?.url} />
                                             </div>}
@@ -126,10 +126,10 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                                                             {item.map((l, i) => <img src={l?.url} key={i} style={{ width: length === 6 ? 19.9 : 29.9 }} />)}
                                                         </div>
                                                     </div>
-                                                } else if (item?.url?.includes('mp4')) {
+                                                } else if (item?.url?.includes('mp4') || item?.keyFrameImageUrl) {
                                                     return <div className={styles.boxList_body_item} key={index} style={{ width: 60, height: 60 }}>
                                                         <div className={styles.content} style={{ width: 60, height: 60 }}>
-                                                            <VideoNews src={item?.url} style={{ width: 60, height: 60 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
+                                                            <VideoNews src={item?.url} style={{ width: 60, height: 60 }} keyFrameImageUrl={item?.keyFrameImageUrl} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
                                                         </div>
                                                     </div>
                                                 } else {
@@ -195,6 +195,7 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
             onClose={() => {
                 setNewVisible(false)
             }}
+            accountCreateLogs={accountCreateLogs}
             onChange={({ dynamicMaterialDTos, mediaType }) => {
                 let newAddelivery = { ...addelivery, dynamicMaterialDTos, mediaType }
                 if (addelivery.mediaType !== mediaType && mediaType !== 1 && addelivery.dynamicCreativesTextDTOS?.type === 4) {

+ 3 - 3
src/pages/launchSystemV3/tencentAdPutIn/create/Save/PreviewImg.tsx

@@ -30,7 +30,7 @@ const PreviewImg: React.FC<{ strategyValue: { dynamicMaterialDTos: any, mediaTyp
                                         return <div key={index} style={{ width: deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' ? [641, 642, 643].includes(creativeTemplateId) ? '100%' : [720, 721, 722, 1529, 618].includes(creativeTemplateId) ? '50%' : '25%' : '100%' }}>
                                             <Title level={5} style={{ fontSize: 12 }}>创意组{index + 1}</Title>
                                             {(keys.includes('video_id') || keys.includes('short_video1')) ? <div className={style.video}>
-                                                <VideoNews src={item?.video_id?.url || item?.short_video1?.url} />
+                                                <VideoNews src={item?.video_id?.url || item?.short_video1?.url} keyFrameImageUrl={item?.video_id?.keyFrameImageUrl || item?.short_video1?.keyFrameImageUrl} />
                                                 {item?.cover_id && <div className={style.cover_image}>
                                                     <img src={item?.cover_id?.url} />
                                                 </div>}
@@ -55,10 +55,10 @@ const PreviewImg: React.FC<{ strategyValue: { dynamicMaterialDTos: any, mediaTyp
                                                                 {item.map((l, i) => <img src={l?.url} key={i} style={{ width: length === 6 ? 19.9 : 29.9 }} />)}
                                                             </div>
                                                         </div>
-                                                    } else if (item?.url?.includes('mp4')) {
+                                                    } else if (item?.url?.includes('mp4') || item?.keyFrameImageUrl) {
                                                         return <div className={styles.boxList_body_item} key={index} style={{ width: 60, height: 60 }}>
                                                             <div className={styles.content} style={{ width: 60, height: 60 }}>
-                                                                <VideoNews src={item?.url} style={{ width: 60, height: 60 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
+                                                                <VideoNews src={item?.url} style={{ width: 60, height: 60 }} keyFrameImageUrl={item?.keyFrameImageUrl} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
                                                             </div>
                                                         </div>
                                                     } else {

+ 97 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/SelectAccount/index.less

@@ -0,0 +1,97 @@
+.selectAccount {
+    position: relative;
+}
+
+.selectAccount_row {
+    position: relative;
+    z-index: 1;
+    background-color: #FFF;
+    border-radius: 6px;
+}
+
+.selectAccount_select {
+    height: 32px;
+    padding: 1px 24px 1px 4px;
+    position: relative;
+
+    .clear {
+        position: absolute;
+        font-size: 12px;
+        right: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+        color: #c3c3c3;
+        display: none;
+    }
+
+    &:hover .clear {
+        display: block;
+    }
+}
+
+.selectAccount_list {
+    position: absolute;
+    width: 800px;
+    height: 400px;
+    background-color: #FFF;
+    margin-top: 10px;
+    border-radius: 6px;
+    display: flex;
+    flex-direction: column;
+    padding: 10px 0;
+
+    .selectAccount_list_header {
+        padding: 2px 10px;
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        margin-bottom: 10px;
+    }
+
+    .selectAccount_list_table {
+        height: 308px;
+        padding: 0 10px;
+    }
+
+
+    .selectAccount_list_footer {
+        padding: 4px 10px;
+        text-align: right;
+        background: transparent;
+        border-top: 1px solid #f0f0f0;
+        box-sizing: border-box;
+
+        .resetCss {
+            height: 28px;
+            line-height: normal;
+            border-radius: 6px !important;
+            padding: 3px 11px;
+        }
+    }
+}
+
+.selectAccount_select_content {
+    height: 100%;
+    min-width: 180px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+}
+
+.selectAccount_mask {
+    width: 100vw;
+    height: 100vh;
+    position: fixed;
+    background-color: rgba(0, 0, 0, .4);
+    z-index: 999;
+    top: 0;
+    left: 0;
+}
+
+.content_tag {
+    color: rgba(0, 0, 0, 0.85);
+
+    >span {
+        color: rgba(151, 151, 151, 0.85) !important;
+    }
+}

+ 483 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/SelectAccount/index.tsx

@@ -0,0 +1,483 @@
+import Selector from "@/pages/launchSystemNew/launchManage/createAd/selector"
+import React, { useEffect, useState } from "react"
+import style from './index.less'
+import { Button, Input, message, Modal, Select, Table, Tag, Tooltip, Typography } from "antd";
+import { CloseCircleFilled } from "@ant-design/icons";
+import { getGroupListApi, getAccountListApi } from "@/services/launchAdq/subgroup";
+import { useAjax } from "@/Hook/useAjax";
+import { getAccountAssetsGroupListAllApi } from "@/services/adqV3/global";
+import { getAllUserAccountApi } from "@/services/launchAdq/adAuthorize";
+import { groupBy } from "@/utils/utils";
+import { DELIVERY_MODE_ENUM } from "../../const";
+import '../../index.less'
+const { Text } = Typography;
+
+interface Props {
+    setAccountCreateLogs: React.Dispatch<React.SetStateAction<PULLIN.AccountCreateLogsProps[]>>
+    accountCreateLogs: PULLIN.AccountCreateLogsProps[]
+    setOwnerAccountId: React.Dispatch<React.SetStateAction<number | undefined>>
+    ownerAccountId: number | undefined
+    deliveryMode?: keyof typeof DELIVERY_MODE_ENUM
+    mType?: string,
+    putInType?: 'NOVEL' | 'GAME'
+    onChange?: (value?: any[], isClear?: boolean) => void
+    dynamicGroup?: any[]
+}
+/**
+ * 选择账户
+ * @returns 
+ */
+const SelectAccount: React.FC<Props> = ({ putInType, accountCreateLogs, setAccountCreateLogs, onChange, dynamicGroup, deliveryMode, mType }) => {
+
+    /***********************************/
+
+    const [visible, setVisible] = useState<boolean>(false)
+    const [assetSharingDeta, setAssetSharingDeta] = useState<{ authMainAccountId: number, groupId: number }>() // 资产共享组
+    const [dataSource, setDataSource] = useState<any[]>([])
+    const [options, setOptions] = useState<any[]>([])
+    const [selectedRows, setSelectedRows] = useState<any[]>([])
+    const [mediaTypeGroupIds, setMediaTypeGroupIds] = useState<number[]>([])
+    const [loading, setLoading] = useState<boolean>(false)
+    const [inputAccountList, setInputAccountList] = useState<string[]>([])
+    const [tipsVisible, setTipsVisible] = useState<boolean>(false)
+
+    const getGroupList = useAjax(() => getGroupListApi())
+    const getAccountAssetsGroupListAll = useAjax((params) => getAccountAssetsGroupListAllApi(params))
+    const getAllUserAccount = useAjax(() => getAllUserAccountApi())
+    /***********************************/
+
+    // 判断选择的账号是否在同一个资产组,允许选择云端素材的关键
+    const getauthMainAccountData = (accountCreateLogs: PULLIN.AccountCreateLogsProps[]) => {
+        console.log('---->', getAllUserAccount?.data)
+        if (getAllUserAccount?.data?.length && accountCreateLogs?.length) {
+            const list = accountCreateLogs.map(item => item.accountId)
+            const selectAccountList = getAllUserAccount.data.filter((item: { accountId: any }) => list.includes(item.accountId))
+            const groupAccount = groupBy(selectAccountList, (item) => item.groupId, true)
+            const GrounpArray = Object.keys(groupAccount).map(function (group) {
+                return groupAccount[group];
+            })
+            if (GrounpArray.length === 1) {
+                return { authMainAccountId: GrounpArray?.[0]?.[0]?.authMainAccountId || GrounpArray?.[0]?.[0]?.accountId, isSelectRemote: true }
+            } else if (GrounpArray.length === 2 && groupAccount?.['undefined']?.length === 1) {
+                const undefinedAccount = groupAccount?.['undefined'][0].accountId
+                let authMainAccountId: any
+                Object.keys(groupAccount).forEach(key => {
+                    if (key !== 'undefined') {
+                        authMainAccountId = groupAccount[key][0].authMainAccountId
+                    }
+                })
+                if (undefinedAccount === authMainAccountId) {
+                    return { authMainAccountId: authMainAccountId, isSelectRemote: true }
+                } else {
+                    return { authMainAccountId: -1, isSelectRemote: false }
+                }
+            } else {
+                return { authMainAccountId: -1, isSelectRemote: false }
+            }
+        } else {
+            return { authMainAccountId: undefined, isSelectRemote: false }
+        }
+    }
+
+    useEffect(() => {
+        if (visible && accountCreateLogs?.length) {
+            setSelectedRows(accountCreateLogs)
+        }
+    }, [accountCreateLogs, visible])
+
+    useEffect(() => {
+        if (putInType) {
+            // 获取账户组
+            getGroupList.run()
+            // 获取资产共享组
+            getAccountAssetsGroupListAll.run({})
+            // 账户
+            getAllUserAccount.run().then(res => {
+                if (res) {
+                    let options = res?.filter((item: any) => putInType === 'NOVEL' ? ['NOVEL', 'NOVEL_IAA'].includes(item.adUnitType) : putInType === 'GAME' ? ['GAME', 'GAME_IAA'].includes(item.adUnitType) : false)
+                    setOptions(options || [])
+                    setDataSource(options || [])
+                }
+            })
+        }
+        return () => {
+            document.body.style.overflow = 'auto';
+        }
+    }, [putInType])
+
+    /** 获取分组里账号 */
+    const getGroupAccountList = (ids: number[]) => {
+        setMediaTypeGroupIds(ids)
+        setAssetSharingDeta(undefined)
+        if (ids.length > 0) {
+            setLoading(true)
+            let data = ids.map(id => getAccountListApi(id))
+            Promise.all(data).then(res => {
+                setLoading(false)
+                if (res?.length > 0 && res.every((item: { code: number }) => item.code === 200)) {
+                    let userArr: any[] = []
+                    res.forEach((item: { data: { adAccountList: { accountId: number, id: number }[] } }) => {
+                        item.data.adAccountList.forEach(acc => {
+                            let obj = userArr.find((item: { accountId: number }) => item.accountId === acc.accountId)
+                            if (!obj) {
+                                userArr.push(acc.accountId)
+                            }
+                        })
+                    })
+                    setDataSource(options.filter(item => userArr.includes(item.accountId)))
+                } else {
+                    message.error('操作异常')
+                }
+            })
+        } else {
+            setDataSource(options)
+        }
+    }
+
+    const handleOk = (isClear: boolean) => {
+        document.body.style.overflow = 'auto';
+        onChange?.(selectedRows, isClear)
+        setSelectedRows([])
+        setInputAccountList([])
+        setMediaTypeGroupIds([])
+        setAssetSharingDeta(undefined)
+        setVisible(false)
+        setTipsVisible(false)
+    }
+
+    return <div className={style.selectAccount}>
+        <div className={style.selectAccount_row} style={{ zIndex: visible ? 1000 : 1 }}>
+            <Selector
+                label={visible ? '选择账户' : '媒体账户'}
+                style={visible ? { borderColor: '#1890ff' } : {}}
+                titleStyle={visible ? { borderColor: '#1890ff', color: '#1890ff' } : {}}
+            >
+                <div
+                    className={style.selectAccount_select}
+                    onClick={() => {
+                        document.body.style.overflow = 'hidden';
+                        setVisible(true)
+                    }}
+                >
+                    <div className={style.selectAccount_select_content}>
+                        {(visible && selectedRows.length > 0) ? <>
+                            <Tag
+                                closable
+                                color="#F5F5F5"
+                                className={style.content_tag}
+                                onClose={() => {
+                                    setSelectedRows(selectedRows.slice(1))
+                                }}
+                            >{selectedRows[0].accountId}</Tag>
+                            {selectedRows?.length > 1 && <Tooltip
+                                color="#FFF"
+                                title={<span style={{ color: '#000' }}>
+                                    {selectedRows?.filter((item, index) => index !== 0)?.map((item, index) => <Tag
+                                        color="#F5F5F5"
+                                        className={style.content_tag}
+                                        key={index}
+                                        closable
+                                        onClose={() => {
+                                            setSelectedRows(selectedRows?.filter(item1 => item1.accountId !== item.accountId))
+                                        }}
+                                    >{item.accountId}</Tag>)}</span>
+                                }
+                            >
+                                <Tag color="#F5F5F5" className={style.content_tag}>+{selectedRows.length - 1}</Tag>
+                            </Tooltip>}
+                        </> : (!visible && accountCreateLogs?.length > 0) ? <>
+                            <Tag
+                                closable
+                                color="#F5F5F5"
+                                className={style.content_tag}
+                                onClose={() => {
+                                    setAccountCreateLogs(accountCreateLogs.slice(1))
+                                }}
+                            >{accountCreateLogs[0].accountId}</Tag>
+                            {accountCreateLogs?.length > 1 && <Tooltip
+                                color="#FFF"
+                                title={<span style={{ color: '#000' }}>
+                                    {accountCreateLogs?.filter((item, index) => index !== 0)?.map((item, index) => <Tag
+                                        className={style.content_tag}
+                                        color="#F5F5F5"
+                                        key={index}
+                                        closable
+                                        onClose={() => {
+                                            setAccountCreateLogs(accountCreateLogs?.filter(item1 => item1.accountId !== item.accountId))
+                                        }}
+                                    >{item.accountId}</Tag>)}</span>
+                                }
+                            >
+                                <Tag color="#F5F5F5" className={style.content_tag}>+{accountCreateLogs.length - 1}</Tag>
+                            </Tooltip>}
+                        </> : <Text type="secondary">请选择媒体账户</Text>}
+                    </div>
+                    {visible}
+                    {((visible && selectedRows.length > 0) || (!visible && accountCreateLogs?.length > 0)) && <a className={style.clear} onClick={(e) => {
+                        e.stopPropagation()
+                        if (visible) {
+                            setSelectedRows([])
+                        } else {
+                            setAccountCreateLogs([])
+                        }
+                    }}><CloseCircleFilled /></a>}
+                </div>
+            </Selector>
+            {visible && <div className={style.selectAccount_list}>
+                <div className={style.selectAccount_list_header}>
+                    <Input.TextArea
+                        rows={1}
+                        autoSize={{ minRows: 1, maxRows: 1 }}
+                        placeholder="账户/备注(多个,,空格换行)"
+                        style={{ width: 200 }}
+                        allowClear
+                        onChange={e => {
+                            let input = e.target.value
+                            let newInput: string[] = input ? input?.split(/[,,\n\s]+/ig).filter((item: any) => item) : []
+                            setInputAccountList(newInput)
+                        }}
+                    />
+                    <Select
+                        mode="multiple"
+                        style={{ minWidth: 200 }}
+                        placeholder="快捷选择媒体账户组"
+                        maxTagCount={1}
+                        allowClear
+                        filterOption={(input: any, option: any) => {
+                            return option!.children?.toString().toLowerCase().includes(input.toLowerCase())
+                        }}
+                        value={mediaTypeGroupIds}
+                        onChange={(e) => { getGroupAccountList(e) }}
+                    >
+                        {getGroupList?.data && getGroupList?.data?.map((item: any) => <Select.Option value={item.groupId} key={item.groupId}>{item.groupName}</Select.Option>)}
+                    </Select>
+                    <Select
+                        allowClear
+                        showSearch
+                        placeholder="账户资产共享组"
+                        filterOption={(input: any, option: any) => {
+                            return option!.children?.toString().toLowerCase().includes(input.toLowerCase())
+                        }}
+                        style={{ width: 145 }}
+                        value={assetSharingDeta?.groupId}
+                        onChange={(_, option) => {
+                            setMediaTypeGroupIds([])
+                            if (option) {
+                                let assetSharingDeta = { groupId: option.value, authMainAccountId: option.authMainAccountId }
+                                setAssetSharingDeta({ groupId: option.value, authMainAccountId: option.authMainAccountId })
+                                setDataSource(options?.filter((item: { accountId: number; groupId: number }) => assetSharingDeta?.groupId ? (item.accountId?.toString() === assetSharingDeta?.authMainAccountId?.toString() || item?.groupId?.toString() === assetSharingDeta?.groupId?.toString()) : true))
+                            } else {
+                                setAssetSharingDeta(undefined)
+                                setDataSource(options)
+                            }
+                        }}
+                    >
+                        {getAccountAssetsGroupListAll?.data?.map((item: { accountId: any; authMainAccountId: number; id: number; accountGroupName: string }) => <Select.Option value={item.accountId} authMainAccountId={item.authMainAccountId} key={item.id}>{item.accountGroupName}({item.authMainAccountId})</Select.Option>)}
+                    </Select>
+                </div>
+                <div className={style.selectAccount_list_table}>
+                    <Table
+                        size={'small'}
+                        bordered
+                        dataSource={dataSource.filter(item => inputAccountList?.length > 0 ? inputAccountList?.some(val => item.accountId?.toString().toLowerCase()?.includes(val) || item?.remark?.toString().toLowerCase()?.includes(val)) : true)}
+                        rowKey={'accountId'}
+                        scroll={{ y: 220 }}
+                        pagination={{
+                            pageSize: 50,
+                            showTotal: total => `总共 ${total} 账户`
+                        }}
+                        loading={getAllUserAccount.loading || loading}
+                        columns={[
+                            {
+                                title: '账号',
+                                dataIndex: 'accountId',
+                                key: 'accountId',
+                                width: 80,
+                                align: 'center',
+                                render(value) {
+                                    return <span style={{ fontSize: 12 }}>{value}</span>
+                                }
+                            },
+                            {
+                                title: '企业',
+                                dataIndex: 'corporationName',
+                                key: 'corporationName',
+                                width: 180,
+                                ellipsis: true,
+                                render(value) {
+                                    return <span style={{ fontSize: 12 }}>{value}</span>
+                                }
+                            },
+                            {
+                                title: '企业标识',
+                                dataIndex: 'corporationLicence',
+                                key: 'corporationLicence',
+                                width: 150,
+                                ellipsis: true,
+                                render(value) {
+                                    return <span style={{ fontSize: 12 }}>{value}</span>
+                                }
+                            },
+                            {
+                                title: '备注',
+                                dataIndex: 'remark',
+                                key: 'remark',
+                                ellipsis: true,
+                                render(value) {
+                                    return <span style={{ fontSize: 12 }}>{value || '--'}</span>
+                                }
+                            },
+                        ]}
+                        rowSelection={{
+                            selectedRowKeys: selectedRows.map(item => item.accountId),
+                            onSelect: (record: { accountId: number }, selected: boolean) => {
+                                if (selected) {
+                                    selectedRows.push({ ...record })
+                                    setSelectedRows([...selectedRows])
+                                } else {
+                                    let newSelectAccData = selectedRows.filter((item: { accountId: number }) => item.accountId !== record.accountId)
+                                    setSelectedRows([...newSelectAccData])
+                                }
+                            },
+                            onSelectAll: (selected: boolean, selectedRowss: { accountId: number }[], changeRows: { accountId: number }[]) => {
+                                if (selected) {
+                                    let newSelectAccData = [...selectedRows]
+                                    changeRows.forEach((item: { accountId: number }) => {
+                                        let index = newSelectAccData.findIndex((ite: { accountId: number }) => ite.accountId === item.accountId)
+                                        if (index === -1) {
+                                            let data: any = { ...item }
+                                            newSelectAccData.push(data)
+                                        }
+                                    })
+                                    setSelectedRows([...newSelectAccData])
+                                } else {
+                                    let newSelectAccData = selectedRows.filter((item: { accountId: number }) => {
+                                        let index = changeRows.findIndex((ite: { accountId: number }) => ite.accountId === item.accountId)
+                                        if (index !== -1) {
+                                            return false
+                                        } else {
+                                            return true
+                                        }
+                                    })
+                                    setSelectedRows([...newSelectAccData])
+                                }
+                            }
+                        }}
+                    />
+                </div>
+                <div className={style.selectAccount_list_footer}>
+                    <Button
+                        className={style.resetCss}
+                        onClick={() => {
+                            document.body.style.overflow = 'auto';
+                            setSelectedRows([])
+                            setInputAccountList([])
+                            setMediaTypeGroupIds([])
+                            setAssetSharingDeta(undefined)
+                            setVisible(false)
+                        }}
+                    >取消</Button>
+                    <Button
+                        className={style.resetCss}
+                        type="primary"
+                        style={{ marginLeft: 8 }}
+                        onClick={() => {
+                            let authMainAccountId: any
+                            let isY = false
+                            // 1.判断是否有账户组素材 有的话 获取账户组ID
+                            if (dynamicGroup && dynamicGroup?.length > 0) {
+                                if (deliveryMode && mType) {
+                                    if (dynamicGroup.some((item: any) => {
+                                        if (deliveryMode === 'DELIVERY_MODE_CUSTOMIZE') {
+                                            if (['short_video', 'video'].includes(mType) && (item?.video_id?.materialType || item?.short_video1?.materialType) === 1) {
+                                                authMainAccountId = item?.video_id?.accountId || item?.short_video1?.accountId
+                                                return true
+                                            } else if (['image_list'].includes(mType) && item?.image_list?.some((list: any) => {
+                                                if (list?.materialType === 1) {
+                                                    authMainAccountId = list?.accountId
+                                                    return true
+                                                } else {
+                                                    return false
+                                                }
+                                            })) {
+                                                return true
+                                            } else if (['element_story'].includes(mType) && item?.element_story?.some((list: any) => {
+                                                if (list?.materialType === 1) {
+                                                    authMainAccountId = list?.accountId
+                                                    return true
+                                                } else {
+                                                    return false
+                                                }
+                                            })) {
+                                                return true
+                                            } else if (['image'].includes(mType) && item?.image_id?.materialType === 1) {
+                                                authMainAccountId = item?.image_id?.accountId
+                                                return true
+                                            } else {
+                                                return false
+                                            }
+                                        } else {
+                                            if (item?.list?.some((list: any) => {
+                                                if (Array.isArray(list) && list.some((l: any) => {
+                                                    if (l.materialType === 1) {
+                                                        authMainAccountId = l?.accountId
+                                                        return true
+                                                    } else {
+                                                        return false
+                                                    }
+                                                })) {
+                                                    return true
+                                                } else if (list.materialType === 1) {
+                                                    authMainAccountId = list?.accountId
+                                                    return true
+                                                } else {
+                                                    return false
+                                                }
+                                            })) {
+                                                return true
+                                            } else {
+                                                return false
+                                            }
+                                        }
+                                    })) {
+                                        isY = true
+                                    }
+                                } else {
+                                    message.error('请联系管理员')
+                                }
+                            }
+                            
+                            if (isY && authMainAccountId) {
+                                // 2.有的话判断 判断账户是否为一组
+                                console.log('authMainAccountId---->', authMainAccountId, getauthMainAccountData(selectedRows))
+                                const authMainData = getauthMainAccountData(selectedRows)
+                                if (authMainData.isSelectRemote && authMainData.authMainAccountId === authMainAccountId) {
+                                    handleOk(false)
+                                } else {
+                                    setTipsVisible(true)
+                                }
+                                // 3.是的话 判断是以前那组吗
+                            } else {
+                                handleOk(false)
+                            }
+                        }}
+                    >确定</Button>
+                </div>
+            </div>}
+        </div>
+        {visible && <div className={style.selectAccount_mask}></div>}
+        {tipsVisible && <Modal
+            title={<strong>提示</strong>}
+            open={tipsVisible}
+            onCancel={() => {
+                setTipsVisible(false)
+            }}
+            onOk={() => handleOk(true)}
+            className="modalResetCss"
+            okText="清空"
+            cancelText="留下来修改账户"
+        >选择的素材有账户组素材,但是与当前账户不匹配,是否使用当前账号清空素材?</Modal>}
+    </div>
+}
+
+export default React.memo(SelectAccount)

+ 37 - 121
src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx

@@ -1,12 +1,10 @@
-import { Button, Card, Checkbox, Divider, Empty, Modal, Popconfirm, Radio, Select, Space, Spin, Table, Tabs, Tag, Tooltip, message, notification } from "antd"
+import { Button, Card, Empty, Modal, Popconfirm, Radio, Space, Spin, Table, Tabs, Tag, message, notification } from "antd"
 import React, { useEffect, useState } from "react"
 import style from './index.less'
 import '../index.less'
-import Selector from "@/pages/launchSystemNew/launchManage/createAd/selector"
 import { CheckOutlined, SearchOutlined } from "@ant-design/icons"
 import Ad from "./Ad"
 import Target from "./Target"
-import { getAccountListApi, getGroupListApi } from "@/services/launchAdq/subgroup"
 import { useAjax } from "@/Hook/useAjax"
 import { useModel } from "umi"
 import GoodsModal from "../../components/GoodsModal"
@@ -27,6 +25,7 @@ import ConversionSelect from "../../components/ConversionSelect"
 import VideoChannel from "../../components/VideoChannel"
 import Save from "./Save"
 import { useLocalStorageState } from "ahooks"
+import SelectAccount from "./SelectAccount"
 
 export const DispatchAddelivery = React.createContext<PULLIN.DispatchAddelivery | null>(null);
 
@@ -38,12 +37,10 @@ const Create: React.FC = () => {
 
     /*******************************************/
     const { initialState } = useModel('@@initialState');
-    const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
     const { initTargeting } = useModel('useLaunchV3.useTargeting')
     const [addelivery, setAddelivery] = useState<PULLIN.AddeliveryProps>({ adgroups: {}, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {}, mediaType: 0 })
     const { marketingAssetOuterSpec, marketingCarrierType, marketingGoal, marketingSubGoal, siteSet, automaticSiteEnabled, sceneSpec, isConversion } = addelivery.adgroups
     const { deliveryMode, creativeTemplateId, creativeComponents } = addelivery.dynamic
-    const [accSearch, setAccSearch] = useState<string>()
     const [accountCreateLogs, setAccountCreateLogs] = useState<PULLIN.AccountCreateLogsProps[]>([])  // 账户
     const [goodsVisible, setGoodsVisible] = useState<boolean>(false) // 选择小说弹窗控制
     const [wechatVisible, setWechatVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
@@ -62,8 +59,8 @@ const Create: React.FC = () => {
     const [putInTypeList, setPutInTypeList] = useState<{ label: string, value: string }[]>([])
     const [putInType, setPutInType] = useLocalStorageState<'NOVEL' | 'GAME'>('PUTINTYPE');
     const [copyTask, setCopyTask] = useState<{ copyTaskId?: number, uuid?: string }>({})
+    const [ownerAccountId, setOwnerAccountId] = useState<number>()
 
-    const getGroupList = useAjax(() => getGroupListApi())
     const createAdgroupTask = useAjax((params) => createAdgroupTaskApi(params))
     const getSelectTaskDetail = useAjax((params) => getSelectTaskDetailApi(params))
     const getCreativeDetails = useAjax((params) => getCreativeDetailsApi(params))
@@ -90,10 +87,6 @@ const Create: React.FC = () => {
     }, [initialState?.menu])
 
     useEffect(() => {
-        // 获取账户组
-        getGroupList.run()
-        // 获取账户列表
-        getAllUserAccount.run()
         initTargeting()
     }, [])
 
@@ -145,33 +138,6 @@ const Create: React.FC = () => {
         }
     }, [creativeTemplateId, deliveryMode, marketingGoal, marketingAssetOuterSpec, marketingCarrierType, siteSet, sceneSpec?.wechatPosition, automaticSiteEnabled, putInType])
 
-    /** 获取分组里账号 */
-    const getGroupAccountList = (ids: number[]) => {
-        if (ids.length > 0) {
-            let data = ids.map(id => getAccountListApi(id))
-            Promise.all(data).then(res => {
-                if (res?.length > 0 && res.every((item: { code: number }) => item.code === 200)) {
-                    let userArr: any[] = []
-                    res.forEach((item: { data: { adAccountList: { accountId: number, id: number }[] } }) => {
-                        item.data.adAccountList.forEach(acc => {
-                            let obj = userArr.find((item: { accountId: number }) => item.accountId === acc.accountId)
-                            if (!obj) {
-                                userArr.push(acc)
-                            }
-                        })
-                    })
-                    setAccountCreateLogs(userArr?.filter((item: any) => putInType === 'NOVEL' ? ['NOVEL', 'NOVEL_IAA'].includes(item.adUnitType) : putInType === 'GAME' ? ['GAME', 'GAME_IAA'].includes(item.adUnitType) : false)?.map((item) => ({ accountId: item?.accountId })))
-                    clearData()
-                    setAddelivery({ adgroups: {}, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {}, mediaType: 0 })
-                } else {
-                    message.error('操作异常')
-                }
-            })
-        } else {
-            setAccountCreateLogs([])
-        }
-    }
-
     /** 存为预设 */
     const severBd = () => {
         if (putInType) {
@@ -702,7 +668,8 @@ const Create: React.FC = () => {
                                 value: {
                                     imageUrl: item?.image_id?.url,
                                     imageId: item?.image_id?.id,
-                                    materialType: item?.image_id?.materialType
+                                    materialType: item?.image_id?.materialType,
+                                    accountId: item?.image_id?.accountId
                                 }
                             })
                         }]
@@ -715,7 +682,8 @@ const Create: React.FC = () => {
                             return {
                                 imageUrl: l?.url,
                                 imageId: l?.id,
-                                materialType: l?.materialType
+                                materialType: l?.materialType,
+                                accountId: l?.accountId
                             }
                         })
                         return [{
@@ -730,12 +698,15 @@ const Create: React.FC = () => {
                         let value: any = {
                             materialType: item?.video_id?.materialType || item?.short_video1?.materialType || 0,
                             videoUrl: item?.video_id?.url || item?.short_video1?.url,
-                            videoId: item?.video_id?.id || item?.short_video1?.id
+                            videoId: item?.video_id?.id || item?.short_video1?.id,
+                            keyFrameImageUrl: item?.video_id?.keyFrameImageUrl || item?.short_video1?.keyFrameImageUrl,
+                            accountId: item?.video_id?.accountId || item?.short_video1?.accountId,
                         }
                         if (item?.cover_id?.url) {
                             value.imageUrl = item?.cover_id?.url
                             value.imageId = item?.cover_id?.id
                             value.materialCoverType = item?.cover_id?.materialType
+                            value.accountId = item?.cover_id?.accountId
                         }
                         return [{
                             type: mType,
@@ -763,20 +734,23 @@ const Create: React.FC = () => {
                                             return {
                                                 imageUrl: i?.url,
                                                 imageId: i?.id,
-                                                materialType: i?.materialType
+                                                materialType: i?.materialType,
+                                                accountId: i?.accountId
                                             }
                                         })
                                     }
                                 })
                             }
-                        } else if (l?.url?.includes('mp4')) {
+                        } else if (l?.url?.includes('mp4') || l?.keyFrameImageUrl) {
                             return {
                                 type: 'video',
                                 valueJson: JSON.stringify({
                                     value: {
                                         materialType: l?.materialType,
                                         videoUrl: l?.url,
-                                        videoId: l?.id
+                                        videoId: l?.id,
+                                        keyFrameImageUrl: l?.keyFrameImageUrl,
+                                        accountId: l?.accountId
                                     }
                                 })
                             }
@@ -787,7 +761,8 @@ const Create: React.FC = () => {
                                     value: {
                                         imageUrl: l?.url,
                                         imageId: l?.id,
-                                        materialType: l?.materialType
+                                        materialType: l?.materialType,
+                                        accountId: l?.accountId
                                     }
                                 })
                             }
@@ -863,6 +838,7 @@ const Create: React.FC = () => {
     const clearData = () => {
         setTableData({})
     }
+    console.log('addelivery---->', addelivery)
 
     return <Space direction="vertical" style={{ width: '100%' }}>
         <Spin spinning={createAdgroupTask.loading || getSelectTaskDetail.loading || getCreativeDetails.loading}>
@@ -885,83 +861,23 @@ const Create: React.FC = () => {
                 className={style.createAd}
             >
                 <Space wrap>
-                    <Selector label="媒体账户组">
-                        <Select
-                            mode="multiple"
-                            style={{ minWidth: 200 }}
-                            placeholder="快捷选择媒体账户组"
-                            maxTagCount={1}
-                            allowClear
-                            bordered={false}
-                            filterOption={(input: any, option: any) => {
-                                return option!.children?.toString().toLowerCase().includes(input.toLowerCase())
-                            }}
-                            onChange={(e) => { getGroupAccountList(e) }}
-                        >
-                            {getGroupList?.data && getGroupList?.data?.map((item: any) => <Select.Option value={item.groupId} key={item.groupId}>{item.groupName}</Select.Option>)}
-                        </Select>
-                    </Selector>
-                    <Selector label="媒体账户">
-                        <Select
-                            mode="multiple"
-                            style={{ minWidth: 200, maxWidth: 500 }}
-                            placeholder="媒体账户(多个,,空格换行)"
-                            maxTagCount={1}
-                            allowClear
-                            bordered={false}
-                            maxTagPlaceholder={
-                                <Tooltip
-                                    color="#FFF"
-                                    title={<span style={{ color: '#000' }}>
-                                        {accountCreateLogs?.filter((item, index) => index !== 0)
-                                            ?.map((item, index) => <Tag
-                                                key={index}
-                                                closable
-                                                onClose={() => {
-                                                    setAccountCreateLogs(accountCreateLogs?.filter(item1 => item1.accountId !== item.accountId))
-                                                }}
-                                            >{item.accountId}</Tag>)}</span>
-                                    }
-                                >
-                                    <span>+{accountCreateLogs?.length > 1 ? accountCreateLogs.length - 1 : 0}</span>
-                                </Tooltip>
+                    <SelectAccount
+                        setAccountCreateLogs={setAccountCreateLogs}
+                        accountCreateLogs={accountCreateLogs}
+                        setOwnerAccountId={setOwnerAccountId}
+                        ownerAccountId={ownerAccountId}
+                        putInType={putInType}
+                        onChange={(e, isClear) => {
+                            clearData()
+                            setAccountCreateLogs(e?.map((item: any) => ({ accountId: item.accountId })) || [])
+                            if (isClear) {
+                                setAddelivery({ ...addelivery, dynamicMaterialDTos: {} })
                             }
-                            autoClearSearchValue={false}
-                            filterOption={(input: any, option: any) => {
-                                let newInput: string[] = input ? input?.split(/[,,\n\s]+/ig).filter((item: any) => item) : []
-                                return newInput?.some(val => option!.children?.toString().toLowerCase()?.includes(val))
-                            }}
-                            value={accountCreateLogs?.map(item => item?.accountId)}
-                            onChange={(e) => {
-                                setAccountCreateLogs(e?.map((item: any) => ({ accountId: item })))
-                            }}
-                            searchValue={accSearch}
-                            onSearch={(val) => {
-                                setAccSearch(val)
-                            }}
-                            dropdownRender={menu => (
-                                <>
-                                    {menu}
-                                    <Divider style={{ margin: '8px 0' }} />
-                                    <Space style={{ padding: '0 8px 4px' }}>
-                                        <Checkbox onChange={(e) => {
-                                            let data = []
-                                            if (e.target.checked) {
-                                                data = JSON.parse(JSON.stringify(getAllUserAccount?.data?.data?.filter((item: any) => putInType === 'NOVEL' ? ['NOVEL', 'NOVEL_IAA'].includes(item.adUnitType) : putInType === 'GAME' ? ['GAME', 'GAME_IAA'].includes(item.adUnitType) : false)))
-                                                if (accSearch) {
-                                                    let newAccSearch = accSearch?.split(/[,,\n\s]+/ig).filter((item: any) => item)
-                                                    data = data?.filter((item: any) => newAccSearch?.some(val => item!.accountId?.toString().toLowerCase()?.includes(val)))
-                                                }
-                                            }
-                                            setAccountCreateLogs(data?.map((item: any) => ({ accountId: item?.accountId })))
-                                        }}>全选</Checkbox>
-                                    </Space>
-                                </>
-                            )}
-                        >
-                            {getAllUserAccount?.data?.data?.filter((item: any) => putInType === 'NOVEL' ? ['NOVEL', 'NOVEL_IAA'].includes(item.adUnitType) : putInType === 'GAME' ? ['GAME', 'GAME_IAA'].includes(item.adUnitType) : false)?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
-                        </Select>
-                    </Selector>
+                        }}
+                        dynamicGroup={addelivery?.dynamicMaterialDTos?.dynamicGroup}
+                        deliveryMode={deliveryMode}
+                        mType={Object.keys(materialData)[0]}
+                    />
 
                     {accountCreateLogs?.length > 0 && <>
                         {['MARKETING_TARGET_TYPE_FICTION', 'MARKETING_TARGET_TYPE_SHORT_DRAMA'].includes(marketingAssetOuterSpec?.marketingTargetType) && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.productList?.length)} onClick={() => { setGoodsVisible(true) }}>{accountCreateLogs?.some(item => item?.productList?.length) ? <>重新选择{marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' ? '小说' : '短剧'} <CheckOutlined style={{ color: '#FFFFFF' }} /></> : `请选择${marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' ? '小说' : '短剧'}`}</Button>}
@@ -988,7 +904,7 @@ const Create: React.FC = () => {
                                 textData,
                                 setTextData,
                                 clearData,
-                                putInType
+                                putInType,
                             }}>
                             <div className={style.settingsBody_content_right}>
                                 {/* 广告信息 */}

+ 3 - 3
src/pages/launchSystemV3/tencentAdPutIn/create/tableConfig.tsx

@@ -129,7 +129,7 @@ const columns = (): TableProps<any>['columns'] => {
                                         {(keys.includes('video_id') || keys.includes('short_video1')) ? <>
                                             <Title style={{ fontSize: 12, color: '#1890ff', marginBottom: 0, width: '100%' }}>已选1个视频,0张图片</Title>
                                             <div className={style.video}>
-                                                <VideoNews src={dynamicGroup?.video_id?.url || dynamicGroup?.short_video1?.url} />
+                                                <VideoNews src={dynamicGroup?.video_id?.url || dynamicGroup?.short_video1?.url} keyFrameImageUrl={dynamicGroup?.video_id?.keyFrameImageUrl || dynamicGroup?.short_video1?.keyFrameImageUrl}/>
                                                 {dynamicGroup?.cover_id && <div className={style.cover_image} style={{ marginLeft: 4 }}>
                                                     <img src={dynamicGroup?.cover_id?.url} />
                                                 </div>}
@@ -157,10 +157,10 @@ const columns = (): TableProps<any>['columns'] => {
                                                     {item.map((l, i) => <img src={l?.url} key={i} style={{ width: length === 6 ? 9.999 : 14.999 }} />)}
                                                 </div>
                                             </div>
-                                        } else if (item?.url?.includes('mp4')) {
+                                        } else if (item?.url?.includes('mp4') || item?.keyFrameImageUrl) {
                                             return <div className={styles.boxList_body_item} key={index} style={{ width: 30, height: 30 }}>
                                                 <div className={styles.content} style={{ width: 30, height: 30 }}>
-                                                    <VideoNews src={item?.url} style={{ width: 30, height: 30 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
+                                                    <VideoNews src={item?.url} style={{ width: 30, height: 30 }} keyFrameImageUrl={item?.keyFrameImageUrl} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
                                                 </div>
                                             </div>
                                         } else {

+ 3 - 1
src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts

@@ -32,7 +32,9 @@ declare namespace PULLIN {
         textData: any,
         setTextData: React.Dispatch<any>,
         clearData: () => void,
-        putInType?: 'NOVEL' | 'GAME'
+        putInType?: 'NOVEL' | 'GAME',
+        // 是否可以选择云端素材
+        isSelectRemote?: boolean
     }
     type DataType = { label: string | number, value: any, disabled?: boolean }
     interface FormItemDataProps {

+ 19 - 0
src/services/adqV3/cloudNew.ts

@@ -125,6 +125,25 @@ export async function getMaterialDataListApi(data: CLOUDNEW.GetMaterialDataListP
     })
 }
 
+/**
+ * 素材组选择
+ * @param data 
+ * @returns 
+ */
+export async function getPageRemoteImageDataListApi({ materialType, ...data }: CLOUDNEW.GetPageRemoteImageDataListProps) {
+    if (materialType === 'image') { // 图片
+        return request(api + `/material/material/pageRemoteImageDataList`, {
+            method: 'POST',
+            data
+        })
+    } else { // 视频
+        return request(api + `/material/material/pageRemoteVideoDataList`, {
+            method: 'POST',
+            data
+        })
+    }
+}
+
 
 /**
  * 新增素材

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

@@ -699,6 +699,18 @@ export async function getAccountAssetsGroupListApi(data: { accountGroupName?: st
     })
 }
 
+/**
+ * 所有账户资产共享组
+ * @param data 
+ * @returns 
+ */
+export async function getAccountAssetsGroupListAllApi(data: { accountGroupName?: string, authMainAccountIds?: number[] }) {
+    return request(api + `/adq/v3/marketingAssets/accountAssetsGroup/ListAll`, {
+        method: 'POST',
+        data
+    })
+}
+
 
 /**
  * 账户资产组修改(只能改名字)
@@ -731,8 +743,7 @@ export async function revokeAuthAccountAssetsGroupApi(data: { accountAssetsGroup
  */
 export async function delAuthAccountAssetsGroupApi(data: { id: number }) {
     return request(api + `/adq/v3/marketingAssets/accountAssetsGroup/delete/${data.id}`, {
-        method: 'POST',
-        data
+        method: 'DELETE',
     })
 }
 

+ 10 - 0
src/services/launchAdq/adAuthorize.ts

@@ -49,6 +49,16 @@ export async function getAllUserAccountApi() {
     });
 }
 
+/**
+ * 获取所有账户
+ * @returns 
+ */
+export async function getAccountAllListApi() {
+    return request(api + '/adq/adAccount/allList', {
+        method: 'GET',
+    });
+}
+
 /**
  * 获取账号列表
  * @returns 

+ 4 - 1
src/utils/utils.ts

@@ -175,13 +175,16 @@ export const copy = (str: string) => {
 }
 
 // 数组分组
-export const groupBy = (array: any[], f: (item: any) => any[]) => {
+export const groupBy = (array: any[], f: (item: any) => any[], isObject?: boolean) => {
     const groups: any = {};
     array.forEach(function (o) { //注意这里必须是forEach 大写
         const group = JSON.stringify(f(o));
         groups[group] = groups[group] || [];
         groups[group].push(o);
     });
+    if (isObject) {
+        return groups
+    }
     return Object.keys(groups).map(function (group) {
         return groups[group];
     });