Browse Source

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

wjx 6 tháng trước cách đây
mục cha
commit
acc29e7fea
24 tập tin đã thay đổi với 1151 bổ sung78 xóa
  1. 1 0
      src/components/FileBoxAD/index.tsx
  2. 3 3
      src/pages/launchSystemV3/adMonitorListV3/adExpandedRowRender.tsx
  3. 28 2
      src/pages/launchSystemV3/adMonitorListV3/config.ts
  4. 159 0
      src/pages/launchSystemV3/adMonitorListV3/tableDynamicConfig.tsx
  5. 159 0
      src/pages/launchSystemV3/adMonitorListV3/tablePlanListConfig.tsx
  6. 315 0
      src/pages/launchSystemV3/components/TextAideInput/const.ts
  7. 61 18
      src/pages/launchSystemV3/components/TextAideInput/index.less
  8. 73 26
      src/pages/launchSystemV3/components/TextAideInput/index.tsx
  9. 111 0
      src/pages/launchSystemV3/material/cloudNew/copyFile.tsx
  10. 39 3
      src/pages/launchSystemV3/material/cloudNew/folder.tsx
  11. 13 0
      src/pages/launchSystemV3/material/cloudNew/index.less
  12. 12 3
      src/pages/launchSystemV3/material/cloudNew/index.tsx
  13. 55 7
      src/pages/launchSystemV3/material/cloudNew/material.tsx
  14. 1 1
      src/pages/launchSystemV3/material/tencent/const.ts
  15. 32 1
      src/pages/launchSystemV3/material/tencent/search.tsx
  16. 2 0
      src/pages/launchSystemV3/material/typings.d.ts
  17. 40 0
      src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/delPage.tsx
  18. 1 1
      src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/index.tsx
  19. 9 5
      src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/tableConfig.tsx
  20. 1 1
      src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/index.tsx
  21. 9 5
      src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx
  22. 13 0
      src/services/adqV3/cloudNew.ts
  23. 12 0
      src/services/adqV3/global.ts
  24. 2 2
      src/utils/utils.ts

+ 1 - 0
src/components/FileBoxAD/index.tsx

@@ -192,6 +192,7 @@ function FlieBox(props: Props) {
         return {
             draggable: true,
             onDragStart: (ev: any) => {
+                console.log('---->', ev)
                 let img = document.createElement('img')
                 img.src = fileImg;
                 ev.dataTransfer.setDragImage(img, 0, 0)

+ 3 - 3
src/pages/launchSystemV3/adMonitorListV3/adExpandedRowRender.tsx

@@ -52,16 +52,16 @@ const AdExpandedRowRender: React.FC<{ data: any, scrollLeft: number, width?: num
                         </div>
                     </Descriptions.Item>
                     <Descriptions.Item labelStyle={{ width: 100 }} label="状态">
-                        {ADGROUP_STATUS[systemStatus] || '--'}
+                        {ADGROUP_STATUS[systemStatus as keyof typeof ADGROUP_STATUS] || '--'}
                     </Descriptions.Item>
-                    <Descriptions.Item labelStyle={{ width: 100 }} label="出价">{`${BidModeEnum[bidMode]} ${bidAmount}元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal]}`}</Descriptions.Item>
+                    <Descriptions.Item labelStyle={{ width: 100 }} label="出价">{`${BidModeEnum[bidMode as keyof typeof BidModeEnum]} ${bidAmount}元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal as keyof typeof OptimizationGoalEnum]}`}</Descriptions.Item>
                     <Descriptions.Item label="投放时间" labelStyle={{ width: 100 }}>{<TimeSeriesLook timeSeries={timeSeries} />}</Descriptions.Item>
                     <Descriptions.Item label="定向" labelStyle={{ width: 100, flex: '0 0 auto' }} span={2}><div style={{ width: '98%' }}>
                         <Typography.Paragraph ellipsis={{ tooltip: true, rows: 2 }}>{targetingTranslation}</Typography.Paragraph>
                     </div></Descriptions.Item>
                     <Descriptions.Item label="首日开始时间" labelStyle={{ width: 100 }}>{firstDayBeginTime}</Descriptions.Item>
                     <Descriptions.Item label="日预算" labelStyle={{ width: 100 }}>{dailyBudget}</Descriptions.Item>
-                    <Descriptions.Item label="出价策略" labelStyle={{ width: 100 }}>{BidStrategyEnum[bidStrategy]}</Descriptions.Item>
+                    <Descriptions.Item label="出价策略" labelStyle={{ width: 100 }}>{BidStrategyEnum[bidStrategy as keyof typeof BidStrategyEnum]}</Descriptions.Item>
                 </Descriptions>
             </Spin>
         }

+ 28 - 2
src/pages/launchSystemV3/adMonitorListV3/config.ts

@@ -129,7 +129,16 @@ const planAdConfig = [
             { title: '激活首24小时广告变现人数', serverIndex: 'adgroup_data.ad_paying_users24h_total', dataIndex: 'ad_paying_users24h_total', label: '其他业务(其他指标)', width: 90 },
             { title: '激活首日广告变现人数', serverIndex: 'adgroup_data.ad_paying_users_d1_total', dataIndex: 'ad_paying_users_d1_total', label: '其他业务(其他指标)', width: 80 },
             { title: '激活3日广告变现人数', serverIndex: 'adgroup_data.ad_monetization_dedup_active3d_pv_total', dataIndex: 'ad_monetization_dedup_active3d_pv_total', label: '其他业务(其他指标)', width: 80 },
-            { title: '激活7日广告变现人数', serverIndex: 'adgroup_data.ad_monetization_dedup_active7d_pv_total', dataIndex: 'ad_monetization_dedup_active7d_pv_total', label: '其他业务(其他指标)', width: 80 }
+            { title: '激活7日广告变现人数', serverIndex: 'adgroup_data.ad_monetization_dedup_active7d_pv_total', dataIndex: 'ad_monetization_dedup_active7d_pv_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '注册次数', serverIndex: 'adgroup_data.reg_pv_total', dataIndex: 'reg_pv_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '注册人数', serverIndex: 'adgroup_data.reg_dedup_pv_total', dataIndex: 'reg_dedup_pv_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '注册成本', serverIndex: 'adgroup_data.reg_cost_total', dataIndex: 'reg_cost_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '激活注册率', serverIndex: 'adgroup_data.activate_register_rate_total', dataIndex: 'activate_register_rate_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '次日留存人数', serverIndex: 'adgroup_data.mini_game_retention_d1_total', dataIndex: 'mini_game_retention_d1_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '次日留存成本', serverIndex: 'adgroup_data.retention_cost_total', dataIndex: 'retention_cost_total', label: '其他业务(其他指标)', width: 80 },
+            { title: '关键页面次日留存率', serverIndex: 'adgroup_data.app_key_page_retention_rate_total', dataIndex: 'app_key_page_retention_rate_total', label: '其他业务(其他指标)', width: 90 },
+            { title: '小游戏次日留存率', serverIndex: 'adgroup_data.mini_game_retention_d1_rate_total', dataIndex: 'mini_game_retention_d1_rate_total', label: '其他业务(其他指标)', width: 90 },
+            { title: '小游戏次日留存成本', serverIndex: 'adgroup_data.mini_game_retention_d1_cost_total', dataIndex: 'mini_game_retention_d1_cost_total', label: '其他业务(其他指标)', width: 90 }
         ]
     },
     {
@@ -148,6 +157,10 @@ const planAdConfig = [
             { title: '小游戏注册3日广告变现人数(平台上报)', serverIndex: 'adgroup_data.minigame3d_income_uv_total', dataIndex: 'minigame3d_income_uv_total', label: '小游戏(平台上报)', width: 110 },
             { title: '小游戏注册7日广告变现人数(平台上报)', serverIndex: 'adgroup_data.minigame7d_income_uv_total', dataIndex: 'minigame7d_income_uv_total', label: '小游戏(平台上报)', width: 110 },
             { title: '小游戏广告变现人数(平台上报)', serverIndex: 'adgroup_data.mini_game_ad_monetization_users_total', dataIndex: 'mini_game_ad_monetization_users_total', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册次数(平台上报)', serverIndex: 'adgroup_data.reg_pla_pv_total', dataIndex: 'reg_pla_pv_total', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册人数(平台上报+广告主上报)', serverIndex: 'adgroup_data.reg_all_dedup_pv_total', dataIndex: 'reg_all_dedup_pv_total', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册成本(平台上报+广告主上报)', serverIndex: 'adgroup_data.reg_cost_pla_total', dataIndex: 'reg_cost_pla_total', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册率(平台上报+广告主上报)', serverIndex: 'adgroup_data.reg_click_rate_pla_total', dataIndex: 'reg_click_rate_pla_total', label: '小游戏(平台上报)', width: 110 },
         ]
     }
 ]
@@ -384,7 +397,16 @@ const dynamicConfig = [
             { title: '激活首24小时广告变现人数', serverIndex: 'creative_data.ad_paying_users24h_day', dataIndex: 'ad_paying_users24h_day', label: '其他业务(其他指标)', width: 90 },
             { title: '激活首日广告变现人数', serverIndex: 'creative_data.ad_paying_users_d1_day', dataIndex: 'ad_paying_users_d1_day', label: '其他业务(其他指标)', width: 80 },
             { title: '激活3日广告变现人数', serverIndex: 'creative_data.ad_monetization_dedup_active3d_pv_day', dataIndex: 'ad_monetization_dedup_active3d_pv_day', label: '其他业务(其他指标)', width: 80 },
-            { title: '激活7日广告变现人数', serverIndex: 'creative_data.ad_monetization_dedup_active7d_pv_day', dataIndex: 'ad_monetization_dedup_active7d_pv_day', label: '其他业务(其他指标)', width: 80 }
+            { title: '激活7日广告变现人数', serverIndex: 'creative_data.ad_monetization_dedup_active7d_pv_day', dataIndex: 'ad_monetization_dedup_active7d_pv_day', label: '其他业务(其他指标)', width: 80 },
+            { title: '注册次数', serverIndex: 'creative_data.reg_pv_day', dataIndex: 'reg_pv_day', label: '其他业务(其他指标)', width: 80 },
+            { title: '注册人数', serverIndex: 'creative_data.reg_dedup_pv_day', dataIndex: 'reg_dedup_pv_day', label: '其他业务(其他指标)', width: 80 },
+            { title: '注册成本', serverIndex: 'calculate_creative_data.reg_cost', dataIndex: 'reg_cost', label: '其他业务(其他指标)', width: 80 },
+            { title: '激活注册率', serverIndex: 'calculate_creative_data.activate_register_rate', dataIndex: 'activate_register_rate', label: '其他业务(其他指标)', width: 80 },
+            { title: '次日留存人数', serverIndex: 'creative_data.mini_game_retention_d1_day', dataIndex: 'mini_game_retention_d1_day', label: '其他业务(其他指标)', width: 80 },
+            { title: '次日留存成本', serverIndex: 'calculate_creative_data.retention_cost', dataIndex: 'retention_cost', label: '其他业务(其他指标)', width: 80 },
+            { title: '关键页面次日留存率', serverIndex: 'calculate_creative_data.app_key_page_retention_rate', dataIndex: 'app_key_page_retention_rate', label: '其他业务(其他指标)', width: 90 },
+            { title: '小游戏次日留存率', serverIndex: 'calculate_creative_data.mini_game_retention_d1_rate', dataIndex: 'mini_game_retention_d1_rate', label: '其他业务(其他指标)', width: 90 },
+            { title: '小游戏次日留存成本', serverIndex: 'calculate_creative_data.mini_game_retention_d1_cost', dataIndex: 'mini_game_retention_d1_cost', label: '其他业务(其他指标)', width: 90 }
         ]
     },
     {
@@ -403,6 +425,10 @@ const dynamicConfig = [
             { title: '小游戏注册3日广告变现人数(平台上报)', serverIndex: 'creative_data.minigame3d_income_uv_day', dataIndex: 'minigame3d_income_uv_day', label: '小游戏(平台上报)', width: 110 },
             { title: '小游戏注册7日广告变现人数(平台上报)', serverIndex: 'creative_data.minigame7d_income_uv_day', dataIndex: 'minigame7d_income_uv_day', label: '小游戏(平台上报)', width: 110 },
             { title: '小游戏广告变现人数(平台上报)', serverIndex: 'creative_data.mini_game_ad_monetization_users_day', dataIndex: 'mini_game_ad_monetization_users_day', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册次数(平台上报)', serverIndex: 'creative_data.reg_pla_pv_day', dataIndex: 'reg_pla_pv_day', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册人数(平台上报+广告主上报)', serverIndex: 'creative_data.reg_all_dedup_pv_day', dataIndex: 'reg_all_dedup_pv_day', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册成本(平台上报+广告主上报)', serverIndex: 'calculate_creative_data.reg_cost_pla', dataIndex: 'reg_cost_pla', label: '小游戏(平台上报)', width: 110 },
+            { title: '注册率(平台上报+广告主上报)', serverIndex: 'calculate_creative_data.reg_click_rate_pla', dataIndex: 'reg_click_rate_pla', label: '小游戏(平台上报)', width: 110 },
         ]
     }
 ]

+ 159 - 0
src/pages/launchSystemV3/adMonitorListV3/tableDynamicConfig.tsx

@@ -1527,6 +1527,165 @@ function tableDynamicConfig(
                 return <StatisticNull data={b} field='mini_game_ad_monetization_users_day' />
             }
         },
+        {
+            title: '注册次数',
+            dataIndex: 'reg_pv_day',
+            key: 'reg_pv_day',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_pv_day' />
+            }
+        },
+        {
+            title: '注册人数',
+            dataIndex: 'reg_dedup_pv_day',
+            key: 'reg_dedup_pv_day',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_dedup_pv_day' />
+            }
+        },
+        {
+            title: '注册成本',
+            dataIndex: 'reg_cost',
+            key: 'reg_cost',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_cost' />
+            }
+        },
+        {
+            title: '注册次数(平台上报)',
+            dataIndex: 'reg_pla_pv_day',
+            key: 'reg_pla_pv_day',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_pla_pv_day' />
+            }
+        },
+        {
+            title: '注册人数(平台上报+广告主上报)',
+            dataIndex: 'reg_all_dedup_pv_day',
+            key: 'reg_all_dedup_pv_day',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_all_dedup_pv_day' />
+            }
+        },
+        {
+            title: '注册成本(平台上报+广告主上报)',
+            dataIndex: 'reg_cost_pla',
+            key: 'reg_cost_pla',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_cost_pla' />
+            }
+        },
+        {
+            title: '注册率(平台上报+广告主上报)',
+            dataIndex: 'reg_click_rate_pla',
+            key: 'reg_click_rate_pla',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.reg_click_rate_pla !== undefined && b?.reg_click_rate_pla !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '激活注册率',
+            dataIndex: 'activate_register_rate',
+            key: 'activate_register_rate',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.activate_register_rate !== undefined && b?.activate_register_rate !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '次日留存人数',
+            dataIndex: 'mini_game_retention_d1_day',
+            key: 'mini_game_retention_d1_day',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mini_game_retention_d1_day' />
+            }
+        },
+        {
+            title: '次日留存成本',
+            dataIndex: 'retention_cost',
+            key: 'retention_cost',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='retention_cost' />
+            }
+        },
+        {
+            title: '关键页面次日留存率',
+            dataIndex: 'app_key_page_retention_rate',
+            key: 'app_key_page_retention_rate',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.app_key_page_retention_rate !== undefined && b?.app_key_page_retention_rate !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '小游戏次日留存率',
+            dataIndex: 'mini_game_retention_d1_rate',
+            key: 'mini_game_retention_d1_rate',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.mini_game_retention_d1_rate !== undefined && b?.mini_game_retention_d1_rate !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '小游戏次日留存成本',
+            dataIndex: 'mini_game_retention_d1_cost',
+            key: 'mini_game_retention_d1_cost',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mini_game_retention_d1_cost' />
+            }
+        },
     ]
 
     return [

+ 159 - 0
src/pages/launchSystemV3/adMonitorListV3/tablePlanListConfig.tsx

@@ -1402,6 +1402,165 @@ function tablePlanConfig(
                 return <StatisticNull data={b} field='mini_game_ad_monetization_users_total' />
             }
         },
+        {
+            title: '注册次数',
+            dataIndex: 'reg_pv_total',
+            key: 'reg_pv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_pv_total' />
+            }
+        },
+        {
+            title: '注册人数',
+            dataIndex: 'reg_dedup_pv_total',
+            key: 'reg_dedup_pv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_dedup_pv_total' />
+            }
+        },
+        {
+            title: '注册成本',
+            dataIndex: 'reg_cost_total',
+            key: 'reg_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_cost_total' />
+            }
+        },
+        {
+            title: '注册次数(平台上报)',
+            dataIndex: 'reg_pla_pv_total',
+            key: 'reg_pla_pv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_pla_pv_total' />
+            }
+        },
+        {
+            title: '注册人数(平台上报+广告主上报)',
+            dataIndex: 'reg_all_dedup_pv_total',
+            key: 'reg_all_dedup_pv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_all_dedup_pv_total' />
+            }
+        },
+        {
+            title: '注册成本(平台上报+广告主上报)',
+            dataIndex: 'reg_cost_pla_total',
+            key: 'reg_cost_pla_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='reg_cost_pla_total' />
+            }
+        },
+        {
+            title: '注册率(平台上报+广告主上报)',
+            dataIndex: 'reg_click_rate_pla_total',
+            key: 'reg_click_rate_pla_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.reg_click_rate_pla_total !== undefined && b?.reg_click_rate_pla_total !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '激活注册率',
+            dataIndex: 'activate_register_rate_total',
+            key: 'activate_register_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.activate_register_rate_total !== undefined && b?.activate_register_rate_total !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '次日留存人数',
+            dataIndex: 'mini_game_retention_d1_total',
+            key: 'mini_game_retention_d1_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mini_game_retention_d1_total' />
+            }
+        },
+        {
+            title: '次日留存成本',
+            dataIndex: 'retention_cost_total',
+            key: 'retention_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='retention_cost_total' />
+            }
+        },
+        {
+            title: '关键页面次日留存率',
+            dataIndex: 'app_key_page_retention_rate_total',
+            key: 'app_key_page_retention_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.app_key_page_retention_rate_total !== undefined && b?.app_key_page_retention_rate_total !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '小游戏次日留存率',
+            dataIndex: 'mini_game_retention_d1_rate_total',
+            key: 'mini_game_retention_d1_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.mini_game_retention_d1_rate_total !== undefined && b?.mini_game_retention_d1_rate_total !== null) {
+                    return <Statistic value={a ? (a * 100) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '小游戏次日留存成本',
+            dataIndex: 'mini_game_retention_d1_cost_total',
+            key: 'mini_game_retention_d1_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mini_game_retention_d1_cost_total' />
+            }
+        },
     ]
 
     return [

+ 315 - 0
src/pages/launchSystemV3/components/TextAideInput/const.ts

@@ -0,0 +1,315 @@
+// 表情
+export const emojiList = [
+    {
+        text: "[微笑]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_0.png"
+    },
+    {
+        text: "[撇嘴]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_1.png"
+    },
+    {
+        text: "[色]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_2.png"
+    },
+    {
+        text: "[发呆]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_3.png"
+    },
+    {
+        text: "[流泪]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_5.png"
+    },
+    {
+        text: "[害羞]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_6.png"
+    },
+    {
+        text: "[睡]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_8.png"
+    },
+    {
+        text: "[大哭]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_9.png"
+    },
+    {
+        text: "[尴尬]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_10.png"
+    },
+    {
+        text: "[发怒]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_11.png"
+    },
+    {
+        text: "[调皮]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_12.png"
+    },
+    {
+        text: "[呲牙]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_13.png"
+    },
+    {
+        text: "[惊讶]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_14.png"
+    },
+    {
+        text: "[难过]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_15.png"
+    },
+    {
+        text: "[冷汗]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_17.png"
+    },
+    {
+        text: "[抓狂]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_18.png"
+    },
+    {
+        text: "[偷笑]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_20.png"
+    },
+    {
+        text: "[愉快]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_21.png"
+    },
+    {
+        text: "[白眼]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_22.png"
+    },
+    {
+        text: "[傲慢]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_23.png"
+    },
+    {
+        text: "[惊恐]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_26.png"
+    },
+    {
+        text: "[流汗]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_27.png"
+    },
+    {
+        text: "[憨笑]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_28.png"
+    },
+    {
+        text: "[奋斗]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_30.png"
+    },
+    {
+        text: "[疑问]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_32.png"
+    },
+    {
+        text: "[晕]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_34.png"
+    },
+    {
+        text: "[衰]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_36.png"
+    },
+    {
+        text: "[敲打]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_38.png"
+    },
+    {
+        text: "[再见]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_39.png"
+    },
+    {
+        text: "[擦汗]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_40.png"
+    },
+    {
+        text: "[鼓掌]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_42.png"
+    },
+    {
+        text: "[坏笑]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_44.png"
+    },
+    {
+        text: "[左哼哼]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_45.png"
+    },
+    {
+        text: "[右哼哼]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_46.png"
+    },
+    {
+        text: "[哈欠]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_47.png"
+    },
+    {
+        text: "[委屈]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_49.png"
+    },
+    {
+        text: "[快哭了]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_50.png"
+    },
+    {
+        text: "[阴险]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_51.png"
+    },
+    {
+        text: "[亲亲]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_52.png"
+    },
+    {
+        text: "[可怜]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_54.png"
+    },
+    {
+        text: "[西瓜]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_56.png"
+    },
+    {
+        text: "[咖啡]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_60.png"
+    },
+    {
+        text: "[猪头]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_62.png"
+    },
+    {
+        text: "[玫瑰]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_63.png"
+    },
+    {
+        text: "[嘴唇]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_65.png"
+    },
+    {
+        text: "[爱心]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_66.png"
+    },
+    {
+        text: "[蛋糕]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_68.png"
+    },
+    {
+        text: "[月亮]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_75.png"
+    },
+    {
+        text: "[太阳]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_76.png"
+    },
+    {
+        text: "[拥抱]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_78.png"
+    },
+    {
+        text: "[强]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_79.png"
+    },
+    {
+        text: "[握手]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_81.png"
+    },
+    {
+        text: "[胜利]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_82.png"
+    },
+    {
+        text: "[抱拳]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_83.png"
+    },
+    {
+        text: "[勾引]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_84.png"
+    },
+    {
+        text: "[拳头]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_85.png"
+    },
+    {
+        text: "[OK]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_89.png"
+    },
+    {
+        text: "[跳跳]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_92.png"
+    },
+    {
+        text: "[发抖]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_93.png"
+    },
+    {
+        text: "[怄火]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_94.png"
+    },
+    {
+        text: "[转圈]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/smiley_95.png"
+    },
+    {
+        text: "[嘿哈]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_04.png"
+    },
+    {
+        text: "[捂脸]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_05.png"
+    },
+    {
+        text: "[奸笑]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_02.png"
+    },
+    {
+        text: "[机智]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_06.png"
+    },
+    {
+        text: "[皱眉]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_12.png"
+    },
+    {
+        text: "[耶]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_11.png"
+    },
+    {
+        text: "[加油]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_20.png"
+    },
+    {
+        text: "[汗]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_21.png"
+    },
+    {
+        text: "[天啊]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_22.png"
+    },
+    {
+        text: "[社会社会]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_23.png"
+    },
+    {
+        text: "[旺柴]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_24.png"
+    },
+    {
+        text: "[好的]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_25.png"
+    },
+    {
+        text: "[加油加油]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_26.png"
+    },
+    {
+        text: "[哇]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_27.png"
+    },
+    {
+        text: "[红包]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_09.png"
+    },
+    {
+        text: "[發]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_16.png"
+    },
+    {
+        text: "[福]",
+        url: "https://wxa.wxs.qq.com/wxad-design/emojis/2_15.png"
+    }
+]

+ 61 - 18
src/pages/launchSystemV3/components/TextAideInput/index.less

@@ -1,20 +1,63 @@
 .textAideInputPopover {
-  .ant-popover-inner-content {
-    padding: 0;
-    cursor: pointer;
-  }
-  .crt {
-    display: inline-flex;
-    align-items: center;
-    width: auto;
-    margin-left: 8px;
-    padding: 1px 4px;
-    height: 16px;
-    border-radius: 3px;
-    font-size: 12px;
-    color: #fff;
-    border: 1px solid #1890ff;
-    background-color: #1890ff;
-    line-height: normal;
-  }
+    .ant-popover-inner-content {
+        padding: 0;
+        cursor: pointer;
+    }
+
+    .crt {
+        display: inline-flex;
+        align-items: center;
+        width: auto;
+        margin-left: 8px;
+        padding: 1px 4px;
+        height: 16px;
+        border-radius: 3px;
+        font-size: 12px;
+        color: #fff;
+        border: 1px solid #1890ff;
+        background-color: #1890ff;
+        line-height: normal;
+    }
 }
+
+.emoji {
+    .ant-popover-inner-content {
+        padding: 2px 0px;
+    }
+}
+
+.emoji-list-scroll {
+    width: 348px;
+    height: 96px;
+    margin: 7px;
+    box-sizing: border-box;
+    overflow-x: hidden;
+    overflow-y: auto;
+    scrollbar-width: thin;
+}
+
+.emoji-list {
+    display: grid;
+    grid-template-columns: repeat(10, 32px);
+    margin-block-start: 0;
+    margin-block-end: 0;
+    padding-inline-start: 0;
+
+    >li {
+        height: 32px;
+        border-radius: 6px;
+
+        &:hover {
+            background: #f2f4f7;
+        }
+    }
+
+    img {
+        padding: 4px;
+        max-width: 32px;
+        max-height: 32px;
+        cursor: pointer;
+        vertical-align: middle;
+        image-rendering: -webkit-optimize-contrast;
+    }
+}

+ 73 - 26
src/pages/launchSystemV3/components/TextAideInput/index.tsx

@@ -1,10 +1,12 @@
 import { useAjax } from "@/Hook/useAjax"
 import { txtLength } from "@/utils/utils";
-import { Input, List, Popover } from "antd";
-import React, { useEffect } from "react"
+import { Button, Input, InputRef, List, Popover } from "antd";
+import React, { useEffect, useRef } from "react"
 import { useState } from "react";
 import './index.less'
 import { getTextApi } from "@/services/adqV3/global";
+import { SmileOutlined } from "@ant-design/icons";
+import { emojiList } from "./const";
 
 
 interface Props {
@@ -27,6 +29,9 @@ const TextAideInput: React.FC<Props> = (props) => {
     const { value, onChange, style, placeholder, maxTextLength = 10, isShowAjax = true } = props
     const [text, setText] = useState<any>(value)
     const [descriptionShow, setDescriptionshow] = useState(false)
+    const [cursorPosition, setCursorPosition] = useState<number | null>(null);
+    const inputRef = useRef<InputRef>(null);  // 获取 input DOM 元素
+    const [emojiOpen, setEmojiOpen] = useState<boolean>(false)
 
     const getTextLsit = useAjax((params) => getTextApi(params))
     /************************/
@@ -40,12 +45,34 @@ const TextAideInput: React.FC<Props> = (props) => {
         getTextLsit.run({ keyword, maxTextLength })
     }
 
+    const insertTextAtCursor = (emoji: string) => {
+        const inputElement = inputRef.current;
+        if (inputElement) {
+            if (cursorPosition !== null) {
+                const newValue = text.slice(0, cursorPosition) + emoji + text.slice(cursorPosition);
+                setText(newValue);
+                onChange?.(newValue)
+            } else {
+                const newValue = text + emoji
+                setText(newValue);
+                onChange?.(newValue)
+            }
+            const newCursorPosition = (cursorPosition || text.length) + emoji.length;
+            setCursorPosition(newCursorPosition);
+            // 等待状态更新后再设置光标位置
+            setTimeout(() => {
+                inputElement.setSelectionRange(newCursorPosition, newCursorPosition);
+                inputElement.focus();
+            }, 0);
+        }
+    };
+
     return <div style={{ display: 'inline-flex', alignItems: 'center', columnGap: 5 }}>
         <Popover
             placement="topLeft"
             overlayClassName="textAideInputPopover"
             style={{ minWidth: 600 }}
-            visible={descriptionShow}
+            open={descriptionShow}
             content={<List
                 loading={getTextLsit?.loading}
                 size="small"
@@ -58,29 +85,49 @@ const TextAideInput: React.FC<Props> = (props) => {
                 }}><span >{item.text}{item.tag && <span className="crt">{'CTR 高'}</span>}</span></List.Item>}
             />}
         >
-            <Input
-                placeholder={placeholder}
-                style={style}
-                value={text}
-                onFocus={() => {
-                    if (isShowAjax) {
-                        setDescriptionshow(true)
-                        textList(value)
-                    } 
-                }}
-                onBlur={() => {
-                    setTimeout(() => { setDescriptionshow(false) }, 500)
-                }}
-                onChange={(e) => {
-                    let value = e.target.value
-                    setText(value)
-                    if (isShowAjax) {
-                        textList(value)
-                    }
-                    onChange && onChange(value)
-                }}
-                allowClear
-            />
+
+            <Input.Group compact>
+                <Input
+                    ref={inputRef}
+                    placeholder={placeholder}
+                    style={style}
+                    value={text}
+                    onFocus={() => {
+                        if (isShowAjax) {
+                            setDescriptionshow(true)
+                            textList(value)
+                        }
+                    }}
+                    onBlur={(e) => {
+                        setCursorPosition(e.target.selectionStart)
+                        if (!emojiOpen) {
+                            setTimeout(() => { setDescriptionshow(false) }, 0)
+                        }
+                    }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        setText(value)
+                        if (isShowAjax) {
+                            textList(value)
+                        }
+                        onChange && onChange(value)
+                    }}
+                    allowClear
+                />
+                <Popover
+                    placement="bottomRight"
+                    overlayClassName="emoji"
+                    content={<div className="emoji-list-scroll">
+                        <ul className="emoji-list">{emojiList.map(item => <li key={item.text} onClick={() => insertTextAtCursor(item.text)}><img src={item.url} alt={item.text} /></li>)}</ul>
+                    </div>}
+                    trigger="click"
+                    onOpenChange={(e) => {
+                        setEmojiOpen(e)
+                    }}
+                >
+                    <Button><SmileOutlined /></Button>
+                </Popover>
+            </Input.Group>
         </Popover>
         <span>{`${txtLength(text)}/${maxTextLength}`}</span>
     </div>

+ 111 - 0
src/pages/launchSystemV3/material/cloudNew/copyFile.tsx

@@ -0,0 +1,111 @@
+import { useAjax } from "@/Hook/useAjax"
+import { copyMaterialApi, getFolderListApi } from "@/services/adqV3/cloudNew"
+import { FolderOpenOutlined, FolderOutlined } from "@ant-design/icons"
+import { Card, message, Modal, Spin } from "antd"
+import Tree, { DataNode, EventDataNode } from "antd/lib/tree"
+import React, { useEffect, useState } from "react"
+import { updateTreeData } from "./const"
+import '../../tencentAdPutIn/index.less'
+import { CheckboxValueType } from "antd/lib/checkbox/Group"
+
+interface Props {
+    checkedList: CheckboxValueType[]
+    userId: string,
+    visible?: boolean
+    onChange?: () => void
+    onClose?: () => void
+}
+const CopyFile: React.FC<Props> = ({ checkedList, visible, userId, onChange, onClose }) => {
+
+    /**********************************/
+    const [treeData, setTreeData] = useState<DataNode[]>([]);
+    const [folderLoading, setFolderLoading] = useState<boolean>(false)
+    const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([])
+
+    const getFolderList = useAjax((params) => getFolderListApi(params))
+    const copyMaterial = useAjax((params) => copyMaterialApi(params))
+    /**********************************/
+
+    useEffect(() => {
+        handleGetFolder()
+    }, [])
+
+    // 获取顶级文件夹列表
+    const handleGetFolder = () => {
+        setFolderLoading(() => true)
+        getFolderList.run({}).then(res => {
+            setFolderLoading(() => false)
+            setTreeData(() => res?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                title: item.folderName,
+                key: '0-' + item.id,
+                disabled: userId !== item.createBy.toString(),
+                icon: ({ selected }: any) => (selected ? <FolderOpenOutlined style={{ color: 'rgb(255, 202, 40)' }} /> : <FolderOutlined style={{ color: 'rgb(255, 202, 40)' }} />),
+            })) || [])
+        }).catch(() => setFolderLoading(false))
+    }
+
+    // 下级目录
+    const handleUpdateFolder = (parentIdStr: string) => {
+        const parentIdArr = parentIdStr.split('-')
+        const parentIdArrLength = parentIdArr.length
+        const parentId = Number(parentIdArr[parentIdArrLength - 1])
+        return getFolderListApi({ parentId }).then(res => {
+            setTreeData(origin =>
+                updateTreeData(origin, parentIdStr, res?.data?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                    title: item.folderName,
+                    key: parentIdStr + '-' + item.id,
+                    disabled: userId !== item.createBy.toString(),
+                    icon: ({ selected }: any) => (selected ? <FolderOpenOutlined /> : <FolderOutlined />)
+                })) || []),
+            );
+        })
+    }
+
+    const handleOk = () => {
+        if (selectedKeys?.length) {
+            const parentIdArr = (selectedKeys[0] as string).split('-')
+            const parentIdArrLength = parentIdArr.length
+            const parentId = Number(parentIdArr[parentIdArrLength - 1])
+            copyMaterial.run({ folderId: parentId, materialIds: checkedList }).then(res => {
+                if (res) {
+                    message.success('移动成功')
+                    onChange?.()
+                }
+            })
+        } else {
+            message.error('请选择文件夹')
+        }
+    }
+
+    return <Modal
+        title={<strong>复制素材到</strong>}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        className="modalResetCss"
+        bodyStyle={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '10px' }}
+        maskClosable={false}
+        okText="复制"
+        confirmLoading={copyMaterial.loading}
+    >
+        <Card className="cardResetCss" title={<strong style={{ fontSize: 12 }}>文件夹列表</strong>}>
+            <Spin spinning={folderLoading}>
+                <Tree
+                    showIcon
+                    blockNode={true}
+                    selectedKeys={selectedKeys}
+                    loadData={(treeNode: EventDataNode<DataNode>) => {
+                        return new Promise<void>(async (resolve) => {
+                            await handleUpdateFolder(treeNode.key as string)
+                            resolve()
+                        })
+                    }}
+                    onSelect={(keys) => setSelectedKeys(keys)}
+                    treeData={treeData}
+                />
+            </Spin>
+        </Card>
+    </Modal>
+}
+
+export default React.memo(CopyFile)

+ 39 - 3
src/pages/launchSystemV3/material/cloudNew/folder.tsx

@@ -1,8 +1,11 @@
 import React, { forwardRef, Ref, useContext, useImperativeHandle } from "react"
 import style from './index.less'
-import { Button, Empty, Spin, Tree } from "antd"
+import { Button, Empty, message, Spin, Tree } from "antd"
 import { DataNode, DirectoryTreeProps, EventDataNode } from "antd/lib/tree";
 import { DispatchCloudNew } from ".";
+import { useDrop } from "ahooks";
+import { useAjax } from "@/Hook/useAjax";
+import { moveMaterialApi } from "@/services/adqV3/cloudNew";
 
 interface FolderRef {
     folderRefresh: () => void
@@ -10,15 +13,47 @@ interface FolderRef {
 interface Props {
     loading: boolean
     onLoadData: (treeNode: EventDataNode<DataNode>) => Promise<void>
+    onRefreshMaterial?: () => void
 }
+
+/**
+ * 自定义头部
+ * @param param0 
+ * @returns 
+ */
+const TreeTitleRender: React.FC<{ nodeData: any, dragging: any, onChange: () => void }> = ({ nodeData, dragging, onChange }) => {
+
+    /***************************/
+    const moveMaterial = useAjax((params) => moveMaterialApi(params))
+    /***************************/
+
+    const [props, { isHovering }] = useDrop({
+        onDom: (content: any) => {
+            const hide = message.loading('移动中。。。', 0)
+            const parentIdArr = nodeData.key.split('-')
+            const parentIdArrLength = parentIdArr.length
+            const parentId = Number(parentIdArr[parentIdArrLength - 1])
+            moveMaterial.run({ folderId: parentId, materialIds: [content.id] }).then(res => {
+                hide()
+                if (res) {
+                    message.success('移动成功')
+                    onChange?.()
+                }
+            }).catch(() => hide())
+        },
+    });
+
+    return <span {...props} style={dragging ? { border: '1px dashed #f3c6c6', borderColor: isHovering ? '#1890ff' : '#f3c6c6' } : {}}>{nodeData.title}</span>
+}
+
 /**
  * 文件夹
  * @returns 
  */
-const Folder = forwardRef(({ loading, onLoadData }: Props, ref: Ref<FolderRef>) => {
+const Folder = forwardRef(({ loading, onLoadData, onRefreshMaterial }: Props, ref: Ref<FolderRef>) => {
 
     /******************************/
-    const { treeData, setSelectedKeys, selectedKeys, setMaterialParams, setBreadcrumdData, findParentKeys, loadedKeys, setBatchFolderVisible, handleUpdateFolder, expandedKeys, setExpandedKeys } = useContext(DispatchCloudNew)!;
+    const { treeData, setSelectedKeys, selectedKeys, setMaterialParams, setBreadcrumdData, findParentKeys, loadedKeys, setBatchFolderVisible, handleUpdateFolder, expandedKeys, setExpandedKeys, dragging } = useContext(DispatchCloudNew)!;
 
     /******************************/
 
@@ -75,6 +110,7 @@ const Folder = forwardRef(({ loading, onLoadData }: Props, ref: Ref<FolderRef>)
                     loadedKeys={loadedKeys}
                     onExpand={onExpand}
                     expandedKeys={expandedKeys}
+                    titleRender={(nodeData: any) => nodeData.isSelf ? <TreeTitleRender nodeData={nodeData} dragging={dragging} onChange={() => onRefreshMaterial?.()}/> : <span>{nodeData.title}</span>}
                 />
                 {!(treeData?.length > 0) && (loading ? <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 200 }} ></div> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无文件夹" />)}
             </Spin>

+ 13 - 0
src/pages/launchSystemV3/material/cloudNew/index.less

@@ -132,6 +132,7 @@
         display: flex;
         justify-content: center;
         align-items: center;
+        transition: all 0.2s;
 
         .checkbox {
             position: absolute;
@@ -188,6 +189,15 @@
             transition: opacity 0.2s;
         }
 
+        .move {
+            position: absolute;
+            top: 6px;
+            right: 10px;
+            cursor: grab;
+            color: rgb(153, 153, 153);
+            opacity: 0;
+        }
+
         &:hover {
             .file_info>div {
                 opacity: 0.25;
@@ -195,6 +205,9 @@
             .imgPreview {
                 opacity: 1;
             }
+            .move {
+                opacity: 1;
+            }
         }
     }
 

+ 12 - 3
src/pages/launchSystemV3/material/cloudNew/index.tsx

@@ -11,6 +11,7 @@ import ManageFolder from "./manageFolder";
 import { ExclamationCircleOutlined, FolderOpenOutlined, FolderOutlined } from "@ant-design/icons";
 import { updateTreeData } from "./const";
 import Search from "./search";
+import { useModel } from "umi";
 
 export const DispatchCloudNew = React.createContext<CLOUDNEW.CloudNewReactContent | null>(null);
 
@@ -21,6 +22,7 @@ export const DispatchCloudNew = React.createContext<CLOUDNEW.CloudNewReactConten
 const CloudNew: React.FC = () => {
 
     /**********************************/
+    const { initialState } = useModel('@@initialState');
     const refMaterial = useRef(null);
     const [treeData, setTreeData] = useState<DataNode[]>([]);
     const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
@@ -35,6 +37,7 @@ const CloudNew: React.FC = () => {
     const [batchFolderVisible, setBatchFolderVisible] = useState<boolean>(false) // 批量操作文件夹控制
     const [handleType, setHandleType] = useState<'folder' | 'file'>('file') // 操作类型
     const [materialParams, setMaterialParams] = useState<CLOUDNEW.GetMaterialListProps>({ pageNum: 1, pageSize: 30 })
+    const [dragging, setDragging] = useState<any>()
 
     const getFolderList = useAjax((params) => getFolderListApi(params))
     const delFolderAjax = useAjax((params) => delFolderApi(params))
@@ -59,9 +62,10 @@ const CloudNew: React.FC = () => {
         setLoadedKeys(() => [])
         getFolderList.run({ folderName: queryFormFolder?.folderName }).then(res => {
             setFolderLoading(() => false)
-            setTreeData(() => res?.map((item: { folderName: any; id: any; }) => ({
+            setTreeData(() => res?.map((item: { folderName: any; id: any; createBy: number }) => ({
                 title: item.folderName,
                 key: '0-' + item.id,
+                isSelf: item.createBy?.toString() === initialState?.currentUser?.userId?.toString(),
                 icon: ({ selected }: any) => (selected ? <FolderOpenOutlined style={{ color: 'rgb(255, 202, 40)' }} /> : <FolderOutlined style={{ color: 'rgb(255, 202, 40)' }} />),
             })) || [])
         }).catch(() => setFolderLoading(false))
@@ -76,9 +80,10 @@ const CloudNew: React.FC = () => {
             setLoadedKeys(loadedkeys => [...loadedkeys, parentIdStr].filter(item => !item.includes(parentIdStr + '-')))
             setExpandedKeys(expandedKeys => [...expandedKeys, parentIdStr])
             setTreeData(origin =>
-                updateTreeData(origin, parentIdStr, res?.data?.map((item: { folderName: any; id: any; }) => ({
+                updateTreeData(origin, parentIdStr, res?.data?.map((item: { folderName: any; id: any; createBy: number }) => ({
                     title: item.folderName,
                     key: parentIdStr + '-' + item.id,
+                    isSelf: item.createBy?.toString() === initialState?.currentUser?.userId?.toString(),
                     icon: ({ selected }: any) => (selected ? <FolderOpenOutlined /> : <FolderOutlined />)
                 })) || []),
             );
@@ -147,7 +152,8 @@ const CloudNew: React.FC = () => {
                 folderCreateBy, setFolderCreateBy,
                 batchFolderVisible, setBatchFolderVisible,
                 handleType, setHandleType,
-                materialParams, setMaterialParams
+                materialParams, setMaterialParams,
+                dragging, setDragging
             }}>
                 {/* 搜索 */}
                 <Search
@@ -170,6 +176,9 @@ const CloudNew: React.FC = () => {
                                     resolve()
                                 })
                             }}
+                            onRefreshMaterial={() => {
+                                (refMaterial.current as any)?.onRefreshMaterial()
+                            }}
                         />
                         {/* 素材列表 */}
                         <Material

+ 55 - 7
src/pages/launchSystemV3/material/cloudNew/material.tsx

@@ -4,7 +4,7 @@ import { Breadcrumb, Button, Card, Checkbox, Dropdown, Empty, message, Modal, Pa
 import { DispatchCloudNew } from "."
 import { deleteBatchApi, delMaterialApi, getFolderListApi, getMaterialListApi } from "@/services/adqV3/cloudNew"
 import { useAjax } from "@/Hook/useAjax"
-import { useSize } from "ahooks"
+import { useDrag, useSize } from "ahooks"
 import './global.less'
 import { useModel } from "umi"
 import { CheckboxValueType } from "antd/lib/checkbox/Group"
@@ -13,12 +13,13 @@ import { ItemType } from "antd/lib/menu/hooks/useItems"
 import UploadFile from "./uploadFile"
 import UpdateCreate from "./updateCreate"
 import { formatSecondsToTime, getVideoImgUrl } from "@/utils/utils"
-import { ExclamationCircleOutlined } from "@ant-design/icons"
+import { ExclamationCircleOutlined, MenuOutlined } from "@ant-design/icons"
 import UpdateFile from "./updateFile"
 import Details from "./details"
 import useFileDrop from "@/Hook/useFileDrop"
 import UploadsTable from "./uploadsTable"
 import Lazyimg from "react-lazyimg-component"
+import CopyFile from "./copyFile"
 const { Text } = Typography;
 
 interface Props {
@@ -34,6 +35,7 @@ interface MaterialRef {
         searchType: "file" | "folder";
         keyword?: string;
     }) => void
+    onRefreshMaterial: () => void
 }
 /**
  * 素材列表
@@ -43,7 +45,7 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
 
     /********************************/
     const { initialState } = useModel('@@initialState');
-    const { breadcrumdData, setSelectedKeys, setBreadcrumdData, setMaterialParams, materialParams, selectedKeys, treeData, folderCreateBy, findParentKeys, loadedKeys, handleUpdateFolder, batchFolderVisible, setBatchFolderVisible, handleType, setHandleType } = useContext(DispatchCloudNew)!;
+    const { breadcrumdData, setSelectedKeys, setBreadcrumdData, setMaterialParams, materialParams, selectedKeys, treeData, folderCreateBy, findParentKeys, loadedKeys, handleUpdateFolder, batchFolderVisible, setBatchFolderVisible, handleType, setHandleType, setDragging, dragging } = useContext(DispatchCloudNew)!;
     const ref = useRef<HTMLDivElement>(null);
     const size = useSize(ref);
     const [folderList, setFolderList] = useState<{ id: number, folderName: string, createBy: number, description?: string }[]>([])
@@ -59,6 +61,7 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
     const [updateOwnerData, setUpdateOwnerData] = useState<{ visible?: boolean, folderId: number }>({ folderId: 0 })
     const [updateFileData, setUpdateFileData] = useState<{ visible?: boolean, initialValues: any }>({ visible: false, initialValues: {} })
     const [detailsData, setDetailsData] = useState<{ visible?: boolean, data: any }>({ visible: false, data: {} })
+    const [copyVisible, setCopyVisible] = useState<boolean>(false)
 
     const getFolderList = useAjax((params) => getFolderListApi(params))
     const delMaterial = useAjax((params) => delMaterialApi(params))
@@ -74,6 +77,9 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
         search(value) {
             setHandleType('file')
             setMaterialParams(data => ({ ...data, ...value, pageNum: 1 }))
+        },
+        onRefreshMaterial() {
+            getMaterialList.refresh()
         }
     }));
 
@@ -257,9 +263,23 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
     const [fileList, setFileList] = useState<FileList>()
     const [uploadsVisible, setUploadsVisible] = useState<boolean>(false)
     const { isDragOver, dropAreaProps } = useFileDrop((fileList) => {
-        console.log('fileList--->', fileList)
-        setFileList(fileList)
-        setUploadsVisible(true)
+        if (!dragging) {
+            setFileList(fileList)
+            setUploadsVisible(true)
+        }
+    });
+
+    const getDragProps = useDrag({
+        onDragStart: (data, e) => {
+            const dragImage = new Image();
+            dragImage.width = 50
+            dragImage.src = data.materialType === 'image' ? data.ossUrl : getVideoImgUrl(data.ossUrl);
+            e.dataTransfer.setDragImage((e.target as any).parentNode.querySelector('.lazy') || dragImage, 0, 0);
+            setDragging(data);
+        },
+        onDragEnd: () => {
+            setDragging(null);
+        },
     });
 
     return <div className={style.material}>
@@ -316,6 +336,13 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
                     setMoveVisible(true)
                     setMoveType(batchType as any)
                 }}>{batchType === 'folder' ? '移动文件夹' : '移动素材'}</Button>
+                {batchType === 'file' && <Button
+                    disabled={checkedFolderList?.length === 0}
+                    onClick={() => {
+                        setCheckedList(checkedFolderList)
+                        setCopyVisible(true)
+                    }}
+                >复制素材到</Button>}
                 {batchType === 'file' && <Popconfirm
                     title="确定删除?"
                     onConfirm={() => {
@@ -359,7 +386,7 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
         <div className={`${style.content} content_global`}>
             <Spin spinning={getFolderList.loading || getMaterialList.loading}>
                 <div className={style.content_scroll} ref={ref} {...dropAreaProps}>
-                    {isDragOver && <div className={`${style.placeholder} ${isDragOver ? style.dragOver : ''}`}><span>拖动文件到这里</span></div>}
+                    {(isDragOver && !dragging) && <div className={`${style.placeholder} ${isDragOver ? style.dragOver : ''}`}><span>拖动文件到这里</span></div>}
                     {(isShowFolder() && folderList.length > 0) || (!isShowFolder() && getMaterialList?.data?.records?.length > 0) ? <Checkbox.Group value={checkedFolderList} style={{ width: '100%' }} onChange={onCheckboxChange}>
                         <div className={style.content_scroll_div}>
                             {isShowFolder() ? folderList.map((item) => <div key={item.id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 200 }}>
@@ -417,6 +444,10 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
                                             className={`${style.coverImg} lazy`}
                                             animateClassName={['transition-enter', 'transition-enter-active']}
                                         />
+                                        {item.createBy?.toString() === initialState?.currentUser?.userId?.toString() && <div
+                                            className={style.move}
+                                            {...getDragProps(item)}
+                                        ><MenuOutlined /></div>}
                                     </div>}
                                     onClick={() => { setDetailsData({ visible: true, data: item }) }}
                                 >
@@ -484,6 +515,23 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
             }}
         />}
 
+        {/* 复制 素材 */}
+        {copyVisible && <CopyFile
+            checkedList={checkedList}
+            userId={initialState?.currentUser?.userId?.toString() || ''}
+            visible={copyVisible}
+            onClose={() => {
+                setCopyVisible(false)
+                cancelSelect()
+            }}
+            onChange={() => {
+                setCopyVisible(false)
+                getMaterialList.refresh()
+                cancelSelect()
+                setBatchFolderVisible(false)
+            }}
+        />}
+
         {/* 上传文件 */}
         {uploadVisible && <UploadFile
             folderId={breadcrumdData[breadcrumdData.length - 1].key}

+ 1 - 1
src/pages/launchSystemV3/material/tencent/const.ts

@@ -2,7 +2,7 @@
 /** 选择素材 展示字段 */
 export const showFieldList = [
     { label: '创建时间', value: 'created_time', field: 'created_time' },
-    { label: '消耗', value: 'material_data.cost', field: 'cost' },
+    { label: '消耗(元)', value: 'material_data.cost', field: 'cost' },
     { label: '曝光量', value: 'material_data.view_count', field: 'view_count' },
     { label: '点击量', value: 'material_data.valid_click_count', field: 'valid_click_count' },
     { label: '公众号关注人数(点击归因)', value: 'material_data.from_follow_by_click_uv', field: 'from_follow_by_click_uv' },

+ 32 - 1
src/pages/launchSystemV3/material/tencent/search.tsx

@@ -1,4 +1,4 @@
-import { Button, Card, Col, Form, Row, Select, Space } from "antd"
+import { Button, Card, Col, Form, Input, InputNumber, Row, Select, Space } from "antd"
 import React, { useEffect } from "react"
 import { getUserAllApi } from "@/services/operating/account";
 import { useAjax } from "@/Hook/useAjax";
@@ -38,6 +38,8 @@ const Search: React.FC<Props> = ({ onSearch }) => {
             } 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 ('sizeQueries' === key) {
+                params[key] = [value]
             } else {
                 params[key] = value
             }
@@ -56,6 +58,11 @@ const Search: React.FC<Props> = ({ onSearch }) => {
             colon={false}
             form={form}
             onFinish={handleOk}
+            initialValues={{
+                sizeQueries: {
+                    relation: '='
+                }
+            }}
         >
             <Row gutter={[0, 6]}>
                 <Col><Form.Item name="materialType">
@@ -72,6 +79,30 @@ const Search: React.FC<Props> = ({ onSearch }) => {
                         ]}
                     />
                 </Form.Item></Col>
+                <Col><Form.Item name={['sizeQueries', 'width']}>
+                    <InputNumber placeholder="素材宽" />
+                </Form.Item></Col>
+                <Col><Form.Item name={['sizeQueries', 'height']}>
+                    <InputNumber placeholder="素材高" />
+                </Form.Item></Col>
+                <Col>
+                    <Form.Item name={['sizeQueries', 'relation']}>
+                        <Select
+                            placeholder="运算符"
+                            filterOption={(input, option) =>
+                                ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                            }
+                            style={{ width: 100 }}
+                            options={[
+                                { label: '小于', value: '<' },
+                                { label: '小于等于', value: '<=' },
+                                { label: '等于', value: '=' },
+                                { label: '大于', value: '>' },
+                                { label: '大于等于', value: '>=' },
+                            ]}
+                        />
+                    </Form.Item>
+                </Col>
                 {/* <Col><Form.Item name={'sysUserIds'}>
                     <Select
                         placeholder="投手"

+ 2 - 0
src/pages/launchSystemV3/material/typings.d.ts

@@ -22,6 +22,8 @@ declare namespace CLOUDNEW {
         setHandleType: React.Dispatch<React.SetStateAction<"folder" | "file">>
         materialParams: GetMaterialListProps
         setMaterialParams: React.Dispatch<React.SetStateAction<GetMaterialListProps>>
+        dragging: any
+        setDragging: React.Dispatch<any>
     }
     interface SearchProps {
         searchType: "file" | "folder";

+ 40 - 0
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/delPage.tsx

@@ -0,0 +1,40 @@
+import { useAjax } from "@/Hook/useAjax"
+import { delPageApi } from "@/services/adqV3/global"
+import { Button, message, Popconfirm } from "antd"
+import React from "react"
+
+interface Props {
+    accountId: number,
+    pageIds: number[],
+    onChange?: () => void
+}
+
+/**
+ * 删除落地页
+ * @param param0 
+ * @returns 
+ */
+const DelPage: React.FC<Props> = ({ accountId, pageIds, onChange }) => {
+
+    /**********************************/
+    const delPage = useAjax((params) => delPageApi(params))
+    /**********************************/
+
+    const handleDel = () => {
+        delPage.run({ accountId, pageIds }).then(res => {
+            if (res?.length === 0) {
+                message.success('删除成功,结果可能会延迟几分钟返回')
+                onChange?.()
+            }
+        })
+    }
+
+    return <Popconfirm
+        title="确定删除?"
+        onConfirm={() => handleDel?.()}
+    >
+        <Button style={{ fontSize: 12, color: 'red', padding: 0 }} danger type="link" loading={delPage.loading}>删除</Button>
+    </Popconfirm>
+}
+
+export default React.memo(DelPage)

+ 1 - 1
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/index.tsx

@@ -107,7 +107,7 @@ const WechatCanvasPage: React.FC = () => {
         </div>}
     >
         <Table
-            columns={columns(handleCopy)}
+            columns={columns(handleCopy, () => getAdqLandingPageList.refresh(), queryParamsNew.accountId)}
             dataSource={getAdqLandingPageList.data?.records}
             size="small"
             loading={getAdqLandingPageList?.loading || getAllUserAccount.loading}

+ 9 - 5
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/tableConfig.tsx

@@ -1,9 +1,10 @@
 import { PageStatusEnum } from "@/services/launchAdq/enum"
-import { Badge, TableProps } from "antd"
+import { Badge, Space, TableProps } from "antd"
 import React from "react"
 import QrCode from "../../components/QrCode"
+import DelPage from "./delPage"
 
-let columns = (handleCopy: (data: any) => void): TableProps<any>['columns'] => {
+let columns = (handleCopy: (data: any) => void, refresh?: () => void, accountId?: number): TableProps<any>['columns'] => {
 
     return [
         {
@@ -11,13 +12,16 @@ let columns = (handleCopy: (data: any) => void): TableProps<any>['columns'] => {
             dataIndex: 'cz',
             key: 'cz',
             align: 'center',
-            width: 80,
+            width: 120,
             render(_, record) {
                 let { canvasType } = record
                 if (canvasType === 'COMMON_PAGE') {
                     return '--'
                 }
-                return <a style={{ fontSize: 12 }} onClick={() => handleCopy(record)}>批量复制</a>
+                return <Space>
+                    <a style={{ fontSize: 12 }} onClick={() => handleCopy(record)}>批量复制</a>
+                    {record.ownerUid === accountId && <DelPage accountId={record.ownerUid} pageIds={[record.pageId]} onChange={refresh}/>}
+                </Space>
             }
         },
         {
@@ -46,7 +50,7 @@ let columns = (handleCopy: (data: any) => void): TableProps<any>['columns'] => {
             width: 60,
             align: 'center',
             render(value) {
-                return <QrCode url={value}/>
+                return <QrCode url={value} />
             }
         },
         {

+ 1 - 1
src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/index.tsx

@@ -40,7 +40,7 @@ const MaterialText: React.FC = () => {
         <div className={style.detail}>
             <div className={style.detail_body}>
                 <Title level={5} style={{ fontSize: 12 }} ellipsis>{dynamicCreativesTextDTOS?.type === 0 ? '全部相同' : dynamicCreativesTextDTOS?.type === 1 ? '按创意组一一对应' : dynamicCreativesTextDTOS?.type === 2 ? '按文案顺序分配' : dynamicCreativesTextDTOS?.type === 3 ? '先分配创意组,文案后叉乘': dynamicCreativesTextDTOS?.type === 4 ? '创意组和文案叉乘打乱后分配' : null}</Title>
-                {dynamicCreativesTextDTOS?.dynamicCreativesTextDetailDTOList?.map((item: { [x: string]: (boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined)[]; }, index: number) => {
+                {dynamicCreativesTextDTOS?.dynamicCreativesTextDetailDTOList?.map((item: { [x: string]: (boolean | React.ReactPortal | null | undefined)[]; }, index: number) => {
                     if (item) {
                         let keys = Object.keys(item)
                         return <div key={index}>

+ 9 - 5
src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx

@@ -1,5 +1,5 @@
 import TextAideInput from "@/pages/launchSystemV3/components/TextAideInput"
-import { txtLength } from "@/utils/utils"
+import { extractAndFilterBracketsContent, txtLength } from "@/utils/utils"
 import { Button, Card, Form, Modal, Popconfirm, Radio, Space, message } from "antd"
 import React, { useEffect, useState } from "react"
 import { TextTypeList } from "../../const"
@@ -223,13 +223,17 @@ const NewText: React.FC<Props> = ({ visible, onClose, onChange, value, textData,
                                                     </Space>}
                                                     style={{ marginBottom: 0, width: '100%' }}
                                                     rules={[{
-                                                        required: item.required, message: '请输入正确的' + item.label, validator: (rule, value) => {
+                                                        required: item.required, validator: (rule, value) => {
                                                             if (!value) {
-                                                                return Promise.reject()
+                                                                return Promise.reject('请输入正确的' + item.label)
                                                             } else if (!value.match(RegExp(item.restriction.textRestriction.textPattern))) {
-                                                                return Promise.reject()
+                                                                return Promise.reject('请输入正确的' + item.label)
                                                             } else if (txtLength(value) > item.restriction.textRestriction.maxLength) {
-                                                                return Promise.reject()
+                                                                return Promise.reject('请输入正确的' + item.label)
+                                                            }
+                                                            const result = extractAndFilterBracketsContent(value);
+                                                            if (result.extracted.length > 4) {
+                                                                return Promise.reject('表情数量不得超出: 4 个')
                                                             }
                                                             return Promise.resolve()
                                                         }

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

@@ -64,6 +64,19 @@ export async function moveFolderApi({ targetId, sourceId }: { targetId: number,
     })
 }
 
+/**
+ * 复制文件到文件夹
+ * @param param0 
+ * @returns 
+ */
+export async function copyMaterialApi({ folderId, materialIds }: { folderId: number, materialIds: number[] }) {
+    return request(api + `/material/material/copy`, {
+        method: 'POST',
+        data: materialIds,
+        params: { folderId }
+    })
+}
+
 /**
  * 修改文件所属人
  * @param params 

+ 12 - 0
src/services/adqV3/global.ts

@@ -262,6 +262,18 @@ export async function getAdqLandingPageListApi(params: {
     });
 }
 
+/**
+ * 删除落地页
+ * @param data 
+ * @returns 
+ */
+export async function delPageApi(data: { accountId: number, pageIds: number[] }) {
+    return request(api + `/adq/v3/marketingAssets/batchDelWXDownPage`, {
+        method: 'DELETE',
+        data
+    });
+}
+
 /**
  * 灵鹊落地页
  * @param params 

+ 2 - 2
src/utils/utils.ts

@@ -90,8 +90,8 @@ function arrayToRegex(arr: string[]) {
     return regex;
 }
 
-function extractAndFilterBracketsContent(input: string): { extracted: string[], filteredString: string } {
-    const arr = ['[微笑]', '[撇嘴]', '[色]', '[发呆]', '[流泪]', '[害羞]', '[睡]', '[大哭]', '[尴尬]', '[发怒]', '[调皮]', '[呲牙]', '[惊讶]', '[难过]', '[冷汗]', '[抓狂]', '[偷笑]', '[愉快]', '[白眼]', '[傲慢]', '[惊恐]', '[流汗]', '[憨笑]', '[奋斗]', '[疑问]', '[晕]', '[衰]', '[敲打]', '[再见]', '[擦汗]', '[鼓掌]', '[坏笑]', '[左哼哼]', '[右哼哼]', '[哈欠]', '[委屈]', '[快哭了]', '[阴险]', '[亲亲]', '[可怜]', '[西瓜]', '[咖啡]', '[猪头]', '[玫瑰]', '[嘴唇]', '[爱心]', '[蛋糕]', '[月亮]', '[太阳]', '[拥抱]', '[强]', '[胜利]', '[握手]', '[抱拳]', '[勾引]', '[拳头]', '[OK]', '[跳跳]', '[发抖]', '[怄火]', '[转圈]', '[嘿哈]', '[捂脸]', '[奸笑]', '[机智]', '[皱眉]', '[耶]', '[加油]', '[汗]', '[天啊]', '[社会社会]', '[旺柴]', '[好的]', '[加油加油]', '[哇]', '[红包]', '[發]', '[福]'];
+const arr = ['[微笑]', '[撇嘴]', '[色]', '[发呆]', '[流泪]', '[害羞]', '[睡]', '[大哭]', '[尴尬]', '[发怒]', '[调皮]', '[呲牙]', '[惊讶]', '[难过]', '[冷汗]', '[抓狂]', '[偷笑]', '[愉快]', '[白眼]', '[傲慢]', '[惊恐]', '[流汗]', '[憨笑]', '[奋斗]', '[疑问]', '[晕]', '[衰]', '[敲打]', '[再见]', '[擦汗]', '[鼓掌]', '[坏笑]', '[左哼哼]', '[右哼哼]', '[哈欠]', '[委屈]', '[快哭了]', '[阴险]', '[亲亲]', '[可怜]', '[西瓜]', '[咖啡]', '[猪头]', '[玫瑰]', '[嘴唇]', '[爱心]', '[蛋糕]', '[月亮]', '[太阳]', '[拥抱]', '[强]', '[胜利]', '[握手]', '[抱拳]', '[勾引]', '[拳头]', '[OK]', '[跳跳]', '[发抖]', '[怄火]', '[转圈]', '[嘿哈]', '[捂脸]', '[奸笑]', '[机智]', '[皱眉]', '[耶]', '[加油]', '[汗]', '[天啊]', '[社会社会]', '[旺柴]', '[好的]', '[加油加油]', '[哇]', '[红包]', '[發]', '[福]'];
+export function extractAndFilterBracketsContent(input: string): { extracted: string[], filteredString: string } {
     const regex = arrayToRegex(arr);
     const matches: string[] = [];
     let match;