wjx 10 månader sedan
förälder
incheckning
10badf2e33
100 ändrade filer med 12244 tillägg och 26 borttagningar
  1. 59 1
      config/routerConfig.ts
  2. BIN
      public/iconspull/appicon-adnet.png
  3. 0 0
      public/iconspull/appicon-channels.svg
  4. 31 0
      public/iconspull/appicon-game.svg
  5. 0 0
      public/iconspull/appicon-mements.svg
  6. 3 0
      public/iconspull/appicon-minapp.svg
  7. 3 0
      public/iconspull/appicon-mp.svg
  8. 1 0
      public/iconspull/appicon-qbrowser.svg
  9. 15 0
      public/iconspull/appicon-qmusic.svg
  10. 0 0
      public/iconspull/appicon-qq.svg
  11. 6 0
      public/iconspull/appicon-qqweb.svg
  12. 6 0
      public/iconspull/appicon-vqq.svg
  13. 6 0
      public/iconspull/appicon-yyb.svg
  14. 5 1
      src/app.tsx
  15. 1 0
      src/assets/asset.svg
  16. 1 0
      src/assets/assetLibrary.svg
  17. 2 2
      src/components/FileBoxAD/index.tsx
  18. 45 0
      src/models/useLaunchV3/useTargeting.ts
  19. 2 2
      src/pages/launchSystemNew/components/textAideInput/index.less
  20. 1 1
      src/pages/launchSystemNew/components/timeInSelect/Thead.tsx
  21. 1 0
      src/pages/launchSystemNew/components/timeInSelect/const.ts
  22. 14 4
      src/pages/launchSystemNew/components/timeInSelect/index.less
  23. 3 3
      src/pages/launchSystemNew/components/timeInSelect/index.tsx
  24. 1 0
      src/pages/launchSystemNew/components/videoFrame/index.tsx
  25. 2 2
      src/pages/launchSystemNew/launchManage/createAd/creative/modal/index.less
  26. 5 4
      src/pages/launchSystemNew/launchManage/createAd/creativeCL/modal/brandImage.tsx
  27. 2 2
      src/pages/launchSystemNew/launchManage/createAd/creativeCL/modal/index.less
  28. 2 2
      src/pages/launchSystemNew/launchManage/localAd/creative/index.less
  29. 2 2
      src/pages/launchSystemNew/launchManage/taskList/index.less
  30. 62 0
      src/pages/launchSystemV3/components/AdgroupTooltip/index.tsx
  31. 197 0
      src/pages/launchSystemV3/components/BrandImage/index.tsx
  32. 73 0
      src/pages/launchSystemV3/components/BrandImage/tableConfig.tsx
  33. 0 0
      src/pages/launchSystemV3/components/DataSourceModal/index.less
  34. 174 0
      src/pages/launchSystemV3/components/DataSourceModal/index.tsx
  35. 38 0
      src/pages/launchSystemV3/components/DataSourceModal/tableConfig.tsx
  36. 83 0
      src/pages/launchSystemV3/components/DynamicTooltip/index.tsx
  37. 124 0
      src/pages/launchSystemV3/components/GoodsModal/index.less
  38. 208 0
      src/pages/launchSystemV3/components/GoodsModal/index.tsx
  39. 37 0
      src/pages/launchSystemV3/components/GoodsModal/tableConfig.tsx
  40. 55 0
      src/pages/launchSystemV3/components/New1Radio/index.less
  41. 26 0
      src/pages/launchSystemV3/components/New1Radio/index.tsx
  42. 54 0
      src/pages/launchSystemV3/components/New2Radio/index.less
  43. 23 0
      src/pages/launchSystemV3/components/New2Radio/index.tsx
  44. 46 0
      src/pages/launchSystemV3/components/NewRadio/index.less
  45. 20 0
      src/pages/launchSystemV3/components/NewRadio/index.tsx
  46. 51 0
      src/pages/launchSystemV3/components/NewTree/index.less
  47. 280 0
      src/pages/launchSystemV3/components/NewTree/index.tsx
  48. 179 0
      src/pages/launchSystemV3/components/PageModal/index.tsx
  49. 60 0
      src/pages/launchSystemV3/components/PageModal/tableConfig.tsx
  50. 10 0
      src/pages/launchSystemV3/components/TargetingTooltip/index.less
  51. 188 0
      src/pages/launchSystemV3/components/TargetingTooltip/index.tsx
  52. 20 0
      src/pages/launchSystemV3/components/TextAideInput/index.less
  53. 86 0
      src/pages/launchSystemV3/components/TextAideInput/index.tsx
  54. 48 0
      src/pages/launchSystemV3/components/VideoFrameSelect/index.less
  55. 77 0
      src/pages/launchSystemV3/components/VideoFrameSelect/index.tsx
  56. 165 0
      src/pages/launchSystemV3/components/WechatAccount/index.tsx
  57. 26 0
      src/pages/launchSystemV3/components/WechatAccount/tableConfig.tsx
  58. 106 0
      src/pages/launchSystemV3/tencenTasset/brand/index.tsx
  59. 186 0
      src/pages/launchSystemV3/tencenTasset/profiles/index.tsx
  60. 81 0
      src/pages/launchSystemV3/tencenTasset/profiles/tableConfig.tsx
  61. 141 0
      src/pages/launchSystemV3/tencenTasset/targeting/index.tsx
  62. 107 0
      src/pages/launchSystemV3/tencenTasset/targeting/tableConfig.tsx
  63. 28 0
      src/pages/launchSystemV3/tencentAdPutIn/const.ts
  64. 127 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsAdSetting.tsx
  65. 204 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsMarketingContent.tsx
  66. 164 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsPrice.tsx
  67. 276 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsSitSet.tsx
  68. 76 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/index.less
  69. 110 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/index.tsx
  70. 27 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/marketingGoal.tsx
  71. 241 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Ad/newCreateAd.tsx
  72. 375 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeConversionAssistant.tsx
  73. 132 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeTemplateContent.tsx
  74. 51 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeTemplateSetup.tsx
  75. 167 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/index.tsx
  76. 543 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/newDynamic.tsx
  77. 448 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx
  78. 155 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.less
  79. 89 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.tsx
  80. 83 0
      src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/index.tsx
  81. 168 0
      src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx
  82. 58 0
      src/pages/launchSystemV3/tencentAdPutIn/create/PageList/index.tsx
  83. 833 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Target/addTarget.tsx
  84. 53 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Target/dataItem.tsx
  85. 34 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Target/index.less
  86. 119 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Target/index.tsx
  87. 203 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Target/selectTarget.tsx
  88. 89 0
      src/pages/launchSystemV3/tencentAdPutIn/create/Target/tableConfig.tsx
  89. 364 0
      src/pages/launchSystemV3/tencentAdPutIn/create/index.less
  90. 589 0
      src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx
  91. 53 0
      src/pages/launchSystemV3/tencentAdPutIn/create/submitModal.tsx
  92. 167 0
      src/pages/launchSystemV3/tencentAdPutIn/create/tableConfig.tsx
  93. 130 0
      src/pages/launchSystemV3/tencentAdPutIn/index.less
  94. 1840 0
      src/pages/launchSystemV3/tencentAdPutIn/rules.ts
  95. 63 0
      src/pages/launchSystemV3/tencentAdPutIn/taskList/dynamicLog.tsx
  96. 99 0
      src/pages/launchSystemV3/tencentAdPutIn/taskList/index.tsx
  97. 79 0
      src/pages/launchSystemV3/tencentAdPutIn/taskList/log.tsx
  98. 359 0
      src/pages/launchSystemV3/tencentAdPutIn/taskList/tableConfig.tsx
  99. 125 0
      src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts
  100. 273 0
      src/services/adqV3/global.ts

+ 59 - 1
config/routerConfig.ts

@@ -196,7 +196,65 @@ const launchSystemV3 = {
             name: '腾讯广告',
             component: './launchSystemV3/adqv3',
             access: 'adqv3',
-        }
+        },
+        {
+            path: '/launchSystemV3/tencentAdPutIn',
+            name: '广告投放',
+            access: 'tencentAdPutIn',
+            routes: [
+                {
+                    name: '广告创建',
+                    path: '/launchSystemV3/tencentAdPutIn/create',
+                    access: 'create',
+                    component: './launchSystemV3/tencentAdPutIn/create',
+                },
+                {
+                    name: '任务列表',
+                    path: '/launchSystemV3/tencentAdPutIn/taskList',
+                    access: 'taskList',
+                    component: './launchSystemV3/tencentAdPutIn/taskList',
+                },
+            ],
+        },
+        {
+            path: '/launchSystemV3/tencenTasset',
+            name: '资产',
+            access: 'tencenTasset',
+            routes: [
+                {
+                    name: '定向模板',
+                    path: '/launchSystemV3/tencenTasset/targeting',
+                    access: 'targeting',
+                    component: './launchSystemV3/tencenTasset/targeting',
+                },
+                {
+                    name: '品牌形象',
+                    path: '/launchSystemV3/tencenTasset/brand',
+                    access: 'brand',
+                    component: './launchSystemV3/tencenTasset/brand',
+                },
+                {
+                    name: '头像昵称跳转页',
+                    path: '/launchSystemV3/tencenTasset/profiles',
+                    access: 'profiles',
+                    component: './launchSystemV3/tencenTasset/profiles',
+                }
+            ],
+        },
+        {
+            path: '/launchSystemV3/material',
+            name: '素材库',
+            access: 'material',
+            icon: 'DatabaseOutlined',
+            routes: [
+                {
+                    name: '本地素材',
+                    path: '/launchSystemV3/material/cloud',
+                    access: 'cloud',
+                    component: './launchSystemNew/material/cloud',
+                },
+            ],
+        },
     ]
 }
 /** 头条 */

BIN
public/iconspull/appicon-adnet.png


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
public/iconspull/appicon-channels.svg


+ 31 - 0
public/iconspull/appicon-game.svg

@@ -0,0 +1,31 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.46094 10.2148L13.5562 17.0895L16.5027 13.8425L11.9101 10.5773L9.46094 10.2148Z" fill="url(#paint0_radial_1_552)"/>
+<path d="M8.35498 10.2545L6.3125 2.51758L10.6939 2.68633L9.81077 8.25173L8.35498 10.2545Z" fill="url(#paint1_radial_1_552)"/>
+<path d="M8.97071 10.6493L1 9.94073L1.56034 7.35742L7.57278 8.82656L8.97071 10.6493Z" fill="url(#paint2_radial_1_552)"/>
+<path d="M18.3074 12.0104C18.0832 11.691 17.7614 11.4812 17.4109 11.3919L10.0293 9.36823C10.0293 9.36823 12.3538 4.28821 12.5253 3.90723C12.6968 3.52625 12.6574 2.99159 12.3785 2.59213C11.9407 1.96504 11.0774 1.81169 10.4503 2.24949C10.1679 2.44665 9.98171 2.73041 9.9023 3.03985L8.28563 8.8962C8.28563 8.8962 2.0644 7.19704 1.97061 7.17034C1.87682 7.14364 1.79193 7.15049 1.71423 7.20526C1.58621 7.29494 1.5554 7.47156 1.64508 7.59958C1.6776 7.64579 1.72176 7.67899 1.77036 7.69885L7.85194 10.4667L6.14217 16.6646C6.12027 16.742 6.12437 16.8221 6.1723 16.8909C6.25137 17.0042 6.40711 17.0319 6.52041 16.9528C6.56149 16.9241 6.59058 16.8854 6.60838 16.8423L9.2379 11.0961L16.435 14.3644C16.9115 14.5564 17.4722 14.5116 17.924 14.1946C18.6333 13.6976 18.8047 12.7196 18.3074 12.0104Z" fill="url(#paint3_radial_1_552)"/>
+<path d="M18.9992 3.78049C19.0002 2.81418 18.2181 2.03032 17.2518 2.0293C16.8164 2.02896 16.4186 2.18778 16.1123 2.45032L16.1119 2.44998L2.35095 13.8276L2.35129 13.8283C2.26127 13.8926 2.20616 14.0025 2.22122 14.1247C2.23765 14.2592 2.34205 14.3705 2.4752 14.3951C2.56078 14.4112 2.63163 14.393 2.70523 14.3499C2.77882 14.3068 7.24202 11.6855 7.24202 11.6855L7.50662 11.5301C7.64799 11.4472 7.82769 11.539 7.8431 11.7019L8.36202 17.1431C8.44417 17.7335 8.82549 18.2699 9.42279 18.5191C10.259 18.8679 11.2359 18.5109 11.6501 17.7048C11.861 17.294 11.8935 16.8425 11.7795 16.4338L10.2618 10.6035C10.1693 10.2485 10.3237 9.87507 10.64 9.68921C10.64 9.68921 17.7016 5.54263 18.1986 5.24825C18.6956 4.95354 18.9985 4.39628 18.9992 3.78049Z" fill="url(#paint4_radial_1_552)"/>
+<defs>
+<radialGradient id="paint0_radial_1_552" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.5061 11.6119) rotate(-49.1116) scale(5.84767)">
+<stop stop-color="#F8DF70"/>
+<stop offset="1" stop-color="#F8DF8D" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint1_radial_1_552" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.09779 8.51941) rotate(3.53738) scale(5.84759)">
+<stop stop-color="#F8DF70"/>
+<stop offset="1" stop-color="#F8DF8D" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint2_radial_1_552" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.02803 10.6089) rotate(-66.5908) scale(5.37224)">
+<stop stop-color="#F8DF70"/>
+<stop offset="1" stop-color="#F8DF8D" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint3_radial_1_552" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.86315 10.0936) scale(8.57125)">
+<stop offset="0.1361" stop-color="#F8DF8E"/>
+<stop offset="0.3309" stop-color="#FBD172"/>
+<stop offset="0.5363" stop-color="#FEC55A"/>
+<stop offset="0.6638" stop-color="#FFC151"/>
+</radialGradient>
+<radialGradient id="paint4_radial_1_552" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.07438 9.87598) rotate(-35.3032) scale(6.40931 5.21711)">
+<stop stop-color="#FFC151"/>
+<stop offset="1" stop-color="#FF8931"/>
+</radialGradient>
+</defs>
+</svg>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
public/iconspull/appicon-mements.svg


+ 3 - 0
public/iconspull/appicon-minapp.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.1305 9.19719C15.671 9.94945 14.9067 10.5261 13.9913 10.7915C13.9682 10.7981 13.9454 10.8051 13.9222 10.8115C13.7808 10.8517 13.6356 10.8761 13.492 10.8761C12.8577 10.8761 12.4996 10.4488 12.692 9.92153C12.8326 9.53599 13.2021 9.21043 13.6529 9.05985C14.3516 8.80565 14.84 8.22589 14.84 7.5514C14.84 6.64236 13.9533 5.90541 12.8599 5.90541C11.7663 5.90541 10.8799 6.64236 10.8799 7.5514V12.5986C10.8799 13.8123 10.1749 14.8769 9.11988 15.4788C8.54488 15.807 7.86745 16 7.14005 16C5.0777 16 3.39999 14.4742 3.39999 12.5986C3.39999 12.0017 3.57127 11.4409 3.86945 10.9526C4.33102 10.1972 5.09989 9.61872 6.02096 9.35501C6.02179 9.3548 6.02241 9.35439 6.02324 9.35418C6.17855 9.30599 6.33946 9.2791 6.49829 9.2791C7.13549 9.2791 7.49525 9.70869 7.302 10.2382C7.17116 10.5971 6.81845 10.9061 6.40726 11.0705C6.35833 11.0868 6.31043 11.1052 6.26336 11.1245C5.61019 11.3934 5.16002 11.9516 5.16002 12.5986C5.16002 13.5076 6.04646 14.2444 7.14005 14.2444C8.23364 14.2444 9.11988 13.5076 9.11988 12.5986V7.5514C9.11988 6.33749 9.82488 5.27291 10.8799 4.67102C11.4551 4.34277 12.1323 4.15 12.8599 4.15C14.9221 4.15 16.6 5.67582 16.6 7.5514C16.6 8.14833 16.4287 8.70906 16.1305 9.19719" fill="#6467F0"/>
+</svg>

+ 3 - 0
public/iconspull/appicon-mp.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.7444 5.7907C14.0001 4.26025 12.1173 2.9503 9.98562 2.95C8.86707 2.9497 7.15827 3.3337 5.88132 4.8778C5.02857 5.9089 4.74147 7.11565 4.92117 8.3191C5.04462 9.1459 5.50512 10.2367 6.16407 10.9267C6.41247 9.17755 7.33377 7.8019 8.54172 6.79075C10.7227 5.12455 13.0512 5.2189 14.7444 5.7907ZM16.4462 8.41481C15.1118 6.68306 12.8051 6.19856 10.7228 6.97076L10.7399 6.97592C10.8013 6.99442 10.8627 7.01288 10.9239 7.03361C14.0069 8.08391 15.6731 11.4285 14.6456 14.5038C14.3721 15.3219 13.9344 16.0374 13.383 16.6288C14.0472 16.4449 14.727 16.182 15.3102 15.7326C17.6582 13.9234 18.1203 10.5874 16.4462 8.41481ZM8.62105 13.7957C9.05755 13.8932 9.5113 13.947 9.97975 13.947C11.406 13.947 12.5356 13.6152 13.6026 12.8303C13.575 13.2743 13.4415 13.8087 13.3194 14.1234C12.3151 16.7147 9.4078 17.8379 6.361 16.8372C3.7117 15.9671 2.08825 12.8606 2.83 10.2675C3.0289 9.57257 3.23215 9.09587 3.61375 8.49602C3.84265 10.1676 4.88335 11.7935 6.4006 12.8282C6.51595 12.9059 6.59485 13.0356 6.6013 13.1858C6.6034 13.2357 6.5947 13.2825 6.5836 13.3311L6.3289 14.4872C6.32593 14.5009 6.32243 14.5148 6.31889 14.5289C6.30851 14.5702 6.29786 14.6125 6.29965 14.6546C6.30505 14.7794 6.41005 14.8767 6.5344 14.8713C6.5833 14.8692 6.6223 14.8491 6.66265 14.8235L8.10235 13.9005C8.21065 13.8311 8.3266 13.7864 8.4559 13.7808C8.5129 13.7783 8.5678 13.7847 8.62105 13.7957Z" fill="#07C160"/>
+</svg>

+ 1 - 0
public/iconspull/appicon-qbrowser.svg

@@ -0,0 +1 @@
+<svg t="1715406543844" class="icon" viewBox="0 0 1088 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2494" width="20" height="20"><path d="M3.764706 0h1024v1024H3.764706z" fill="#017AFF" fill-opacity="0" p-id="2495"></path><path d="M360.975059 709.481412a252.054588 252.054588 0 0 1 23.717647-61.199059 212.720941 212.720941 0 0 1-89.720471-173.733647c0-117.579294 95.397647-212.961882 212.976941-212.961882 117.564235 0 212.961882 95.382588 212.961883 212.961882 0 23.055059-3.719529 45.236706-10.390588 66.002823a253.44 253.44 0 0 1 108.619294 94.298353 193.355294 193.355294 0 0 1 90.48847 28.521412 441.630118 441.630118 0 0 0 42.059294-188.717176c0-244.976941-198.656-443.632941-443.632941-443.632942S64.301176 229.571765 64.301176 474.548706c0 152.756706 77.251765 287.488 194.725648 367.254588a161.942588 161.942588 0 0 1 101.948235-132.321882z" fill="#4795F4" p-id="2496"></path><path d="M964.472471 801.159529c-11.474824-80.971294-80.534588-143.691294-164.336942-144.896a223.412706 223.412706 0 0 0-153.856-106.420705 219.346824 219.346824 0 0 0-38.896941-3.824942c-106.767059 0-197.345882 74.089412-218.864941 177.453177-55.070118 18.356706-94.960941 70.264471-94.960941 131.44847 0 76.378353 62.177882 138.661647 138.541176 138.661647 27.557647 0 53.775059-7.981176 76.498824-23.04a223.774118 223.774118 0 0 0 98.785882 23.04c35.614118 0 70.927059-8.628706 102.384941-24.907294a166.746353 166.746353 0 0 0 87.958589 24.907294c88.079059 0 160.527059-67.855059 167.845647-154.066823 0.436706-4.818824 0.752941-9.728 0.752941-14.637177a141.718588 141.718588 0 0 0-1.852235-23.717647z" fill="#ffffff" p-id="2497"></path></svg>

+ 15 - 0
public/iconspull/appicon-qmusic.svg

@@ -0,0 +1,15 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M14.6887 3.65663L14.4334 2.00195L13.5418 2.96409C12.4739 2.43634 11.2717 2.13818 10 2.13818C5.58181 2.13818 2 5.71998 2 10.1382C2 14.5564 5.58181 18.1382 10 18.1382C14.4182 18.1382 18 14.5564 18 10.1382C18 7.47183 16.6949 5.11031 14.6887 3.65663Z" fill="#FFDC00"/>
+<path d="M13.2635 12.7235L9.95799 7.03263C11.6232 6.16385 13.6312 4.36056 14.4314 2C12.3681 3.51274 9.7608 3.88616 7.47264 3.86902C7.15256 3.86616 6.95156 4.21386 7.11255 4.49012L7.77938 5.64182L11.0468 11.2851C10.6534 11.2022 10.2352 11.1746 9.80462 11.2117C7.80224 11.387 6.29903 12.9035 6.44763 14.5982C6.59624 16.2939 8.33951 17.5256 10.3419 17.3503C12.3443 17.175 13.8475 15.6585 13.6989 13.9638C13.6608 13.5123 13.5074 13.1427 13.2635 12.7235Z" fill="url(#paint0_linear_1_546)"/>
+<defs>
+<linearGradient id="paint0_linear_1_546" x1="10.4353" y1="2.00095" x2="10.4353" y2="17.3685" gradientUnits="userSpaceOnUse">
+<stop stop-color="#00CBCB"/>
+<stop offset="0.1617" stop-color="#0FC8B2"/>
+<stop offset="0.4497" stop-color="#28C48B"/>
+<stop offset="0.6" stop-color="#31C27C"/>
+<stop offset="0.7" stop-color="#21C077"/>
+<stop offset="0.8857" stop-color="#09BD71"/>
+<stop offset="1" stop-color="#00BC6E"/>
+</linearGradient>
+</defs>
+</svg>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
public/iconspull/appicon-qq.svg


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 6 - 0
public/iconspull/appicon-qqweb.svg


+ 6 - 0
public/iconspull/appicon-vqq.svg

@@ -0,0 +1,6 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M4.6841 13.857C5.04028 16.2745 5.46797 18.0919 8.2529 17.1874C10.9228 16.3208 13.4946 14.7881 15.7509 13.0465C16.5586 12.3495 17.9693 11.3707 17.9973 10.2125C17.9693 9.05418 16.5586 8.07539 15.7509 7.37846C13.4946 5.63683 10.9228 4.10414 8.2529 3.23613C5.46657 2.33166 5.04028 4.15042 4.6841 6.56654C4.50741 7.76409 4.47656 8.9995 4.47656 10.2111C4.47656 11.4226 4.50741 12.6594 4.6841 13.857Z" fill="#0098FF"/>
+<path d="M2.25662 7.01976C2.56231 5.41976 3.02086 4.25867 5.07379 4.65692C4.90131 5.20801 4.79053 5.86148 4.68677 6.56682C4.51008 7.76437 4.47923 8.99977 4.47923 10.2113C4.47923 11.4229 4.51008 12.6583 4.68677 13.8559C4.79053 14.5612 4.90131 15.2161 5.07379 15.7658C3.02086 16.1654 2.56231 15.0043 2.25662 13.4043L2.23558 13.2907C2.22156 13.215 2.20754 13.1379 2.19351 13.0594C2.02945 12.1241 2 11.1593 2 10.2127C2 9.26621 2.02945 8.30004 2.19351 7.36472C2.20754 7.28619 2.22156 7.21047 2.23558 7.13334L2.25662 7.01976Z" fill="#FF8800"/>
+<path d="M5.07113 4.65625C5.21416 4.6843 5.3642 4.71935 5.52266 4.76282C8.01451 5.44012 10.4152 6.63767 12.5214 7.99788C13.2759 8.54196 14.5912 9.30761 14.6178 10.2121C14.5912 11.118 13.2759 11.8822 12.5214 12.4263C10.4152 13.7865 8.01451 14.984 5.52266 15.6613C5.3642 15.7048 5.21416 15.7399 5.07113 15.7679C4.89865 15.2168 4.78787 14.5633 4.6841 13.858C4.50741 12.6605 4.47656 11.425 4.47656 10.2135C4.47656 9.00191 4.50741 7.7651 4.6841 6.56896C4.78787 5.86782 4.89725 5.21716 5.06832 4.66887L5.07113 4.65625Z" fill="#43E700"/>
+<path d="M6.54067 7.91455C6.54067 7.91455 6.39062 8.16275 6.39062 10.2171C6.39062 12.2756 6.54067 12.5112 6.54067 12.5112C6.59255 12.7664 6.69913 12.8464 6.97117 12.7833C6.97117 12.7833 7.42831 12.7103 9.26389 11.7764C11.0995 10.8369 11.4066 10.4877 11.4066 10.4877C11.6029 10.2872 11.6548 10.1554 11.4066 9.94365C11.4066 9.94365 10.9032 9.47529 9.26389 8.6255C7.64146 7.78414 6.97117 7.64391 6.97117 7.64391C6.7454 7.578 6.60097 7.65092 6.54067 7.91455Z" fill="white"/>
+</svg>

+ 6 - 0
public/iconspull/appicon-yyb.svg

@@ -0,0 +1,6 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.0046 2.00001C8.95499 1.99864 7.91541 2.20453 6.94552 2.60587C5.97562 3.0072 5.09452 3.59607 4.35274 4.33872C3.94053 4.75142 3.70898 5.31087 3.70898 5.89417C3.70898 6.47746 3.94053 7.0369 4.35274 7.4496C4.55706 7.65415 4.79969 7.81643 5.06675 7.92714C5.33383 8.03786 5.62011 8.09484 5.90923 8.09484C6.19834 8.09484 6.48462 8.03786 6.7517 7.92714C7.01878 7.81643 7.26141 7.65415 7.46572 7.4496L12.5128 2.40251C11.7036 2.13551 10.8568 1.99962 10.0046 2.00001Z" fill="#FFCC00"/>
+<path d="M2.40269 7.49902C1.93892 8.90585 1.87435 10.4137 2.21617 11.855C2.55798 13.2963 3.29279 14.6146 4.33891 15.6634C4.75174 16.0753 5.31114 16.3066 5.89435 16.3066C6.47757 16.3066 7.03695 16.0753 7.44979 15.6634C7.86271 15.2504 8.09468 14.6903 8.09468 14.1064C8.09468 13.5223 7.86271 12.9622 7.44979 12.5493L2.40269 7.49902Z" fill="#0080FF"/>
+<path d="M9.99997 18.0096C11.0496 18.0107 12.0891 17.8047 13.059 17.4034C14.0288 17.0021 14.91 16.4134 15.6518 15.6709C16.0639 15.258 16.2952 14.6986 16.2952 14.1154C16.2952 13.5322 16.0639 12.9727 15.6518 12.5599C15.2389 12.147 14.6789 11.915 14.0948 11.915C13.5109 11.915 12.9508 12.147 12.5378 12.5599L7.49072 17.6071C8.30038 17.874 9.14746 18.0098 9.99997 18.0096Z" fill="#FF0055"/>
+<path d="M12.55 4.36123C12.3455 4.56555 12.1832 4.80818 12.0725 5.07525C11.9618 5.34233 11.9048 5.62861 11.9048 5.91773C11.9048 6.20684 11.9618 6.49313 12.0725 6.76021C12.1832 7.02728 12.3455 7.26992 12.55 7.47424L17.5971 12.5213C18.0612 11.1151 18.1261 9.60779 17.7848 8.16686C17.4435 6.72594 16.7095 5.40786 15.6641 4.35911C15.4595 4.15467 15.2167 3.99254 14.9494 3.88199C14.6822 3.77144 14.3958 3.71464 14.1066 3.71484C13.8174 3.71503 13.5311 3.77222 13.264 3.88314C12.9969 3.99405 12.7543 4.15651 12.55 4.36123Z" fill="#2BD6A9"/>
+</svg>

+ 5 - 1
src/app.tsx

@@ -13,6 +13,8 @@ import { ReactComponent as AdLaunchSvg } from '@/assets/adLaunch.svg'
 import { ReactComponent as MaterialSvg } from '@/assets/material.svg'
 import { ReactComponent as GdtSvg } from '@/assets/gdt.svg'
 import { ReactComponent as MonitorSvg } from '@/assets/monitor.svg'
+import { ReactComponent as AssetSvg } from '@/assets/asset.svg'
+import { ReactComponent as AssetLibrarySvg } from '@/assets/assetLibrary.svg'
 import versions from './utils/versions';
 
 
@@ -108,7 +110,9 @@ const IconMap = {
   gdt: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><GdtSvg /></span>,
   monitor: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MonitorSvg /></span>,
   eye: <EyeOutlined />,
-  user: <UserSwitchOutlined />
+  user: <UserSwitchOutlined />,
+  asset: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AssetSvg /></span>,
+  assetLibrary: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AssetLibrarySvg /></span>,
 };
 //处理菜单
 const loopMenuItem = (menus: MenuDataItem[],): MenuDataItem[] => {

+ 1 - 0
src/assets/asset.svg

@@ -0,0 +1 @@
+<svg version="1.1" p-id="2578" viewBox="64 64 896 896" focusable="false" data-icon="fund-view" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M512.5 63.9C265 63.9 64.3 264.5 64.3 512S265 960.2 512.5 960.2 960.7 759.5 960.7 512 760 63.9 512.5 63.9m201.7 477L541.3 713.7c-15.9 15.9-41.7 15.9-57.6 0L310.8 540.9c-15.9-15.9-15.9-41.7 0-57.6l172.9-172.9c15.9-15.9 41.7-15.9 57.6 0l172.9 172.9c15.9 15.9 15.9 41.6 0 57.6" fill="" p-id="2579"></path></svg>

+ 1 - 0
src/assets/assetLibrary.svg

@@ -0,0 +1 @@
+<svg viewBox="64 64 896 896" focusable="false" data-icon="fund-view" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M254.976 448.512q27.648 0 47.104 19.456t19.456 47.104q0 26.624-19.456 46.592t-47.104 19.968-47.104-19.968-19.456-46.592q0-27.648 19.456-47.104t47.104-19.456zM771.072 66.56q45.056 0 79.872 15.872t58.88 40.96 36.864 56.832 12.8 62.464l0 523.264q0 23.552-10.752 46.08t-28.672 40.96-40.96 29.696-47.616 11.264l0-572.416q0-26.624-10.24-49.664t-27.648-40.448-40.448-27.648-49.664-10.24l-575.488 0q0-22.528 10.24-45.056t27.648-40.96 40.96-29.696 50.176-11.264l514.048 0zM681.984 258.048q46.08 0 65.536 25.088t19.456 67.072l0 603.136q0 25.6-21.504 48.128t-54.272 22.528l-620.544 0q-27.648 0-49.152-22.528t-21.504-54.272l0-614.4q0-32.768 17.92-53.76t47.616-20.992l616.448 0zM640 413.696q0-11.264-7.168-18.432t-18.432-7.168l-461.824 0q-11.264 0-18.432 7.168t-7.168 18.432l0 228.352q9.216 11.264 16.896 25.088t18.944 26.112 29.696 20.992 51.2 8.704q48.128 0 78.336-15.36t53.248-38.912 43.008-50.688 47.616-51.2 68.608-40.96 105.472-18.944l0-93.184z" p-id="2537"></path></svg>

+ 2 - 2
src/components/FileBoxAD/index.tsx

@@ -290,12 +290,12 @@ function FlieBox(props: Props) {
         }
     }
 
-    return <div style={{ display: 'flex', flexFlow: 'row', width: '100%' }}>
+    return <div style={{ display: 'flex', flexFlow: 'row', width: '100%', padding: '0 16px 0' }}>
         {get_folder_tree?.data?.length > 0 && <div style={{ flexShrink: 0 }}>
             {treeEl}
         </div>}
         <div
-            style={{ flex: 1, paddingTop: 10 }}
+            style={{ flex: 1, padding: '10px 0px 0px' }}
             data-type="right-boxzones"
             onMouseDown={onMouseDown}
             onMouseUp={onMouseUp}

+ 45 - 0
src/models/useLaunchV3/useTargeting.ts

@@ -0,0 +1,45 @@
+import { useAjax } from '@/Hook/useAjax'
+import { getTargetingGagsApi } from '@/services/adqV3/global'
+import { useState } from 'react'
+
+/**
+ * 获取所有品牌手机 所有地域列表
+ * @returns 
+ */
+export default function useTargeting() {
+
+    const getTargetingGags = useAjax((params) => getTargetingGagsApi(params))
+
+    const [geoLocationList, setGeoLocationList] = useState<any>({}) // 所有地域列表
+    const [modelList, setModelList] = useState<any>({})  // 所有品牌手机
+
+
+    const initTargeting = async () => {
+        if (!(geoLocationList && Object.keys(geoLocationList).length)) {
+            await getTargetingGags.run({ type: 'REGION' }).then(res => {
+                if (res && Array.isArray(res)) {
+                    setGeoLocationList(() => (res as any[])?.reduce((prev: any, cur: { id: number }) => {
+                        prev[cur.id] = cur
+                        return prev
+                    }, {}))
+                }
+            })
+        }
+        if (!(modelList && Object.keys(modelList).length)) {
+            await getTargetingGags.run({ type: 'DEVICE_BRAND_MODEL' }).then(res => {
+                if (res && Array.isArray(res)) {
+                    setModelList(() => (res as any[])?.reduce((prev: any, cur: { id: number }) => {
+                        prev[cur.id] = cur
+                        return prev
+                    }, {}))
+                }
+            })
+        }
+    }
+
+    return {
+        initTargeting,
+        geoLocationList,
+        modelList
+    }
+}

+ 2 - 2
src/pages/launchSystemNew/components/textAideInput/index.less

@@ -13,8 +13,8 @@
     border-radius: 3px;
     font-size: 12px;
     color: #fff;
-    border: 1px solid #296bef;
-    background-color: #296bef;
+    border: 1px solid #1890ff;
+    background-color: #1890ff;
     line-height: normal;
   }
 }

+ 1 - 1
src/pages/launchSystemNew/components/timeInSelect/Thead.tsx

@@ -145,7 +145,7 @@ const Thead: React.FC<Props> = ({ value = [], onChange }) => {
     const Footer = (
         <div className="footer_wrapper">
             <Space>
-                <Tag color="rgb(56,128,255)">已选</Tag>
+                <Tag color="#1890ff">已选</Tag>
                 <Tag>未选</Tag>
                 <Typography.Text type="secondary">可拖动鼠标选择时间段</Typography.Text>
             </Space>

+ 1 - 0
src/pages/launchSystemNew/components/timeInSelect/const.ts

@@ -5,6 +5,7 @@ import moment from 'moment';
  * @param onChange (data: string[]) => void
  */
 export interface Props {
+    id?: any,
     value?: string[],
     onChange?: (data: string[]) => void
 }

+ 14 - 4
src/pages/launchSystemNew/components/timeInSelect/index.less

@@ -6,6 +6,16 @@
     box-shadow: 0px 0px 0px 1px rgba(217, 218, 219, .8);
 }
 
+.selectNone {
+    user-select: none;
+    -webkit-user-select: none;
+    /* Chrome, Safari and Opera */
+    -moz-user-select: none;
+    /* Firefox */
+    -ms-user-select: none;
+    /* IE 10+ */
+}
+
 .thead_wrapper {
     .thead_wrapper_content {
         display: flex;
@@ -21,18 +31,18 @@
             }
         }
 
-        .time_series {
+        .time_series:extend(.selectNone) {
             width: calc(100% - 73px);
             border-right: 1px solid rgba(217, 218, 219, .8);
             border-bottom: 1px solid rgba(217, 218, 219, .8);
 
-            &__row {
+            &__row:extend(.selectNone) {
                 display: flex;
                 height: 39px;
                 width: 100%;
                 margin-top: 1px;
 
-                .row_item {
+                .row_item:extend(.selectNone) {
                     width: 100%;
                     height: 100%;
                     margin-right: 1px;
@@ -40,7 +50,7 @@
                     box-sizing: border-box;
                     // cursor: pointer;
 
-                    &__content {
+                    &__content:extend(.selectNone) {
                         display: none;
                     }
 

+ 3 - 3
src/pages/launchSystemNew/components/timeInSelect/index.tsx

@@ -4,12 +4,12 @@ import Header from './Header';
 import Thead from './Thead';
 
 
-const TimeInSelect: React.FC<Props> = (props) => {
+const TimeInSelect: React.FC<Props> = ({ id, ...props }) => {
 
-    return <>
+    return <div id={id}>
         <Header />
         <Thead {...props} />
-    </>
+    </div>
 }
 
 

+ 1 - 0
src/pages/launchSystemNew/components/videoFrame/index.tsx

@@ -26,6 +26,7 @@ const VideoFrame: React.FC<Props> = ({ url, onChange }) => {
 
     const handleOk = () => {
         if (selectUrl) {
+            console.log(selectUrl)
             onChange?.(selectUrl)
             setOpen(false);
         }

+ 2 - 2
src/pages/launchSystemNew/launchManage/createAd/creative/modal/index.less

@@ -109,7 +109,7 @@
     border-radius: 3px;
     font-size: 12px;
     color: #fff;
-    border: 1px solid #296bef;
-    background-color: #296bef;
+    border: 1px solid #1890ff;
+    background-color: #1890ff;
     line-height: normal;
 }

+ 5 - 4
src/pages/launchSystemNew/launchManage/createAd/creativeCL/modal/brandImage.tsx

@@ -10,7 +10,8 @@ import { brandColumns } from "./tableConfig"
 
 interface Props {
     onChange?: (data: any) => void,
-    value?: any
+    value?: any,
+    id?: any
 }
 
 /**
@@ -20,7 +21,7 @@ interface Props {
 const BrandImage: React.FC<Props> = (props) => {
 
     /****************************/
-    const { onChange, value } = props
+    const { onChange, value, id } = props
 
     const [visible, setVisible] = useState<boolean>(false)
     const [addVisible, setAddVisible] = useState<boolean>(false)
@@ -79,7 +80,7 @@ const BrandImage: React.FC<Props> = (props) => {
         setAddVisible(true)
     }
 
-    return <div>
+    return <div id={id}>
         <Select
             showSearch
             placeholder="请选择一个品牌跳转页,与广告创意一起展示"
@@ -138,7 +139,7 @@ const BrandImage: React.FC<Props> = (props) => {
                     <UploadImage />
                 </Form.Item>
                 <Form.Item label={<strong>名称</strong>} name="name" rules={[{ required: true, message: '请输入名称!' }]}>
-                    <Input placeholder="请输入名称" maxLength={12}/>
+                    <Input placeholder="请输入名称" maxLength={12} />
                 </Form.Item>
             </Form>
         </Modal>}

+ 2 - 2
src/pages/launchSystemNew/launchManage/createAd/creativeCL/modal/index.less

@@ -112,8 +112,8 @@
     border-radius: 3px;
     font-size: 12px;
     color: #fff;
-    border: 1px solid #296bef;
-    background-color: #296bef;
+    border: 1px solid #1890ff;
+    background-color: #1890ff;
     line-height: normal;
 }
 

+ 2 - 2
src/pages/launchSystemNew/launchManage/localAd/creative/index.less

@@ -73,7 +73,7 @@
     border-radius: 3px;
     font-size: 12px;
     color: #fff;
-    border: 1px solid #296bef;
-    background-color: #296bef;
+    border: 1px solid #1890ff;
+    background-color: #1890ff;
     line-height: normal;
 }

+ 2 - 2
src/pages/launchSystemNew/launchManage/taskList/index.less

@@ -145,7 +145,7 @@
   border-radius: 3px;
   font-size: 12px;
   color: #fff;
-  border: 1px solid #296bef;
-  background-color: #296bef;
+  border: 1px solid #1890ff;
+  background-color: #1890ff;
   line-height: normal;
 }

+ 62 - 0
src/pages/launchSystemV3/components/AdgroupTooltip/index.tsx

@@ -0,0 +1,62 @@
+import React from "react"
+import style from '../../tencentAdPutIn/create/index.less'
+import { AD_STATUS_ENUM, BID_MODE_ENUM, DEEP_CONVERSION_ENUM, GOAL_ROAS_ENUM, MARKETING_CARRIER_TYPE_ENUM, MARKETING_GOAL_ENUM, MARKETING_TARGET_TYPE_ENUM, OPTIMIZATIONGOAL_ENUM, SITE_SET_ENUM, SMART_BID_TYPE_ENUM } from "../../tencentAdPutIn/const"
+import { Typography } from "antd"
+import TimeSeriesLook from "@/pages/launchSystemNew/adq/ad/timeSeriesLook"
+
+
+interface Props {
+    data: any
+}
+
+/**
+ * 广告详情
+ * @param param0 
+ * @returns 
+ */
+const AdgroupTooltip: React.FC<Props> = ({ data: adgroups }) => {
+
+    /************************************/
+    const {
+        marketingGoal, marketingAssetOuterSpec, marketingCarrierType, automaticSiteEnabled, siteSet, searchExpandTargetingSwitch, bidMode, smartBidType, bidAmount, optimizationGoal,
+        deepConversionSpec, autoAcquisitionEnabled, autoAcquisitionBudget, dailyBudget, endDate, beginDate, timeSeries, firstDayBeginTime, configuredStatus, adgroupName
+    } = adgroups
+    /************************************/
+
+    return <div className={style.detail_body} style={{ height: 'auto' }}>
+        {(adgroups && Object.keys(adgroups).length > 0) && <>
+            <p>营销目的:{MARKETING_GOAL_ENUM[marketingGoal]}</p>
+            <p style={{ fontWeight: 'bold', color: '#000' }}>推广产品类型:{MARKETING_TARGET_TYPE_ENUM[marketingAssetOuterSpec?.marketingTargetType]}</p>
+            <p>营销载体类型:{MARKETING_CARRIER_TYPE_ENUM[marketingCarrierType]}</p>
+            <p>版位选择:{automaticSiteEnabled ? '自动版位' : '选择特定版位'}</p>
+            {!automaticSiteEnabled && <Typography.Paragraph className={style.tpP} style={{ marginBottom: 0 }} ellipsis={{ tooltip: true, rows: 2 }}>广告版位:{siteSet.map((item: string | number) => SITE_SET_ENUM[item]).toString()}</Typography.Paragraph>}
+            <p>搜索场景扩量:{searchExpandTargetingSwitch === 'SEARCH_EXPAND_TARGETING_SWITCH_OPEN' ? '开启' : '关闭'}</p>
+            <p>计费方式:{BID_MODE_ENUM[bidMode]}</p>
+            <p>出价类型:{SMART_BID_TYPE_ENUM[smartBidType]}</p>
+            <p>出价:{bidAmount}元/{optimizationGoal ? OPTIMIZATIONGOAL_ENUM[optimizationGoal] : ['BID_MODE_OCPM', 'BID_MODE_OCPC'].includes(bidMode) ? '千次曝光' : '点击'}</p>
+            {optimizationGoal && <p style={{ fontWeight: 'bold', color: '#000' }}>优化目标:{OPTIMIZATIONGOAL_ENUM[optimizationGoal]}</p>}
+            {deepConversionSpec && <>
+                <p style={{ fontWeight: 'bold', color: '#000' }}>深度转化优化:开启</p>
+                <p style={{ fontWeight: 'bold', color: '#000' }}>深度优化类型:{DEEP_CONVERSION_ENUM[deepConversionSpec?.deepConversionType]}</p>
+                {deepConversionSpec.deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? <>
+                    <p style={{ fontWeight: 'bold', color: '#000' }}>深度优化目标:{OPTIMIZATIONGOAL_ENUM[deepConversionSpec.deepConversionBehaviorSpec.goal]}</p>
+                    <p style={{ fontWeight: 'bold', color: '#000' }}>深度目标出价:{deepConversionSpec.deepConversionBehaviorSpec.bidAmount}元/{OPTIMIZATIONGOAL_ENUM[deepConversionSpec.deepConversionBehaviorSpec.goal] || '优化目标'}</p>
+                </> : <>
+                    <p style={{ fontWeight: 'bold', color: '#000' }}>深度优化目标:{GOAL_ROAS_ENUM[deepConversionSpec.deepConversionWorthSpec.goal]}</p>
+                    <p style={{ fontWeight: 'bold', color: '#000' }}>期望ROI:{deepConversionSpec.deepConversionWorthSpec.expectedRoi}</p>
+                </>}
+            </>}
+            <p>一键起量:{autoAcquisitionEnabled ? '开启' : '关闭'}</p>
+            {autoAcquisitionEnabled && <p>起量预算:{autoAcquisitionBudget}元/天</p>}
+            <p>广告日预算:{dailyBudget ? dailyBudget + '元/天' : '不限'}</p>
+            <p style={{ fontWeight: 'bold', color: '#000' }}>投放日期:{beginDate} 至 {endDate}</p>
+            <p>投放时段:{timeSeries.includes('0') ? <TimeSeriesLook timeSeries={timeSeries} /> : '全天'}</p>
+            <p>首日开始时间:{firstDayBeginTime ? firstDayBeginTime : '关闭'}</p>
+            <p>广告状态:{AD_STATUS_ENUM[configuredStatus]}</p>
+            <p>广告名称:{adgroupName}</p>
+        </>}
+    </div>
+}
+
+
+export default React.memo(AdgroupTooltip)

+ 197 - 0
src/pages/launchSystemV3/components/BrandImage/index.tsx

@@ -0,0 +1,197 @@
+import { useAjax } from "@/Hook/useAjax"
+import SelectCloud from "@/pages/launchSystemNew/components/selectCloud"
+import { PlusOutlined } from "@ant-design/icons"
+import { Button, Divider, Form, Input, message, Modal, Select, Space, Table } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+import brandColumns from "./tableConfig"
+import { addSysBrandApi, delSysBrandApi, editSysBrandApi, getSysBrandApi } from "@/services/adqV3/global"
+
+interface Props {
+    onChange?: (data: any) => void,
+    value?: any,
+    id?: any
+}
+
+/**
+ * 品牌形象
+ * @returns 
+ */
+const BrandImage: React.FC<Props> = (props) => {
+
+    /****************************/
+    const { onChange, value, id } = props
+
+    const [visible, setVisible] = useState<boolean>(false)
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    const [form] = Form.useForm()
+    const [initialValues, setInitialValues] = useState<any>({})
+
+    const getSysBrand = useAjax(() => getSysBrandApi())
+    const addSysBrand = useAjax((params) => addSysBrandApi(params))
+    const editSysBrand = useAjax((params) => editSysBrandApi(params))
+    const delSysBrand = useAjax((params) => delSysBrandApi(params))
+    /****************************/
+
+    // 获取列表
+    useEffect(() => {
+        getSysBrand.run()
+    }, [])
+
+
+    // 新增修改
+    const handleOk = async () => {
+        form.submit()
+        let data = await form.validateFields()
+        if (Object.keys(initialValues).length > 0) { // 修改
+            editSysBrand.run({ ...data, sysBrandId: initialValues.id }).then(res => {
+                if (res) {
+                    message.success('修改成功')
+                    setAddVisible(false)
+                    getSysBrand.refresh()
+                }
+            })
+        } else { // 新增
+            addSysBrand.run(data).then(res => {
+                if (res) {
+                    message.success('新增成功')
+                    setAddVisible(false)
+                    getSysBrand.refresh()
+                }
+            })
+        }
+        setInitialValues({})
+    }
+
+    /** 删除 */
+    const del = (id: number) => {
+        delSysBrand.run({ sysBrandId: id }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getSysBrand.refresh()
+            }
+        })
+    }
+
+    /** 修改 */
+    const edit = (data: any) => {
+        setInitialValues(data)
+        setAddVisible(true)
+    }
+
+    return <div id={id}>
+        <Select
+            showSearch
+            placeholder="请选择一个品牌跳转页,与广告创意一起展示"
+            optionFilterProp="children"
+            style={{ width: 400 }}
+            onChange={(e) => { onChange && onChange(e) }}
+            allowClear
+            value={value}
+            filterOption={(input, option) => {
+                return (option!.value as unknown as string).toLowerCase().includes(input.toLowerCase())
+            }}
+            dropdownRender={menu => <>
+                {menu}
+                <Divider style={{ margin: '8px 0' }} />
+                <div>
+                    <Button type="link" onClick={() => { setAddVisible(true); setInitialValues({}) }}>新增</Button>
+                    <Button type="link" onClick={() => setVisible(true)}>前往管理</Button>
+                </div>
+            </>}
+        >
+            {
+                getSysBrand?.data?.map((item: any) => {
+                    return <Select.Option value={item.name + '_' + item.brandImgUrl} key={item.id}>
+                        <Space>
+                            <img src={item.brandImgUrl} style={{ width: 20 }} />
+                            <span>{item.name}</span>
+                        </Space>
+                    </Select.Option>
+                })
+            }
+        </Select>
+
+        {visible && <Modal title="品牌形象" width={1000} visible={visible} footer={null} onCancel={() => setVisible(false)}>
+            <Space direction='vertical' style={{ width: '100%' }}>
+                <Button type="primary" icon={<PlusOutlined />} onClick={() => { setAddVisible(true); setInitialValues({}) }}>上传品牌形象</Button>
+                <Table
+                    columns={brandColumns(del, edit)}
+                    dataSource={getSysBrand?.data}
+                    size="small"
+                    loading={getSysBrand?.loading}
+                    scroll={{ y: 300 }}
+                    bordered
+                    rowKey={'id'}
+                />
+            </Space>
+        </Modal>}
+
+        {addVisible && <Modal title="上传品牌形象" visible={addVisible} confirmLoading={addSysBrand.loading} onOk={handleOk} onCancel={() => setAddVisible(false)}>
+            <Form
+                name="basic"
+                form={form}
+                layout='vertical'
+                autoComplete="off"
+                initialValues={{ ...initialValues }}
+            >
+                <Form.Item label={<strong>头像</strong>} name="brandImgUrl" rules={[{ required: true, message: '请选择头像!' }]}>
+                    <UploadImage />
+                </Form.Item>
+                <Form.Item label={<strong>名称</strong>} name="name" rules={[{ required: true, message: '请输入名称!' }]}>
+                    <Input placeholder="请输入名称" maxLength={12} />
+                </Form.Item>
+            </Form>
+        </Modal>}
+    </div>
+}
+
+
+interface ImageProps {
+    onChange?: (value: string) => void,
+    value?: string
+}
+/**
+ * 处理选择图片Form
+ * @returns 
+ */
+export const UploadImage: React.FC<ImageProps> = (props) => {
+
+    /*********************/
+    const { onChange, value } = props
+    const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false)
+    const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([])  // 保存回填数据
+    const { init } = useModel('useLaunchAdq.useBdMediaPup')
+    /*********************/
+
+    useEffect(() => {
+        if (value) {
+            setSliderImgContent([{ url: value }])
+        } else {
+            setSliderImgContent([])
+        }
+    }, [value])
+
+    const setImg = (content: any[]) => {
+        onChange && onChange(content[0]?.url)
+        setSelectImgVisible(false)
+    }
+
+    const selectImg = () => {
+        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 512, height: 512 }]], maxSize: 400 * 1024 })
+        setTimeout(() => { setSelectImgVisible(true) }, 50)
+    }
+
+    return <>
+        {value ? <img src={value} onClick={selectImg} width={100} height={100} /> : <Button onClick={selectImg}>选择图片</Button>}
+        <div style={{ fontSize: 12, color: 'rgba(0,0,0,.5)' }}>
+            <div>图片尺寸:512×512 像素</div>
+            <div>图片格式:大小要求在 400KB 以内,仅支持 jpg 和 png 格式</div>
+        </div>
+
+        {/* 选择素材 */}
+        {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
+    </>
+}
+
+export default React.memo(BrandImage)

+ 73 - 0
src/pages/launchSystemV3/components/BrandImage/tableConfig.tsx

@@ -0,0 +1,73 @@
+import { Space, Image, Popconfirm, TableProps } from "antd"
+import React from "react"
+
+
+const brandColumns = (del: (id: number) => void, edit: (data: any) => void): TableProps<any>['columns'] => {
+
+
+    const data: any[] = [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            width: 100,
+            render: (a: any, b: any) => {
+                return <Space>
+                    <a onClick={() => edit(b)}>修改</a>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => del(b.id)}
+                        okText="是"
+                        cancelText="否"
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            }
+        },
+        {
+            title: 'ID',
+            dataIndex: 'id',
+            key: 'id',
+            width: 60,
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '头像预览图',
+            dataIndex: 'brandImgUrl',
+            key: 'brandImgUrl',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <Image width={40} style={{ borderRadius: 4 }} src={a} />
+            }
+        },
+        {
+            title: '头像名称',
+            dataIndex: 'name',
+            key: 'name',
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        }
+    ]
+
+    return data
+}
+
+export default brandColumns

+ 0 - 0
src/pages/launchSystemV3/components/DataSourceModal/index.less


+ 174 - 0
src/pages/launchSystemV3/components/DataSourceModal/index.tsx

@@ -0,0 +1,174 @@
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, message, Modal, Space, Table, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../GoodsModal/index.less'
+import columns from './tableConfig'
+import { getDataSourceV3Api, sysDataSourceV3Api } from "@/services/adqV3/global"
+
+/**
+ * 获取数据源
+ * @returns 
+ */
+interface Props {
+    promotedObjectType?: string
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const DataSourceModal: React.FC<Props> = (props) => {
+
+    /************************/
+    const { visible, onClose, data: data1, onChange, promotedObjectType } = props
+    const [tableData, setTableData] = useState<any[]>([])//table数据
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1)
+
+    const getDataSource = useAjax((params) => getDataSourceV3Api(params))
+    const sysDataSource = useAjax((params) => sysDataSourceV3Api(params))
+    /************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            getList([data[selectAdz - 1].accountId])
+        } else {
+            setTableData([])
+        }
+    }, [selectAdz])
+
+    // 获取数据源
+    const getList = (params: number[]) => {
+        getDataSource.run({ accountIds: params, promotedObjectType }).then(res => {
+            if (res && Object.keys(res)?.indexOf(params[0].toString()) !== -1) {
+                setTableData(res[params[0]])
+            } else {
+                setTableData([])
+            }
+        })
+    }
+
+    const handleOk = () => {
+        onChange && onChange(data)
+    }
+
+    // 同步数据源
+    const synDataSourceList = () => {
+        sysDataSource.run(data?.map(item => item?.accountId)).then(res => {
+            getList([data[selectAdz - 1].accountId])
+        })
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number, item: any) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (_: React.Key[], selectedRows: any) => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['userActionSetsList'] = selectedRows
+        setData([...newData])
+    }
+
+    /** 一键设置 */
+    const setOnekey = () => {
+        let dataSourceIds: number[] = data[selectAdz - 1]['userActionSetsList']?.map((item: { userActionSetId: number }) => item.userActionSetId) as number[]
+        let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        getDataSource.run({ accountIds: newData?.filter(item => item.accountId !== data[selectAdz - 1].accountId)?.map(item => item?.accountId), promotedObjectType }).then(res => {
+            if (res && typeof res === 'object') {
+                Object.keys(res).forEach((key: string) => {
+                    let values = dataSourceIds.map(id => {
+                        let value = res[key]?.find((item: { userActionSetId: number }) => item.userActionSetId === id)
+                        if (value) {
+                            return { ...value, id: value.userActionSetId }
+                        }
+                        return undefined
+                    }).filter(item => item)
+                    if (values.length > 0) {
+                        newData = newData.map(item => {
+                            if (item.accountId.toString() === key.toString()) {
+                                return { ...item, userActionSetsList: values }
+                            }
+                            return item
+                        })
+                    }
+                })
+                setData(newData)
+            }
+            message.success('设置完成');
+            hide()
+        })
+    }
+
+    // 清空已选
+    const clearUserActionSets = () => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['userActionSetsList'] = []
+        setData([...newData])
+    }
+
+    return <Modal
+        title={<Space>
+            <span>数据源</span>
+            <Button size="small" onClick={() => { synDataSourceList() }} type='link' loading={sysDataSource?.loading}>同步数据源</Button>
+        </Space>}
+        visible={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={900}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        let userActionSetsList = data?.[index]?.userActionSetsList || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1, item) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            {item?.accountId}
+                            {userActionSetsList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={0}>
+                    <Button icon={<SyncOutlined />} type='link' loading={getDataSource?.loading} onClick={() => { getList([data[selectAdz - 1].accountId]) }}>刷新</Button>
+                    {data.length > 1 && <Button disabled={!data[selectAdz - 1]['userActionSetsList']?.length} onClick={setOnekey} type="link" loading={getDataSource.loading}>
+                        <Space>
+                            <span>一键设置</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同ID的数据源为那个账号的数据源(注意需要数据源ID相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                    {(data[selectAdz - 1]?.userActionSetsList || [])?.length > 0 && <Button type='link' onClick={() => clearUserActionSets()}>清空</Button>}
+                </Space>
+                <Table
+                    columns={columns() as any}
+                    dataSource={tableData}
+                    size="small"
+                    loading={getDataSource?.loading}
+                    scroll={{ y: 400 }}
+                    bordered
+                    rowKey={'userActionSetId'}
+                    rowSelection={{
+                        type: 'checkbox',
+                        selectedRowKeys: data[selectAdz - 1]?.userActionSetsList?.map((item: any) => item?.userActionSetId),
+                        onChange: onChangeTable
+                    }}
+                />
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(DataSourceModal)

+ 38 - 0
src/pages/launchSystemV3/components/DataSourceModal/tableConfig.tsx

@@ -0,0 +1,38 @@
+import React from "react"
+let columns = () => [
+    {
+        title: '数据源名称',
+        dataIndex: 'name',
+        key: 'name',
+        ellipsis: true,
+        render: (a: any, b: any) => {
+            return <span style={{fontSize: "12px"}}>{a}</span>
+        }
+    },
+    {
+        title: '数据源ID',
+        dataIndex: 'userActionSetId',
+        key: 'userActionSetId',
+        align: 'center'
+    },
+    {
+        title: '类型',
+        dataIndex: 'type',
+        key: 'type',
+        align: 'center',
+        render: (a: any, b: any) => {
+            return <span style={{fontSize: "12px"}}>{a === 'WECHAT' ? '公众号' : a}</span>
+        }
+    },
+    {
+        title: '接入状态',
+        dataIndex: 'activateStatus',
+        key: 'activateStatus',
+        align: 'center',
+        render: (a: any, b: any) => {
+            return <span style={{fontSize: "12px"}}>{a ? '已接入' : '未接入'}</span>
+        }
+    }
+]
+
+export default columns

+ 83 - 0
src/pages/launchSystemV3/components/DynamicTooltip/index.tsx

@@ -0,0 +1,83 @@
+import React, { useEffect, useState } from "react"
+import style from '../../tencentAdPutIn/create/index.less'
+import { AD_STATUS_ENUM, CONVERSION_DATA_ENUM, CONVERSION_TARGET_ENUM, DELIVERY_MODE_ENUM, PAGE_TYPE_ENUM, TEXT_LINK_TYPE_ENUM, pageSpecFieldConVertUn } from "../../tencentAdPutIn/const"
+import { Space } from "antd"
+import { getProfilesApi } from "@/services/adqV3/global"
+import { useAjax } from "@/Hook/useAjax"
+
+
+interface Props {
+    data: any
+}
+
+/**
+ * 创意详情
+ * @param param0 
+ * @returns 
+ */
+const DynamicTooltip: React.FC<Props> = ({ data: dynamicData }) => {
+
+    /************************************/
+    const { deliveryMode, creativeTemplateId, dynamicCreativeName, creativeComponents, configuredStatus } = dynamicData
+    const { textLink, actionButton, showData, brand, mainJumpInfo } = creativeComponents || {}
+
+    const [profileData, setprofileData] = useState<any>()
+    const getProfiles = useAjax((params) => getProfilesApi(params))
+    /************************************/
+
+    useEffect(() => {
+        if (['PAGE_TYPE_H5_PROFILE'].includes(brand?.[0]?.value?.jumpInfo?.pageType)) {
+            getProfiles.run({}).then(res => {
+                if (res) {
+                    setprofileData(res?.find((item: { id: any }) => item.id === brand?.[0]?.value?.profileId))
+                }
+            })
+        }
+    }, [brand])
+    
+    return <div className={style.detail_body} style={{ height: 'auto' }}>
+        {dynamicData && Object.keys(dynamicData).length > 0 && <>
+            <p>创意名称:{dynamicCreativeName}</p>
+            <p style={{ fontWeight: 'bold', color: '#000' }}>创意状态:{AD_STATUS_ENUM[configuredStatus]}</p>
+            <p>投放模式:{DELIVERY_MODE_ENUM[deliveryMode]}</p>
+            <p>创意形式ID:{creativeTemplateId}</p>
+            {brand?.length > 0 && <>
+                <p style={{ fontWeight: 'bold', color: '#000' }}>品牌形象跳转:{PAGE_TYPE_ENUM[brand?.[0]?.value?.jumpInfo?.pageType]}</p>
+                {['PAGE_TYPE_H5_PROFILE'].includes(brand?.[0]?.value?.jumpInfo?.pageType) ? <>
+                    {profileData ? <>
+                        <Space>
+                            <img src={profileData?.headImageUrl} alt="" width={20} style={{ display: 'block' }} />
+                            <span style={{ fontWeight: 'bold', color: '#000', fontSize: 12 }}>{profileData?.profileName}</span>
+                        </Space>
+                        <p>详细描述:{profileData?.description}</p>
+                    </> : <span style={{ color: 'red', fontWeight: 'bold', fontSize: 12 }}>{getProfiles.loading ? '' : '当前头像昵称跳转页被删除'} </span>}
+                </> : ['PAGE_TYPE_NOT_USED'].includes(brand?.[0]?.value?.jumpInfo?.pageType) && <Space align="center">
+                    <img src={brand?.[0]?.value?.brandImageId} alt="" width={20} style={{ display: 'block' }} />
+                    <span style={{ fontWeight: 'bold', color: '#000', fontSize: 12 }}>{brand?.[0]?.value?.brandName}</span>
+                </Space>}
+            </>}
+            <p style={{ fontWeight: 'bold', color: '#000' }}>跳转类型:{mainJumpInfo?.map((item: any) => {
+                let pageSpec = item.value.pageSpec
+                return PAGE_TYPE_ENUM[pageSpecFieldConVertUn[Object.keys(pageSpec)?.[0]]]
+            }).toString()}</p>
+            {textLink?.length > 0 && <>
+                <p style={{ fontWeight: 'bold', color: '#000' }}>朋友圈文字链:开启</p>
+                <p>文字链文案:{TEXT_LINK_TYPE_ENUM[textLink?.[0]?.value?.linkNameType]}</p>
+                {textLink?.[0]?.value?.jumpInfo?.pageType && <p>跳转落地页:{PAGE_TYPE_ENUM[textLink?.[0]?.value?.jumpInfo?.pageType]}</p>}
+            </>}
+            {actionButton?.length > 0 && <>
+                <p style={{ fontWeight: 'bold', color: '#000' }}>行动按钮:开启</p>
+                <p>按钮文案:{actionButton?.[0]?.value?.buttonText}</p>
+                {actionButton?.[0]?.value?.jumpInfo?.pageType && <p>跳转落地页:{PAGE_TYPE_ENUM[actionButton?.[0]?.value?.jumpInfo?.pageType]}</p>}
+            </>}
+            {showData?.length > 0 && <>
+                <p style={{ fontWeight: 'bold', color: '#000' }}>数据外显:开启</p>
+                <p>数据类型:{CONVERSION_DATA_ENUM[showData?.[0]?.value?.conversionDataType]}</p>
+                <p>转化行为:{CONVERSION_TARGET_ENUM[showData?.[0]?.value?.conversionTargetType]}</p>
+            </>}
+        </>}
+    </div>
+}
+
+
+export default React.memo(DynamicTooltip)

+ 124 - 0
src/pages/launchSystemV3/components/GoodsModal/index.less

@@ -0,0 +1,124 @@
+.SelectPackage {
+    .topContent {
+        padding: 20px 10px 4px;
+
+        .title {
+            width: 135px;
+            display: inline-block;
+        }
+    }
+
+    .content {
+        width: 100%;
+        display: flex;
+        justify-content: flex-start;
+        min-height: 200px;
+
+        .left {
+            width: 150px;
+            padding-top: 10px;
+            border-right: 1px solid #f0f0f0;
+            box-sizing: border-box;
+
+            .title {
+                font-weight: 700;
+                padding-left: 10px;
+                box-sizing: border-box;
+            }
+
+            .accountIdList {
+                max-height: 510px;
+                overflow: hidden;
+                overflow-y: auto;
+
+                &::-webkit-scrollbar {
+                    width: 1px;
+                    height: 1px;
+                }
+            }
+
+            .accItem {
+                height: 32px;
+                line-height: 32px;
+                cursor: pointer;
+                padding-left: 10px;
+                padding-right: 10px;
+                box-sizing: border-box;
+                margin-bottom: 1px;
+                box-sizing: border-box;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+
+                &:hover {
+                    background-color: #e6f7ff;
+                    color: #1890ff;
+                }
+            }
+
+            .select {
+                background-color: #e6f7ff;
+                color: #1890ff;
+                border-right: 2px solid #1890ff;
+            }
+
+            .alreadySelect {
+                background-color: #d1f0ff;
+            }
+        }
+
+        .right {
+            padding: 10px;
+            box-sizing: border-box;
+            // width: calc(100% - 150px);
+            flex: 1;
+            overflow: hidden;
+        }
+
+        .center {
+            width: 150px;
+            border-left: 1px solid #e8e8e8;
+            padding: 5px;
+            box-sizing: border-box;
+
+            .select_content {
+                height: calc(100% - 28px);
+                overflow: hidden;
+                overflow-y: auto;
+
+                > div {
+                    padding: 3px 4px;
+                    background-color: #ebebeb;
+                    margin-bottom: 2px;
+                    border-radius: 4px;
+                    display: flex;
+                    align-items: center;
+                }
+            }
+            .marketingAssetName {
+                flex: 1;
+                overflow: hidden;
+                font-size: 12px;
+            }
+            .close {
+                width: 20px;
+                cursor: pointer;
+                color: red;
+            }
+        }
+    }
+
+    .refresh {
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+
+        .tips {
+            margin-left: 20px;
+
+            div {
+                font-size: 12px;
+            }
+        }
+    }
+}

+ 208 - 0
src/pages/launchSystemV3/components/GoodsModal/index.tsx

@@ -0,0 +1,208 @@
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, CloseOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, Input, message, Modal, Space, Table, Tooltip, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import style from './index.less'
+import columns from './tableConfig'
+import { getmarketingAssetContentApi, synMarketingAssetContentApi } from "@/services/adqV3/global"
+const { Title, Text } = Typography;
+
+/**
+ * 获取商品列表
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const GoodsModal: React.FC<Props> = (props) => {
+
+    /************************/
+    const { visible, onClose, data: data1, onChange } = props
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1)
+    const [queryForm, setQueryForm] = useState<{
+        marketingAssetType?: string,
+        marketingAssetName?: string,
+        pageNum: number,
+        pageSize: number,
+        accountId?: number
+    }>({ pageNum: 1, pageSize: 50 })
+
+    const getmarketingAssetContent = useAjax((params) => getmarketingAssetContentApi(params))
+    const synMarketingAssetContent = useAjax((params) => synMarketingAssetContentApi(params))
+    /************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            getList({ accountId: data[selectAdz - 1].accountId, pageNum: 1, pageSize: 50 })
+        } else {
+            getmarketingAssetContent.data && getmarketingAssetContent.mutate({ records: [] })
+        }
+    }, [selectAdz])
+
+    // 获取商品列表
+    const getList = (data: {
+        marketingAssetType?: string,
+        marketingAssetName?: string,
+        pageNum: number,
+        pageSize: number,
+        accountId?: number
+    }) => {
+        setQueryForm(data)
+        getmarketingAssetContent.run(data)
+    }
+
+    const handleOk = () => {
+        onChange && onChange(data)
+    }
+
+    // 同步小说
+    const synGoodsList = () => {
+        synMarketingAssetContent.run({ accountId: data[selectAdz - 1].accountId }).then(res => {
+            if (res) {
+                message.success('同步成功,结果可能几分钟后展示,请勿重复点击同步')
+            }
+        })
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (_: React.Key[], selectedRows: any) => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['productList'] = selectedRows
+        setData([...newData])
+    }
+
+    // 清空已选
+    const clearGoods = () => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['productList'] = []
+        setData([...newData])
+    }
+
+    /** 一键设置 */
+    const setOnekey = () => {
+        let marketingAssetNameList: string[] = data[selectAdz - 1]['productList']?.map((item: { marketingAssetName: string }) => item.marketingAssetName) || []
+        let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        let dataAjax = newData?.filter(item => item.accountId !== data[selectAdz - 1].accountId)?.map(item => item?.accountId).map(accountId => {
+            return getmarketingAssetContentApi({ pageNum: 1, pageSize: 100, accountId: accountId, marketingAssetNameList })
+        })
+        Promise.all(dataAjax).then(res => {
+            if (res?.length > 0) {
+                res.forEach(ajax => {
+                    let data = ajax.data?.records
+                    if (data?.length > 0) {
+                        let accountId = data[0].accountId
+                        newData = newData.map(item => {
+                            if (item.accountId.toString() === accountId.toString()) {
+                                return { ...item, productList: ajax.data?.records }
+                            }
+                            return item
+                        })
+                    }
+                })
+            }
+            setData(newData)
+            message.success('设置完成');
+            hide()
+        }).catch(() => {
+            hide()
+            message.error('设置失败')
+        })
+    }
+
+    return <Modal
+        title={<Space>
+            <strong>产品库</strong>
+            <Button size="small" style={{ padding: 0 }} onClick={() => { synGoodsList() }} type="link" loading={synMarketingAssetContent?.loading}>同步小说</Button>
+        </Space>}
+        visible={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={1000}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        let productList = data[index].productList || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            {item?.accountId}
+                            {productList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={0}>
+                    <Input allowClear placeholder="请输入商品名称" value={queryForm.marketingAssetName} onChange={(e) => getList({ ...queryForm, marketingAssetName: e.target.value, pageNum: 1 })} />
+                    <Button icon={<SyncOutlined />} type='link' loading={getmarketingAssetContent?.loading} onClick={() => { getList({ ...queryForm, accountId: data[selectAdz - 1].accountId }) }}>刷新</Button>
+                    {data?.length > 1 && <Button disabled={!data[selectAdz - 1]['productList']?.length} onClick={setOnekey} type="link" loading={getmarketingAssetContent.loading}>
+                        <Space>
+                            <span>一键设置</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同名称的商品为那个账号的商品(注意需要用户商品称相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                    {(data[selectAdz - 1]?.productList || [])?.length > 0 && <Button type='link' onClick={() => { clearGoods() }}>清空</Button>}
+                </Space>
+                <Table
+                    columns={columns()}
+                    dataSource={getmarketingAssetContent.data?.records}
+                    size="small"
+                    loading={getmarketingAssetContent?.loading}
+                    scroll={{ y: 400 }}
+                    bordered
+                    pagination={{
+                        defaultPageSize: 100,
+                        current: getmarketingAssetContent.data?.current || 1,
+                        pageSize: getmarketingAssetContent.data?.size || 50,
+                        total: getmarketingAssetContent.data?.total || 0
+                    }}
+                    rowKey={'marketingAssetId'}
+                    rowSelection={{
+                        type: 'checkbox',
+                        selectedRowKeys: data[selectAdz - 1]?.productList?.map((item: any) => item?.marketingAssetId),
+                        onChange: onChangeTable
+                    }}
+                    onChange={(pagination) => {
+                        const { current, pageSize } = pagination
+                        getList({ ...queryForm, pageNum: current || 1, pageSize: pageSize || 50 })
+                    }}
+                />
+            </div>
+            <div className={style.center}>
+                <Title level={5}>已选:{data[selectAdz - 1]?.productList?.length || 0}</Title>
+                <div className={style.select_content}>
+                    {data[selectAdz - 1]?.productList?.map(item => <div key={item.marketingAssetId}>
+                        <Text ellipsis={{ tooltip: true }} className={style.marketingAssetName}>{item.marketingAssetName}</Text>
+                        <CloseOutlined className={style.close} onClick={() => {
+                            let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+                            newData[selectAdz - 1].productList = newData[selectAdz - 1]?.productList?.filter((i: any) => i?.marketingAssetId !== item.marketingAssetId)
+                            setData(newData)
+                        }}/>
+                    </div>)}
+                </div>
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(GoodsModal)

+ 37 - 0
src/pages/launchSystemV3/components/GoodsModal/tableConfig.tsx

@@ -0,0 +1,37 @@
+import { TableProps } from "antd"
+import React from "react"
+let columns = (): TableProps<any>['columns'] => {
+    return [
+        {
+            title: '小说名称',
+            dataIndex: 'marketingAssetName',
+            key: 'marketingAssetName',
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '商品封面',
+            dataIndex: 'productImageUrl',
+            key: 'productImageUrl',
+            ellipsis: true,
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <img src={a} style={{ height: 35 }} />
+            }
+        },
+        {
+            title: '分类',
+            dataIndex: 'fictionType',
+            key: 'fictionType',
+            align: 'center',
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+    ]
+}
+
+export default columns

+ 55 - 0
src/pages/launchSystemV3/components/New1Radio/index.less

@@ -0,0 +1,55 @@
+.new1Radio {
+    border: 1px solid #E2E5EA;
+    border-radius: 999px;
+    padding: 3px;
+    display: inline-flex;
+    gap: 4px;
+
+    >div {
+        height: 28px;
+        padding: 0 20px;
+        box-sizing: border-box;
+        border-radius: 999px;
+        align-items: center;
+        position: relative;
+        line-height: 28px;
+        color: rgba(0, 0, 0, 0.95);
+        font-weight: 500;
+        cursor: pointer;
+        user-select: none;
+
+        >span {
+            display: block;
+            transition-property: opacity, transform, right, left;
+            transition-duration: .2s;
+        }
+
+        >div {
+            position: absolute;
+            left: 12px;
+            content: '';
+            width: 16px;
+            height: 16px;
+            color: #1890ff;
+            opacity: 0;
+        }
+
+        &.newRadioActive {
+            background: #f2f4fa;
+
+            >div {
+                opacity: 1;
+            }
+
+            >span {
+                transform: translateX(8px);
+                font-weight: 600;
+            }
+        }
+
+        &.disabled {
+            color: rgb(196, 199, 204);
+            cursor: not-allowed;
+        }
+    }
+}

+ 26 - 0
src/pages/launchSystemV3/components/New1Radio/index.tsx

@@ -0,0 +1,26 @@
+import React from "react"
+import style from './index.less'
+import { CheckCircleFilled } from "@ant-design/icons"
+
+/**
+ * 新的Radio样式
+ * @param param0 
+ * @returns 
+ */
+const New1Radio: React.FC<PULLIN.FormItemDataNewProps> = ({ data = [], value, onChange, id }) => {
+
+    return <div className={style.new1Radio} id={id}>
+        {data.map((item, index) => <div
+            onClick={() => {
+                if (!item?.disabled) onChange?.(item?.value)
+            }}
+            key={item?.value || index}
+            className={`${value === item.value ? style.newRadioActive : ''} ${item?.disabled ? style.disabled : ''}`}
+        >
+            <div><CheckCircleFilled /></div>
+            <span>{item?.label || item?.value || index}</span>
+        </div>)}
+    </div>
+}
+
+export default React.memo(New1Radio)

+ 54 - 0
src/pages/launchSystemV3/components/New2Radio/index.less

@@ -0,0 +1,54 @@
+.new2Radio {
+    width: 100%;
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr 1fr;
+    grid-template-rows: auto;
+    row-gap: 10px;
+    column-gap: 10px;
+
+    .spaui_utton {
+        width: 100%;
+        padding: 9px;
+        box-sizing: border-box;
+        background-color: #F2F4FA;
+        display: flex;
+        align-items: center;
+        border-radius: 6px;
+        color: rgb(49, 50, 51);
+        border: 1px solid transparent;
+        position: relative;
+        cursor: pointer;
+
+        .img {
+            width: 22px;
+            height: 36px;
+            margin-right: 6px;
+        }
+
+        &:hover {
+            background-color: rgb(235, 237, 245);
+        }
+
+        .tag_top_right {
+            position: absolute;
+            top: -8px;
+            right: -8px;
+            font-size: 10px;
+            background-color: rgb(255, 230, 196);
+            display: inline-block;
+            padding: 1px 4px;
+            border-radius: 5px;
+            color: rgb(230, 131, 0);
+        }
+    }
+
+    .active {
+        box-shadow: 0 3px 6px rgba(0, 0, 0, .04);
+        border-color: #1890ff;
+        background-color: #FFFFFF;
+
+        &:hover {
+            background-color: #FFFFFF;
+        }
+    }
+}

+ 23 - 0
src/pages/launchSystemV3/components/New2Radio/index.tsx

@@ -0,0 +1,23 @@
+import React from "react"
+import style from './index.less'
+import { Tooltip } from "antd"
+
+
+/**
+ * 新的radio杨撒
+ * @returns 
+ */
+const New2Radio: React.FC<PULLIN.FormItemDataNew2Props> = ({ data = [], value, onChange, id }) => {
+
+    return <div id={id} className={style.new2Radio}>
+        {data.map(item => <Tooltip title={item?.tooltip} visible={item?.tooltip ? undefined : false} key={item.value}>
+            <div className={`${style.spaui_utton} ${value === item.value ? style.active : ''}`} onClick={() => onChange?.(item.value)}>
+                {item?.img && <img className={style.img} src={item?.img} />}
+                <span>{item?.label}</span>
+                {item?.isGeneral && <span className={style.tag_top_right}>通投</span>}
+            </div>
+        </Tooltip>)}
+    </div>
+}
+
+export default React.memo(New2Radio)

+ 46 - 0
src/pages/launchSystemV3/components/NewRadio/index.less

@@ -0,0 +1,46 @@
+.newRadio {
+    display: flex;
+    gap: 4px;
+    flex-wrap: wrap;
+
+    >div {
+        height: 36px;
+        padding: 0 20px;
+        box-sizing: border-box;
+        border-radius: 8px;
+        align-items: center;
+        position: relative;
+        line-height: 36px;
+        background: #f2f4fa;
+        color: rgba(0, 0, 0, 0.95);
+        font-weight: 500;
+        cursor: pointer;
+
+        >span {
+            display: block;
+            transition-property: opacity, transform, right, left;
+            transition-duration: .2s;
+        }
+
+        >div {
+            position: absolute;
+            left: 12px;
+            content: '';
+            width: 16px;
+            height: 16px;
+            color: #1890ff;
+            opacity: 0;
+        }
+
+        &.newRadioActive {
+            >div {
+                opacity: 1;
+            }
+
+            >span {
+                transform: translateX(8px);
+                font-weight: 600;
+            }
+        }
+    }
+}

+ 20 - 0
src/pages/launchSystemV3/components/NewRadio/index.tsx

@@ -0,0 +1,20 @@
+import React from "react"
+import style from './index.less'
+import { CheckCircleFilled } from "@ant-design/icons"
+
+/**
+ * 推广产品
+ * @param param0 
+ * @returns 
+ */
+const NewRadio: React.FC<PULLIN.FormItemDataNewProps> = ({ data = [], value, onChange, id }) => {
+
+    return <div className={style.newRadio} id={id}>
+        {data.map((item, index) => <div onClick={() => onChange?.(item?.value)} key={item?.value || index} className={`${value === item.value ? style.newRadioActive : ''}`}>
+            <div><CheckCircleFilled /></div>
+            <span>{item?.label || item?.value || index}</span>
+        </div>)}
+    </div>
+}
+
+export default React.memo(NewRadio)

+ 51 - 0
src/pages/launchSystemV3/components/NewTree/index.less

@@ -0,0 +1,51 @@
+.newTree {
+    border: 1px solid #E2E5EA;
+    border-radius: 8px;
+    padding: 0 10px;
+    box-sizing: border-box;
+    max-width: 480px;
+    user-select: none;
+
+    .newTree_col {
+        padding: 12px 0;
+
+
+        &:not(:last-child) {
+            border-bottom: 1px solid #E2E5EA;
+        }
+    }
+
+    .newTree_col_content {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .newTree_col_content_childs {
+            display: flex;
+            flex-direction: column;
+        }
+        .child {
+            padding: 12px 0 0 24px;
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+
+            > div {
+                display: flex;
+                align-items: center;
+            }
+        }
+
+        .icons {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+        }
+
+        .open {
+            cursor: pointer;
+            color: #979797;
+        }
+    }
+
+}

+ 280 - 0
src/pages/launchSystemV3/components/NewTree/index.tsx

@@ -0,0 +1,280 @@
+import React, { useState } from "react"
+import style from './index.less'
+import { Checkbox, Space, Tooltip } from "antd"
+import { ReactComponent as AppiconChannelsSvg } from '../../../../../public/iconspull/appicon-channels.svg'
+import { ReactComponent as AppiconMementsSvg } from '../../../../../public/iconspull/appicon-mements.svg'
+import { ReactComponent as AppiconMpSvg } from '../../../../../public/iconspull/appicon-mp.svg'
+import { ReactComponent as AppiconMinappSvg } from '../../../../../public/iconspull/appicon-minapp.svg'
+import { ReactComponent as AppiconQqwebSvg } from '../../../../../public/iconspull/appicon-qqweb.svg'
+import { ReactComponent as AppiconQbrowserSvg } from '../../../../../public/iconspull/appicon-qbrowser.svg'
+import { ReactComponent as AppiconQqSvg } from '../../../../../public/iconspull/appicon-qq.svg'
+import { ReactComponent as AppiconQmusicSvg } from '../../../../../public/iconspull/appicon-qmusic.svg'
+import { ReactComponent as AppiconGameSvg } from '../../../../../public/iconspull/appicon-game.svg'
+import { ReactComponent as AppiconVqqSvg } from '../../../../../public/iconspull/appicon-vqq.svg'
+import { ReactComponent as AppiconAdnetSvg } from '../../../../../public/iconspull/appicon-adnet.svg'
+import { ReactComponent as AppiconYybSvg } from '../../../../../public/iconspull/appicon-yyb.svg'
+import { QuestionCircleFilled, RightOutlined, UpOutlined } from "@ant-design/icons"
+
+import appiconAdnetImg from '../../../../../public/iconspull/appicon-adnet.png'
+
+const NewTree: React.FC<PULLIN.FormItemDataArrayProps> = ({ value = [], onChange, id }) => {
+
+    // { lebel: ReactNode, value: string,  }
+    /***********************************/
+    const [open1, setOpen1] = useState<boolean>(false)
+    const [open2, setOpen2] = useState<boolean>(false)
+    /***********************************/
+
+
+    const handleChange = (checkedValue: any[], isCheck: boolean, isYyb?: boolean) => {
+        let newValue: string[] = JSON.parse(JSON.stringify(value)) || []
+        if (isYyb) {
+            newValue = isCheck ? checkedValue : []
+        } else {
+            if (isCheck) {
+                newValue = newValue.concat(checkedValue)
+                newValue = [...new Set(newValue.filter(item => item !== 'SITE_SET_MOBILE_YYB'))]
+            } else {
+                newValue = newValue.filter(item => !checkedValue.includes(item))
+            }
+        }
+        onChange?.(newValue)
+    }
+
+    return <div className={style.newTree} id="id">
+        <div className={style.newTree_col}>
+            <div className={style.newTree_col_content}>
+                <Checkbox
+                    value={['SITE_SET_CHANNELS']}
+                    checked={value.includes('SITE_SET_CHANNELS')}
+                    onChange={(e) => {
+                        handleChange(e.target.value, e.target.checked)
+                    }}
+                >
+                    <Space>
+                        <span>微信视频号</span>
+                        <Tooltip title="向微信全场景内的视频号浏览用户展示的原生广告">
+                            <QuestionCircleFilled />
+                        </Tooltip>
+                    </Space>
+                </Checkbox>
+                <div className={style.icons}>
+                    <AppiconChannelsSvg />
+                </div>
+            </div>
+        </div>
+        <div className={style.newTree_col}>
+            <div className={style.newTree_col_content}>
+                <Checkbox
+                    value={['SITE_SET_MOMENTS']}
+                    checked={value.includes('SITE_SET_MOMENTS')}
+                    onChange={(e) => {
+                        handleChange(e.target.value, e.target.checked)
+                    }}
+                >
+                    <Space>
+                        <span>微信朋友圈</span>
+                        <Tooltip title="以类似朋友的原创内容形式在用户朋友圈进行展示的原生广告">
+                            <QuestionCircleFilled />
+                        </Tooltip>
+                    </Space>
+                </Checkbox>
+                <div className={style.icons}>
+                    <AppiconMementsSvg />
+                </div>
+            </div>
+        </div>
+        <div className={style.newTree_col}>
+            <div className={style.newTree_col_content}>
+                <div className={style.newTree_col_content_childs}>
+                    <Checkbox
+                        value={['SITE_SET_WECHAT', 'SITE_SET_WECHAT_PLUGIN']}
+                        checked={value.includes('SITE_SET_WECHAT') && value.includes('SITE_SET_WECHAT_PLUGIN')}
+                        onChange={(e) => {
+                            handleChange(e.target.value, e.target.checked)
+                        }}
+                    >
+                        <Space>
+                            <span>微信公众号与小程序</span>
+                            <Tooltip title="基于微信公众号、看一看与视频号评论区等信息流广告场景和基于小程序与小游戏的生态广告场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                            <div
+                                className={style.open}
+                                onClick={(e) => {
+                                    e.stopPropagation()
+                                    e.preventDefault()
+                                    setOpen1(!open1)
+                                }}
+                            >{open1 ? <UpOutlined /> : <RightOutlined />}</div>
+                        </Space>
+                    </Checkbox>
+                    {open1 && <div className={style.child}>
+                        <div>
+                            <Checkbox
+                                value={['SITE_SET_WECHAT']}
+                                checked={value.includes('SITE_SET_WECHAT')}
+                                disabled
+                                onChange={(e) => {
+                                    handleChange(e.target.value, e.target.checked)
+                                }}
+                            >微信公众号与小程序</Checkbox>
+                            <Tooltip title="基于微信公众号、小程序与小游戏生态的广告场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </div>
+                        <div>
+                            <Checkbox
+                                value={['SITE_SET_WECHAT_PLUGIN']}
+                                checked={value.includes('SITE_SET_WECHAT_PLUGIN')}
+                                disabled
+                                onChange={(e) => {
+                                    handleChange(e.target.value, e.target.checked)
+                                }}
+                            >微信新闻插件</Checkbox>
+                            <Tooltip title="微信内部的腾讯新闻以及微视短视频相关订阅插件场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </div>
+                    </div>}
+                </div>
+                <div className={style.icons} style={open1 ? { alignSelf: 'flex-start' } : {}}>
+                    <AppiconMpSvg />
+                    <AppiconMinappSvg />
+                    <AppiconQqwebSvg />
+                </div>
+            </div>
+        </div>
+        <div className={style.newTree_col}>
+            <div className={style.newTree_col_content}>
+                <div className={style.newTree_col_content_childs}>
+                    <Checkbox
+                        value={['SITE_SET_KANDIAN', 'SITE_SET_QQ_MUSIC_GAME', 'SITE_SET_TENCENT_NEWS', 'SITE_SET_TENCENT_VIDEO']}
+                        checked={value.includes('SITE_SET_KANDIAN') && value.includes('SITE_SET_QQ_MUSIC_GAME') && value.includes('SITE_SET_TENCENT_NEWS') && value.includes('SITE_SET_TENCENT_VIDEO')}
+                        indeterminate={(value.includes('SITE_SET_KANDIAN') || value.includes('SITE_SET_QQ_MUSIC_GAME') || value.includes('SITE_SET_TENCENT_NEWS') || value.includes('SITE_SET_TENCENT_VIDEO')) && !(value.includes('SITE_SET_KANDIAN') && value.includes('SITE_SET_QQ_MUSIC_GAME') && value.includes('SITE_SET_TENCENT_NEWS') && value.includes('SITE_SET_TENCENT_VIDEO'))}
+                        onChange={(e) => {
+                            handleChange(e.target.value, e.target.checked)
+                        }}
+                    >
+                        <Space>
+                            <span>腾讯平台与内容媒体</span>
+                            <Tooltip title="汇聚腾讯产品矩阵,全场景打通腾讯生态内优质媒体与平台">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                            <div
+                                className={style.open}
+                                onClick={(e) => {
+                                    e.stopPropagation()
+                                    e.preventDefault()
+                                    setOpen2(!open2)
+                                }}
+                            >{open2 ? <UpOutlined /> : <RightOutlined />}</div>
+                        </Space>
+                    </Checkbox>
+                    {open2 && <div className={style.child}>
+                        <div>
+                            <Checkbox
+                                value={['SITE_SET_KANDIAN']}
+                                checked={value.includes('SITE_SET_KANDIAN')}
+                                onChange={(e) => {
+                                    handleChange(e.target.value, e.target.checked)
+                                }}
+                            >QQ 浏览器</Checkbox>
+                            <Tooltip title="包括QQ 浏览器及QQ 看点等场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </div>
+                        <div>
+                            <Checkbox
+                                value={['SITE_SET_QQ_MUSIC_GAME']}
+                                checked={value.includes('SITE_SET_QQ_MUSIC_GAME')}
+                                onChange={(e) => {
+                                    handleChange(e.target.value, e.target.checked)
+                                }}
+                            >QQ、腾讯音乐及游戏</Checkbox>
+                            <Tooltip title="QQ 空间、公众号、小程序,QQ 音乐、全民K歌、腾讯游戏等场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </div>
+                        <div>
+                            <Checkbox
+                                value={['SITE_SET_TENCENT_NEWS']}
+                                checked={value.includes('SITE_SET_TENCENT_NEWS')}
+                                onChange={(e) => {
+                                    handleChange(e.target.value, e.target.checked)
+                                }}
+                            >腾讯新闻</Checkbox>
+                            <Tooltip title="腾讯新闻信息流、插件、闪屏等场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </div>
+                        <div>
+                            <Checkbox
+                                value={['SITE_SET_TENCENT_VIDEO']}
+                                checked={value.includes('SITE_SET_TENCENT_VIDEO')}
+                                onChange={(e) => {
+                                    handleChange(e.target.value, e.target.checked)
+                                }}
+                            >腾讯视频</Checkbox>
+                            <Tooltip title="腾讯视频信息流、焦点图、贴片、闪屏等场景">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </div>
+                    </div>}
+                </div>
+                <div className={style.icons} style={open2 ? { alignSelf: 'flex-start' } : {}}>
+                    <AppiconQbrowserSvg />
+                    <AppiconQqSvg />
+                    <AppiconQmusicSvg />
+                    <AppiconGameSvg />
+                    <AppiconQqwebSvg />
+                    <AppiconVqqSvg />
+                </div>
+            </div>
+        </div>
+        <div className={style.newTree_col}>
+            <div className={style.newTree_col_content}>
+                <Checkbox
+                    value={['SITE_SET_MOBILE_UNION']}
+                    checked={value.includes('SITE_SET_MOBILE_UNION')}
+                    onChange={(e) => {
+                        handleChange(e.target.value, e.target.checked)
+                    }}
+                >
+                    <Space>
+                        <span>优量汇</span>
+                        <Tooltip title="集合数万优质媒体海量曝光,覆盖10亿+人群全营销场景的优量生态">
+                            <QuestionCircleFilled />
+                        </Tooltip>
+                    </Space>
+                </Checkbox>
+                <div className={style.icons}>
+                    <img src={appiconAdnetImg} width={20} height={20} alt="优量汇" />
+                </div>
+            </div>
+        </div>
+        {/* <div className={style.newTree_col}>
+            <div className={style.newTree_col_content}>
+                <Checkbox
+                    value={['SITE_SET_MOBILE_YYB']}
+                    checked={value.includes('SITE_SET_MOBILE_YYB')}
+                    onChange={(e) => {
+                        handleChange(e.target.value, e.target.checked, true)
+                    }}
+                >
+                    <Space>
+                        <span>应用宝</span>
+                        <Tooltip title="应用宝场景,首页、搜索、分类页等位置">
+                            <QuestionCircleFilled />
+                        </Tooltip>
+                    </Space>
+                </Checkbox>
+                <div className={style.icons}>
+                    <AppiconYybSvg />
+                </div>
+            </div>
+        </div> */}
+    </div>
+}
+
+export default React.memo(NewTree)

+ 179 - 0
src/pages/launchSystemV3/components/PageModal/index.tsx

@@ -0,0 +1,179 @@
+
+import Tables from "@/components/Tables"
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, Input, message, Modal, Space, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../GoodsModal/index.less'
+import columns from "./tableConfig"
+import { getAdqLandingPageListApi, putAdqLandingPageApi } from "@/services/adqV3/global"
+
+
+/**
+ * 获取adq落地页
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    adgroups: any
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const PageModal: React.FC<Props> = (props) => {
+
+    /*************************/
+    const { visible, onClose, data: data1, adgroups, onChange } = props
+    const { marketingGoal, marketingAssetOuterSpec: { marketingTargetType }, marketingCarrierType, siteSet } = adgroups
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1 || [])
+    const [queryForm, setQueryForm] = useState<{ accountId?: number, pageName?: string, pageSize: number, pageNum: number }>({ pageNum: 1, pageSize: 20 })
+    const [loading, setLoading] = useState<boolean>(false)
+
+    const listAjax = useAjax((params) => getAdqLandingPageListApi(params))
+    /*************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            setQueryForm({ ...queryForm, pageNum: 1, accountId: data[selectAdz - 1].accountId })
+        }
+    }, [selectAdz])
+
+    useEffect(() => {
+        if (queryForm?.accountId) {
+            getList()
+        }
+    }, [queryForm, marketingGoal, marketingTargetType, marketingCarrierType, siteSet])
+
+    // 获取落地页列表
+    const getList = () => {
+        listAjax.run({ ...queryForm, pageStatus: 'NORMAL', marketingGoal, marketingTargetType, marketingCarrierType, siteSet })
+    }
+
+    const handleOk = () => {
+        if (data?.every(item => item.pageList)) {
+            onChange && onChange(data)
+        } else {
+            message.error('还有账号未选择落地页!')
+        }
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number, item: any) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (selectedRowKeys: React.Key[], selectedRows: any) => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['pageList'] = selectedRows
+        setData([...newData])
+    }
+
+    /** 同步落地页 */
+    const synPageList = () => {
+        setLoading(true)
+        putAdqLandingPageApi({ accountIdList: data?.map(item => item.accountId), isUsePool: false }).then(res => {
+            message.success('同步成功,结果可能需要几分钟返回,请手动点击下面刷新按钮')
+            setLoading(false)
+            listAjax.refresh()
+        }).catch(() => setLoading(false))
+    }
+
+    /** 一键设置 */
+    const setOnekey = () => {
+        let pageName: string = data[selectAdz - 1]['pageList']?.[0]?.pageName
+        let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        let ajax = newData?.filter(item => item.accountId !== data[selectAdz - 1].accountId)?.map(item => {
+            return getAdqLandingPageListApi({ pageName, accountId: item.accountId, pageNum: 1, pageSize: 20, pageStatus: 'NORMAL' })
+        })
+        Promise.all(ajax).then(res => {
+            if (res && Array.isArray(res)) {
+                res.forEach((item: any) => {
+                    let records = item?.data?.records
+                    if (Array.isArray(records) && records?.length > 0) {
+                        let record = records[0]
+                        newData = newData.map(item => {
+                            if (item.accountId.toString() === record.accountId.toString()) {
+                                return { ...item, pageList: [{ ...record, id: record.pageId }] }
+                            }
+                            return item
+                        })
+                    }
+                })
+                setData(newData)
+            }
+            message.success('设置完成');
+            hide()
+        })
+    }
+
+    return <Modal
+        title={<Space>
+            <span>ADQ落地页</span>
+            <Button size="small" onClick={() => { synPageList() }} type="link" loading={loading}>同步落地页</Button>
+        </Space>}
+        visible={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={1100}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                {data?.map((item, index) => (
+                    <div key={index} onClick={() => { handleSelectAdz(index + 1, item) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                        {item?.accountId}
+                        {data[index].pageList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                    </div>
+                ))}
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={0}>
+                    <Input value={queryForm?.pageName} allowClear placeholder='请输入落地页名称' onChange={(e) => setQueryForm({ ...queryForm, pageNum: 1, pageName: e.target.value })} />
+                    <Button icon={<SyncOutlined />} type='link' loading={listAjax?.loading} onClick={() => { listAjax?.refresh() }}>刷新</Button>
+                    {data?.length > 1 && <Button disabled={!data[selectAdz - 1]['pageList']?.length} onClick={setOnekey} type="link" loading={listAjax.loading}>
+                        <Space>
+                            <span>一键设置</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同名称的落地页为那个账号的落地页(注意需要落地页名称相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                </Space>
+                <Tables
+                    columns={columns()}
+                    dataSource={listAjax?.data?.records?.map((item: any) => ({ ...item, id: item.pageId }))}
+                    size="small"
+                    loading={listAjax?.loading}
+                    scroll={{ y: 300 }}
+                    bordered
+                    total={listAjax?.data?.total}
+                    defaultPageSize={20}
+                    current={listAjax?.data?.current}
+                    pageSize={listAjax?.data?.size}
+                    pageChange={(page: number, pageSize?: number) => {
+                        setQueryForm({ ...queryForm, pageNum: page, pageSize: pageSize as number || 20 })
+                    }}
+                    rowSelection={{
+                        type: 'radio',
+                        selectedRowKeys: data[selectAdz - 1]?.pageList?.map((item: any) => item?.id?.toString()),
+                        onChange: onChangeTable
+                    }}
+                />
+            </div>
+        </div>
+
+    </Modal>
+}
+
+export default React.memo(PageModal)

+ 60 - 0
src/pages/launchSystemV3/components/PageModal/tableConfig.tsx

@@ -0,0 +1,60 @@
+import { PageStatusEnum, PageTypeEnum, SourceTypeEnum } from "@/services/launchAdq/enum"
+import { Badge } from "antd"
+import React from "react"
+let columns = () => [
+    {
+        title: '落地页ID',
+        dataIndex: 'pageId',
+        key: 'pageId',
+        align: 'center',
+        width: 100,
+        render: (a: string) => {
+            return <a>{a}</a>
+        }
+    },
+    {
+        title: '落地页名称',
+        dataIndex: 'pageName',
+        key: 'pageName',
+        ellipsis: true
+    },
+    {
+        title: '落地页类型',
+        dataIndex: 'pageType',
+        key: 'pageType',
+        align: 'center',
+        width: 120,
+        render: (a: string) => {
+            return PageTypeEnum[a]
+        }
+    },
+    {
+        title: '落地页状态',
+        dataIndex: 'pageStatus',
+        key: 'pageStatus',
+        align: 'center',
+        width: 90,
+        render: (a: string | number) => {
+            return <Badge status={a === 'NORMAL' ? "processing" : "error"} text={PageStatusEnum[a]} />
+        }
+    },
+    {
+        title: '配置来源',
+        dataIndex: 'sourceType',
+        key: 'sourceType',
+        align: 'center',
+        width: 130,
+        render: (a: any, b: any) => {
+            return SourceTypeEnum[a]
+        }
+    },
+    {
+        title: '创建时间',
+        dataIndex: 'createdTime',
+        key: 'createdTime',
+        align: 'center',
+        width: 145,
+    },
+]
+
+export default columns

+ 10 - 0
src/pages/launchSystemV3/components/TargetingTooltip/index.less

@@ -0,0 +1,10 @@
+.targetingTooltip {
+    >div {
+        font-size: 12px;
+        word-break: break-all;
+
+        >span {
+            color: #3085ff;
+        }
+    }
+}

+ 188 - 0
src/pages/launchSystemV3/components/TargetingTooltip/index.tsx

@@ -0,0 +1,188 @@
+import React, { useEffect, useState } from "react"
+import './index.less'
+import { DEVICE_PRICE_ENUM, EDUCATION_ENUM, EXCLUDED_DIMENSION_ENUM, LOCATION_TYPES_ENUM, MARITAL_STATUS_ENUM, NETWORK_ENUM, OPTIMIZATIONGOAL_ENUM, USER_OS_ENUM, WECHAT_AD_NEHAVIOR_ENUM } from "../../tencentAdPutIn/const"
+
+const targetingData = [
+    { key: 'geoLocation', name: '地域' },
+    { key: 'age', name: '年龄' },
+    { key: 'gender', name: '性别' },
+    { key: 'education', name: '学历' },
+    { key: 'maritalStatus', name: '婚恋' },
+    // { key: 'customAudience', name: '定向人群' },
+    { key: 'deviceBrandModel', name: '品牌型号' },
+    { key: 'wechatAdBehavior', name: '微信再营销' },
+    { key: 'networkType', name: '联网方式' },
+    { key: 'devicePrice', name: '设备价格' },
+    { key: 'userOs', name: '操作系统' },
+    { key: 'excludedOs', name: '排除操作系统' },
+    { key: 'excludedConvertedAudience', name: '排除已转化用户' }
+]
+interface Props {
+    data: any,
+    geoLocationList: any // 所有地域
+    modelList: any  // 所有品牌手机
+}
+interface ContentProps {
+    unlimited?: string,         // 不限
+    location?: string[],        // 地点类型
+    geoLocation?: any[],        // 地域
+    age?: string,               // 年龄
+    gender?: string,            // 性别
+    education?: string[]        // 学历
+    maritalStatus?: string[],   // 婚恋
+    deviceBrandModel?: {
+        excludedList?: string[], // 品牌型号
+        includedList?: string[]  // 排除品牌型号
+    },
+    wechatAdBehavior?: {
+        actions?: string[],      // 再营销
+        excludedActions?: string[]// 排除营销
+    },
+    networkType?: string[]      // 联网方式
+    devicePrice?: string[]      // 手机价格
+    userOs?: string[]           // 手机系统
+    excludedOs?: string[]           // 手机系统
+    excludedConvertedAudience?: {
+        conversionBehaviorList: string[],
+        excludedDimension: string
+    }
+}
+/**
+ * 定向处理展示
+ * @returns 
+ */
+const TargetingTooltip: React.FC<Props> = (props) => {
+
+    /**********************/
+    const { data, geoLocationList, modelList } = props
+    const [content, setContent] = useState<ContentProps>({})
+    /**********************/
+    useEffect(() => {
+        if (data && geoLocationList) {
+            let newConten: ContentProps = {}
+            newConten.unlimited = targetingData.filter((item: any) => !Object.keys(data).includes(item.key))?.map((item: any) => item?.name)?.toString()
+            Object.keys(data)?.forEach((item: any) => {
+                switch (item) {
+                    case 'geoLocation': // 地域
+                        newConten.location = data[item]?.locationTypes || []
+                        if (data[item]?.regions && data[item]?.regions?.length > 0) {
+                            newConten.geoLocation = data[item]?.regions?.map((item: any) => geoLocationList[item]?.name)
+                        }
+                        break
+                    case 'age':
+                        let newAge = data[item][0]
+                        newConten.age = `${newAge.min}至${newAge.max > 65 ? '66岁及以上' : newAge.max + '岁'}`
+                        break
+                    case 'gender':
+                        newConten.gender = data[item][0]
+                        break
+                    case 'education':
+                        newConten.education = data[item]
+                        break
+                    case 'maritalStatus':
+                        newConten.maritalStatus = data[item]
+                        break
+                    case 'deviceBrandModel': // 品牌型号
+                        let newData = data[item]
+                        let deviceBrandModel: { excludedList?: string[], includedList?: string[] } = {}
+                        if (newData?.excludedList) {
+                            deviceBrandModel.excludedList = newData?.excludedList?.map((key: any) => modelList[key]?.name)
+                        }
+                        if (newData?.includedList) {  // 排除
+                            deviceBrandModel.includedList = newData?.includedList?.map((key: any) => modelList[key]?.name)
+                        }
+                        newConten.deviceBrandModel = deviceBrandModel
+                        break
+                    case 'wechatAdBehavior': // 微信再营销
+                        let wechatAdBehaviorData = data[item]
+                        let wechatAdBehavior: {
+                            actions?: string[],      // 再营销
+                            excludedActions?: string[]// 排除营销
+                        } = {}
+                        if (wechatAdBehaviorData?.actions) {
+                            wechatAdBehavior.actions = wechatAdBehaviorData?.actions
+                        }
+                        if (wechatAdBehaviorData?.excludedActions) {  // 排除
+                            wechatAdBehavior.excludedActions = wechatAdBehaviorData?.excludedActions
+                        }
+                        newConten.wechatAdBehavior = wechatAdBehavior
+                        break
+                    case 'networkType':
+                        newConten.networkType = data[item]
+                        break
+                    case 'devicePrice':
+                        newConten.devicePrice = data[item]
+                        break
+                    case 'userOs':
+                        newConten.userOs = data[item]
+                        break
+                    case 'excludedOs':
+                        newConten.excludedOs = data[item]
+                        break
+                    case 'excludedConvertedAudience':
+                        newConten.excludedConvertedAudience = data[item]
+                        break
+                }
+            })
+            setContent(newConten)
+        } else {
+            setContent({ unlimited: targetingData?.map((item: any) => item?.name)?.toString() })
+        }
+    }, [data, geoLocationList])
+
+
+
+    return <div className='targetingTooltip'>
+        {content?.geoLocation && <div>
+            <strong>地域:</strong><span>{(content?.location && content?.location?.length > 0) && `(${content?.location?.map((key: string) => LOCATION_TYPES_ENUM[key]).toString()})`}{content?.geoLocation?.toString()}</span>
+        </div>}
+        {content?.age && <div>
+            <strong>年龄:</strong><span>{content?.age}</span>
+        </div>}
+        {content?.gender && <div>
+            <strong> 性别:</strong><span>{content?.gender === 'MALE' ? '男' : '女'}</span>
+        </div>}
+        {(content?.education && content?.education?.length > 0) && <div>
+            <strong>学历:</strong><span>{content?.education?.map((key: string) => EDUCATION_ENUM[key]).toString()}</span>
+        </div>}
+        {(content?.networkType && content?.networkType?.length > 0) && <div>
+            <strong>联网方式:</strong><span>{content?.networkType?.map((key: string) => NETWORK_ENUM[key]).toString()}</span>
+        </div>}
+        {(content?.devicePrice && content?.devicePrice?.length > 0) && <div>
+            <strong> 手机价格:</strong><span>{content?.devicePrice?.map((key: string) => DEVICE_PRICE_ENUM[key]).toString()}</span>
+        </div>}
+        {(content?.userOs && content?.userOs?.length > 0) && <div>
+            <strong>操作系统:</strong><span>{content?.userOs?.map((key: string) => USER_OS_ENUM[key]).toString()}</span>
+        </div>}
+        {(content?.excludedOs && content?.excludedOs?.length > 0) && <div>
+            <strong>排除操作系统:</strong><span>{content?.excludedOs?.map((key: string) => USER_OS_ENUM[key]).toString()}</span>
+        </div>}
+        {(content?.maritalStatus && content?.maritalStatus?.length > 0) && <div>
+            <strong>婚恋:</strong><span>{content?.maritalStatus?.map((key: string) => MARITAL_STATUS_ENUM[key]).toString()}</span>
+        </div>}
+        {content?.deviceBrandModel && <>
+            {(content?.deviceBrandModel?.includedList && content?.deviceBrandModel?.includedList?.length > 0) && <div>
+                <strong>品牌型号:</strong><span>{content?.deviceBrandModel?.includedList?.toString()}</span>
+            </div>}
+            {(content?.deviceBrandModel?.excludedList && content?.deviceBrandModel?.excludedList?.length > 0) && <div>
+                <strong>排除品牌型号:</strong><span>{content?.deviceBrandModel?.excludedList?.toString()}</span>
+            </div>}
+        </>}
+        {content?.wechatAdBehavior && <>
+            {(content?.wechatAdBehavior?.actions && content?.wechatAdBehavior?.actions?.length > 0) && <div>
+                <strong> 再营销:</strong><span>{content?.wechatAdBehavior?.actions?.map((key: string) => WECHAT_AD_NEHAVIOR_ENUM[key]).toString()}</span>
+            </div>}
+            {(content?.wechatAdBehavior?.excludedActions && content?.wechatAdBehavior?.excludedActions?.length > 0) && <div>
+                <strong>排除营销:</strong><span>{content?.wechatAdBehavior?.excludedActions?.map((key: string) => WECHAT_AD_NEHAVIOR_ENUM[key]).toString()}</span>
+            </div>}
+        </>}
+        {content?.excludedConvertedAudience && <div>
+            <strong>排除已转化用户:</strong><span>{EXCLUDED_DIMENSION_ENUM[content?.excludedConvertedAudience?.excludedDimension]}{`(自定义转化行为:${content?.excludedConvertedAudience?.conversionBehaviorList?.map(item => OPTIMIZATIONGOAL_ENUM[item])?.toString()})`}</span>
+        </div>}
+        {content?.unlimited && <div>
+            <strong>不限:</strong><span>{!content?.geoLocation && '地域,'}{content?.unlimited}</span>
+        </div>}
+    </div>
+}
+
+export default React.memo(TargetingTooltip)

+ 20 - 0
src/pages/launchSystemV3/components/TextAideInput/index.less

@@ -0,0 +1,20 @@
+.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;
+  }
+}

+ 86 - 0
src/pages/launchSystemV3/components/TextAideInput/index.tsx

@@ -0,0 +1,86 @@
+import { useAjax } from "@/Hook/useAjax"
+import { txtLength } from "@/utils/utils";
+import { Input, List, Popover, Space } from "antd";
+import React, { useEffect } from "react"
+import { useState } from "react";
+import './index.less'
+import { getTextApi } from "@/services/adqV3/global";
+
+
+interface Props {
+    value?: any,
+    onChange?: (value: any) => void;
+    style?: React.CSSProperties;
+    placeholder?: string;
+    maxTextLength?: number
+}
+
+/**
+ * 文案助手输入框
+ * @param props 
+ * @returns 
+ */
+const TextAideInput: React.FC<Props> = (props) => {
+
+    /************************/
+    const { value, onChange, style, placeholder, maxTextLength = 10 } = props
+    const [text, setText] = useState<any>(value)
+    const [descriptionShow, setDescriptionshow] = useState(false)
+
+    const getTextLsit = useAjax((params) => getTextApi(params))
+    /************************/
+
+    useEffect(() => {
+        setText(value)
+    }, [value])
+
+    // 文案助手
+    const textList = (keyword?: any) => {
+        getTextLsit.run({ keyword, maxTextLength })
+    }
+
+    return <div className="textAideInput">
+        <Space>
+            <Popover placement="topLeft" overlayClassName="textAideInputPopover" style={{ minWidth: 600 }} visible={descriptionShow} content={<>
+                {
+                    <List
+                        loading={getTextLsit?.loading}
+                        size="small"
+                        style={{ maxHeight: 300, overflowX: 'auto', minWidth: 600 }}
+                        dataSource={getTextLsit?.data?.returnTexts}
+                        renderItem={(item: any) => <List.Item onClick={(e: any) => {
+                            setText(item.text)
+                            onChange && onChange(item.text)
+                            setTimeout(() => { setDescriptionshow(false) }, 50)
+                        }}><span >{item.text}{item.tag && <span className="crt">{'CTR 高'}</span>}</span></List.Item>}
+                    />
+                }
+            </>}>
+                <Input
+                    placeholder={placeholder}
+                    style={style}
+                    value={text}
+                    onFocus={() => {
+                        setDescriptionshow(true)
+                        textList(value)
+                    }}
+                    onBlur={() => {
+                        setTimeout(() => { setDescriptionshow(false) }, 500)
+                    }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        setText(value)
+                        textList(value)
+                        onChange && onChange(value)
+                    }}
+                    allowClear
+                />
+            </Popover>
+
+            <span>{`${txtLength(text)}/${maxTextLength}`}</span>
+        </Space>
+    </div>
+}
+
+
+export default React.memo(TextAideInput)

+ 48 - 0
src/pages/launchSystemV3/components/VideoFrameSelect/index.less

@@ -0,0 +1,48 @@
+.popover {
+    display: flex;
+    flex-direction: column;
+    gap: 2px;
+}
+.content {
+    width: 400px;
+    height: 80px;
+    overflow-x: scroll;
+    display: flex;
+    gap: 4px;
+    padding: 2px;
+    box-sizing: border-box;
+
+    .imgBox {
+        height: 100%;
+        border-radius: 6px;
+        position: relative;
+
+        &>img {
+            height: 100%;
+            border-radius: 6px;
+            cursor: pointer;
+        }
+
+        &>.look {
+            position: absolute;
+            padding: 2px 6px;
+            background-color: rgba(0, 0, 0, .6);
+            color: #FFF;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            border-radius: 4px;
+            opacity: 0;
+            transition: all 0.2s;
+            cursor: pointer;
+        }
+
+        &:hover .look {
+            opacity: 1;
+        }
+    }
+
+    &>.select {
+        box-shadow: 0 0 0 1.5px #00b96b;
+    }
+}

+ 77 - 0
src/pages/launchSystemV3/components/VideoFrameSelect/index.tsx

@@ -0,0 +1,77 @@
+import { videoUrlMessage } from "@/utils/compress"
+import { ScissorOutlined } from "@ant-design/icons"
+import { Button, Popover, Image } from "antd"
+import React, { useState } from "react"
+import style from './index.less'
+import Lazyimg from "react-lazyimg-component"
+
+interface Props {
+    url: string,
+    onChange?: (url: string) => void
+}
+/**
+ * 获取oo视频帧序列
+ * @returns 
+ */
+const VideoFrameSelect: React.FC<Props> = ({ url, onChange }) => {
+    
+    /************************************/
+    const [urlList, setUrlList] = useState<string[]>([])
+    const [urlNum, seturlNum] = useState<number>(30)
+    const [open, setOpen] = useState(false);
+    const [selectUrl, setSelectUrl] = useState<string>()
+    const [visible, setVisible] = useState<boolean>(false)
+    const [lookUrl, setLookUrl] = useState<string>()
+    /************************************/
+
+    const handleOk = () => {
+        if (selectUrl) {
+            console.log(selectUrl)
+            onChange?.(selectUrl)
+            setOpen(false);
+        }
+    };
+
+    const onVisibleChange = async (newOpen: boolean) => {
+        if (newOpen && url && url.includes('.mp4')) {
+            let data = await videoUrlMessage(url)
+            let duration = data.videoLength
+            let gap = duration / urlNum
+            setUrlList(Array(urlNum).fill('').map((item, index) => url + `?x-oss-process=video/snapshot,t_${(index * gap * 1000).toFixed(0)}`))
+        }
+        setOpen(newOpen);
+    };
+
+    const selectHandle = (url: string) => {
+        setSelectUrl(url)
+    }
+
+    return <Popover
+        content={<div className={style.popover}>
+            <div className={style.content}>
+                {urlList.map((url, index) => <div className={`${style.imgBox} ${selectUrl === url ? style.select : ''}`} key={index}>
+                    <Lazyimg onClick={() => { selectHandle(url) }} className={`lazy`} src={url} />
+                    <span className={style.look} onClick={() => { setLookUrl(url); setVisible(true) }}>查看</span>
+                </div>)}
+            </div>
+            <Image width={200} style={{ display: 'none' }} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
+                preview={{
+                    visible,
+                    src: lookUrl,
+                    onVisibleChange: (value) => {
+                        setVisible(value);
+                    },
+                }}
+            />
+            <Button type="primary" style={{ backgroundColor: '#00b96b', borderColor: '#00b96b' }} onClick={handleOk} disabled={!selectUrl} size="small">确定</Button>
+        </div>}
+        destroyTooltipOnHide={true}
+        trigger="click"
+        visible={open}
+        onVisibleChange={onVisibleChange}
+    >
+        <Button type="link" icon={<ScissorOutlined />}>视频截帧更换封面</Button>
+    </Popover>
+}
+
+export default React.memo(VideoFrameSelect)

+ 165 - 0
src/pages/launchSystemV3/components/WechatAccount/index.tsx

@@ -0,0 +1,165 @@
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, message, Modal, Space, Table, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../GoodsModal/index.less'
+import columns from './tableConfig'
+import { getWechatOfficialAccountApi, getWechatOfficialAccountsApi } from "@/services/adqV3/global"
+
+/**
+ * 获取公众号列表
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const WechatAccount: React.FC<Props> = (props) => {
+
+    /************************/
+    const { visible, onClose, data: data1, onChange } = props
+    const [tableData, setTableData] = useState<any[]>([])//table数据
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1)
+
+    const getWechatOfficialAccount = useAjax((params) => getWechatOfficialAccountApi(params))
+    const getWechatOfficialAccounts = useAjax((params) => getWechatOfficialAccountsApi(params))
+    /************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            getList(data[selectAdz - 1].accountId)
+        } else {
+            setTableData([])
+        }
+    }, [selectAdz])
+
+    // 获取公众号列表
+    const getList = (accountId: number) => {
+        getWechatOfficialAccount.run({ accountId }).then(res => {
+            setTableData(res || [])
+        })
+    }
+
+    const handleOk = () => {
+        onChange && onChange(data)
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (_: React.Key[], selectedRows: any) => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['wechatChannelList'] = selectedRows
+        setData([...newData])
+    }
+
+    // 清空已选
+    const clearGoods = () => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['wechatChannelList'] = []
+        setData([...newData])
+    }
+
+    /** 一键设置 */
+    const setOnekey = () => {
+        let wechatChannelNames: string[] = data[selectAdz - 1]['wechatChannelList']?.map((item: { wechatOfficialAccountName: string }) => item.wechatOfficialAccountName) || []
+        let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        getWechatOfficialAccounts.run({ accountIdList: newData?.filter(item => item.accountId !== data[selectAdz - 1].accountId)?.map(item => item?.accountId) }).then(res => {
+            
+            if (res?.length > 0) {
+                res.forEach((i: { accountId: number, wechatOfficialAccountName: string }) => {
+                    if (wechatChannelNames.includes(i.wechatOfficialAccountName)) {
+                        newData = newData.map(item => {
+                            if (item.accountId.toString() === i.accountId.toString()) {
+                                return { ...item, wechatChannelList: [i] }
+                            }
+                            return item
+                        })
+                    }
+                })
+                setData(newData)
+            }
+            message.success('设置完成');
+            hide()
+        })
+    }
+
+
+    return <Modal
+        title={<strong>选择公众号</strong>}
+        visible={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={900}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        let wechatChannelList = data[index].wechatChannelList || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            {item?.accountId}
+                            {wechatChannelList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={0}>
+                    <Button icon={<SyncOutlined />} type='link' loading={getWechatOfficialAccount?.loading} onClick={() => { getList(data[selectAdz - 1].accountId) }}>刷新</Button>
+                    {data?.length > 1 && <Button disabled={!data[selectAdz - 1]['wechatChannelList']?.length} onClick={setOnekey} type="link" loading={getWechatOfficialAccount.loading}>
+                        <Space>
+                            <span>一键设置</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同名称的商品为那个账号的商品(注意需要用户商品称相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                    {(data[selectAdz - 1]?.wechatChannelList || [])?.length > 0 && <Button type='link' onClick={() => { clearGoods() }}>清空</Button>}
+                </Space>
+                <Table
+                    columns={columns()}
+                    dataSource={tableData}
+                    size="small"
+                    loading={getWechatOfficialAccount?.loading}
+                    scroll={{ y: 400 }}
+                    rowKey={'wechatOfficialAccountId'}
+                    rowSelection={{
+                        type: 'radio',
+                        selectedRowKeys: data[selectAdz - 1]?.wechatChannelList?.map((item: any) => item?.wechatOfficialAccountId),
+                        onChange: onChangeTable
+                    }}
+                    onRow={(record) => ({
+                        onClick: () => {
+                            let newDatas = JSON.parse(JSON.stringify(data))
+                            let oldData = newDatas[selectAdz - 1]?.wechatChannelList || []
+                            const selected = oldData?.some((item: any) => item?.wechatOfficialAccountId === record.wechatOfficialAccountId);
+                            const newSelectedRows = selected
+                                ? oldData?.filter((item: any) => item?.wechatOfficialAccountId !== record.wechatOfficialAccountId)
+                                : [...oldData, record];
+                            newDatas[selectAdz - 1]['wechatChannelList'] = newSelectedRows;
+                            setData([...newDatas])
+                        },
+                    })}
+                />
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(WechatAccount)

+ 26 - 0
src/pages/launchSystemV3/components/WechatAccount/tableConfig.tsx

@@ -0,0 +1,26 @@
+import { TableProps } from "antd"
+import React from "react"
+
+const columns = (): TableProps<any>['columns'] => [
+    {
+        title: '公众号名称',
+        dataIndex: 'wechatOfficialAccountName',
+        key: 'wechatOfficialAccountName',
+        ellipsis: true,
+        width: 120,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    {
+        title: '公众号ID',
+        dataIndex: 'wechatOfficialAccountId',
+        key: 'wechatOfficialAccountId',
+        ellipsis: true,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    }
+]
+
+export default columns

+ 106 - 0
src/pages/launchSystemV3/tencenTasset/brand/index.tsx

@@ -0,0 +1,106 @@
+import { Button, Card, Form, Input, Modal, Table, message } from 'antd'
+import '../../tencentAdPutIn/index.less'
+import React, { useEffect, useState } from 'react'
+import { PlusOutlined, SearchOutlined } from '@ant-design/icons'
+import { useAjax } from '@/Hook/useAjax'
+import { addSysBrandApi, delSysBrandApi, editSysBrandApi, getSysBrandApi } from '@/services/adqV3/global'
+import brandColumns from '../../components/BrandImage/tableConfig'
+import { UploadImage } from '../../components/BrandImage'
+
+const Brand: React.FC = () => {
+
+
+    /***************************************/
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>({})
+    const [form] = Form.useForm()
+
+    const getSysBrand = useAjax(() => getSysBrandApi())
+    const addSysBrand = useAjax((params) => addSysBrandApi(params))
+    const editSysBrand = useAjax((params) => editSysBrandApi(params))
+    const delSysBrand = useAjax((params) => delSysBrandApi(params))
+    /***************************************/
+
+    // 获取列表
+    useEffect(() => {
+        getSysBrand.run()
+    }, [])
+
+    // 新增修改
+    const handleOk = async () => {
+        form.submit()
+        let data = await form.validateFields()
+        if (Object.keys(initialValues).length > 0) { // 修改
+            editSysBrand.run({ ...data, sysBrandId: initialValues.id }).then(res => {
+                if (res) {
+                    message.success('修改成功')
+                    setAddVisible(false)
+                    getSysBrand.refresh()
+                }
+            })
+        } else { // 新增
+            addSysBrand.run(data).then(res => {
+                if (res) {
+                    message.success('新增成功')
+                    setAddVisible(false)
+                    getSysBrand.refresh()
+                }
+            })
+        }
+        setInitialValues({})
+    }
+
+    /** 删除 */
+    const del = (id: number) => {
+        delSysBrand.run({ sysBrandId: id }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getSysBrand.refresh()
+            }
+        })
+    }
+
+    /** 修改 */
+    const edit = (data: any) => {
+        setInitialValues(data)
+        setAddVisible(true)
+    }
+
+    return <Card
+        className="cardResetCss"
+        title={<div className="flexStart" style={{ gap: 8 }}>
+            <Button type="primary" icon={<SearchOutlined />} onClick={() => getSysBrand.refresh()}>搜索</Button>
+            <Button type="primary" icon={<PlusOutlined />} onClick={() => { setAddVisible(true); setInitialValues({}) }}>上传品牌形象</Button>
+        </div>}
+    >
+        <Table
+            columns={brandColumns(del, edit)}
+            dataSource={getSysBrand?.data}
+            size="small"
+            loading={getSysBrand?.loading}
+            scroll={{ y: 300 }}
+            bordered
+            rowKey={'id'}
+        />
+
+        {addVisible && <Modal className="modalResetCss" title="上传品牌形象" visible={addVisible} confirmLoading={addSysBrand.loading} onOk={handleOk} onCancel={() => setAddVisible(false)}>
+            <Form
+                name="basic"
+                form={form}
+                layout='vertical'
+                autoComplete="off"
+                initialValues={{ ...initialValues }}
+            >
+                <Form.Item label={<strong>头像</strong>} name="brandImgUrl" rules={[{ required: true, message: '请选择头像!' }]}>
+                    <UploadImage />
+                </Form.Item>
+                <Form.Item label={<strong>名称</strong>} name="name" rules={[{ required: true, message: '请输入名称!' }]}>
+                    <Input placeholder="请输入名称" maxLength={12} />
+                </Form.Item>
+            </Form>
+        </Modal>}
+    </Card>
+}
+
+
+export default Brand

+ 186 - 0
src/pages/launchSystemV3/tencenTasset/profiles/index.tsx

@@ -0,0 +1,186 @@
+import { useAjax } from "@/Hook/useAjax";
+import { addProfilesApi, delProfilesApi, delSysBrandApi, getProfilesApi } from "@/services/adqV3/global";
+import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
+import { Button, Card, Form, Input, Modal, Select, Space, Table, message, Image, Divider } from "antd";
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import profilesColumns from "./tableConfig";
+import SelectCloud from "@/pages/launchSystemNew/components/selectCloud";
+import { useModel } from "umi";
+
+
+const Profiles: React.FC = () => {
+
+
+    /***************************************/
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>({})
+    const [form] = Form.useForm()
+    const [queryForm, setQueryForm] = useState<{ profileName?: string }>({})
+
+    const getProfiles = useAjax((params) => getProfilesApi(params))
+    const addProfiles = useAjax((params) => addProfilesApi(params))
+    const delProfiles = useAjax((params) => delProfilesApi(params))
+    /***************************************/
+
+    // 获取列表
+    useEffect(() => {
+        getProfiles.run(queryForm)
+    }, [queryForm])
+
+    // 新增修改
+    const handleOk = async () => {
+        form.submit()
+        let { imageData, ...data } = await form.validateFields()
+        addProfiles.run({ ...imageData, ...data }).then(res => {
+            if (res) {
+                message.success('新增成功')
+                setAddVisible(false)
+                getProfiles.refresh()
+            }
+        })
+        setInitialValues({})
+    }
+
+    /** 删除 */
+    const del = (id: number) => {
+        delProfiles.run({ id }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getProfiles.refresh()
+            }
+        })
+    }
+
+    return <Card
+        className="cardResetCss"
+        title={<div className="flexStart" style={{ gap: 8 }}>
+            <Input style={{ width: 200 }} placeholder="请输入头像昵称跳转页" value={queryForm?.profileName} allowClear onChange={(e) => setQueryForm({ ...queryForm, profileName: e.target.value })} />
+            <Button type="primary" icon={<SearchOutlined />} onClick={() => getProfiles.refresh()}>搜索</Button>
+            <Button type="primary" icon={<PlusOutlined />} onClick={() => { setAddVisible(true); setInitialValues({}), form.resetFields() }}>上传头像昵称跳转页</Button>
+        </div>}
+    >
+        <Table
+            columns={profilesColumns(del)}
+            dataSource={getProfiles?.data}
+            size="small"
+            loading={getProfiles?.loading}
+            scroll={{ y: 300 }}
+            bordered
+            rowKey={'id'}
+        />
+
+        {addVisible && <Modal className="modalResetCss" title="上传头像昵称跳转页" visible={addVisible} confirmLoading={addProfiles.loading} onOk={handleOk} onCancel={() => setAddVisible(false)}>
+            <Form
+                name="basicProfiles"
+                form={form}
+                layout='vertical'
+                autoComplete="off"
+                initialValues={{ ...initialValues }}
+            >
+                <Form.Item label={<strong>头像</strong>} name="imageData" rules={[{ required: true, message: '请选择头像!' }]}>
+                    <UploadImageOrMd5 />
+                </Form.Item>
+                <Form.Item label={<strong>昵称</strong>} name="profileName" rules={[{ required: true, message: '请输入昵称!' }]}>
+                    <Input placeholder="请输入名称" maxLength={12} />
+                </Form.Item>
+                <Form.Item label={<strong>详细描述</strong>} name="description">
+                    <Input.TextArea placeholder="请输入名称" maxLength={120} />
+                </Form.Item>
+            </Form>
+        </Modal>}
+    </Card>
+}
+
+export default React.memo(Profiles)
+
+
+interface ImageProps {
+    onChange?: (data: { imageUrl: string, imageMd5: string }) => void,
+    value?: { imageUrl: string, imageMd5: string }
+}
+/**
+ * 处理选择图片Form
+ * @returns 
+ */
+const UploadImageOrMd5: React.FC<ImageProps> = (props) => {
+
+    /*********************/
+    const { onChange, value } = props
+    const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false)
+    const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([])  // 保存回填数据
+    const { init } = useModel('useLaunchAdq.useBdMediaPup')
+    /*********************/
+
+    useEffect(() => {
+        if (value && value?.imageUrl) {
+            setSliderImgContent([{ url: value.imageUrl }])
+        } else {
+            setSliderImgContent([])
+        }
+    }, [value])
+
+    const setImg = (content: any[]) => {
+        console.log(content)
+        onChange && onChange({ imageUrl: content[0]?.url, imageMd5: content[0]?.fileMd5 })
+        setSelectImgVisible(false)
+    }
+
+    const selectImg = () => {
+        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 512, height: 512 }]], maxSize: 400 * 1024 })
+        setTimeout(() => { setSelectImgVisible(true) }, 50)
+    }
+
+    return <>
+        {value?.imageUrl ? <img src={value?.imageUrl} onClick={selectImg} width={100} height={100} /> : <Button onClick={selectImg}>选择图片</Button>}
+        <div style={{ fontSize: 12, color: 'rgba(0,0,0,.5)' }}>
+            <div>图片尺寸:512×512 像素</div>
+            <div>图片格式:大小要求在 400KB 以内,仅支持 jpg 和 png 格式</div>
+        </div>
+
+        {/* 选择素材 */}
+        {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
+    </>
+}
+
+
+
+export const SelectProfiles: React.FC<{ value?: number, onChange?: (value?: number) => void }> = ({value, onChange}) => {
+
+    /*******************************/
+    const getProfiles = useAjax((params) => getProfilesApi(params))
+    /*******************************/
+
+    // 获取列表
+    useEffect(() => {
+        getProfiles.run({})
+    }, [])
+
+    return <Select
+        showSearch
+        allowClear
+        placeholder="选择头像昵称跳转页"
+        filterOption={(input, option) =>
+            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+        }
+        style={{ width: 480 }}
+        dropdownRender={menu => <>
+            {menu}
+            <Divider style={{ margin: '8px 0' }} />
+            <div>
+                <Button type="link" onClick={() => {
+                    window.location.href = '/#/launchSystemV3/tencentAdPutIn/create'
+                }}>前往管理</Button>
+            </div>
+        </>}
+        value={value}
+        onChange={(e) => onChange?.(e)}
+    >
+        {getProfiles?.data?.map((item: { id: number; headImageUrl: string; profileName: string; }) => <Select.Option value={item.id} key={item.id}>
+            <Space>
+                <Image width={20} style={{ borderRadius: 4 }} src={item.headImageUrl} />
+                <span>{item.profileName}</span>
+            </Space>
+        </Select.Option>)}
+    </Select>
+}

+ 81 - 0
src/pages/launchSystemV3/tencenTasset/profiles/tableConfig.tsx

@@ -0,0 +1,81 @@
+import { Space, Image, Popconfirm, TableProps } from "antd"
+import React from "react"
+
+
+const profilesColumns = (del: (id: number) => void): TableProps<any>['columns'] => {
+
+
+    const data: any[] = [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            width: 100,
+            render: (a: any, b: any) => {
+                return <Space>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => del(b.id)}
+                        okText="是"
+                        cancelText="否"
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            }
+        },
+        {
+            title: 'ID',
+            dataIndex: 'id',
+            key: 'id',
+            width: 60,
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '头像预览图',
+            dataIndex: 'headImageUrl',
+            key: 'headImageUrl',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+            render: (a: any) => {
+                return <Image width={40} style={{ borderRadius: 4 }} src={a} />
+            }
+        },
+        {
+            title: '昵称',
+            dataIndex: 'profileName',
+            key: 'profileName',
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '详细描述',
+            dataIndex: 'description',
+            key: 'description',
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a}</span>
+            }
+        }
+    ]
+
+    return data
+}
+
+export default profilesColumns

+ 141 - 0
src/pages/launchSystemV3/tencenTasset/targeting/index.tsx

@@ -0,0 +1,141 @@
+import { Button, Card, DatePicker, Input, Select, message } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import { delTargetingApi, getTargetingListApi } from "@/services/adqV3"
+import { useAjax } from "@/Hook/useAjax"
+import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
+import moment from "moment"
+import AddTarget from "../../tencentAdPutIn/create/Target/addTarget"
+import Tables from "@/components/Tables"
+import { TableConfig } from "./tableConfig"
+import { randomString } from "@/utils/utils"
+import { useModel } from "umi"
+
+/**
+ * 资产定向模板
+ * @returns 
+ */
+const Targeting: React.FC = () => {
+
+    /**************************************/
+    const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
+    const { initTargeting, geoLocationList, modelList } = useModel('useLaunchV3.useTargeting')
+    const [queryParams, setQueryParams] = useState<PULLIN.GetTargeting>({ pageNum: 1, pageSize: 20 })
+    const [queryParamsNew, setQueryParamsNew] = useState<PULLIN.GetTargeting>({ pageNum: 1, pageSize: 20 })
+    const getTargetingList = useAjax((params) => getTargetingListApi(params))
+    const [addTemVisible, setAddTemVisible] = useState<boolean>(false)
+    const [modifyDta, setModifyDta] = useState<any>()
+
+    const delTargeting = useAjax((params) => delTargetingApi(params))
+    /**************************************/
+
+    // 设置地域
+    useEffect(() => {
+        initTargeting()
+        // 获取账户列表
+        getAllUserAccount.run()
+    }, [])
+
+    useEffect(() => {
+        getTargetingList.run(queryParamsNew)
+    }, [queryParamsNew])
+
+    /** 编辑 复制 */
+    const editHandle = (data: any, isCopy?: boolean) => {
+        const { targetingName, targeting, description, id, accountId } = data
+        let newModifyDta = {
+            ...targeting,
+            targetingName,
+            description,
+            accountId: accountId || undefined
+        }
+        if (!isCopy) {
+            newModifyDta.id = id
+        } else {
+            newModifyDta.targetingName = newModifyDta.targetingName + `_副本${randomString(true, 3, 5)}`
+            newModifyDta.isCopy = isCopy
+        }
+        setModifyDta(newModifyDta)
+        setAddTemVisible(true)
+    }
+
+    /** 删除 */
+    const delHandle = (id: number) => {
+        delTargeting.run({ id }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getTargetingList.refresh()
+            }
+        })
+    }
+
+    return <Card
+        className="cardResetCss"
+        title={<div className="flexStart" style={{ gap: 8 }}>
+            <Input style={{ width: 180 }} placeholder="请输入关键字查找" value={queryParams?.targetingName} allowClear onChange={(e) => setQueryParams({ ...queryParams, targetingName: e.target.value, pageNum: 1 })} />
+            <Select
+                style={{ width: 250 }}
+                showSearch
+                filterOption={(input, option) =>
+                    (option!.children as unknown as string)?.toLowerCase()?.includes(input?.toLowerCase())
+                }
+                allowClear
+                value={queryParams?.accountId}
+                onChange={(e) => setQueryParams({ ...queryParams, accountId: e })}
+                placeholder='请选择媒体账户'
+            >
+                {getAllUserAccount?.data?.data?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
+            </Select>
+            <DatePicker.RangePicker style={{ width: 250 }} placeholder={['创建开始时间', '创建结束时间']} value={(queryParams?.min && queryParams?.max) ? [moment(queryParams?.min), moment(queryParams?.max)] as any : undefined} onChange={(_, option) => setQueryParams({ ...queryParams, min: option[0], max: option[1], pageNum: 1 })} />
+            <Button type="primary" icon={<SearchOutlined />} loading={getTargetingList.loading} onClick={() => setQueryParamsNew({ ...queryParams })}>搜索</Button>
+        </div>}
+    >
+        <div className="flexColumnStart" style={{ gap: 8 }}>
+            <div>
+                <Button type="primary" icon={<PlusOutlined />} onClick={() => setAddTemVisible(true)}>新增定向模板</Button>
+            </div>
+            <div>
+                <Tables
+                    columns={TableConfig(geoLocationList, modelList, editHandle, delHandle)}
+                    dataSource={getTargetingList?.data?.records}
+                    size="small"
+                    loading={getTargetingList?.loading}
+                    scroll={{ y: 650 }}
+                    bordered
+                    // rowSelection={{
+                    //     type: 'checkbox',
+                    //     selectedRowKeys: data[selectAdz - 1]?.userActionSetsList?.map((item: any) => item?.id?.toString()),
+                    //     onChange: onChangeTable
+                    // }}
+                    total={getTargetingList?.data?.total}
+                    defaultPageSize={20}
+                    current={getTargetingList?.data?.current}
+                    pageSize={getTargetingList?.data?.size}
+                    pageChange={(page: number, pageSize?: number) => {
+                        setQueryParamsNew({ ...queryParamsNew, pageNum: page, pageSize: pageSize as number || 20 })
+                        setQueryParams({ ...queryParams, pageNum: page, pageSize: pageSize as number || 20 })
+                    }}
+                />
+            </div>
+        </div>
+
+
+        {/* 新增修改定向模板 */}
+        {addTemVisible && <AddTarget
+            value={modifyDta}
+            visible={addTemVisible}
+            onClose={() => {
+                setAddTemVisible(false)
+                setModifyDta(undefined)
+            }}
+            onChange={() => {
+                setAddTemVisible(false)
+                setModifyDta(undefined)
+                getTargetingList.refresh()
+            }}
+        />}
+    </Card>
+}
+
+
+export default Targeting

+ 107 - 0
src/pages/launchSystemV3/tencenTasset/targeting/tableConfig.tsx

@@ -0,0 +1,107 @@
+import { Button, Popconfirm, Popover, Space, Typography } from "antd";
+import { ColumnsType } from "antd/es/table";
+import React from "react";
+import TargetingTooltip from "../../components/TargetingTooltip";
+import { QuestionCircleFilled } from "@ant-design/icons";
+
+
+export function TableConfig(geoLocationList: any, modelList: any, editHandle: (data: any, isCopy?: boolean) => void, del: (id: number) => void): ColumnsType<any> {
+    let arr: ColumnsType<any> = [
+        {
+            title: '定向模板名称',
+            dataIndex: 'targetingName',
+            key: 'targetingName',
+            width: 300,
+            ellipsis: true,
+            render(value, records) {
+                return <div style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
+                    <div style={{ width: 'calc(100% - 20px)' }}><Typography.Text ellipsis={{ tooltip: true }}>{value}</Typography.Text></div>
+                    <Popover
+                        placement="right"
+                        overlayInnerStyle={{ maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' }}
+                        mouseEnterDelay={0.5}
+                        content={<TargetingTooltip
+                            data={records?.targeting}
+                            geoLocationList={geoLocationList}
+                            modelList={modelList}
+                        />}
+                    >
+                        <QuestionCircleFilled />
+                    </Popover>
+                </div>
+            }
+        },
+        {
+            title: '关联账户',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render(value) {
+                return value || '不限'
+            },
+        },
+        {
+            title: '定向模板描述',
+            dataIndex: 'description',
+            key: 'description',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            }
+        },
+        {
+            title: '创建人',
+            dataIndex: 'createByName',
+            key: 'createByName',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 145,
+            ellipsis: true,
+        },
+        {
+            title: '更新时间',
+            dataIndex: 'updateTime',
+            key: 'updateTime',
+            align: 'center',
+            width: 145,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            fixed: 'right',
+            width: 120,
+            render: (a: any, b: any) => {
+                return <Space wrap>
+                    <Button type="link" style={{ padding: 0 }} onClick={() => { editHandle(b) }} >编辑</Button>
+                    <Button type="link" style={{ padding: 0 }} onClick={() => { editHandle(b, true) }} >复制</Button>
+                    <Popconfirm
+                        title="确定删除?"
+                        onConfirm={() => { del(b.id) }}
+                    >
+                        <Button type="link" style={{ padding: 0 }} danger>删除</Button>
+                    </Popconfirm>
+                </Space>
+            }
+        },
+    ]
+    return arr
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 28 - 0
src/pages/launchSystemV3/tencentAdPutIn/const.ts


+ 127 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsAdSetting.tsx

@@ -0,0 +1,127 @@
+import { Card, DatePicker, Form, Radio, Select, Space, Switch, Tooltip } from "antd"
+import React, { useContext, useEffect } from "react"
+import { DispatchAd } from "./newCreateAd";
+import { RangePickerProps } from "antd/lib/date-picker";
+import moment from "moment";
+import style from '../index.less'
+import TimeInSelect from "@/pages/launchSystemNew/components/timeInSelect";
+import { QuestionCircleFilled } from "@ant-design/icons";
+import { AD_STATUS_ENUM, MARKETING_TARGET_TYPE_ENUM, SelectTimeList, marketingGoalList } from "../../const";
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { txtLength } from "@/utils/utils";
+import InputName from "@/components/InputName";
+
+/**
+ * 广告设置
+ * @returns 
+ */
+const AdgroupsAdSetting: React.FC<{ value?: any }> = ({ value }) => {
+
+    /****************************************/
+    const { form } = useContext(DispatchAd)!;
+    const timeSeriesType = Form.useWatch('timeSeriesType', form)
+    const isSetfirstDayBeginTime = Form.useWatch('isSetfirstDayBeginTime', form)
+    const marketingGoal = Form.useWatch('marketingGoal', form)
+    const marketingTargetType = Form.useWatch('marketingTargetType', form)
+    /****************************************/
+
+    useEffect(() => {
+        if (!(value && Object.keys(value).length)) {
+            form.setFieldsValue({
+                adgroupName: marketingGoalList.find(item => item.value === marketingGoal)?.label + '_' + MARKETING_TARGET_TYPE_ENUM[marketingTargetType] + '_' + localStorage.getItem('userId')// + '_' + moment().format('MM_DD_HH:mm:ss')
+            })
+        }
+    }, [marketingGoal, marketingTargetType])
+
+    /** 禁止选择以前时间 */
+    const disabledDate: RangePickerProps['disabledDate'] = current => {
+        // Can not select days before today and today
+        return current && current < moment().startOf('day');
+    };
+
+    return <Card
+        title={<strong style={{ fontSize: 18 }}>广告设置</strong>}
+        className="cardResetCss"
+    >
+        <Form.Item label={<strong>投放日期</strong>} name='date' rules={[{ required: true, message: '请选择投放日期' }]}>
+            <DatePicker.RangePicker disabledDate={disabledDate} />
+        </Form.Item>
+        <Form.Item label={<strong>投放时间</strong>}>
+            <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>选择时段</div>
+                        <Form.Item name='timeSeriesType' style={{ marginBottom: 0 }}>
+                            <Radio.Group>
+                                <Radio value="0">全天</Radio>
+                                <Radio value="2">指定多个时段</Radio>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                    {timeSeriesType === '2' && <div className={style.newSpace_bottom}>
+                        <Form.Item name='timeSeries' noStyle rules={[{ required: true, message: '请选择时段' }]}>
+                            <TimeInSelect />
+                        </Form.Item>
+                    </div>}
+                </div>
+                <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}><Space>
+                            <span>首日开始时间</span>
+                            <Tooltip title={`指定了多个时段需要注意星期一到星期天都没指定的时段开始时间不可选`}>
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </Space>
+
+                        </div>
+                        <Form.Item name='isSetfirstDayBeginTime' style={{ marginBottom: 0 }} valuePropName="checked">
+                            <Switch checkedChildren="开启" unCheckedChildren="关闭" />
+                        </Form.Item>
+                    </div>
+                    {isSetfirstDayBeginTime && <div className={style.newSpace_bottom}>
+                        <Form.Item name='firstDayBeginTime' noStyle rules={[{ required: true, message: '请选择首日开始时间' }]}>
+                            <Select
+                                style={{ width: 180 }}
+                                allowClear
+                                placeholder='请选择首日开始时间'
+                                options={SelectTimeList}
+                            />
+                        </Form.Item>
+                    </div>}
+                </div>
+            </Card>
+        </Form.Item>
+        <Form.Item label={<strong>广告状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择广告状态' }]}>
+            <New1Radio data={Object.keys(AD_STATUS_ENUM).map(key => ({ label: AD_STATUS_ENUM[key], value: key }))} />
+        </Form.Item>
+        <Form.Item
+            label={<strong>广告名称</strong>}
+            name='adgroupName'
+            // tooltip="下标、日期时分秒、广告账户创建时默认自带"
+            rules={[
+                { required: true, message: '请输入广告名称!' },
+                {
+                    required: true, message: '广告名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(_, value) {
+                        let reg = /[&‘’“”/\n\t\f]/ig
+                        if (value && reg.test(value)) {
+                            return Promise.reject()
+                        }
+                        return Promise.resolve()
+                    }
+                },
+                {
+                    required: true, message: '请确保广告名称长度不超过60个字(1个汉字等于2个字符)', validator(_, value) {
+                        if (value && txtLength(value) > 60) {
+                            return Promise.reject()
+                        }
+                        return Promise.resolve()
+                    }
+                }
+            ]}
+        >
+            <InputName placeholder='广告名称' style={{ width: 480 }} length={60} />
+        </Form.Item>
+    </Card>
+}
+
+export default React.memo(AdgroupsAdSetting)

+ 204 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsMarketingContent.tsx

@@ -0,0 +1,204 @@
+import { Card, Form, Select, Switch } from "antd"
+import React, { useContext, useEffect, useState } from "react"
+import MarketingGoal from "./marketingGoal"
+import NewRadio from "@/pages/launchSystemV3/components/NewRadio"
+import { DispatchAd } from "./newCreateAd"
+import { GOAL_ROAS_ENUM, MARKETING_CARRIER_TYPE_ENUM, MARKETING_TARGET_TYPE_ENUM, OPTIMIZATIONGOAL_ENUM, defaultSiteSet } from "../../const"
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio"
+import { useAjax } from "@/Hook/useAjax"
+import { getOptimizationGoalPermissionsV3Api } from "@/services/adqV3/global"
+import { adRules } from "../../rules"
+
+
+/**
+ * 营销内容
+ * @param value 回填
+ * @returns 
+ */
+const AdgroupsMarketingContent: React.FC<{ value?: any }> = ({ value }) => {
+
+    /****************************************/
+    const { form, OGPParams, setOGPparams } = useContext(DispatchAd)!;
+
+    const marketingGoal = Form.useWatch('marketingGoal', form);
+    const marketingTargetType = Form.useWatch('marketingTargetType', form);
+    const marketingCarrierType = Form.useWatch('marketingCarrierType', form);
+    const bidMode = Form.useWatch('bidMode', form);
+    const optimizationGoal = Form.useWatch('optimizationGoal', form);
+    const smartBidType = Form.useWatch('smartBidType', form);
+    const depthConversionEnabled = Form.useWatch('depthConversionEnabled', form);
+    const deepConversionType = Form.useWatch(['deepConversionSpec', 'deepConversionType'], form);
+    // 推广产品
+    const [marketingTargetTypeList, setMarketingTargetTypeList] = useState<PULLIN.DataType[]>([])
+    const [marketingCarrierTypeList, setMarketingCarrierTypeList] = useState<PULLIN.DataType[]>([])
+    const [rules, setRules] = useState<any>({})
+    const [behaviorList, setBehaviorList] = useState<string[]>([])
+    const [worthList, setWorthList] = useState<string[]>([])
+    const [deepConversionData, setDeepConversionData] = useState<PULLIN.DataType[]>([])
+
+    const queryOptimizationGoalPermissions = useAjax((params) => getOptimizationGoalPermissionsV3Api(params))
+    /****************************************/
+
+    /** 获取深度优化 出价和版位改变时查询 */
+    const getOptimizationGoalPermissions = () => {
+        let marketingCarrierType = OGPParams?.marketingCarrierType
+        let bidMode = OGPParams.bidMode
+        let siteSet = OGPParams.siteSet
+        let automaticSiteEnabled = OGPParams.automaticSiteEnabled
+        let marketingGoal = OGPParams.marketingGoal
+        let marketingTargetType = OGPParams?.marketingTargetType
+        if (marketingTargetType === 'MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT') {
+            marketingCarrierType = 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT'
+        }
+        if ((bidMode && siteSet && siteSet?.length > 0 && marketingGoal && marketingCarrierType && marketingTargetType) || automaticSiteEnabled) {
+            let obj: any = { siteSet: automaticSiteEnabled ? defaultSiteSet : siteSet, marketingGoal, marketingCarrierType: marketingCarrierType, marketingTargetType, marketingSubGoal: 'MARKETING_SUB_GOAL_UNKNOWN' }
+            if (bidMode === 'BID_MODE_OCPC' || bidMode === 'BID_MODE_OCPM') {
+                obj.bidMode = bidMode
+            }
+            queryOptimizationGoalPermissions.run(obj)
+        }
+    }
+
+    useEffect(() => {
+        if (OGPParams.marketingCarrierType && OGPParams.marketingTargetType) {
+            getOptimizationGoalPermissions()
+        }
+    }, [OGPParams])
+
+    // 处理深度转化优化
+    useEffect(() => {
+        if (optimizationGoal && queryOptimizationGoalPermissions?.data) {
+            let { deepBehaviorOptimizationGoalPermissionList, deepWorthOptimizationGoalPermissionList } = queryOptimizationGoalPermissions?.data
+            let behavior = deepBehaviorOptimizationGoalPermissionList?.find((item: { optimizationGoal: string }) => item.optimizationGoal === optimizationGoal)
+            let worth = deepWorthOptimizationGoalPermissionList?.find((item: { optimizationGoal: string }) => item.optimizationGoal === optimizationGoal)
+            let newBehaviorList = behavior?.deepBehaviorOptimizationGoalList || []
+            setBehaviorList(newBehaviorList)
+            let newWorthList = worth?.deepWorthOptimizationGoalList || []
+            setWorthList(newWorthList)
+            let newDeepConversionData: PULLIN.DataType[] = [];
+            (newBehaviorList?.length > 0 && newDeepConversionData.push({ label: '优化转化行为', value: 'DEEP_CONVERSION_BEHAVIOR' }))
+            { newWorthList?.length > 0 && newDeepConversionData.push({ label: '优化ROI', value: 'DEEP_CONVERSION_WORTH' }) }
+            setDeepConversionData(newDeepConversionData)
+            form.setFieldsValue({
+                deepConversionSpec: {
+                    deepConversionType: newBehaviorList?.length > 0 ? 'DEEP_CONVERSION_BEHAVIOR' : newWorthList?.length > 0 ? "DEEP_CONVERSION_WORTH" : ''
+                }
+            })
+        }
+    }, [optimizationGoal, queryOptimizationGoalPermissions?.data])
+
+    // 选择营销目的触发
+    useEffect(() => {
+        let newRule: any = {}
+        let newMarketingTargetTypeList: PULLIN.DataType[] = []
+        if (marketingGoal) {
+            newRule = adRules[marketingGoal]
+            // 根据const里数据对比选出可展示数据
+            newMarketingTargetTypeList = Object.keys(MARKETING_TARGET_TYPE_ENUM).filter(key => newRule?.[key]).map(key => ({ label: MARKETING_TARGET_TYPE_ENUM[key], value: key }))
+        }
+        setMarketingTargetTypeList(newMarketingTargetTypeList)
+        setRules(newRule)
+    }, [marketingGoal])
+
+    // 推广产品设置默认值
+    useEffect(() => {
+        if (!(value && Object.keys(value).length > 0) && marketingTargetTypeList && (!marketingTargetType || (marketingTargetType && !marketingTargetTypeList.some(item => item.value === marketingTargetType)))) {
+            form.setFieldsValue({ marketingTargetType: marketingTargetTypeList?.[0]?.value })
+            // setOGPparams({ ...OGPParams, marketingTargetType: marketingTargetTypeList?.[0]?.value })
+        }
+    }, [marketingTargetType, marketingTargetTypeList, OGPParams, value])
+
+    // 营销载体类型
+    useEffect(() => {
+        let newMarketingTargetTypeListList: PULLIN.DataType[] = []
+        // 推广产品是公众号不展示 营销载体类型
+        if (marketingTargetType) {
+            let marketingTargetTypeRules: string[] = rules?.[marketingTargetType]?.MARKETING_SUB_GOAL_UNKNOWN
+            newMarketingTargetTypeListList = Object.keys(MARKETING_CARRIER_TYPE_ENUM).filter(key => marketingTargetTypeRules?.[key]).map(key => ({ label: MARKETING_CARRIER_TYPE_ENUM[key], value: key }))
+        }
+        setMarketingCarrierTypeList(newMarketingTargetTypeListList)
+    }, [marketingTargetType, rules])
+
+    // 设置营销载体默认值
+    useEffect(() => {
+        if (!(value && Object.keys(value).length > 0) && !marketingCarrierType || (marketingCarrierType && !marketingCarrierTypeList.some(item => item.value === marketingCarrierType))) {
+            let newMarketingCarrierType = marketingCarrierTypeList?.[0]?.value
+            form.setFieldsValue({ marketingCarrierType: newMarketingCarrierType })
+            setOGPparams({ ...OGPParams, marketingTargetType, marketingCarrierType: newMarketingCarrierType })
+        }
+    }, [marketingCarrierType, marketingCarrierTypeList, OGPParams, value, marketingTargetType])
+
+    useEffect(() => {
+        let optimizationGoalPermissionList: string[] = queryOptimizationGoalPermissions?.data?.optimizationGoalPermissionList
+        // OPTIMIZATIONGOAL_ECOMMERCE_ORDER
+        if (optimizationGoalPermissionList?.length > 0 && !optimizationGoalPermissionList?.includes(optimizationGoal)) {
+            form.setFieldsValue({ optimizationGoal: optimizationGoalPermissionList?.includes('OPTIMIZATIONGOAL_ECOMMERCE_ORDER') ? 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER' : optimizationGoalPermissionList?.includes('OPTIMIZATIONGOAL_PAGE_SCAN_CODE') ? 'OPTIMIZATIONGOAL_PAGE_SCAN_CODE' : undefined })
+        }
+    }, [queryOptimizationGoalPermissions?.data?.optimizationGoalPermissionList, marketingGoal, marketingTargetType, marketingCarrierType, optimizationGoal, value])
+
+    return <Card
+        title={<strong style={{ fontSize: 18 }}>营销内容</strong>}
+        className="cardResetCss"
+    >
+        <Form.Item name="marketingGoal" label={<strong>营销目的</strong>} rules={[{ required: true, message: '请选择营销目的!' }]}>
+            <MarketingGoal onChange={(e) => { setOGPparams({ ...OGPParams, marketingGoal: e as string }) }} />
+        </Form.Item>
+        {marketingTargetTypeList?.length > 0 && <Form.Item name="marketingTargetType" label={<strong>推广产品</strong>} rules={[{ required: true, message: '请选择推广产品!' }]}>
+            <NewRadio data={marketingTargetTypeList} onChange={(e) => {
+                if (e === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') { // 公众号
+                    setOGPparams({ ...OGPParams, marketingTargetType: e, marketingCarrierType: 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' })
+                    form.setFieldsValue({ marketingCarrierType: 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' })
+                } else {
+                    setOGPparams({ ...OGPParams, marketingTargetType: e })
+                }
+            }} />
+        </Form.Item>}
+        {marketingCarrierTypeList?.length > 0 && <Form.Item name="marketingCarrierType" label={<strong>营销载体类型</strong>} rules={[{ required: true, message: '请选择营销载体类型!' }]}>
+            <New1Radio data={marketingCarrierTypeList} onChange={(e) => setOGPparams({ ...OGPParams, marketingCarrierType: e })} />
+        </Form.Item>}
+        {(bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
+            <Form.Item label={<strong>优化目标</strong>} name='optimizationGoal' rules={[{ required: true, message: '请选择优化目标' }]}>
+                <Select
+                    style={{ width: 480 }}
+                    showSearch
+                    filterOption={(input, option) =>
+                        (option!.children as unknown as string)?.toLowerCase()?.includes(input?.toLowerCase())
+                    }
+                    allowClear
+                    placeholder='请选择'
+                >
+                    {queryOptimizationGoalPermissions?.data?.optimizationGoalPermissionList.filter((key: string) => key !== 'UNKNOWN').map((key: string) => {
+                        return <Select.Option value={key} key={key}>{OPTIMIZATIONGOAL_ENUM[key]}</Select.Option>
+                    })}
+                </Select>
+            </Form.Item>
+            {/* 深度优化 */}
+            {((behaviorList?.length > 0 || worthList?.length > 0) && smartBidType !== 'SMART_BID_TYPE_SYSTEMATIC') && <>
+                <Form.Item label={<strong>深度转化优化</strong>} name='depthConversionEnabled' valuePropName="checked">
+                    <Switch checkedChildren="开启" unCheckedChildren="关闭" onChange={(e) => {
+                        if (e) {
+                            form.setFieldsValue({
+                                deepConversionSpec: {
+                                    deepConversionType: behaviorList?.length > 0 ? 'DEEP_CONVERSION_BEHAVIOR' : worthList?.length > 0 ? "DEEP_CONVERSION_WORTH" : ''
+                                }
+                            })
+                        }
+                    }} />
+                </Form.Item>
+                {depthConversionEnabled && <>
+                    <Form.Item label={<strong>深度优化类型</strong>} name={['deepConversionSpec', 'deepConversionType']} rules={[{ required: true, message: '请选择深度优化类型' }]}>
+                        <New1Radio data={deepConversionData} />
+                    </Form.Item>
+                    <Form.Item label={<strong>深度优化目标</strong>} name={['deepConversionSpec', deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? 'deepConversionBehaviorSpec' : 'deepConversionWorthSpec', 'goal']} rules={[{ required: true, message: '请选择深度优化目标' }]}>
+                        <Select style={{ width: 480 }} placeholder='请选择'>
+                            {deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? Object.keys(OPTIMIZATIONGOAL_ENUM).filter(key => behaviorList?.includes(key)).map(key => <Select.Option value={key} key={key}>{OPTIMIZATIONGOAL_ENUM[key]}</Select.Option>) : deepConversionType === 'DEEP_CONVERSION_WORTH' ?
+                                Object.keys(GOAL_ROAS_ENUM).filter(key => worthList?.includes(key)).map(key => <Select.Option value={key} key={key}>{GOAL_ROAS_ENUM[key]}</Select.Option>) : null}
+                        </Select>
+                    </Form.Item>
+                </>}
+            </>}
+        </>}
+    </Card>
+}
+
+export default React.memo(AdgroupsMarketingContent)

+ 164 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsPrice.tsx

@@ -0,0 +1,164 @@
+import { Card, Form, Input, InputNumber, Space, Switch, Tooltip } from "antd"
+import React, { useContext } from "react"
+import { DispatchAd } from "./newCreateAd";
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { BID_MODE_ENUM, OPTIMIZATIONGOAL_ENUM, SMART_BID_TYPE_ENUM } from "../../const";
+import { QuestionCircleFilled } from "@ant-design/icons";
+
+
+/**
+ * 出价与预算
+ * @returns 
+ */
+const AdgroupsPrice: React.FC = () => {
+
+    /****************************************/
+    const { form, setOGPparams, OGPParams } = useContext(DispatchAd)!;
+
+    const siteSet = Form.useWatch('siteSet', form)
+    const bidMode = Form.useWatch('bidMode', form)
+    const optimizationGoal = Form.useWatch('optimizationGoal', form)
+    const smartBidType = Form.useWatch('smartBidType', form)
+    const autoAcquisitionEnabled = Form.useWatch('autoAcquisitionEnabled', form)
+    const automaticSiteEnabled = Form.useWatch('automaticSiteEnabled', form)
+    const deepConversionType = Form.useWatch(['deepConversionSpec', 'deepConversionType'], form);
+    const goal = Form.useWatch(['deepConversionSpec', deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? 'deepConversionBehaviorSpec' : 'deepConversionWorthSpec', 'goal'], form);
+    /****************************************/
+
+
+    return <Card
+        title={<strong style={{ fontSize: 18 }}>出价与预算</strong>}
+        className="cardResetCss"
+    >
+        <Form.Item
+            label={<Space>
+                <strong>计费方式</strong>
+                <Tooltip title={`设置了优化目标CPM、CPC、CPA不可选`}>
+                    <QuestionCircleFilled />
+                </Tooltip>
+            </Space>}
+            name='bidMode'
+            rules={[{ required: true, message: '请选择计费方式' }]}
+        >
+            <New1Radio
+                data={Object.keys(BID_MODE_ENUM).filter(key => {
+                    if (siteSet?.some((name: string) => ['SITE_SET_CHANNELS', 'SITE_SET_MOMENTS'].includes(name)) || automaticSiteEnabled) {
+                        return key === 'BID_MODE_OCPM' || key === 'BID_MODE_CPM'
+                    } else {
+                        return true
+                    }
+                })?.map(key => ({ label: BID_MODE_ENUM[key], value: key, disabled: optimizationGoal && ['BID_MODE_CPM', 'BID_MODE_CPC', 'BID_MODE_CPA'].includes(key) ? true : false }))}
+                onChange={(e) => {
+                    // form.setFieldsValue({ siteSet: defaultSiteSet })
+                    setOGPparams({ ...OGPParams, automaticSiteEnabled: e, bidMode: e as string })
+                    if (e === "BID_MODE_CPM" || e === "BID_MODE_CPC") {
+                        form.setFieldsValue({
+                            optimizationGoal: null,
+                            smartBidType: null,
+                            // bidAmount:null,
+                            bidStrategy: null,
+                            autoAcquisitionEnabled: false,
+                            autoAcquisitionBudget: null,
+                            dailyBudget: null,
+                        })
+                    } else {
+                        form.setFieldsValue({
+                            optimizationGoal: "OPTIMIZATIONGOAL_ECOMMERCE_ORDER",
+                            smartBidType: "SMART_BID_TYPE_CUSTOM",
+                            bidAmount: '1000',
+                            bidStrategy: "BID_STRATEGY_TARGET_COST",
+                            autoAcquisitionEnabled: false,
+                            autoAcquisitionBudget: null,
+                            dailyBudget: null,
+                        })
+                    }
+                }}
+            />
+        </Form.Item>
+        {(bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
+            <Form.Item label={<strong>出价类型</strong>} name='smartBidType' rules={[{ required: true, message: '请选择出价类型' }]}>
+                <New1Radio data={Object.keys(SMART_BID_TYPE_ENUM).map(key => ({ label: SMART_BID_TYPE_ENUM[key], value: key }))} />
+            </Form.Item>
+        </>}
+
+        {smartBidType !== 'SMART_BID_TYPE_SYSTEMATIC' && <>
+            <Form.Item label={<strong>出价</strong>} name='bidAmount' rules={[{ required: true, message: '请输入价格' }]}>
+                <Input
+                    placeholder={`请输入价格`}
+                    style={{ width: 480 }}
+                    suffix={`元/${optimizationGoal ? OPTIMIZATIONGOAL_ENUM[optimizationGoal] : ['BID_MODE_OCPM', 'BID_MODE_OCPC'].includes(bidMode) ? '千次曝光' : '点击'}`}
+                />
+            </Form.Item>
+
+            {deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? <>
+                <Form.Item label={<strong>深度目标出价</strong>} name={['deepConversionSpec', 'deepConversionBehaviorSpec', 'bidAmount']} rules={[{ required: true, message: '请输入深度目标出价' }]}>
+                    <Input style={{ width: 480 }} suffix={`元/${OPTIMIZATIONGOAL_ENUM[goal] || '优化目标'}`} placeholder={`请输入深度目标出价,范围0.1~10000`} />
+                </Form.Item>
+            </> :
+                deepConversionType === 'DEEP_CONVERSION_WORTH' ? <>
+                    <Form.Item
+                        label={<strong>期望ROI</strong>}
+                        name={['deepConversionSpec', 'deepConversionWorthSpec', 'expectedRoi']}
+                        rules={[
+                            { required: true, message: '请输入期望ROI' },
+                            { type: 'number', min: 0.001, max: 1000, message: '范围0.001~1000' },
+                            {
+                                validator: (_: any, value: string) => {
+                                    if (!value || /^\d+(\.\d{0,3})?$/.test(value)) {
+                                        return Promise.resolve();
+                                    }
+                                    return Promise.reject(new Error('请输入最多三位小数'));
+                                }
+                            }
+                        ]}
+                    >
+                        <InputNumber style={{ width: 480 }} placeholder={`期望ROI目标范围0.001~1000,输入0.05,表示ROI目标为5%`} />
+                    </Form.Item>
+                </> : null}
+
+            {(bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
+                <Form.Item
+                    style={{ marginBottom: 10 }}
+                    label={<Space>
+                        <strong>一键起量</strong>
+                        <Tooltip title={<div>
+                            <p>1. 一键起量原理:给该广告提供一笔起量预算,系统会在 6 小时内快速花完预算,帮助广告激进探索,获取更多曝光,期间转化成本可能高于预期;</p>
+                            <p>
+                                <span>2. 一键起量注意事项:</span><br />
+                                探索中任何原因导致广告暂停播放,都会导致起量中止,且恢复播放后也不会再继续探索; 一键起量期间产生的消耗不赔付,但转化计入赔付门槛判断;你可以在该广告的一键起量状态中止或结束时,重新设置起量预算,开始一次新的起量周期
+                            </p>
+                            <p>
+                                <span>点击查看</span><a href="https://e.qq.com/ads/helpcenter/detail?cid=3532&pid=2004" target="__blank">赔付规则</a><br />
+                                <span>点击了解</span><a href="https://e.qq.com/ads/helpcenter/detail?cid=3532&pid=2005" target="__blank">一键起量</a>
+                            </p>
+                        </div>}>
+                            <QuestionCircleFilled />
+                        </Tooltip>
+                    </Space>}
+                    name='autoAcquisitionEnabled'
+                    valuePropName="checked"
+                >
+                    <Switch checkedChildren="开启" unCheckedChildren="关闭" />
+                </Form.Item>
+                {/* 一键起量开启时才出现 */}
+                {autoAcquisitionEnabled && <Form.Item
+                    name='autoAcquisitionBudget'
+                    rules={[{ required: true, message: '请输入起量预算' }]}
+                    help={<div>
+                        <span>1. 一键起量期间产生的消耗不赔付,但转化计入赔付门槛判断</span><br />
+                        <span>2. 一键起量可能导致转化成本高于预期,且起量结束后不一定能持续消耗</span>
+                    </div>}
+                >
+                    <Input placeholder='请输入起量预算,建议设置为出价的10倍,范围 200~100000 元,不能低于出价' style={{ width: 560 }} suffix="元" />
+                </Form.Item>}
+            </>}
+        </>}
+
+        <Form.Item label={<strong>广告日预算</strong>} name='dailyBudget' rules={[{ required: smartBidType === 'SMART_BID_TYPE_SYSTEMATIC', message: '请输入广告日预算' }]}>
+            <Input placeholder={`广告日预算${smartBidType === 'SMART_BID_TYPE_SYSTEMATIC' ? '' : ', 不填默认为不限'}`} style={{ width: 480 }} suffix="元/天" />
+        </Form.Item>
+
+    </Card>
+}
+
+export default React.memo(AdgroupsPrice)

+ 276 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsSitSet.tsx

@@ -0,0 +1,276 @@
+import React, { useContext, useEffect, useState } from "react"
+import { DispatchAd } from "./newCreateAd";
+import { Card, Checkbox, Form, Radio, Space, Tooltip, message } from "antd";
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { DISPLAY_SCENE_ENUM, TENCENT_NEWS_ENUM, defaultSiteSet } from "../../const";
+import NewTree from "@/pages/launchSystemV3/components/NewTree";
+import { QuestionCircleFilled } from "@ant-design/icons";
+import style from '../index.less'
+import { getSceneTagsList } from "@/services/launchAdq/global";
+import { useAjax } from "@/Hook/useAjax";
+
+/**
+ * 广告版位
+ * @returns 
+ */
+const AdgroupsSitSet: React.FC = () => {
+
+    /****************************************/
+    const { form, setOGPparams, OGPParams } = useContext(DispatchAd)!;
+    const automaticSiteEnabled = Form.useWatch('automaticSiteEnabled', form);
+
+    const siteSet = Form.useWatch('siteSet', form);
+    const wechatPositionType = Form.useWatch('wechatPositionType', form);
+    const wechatSceneType = Form.useWatch('wechatSceneType', form);
+    const displaySceneType = Form.useWatch('displaySceneType', form);
+    const tencentNewsType = Form.useWatch('tencentNewsType', form);
+    const wechatChannelsSceneType = Form.useWatch('wechatChannelsSceneType', form);
+    const officialAccountMediaCategory = Form.useWatch(['wechatScene', 'officialAccountMediaCategory'], form);
+    const miniProgramAndMiniGame = Form.useWatch(['wechatScene', 'miniProgramAndMiniGame'], form);
+    const payScene = Form.useWatch(['wechatScene', 'payScene'], form);
+
+    const [OFFICIAL_ACCOUNT_MEDIA_CATEGORY, SET_OFFICIAL_ACCOUNT_MEDIA_CATEGORY] = useState<{ description: string, id: number }[]>([])
+    const [MINI_PROGRAM_AND_MINI_GAME, SET_MINI_PROGRAM_AND_MINI_GAME] = useState<{ description: string, id: number }[]>([])
+    const [PAY_SCENE, SET_PAY_SCENE] = useState<{ description: string, id: number }[]>([])
+    const [noLimitOAMC, setNoLimitOAMC] = useState<number>()
+    const [noLimitMPAMG, setNoLimitMPAMG] = useState<number>()
+    const [noLimitPS, setNoLimitPS] = useState<number>()
+
+    const sceneTagsList = useAjax((params) => getSceneTagsList(params))
+    /****************************************/
+
+    // 场景定向
+    useEffect(() => {
+        sceneTagsList.run({ typeList: ['WECHAT_POSITION', 'OFFICIAL_ACCOUNT_MEDIA_CATEGORY', 'MINI_PROGRAM_AND_MINI_GAME', 'PAY_SCENE', 'MOBILE_UNION_CATEGORY', 'WECHAT_CHANNELS_SCENE'] }).then(res => {
+            let NEW_OFFICIAL_ACCOUNT_MEDIA_CATEGORY = res?.OFFICIAL_ACCOUNT_MEDIA_CATEGORY
+            let NEW_MINI_PROGRAM_AND_MINI_GAME = res?.MINI_PROGRAM_AND_MINI_GAME
+            let NEW_PAY_SCENE = res?.PAY_SCENE
+            SET_OFFICIAL_ACCOUNT_MEDIA_CATEGORY(NEW_OFFICIAL_ACCOUNT_MEDIA_CATEGORY || [])
+            SET_MINI_PROGRAM_AND_MINI_GAME(NEW_MINI_PROGRAM_AND_MINI_GAME || [])
+            SET_PAY_SCENE(NEW_PAY_SCENE || [])
+            setNoLimitOAMC(NEW_OFFICIAL_ACCOUNT_MEDIA_CATEGORY?.filter((i: { description: string; }) => i.description === '不限')?.[0].id)
+            setNoLimitMPAMG(NEW_MINI_PROGRAM_AND_MINI_GAME?.filter((i: { description: string; }) => i.description === '不限')?.[0].id)
+            setNoLimitPS(NEW_PAY_SCENE?.filter((i: { description: string; }) => i.description === '不限')?.[0].id)
+        })
+    }, [])
+
+    return <Card
+        title={<strong style={{ fontSize: 18 }}>广告版位</strong>}
+        className="cardResetCss"
+    >
+        <Form.Item name="automaticSiteEnabled" style={{ marginBottom: automaticSiteEnabled ? 24 : 10 }} label={<strong>版位选择</strong>} rules={[{ required: true, message: '版位选择!' }]}>
+            <New1Radio
+                data={[{ label: '自动版位', value: true }, { label: '选择特定版位', value: false }]}
+                onChange={(e) => {
+                    form.setFieldsValue({ siteSet: defaultSiteSet })
+                    setOGPparams({ ...OGPParams, automaticSiteEnabled: e, siteSet: defaultSiteSet })
+                }}
+            />
+        </Form.Item>
+        {!automaticSiteEnabled && <Form.Item
+            name="siteSet"
+            rules={[{ required: true, message: '请选择特定版位!' }]}
+            getValueFromEvent={(e: string[]) => {
+                if (e?.length > 0) {
+                    return e
+                } else {
+                    message.warning('版位不可为空')
+                    return siteSet
+                }
+            }}
+        >
+            <NewTree onChange={(e) => {
+                setOGPparams({ ...OGPParams, siteSet: e as string[] })
+                form.setFieldsValue({ bidMode: 'BID_MODE_OCPM' })
+            }} />
+        </Form.Item>}
+
+        <Form.Item
+            label={<Space>
+                <strong>搜索场景扩量</strong>
+                <Tooltip title="广告将有机会投放至QQ浏览器、搜一搜等搜索流量场景,获取更多曝光">
+                    <QuestionCircleFilled />
+                </Tooltip>
+            </Space>}
+        >
+            <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>定向拓展</div>
+                        <Form.Item name="searchExpandTargetingSwitch" style={{ marginBottom: 0 }}>
+                            <Radio.Group buttonStyle="solid">
+                                <Radio.Button value="SEARCH_EXPAND_TARGETING_SWITCH_CLOSE">关闭</Radio.Button>
+                                <Radio.Button value="SEARCH_EXPAND_TARGETING_SWITCH_OPEN">开启</Radio.Button>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                </div>
+            </Card>
+        </Form.Item>
+
+        <Form.Item style={{ marginBottom: 0 }}>
+            <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+
+                {(siteSet?.includes('SITE_SET_WECHAT') || automaticSiteEnabled) && <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>微信公众号与小程序定投</div>
+                        <Form.Item name='wechatPositionType' style={{ marginBottom: 0 }}>
+                            <Radio.Group >
+                                <Radio value="0">不限</Radio>
+                                <Radio value="1">自定义</Radio>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                    {wechatPositionType === '1' && <div className={style.newSpace_bottom}>
+                        <Form.Item name='wechatPosition'>
+                            <Checkbox.Group options={sceneTagsList?.data?.WECHAT_POSITION?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
+                        </Form.Item>
+                    </div>}
+                </div>}
+
+                {(siteSet?.includes('SITE_SET_CHANNELS') || automaticSiteEnabled) && <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>微信视频号定投</div>
+                        <Form.Item name='wechatChannelsSceneType' style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择!' }]}>
+                            <Radio.Group >
+                                <Radio value="0">不限</Radio>
+                                <Radio value="1">自定义</Radio>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                    {wechatChannelsSceneType === '1' && <div className={style.newSpace_bottom}>
+                        <Form.Item name='wechatChannelsScene' rules={[{ required: true, message: '请选择!' }]}>
+                            <Checkbox.Group options={sceneTagsList?.data?.WECHAT_CHANNELS_SCENE?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
+                        </Form.Item>
+                    </div>}
+                </div>}
+
+
+                {(siteSet?.includes('SITE_SET_WECHAT') || automaticSiteEnabled) && <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>微信公众号与小程序场景</div>
+                        <Form.Item name='wechatSceneType' style={{ marginBottom: 0 }}>
+                            <Radio.Group
+                                onChange={(e) => {
+                                    if (e.target.value === '1') {
+                                        form.setFieldsValue({
+                                            wechatScene: {
+                                                officialAccountMediaCategory: [noLimitOAMC], miniProgramAndMiniGame: [noLimitMPAMG], payScene: [noLimitPS]
+                                            }
+                                        })
+                                    }
+                                }}
+                            >
+                                <Radio value="0">不限</Radio>
+                                <Radio value="1">自定义</Radio>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                    {wechatSceneType === '1' && <div className={style.newSpace_bottom}>
+                        <p style={{ marginBottom: 5 }}><strong style={{ marginRight: 20 }}>公众号媒体类型</strong></p>
+                        <Form.Item
+                            name={['wechatScene', 'officialAccountMediaCategory']}
+                            rules={[{ required: true, message: '请选择!' }]}
+                            getValueFromEvent={(e: number[]) => {
+                                if (e.length > 1 && !officialAccountMediaCategory?.includes(noLimitOAMC) && noLimitOAMC && e.includes(noLimitOAMC)) {
+                                    return [noLimitOAMC]
+                                }
+                                if (e?.length > 0) {
+                                    if (e?.length > 1 && noLimitOAMC && e.includes(noLimitOAMC)) {
+                                        return e.filter(item => item !== noLimitOAMC)
+                                    } else {
+                                        return e
+                                    }
+                                } else {
+                                    return [noLimitOAMC]
+                                }
+                            }}
+                        >
+                            <Checkbox.Group options={OFFICIAL_ACCOUNT_MEDIA_CATEGORY?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
+                        </Form.Item>
+                        <p style={{ marginBottom: 5 }}><strong style={{ marginRight: 20 }}>小程序小游戏流量类型</strong></p>
+                        <Form.Item
+                            name={['wechatScene', 'miniProgramAndMiniGame']}
+                            rules={[{ required: true, message: '请选择!' }]}
+                            getValueFromEvent={(e: number[]) => {
+                                if (e.length > 1 && !miniProgramAndMiniGame?.includes(noLimitMPAMG) && noLimitMPAMG && e.includes(noLimitMPAMG)) {
+                                    return [noLimitMPAMG]
+                                }
+                                if (e?.length > 0) {
+                                    if (e?.length > 1 && noLimitMPAMG && e.includes(noLimitMPAMG)) {
+                                        return e.filter(item => item !== noLimitMPAMG)
+                                    } else {
+                                        return e
+                                    }
+                                } else {
+                                    return [noLimitMPAMG]
+                                }
+                            }}
+                        >
+                            <Checkbox.Group options={MINI_PROGRAM_AND_MINI_GAME?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
+                        </Form.Item>
+                        <p style={{ marginBottom: 5 }}><strong style={{ marginRight: 20 }}>订单详情页消费场景</strong></p>
+                        <Form.Item
+                            name={['wechatScene', 'payScene']}
+                            rules={[{ required: true, message: '请选择!' }]}
+                            getValueFromEvent={(e: number[]) => {
+                                if (e.length > 1 && !payScene?.includes(noLimitPS) && noLimitPS && e.includes(noLimitPS)) {
+                                    return [noLimitPS]
+                                }
+                                if (e?.length > 0) {
+                                    if (e?.length > 1 && noLimitPS && e.includes(noLimitPS)) {
+                                        return e.filter(item => item !== noLimitPS)
+                                    } else {
+                                        return e
+                                    }
+                                } else {
+                                    return [noLimitPS]
+                                }
+                            }}
+                        >
+                            <Checkbox.Group options={PAY_SCENE?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
+                        </Form.Item>
+                    </div>}
+                </div>}
+
+                {(siteSet?.includes('SITE_SET_TENCENT_NEWS') || automaticSiteEnabled) && <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>腾讯新闻流量场景</div>
+                        <Form.Item name='tencentNewsType' style={{ marginBottom: 0 }}>
+                            <Radio.Group>
+                                <Radio value="0">不限</Radio>
+                                <Radio value="1">自定义</Radio>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                    {tencentNewsType === '1' && <div className={style.newSpace_bottom}>
+                        <Form.Item name='tencentNews' rules={[{ required: true, message: '请选择!' }]}>
+                            <Checkbox.Group options={Object.keys(TENCENT_NEWS_ENUM)?.map(key => ({ label: TENCENT_NEWS_ENUM[key], value: key }))} />
+                        </Form.Item>
+                    </div>}
+                </div>}
+
+                {(siteSet?.includes('SITE_SET_MOBILE_UNION') || automaticSiteEnabled) && <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>优量汇广告展示场景</div>
+                        <Form.Item name='displaySceneType' style={{ marginBottom: 0 }}>
+                            <Radio.Group>
+                                <Radio value="0">不限</Radio>
+                                <Radio value="1">自定义</Radio>
+                            </Radio.Group>
+                        </Form.Item>
+                    </div>
+                    {displaySceneType === '1' && <div className={style.newSpace_bottom}>
+                        <Form.Item name='displayScene' rules={[{ required: true, message: '请选择!' }]}>
+                            <Checkbox.Group options={Object.keys(DISPLAY_SCENE_ENUM)?.map(key => ({ label: DISPLAY_SCENE_ENUM[key], value: key }))} />
+                        </Form.Item>
+                    </div>}
+                </div>}
+
+            </Card>
+        </Form.Item>
+    </Card>
+}
+
+
+export default React.memo(AdgroupsSitSet)

+ 76 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/index.less

@@ -0,0 +1,76 @@
+.marketingGoal {
+    width: 100%;
+    overflow: hidden;
+    overflow-x: auto;
+
+    .marketingGoal_row {
+        display: flex;
+        gap: 10px;
+    }
+
+    .marketingGoal_col {
+        width: 140px;
+        height: 120px;
+        border-radius: 8px;
+        background: #f2f4fa;
+        border-width: 1px;
+        border-style: solid;
+        border-color: transparent;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+        position: relative;
+
+        >img {
+            width: 36px;
+            height: 36px;
+            transition: .2s ease;
+        }
+
+        >span {
+            font-size: 16px;
+            font-weight: 400;
+            color: rgb(49, 50, 51);
+        }
+
+        >div {
+            color: #1890ff;
+            position: absolute;
+            top: 0;
+            right: 0;
+            padding: 0 4px;
+            font-weight: 600;
+        }
+
+        &:hover {
+            background: #ebedf5;
+
+            & img {
+                transform: scale(1.2);
+            }
+        }
+    }
+
+    .marketingGoal_active {
+        border-color: rgba(41, 107, 239, 0.8);
+        background-color: #FAFCFF;
+
+        >span {
+            color: #1890ff;
+            font-weight: 600;
+        }
+
+        >img {
+            transform: scale(1.2);
+        }
+
+        &:hover {
+            background-color: #FAFCFF;
+            border-color: rgba(41, 107, 239, 0.8);
+        }
+    }
+}

+ 110 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/index.tsx

@@ -0,0 +1,110 @@
+import React, { useContext, useState } from "react"
+import style from '../index.less'
+import NewCreateAd from "./newCreateAd"
+import { DispatchAddelivery } from "..";
+import { Button, Typography } from "antd";
+import { EditOutlined } from "@ant-design/icons";
+import { AD_STATUS_ENUM, BID_MODE_ENUM, DEEP_CONVERSION_ENUM, GOAL_ROAS_ENUM, MARKETING_CARRIER_TYPE_ENUM, MARKETING_GOAL_ENUM, MARKETING_TARGET_TYPE_ENUM, OPTIMIZATIONGOAL_ENUM, SITE_SET_ENUM, SMART_BID_TYPE_ENUM } from "../../const";
+import TimeSeriesLook from "@/pages/launchSystemNew/adq/ad/timeSeriesLook";
+import { arraysHaveSameValues } from "@/utils/utils";
+
+
+/**
+ * 广告信息
+ * @returns 
+ */
+const Ad: React.FC = () => {
+
+    /*****************************/
+    const { addelivery, setAddelivery, accountCreateLogs, clearData, setAccountCreateLogs } = useContext(DispatchAddelivery)!;
+    const { adgroups } = addelivery
+    const {
+        marketingGoal, marketingAssetOuterSpec, marketingCarrierType, automaticSiteEnabled, siteSet, searchExpandTargetingSwitch, bidMode, smartBidType, bidAmount, optimizationGoal,
+        deepConversionSpec, autoAcquisitionEnabled, autoAcquisitionBudget, dailyBudget, endDate, beginDate, timeSeries, firstDayBeginTime, configuredStatus, adgroupName, wechatPosition
+    } = adgroups
+    const [newVisible, setNewVisible] = useState<boolean>(false)
+    /*****************************/
+
+    return <>
+        <div className={`${style.settingsBody_content_row} ${style.row1}`}>
+            <div className={style.title}>
+                <span>广告信息</span>
+            </div>
+            <div className={style.detail}>
+                <div className={style.detail_body}>
+                    {(adgroups && Object.keys(adgroups).length > 0) ? <>
+                        <p>营销目的:{MARKETING_GOAL_ENUM[marketingGoal]}</p>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>推广产品类型:{MARKETING_TARGET_TYPE_ENUM[marketingAssetOuterSpec?.marketingTargetType]}</p>
+                        <p>营销载体类型:{MARKETING_CARRIER_TYPE_ENUM[marketingCarrierType]}</p>
+                        <p>版位选择:{automaticSiteEnabled ? '自动版位' : '选择特定版位'}</p>
+                        {!automaticSiteEnabled && <Typography.Paragraph className={style.tpP} style={{ marginBottom: 0 }} ellipsis={{ tooltip: true, rows: 2 }}>广告版位:{siteSet.map((item: string | number) => SITE_SET_ENUM[item]).toString()}</Typography.Paragraph>}
+                        <p>搜索场景扩量:{searchExpandTargetingSwitch === 'SEARCH_EXPAND_TARGETING_SWITCH_OPEN' ? '开启' : '关闭'}</p>
+                        <p>计费方式:{BID_MODE_ENUM[bidMode]}</p>
+                        <p>出价类型:{SMART_BID_TYPE_ENUM[smartBidType]}</p>
+                        <p>出价:{bidAmount}元/{optimizationGoal ? OPTIMIZATIONGOAL_ENUM[optimizationGoal] : ['BID_MODE_OCPM', 'BID_MODE_OCPC'].includes(bidMode) ? '千次曝光' : '点击'}</p>
+                        {optimizationGoal && <p style={{ fontWeight: 'bold', color: '#000' }}>优化目标:{OPTIMIZATIONGOAL_ENUM[optimizationGoal]}</p>}
+                        {deepConversionSpec && <>
+                            <p style={{ fontWeight: 'bold', color: '#000' }}>深度转化优化:开启</p>
+                            <p style={{ fontWeight: 'bold', color: '#000' }}>深度优化类型:{DEEP_CONVERSION_ENUM[deepConversionSpec?.deepConversionType]}</p>
+                            {deepConversionSpec.deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? <>
+                                <p style={{ fontWeight: 'bold', color: '#000' }}>深度优化目标:{OPTIMIZATIONGOAL_ENUM[deepConversionSpec.deepConversionBehaviorSpec.goal]}</p>
+                                <p style={{ fontWeight: 'bold', color: '#000' }}>深度目标出价:{deepConversionSpec.deepConversionBehaviorSpec.bidAmount}元/{OPTIMIZATIONGOAL_ENUM[deepConversionSpec.deepConversionBehaviorSpec.goal] || '优化目标'}</p>
+                            </> : <>
+                                <p style={{ fontWeight: 'bold', color: '#000' }}>深度优化目标:{GOAL_ROAS_ENUM[deepConversionSpec.deepConversionWorthSpec.goal]}</p>
+                                <p style={{ fontWeight: 'bold', color: '#000' }}>期望ROI:{deepConversionSpec.deepConversionWorthSpec.expectedRoi}</p>
+                            </>}
+                        </>}
+                        <p>一键起量:{autoAcquisitionEnabled ? '开启' : '关闭'}</p>
+                        {autoAcquisitionEnabled && <p>起量预算:{autoAcquisitionBudget}元/天</p>}
+                        <p>广告日预算:{dailyBudget ? dailyBudget + '元/天' : '不限'}</p>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>投放日期:{beginDate} 至 {endDate}</p>
+                        <p>投放时段:{timeSeries.includes('0') ? <TimeSeriesLook timeSeries={timeSeries} /> : '全天'}</p>
+                        <p>首日开始时间:{firstDayBeginTime ? firstDayBeginTime : '关闭'}</p>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>广告状态:{AD_STATUS_ENUM[configuredStatus]}</p>
+                        <p>广告名称:{adgroupName}</p>
+                    </> : <div className={style.ad_config}>
+                        {accountCreateLogs?.length > 0 ?
+                            <div className={style.ad_config_item} onClick={() => setNewVisible(true)}>新建广告</div>
+                            :
+                            <div className={style.ad_config_item} style={{ backgroundColor: '#FFF' }}>请完善媒体账户信息</div>
+                        }
+                    </div>}
+                </div>
+                <div className={style.detail_footer}>
+                    {(adgroups && Object.keys(adgroups).length > 0) ? <>
+                        <Button type="link" icon={<EditOutlined />} style={{ padding: 0, fontSize: 12 }} onClick={() => setNewVisible(true)}>编辑</Button>
+                    </> : <></>}
+                </div>
+            </div>
+        </div>
+
+        {/* 新建广告 */}
+        {newVisible && <NewCreateAd
+            value={addelivery.adgroups}
+            visible={newVisible}
+            onClose={() => {
+                setNewVisible(false)
+            }}
+            onChange={(adgroups) => {
+                if (
+                    adgroups.marketingGoal === marketingGoal &&  // 营销内容
+                    adgroups.marketingCarrierType === marketingCarrierType && // 营销载体
+                    adgroups.marketingAssetOuterSpec.marketingTargetType === marketingAssetOuterSpec.marketingTargetType && // 推广产品
+                    adgroups.automaticSiteEnabled === automaticSiteEnabled &&  // 版位选择
+                    arraysHaveSameValues(adgroups?.siteSet || [], siteSet || []) &&  // 版位
+                    arraysHaveSameValues(adgroups?.wechatPosition || [], wechatPosition || []) // 微信公众号与小程序定投
+                ) {
+                    setAddelivery({ ...addelivery, adgroups })
+                } else {
+                    setAccountCreateLogs(accountCreateLogs.map(item => ({ accountId: item.accountId })))
+                    setAddelivery({ adgroups, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {} })
+                }
+                setNewVisible(false)
+                clearData()
+            }}
+        />}
+    </>
+}
+
+
+export default React.memo(Ad)

+ 27 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/marketingGoal.tsx

@@ -0,0 +1,27 @@
+import React from "react"
+import style from './index.less'
+import { marketingGoalList } from "../../const"
+import { CheckCircleFilled } from "@ant-design/icons"
+
+/**
+ * 营销目的
+ * @param param0 
+ * @returns 
+ */
+const MarketingGoal: React.FC<PULLIN.FormItemDataProps> = ({ value, onChange, id }) => {
+
+
+    return <div className={style.marketingGoal} id={id}>
+        <div className={style.marketingGoal_row}>
+            {marketingGoalList.map(item => <div key={item.value} onClick={() => onChange?.(item.value)} className={`${style.marketingGoal_col} ${value === item.value ? style.marketingGoal_active : ''}`}>
+                {value === item.value ? <>
+                    <img src={item.iconSelected} />
+                    <div><CheckCircleFilled /></div>
+                </> : <img src={item.icon} />}
+                <span>{item.label}</span>
+            </div>)}
+        </div>
+    </div>
+}
+
+export default React.memo(MarketingGoal)

+ 241 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/newCreateAd.tsx

@@ -0,0 +1,241 @@
+import { Button, Form, Modal, Space, message } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../index.less'
+import { defaultMarketingGoal, defaultSiteSet } from "../../const"
+import AdgroupsMarketingContent from "./adgroupsMarketingContent"
+import AdgroupsSitSet from "./adgroupsSitSet"
+import AdgroupsPrice from "./adgroupsPrice"
+import AdgroupsAdSetting from "./adgroupsAdSetting"
+import moment from "moment"
+import { getTimeSeriesList } from "@/pages/launchSystemNew/adq/ad/const"
+
+export const DispatchAd = React.createContext<PULLIN.AdReactContent | null>(null);
+
+interface Props {
+    value?: any,
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (value: any) => void
+}
+
+/**
+ * 广告
+ * @param param0 
+ * @returns 
+ */
+const NewCreateAd: React.FC<Props> = ({ value, visible, onChange, onClose }) => {
+
+    /***********************************/
+    const [form] = Form.useForm();
+    // 深度优化副作用参数
+    const [OGPParams, setOGPparams] = useState<PULLIN.OGPParamsProps>({ bidMode: 'BID_MODE_OCPM', siteSet: defaultSiteSet, automaticSiteEnabled: false, marketingGoal: defaultMarketingGoal })
+    /***********************************/
+
+    const handleOk = (values: any) => {
+        const {
+            wechatPositionType,      // 微信公众号与小程序定投
+            wechatChannelsSceneType, // 微信视频号定投
+            wechatSceneType,         // 微信公众号与小程序场景
+            marketingTargetType,     // 推广产品
+            tencentNewsType,
+            displaySceneType,
+            date,                    // 开始时间 结束时间
+            timeSeriesType,          // 选择时段类型
+            timeSeries,              // 时段
+            isSetfirstDayBeginTime,  // 首日开始时间是否开启
+            ...surplusValues
+        } = values
+
+        let adgroupsValues: any = {
+            ...surplusValues,
+            beginDate: moment(date?.[0]).format('YYYY-MM-DD'),
+            endDate: moment(date?.[1]).format('YYYY-MM-DD'),
+        }
+
+        if (marketingTargetType) {
+            adgroupsValues.marketingAssetOuterSpec = {
+                marketingTargetType
+            }
+        }
+
+        // 投放时段处理
+        if (timeSeriesType === '0') {
+            adgroupsValues.timeSeries = Array(336).fill(1).join('');
+        } else {
+            adgroupsValues.timeSeries = timeSeries.join('');
+        }
+
+        Object.keys(adgroupsValues).forEach(key => {
+            if (adgroupsValues[key] === undefined || adgroupsValues === null) {
+                delete adgroupsValues[key]
+            }
+        })
+        onChange?.(adgroupsValues)
+    }
+
+    // 数据回填
+    useEffect(() => {
+        if (value && Object.keys(value).length > 0) {
+            const {
+                firstDayBeginTime,
+                timeSeries,
+                beginDate,
+                endDate,
+                wechatPosition,
+                wechatChannelsScene,
+                wechatScene,
+                tencentNews,
+                displayScene,
+                marketingAssetOuterSpec,
+                ...surplusValues
+            } = JSON.parse(JSON.stringify(value))
+            let adgroupsValues: any = {
+                ...surplusValues,
+                wechatPosition,
+                tencentNews,
+                displayScene,
+                wechatScene,
+                marketingTargetType: marketingAssetOuterSpec?.marketingTargetType
+            }
+
+            // 首日开始时间
+            if (firstDayBeginTime) {
+                adgroupsValues.firstDayBeginTime = firstDayBeginTime
+                adgroupsValues.isSetfirstDayBeginTime = true
+            } else {
+                adgroupsValues.isSetfirstDayBeginTime = false
+            }
+
+            // 时段
+            if (timeSeries && timeSeries.includes('0')) {
+                adgroupsValues.timeSeriesType = '2'
+            } else {
+                adgroupsValues.timeSeriesType = '0'
+            }
+            adgroupsValues.timeSeries = timeSeries?.split('') || getTimeSeriesList()
+
+            // 投放时间
+            adgroupsValues.date = [moment(beginDate), moment(endDate)]
+
+            // 微信公众号与小程序定投
+            if (wechatPosition?.length > 0) {
+                adgroupsValues.wechatPositionType = '1'
+            } else {
+                adgroupsValues.wechatPositionType = '0'
+            }
+            // 微信视频号定投
+            if (wechatChannelsScene?.length > 0) {
+                adgroupsValues.wechatChannelsSceneType = '1'
+            } else {
+                adgroupsValues.wechatChannelsSceneType = '0'
+            }
+            // 微信公众号与小程序场景
+            if (wechatScene && Object.keys(wechatScene).length > 0) {
+                adgroupsValues.wechatSceneType = '1'
+            } else {
+                adgroupsValues.wechatSceneType = '0'
+            }
+            // 腾讯新闻流量场景
+            if (tencentNews && tencentNews?.length > 0) {
+                adgroupsValues.tencentNewsType = '1'
+            } else {
+                adgroupsValues.tencentNewsType = '0'
+            }
+            // 优量汇广告展示场景
+            if (displayScene && displayScene?.length > 0) {
+                adgroupsValues.displaySceneType = '1'
+            } else {
+                adgroupsValues.displaySceneType = '0'
+            }
+            setOGPparams({
+                bidMode: adgroupsValues.bidMode,
+                siteSet: adgroupsValues.siteSet,
+                automaticSiteEnabled: adgroupsValues.automaticSiteEnabled,
+                marketingGoal: adgroupsValues.marketingGoal,
+                marketingCarrierType: adgroupsValues?.marketingCarrierType,
+                marketingTargetType: adgroupsValues?.marketingTargetType
+            })
+            form.setFieldsValue({ ...adgroupsValues })
+        }
+    }, [value])
+
+    return <Modal
+        title={<strong style={{ fontSize: 20 }}>广告的基本信息</strong>}
+        visible={visible}
+        onCancel={onClose}
+        footer={null}
+        width={900}
+        className={`modalResetCss`}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+    >
+        <Form
+            form={form}
+            name="newAd"
+            labelAlign='left'
+            layout="vertical"
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '0 10px 10px', borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{
+                marketingGoal: defaultMarketingGoal,
+                automaticSiteEnabled: false,
+                siteSet: defaultSiteSet,
+                searchExpandTargetingSwitch: 'SEARCH_EXPAND_TARGETING_SWITCH_OPEN',
+                bidMode: 'BID_MODE_OCPM',
+                smartBidType: 'SMART_BID_TYPE_CUSTOM',
+                optimizationGoal: 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER',
+                depthConversionEnabled: true,
+                deepConversionSpec: {
+                    deepConversionType: 'DEEP_CONVERSION_WORTH'
+                },
+                // bidAmount: 1000,
+                timeSeries: getTimeSeriesList(),
+                autoAcquisitionEnabled: false,
+                configuredStatus: 'AD_STATUS_SUSPEND',
+                // adgroupName: '<营销目的>-<推广产品>-<日期>-<时分秒>',
+                // marketingTargetType: 'MARKETING_TARGET_TYPE_FICTION'
+                // 自定义字段
+                date: [moment().startOf('day').add(7, 'day'), moment().startOf('day').add(20, 'day')],
+                wechatPositionType: '0',
+                wechatSceneType: '0',
+                displaySceneType: '0',
+                tencentNewsType: '0',
+                wechatChannelsSceneType: '0',
+                timeSeriesType: '0',
+                isSetfirstDayBeginTime: false,
+            }}
+        >
+            <DispatchAd.Provider value={{ form, OGPParams, setOGPparams }}>
+                <Space direction="vertical" style={{ width: '100%' }}>
+                    {/* 营销内容 */}
+                    <AdgroupsMarketingContent value={value} />
+                    {/* 广告版位 */}
+                    <AdgroupsSitSet />
+                    {/* 出价与预算 */}
+                    <AdgroupsPrice />
+                    {/* 广告设置 */}
+                    <AdgroupsAdSetting value={value} />
+                </Space>
+            </DispatchAd.Provider>
+
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" className="modalResetCss">
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(NewCreateAd)

+ 375 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeConversionAssistant.tsx

@@ -0,0 +1,375 @@
+import { Card, Checkbox, Form, Radio, Select, Space, Switch, Tooltip } from "antd"
+import React, { useContext, useEffect, useMemo, useState } from "react"
+import { DispatchDynamic } from "./newDynamic";
+import style from '../index.less'
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { useUpdateEffect } from "ahooks";
+import { QuestionCircleFilled } from "@ant-design/icons";
+
+/**
+ * 营销组件
+ * @returns 
+ */
+const CreativeConversionAssistant: React.FC<{ automaticSiteEnabled?: boolean }> = ({ automaticSiteEnabled }) => {
+
+    /**************************************/
+    const { creativeComponents, form, isUpdate, setIsUpdate } = useContext(DispatchDynamic)!;
+    const textLinkShow = Form.useWatch('textLinkShow', form)
+    const actionButtonShow = Form.useWatch('actionButtonShow', form)
+    const showDataShow = Form.useWatch('showDataShow', form)
+    const pageSpec = Form.useWatch('pageSpec', form)
+    const linkNameType = Form.useWatch(['textLink', 'value', 'linkNameType'], form)
+    const conversionDataType = Form.useWatch(['showData', 'value', 'conversionDataType'], form)
+    const conversionTargetType = Form.useWatch(['showData', 'value', 'conversionTargetType'], form)
+    const creativeTemplateId = Form.useWatch('creativeTemplateId', form)
+    const cardType: string[] = Form.useWatch('cardType', form)
+    const [cardData, setCardData] = useState<{ label: string, value: string, disabled?: boolean }[]>([])
+    /**************************************/
+
+    useUpdateEffect(() => {
+        if (isUpdate) {
+            setDefaultShouData()
+        }
+    }, [linkNameType, conversionDataType, conversionTargetType, showDataShow, creativeComponents?.show_data, isUpdate])
+
+    const setDefaultShouData = () => {
+        setIsUpdate(false)
+        if (showDataShow && creativeComponents?.show_data) {
+            let showData = creativeComponents?.show_data;
+            let link_name_type = linkNameType || ''
+            let conversionDataTypeData = showData?.children?.conversion_data_type
+            let values: any = {}
+            if (conversionDataTypeData) {
+                let conversionDataTypeEnumeration = (conversionDataTypeData.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                    .filter(item => item.category === link_name_type)
+                    .map(item => item.value)
+                if (conversionDataTypeEnumeration.length === 0) {
+                    conversionDataTypeEnumeration = (conversionDataTypeData.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                        .filter(item => item.category === '')
+                        .map(item => item.value)
+                }
+                if (!conversionDataTypeEnumeration.includes(conversionDataType)) {
+                    values.conversionDataType = conversionDataTypeEnumeration?.[0]
+                }
+            }
+            let conversionTargetTypeData = showData?.children?.conversion_target_type
+            if (conversionTargetTypeData) {
+                let conversionTargetTypeEnumeration = (conversionTargetTypeData.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                    .filter(item => item.category === link_name_type)
+                    .map(item => item.value)
+                if (conversionTargetTypeEnumeration.length === 0) {
+                    conversionTargetTypeEnumeration = (conversionTargetTypeData.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                        .filter(item => item.category === '')
+                        .map(item => item.value)
+                }
+                if (!conversionTargetTypeEnumeration.includes(conversionTargetType)) {
+                    values.conversionTargetType = conversionTargetTypeEnumeration?.[0]
+                }
+            }
+            let valueKeys = Object.keys(values)
+            if (valueKeys.length > 0) {
+                if (!valueKeys.includes('conversionTargetType')) {
+                    values.conversionTargetType = conversionTargetType
+                }
+                if (!valueKeys.includes('conversionDataType')) {
+                    values.conversionDataType = conversionDataType
+                }
+                console.log('values--->', values)
+                form.setFieldsValue({
+                    showData: {
+                        value: values
+                    }
+                })
+            }
+        }
+    }
+
+    /** 微信文字链 */
+    const textLinkContent = useMemo(() => {
+        let textLink = creativeComponents?.text_link
+        if (textLink && ![1707, 1708].includes(creativeTemplateId) && !automaticSiteEnabled) {
+            let pageSpecPageType = pageSpec?.[0]?.pageType || 'PAGE_TYPE_WECHAT_CANVAS'
+            let textLinkRequired, linkNameEnumeration: { label: string, value: string }[] = [];
+            let linkNamePageType, linkNamePageTypeEnumeration: PULLIN.DataType[] = [];
+            textLinkRequired = textLink.required
+            let linkNameType = textLink.children.link_name_type
+            linkNameEnumeration = (linkNameType.enumProperty.enumeration as { value: string, description: string }[]).map(item => ({ label: item.description, value: item.value }))
+            linkNamePageType = textLink?.children?.page_type
+            if (linkNamePageType) {
+                linkNamePageTypeEnumeration = (linkNamePageType?.enumProperty?.enumeration as { value: string, description: string }[]).filter(item => item.value === pageSpecPageType).map(item => ({ label: item.description, value: item.value }))
+            }
+
+            return <Form.Item style={{ marginBottom: 0 }}>
+                <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>朋友圈文字链</div>
+                        <Form.Item name={'textLinkShow'} valuePropName="checked" style={{ marginBottom: 0 }}>
+                            <Switch disabled={textLinkRequired} onChange={() => setIsUpdate(true)} />
+                        </Form.Item>
+                    </div>
+                </div>
+                {textLinkShow && <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                    <div className={style.newSpace}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>文字链文案</div>
+                            <Form.Item
+                                name={['textLink', 'value', 'linkNameType']}
+                                rules={[{ required: true, message: '请选择文字链文案' }]}
+                                style={{ marginBottom: 0 }}
+                            >
+                                <Select
+                                    style={{ width: 480 }}
+                                    showSearch
+                                    placeholder="请选择文字链文案"
+                                    optionFilterProp="children"
+                                    filterOption={(input, option: any) =>
+                                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                                    }
+                                    onChange={() => setIsUpdate(true)}
+                                    options={linkNameEnumeration}
+                                />
+                            </Form.Item>
+                        </div>
+                    </div>
+                    {linkNamePageType && <div className={style.newSpace}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>跳转落地页</div>
+                            <Form.Item
+                                name={['textLink', 'value', 'jumpInfo', 'pageType']}
+                                rules={[{ required: true, message: '请选择跳转落地页类型' }]}
+                                style={{ marginBottom: 0 }}
+                            >
+                                <New1Radio data={linkNamePageTypeEnumeration} />
+                            </Form.Item>
+                        </div>
+                    </div>}
+                </Card>}
+            </Form.Item>
+        }
+        return null
+    }, [creativeComponents?.text_link, pageSpec, textLinkShow, creativeTemplateId, automaticSiteEnabled])
+
+    /** 行动按钮 */
+    const actionButtonContent = useMemo(() => {
+        // 卡片广告用卡片组件控制
+        if ([1707, 1708].includes(creativeTemplateId) && !cardType?.includes('action_button')) {
+            return null
+        }
+        let actionButton = creativeComponents?.action_button
+        if (actionButton) {
+            let pageSpecPageType = pageSpec?.[0]?.pageType || 'PAGE_TYPE_WECHAT_CANVAS'
+            let actionButtonRequired, butttonTextEnumeration: { label: string, value: string }[] = [];
+            let actionButtonPageType, actionButtonPageTypeEnumeration: PULLIN.DataType[] = [];
+            actionButtonRequired = actionButton.required
+            let buttonText = actionButton.children.button_text
+            butttonTextEnumeration = (buttonText.enumProperty.enumeration as { value: string }[]).map(item => ({ label: item.value, value: item.value }))
+            actionButtonPageType = actionButton?.children?.page_type
+            if (actionButtonPageType) {
+                actionButtonPageTypeEnumeration = (actionButtonPageType?.enumProperty?.enumeration as { value: string, description: string }[]).filter(item => item.value === pageSpecPageType).map(item => ({ label: item.description, value: item.value }))
+            }
+
+            return <Form.Item style={{ marginBottom: 0 }}>
+                {![1707, 1708].includes(creativeTemplateId) && <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>行动按钮</div>
+                        <Form.Item name={'actionButtonShow'} valuePropName="checked" style={{ marginBottom: 0 }}>
+                            <Switch disabled={actionButtonRequired} />
+                        </Form.Item>
+                    </div>
+                </div>}
+                {(actionButtonShow || cardType?.includes('action_button')) && <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                    <div className={style.newSpace}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>按钮文案</div>
+                            <Form.Item
+                                name={['actionButton', 'value', 'buttonText']}
+                                rules={[{ required: true, message: '请选择按钮文案' }]}
+                                style={{ marginBottom: 0 }}
+                            >
+                                <Select
+                                    style={{ width: 480 }}
+                                    showSearch
+                                    placeholder="请选择按钮文案"
+                                    optionFilterProp="children"
+                                    filterOption={(input, option: any) =>
+                                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                                    }
+                                    options={butttonTextEnumeration}
+                                />
+                            </Form.Item>
+                        </div>
+                    </div>
+                    {actionButtonPageType && <div className={style.newSpace}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>跳转落地页</div>
+                            <Form.Item
+                                name={['actionButton', 'value', 'jumpInfo', 'pageType']}
+                                rules={[{ required: true, message: '请选择跳转落地页类型' }]}
+                                style={{ marginBottom: 0 }}
+                            >
+                                <New1Radio data={actionButtonPageTypeEnumeration} />
+                            </Form.Item>
+                        </div>
+                    </div>}
+                </Card>}
+            </Form.Item>
+        }
+        return null
+    }, [creativeComponents?.action_button, pageSpec, actionButtonShow, cardType, creativeTemplateId])
+
+    const showDataContent = useMemo(() => {
+        let showData = creativeComponents?.show_data;
+        if (showData) {
+            let conversionDataTypeEnumeration: PULLIN.DataType[] = [];
+            let conversionTargetTypeEnumeration: PULLIN.DataType[] = [];
+            let link_name_type = linkNameType || ''
+            let showDataRequired = showData?.required
+            let conversionDataType = showData?.children?.conversion_data_type
+            if (conversionDataType) {
+                conversionDataTypeEnumeration = (conversionDataType.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                    .filter(item => item.category === link_name_type)
+                    .map(item => ({ label: item.description, value: item.value }))
+                if (conversionDataTypeEnumeration.length === 0) {
+                    conversionDataTypeEnumeration = (conversionDataType.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                        .filter(item => item.category === '')
+                        .map(item => ({ label: item.description, value: item.value }))
+                }
+            }
+            let conversionTargetType = showData?.children?.conversion_target_type
+            if (conversionTargetType) {
+                conversionTargetTypeEnumeration = (conversionTargetType.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                    .filter(item => item.category === link_name_type)
+                    .map(item => ({ label: item.description, value: item.value }))
+                if (conversionTargetTypeEnumeration.length === 0) {
+                    conversionTargetTypeEnumeration = (conversionTargetType.enumProperty.enumeration as { value: string, category: string, description: string }[])
+                        .filter(item => item.category === '')
+                        .map(item => ({ label: item.description, value: item.value }))
+                }
+            }
+            // 卡片广告去掉商品数据
+            if ([1707, 1708].includes(creativeTemplateId)) {
+                conversionDataTypeEnumeration = conversionDataTypeEnumeration.filter(item => item.value !== "CONVERSION_DATA_PRODUCT_DATA")
+            }
+
+            return <Form.Item style={{ marginBottom: 0 }}>
+                <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>数据外显</div>
+                        <Form.Item name={'showDataShow'} valuePropName="checked" style={{ marginBottom: 0 }}>
+                            <Switch disabled={showDataRequired} onChange={() => setIsUpdate(true)} />
+                        </Form.Item>
+                    </div>
+                </div>
+                {showDataShow && <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                    <div className={style.newSpace}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>数据类型</div>
+                            <Form.Item
+                                name={['showData', 'value', 'conversionDataType']}
+                                rules={[{ required: true, message: '请选择数据类型' }]}
+                                style={{ marginBottom: 0 }}
+                            >
+                                <Radio.Group options={conversionDataTypeEnumeration} />
+                            </Form.Item>
+                        </div>
+                    </div>
+                    <div className={style.newSpace}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>转化行为</div>
+                            <Form.Item
+                                name={['showData', 'value', 'conversionTargetType']}
+                                rules={[{ required: true, message: '请选择转化行为' }]}
+                                style={{ marginBottom: 0 }}
+                            >
+                                <Radio.Group options={conversionTargetTypeEnumeration} />
+                            </Form.Item>
+                        </div>
+                    </div>
+                </Card>}
+            </Form.Item>
+        }
+        return null
+    }, [creativeComponents?.show_data, linkNameType, showDataShow, creativeTemplateId])
+
+
+    useEffect(() => {
+        let data = [
+            { label: '不使用', value: 'not', disabled: false },
+            { label: '行动按钮', value: 'action_button', disabled: cardType?.some(item => ['chosen_button', 'shop_imag'].includes(item)) },
+            // { label: '选择按钮', value: 'chosen_button', disabled: cardType?.some(item => ['label', 'action_button', 'shop_imag'].includes(item)) },
+            // { label: '卖点图', value: 'shop_imag', disabled: cardType?.some(item => ['chosen_button'].includes(item)) },
+            // { label: '标签', value: 'label', disabled: cardType?.some(item => ['chosen_button', 'count_down'].includes(item)) },
+            // { label: '倒计时', value: 'count_down', disabled: cardType?.some(item => ['living_desc', 'label'].includes(item)) },
+            // { label: '轮播文案', value: 'living_desc', disabled: cardType?.some(item => ['count_down'].includes(item)) }
+        ]
+        if (cardType?.length >= 3) {
+            setCardData(data.map(item => {
+                if (cardType.includes(item.value)) {
+                    return { ...item, disabled: false }
+                } else if (item.value === 'not') {
+                    return { ...item, disabled: false }
+                } else {
+                    return { ...item, disabled: true }
+                }
+            }))
+        } else {
+            setCardData(data)
+        }
+    }, [cardType])
+
+    if (Object.keys(creativeComponents).some(key => ['text_link', 'action_button', 'show_data'].includes(key))) {
+        return <Card
+            title={<strong style={{ fontSize: 18 }}>营销组件</strong>}
+            className="cardResetCss"
+        >
+            {/* 卡片广告所需 */}
+            {[1707, 1708].includes(creativeTemplateId) && <Form.Item style={{ marginBottom: 0 }}>
+                <div className={style.newSpace}>
+                    <div className={style.newSpace_top}>
+                        <div className={style.newSpace_title}>
+                            <Space>
+                                <strong>卡片组件</strong>
+                                <Tooltip title={<div>
+                                    卡片组件有以下使用条件:<br />
+                                    1.卡片组件最多只能使用 3 个<br />
+                                    2.倒计时不能与标签、轮播文案同时使用<br />
+                                    3.卖点图必须与行动按钮同时使用<br />
+                                    4.选择按钮不能与行动按钮、标签、卖点图同时使用
+                                </div>}>
+                                    <QuestionCircleFilled />
+                                </Tooltip>
+                            </Space>
+                        </div>
+                        <Form.Item
+                            noStyle
+                            name={'cardType'}
+                            rules={[{ required: true, message: '请选择跳转落地页类型' }]}
+                            style={{ marginBottom: 0 }}
+                            getValueFromEvent={(e: string[]) => {
+                                if (e.length > 1 && !cardType.includes('not') && e.includes('not')) {
+                                    return ['not'];
+                                }
+                                if (e.includes('shop_imag') && !e.includes('action_button')) {
+                                    e.push('action_button')
+                                }
+                                return e.length > 0 ? (e.length > 1 && e.includes('not') ? e.filter(item => item !== 'not') : e) : ['not'];
+                            }}
+                        >
+                            <Checkbox.Group options={cardData} onChange={(e) => {
+                                console.log(e)
+                            }} />
+                        </Form.Item>
+                    </div>
+                </div>
+
+            </Form.Item>}
+            {textLinkContent}
+            {actionButtonContent}
+            {showDataContent}
+        </Card>
+    } else {
+        return null
+    }
+}
+
+export default React.memo(CreativeConversionAssistant)

+ 132 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeTemplateContent.tsx

@@ -0,0 +1,132 @@
+import { Button, Card, Form, Radio, Space } from "antd"
+import React, { useContext, useMemo } from "react"
+import { DispatchDynamic } from "./newDynamic";
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
+import BrandImage from "@/pages/launchSystemV3/components/BrandImage";
+import { SelectProfiles } from "@/pages/launchSystemV3/tencenTasset/profiles";
+
+
+/**
+ * 创意内容
+ * @returns 
+ */
+const CreativeTemplateContent: React.FC<{ automaticSiteEnabled: boolean }> = ({ automaticSiteEnabled }) => {
+
+    /******************************************/
+    const { creativeComponents, form, adgroups: { siteSet } } = useContext(DispatchDynamic)!;
+    const pageType = Form.useWatch(['jumpInfo', 'pageType'], form)
+    const pageSpec = Form.useWatch('pageSpec', form)
+    const linkName = Form.useWatch(['textLink', 'value', 'linkName'], form)
+    /******************************************/
+
+    const content = useMemo(() => {
+        let brand;
+        let enumeration: PULLIN.DataType[] = []
+        let pageTypeList: PULLIN.DataType[] = []
+        let jumpInfoNumber = 1
+        Object.keys(creativeComponents).forEach(key => {
+            switch (key) {
+                case 'brand':
+                    brand = creativeComponents[key]
+                    let page_type = brand.children.page_type
+                    let typeList = ["PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL", "PAGE_TYPE_H5_PROFILE", "PAGE_TYPE_NOT_USED"]
+                    enumeration = (page_type.enumProperty.enumeration as { value: string, description: string }[]).filter((item: { value: string; }) => typeList.includes(item.value)).map(item => ({ label: item.value === 'PAGE_TYPE_NOT_USED' ? '品牌形象' : item.description, value: item.value }))
+                    break
+                case 'jump_info':
+                    let jump_info = creativeComponents[key]
+                    jumpInfoNumber = jump_info?.arrayProperty?.maxNumber || 1
+                    let jumpInfoPageType = jump_info.children.page_type
+                    pageTypeList = (jumpInfoPageType.enumProperty.enumeration as { value: string, description: string }[]).filter(item => !["PAGE_TYPE_WECHAT_CANVAS_MINI_PROGRAM", "PAGE_TYPE_H5", "PAGE_TYPE_WECHAT_SIMPLE_CANVAS", "PAGE_TYPE_APP_DEEP_LINK"].includes(item.value)).map(item => ({ label: item.description, value: item.value, disabled: "PAGE_TYPE_WECHAT_CANVAS" !== item.value }))
+                    break
+            }
+        })
+
+        return <>
+            {brand && <Form.Item style={{ marginBottom: 0 }}>
+                <Form.Item name={['jumpInfo', 'pageType']} label={<strong>品牌形象跳转</strong>} rules={[{ required: true, message: '请选择品牌形象跳转!' }]} tooltip="品牌形象将在各流量版位下广告外层创意展示。此外,朋友圈广告在此基础上支持跳转,点击品牌形象可跳转到品牌简介页或搜一搜品牌主页">
+                    <New1Radio data={enumeration} />
+                </Form.Item>
+                {['PAGE_TYPE_H5_PROFILE'].includes(pageType) ? <div style={{ margin: '0 0 10px', backgroundColor: '#fafafb', padding: '8px 16px 8px', borderRadius: 8 }}>
+                    <Form.Item name='brand' style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择一个头像及昵称跳转页,与广告创意一起展示' }]}>
+                        <SelectProfiles />
+                    </Form.Item>
+                </div> : ["PAGE_TYPE_NOT_USED"].includes(pageType) && <div style={{ margin: '0 0 10px', backgroundColor: '#fafafb', padding: '8px 16px 8px', borderRadius: 8 }}>
+                    <Form.Item name='brand' style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择一个品牌形象,与广告创意一起展示' }]}>
+                        <BrandImage />
+                    </Form.Item>
+                </div>}
+            </Form.Item>}
+            <Form.Item style={{ marginBottom: 0 }} label={<strong>跳转类型({pageSpec?.length || 1}/{jumpInfoNumber})</strong>}>
+                <Form.List name="pageSpec">
+                    {(fields, { add, remove }) => (
+                        <>
+                            {fields.map(({ key, name, ...restField }) => (
+                                <Space key={key} style={{ display: 'flex', marginBottom: 8, width: '100%' }} align="baseline">
+                                    <Space direction="vertical" style={{ width: '100%' }}>
+                                        <Form.Item
+                                            {...restField}
+                                            name={[name, 'pageType']}
+                                            style={{ marginBottom: 0 }}
+                                            rules={[{ required: true, message: '请选择跳转类型!' }]}
+                                        >
+                                            <New1Radio
+                                                data={pageTypeList}
+                                                onChange={(pageType) => {
+                                                    form.setFieldsValue({ pageSpec: pageSpec.map((item: any) => ({ ...item, pageType })) })
+                                                    if (Object.keys(creativeComponents).includes('text_link')) {
+                                                        let textLink = creativeComponents['text_link']
+                                                        let linkNameType = textLink?.children?.link_name_type
+                                                        let linkNameEnumeration = (linkNameType?.enumProperty?.enumeration as { value: string, description: string }[]).map(item => ({ label: item.description, value: item.value + '_' + item.description }))
+                                                        form.setFieldsValue({
+                                                            textLink: {
+                                                                value: {
+                                                                    linkName: linkName || linkNameEnumeration?.[0]?.value,
+                                                                    jump_info: {
+                                                                        pageType: pageType
+                                                                    }
+                                                                }
+                                                            }
+                                                        })
+                                                    }
+                                                }}
+                                            />
+                                        </Form.Item>
+                                        {((siteSet?.some((name: string) => name === 'SITE_SET_MOMENTS') || automaticSiteEnabled) && pageSpec?.[key]?.pageType === 'PAGE_TYPE_WECHAT_CANVAS') && <div style={{ margin: 0, backgroundColor: '#fafafb', padding: '8px 16px 8px', borderRadius: 8 }}>
+                                            <Form.Item
+                                                name={[name, 'overrideCanvasHeadOption']}
+                                                style={{ marginBottom: 0 }}
+                                            >
+                                                <Radio.Group disabled>
+                                                    <Radio value="OPTION_CREATIVE_OVERRIDE_CANVAS">使用外层创意素材替换原生推广页顶部素材</Radio>
+                                                </Radio.Group>
+                                            </Form.Item>
+                                        </div>}
+                                    </Space>
+                                    {pageSpec?.length > 1 && <MinusCircleOutlined onClick={() => remove(name)} />}
+                                </Space>
+                            ))}
+                            {pageSpec?.length < jumpInfoNumber && <Form.Item>
+                                <Button type="dashed" onClick={() => add({ pageType: pageSpec?.[0]?.pageType || 'PAGE_TYPE_WECHAT_CANVAS', overrideCanvasHeadOption: 'OPTION_CANVAS_OVERRIDE_CREATIVE' })} block icon={<PlusOutlined />}>
+                                    还可以添加 {jumpInfoNumber - pageSpec?.length} 组
+                                </Button>
+                            </Form.Item>}
+                        </>
+                    )}
+                </Form.List>
+            </Form.Item>
+
+
+        </>
+    }, [creativeComponents, pageType, pageSpec, siteSet])
+
+    return <Card
+        title={<strong style={{ fontSize: 18 }}>创意内容</strong>}
+        className="cardResetCss"
+    >
+        {content}
+    </Card>
+}
+
+
+export default React.memo(CreativeTemplateContent)

+ 51 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeTemplateSetup.tsx

@@ -0,0 +1,51 @@
+import { Card, Form } from "antd"
+import React from "react"
+import InputName from "@/components/InputName";
+import { txtLength } from "@/utils/utils";
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { AD_STATUS_ENUM } from "../../const";
+
+
+/**
+ * 创意设置
+ * @returns 
+ */
+const CreativeTemplateSetup: React.FC = () => {
+
+    return <Card
+        title={< strong style={{ fontSize: 18 }}>创意设置</strong >}
+        className="cardResetCss"
+    >
+        <Form.Item label={<strong>创意状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择创意状态' }]}>
+            <New1Radio data={Object.keys(AD_STATUS_ENUM).map(key => ({ label: AD_STATUS_ENUM[key], value: key }))} />
+        </Form.Item>
+        <Form.Item
+            label={<strong>创意名称</strong>}
+            name='dynamicCreativeName'
+            rules={[
+                { required: true, message: '请输入创意名称!' },
+                {
+                    required: true, message: '创意名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(_, value) {
+                        let reg = /[&‘’“”/\n\t\f]/ig
+                        if (value && reg.test(value)) {
+                            return Promise.reject()
+                        }
+                        return Promise.resolve()
+                    }
+                },
+                {
+                    required: true, message: '请确保创意名称长度不超过60个字(1个汉字等于2个字符)', validator(_, value) {
+                        if (value && txtLength(value) > 60) {
+                            return Promise.reject()
+                        }
+                        return Promise.resolve()
+                    }
+                }
+            ]}
+        >
+            <InputName placeholder='请输入创意名称' style={{ width: 480 }} length={60} />
+        </Form.Item>
+    </Card>
+}
+
+export default React.memo(CreativeTemplateSetup)

+ 167 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/index.tsx

@@ -0,0 +1,167 @@
+import React, { useContext, useEffect, useState } from "react"
+import style from '../index.less'
+import { Button, Space } from "antd"
+import { EditOutlined } from "@ant-design/icons"
+import { DispatchAddelivery } from ".."
+import NewDynamic from "./newDynamic"
+import { AD_STATUS_ENUM, CONVERSION_DATA_ENUM, CONVERSION_TARGET_ENUM, DELIVERY_MODE_ENUM, PAGE_TYPE_ENUM, TEXT_LINK_TYPE_ENUM, pageSpecFieldConVertUn } from "../../const"
+import { getCreativeDetailsApi, getProfilesApi } from "@/services/adqV3/global"
+import { useAjax } from "@/Hook/useAjax"
+import { arraysHaveSameValues, processData } from "@/utils/utils"
+
+/**
+ * 创意
+ * @returns 
+ */
+const Dynamic: React.FC = () => {
+
+    /***************************************/
+    const { addelivery, setAddelivery, setMaterialData, setTextData, clearData, setAccountCreateLogs, accountCreateLogs } = useContext(DispatchAddelivery)!;
+    const { adgroups, dynamic: dynamicData } = addelivery;
+    const { marketingGoal, marketingAssetOuterSpec, marketingCarrierType, siteSet, automaticSiteEnabled, wechatPosition } = adgroups
+    const { deliveryMode, creativeTemplateId, dynamicCreativeName, creativeComponents, configuredStatus } = dynamicData
+    const { textLink, actionButton, showData, brand, mainJumpInfo } = creativeComponents || {}
+    const [newVisible, setNewVisible] = useState<boolean>(false);
+    const [creativeTemplateAppellation, setCreativeTemplateAppellation] = useState<string>()
+    const [creativeTemplateStyle, setCreativeTemplateStyle] = useState<string>()
+    const [profileData, setprofileData] = useState<any>()
+
+    const getCreativeDetails = useAjax((params) => getCreativeDetailsApi(params))
+    const getProfiles = useAjax((params) => getProfilesApi(params))
+    /***************************************/
+
+    useEffect(() => {
+        if (creativeTemplateId) {
+            let params: any = {
+                marketingGoal,
+                marketingTargetType: marketingAssetOuterSpec.marketingTargetType,
+                marketingCarrierType,
+                deliveryMode,
+                creativeTemplateId,
+                wechatSceneSpecPosition: wechatPosition,
+                dynamicCreativeType: deliveryMode === 'DELIVERY_MODE_COMPONENT' ? 'DYNAMIC_CREATIVE_TYPE_PROGRAM' : 'DYNAMIC_CREATIVE_TYPE_COMMON'
+            }
+            if (automaticSiteEnabled) {
+                params.automaticSiteEnabled = automaticSiteEnabled
+            } else {
+                params.siteSet = siteSet
+            }
+            getCreativeDetails.run(params).then(res => {
+                if (res?.adcreativeTemplateStructAdpermits?.length > 0) {
+                    let adcreativeTemplateStructAdpermits = res?.adcreativeTemplateStructAdpermits[0]
+                    setCreativeTemplateAppellation(adcreativeTemplateStructAdpermits.creativeTemplateAppellation)
+                    setCreativeTemplateStyle(adcreativeTemplateStructAdpermits.creativeTemplateStyle)
+
+                    let creativeComponents = adcreativeTemplateStructAdpermits?.creativeComponents || []
+                    let result = processData(creativeComponents);
+
+                    let newMaterialData = {};
+                    let newTextData = {};
+
+                    Object.keys(result).forEach(key => {
+                        let data = result[key]
+                        if ((key === 'image_list' || key === 'short_video' || key === 'video' || key === 'image' || key === 'element_story') && data.required) {
+                            newMaterialData[key] = data
+                        } else if (key === 'title' || (data.required && key === 'description')) {
+                            newTextData[key] = data
+                        }
+                    })
+
+                    setMaterialData(newMaterialData)
+                    setTextData(newTextData)
+                }
+            })
+        }
+    }, [creativeTemplateId, deliveryMode, marketingGoal, marketingAssetOuterSpec, marketingCarrierType, siteSet, wechatPosition, automaticSiteEnabled])
+
+    useEffect(() => {
+        if (['PAGE_TYPE_H5_PROFILE'].includes(brand?.[0]?.value?.jumpInfo?.pageType)) {
+            getProfiles.run({}).then(res => {
+                if (res) {
+                    setprofileData(res?.find((item: { id: any }) => item.id === brand?.[0]?.value?.profileId))
+                }
+            })
+        }
+    }, [brand])
+
+    return <div className={`${style.settingsBody_content_row} ${style.row3}`}>
+        <div className={style.title}>
+            <span>创意信息</span>
+        </div>
+        <div className={style.detail}>
+            <div className={style.detail_body}>
+                {dynamicData && Object.keys(dynamicData).length > 0 && <>
+                    <p>创意名称:{dynamicCreativeName}</p>
+                    <p style={{ fontWeight: 'bold', color: '#000' }}>创意状态:{AD_STATUS_ENUM[configuredStatus]}</p>
+                    <p>投放模式:{DELIVERY_MODE_ENUM[deliveryMode]}</p>
+                    <p>创意形式:{creativeTemplateAppellation || creativeTemplateId}</p>
+                    {brand?.length > 0 && <>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>品牌形象跳转:{PAGE_TYPE_ENUM[brand?.[0]?.value?.jumpInfo?.pageType]}</p>
+                        {['PAGE_TYPE_H5_PROFILE'].includes(brand?.[0]?.value?.jumpInfo?.pageType) ? <>
+                            {profileData ? <>
+                                <Space>
+                                    <img src={profileData?.headImageUrl} alt="" width={20} style={{ display: 'block' }} />
+                                    <span style={{ fontWeight: 'bold', color: '#000', fontSize: 12 }}>{profileData?.profileName}</span>
+                                </Space>
+                                <p>详细描述:{profileData?.description}</p>
+                            </> : <span style={{ color: 'red', fontWeight: 'bold', fontSize: 12 }}>{getProfiles.loading ? '' : '当前头像昵称跳转页被删除'} </span>}
+                        </> : ['PAGE_TYPE_NOT_USED'].includes(brand?.[0]?.value?.jumpInfo?.pageType) && <Space align="center">
+                            <img src={brand?.[0]?.value?.brandImageId} alt="" width={20} style={{ display: 'block' }} />
+                            <span style={{ fontWeight: 'bold', color: '#000', fontSize: 12 }}>{brand?.[0]?.value?.brandName}</span>
+                        </Space>}
+                    </>}
+                    <p style={{ fontWeight: 'bold', color: '#000' }}>跳转类型:{mainJumpInfo?.map((item: any) => {
+                        let pageSpec = item.value.pageSpec
+                        return PAGE_TYPE_ENUM[pageSpecFieldConVertUn[Object.keys(pageSpec)?.[0]]]
+                    }).toString()}</p>
+                    {textLink?.length > 0 && <>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>朋友圈文字链:开启</p>
+                        <p>文字链文案:{TEXT_LINK_TYPE_ENUM[textLink?.[0]?.value?.linkNameType]}</p>
+                        {textLink?.[0]?.value?.jumpInfo?.pageType && <p>跳转落地页:{PAGE_TYPE_ENUM[textLink?.[0]?.value?.jumpInfo?.pageType]}</p>}
+                    </>}
+                    {actionButton?.length > 0 && <>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>行动按钮:开启</p>
+                        <p>按钮文案:{actionButton?.[0]?.value?.buttonText}</p>
+                        {actionButton?.[0]?.value?.jumpInfo?.pageType && <p>跳转落地页:{PAGE_TYPE_ENUM[actionButton?.[0]?.value?.jumpInfo?.pageType]}</p>}
+                    </>}
+                    {showData?.length > 0 && <>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>数据外显:开启</p>
+                        <p>数据类型:{CONVERSION_DATA_ENUM[showData?.[0]?.value?.conversionDataType]}</p>
+                        <p>转化行为:{CONVERSION_TARGET_ENUM[showData?.[0]?.value?.conversionTargetType]}</p>
+                    </>}
+                </>}
+            </div>
+            <div className={style.detail_footer}>
+                <Button disabled={!(adgroups && Object.keys(adgroups)?.length > 0)} type="link" icon={<EditOutlined />} style={{ padding: 0, fontSize: 12 }} onClick={() => setNewVisible(true)}>编辑</Button>
+            </div>
+        </div>
+
+        {newVisible && <NewDynamic
+            visible={newVisible}
+            creativeTemplateStyle={creativeTemplateStyle}
+            value={dynamicData}
+            onClose={() => {
+                setNewVisible(false)
+            }}
+            onChange={(dynamic) => {
+                if (
+                    dynamic.deliveryMode === deliveryMode && // 投放模式
+                    dynamic?.creativeTemplateId === creativeTemplateId // 创意形式
+                ) {
+                    setAddelivery({ ...addelivery, dynamic })
+                } else {
+                    setAddelivery({ ...addelivery, dynamic, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {} })
+                }
+                let oldPageTypeList = mainJumpInfo?.map((item: { value: { pageType: any } }) => item.value.pageType) || []
+                let newPageTypeList = dynamic?.creativeComponents?.mainJumpInfo.map((item: { value: { pageType: any } }) => item.value.pageType)
+                setNewVisible(false)
+                clearData()
+                if (!arraysHaveSameValues(oldPageTypeList || [], newPageTypeList || [])) {
+                    setAccountCreateLogs(accountCreateLogs.map(item => ({ ...item, pageList: [] })))
+                }
+            }}
+        />}
+    </div>
+}
+
+export default React.memo(Dynamic)

+ 543 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/newDynamic.tsx

@@ -0,0 +1,543 @@
+import { Button, Card, Form, Modal, Radio, Space, Spin, message } from "antd"
+import React, { useContext, useEffect, useState } from "react"
+import '../../index.less'
+import { getCreativeDetailsApi, getCreativeTemplateListApi } from "@/services/adqV3/global";
+import { useAjax } from "@/Hook/useAjax";
+import { DELIVERY_MODE_ENUM, pageSpecFieldConVert, pageSpecFieldConVertUn } from "../../const";
+import New2Radio from "@/pages/launchSystemV3/components/New2Radio";
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import { outAdcreativeTemplateIdFun } from "@/pages/launchSystemNew/launchManage/localAd/adenum";
+import { DispatchAddelivery } from "..";
+import { processData, randomString } from "@/utils/utils";
+import CreativeTemplateContent from "./creativeTemplateContent";
+import CreativeConversionAssistant from "./creativeConversionAssistant";
+import CreativeTemplateSetup from "./creativeTemplateSetup";
+
+
+export const DispatchDynamic = React.createContext<PULLIN.DynamicReactContent | null>(null);
+
+interface Props {
+    value?: any,
+    visible?: boolean
+    creativeTemplateStyle?: string,
+    onClose?: () => void
+    onChange?: (value: any) => void
+}
+
+/**
+ * 创意新建
+ * @param param0 
+ * @returns 
+ */
+const NewDynamic: React.FC<Props> = ({ value, visible, onClose, onChange, creativeTemplateStyle: oldCreativeTemplateStyle }) => {
+
+    /**********************************/
+    const { addelivery, setMaterialData, setTextData } = useContext(DispatchAddelivery)!;
+    const { adgroups } = addelivery
+    const [form] = Form.useForm();
+    const creativeTemplateStyle = Form.useWatch('creativeTemplateStyle', form);
+    const deliveryMode = Form.useWatch('deliveryMode', form);
+
+    const [marketingGoalTypeList, setMarketingGoalTypeList] = useState<PULLIN.DataType[]>([])
+    const [adcreativeTemplateList, setAdcreativeTemplateList] = useState<PULLIN.AdcreativeTemplateList[]>([])
+    const [creativeComponents, setCreativeComponents] = useState<any>({})
+    const [isUpdate, setIsUpdate] = useState<boolean>(false)
+    const { marketingGoal, marketingAssetOuterSpec, marketingCarrierType, siteSet, wechatPosition, automaticSiteEnabled } = adgroups
+
+    const [newMaterialData, setNewMaterialData] = useState<any>({}) // 素材数据
+    const [newTextData, setNewTextData] = useState<any>({})
+
+    const getCreativeTemplateList = useAjax((params) => getCreativeTemplateListApi(params))
+    const getCreativeDetails = useAjax((params) => getCreativeDetailsApi(params))
+    /**********************************/
+
+    useEffect(() => {
+        if (deliveryMode === 'DELIVERY_MODE_CUSTOMIZE') { // 自定义创意
+            getCreativeTemplateList.run({
+                marketingGoal,
+                marketingTargetType: marketingAssetOuterSpec.marketingTargetType,
+                marketingCarrierType,
+                siteSet,
+                wechatSceneSpecPosition: wechatPosition || []
+            }).then((res: any) => {
+                let newArr: any = []
+                let newData: any[] = []
+                // 过滤掉相同的和即将下线的
+                if (!res) {
+                    return
+                }
+                if (automaticSiteEnabled) {
+                    delete res?.SITE_SET_WECHAT
+                }
+                let creativeTemplateStyle = ''
+                Object.values(res)?.forEach(arr => {
+                    newData.push(arr)
+                    if (Array.isArray(arr)) {
+                        arr?.forEach((item: any) => {
+                            let adcreativeTemplateListStructAdpermit = item?.adcreativeTemplateListStructAdpermit
+                            if (adcreativeTemplateListStructAdpermit) {
+                                creativeTemplateStyle += adcreativeTemplateListStructAdpermit?.creativeTemplateStyle
+                                let creativeTemplateId = adcreativeTemplateListStructAdpermit.creativeTemplateId
+                                if (newArr.length > 0) {//假如已存在ID,需要过滤相同
+                                    if (outAdcreativeTemplateIdFun(creativeTemplateId) && newArr.every((i: { creativeTemplateId: any }) => i.creativeTemplateId !== creativeTemplateId)) {//不重复的添加
+                                        newArr.push(adcreativeTemplateListStructAdpermit)
+                                    }
+                                } else {//不存在ID直接过滤掉即将下线的
+                                    if (outAdcreativeTemplateIdFun(creativeTemplateId)) {
+                                        newArr.push(adcreativeTemplateListStructAdpermit)
+                                    }
+                                }
+                            }
+                        })
+                    }
+                })
+
+                /*****暂时排除激励和banner有问题******/
+                if (!siteSet || siteSet.some((i: string) => i === 'SITE_SET_MOMENTS')) {
+                    newArr = newArr.filter((item: { creativeTemplateId: number }) => ![2107, 2109].includes(item.creativeTemplateId))
+                }
+                /*****暂时排除出框形态 视频合约广告******/
+                if (!siteSet || siteSet.some((i: string) => i === 'SITE_SET_WECHAT')) {
+                    newArr = newArr.filter((item: { creativeTemplateId: number }) => item.creativeTemplateId !== 1945)
+                }
+                newArr = newArr.filter((item: { creativeTemplateId: number }) => ![713, 727, 951, 965].includes(item.creativeTemplateId))
+
+                let newArr1: any[] = []
+                let newArr2: any[] = []
+                newArr?.forEach((arr: { creativeTemplateId: any, creativeTemplateAppellation: string, creativeSampleImage: string, isGeneral?: boolean }) => {
+                    let arr2 = { label: arr.creativeTemplateAppellation, value: arr.creativeTemplateId, img: arr.creativeSampleImage, ...arr }
+                    if (newData.every((item: any[]) => item.find(i => i?.adcreativeTemplateListStructAdpermit?.creativeTemplateId === arr.creativeTemplateId))) {
+                        newArr1.push({ ...arr2, isGeneral: true })
+                    } else {
+                        newArr2.push(arr2)
+                    }
+                })
+
+                setAdcreativeTemplateList([...newArr1, ...newArr2])
+                let goalTypeData: PULLIN.DataType[] = []
+                if (creativeTemplateStyle.includes('视频')) {
+                    goalTypeData.push({ label: '视频', value: 'video' })
+                    form.setFieldsValue({ creativeTemplateStyle: 'video' })
+                } else if (creativeTemplateStyle.includes('图片')) {
+                    form.setFieldsValue({ creativeTemplateStyle: 'image' })
+                }
+                if (creativeTemplateStyle.includes('图片')) {
+                    goalTypeData.push({ label: '图片', value: 'image' })
+                }
+                setMarketingGoalTypeList(goalTypeData)
+            })
+        } else if (deliveryMode === 'DELIVERY_MODE_COMPONENT') {  // 组件化创意
+            getTemplate()
+        }
+    }, [deliveryMode, marketingGoal, marketingAssetOuterSpec, marketingCarrierType, siteSet, wechatPosition, value, automaticSiteEnabled])
+
+    useEffect(() => {
+        if (!(value && Object.keys(value).length > 0) && adcreativeTemplateList?.length > 0 && marketingGoalTypeList?.length > 0) {
+            typeChange(marketingGoalTypeList.some(item => item.value === 'video') ? '视频' : '图片')
+        }
+    }, [value, adcreativeTemplateList, marketingGoalTypeList])
+
+    const typeChange = (creativeTemplateStyle: string, isAntTemplateId?: boolean) => {
+        if (creativeTemplateStyle && adcreativeTemplateList?.length > 0) {
+            let adcreativeTemplateIdArr = adcreativeTemplateList?.filter(item => item.creativeTemplateStyle === creativeTemplateStyle)
+            if (adcreativeTemplateIdArr?.length > 0) {
+                let creativeTemplateId = adcreativeTemplateIdArr[0].creativeTemplateId
+                getTemplate(creativeTemplateId, isAntTemplateId)
+                form.setFieldsValue({ creativeTemplateId })
+            }
+        }
+    }
+
+    // 获取创意形式详情
+    const getTemplate = (id?: any, isAntTemplateId?: boolean) => {
+        // CAMPAIGN_TYPE_NORMAL
+        if (marketingAssetOuterSpec?.marketingTargetType && deliveryMode) {
+            let params: any = {
+                marketingGoal,
+                marketingTargetType: marketingAssetOuterSpec.marketingTargetType,
+                marketingCarrierType,
+                deliveryMode,
+                creativeTemplateId: id,
+                wechatSceneSpecPosition: wechatPosition,
+                dynamicCreativeType: deliveryMode === 'DELIVERY_MODE_COMPONENT' ? 'DYNAMIC_CREATIVE_TYPE_PROGRAM' : 'DYNAMIC_CREATIVE_TYPE_COMMON'
+            }
+            if (automaticSiteEnabled) {
+                params.automaticSiteEnabled = automaticSiteEnabled
+            } else {
+                params.siteSet = siteSet
+            }
+            getCreativeDetails.run(params).then(res => {
+                if (res?.adcreativeTemplateStructAdpermits?.length > 0) {
+                    let adcreativeTemplateStructAdpermits = res?.adcreativeTemplateStructAdpermits[0]
+                    templateChange(adcreativeTemplateStructAdpermits, isAntTemplateId)
+                } else {
+                    message.error({
+                        content: '当前所选营销目的,广告版位下不支持该创意形式。或者无此创意形式权限,请联系相关人员开通白名单',
+                        duration: 10
+                    })
+                    setCreativeComponents({})
+                }
+            })
+        }
+    }
+
+    //每次选中创意设置该展示的界面
+    const templateChange = (adcreativeTemplateStructAdpermits: any, isAntTemplateId?: boolean) => {
+        let creativeComponents = adcreativeTemplateStructAdpermits?.creativeComponents || []
+        let creativeTemplateId = adcreativeTemplateStructAdpermits?.creativeTemplateId
+        let creativeTemplateAppellation = adcreativeTemplateStructAdpermits?.creativeTemplateAppellation
+        let result = processData(creativeComponents);
+        console.log(result);
+        // console.log(JSON.stringify(result));
+        setCreativeComponents(result)
+
+        let newMaterialData = {};
+        let newTextData = {};
+
+        Object.keys(result).forEach(key => {
+            let data = result[key]
+            if ((key === 'image_list' || key === 'short_video' || key === 'video' || key === 'image' || key === 'element_story') && data.required) {
+                newMaterialData[key] = data
+            } else if (key === 'title' || (data.required && key === 'description')) {
+                newTextData[key] = data
+            }
+        })
+
+        setNewMaterialData(newMaterialData)
+        setNewTextData(newTextData)
+        if (!(value && Object.keys(value).length > 0) || isAntTemplateId) {
+            let values: any = {
+                dynamicCreativeName: creativeTemplateAppellation ? '自定义创意' + '_' + creativeTemplateAppellation + '_' + localStorage.getItem('userId') + '_' + randomString(true, 3, 5) : '动态创意' + '_' + localStorage.getItem('userId') + '_' + randomString(true, 3, 5),
+                pageSpec: [{
+                    pageType: 'PAGE_TYPE_WECHAT_CANVAS',
+                    overrideCanvasHeadOption: 'OPTION_CREATIVE_OVERRIDE_CANVAS'
+                }],
+                brand: undefined
+            }
+            Object.keys(result).forEach(key => {
+                switch (key) {
+                    case 'brand':
+                        let brand = result[key]
+                        let page_type = brand.children.page_type
+                        let typeList = ["PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL", "PAGE_TYPE_H5_PROFILE", "PAGE_TYPE_NOT_USED"]
+                        let enumeration = (page_type.enumProperty.enumeration as { value: string, description: string }[]).filter((item: { value: string; }) => typeList.includes(item.value))
+                        values.jumpInfo = {
+                            pageType: enumeration[0]?.value
+                        }
+                        break
+                    case 'text_link':// 朋友圈文字链
+                        let textLink = result[key]
+                        let linkNameType = textLink?.children?.link_name_type
+                        let linkNameEnumeration = (linkNameType?.enumProperty?.enumeration as { value: string, description: string }[]).map(item => ({ label: item.description, value: item.value }))
+                        values.textLink = {
+                            value: {
+                                linkNameType: linkNameEnumeration?.[0]?.value,
+                                jumpInfo: {
+                                    pageType: 'PAGE_TYPE_WECHAT_CANVAS'
+                                }
+                            }
+                        }
+                        values.textLinkShow = true
+                        break
+                    case 'action_button':
+                        let actionButton = result[key]
+                        let buttonText = actionButton.children.button_text
+                        let butttonTextEnumeration = (buttonText.enumProperty.enumeration as { value: string }[]).map(item => ({ label: item.value, value: item.value }))
+                        values.actionButton = {
+                            value: {
+                                buttonText: butttonTextEnumeration?.[0]?.value,
+                                jumpInfo: {
+                                    pageType: 'PAGE_TYPE_WECHAT_CANVAS'
+                                }
+                            }
+                        }
+                        values.actionButtonShow = true
+                        break
+                }
+            })
+            if ([1707, 1708].includes(creativeTemplateId)) {
+                delete values?.actionButtonShow
+                values.cardType = ['not']
+            }
+            form.setFieldsValue(values)
+            setTimeout(() => { setIsUpdate(true) }, 50)
+        }
+    }
+
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        const {
+            creativeTemplateStyle,
+            brand,
+            jumpInfo,
+            pageSpec,
+            textLinkShow,
+            textLink,
+            actionButtonShow,
+            cardType,
+            actionButton,
+            showDataShow,
+            showData,
+            ...surplusValues
+        } = values
+
+        let dynamicValues: any = {
+            ...surplusValues,
+        }
+
+        let creativeComponents: any = {}
+
+        let actionButtonJumpInfo = {
+            pageType: undefined
+        }
+        if (pageSpec?.length > 0) {
+            let { pageType } = pageSpec?.[0]
+            actionButtonJumpInfo.pageType = pageType
+            creativeComponents.mainJumpInfo = pageSpec.map((item: { pageType: string, overrideCanvasHeadOption: string, }) => {
+                return {
+                    value: {
+                        pageType: item.pageType,
+                        pageSpec: {
+                            [pageSpecFieldConVert[item.pageType]]: {
+                                pageId: null,
+                                overrideCanvasHeadOption: item.overrideCanvasHeadOption
+                            }
+                        }
+                    }
+                }
+            })
+        }
+
+        // 品牌形象
+        if (jumpInfo) {
+            let pageType = jumpInfo.pageType
+            let value = {
+                jumpInfo
+            }
+            if (['PAGE_TYPE_H5_PROFILE'].includes(pageType)) {
+                value['profileId'] = brand
+            } else if (["PAGE_TYPE_NOT_USED"].includes(pageType)) {
+                let [brandName, brandImageId] = brand.split('_')
+                value['brandName'] = brandName
+                value['brandImageId'] = brandImageId
+            } else {
+                value['brandName'] = null
+                value['brandImageId'] = null
+            }
+
+            let pageSpecData: any = {}
+
+            if (pageType === 'PAGE_TYPE_H5_PROFILE') {
+                pageSpecData[pageSpecFieldConVert[pageType]] = {
+                    pageId: null
+                }
+            } else if (pageType === 'PAGE_TYPE_H5') {
+                pageSpecData[pageSpecFieldConVert[pageType]] = {
+                    pageUrl: null,
+                    mpaH5WildcardUrl: null
+                }
+            } else if (pageType === 'PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL') {
+                pageSpecData[pageSpecFieldConVert[pageType]] = {
+                    appId: null
+                }
+            }
+            value.jumpInfo = {
+                ...value.jumpInfo,
+                pageSpec: pageSpecData
+            }
+            let length = pageSpec.length
+            creativeComponents.brand = Array(length).fill('').map(() => ({ value }))
+        }
+        // 朋友圈文字链
+        if (textLinkShow) {
+            creativeComponents.textLink = [textLink]
+        }
+
+        // 行动按钮
+        if (actionButton && Object.keys(actionButton).length > 0) {
+            let newActionButton = [actionButton]
+            if (!actionButton.value?.jumpInfo) {
+                newActionButton = [{
+                    ...actionButton,
+                    value: {
+                        ...actionButton.value,
+                        jumpInfo: actionButtonJumpInfo
+                    }
+                }]
+            }
+            creativeComponents.actionButton = newActionButton
+        }
+
+        // 数据外显
+        if (showDataShow) {
+            creativeComponents.showData = [showData]
+        }
+        dynamicValues.creativeComponents = creativeComponents
+        setMaterialData(newMaterialData)
+        setTextData(newTextData)
+        onChange?.(dynamicValues)
+    }
+
+    useEffect(() => {
+        if (value && Object.keys(value).length > 0 && oldCreativeTemplateStyle && adcreativeTemplateList?.length > 0) {
+            getTemplate(value.creativeTemplateId)
+            const {
+                creativeComponents: {
+                    brand,
+                    textLink,
+                    actionButton,
+                    showData,
+                    mainJumpInfo,
+                },
+                ...surplusValues
+            } = JSON.parse(JSON.stringify(value))
+            let dynamicValues: any = {
+                ...surplusValues
+            }
+            // 卡片广告
+            let isCardDynamic = dynamicValues?.creativeTemplateId && [1707, 1708].includes(dynamicValues.creativeTemplateId)
+            let cardType: string[] = []
+
+            // 529 闪屏视频 没有品牌 要单独处理
+            if (brand && brand?.length > 0) {
+                let { jumpInfo, brandName, brandImageId, profileId } = brand[0].value
+                if (jumpInfo) {
+                    let { pageType } = jumpInfo
+                    dynamicValues.jumpInfo = { pageType }
+                }
+                if (brandName && brandImageId) {
+                    dynamicValues.brand = brandName + '_' + brandImageId
+                } else if (profileId) {
+                    dynamicValues.brand = profileId
+                }
+            }
+
+            if (mainJumpInfo && mainJumpInfo?.length > 0) {
+                dynamicValues.pageSpec = mainJumpInfo.map((item: any) => {
+                    let { pageSpec } = item.value
+                    let key = Object.keys(pageSpec)[0]
+                    let pageSpecValue = pageSpec[key]
+                    return { ...pageSpecValue, pageType: pageSpecFieldConVertUn[key] }
+                })
+            }
+
+            // 文字链
+            if (textLink && textLink?.length > 0) {
+                dynamicValues = {
+                    ...dynamicValues,
+                    textLinkShow: true,
+                    textLink: textLink[0]
+                }
+            }
+
+            // 行动按钮
+            if (actionButton && actionButton?.length > 0) {
+                dynamicValues = {
+                    ...dynamicValues,
+                    actionButtonShow: true,
+                    actionButton: actionButton[0]
+                }
+                if (isCardDynamic) {
+                    cardType.push('action_button')
+                    delete dynamicValues.actionButtonShow
+                }
+            }
+            // 数据外显
+            if (showData && showData?.length > 0) {
+                dynamicValues = {
+                    ...dynamicValues,
+                    showDataShow: true,
+                    showData: showData[0]
+                }
+            }
+
+            if (cardType.length === 0 && isCardDynamic) {
+                cardType = ['not']
+            }
+            dynamicValues.cardType = cardType
+            dynamicValues.creativeTemplateStyle = oldCreativeTemplateStyle === '视频' ? 'video' : 'image'
+            form.setFieldsValue({ ...dynamicValues })
+        }
+    }, [value, oldCreativeTemplateStyle, adcreativeTemplateList])
+
+    return <Modal
+        title={<strong style={{ fontSize: 20 }}>创意基本信息</strong>}
+        visible={visible}
+        onCancel={onClose}
+        footer={null}
+        width={900}
+        className={`modalResetCss`}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+    >
+        <Form
+            form={form}
+            name="newDynamic"
+            labelAlign='left'
+            layout="vertical"
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '0 10px 10px', borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{
+                deliveryMode: 'DELIVERY_MODE_CUSTOMIZE',
+                configuredStatus: 'AD_STATUS_SUSPEND'
+            }}
+        >
+            <Spin spinning={getCreativeTemplateList.loading || getCreativeDetails.loading}>
+                <DispatchDynamic.Provider value={{ form, adgroups, value, creativeComponents, setCreativeComponents, isUpdate, setIsUpdate }}>
+                    <Space direction="vertical" style={{ width: '100%' }}>
+                        {/* 创意形式 */}
+                        <Card
+                            title={<strong style={{ fontSize: 18 }}>创意形式</strong>}
+                            className="cardResetCss"
+                        >
+                            <Form.Item name="deliveryMode" label={<strong>投放模式</strong>} rules={[{ required: true, message: '请选择投放模式!' }]}>
+                                <Radio.Group onChange={() => setTimeout(() => { setIsUpdate(true) }, 50)}>
+                                    {Object.keys(DELIVERY_MODE_ENUM).map(key => <Radio value={key} key={key}>{DELIVERY_MODE_ENUM[key]}</Radio>)}
+                                </Radio.Group>
+                            </Form.Item>
+                            {deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' && <>
+                                <Form.Item name="creativeTemplateStyle" label={<strong>创意形式类型</strong>} rules={[{ required: true, message: '请选择营销目的!' }]}>
+                                    <New1Radio data={marketingGoalTypeList} onChange={(e) => { typeChange(e === 'video' ? '视频' : '图片', true); }} />
+                                </Form.Item>
+                                <Form.Item name="creativeTemplateId" label={<strong>创意形式</strong>} rules={[{ required: true, message: '请选择营销目的!' }]}>
+                                    <New2Radio
+                                        data={adcreativeTemplateList.filter(item => creativeTemplateStyle === 'video' ? item.creativeTemplateStyle === '视频' : item.creativeTemplateStyle === '图片')}
+                                        onChange={(id) => { getTemplate(id, true); }}
+                                    />
+                                </Form.Item>
+                            </>}
+                        </Card>
+
+                        {Object.keys(creativeComponents).length > 0 && <>
+                            {/* 创意内容 */}
+                            <CreativeTemplateContent automaticSiteEnabled={automaticSiteEnabled}/>
+                            {/* 营销组件 */}
+                            <CreativeConversionAssistant automaticSiteEnabled={automaticSiteEnabled} />
+                            {/* 创意设置 */}
+                            <CreativeTemplateSetup />
+                        </>}
+                    </Space>
+                </DispatchDynamic.Provider>
+            </Spin>
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" className="modalResetCss">
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(NewDynamic)

+ 448 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx

@@ -0,0 +1,448 @@
+import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
+import { Button, Card, Form, Modal, Popconfirm, Space, message } from "antd"
+import React, { useEffect, useState } from "react"
+import styles from './index.less'
+import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews"
+import { useModel } from "umi"
+import SelectCloud from "@/pages/launchSystemNew/components/selectCloud"
+import { getVideoImgUrl } from "@/utils/utils"
+import VideoFrameSelect from "@/pages/launchSystemV3/components/VideoFrameSelect"
+
+interface Props {
+    creativeTemplateId: number
+    materialData: any
+    value?: any,
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (value: any) => void
+}
+
+const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, visible, value, onChange, onClose }) => {
+
+    /*********************************/
+    const { init } = useModel('useLaunchAdq.useBdMediaPup')
+
+    const [form] = Form.useForm();
+    const dynamicGroup = Form.useWatch('dynamicGroup', form)
+
+    const [materialConfig, setMaterialConfig] = useState<{
+        adcreativeTemplateId?: number,
+        type: string,
+        cloudSize: { relation: string, width: number, height: number }[],
+        list: any[],
+        index: number,
+        max: number,
+        sliderImgContent: any
+    }>({
+        type: '',//类型
+        cloudSize: [],//素材搜索条件
+        list: [],//素材
+        index: 0, // 素材组下标
+        max: 1,//素材数量
+        sliderImgContent: undefined
+    })//图片素材配置
+    const [selectVideoVisible, setSelectVideoVisible] = useState(false)
+    const [mLength, setMLength] = useState<number>(1)
+    const [videoUploads, setVideoUploads] = useState<any>()
+    const [imgUploads, setImgUploads] = useState<any>()
+    const [minNumber, setMinNumber] = useState<number>(3)
+    /*********************************/
+
+    useEffect(() => {
+        console.log('materialData--->', materialData)
+        if (materialData && Object.keys(materialData).length > 0) {
+            let fKey = Object.keys(materialData)[0];
+            let children = materialData[fKey]?.children
+            let childrenKey = Object.keys(children)
+            setMLength(childrenKey?.length)
+
+            let videoData = childrenKey?.find(item => item === 'short_video1' || item === 'video_id')
+            if (videoData) {
+                setVideoUploads(children[videoData])
+            }
+            let imageData = childrenKey?.find(item => item === 'cover_id' || item === 'image_id')
+            if (imageData) {
+                setImgUploads(children[imageData])
+            }
+            let imageListData = childrenKey?.find(item => item === 'list')
+            if (imageListData) {
+                let data = children[imageListData]
+                if (fKey === 'image_list') {
+                    setMinNumber(data.arrayProperty.mixNumber)
+                    setImgUploads({ ...data['children']['image_id'], arrayProperty: data.arrayProperty, name: 'image_list' })
+                } else if (fKey === 'element_story') {
+                    setMinNumber(data.arrayProperty.mixNumber)
+                    setImgUploads({ ...data['children']['image'], arrayProperty: data.arrayProperty, name: 'element_story' })
+                }
+            }
+            // setImgUploads(childrenKey?.find(item => item === 'cover_id' || item === 'list' || item === 'image_id'))
+        } else {
+            setVideoUploads({})
+            setImgUploads({})
+        }
+    }, [materialData])
+
+    const setFrame = (url: string, num: number, field: string) => {
+        let newDynamicGroup = dynamicGroup?.map((item: any, index: number) => {
+            if (num === index) {
+                if (item) {
+                    item[field] = { ...item[field], url, id: null }
+                    return { ...item }
+                } else {
+                    return { [field]: { url, id: null, materialType: 0 } }
+                }
+            }
+            return item
+        })
+        form.setFieldsValue({ dynamicGroup: newDynamicGroup })
+    }
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        onChange?.(values)
+    }
+
+    useEffect(() => {
+        console.log('value--->', value)
+        if (value) {
+            form.setFieldsValue({ ...value })
+        }
+    }, [value])
+
+    return <Modal
+        title={<Space>
+            <strong style={{ fontSize: 20 }}>创意素材</strong>
+            {videoUploads && Object.keys(videoUploads)?.length > 0 && <Button type="link" onClick={() => {
+                init({ mediaType: 'VIDEO', num: 100, cloudSize: creativeTemplateId === 1708 ? [[{ relation: '=', width: 1280, height: 720 }]] : [[{ relation: '=', width: videoUploads.restriction.videoRestriction.minWidth, height: videoUploads.restriction.videoRestriction.minHeight }]], maxSize: videoUploads.restriction.videoRestriction.fileSize * 1024 })
+                setMaterialConfig({
+                    ...materialConfig,
+                    type: videoUploads.name,
+                    max: 1,
+                    index: 99999,
+                    adcreativeTemplateId: creativeTemplateId
+                })
+                setTimeout(() => {
+                    setSelectVideoVisible(true)
+                }, 100)
+            }}>批量添加视频素材</Button>}
+            {imgUploads && Object.keys(imgUploads)?.length > 0 && <Button type="link" onClick={() => {
+                init({ mediaType: 'IMG', num: 100, cloudSize: [[{ relation: '=', width: imgUploads.restriction.imageRestriction.width, height: imgUploads.restriction.imageRestriction.height }]], maxSize: imgUploads.restriction.imageRestriction.fileSize * 1024 })
+                setMaterialConfig({
+                    ...materialConfig,
+                    type: imgUploads.name,
+                    max: (imgUploads.name === 'image_list' || imgUploads.name === 'element_story') ? imgUploads.arrayProperty.maxNumber : 1,
+                    index: 99999,
+                    adcreativeTemplateId: creativeTemplateId
+                })
+                setTimeout(() => {
+                    setSelectVideoVisible(true)
+                }, 100)
+            }}>批量添加图片素材</Button>}
+            {(dynamicGroup && dynamicGroup?.length > 1) && <Popconfirm
+                title="是否清空?"
+                onConfirm={() => form.setFieldsValue({ dynamicGroup: [undefined] })}
+                okText="是"
+                cancelText="否"
+            >
+                <Button type="link" danger>一键清空</Button>
+            </Popconfirm>}
+        </Space>}
+        visible={visible}
+        onCancel={onClose}
+        footer={null}
+        width={900}
+        className={`modalResetCss`}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+    >
+        <Form
+            form={form}
+            name="newMaterial"
+            labelAlign='left'
+            layout="vertical"
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '0 10px 10px', borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{
+                dynamicGroup: [undefined]
+            }}
+        >
+            <Form.List name="dynamicGroup">
+                {(fields, { add, remove }) => (<>
+                    {fields.map((field, num) => (<Card
+                        title={<strong style={{ fontSize: 18 }}>创意组{num + 1}</strong>}
+                        className="cardResetCss"
+                        key={field.key}
+                        style={{ marginTop: 10, marginBottom: 10 }}
+                    >
+                        <Space size={30} style={{ width: '100%' }} className={styles.space}>
+                            {Object.keys(materialData)?.map(key => {
+                                let m = materialData[key]
+                                let children = m.children
+
+                                return Object.keys(children).map(k => {
+                                    let item = children[k]
+                                    if (k === 'short_video1' || k === 'video_id') {
+                                        return <Form.Item
+                                            {...field}
+                                            label={<strong>{item.description}</strong>}
+                                            rules={[{ required: true, message: '请选择素材!' }]}
+                                            name={[field.name, item.name]}
+                                            key={k}
+                                        >
+                                            <div className={`${styles.box} ${styles.video}`} style={{ width: 300, height: 160 }} onClick={() => {
+                                                init({ mediaType: 'VIDEO', cloudSize: creativeTemplateId === 1708 ? [[{ relation: '=', width: 1280, height: 720 }]] : [[{ relation: '=', width: item.restriction.videoRestriction.minWidth, height: item.restriction.videoRestriction.minHeight }]], maxSize: item.restriction.videoRestriction.fileSize * 1024 })
+                                                setMaterialConfig({
+                                                    ...materialConfig,
+                                                    type: item.name,
+                                                    max: 1,
+                                                    index: num,
+                                                    adcreativeTemplateId: creativeTemplateId
+                                                })
+                                                setTimeout(() => {
+                                                    setSelectVideoVisible(true)
+                                                }, 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 }} /> : <>
+                                                        <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>
+                                                    </>}
+                                                </div>
+                                            </div>
+                                        </Form.Item>
+                                    }
+                                    if (item.name === 'image_id' || item.name === 'cover_id') {
+                                        return <Form.Item
+                                            {...field}
+                                            label={<strong>{item.description}</strong>}
+                                            rules={[{ required: true, message: '请选择素材!' }]}
+                                            name={[field.name, item.name]}
+                                            key={key}
+                                        >
+                                            <Space align="end">
+                                                <div className={`${styles.box} ${styles.image}`} style={{ width: 300, height: 160 }} onClick={() => {
+                                                    init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
+                                                    setMaterialConfig({
+                                                        ...materialConfig,
+                                                        type: item.name,
+                                                        max: 1,
+                                                        index: num,
+                                                        adcreativeTemplateId: creativeTemplateId
+                                                    })
+                                                    setTimeout(() => {
+                                                        setSelectVideoVisible(true)
+                                                    }, 100)
+                                                }}>
+                                                    <p>
+                                                        {dynamicGroup?.length > 0 && dynamicGroup[num] && Object.keys(dynamicGroup[num])?.includes(item.name) ? <img src={dynamicGroup[num][item.name]['url']} /> : <>
+                                                            <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
+                                                            <span>{`${item.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
+                                                        </>}
+                                                    </p>
+                                                </div>
+                                                {videoUploads && Object.keys(videoUploads)?.length > 0 && <div style={{ width: 32 }}>
+                                                    {dynamicGroup?.length > 0 && dynamicGroup[num] && (Object.keys(dynamicGroup[num])?.includes('video_id') || Object.keys(dynamicGroup[num])?.includes('short_video1')) && <VideoFrameSelect onChange={(e) => setFrame(e, num, item.name)} url={dynamicGroup[num]?.['video_id']?.['url'] || dynamicGroup[num]?.['short_video1']?.['url']} />}
+                                                </div>}
+                                            </Space>
+                                        </Form.Item>
+                                    }
+
+                                    if (item.name === 'list') {
+                                        let name = ''
+                                        let imageData: any = {}
+                                        if (key === 'image_list') {
+                                            imageData = item.children.image_id
+                                            name = 'image_list';
+                                        } else if (key === 'element_story') {
+                                            imageData = item.children.image
+                                            name = 'element_story'
+                                        }
+                                        return <Form.Item
+                                            {...field}
+                                            label={<strong>{imageData.description}</strong>}
+                                            rules={[
+                                                { required: true, type: 'array', len: minNumber || item.arrayProperty.maxNumber, message: '素材数量不对!' },
+                                                { required: true, message: '请选择素材!' },
+                                            ]}
+                                            name={[field.name, name]}
+                                            key={key}
+                                        >
+                                            <div className={`${styles.box} ${item.arrayProperty.maxNumber >= 3 ? styles.image_list : styles.imageMater}`} style={item.arrayProperty.maxNumber >= 3 ? { flexFlow: 'row', width: '100%', gap: 2 } : {}} onClick={() => {
+                                                init({ mediaType: 'IMG', num: item.arrayProperty.maxNumber, cloudSize: [[{ relation: '=', width: imageData.restriction.imageRestriction.width, height: imageData.restriction.imageRestriction.height }]], maxSize: imageData.restriction.imageRestriction.fileSize * 1024 })
+                                                setMaterialConfig({
+                                                    ...materialConfig,
+                                                    type: name,
+                                                    max: item.arrayProperty.maxNumber,
+                                                    index: num,
+                                                    adcreativeTemplateId: creativeTemplateId
+                                                })
+                                                setTimeout(() => {
+                                                    setSelectVideoVisible(true)
+                                                }, 100)
+                                            }}>
+                                                {Array(item.arrayProperty.maxNumber).fill('').map((arr, index1) => {
+                                                    return <p key={index1} style={item.arrayProperty.maxNumber >= 3 ? { width: 130, height: 130 } : { justifyContent: 'center' }}>
+                                                        {dynamicGroup?.length > 0 && dynamicGroup[num] && Object.keys(dynamicGroup[num])?.includes(name) && dynamicGroup[num][name][index1] ? <img src={dynamicGroup[num][name][index1]['url']} /> : <>
+                                                            <span>{`推荐尺寸(${imageData.restriction.imageRestriction.width} x ${imageData.restriction.imageRestriction.height})`}</span>
+                                                            <span>{`${imageData.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${imageData.restriction.imageRestriction.fileSize}KB`}</span>
+                                                        </>}
+                                                    </p>
+                                                })}
+                                            </div>
+                                        </Form.Item>
+                                    }
+
+                                    return null
+                                })
+                            })}
+                            {fields?.length > 1 && <MinusCircleOutlined className={styles.clear} onClick={() => remove(field.name)} style={{ marginBottom: 24, color: 'red' }} />}
+                        </Space>
+                    </Card>))}
+
+                    <Form.Item style={{ marginBottom: 0 }}>
+                        <Button type="dashed" style={{ color: '#1890ff' }} onClick={() => add()} block icon={<PlusOutlined />}>添加创意组</Button>
+                    </Form.Item>
+                </>)}
+            </Form.List>
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" className="modalResetCss">
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+
+
+        {/* 选择视频素材 */}
+        {selectVideoVisible && <SelectCloud
+            visible={selectVideoVisible}
+            onClose={() => setSelectVideoVisible(false)}
+            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) => item) : undefined :
+                    materialConfig.type === 'image_list' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('image_list')) ? dynamicGroup[materialConfig.index]['image_list']?.map((item: any) => item) : undefined :
+                        (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes(materialConfig.type)) ? [{ url: dynamicGroup[materialConfig.index][materialConfig.type] }] : undefined
+            }
+            onChange={(content: any) => {
+                if (content.length > 0) {
+                    if (materialConfig.index === 99999) {
+                        if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
+                            let urls = content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 }))
+                            let max = materialConfig.max
+                            let materialsNew = dynamicGroup.map((item: any) => {
+                                let newItem = item || {}
+                                // 判断是否有字段,是否设置了值
+                                if (Object.keys(newItem).includes(materialConfig.type) && newItem[materialConfig.type]) {
+                                    if (max > newItem[materialConfig.type].length && urls.length > 0) {
+                                        let difference = max - newItem[materialConfig.type].length
+                                        let material: any[] = []
+                                        if (urls.length >= difference) {
+                                            material = urls.splice(0, difference)
+                                        } else {
+                                            material = urls.splice(0, urls.length)
+                                        }
+                                        newItem[materialConfig.type] = [...newItem[materialConfig.type], ...material]
+                                        return newItem
+                                    } else {
+                                        return newItem
+                                    }
+                                } else {
+                                    if (urls.length >= max) {
+                                        let material = urls.splice(0, max)
+                                        return { ...newItem, [materialConfig.type]: material }
+                                    } else if (urls.length > 0) {
+                                        let material = urls.splice(0, urls.length)
+                                        return { ...newItem, [materialConfig.type]: material }
+                                    } else {
+                                        return newItem
+                                    }
+                                }
+                            })
+                            if (urls.length > 0) {
+                                let data = Array(Math.ceil(urls.length / max)).fill(undefined).map(item => {
+                                    if (urls.length >= max) {
+                                        let material = urls.splice(0, max)
+                                        return { [materialConfig.type]: material }
+                                    } else {
+                                        let material = urls.splice(0, urls.length)
+                                        return { [materialConfig.type]: material }
+                                    }
+                                })
+                                materialsNew = [...materialsNew, ...data]
+                            }
+                            console.log('materialsNew-->', materialsNew)
+                            form.setFieldsValue({ dynamicGroup: materialsNew })
+                        } else {
+                            let newMaterials = content?.map((item: any) => {
+                                if (["short_video1", 'video_id'].includes(materialConfig.type) && mLength === 2) {
+                                    return { [materialConfig.type]: { url: item?.url, id: item?.id, materialType: 0 }, cover_id: { url: getVideoImgUrl(item?.url), id: null, materialType: 0 } }
+                                }
+                                return { [materialConfig.type]: { url: item?.url, id: item?.id, materialType: 0 } }
+                            })
+                            if (newMaterials.length > 0) {
+                                if (dynamicGroup?.every((item: any) => !item)) { // 没设置过
+                                    form.setFieldsValue({ dynamicGroup: newMaterials })
+                                } else { // 设置过
+                                    let materialsNew = dynamicGroup.map((item: any) => {
+                                        let newItem = item || {}
+                                        if (Object.keys(newItem).includes(materialConfig.type) && newItem[materialConfig.type]) {
+                                            return item
+                                        } else {
+                                            if (newMaterials.length > 0) {
+                                                let material = newMaterials.splice(0, 1)
+                                                return { ...newItem, ...material[0] }
+                                            } else {
+                                                return item
+                                            }
+                                        }
+                                    })
+                                    if (newMaterials.length > 0) {
+                                        materialsNew = [...materialsNew, ...newMaterials]
+                                    }
+                                    form.setFieldsValue({ dynamicGroup: materialsNew })
+                                }
+                            }
+                        }
+                    } else {
+                        let newDynamicGroup = dynamicGroup?.map((item: any, index: number) => {
+                            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?.url, materialType: 0 }))
+                                        return { ...item }
+                                    } else {
+                                        return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 })) }
+                                    }
+                                } else {
+                                    if (item) {
+                                        item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.url, materialType: 0 }
+                                        return { ...item }
+                                    } else {
+                                        if (["short_video1", 'video_id'].includes(materialConfig.type) && mLength === 2) {
+                                            return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.url, materialType: 0 }, cover_id: { id: null, url: getVideoImgUrl(content[0]?.url), materialType: 0 } }
+                                        }
+                                        return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.url, materialType: 0 } }
+                                    }
+                                }
+                            }
+                            return item
+                        })
+                        form.setFieldsValue({ dynamicGroup: newDynamicGroup })
+                    }
+                }
+                setSelectVideoVisible(false)
+            }}
+        />}
+    </Modal>
+}
+
+export default React.memo(AddMaterial)

+ 155 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.less

@@ -0,0 +1,155 @@
+.adcreative_template {
+    width: 100%;
+    overflow-y: auto;
+    display: flex;
+    height: 173px;
+
+    >label {
+        height: 100%;
+        margin-right: 15px;
+    }
+}
+
+.videoImgs {
+    width: 100%;
+    overflow-y: auto;
+    display: flex;
+
+    img {
+        width: 100%;
+    }
+
+    label {
+        height: 100%;
+        padding: 0;
+        width: 32%;
+        margin-right: 1%;
+    }
+}
+
+.adcreative_template_item {
+    width: 150px;
+    height: 160px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-flow: column;
+}
+
+.video {}
+
+.box {
+    width: 60%;
+    height: 200px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #f5f7fa;
+    flex-direction: column;
+    color: rgba(0, 0, 0, .3);
+    border-radius: 5px;
+
+    /* TWEENER - IE 10 */
+    >p,
+    .p {
+        display: flex;
+        align-items: center;
+        flex-flow: column;
+        font-size: 10px;
+        cursor: pointer;
+        max-height: 150px;
+        margin-bottom: 0;
+        max-width: 100%;
+
+        img {
+            max-height: 99%;
+            max-width: 99%;
+        }
+
+        video {
+            max-height: 99%;
+            max-width: 99%;
+        }
+    }
+
+    >.p>div {
+        height: 100%;
+
+        >div {
+            height: 100%;
+        }
+    }
+}
+
+.image_list {
+    flex-flow: row wrap;
+    background-color: transparent;
+    height: auto;
+    justify-content: flex-start;
+
+    >p {
+        width: 150px;
+        background-color: #f5f7fa;
+        height: 150px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border: 1px solid #e6e8ed;
+        margin: 0;
+    }
+}
+
+.imageMater {
+    width: 300px;
+    height: 160px;
+}
+
+.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;
+}
+
+.space {
+    width: 100%;
+
+    .clear {
+        width: 20px;
+    }
+}
+
+
+.groups {
+    height: 25px;
+
+    .otherGroup {
+        height: 100%;
+        display: flex;
+        justify-content: flex-start;
+        gap: 4px;
+    }
+
+    .group {
+        height: 100%;
+        display: flex;
+        justify-content: flex-start;
+        gap: 4px;
+
+        img {
+            height: 100%;
+        }
+
+        video {
+            height: 100%;
+        }
+    }
+}

+ 89 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.tsx

@@ -0,0 +1,89 @@
+import React, { useContext, useEffect, useState } from "react"
+import style from '../index.less'
+import { DispatchAddelivery } from "..";
+import { Button, Empty, Typography } from "antd";
+import { FolderOpenOutlined, RedoOutlined } from "@ant-design/icons";
+import AddMaterial from "./addMaterial";
+import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews";
+const { Title } = Typography;
+
+const Material: React.FC = () => {
+
+    /***************************************/
+    const { materialData, addelivery, setAddelivery, clearData } = useContext(DispatchAddelivery)!;
+    const { dynamic, dynamicMaterialDTos } = addelivery
+    const { creativeTemplateId } = dynamic
+
+    const [newVisible, setNewVisible] = useState<boolean>(false)
+    const [mType, setMtype] = useState<string>()
+    /***************************************/
+
+    useEffect(() => {
+        if (materialData && Object.keys(materialData).length > 0) {
+            let type = Object.keys(materialData)[0];
+            setMtype(type)
+        }
+    }, [materialData])
+
+    return <div className={`${style.settingsBody_content_row} ${style.row4}`}>
+        <div className={style.title}>
+            <span>创意素材 <span className={style.selected}>已选 {dynamicMaterialDTos?.dynamicGroup?.length || 0}</span></span>
+            {(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length > 0) ? <Button type="link" size="small" style={{ fontSize: 11, padding: 0 }} onClick={() => setAddelivery({ ...addelivery, dynamicMaterialDTos: [], dynamicCreativesTextDTOS: {} })}><RedoOutlined />清空</Button> : null}
+        </div>
+        <div className={style.detail}>
+            <div className={style.detail_body}>
+                {(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length > 0) ?
+                    <div className={style.detail_body_m}>
+                        {dynamicMaterialDTos.dynamicGroup.map((item: any, index: number) => {
+                            return <div key={index}>
+                                <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} />
+                                    {item?.cover_id && <div className={style.cover_image}>
+                                        <img src={item?.cover_id?.url} />
+                                    </div>}
+                                </div> : ['image_list'].includes(mType) ? <div className={style.imageList}>
+                                    {item?.image_list?.map((item: { url: string | undefined; }, index: undefined) => <div className={style.cover_image} key={index}>
+                                        <img src={item?.url} />
+                                    </div>)}
+                                </div> : ['element_story'].includes(mType) ? <div className={style.imageList}>
+                                    {item?.element_story?.map((item: { url: string | undefined; }, index: undefined) => <div className={style.cover_image} key={index}>
+                                        <img src={item?.url} />
+                                    </div>)}
+                                </div> : ['image'].includes(mType) ? <div className={style.cover_image}>
+                                    <img src={item?.image_id?.url} />
+                                </div> : null : null}
+                            </div>
+                        })}
+                    </div> :
+                    <div className={style.empty_block}>
+                        <Empty description="暂无素材" imageStyle={{ height: 50 }} />
+                    </div>}
+                {((dynamic && Object.keys(dynamic).length > 0) && !(materialData && Object.keys(materialData).length > 0)) && <div className={style.ad_config}>
+                    <div className={style.ad_config_item}>无需设置创意素材</div>
+                </div>}
+            </div>
+            <div className={style.detail_footer}>
+                <Button disabled={!(dynamic && Object.keys(dynamic)?.length > 0)} type="link" icon={<FolderOpenOutlined />} style={{ padding: 0, fontSize: 12 }} onClick={() => setNewVisible(true)}>选择素材</Button>
+            </div>
+
+        </div>
+
+        {newVisible && <AddMaterial
+            creativeTemplateId={creativeTemplateId}
+            value={dynamicMaterialDTos}
+            materialData={materialData}
+            visible={newVisible}
+            onClose={() => {
+                setNewVisible(false)
+            }}
+            onChange={(values) => {
+                setAddelivery({ ...addelivery, dynamicMaterialDTos: values, dynamicCreativesTextDTOS: {} })
+                setNewVisible(false)
+                clearData()
+            }}
+        />}
+    </div>
+}
+
+export default React.memo(Material)

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

@@ -0,0 +1,83 @@
+import React, { useContext, useEffect, useState } from "react"
+import style from '../index.less'
+import { Button, Typography } from "antd"
+import { DispatchAddelivery } from "..";
+import { PlusCircleOutlined } from "@ant-design/icons";
+import NewText from "./newText";
+const { Title, Text } = Typography;
+
+
+const MaterialText: React.FC = () => {
+
+    /*************************************/
+    const { textData, addelivery, setAddelivery, clearData } = useContext(DispatchAddelivery)!;
+    const { dynamic, dynamicCreativesTextDTOS, dynamicMaterialDTos } = addelivery
+
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    const [descName, setDescName] = useState<string>('')
+    const [titleName, setTitleName] = useState<string>('')
+    /*************************************/
+
+    useEffect(() => {
+        if (textData && Object.keys(textData).length > 0) {
+            Object.keys(textData).forEach(key => {
+                let item = textData[key]
+                let content = item.children.content
+                if (key === 'description') {
+                    setDescName(content.description)
+                } else if (key === 'title') {
+                    setTitleName(content.description)
+                }
+            })
+        }
+    }, [textData])
+
+    return <div className={`${style.settingsBody_content_row} ${style.row5}`}>
+        <div className={style.title}>
+            <span>创意文案</span>
+        </div>
+        <div className={style.detail}>
+            <div className={style.detail_body}>
+                <Title level={5} style={{ fontSize: 12 }}>{dynamicCreativesTextDTOS?.type === 0 ? '全部相同' : dynamicCreativesTextDTOS?.type === 1 ? '按创意顺序分配' : null}</Title>
+                {dynamicCreativesTextDTOS?.dynamicCreativesTextDetailDTOList?.map((item: { [x: string]: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined; }, index: number) => {
+                    if (item) {
+                        let keys = Object.keys(item)
+                        return <div key={index}>
+                            {keys.includes('description') ? <>
+                                <Title level={5} style={{ fontSize: 12 }}>{descName}</Title>
+                                <div className={style.text}><Text ellipsis={{ tooltip: true }}>{item['description']?.[0]}</Text></div>
+                            </> : keys.includes('title') ? <>
+                                <Title level={5} style={{ fontSize: 12 }}>{titleName}</Title>
+                                <div className={style.text}><Text ellipsis={{ tooltip: true }}>{item['title']?.[0]}</Text></div>
+                            </> : null}
+                        </div>
+                    }
+                    return null
+                })}
+                {((dynamic && Object.keys(dynamic).length > 0) && !(textData && Object.keys(textData).length > 0)) && <div className={style.ad_config}>
+                    <div className={style.ad_config_item}>无需设置创意文案</div>
+                </div>}
+            </div>
+            <div className={style.detail_footer}>
+                <Button disabled={!(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos)?.length > 0)} type="link" icon={<PlusCircleOutlined />} style={{ padding: 0, fontSize: 12 }} onClick={() => setAddVisible(true)}>添加</Button>
+            </div>
+        </div>
+
+        {addVisible && <NewText
+            value={dynamicCreativesTextDTOS}
+            dynamicMaterialDTos={dynamicMaterialDTos}
+            textData={textData}
+            visible={addVisible}
+            onClose={() => {
+                setAddVisible(false)
+            }}
+            onChange={(values) => {
+                setAddelivery({ ...addelivery, dynamicCreativesTextDTOS: values })
+                setAddVisible(false)
+                clearData()
+            }}
+        />}
+    </div>
+}
+
+export default React.memo(MaterialText)

+ 168 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx

@@ -0,0 +1,168 @@
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio"
+import TextAideInput from "@/pages/launchSystemV3/components/TextAideInput"
+import { txtLength } from "@/utils/utils"
+import { Button, Card, Form, Modal, Space, message } from "antd"
+import React, { useEffect, useState } from "react"
+
+
+interface Props {
+    textData: any,
+    dynamicMaterialDTos: any,
+    value?: any,
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (value: any) => void
+}
+
+/**
+ * 创意文案
+ * @param param0 
+ * @returns 
+ */
+const NewText: React.FC<Props> = ({ visible, onClose, onChange, value, textData, dynamicMaterialDTos }) => {
+
+    /*************************************/
+    const [form] = Form.useForm();
+    const type = Form.useWatch('type', form)
+    const textDto = Form.useWatch('textDto', form)
+
+    const [textList, setTextList] = useState<PULLIN.TextDtoProps[]>([])
+    /*************************************/
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        const { type, textDto } = values
+        onChange?.({
+            type,
+            dynamicCreativesTextDetailDTOList: textDto.map((item: any) => {
+                let data: any = {}
+                Object.keys(item).forEach(key => {
+                    data[key] = [item[key]]
+                })
+                return data
+            })
+        })
+    }
+
+    useEffect(() => {
+        console.log('创意文案--->', value)
+        if (value && Object.keys(value).length) {
+            const { type, dynamicCreativesTextDetailDTOList } = value
+            form.setFieldsValue({ type, textDto: dynamicCreativesTextDetailDTOList.map((item: any) => {
+                let data: any = {}
+                Object.keys(item).forEach(key => {
+                    data[key] = item[key]?.[0]
+                })
+                return data
+            }) })
+        }
+    }, [value])
+
+    useEffect(() => {
+        if (textData && Object.keys(textData)) {
+            let data = Object.values(textData).map((item: any) => {
+                let content = item.children.content
+                return { label: content.description, restriction: content.restriction, value: item.name, required: item.required }
+            })
+            setTextList(data)
+        }
+    }, [textData])
+
+    return <Modal
+        title={<strong style={{ fontSize: 20 }}>创意文案</strong>}
+        visible={visible}
+        onCancel={onClose}
+        footer={null}
+        width={700}
+        className={`modalResetCss`}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+    >
+        <Form
+            form={form}
+            name="newText"
+            labelAlign='left'
+            labelCol={{ span: 5 }}
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '0 10px 10px', borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            initialValues={{
+                type: 0,
+                textDto: [{}]
+            }}
+            onFinish={handleOk}
+        >
+            <Card className="cardResetCss" style={{ marginTop: 10 }}>
+                <Form.Item name="type" label={<strong>文案分配规则</strong>} style={{ marginBottom: 0 }} rules={[{ required: true, message: '请选择营销目的!' }]}>
+                    <New1Radio
+                        data={[{ label: '全部相同', value: 0 }, { label: '按创意顺序分配', value: 1 }]}
+                        onChange={(value) => {
+                            if (value === 0) {
+                                form.setFieldsValue({ textDto: [textDto[0] || {}] })
+                            } else if (value === 1) {
+                                let oldtextDto: PULLIN.TextDtoProps[] = JSON.parse(JSON.stringify(textDto))
+                                oldtextDto = oldtextDto.concat(Array(dynamicMaterialDTos.dynamicGroup.length - oldtextDto.length).fill({}))
+                                form.setFieldsValue({ textDto: oldtextDto })
+                            }
+                        }}
+                    />
+                </Form.Item>
+            </Card>
+
+            <Form.List name="textDto">
+                {(fields) => (<>
+                    {fields.map(({ key, name, ...restField }, num) => (
+                        <Card
+                            title={type === 1 ? <strong style={{ fontSize: 14 }}>创意组{num + 1}</strong> : null}
+                            className="cardResetCss"
+                            style={{ marginTop: 10 }}
+                            key={key}
+                        >
+                            <Space style={{ width: '100%' }} direction="vertical" size={10}>
+                                {textList.map(item => {
+                                    return <Form.Item
+                                        {...restField}
+                                        label={<strong>{item.label}</strong>}
+                                        name={[name, item.value]}
+                                        key={key}
+                                        style={{ marginBottom: 0 }}
+                                        rules={[{
+                                            required: item.required, message: '请输入正确的' + item.label, validator: (rule, value) => {
+                                                if (!value) {
+                                                    return Promise.reject()
+                                                } else if (!value.match(RegExp(item.restriction.textRestriction.textPattern))) {
+                                                    return Promise.reject()
+                                                } else if (txtLength(value) > item.restriction.textRestriction.maxLength) {
+                                                    return Promise.reject()
+                                                }
+                                                return Promise.resolve()
+                                            }
+                                        }]}
+                                    >
+                                        <TextAideInput placeholder={'请输入' + item.label} style={{ width: 450 }} maxTextLength={item.restriction.textRestriction.maxLength} />
+                                    </Form.Item>
+                                })}
+                            </Space>
+                        </Card>
+                    ))}
+                </>)}
+            </Form.List>
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" className="modalResetCss">
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(NewText)

+ 58 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/PageList/index.tsx

@@ -0,0 +1,58 @@
+import React, { useContext, useState } from 'react'
+import style from '../index.less'
+import { Button, Empty, Typography } from 'antd'
+import { DispatchAddelivery } from '..';
+import { PlusCircleOutlined } from '@ant-design/icons';
+import PageModal from '@/pages/launchSystemV3/components/PageModal';
+const { Title, Text } = Typography;
+
+/**
+ * 落地页
+ * @returns 
+ */
+const PageList: React.FC = () => {
+
+    /********************************/
+    const { addelivery, accountCreateLogs, setAccountCreateLogs, clearData } = useContext(DispatchAddelivery)!;
+    const { dynamic, adgroups } = addelivery
+
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    /********************************/
+
+    return <div className={`${style.settingsBody_content_row} ${style.row6}`}>
+        <div className={style.title}>
+            <span>落地页</span>
+        </div>
+        <div className={style.detail}>
+            <div className={style.detail_body}>
+                {accountCreateLogs?.map((item, index) => {
+                    return <div key={index}>
+                        <Title level={5} style={{ fontSize: 12 }}>{item.accountId}</Title>
+                        {item?.pageList?.length ?
+                            item?.pageList?.map((page: { pageName: string, id: number }) => <div key={page.id} className={style.text}><Text ellipsis={{ tooltip: true }}>{page['pageName']}</Text></div>) :
+                            <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ margin: 0, fontSize: 12 }} imageStyle={{ height: 18 }} />}
+                    </div>
+                })}
+            </div>
+            <div className={style.detail_footer}>
+                <Button disabled={!(dynamic && Object.keys(dynamic)?.length > 0)} type="link" icon={<PlusCircleOutlined />} style={{ padding: 0, fontSize: 12 }} onClick={() => setAddVisible(true)}>添加</Button>
+            </div>
+        </div>
+
+        {addVisible && <PageModal
+            data={accountCreateLogs}
+            adgroups={adgroups}
+            visible={addVisible}
+            onClose={() => {
+                setAddVisible(false)
+            }}
+            onChange={(e) => {
+                setAccountCreateLogs(e);
+                setAddVisible(false);
+                clearData()
+            }}
+        />}
+    </div>
+}
+
+export default React.memo(PageList)

+ 833 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Target/addTarget.tsx

@@ -0,0 +1,833 @@
+import { Button, Card, Checkbox, Form, Input, Modal, Radio, Select, Space, Tooltip, TreeSelect, Typography, message } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../index.less'
+import { DEVICE_PRICE_ENUM, EDUCATION_ENUM, EXCLUDED_DIMENSION_ENUM, GENDER_ENUM, LOCATION_TYPES_ENUM, MARITAL_STATUS_ENUM, NETWORK_ENUM, OPTIMIZATIONGOAL_ENUM, USER_OS_ENUM, WECHAT_AD_NEHAVIOR_ENUM } from "../../const"
+import { QuestionCircleFilled } from "@ant-design/icons"
+import { getTargetingGagsApi } from "@/services/adqV3/global"
+import { useAjax } from "@/Hook/useAjax"
+import { DefaultOptionType } from "antd/lib/select"
+import InputName from "@/components/InputName"
+import moment from "moment"
+import { txtLength } from "@/utils/utils"
+import { addTargetingApi, checkTargetingApi, updateTargetingApi } from "@/services/adqV3"
+import { useModel } from "umi"
+const { Title, Paragraph } = Typography;
+
+interface Props {
+    isBackVal?: boolean
+    value?: any,
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (targeting?: any) => void
+}
+
+const AddTarget: React.FC<Props> = ({ isBackVal, value, visible, onChange, onClose }) => {
+
+    /******************************/
+    const [form] = Form.useForm();
+    const geoLocationType = Form.useWatch('geoLocationType', form);
+    const maritalStatusType = Form.useWatch('maritalStatusType', form);
+    const deviceBrandModelType = Form.useWatch('deviceBrandModelType', form);
+    const wechatAdBehaviorType = Form.useWatch('wechatAdBehaviorType', form);
+    const userOsType = Form.useWatch('userOsType', form);
+    const ageType = Form.useWatch('ageType', form);
+    const min = Form.useWatch(['age', 'min'], form);
+    const max = Form.useWatch(['age', 'max'], form);
+    const education = Form.useWatch('education', form);
+    const networkType = Form.useWatch('networkType', form);
+    const devicePrice = Form.useWatch('devicePrice', form);
+    const excludedDimension = Form.useWatch(['excludedConvertedAudience', 'excludedDimension'], form);
+    const conversionBehaviorList = Form.useWatch(['excludedConvertedAudience', 'conversionBehaviorList'], form);
+    const actions = Form.useWatch(['wechatAdBehavior', 'actions'], form);
+    const excludedActions = Form.useWatch(['wechatAdBehavior', 'excludedActions'], form);
+
+    const [regionsList, setRegionsList] = useState<any[]>([])
+    const [modelList, setModelList] = useState([])
+    const [osList, setOsList] = useState<DefaultOptionType[]>([])
+    const [isAdd, setIsAdd] = useState<boolean>(false)
+
+    const getTargetingGags = useAjax((params) => getTargetingGagsApi(params))
+    const checkTargeting = useAjax((params) => checkTargetingApi(params))
+    const updateTargeting = useAjax((params) => updateTargetingApi(params))
+    const addTargeting = useAjax((params) => addTargetingApi(params))
+    const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
+    /******************************/
+
+    // 地址 手机
+    useEffect(() => {
+        getTargetingGags.run({ type: 'REGION' }).then(res => {
+            let arr: any = Object.values(res).filter(v => typeof v !== 'string')
+            let parentList = arr.filter((item: { parentName: any }) => !item.parentName)
+            let childrenList = arr.filter((item: { parentName: any }) => item.parentName)
+            parentList = parentList.map((item: { name: any; id: any, parentId: any }) => {
+                let children = childrenList?.filter((c: { parentId: any }) => {
+                    return item.id === c.parentId
+                })
+                let obj = {
+                    title: item.name,
+                    value: item.id,
+                    key: item.id,
+                    parentId: item.parentId,
+                    disabled: item.id === 710000 || item.id === 810000 || item.id === 820000,
+                    children: children.map((item: { name: any; id: any, parentId: any }) => ({
+                        title: item.name,
+                        value: item.id,
+                        key: item.id,
+                        parentId: item.parentId,
+                        disabled: item.parentId === 710000 || item.parentId === 810000 || item.parentId === 820000
+                    }))
+                }
+                return obj
+            })
+            parentList = parentList.map((item: any) => {
+                let itemArr = item?.children?.map((c: any) => {
+                    let arr = childrenList.filter((d: { parentId: any }) => {
+                        return d.parentId === c.value
+                    })
+                    arr = arr.map((i: { name: any; id: any }) => ({
+                        title: i.name,
+                        value: i.id,
+                        key: i.id,
+                    }))
+                    return { ...c, children: arr }
+                })
+                return { ...item, children: itemArr }
+            })
+            let zg = parentList.filter((item: { title: string }) => item.title === '中国')
+            let zg_children = parentList.filter((item: { title: string }) => (item.title !== '中国' && item.title !== '国外'))
+            let gw = parentList.filter((item: { title: string }) => item.title === '国外')
+            let earth = [
+                { ...zg[0], children: zg_children },
+                { ...gw[0], disabled: true }
+            ]
+            setRegionsList(earth)
+        })
+
+        // 获取手机
+        getTargetingGags.run({ type: 'DEVICE_BRAND_MODEL' }).then(res => {
+            let arr: any = Object.values(res).filter(v => typeof v !== 'string')
+            let parentList = arr.filter((item: { parentName: any }) => !item.parentName)
+            let childrenList = arr.filter((item: { parentName: any }) => item.parentName)
+            parentList = parentList.map((item: { name: any; id: any }) => {
+                let children = childrenList?.filter((c: { parentId: any }) => {
+                    return item.id === c.parentId
+                })
+                let obj = {
+                    title: item.name,
+                    value: item.id,
+                    key: item.id,
+                    children: children.map((item: { name: any; id: any }) => ({
+                        title: item.name,
+                        value: item.id,
+                        key: item.id,
+                    }))
+                }
+                return obj
+            })
+            setModelList(parentList)
+        })
+        // 获取账户列表
+        getAllUserAccount.run()
+    }, [])
+
+    // 系统处理
+    useEffect(() => {
+        let iosChildren: DefaultOptionType[] = []
+        let androidChildren: DefaultOptionType[] = []
+        Object.keys(USER_OS_ENUM).forEach(key => {
+            if (key !== 'ANDROID' && key !== 'IOS') {
+                if (key.includes('ANDROID')) {
+                    androidChildren.push({
+                        label: USER_OS_ENUM[key],
+                        value: key
+                    })
+                }
+                if (key.includes('IOS')) {
+                    iosChildren.push({
+                        label: USER_OS_ENUM[key],
+                        value: key
+                    })
+                }
+            }
+        })
+        setOsList([{ label: 'iOS系统', value: 'IOS', children: iosChildren }, { label: 'Android系统', value: 'ANDROID', children: androidChildren }])
+    }, [USER_OS_ENUM])
+
+    const handleOk = async (values: any) => {
+        console.log(values)
+        const {
+            targetingName,
+            description,
+            accountId,
+            geoLocationType,
+            ageType,
+            age,
+            gender,
+            education,
+            networkType,
+            maritalStatusType,
+            excludedConvertedAudience,
+            deviceBrandModelType,
+            deviceBrandModelList,
+            isExcludedDeviceBrandModel,
+            userOsType,
+            os,
+            isExcludedOs,
+            devicePrice,
+            wechatAdBehaviorType,
+            ...surplusValues
+        } = values
+
+        let targetValues: any = {
+            ...surplusValues,
+        }
+        // 性别
+        if (gender !== '0') {
+            targetValues.gender = [gender]
+        }
+        // 学历
+        if (!education?.includes('0')) {
+            targetValues.education = education
+        }
+        // 联网方式
+        if (!networkType?.includes('0')) {
+            targetValues.networkType = networkType
+        }
+        // 年龄
+        if (ageType !== '0' && ageType !== '1') {
+            let [min, max] = ageType.split('_')
+            targetValues.age = [{ min: Number(min), max: Number(max) }]
+        } else if (ageType === '1') {
+            targetValues.age = [age]
+        }
+        // 排除已转化用户
+        if (excludedConvertedAudience?.excludedDimension !== '0') {
+            targetValues.excludedConvertedAudience = excludedConvertedAudience
+        }
+        // 设备品牌型号 deviceBrandModelList, isExcludedDeviceBrandModel,
+        if (deviceBrandModelType === '1') {
+            if (isExcludedDeviceBrandModel) { // 排除
+                targetValues.deviceBrandModel = {
+                    excludedList: deviceBrandModelList
+                }
+            } else {
+                targetValues.deviceBrandModel = {
+                    includedList: deviceBrandModelList
+                }
+            }
+        }
+        // 操作系统 userOsType, os, isExcludedOs,
+        if (userOsType === '1') {
+            if (isExcludedOs) {
+                targetValues.excludedOs = os
+            } else {
+                targetValues.userOs = os
+            }
+        }
+        // 设备价格
+        if (!devicePrice?.includes('0')) {
+            targetValues.devicePrice = devicePrice
+        }
+        let targetingDTO = {
+            targetingName,
+            description,
+            accountId,
+            targeting: targetValues
+        }
+
+        if (isBackVal && !isAdd) {
+            delete targetingDTO.accountId
+            delete targetingDTO.description
+            onChange?.(targetingDTO)
+            return
+        }
+
+        let checkData = await checkTargeting.run(value?.id ? { ...targetingDTO, id: value?.id } : targetingDTO)
+        if (checkData?.[0]?.isSame) {
+            message.error('存在相同模板名称,请修改')
+            return
+        }
+
+        if (value?.id) {
+            updateTargeting.run({ ...targetingDTO, id: value?.id }).then(res => {
+                message.success('修改成功')
+                onChange?.()
+            })
+        } else {
+            addTargeting.run(targetingDTO).then(res => {
+                message.success('新增成功')
+                if (isBackVal && isAdd) {
+                    delete targetingDTO.accountId
+                    delete targetingDTO.description
+                    onChange?.(targetingDTO)
+                } else {
+                    onChange?.()
+                }
+            })
+        }
+    }
+
+    // 回填数据
+    useEffect(() => {
+        if (value && Object.keys(value).length > 0) {
+            const {
+                geoLocation,
+                age,
+                gender,
+                education,
+                networkType,
+                maritalStatus,
+                excludedConvertedAudience,
+                deviceBrandModel,
+                excludedOs,
+                userOs,
+                devicePrice,
+                wechatAdBehavior,
+                ...surplusValues
+            } = JSON.parse(JSON.stringify(value))
+            let targetValues: any = {
+                ...surplusValues,
+                geoLocation,
+                age: age?.[0] || undefined,
+                gender: gender?.[0] || '0',
+                education: education || '0',
+                networkType: networkType || '0',
+                excludedConvertedAudience: excludedConvertedAudience || { excludedDimension: '0' },
+                devicePrice: devicePrice || '0',
+                wechatAdBehavior,
+                maritalStatus
+            }
+            if (geoLocation && Object.keys(geoLocation).length > 0) {
+                targetValues.geoLocationType = '1'
+            } else {
+                targetValues.geoLocationType = '0'
+                targetValues.geoLocation = {
+                    locationTypes: ['LIVE_IN']
+                }
+            }
+            if (age?.length > 0) {
+                let g = age?.[0]?.min + '_' + age?.[0]?.max
+                if (['14_18', '19_24', '25_29', '30_39', '40_49', '50_66'].includes(g)) {
+                    targetValues.ageType = g
+                } else {
+                    targetValues.ageType = '1'
+                }
+            } else {
+                targetValues.ageType = '0'
+            }
+            if (maritalStatus?.length > 0) {
+                targetValues.maritalStatusType = '1'
+            } else {
+                targetValues.maritalStatusType = '0'
+            }
+            if (deviceBrandModel?.includedList) {
+                targetValues.deviceBrandModelType = '1'
+                targetValues.deviceBrandModelList = deviceBrandModel?.includedList
+            } else if (deviceBrandModel?.excludedList) {
+                targetValues.deviceBrandModelType = '1'
+                targetValues.deviceBrandModelList = deviceBrandModel?.excludedList
+                targetValues.isExcludedDeviceBrandModel = true
+            } else {
+                targetValues.deviceBrandModelType = '0'
+            }
+            if (excludedOs?.length > 0) {
+                targetValues.isExcludedOs = true
+                targetValues.os = excludedOs
+                targetValues.userOsType = '1'
+            } else if (userOs?.length > 0) {
+                targetValues.os = userOs
+                targetValues.userOsType = '1'
+            } else {
+                targetValues.userOsType = '0'
+            }
+            let wechatAdBehaviorType = []
+            if (wechatAdBehavior?.actions) {
+                wechatAdBehaviorType.push('actions')
+            }
+            if (wechatAdBehavior?.excludedActions) {
+                wechatAdBehaviorType.push('excludedActions')
+            }
+            targetValues.wechatAdBehaviorType = wechatAdBehaviorType.length > 0 ? wechatAdBehaviorType : ['0']
+
+            form.setFieldsValue({ ...targetValues })
+        }
+    }, [value])
+
+    return <Modal
+        title={isBackVal ? <strong style={{ fontSize: 20 }}>
+            {(value && Object.keys(value).length > 0) ? `修改定向` : `新增定向`}
+        </strong> : <strong style={{ fontSize: 20 }}>{value?.id ? `修改定向模板` : value?.isCopy ? `复制定向模板` : `新增定向模板`}</strong>}
+        visible={visible}
+        onCancel={onClose}
+        footer={null}
+        width={920}
+        className={`modalResetCss`}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+    >
+        <Form
+            form={form}
+            name="newAdTarget"
+            labelAlign='left'
+            labelCol={{ span: 4 }}
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 600, overflow: 'hidden', overflowY: 'auto', padding: '10px 10px 10px', borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{
+                geoLocationType: '0',
+                maritalStatusType: '0',
+                deviceBrandModelType: '0',
+                userOsType: '0',
+                ageType: '0',
+                wechatAdBehaviorType: ['0'],
+                age: {
+                    min: 14,
+                    max: 66
+                },
+                gender: '0',
+                education: ['0'],
+                networkType: ['0'],
+                devicePrice: ['0'],
+                excludedConvertedAudience: {
+                    excludedDimension: '0'
+                },
+                geoLocation: {
+                    locationTypes: ['LIVE_IN']
+                },
+                targetingName: (isBackVal ? '定向' : '定向模板') + '_' + localStorage.getItem('userId') + '_' + moment().format('MM_DD_HH:mm:ss')
+            }}
+        >
+            <Card
+                title={<strong style={{ fontSize: 18 }}>定向选择</strong>}
+                className="cardResetCss newCss"
+                bodyStyle={{ padding: '4px 6px' }}
+                style={{ marginBottom: 8 }}
+            >
+                <div className={style.newSpace}>
+                    <Form.Item name="geoLocationType" label={<strong>地理位置</strong>} style={{ marginBottom: 0 }}>
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            <Radio value="1">按区域</Radio>
+                        </Radio.Group>
+                    </Form.Item>
+                    {geoLocationType === '1' && <div className={style.newSpace_bottom}>
+                        {/* <Title level={5} style={{ fontSize: 14 }}>微信流量(除视频号/搜一搜)暂时仅支持 “常住地”或“旅行到访”。按法务合规要求,“常住地”暂不支持国内港澳台及国外地区,“旅行到访”仅支持部分国外地区。</Title> */}
+                        <Form.Item
+                            name={['geoLocation', 'regions']}
+                            rules={[
+                                { required: true, message: '请选择区域' },
+                                { type: 'array', max: 1000, message: '地理位置最多选择1000' },
+                            ]}
+                        >
+                            <TreeSelect
+                                placeholder="请选择"
+                                showSearch={true}
+                                maxTagCount={50}
+                                treeCheckable={true}
+                                showCheckedStrategy={TreeSelect.SHOW_PARENT}
+                                treeData={regionsList}
+                                loading={getTargetingGags.loading}
+                                style={{ width: '100%' }}
+                                allowClear
+                                filterTreeNode={(inputValue: string, treeNode: any) => {
+                                    if (treeNode.title.includes(inputValue)) {
+                                        return true
+                                    } else {
+                                        return false
+                                    }
+                                }}
+                            />
+                        </Form.Item>
+                        <Form.Item
+                            name={['geoLocation', 'locationTypes']}
+                            rules={[{ required: true, message: '请选择地点类型' }]}
+                        >
+                            <Checkbox.Group
+                                disabled
+                                options={Object.keys(LOCATION_TYPES_ENUM)?.map(key => ({ label: LOCATION_TYPES_ENUM[key], value: key }))}
+                            />
+                        </Form.Item>
+                    </div>}
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item name="ageType" label={<strong>年龄</strong>} style={{ marginBottom: 0 }}>
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            <Radio value="14_18">14~18岁</Radio>
+                            <Radio value="19_24">19~24岁</Radio>
+                            <Radio value="25_29">25~29岁</Radio>
+                            <Radio value="30_39">30~39岁</Radio>
+                            <Radio value="40_49">40~49岁</Radio>
+                            <Radio value="50_66">50岁及以上</Radio>
+                            <Radio value="1">自定义</Radio>
+                        </Radio.Group>
+                    </Form.Item>
+                    {ageType === '1' && <div className={`${style.newSpace_bottom} flexStart`} style={{ '--g': '5px' } as React.CSSProperties}>
+                        <Form.Item name={['age', 'min']}>
+                            <Select style={{ width: 185 }} placeholder="请选择">
+                                {Array(66 - 13).fill('').map((_, i) => i + 14).filter(i => i !== 15 && i !== 16 && i !== 17).map(i => {
+                                    return <Select.Option disabled={i > max} value={i} key={i}>{i === 66 ? '66 岁及以上' : i + ' 岁'}</Select.Option>
+                                })}
+                            </Select>
+                        </Form.Item>
+                        <span>-</span>
+                        <Form.Item name={['age', 'max']}>
+                            <Select style={{ width: 185 }} placeholder="请选择">
+                                {Array(66 - 17).fill('').map((_, i) => {
+                                    return <Select.Option disabled={i + 18 < min} value={i + 18} key={i + 18}>{i + 18 === 66 ? '66 岁及以上' : i + 18 + ' 岁'}</Select.Option>
+                                })}
+                            </Select>
+                        </Form.Item>
+                    </div>}
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item name="gender" label={<strong>性别</strong>} style={{ marginBottom: 0 }}>
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            {Object.keys(GENDER_ENUM).map(key => {
+                                return <Radio value={key} key={key}>{GENDER_ENUM[key]}</Radio>
+                            })}
+                        </Radio.Group>
+                    </Form.Item>
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item
+                        name="education"
+                        label={<Space>
+                            <strong>学历</strong>
+                            <Tooltip title="用户的最高学历">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </Space>}
+                        style={{ marginBottom: 0 }}
+                        getValueFromEvent={(e: string[]) => {
+                            if (e.length > 1 && !education.includes('0') && e.includes('0')) {
+                                return ['0'];
+                            }
+                            return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
+                        }}
+                    >
+                        <Checkbox.Group
+                            options={[
+                                { label: '不限', value: '0' },
+                                ...Object.keys(EDUCATION_ENUM)?.map(key => ({ label: EDUCATION_ENUM[key], value: key }))
+                            ]}
+                        />
+                    </Form.Item>
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item
+                        name="networkType"
+                        label={<strong>联网方式</strong>}
+                        style={{ marginBottom: 0 }}
+                        getValueFromEvent={(e: string[]) => {
+                            if (e.length > 1 && !networkType.includes('0') && e.includes('0')) {
+                                return ['0'];
+                            }
+                            return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
+                        }}
+                    >
+                        <Checkbox.Group
+                            options={[
+                                { label: '不限', value: '0' },
+                                ...Object.keys(NETWORK_ENUM)?.map(key => ({ label: NETWORK_ENUM[key], value: key }))
+                            ]}
+                        />
+                    </Form.Item>
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item
+                        label={<Space>
+                            <strong>自定义人群</strong>
+                            <Tooltip title={<span>
+                                自定义人群是指客户通过腾讯广告知数(原DMP)创建和管理自己定义类人群,包括您自行上传的号码包人群等。仅当出价方式选择CPC、 CPM或oCPM(且优化目标为“点击”)时,你可以“二方人群”进行投放。
+                                <a href="https://e.qq.com/ads/helpcenter/detail/?cid=3161&pid=8995" target="__blank">了解更多</a>
+                            </span>}>
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </Space>}
+                        style={{ marginBottom: 0 }}
+                    >
+                        <Space>
+                            <Checkbox.Group
+                                defaultValue={['0']}
+                                options={[
+                                    { label: '不限', value: '0' }
+                                ]}
+                            />
+                            <span style={{ color: '#FAAD14' }}>自定义人群必须关联指定账户,当前关联账户为不限</span>
+                        </Space>
+                    </Form.Item>
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item name="maritalStatusType" label={<strong>婚恋育儿状态</strong>} style={{ marginBottom: maritalStatusType === '1' ? 4 : 12 }}>
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            <Radio value="1">自定义</Radio>
+                        </Radio.Group>
+                    </Form.Item>
+                    {maritalStatusType === '1' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
+                        <Form.Item name={'maritalStatus'} rules={[{ required: true, message: '请选择婚恋育儿状态' }]}>
+                            <Checkbox.Group options={Object.keys(MARITAL_STATUS_ENUM).map(key => ({ label: MARITAL_STATUS_ENUM[key], value: key }))} />
+                        </Form.Item>
+                    </div>}
+                    <Form.Item
+                        name={['excludedConvertedAudience', 'excludedDimension']}
+                        label={<Space>
+                            <strong>排除已转化用户</strong>
+                            <Tooltip title={<>
+                                <Paragraph>
+                                    设置排除已转化定向,广告不会曝光给所选范围内已转化的用户;
+                                    系统将自动以当前广告选择的优化目标作为此定向的转化行为(不支持“点击”、“次留率”优化目标);
+                                    该定向仅可选择oCPC、oCPM、oCPA出价方式,若勾选自定义转化行为则不限制出价方式 。
+                                </Paragraph>
+                                <a href="https://e.qq.com/ads/helpcenter/detail/?cid=3531&pid=2612" target="__blank">了解更多</a>
+                            </>}>
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </Space>}
+                        style={{ marginBottom: excludedDimension === '0' ? 12 : 4 }}
+                    >
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            {Object.keys(EXCLUDED_DIMENSION_ENUM).map(key => {
+                                return <Radio value={key} key={key}>{EXCLUDED_DIMENSION_ENUM[key]}</Radio>
+                            })}
+                        </Radio.Group>
+                    </Form.Item>
+                    {excludedDimension !== '0' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
+                        <Title level={5} style={{ fontSize: 14 }}>系统自动依照当前广告选择的优化目标作为此定向的转化行为</Title>
+                        <Form.Item label="自定义转化行为" name={['excludedConvertedAudience', 'conversionBehaviorList']} rules={[{ required: true, message: '请选择自定义转化行为' }]}>
+                            <Select
+                                showSearch
+                                filterOption={(input, option) =>
+                                    (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                                }
+                                allowClear
+                                placeholder='请选择自定义转化行为'
+                                mode="multiple"
+                                style={{ width: 480 }}
+                            >
+                                {Object.keys(OPTIMIZATIONGOAL_ENUM).filter(key => key !== 'OPTIMIZATIONGOAL_NONE').map(key => {
+                                    return <Select.Option value={key} key={key} disabled={conversionBehaviorList?.length >= 2 && !conversionBehaviorList.includes(key)}>{OPTIMIZATIONGOAL_ENUM[key]}</Select.Option>
+                                })}
+                            </Select>
+                        </Form.Item>
+                    </div>}
+
+                    <Form.Item name="deviceBrandModelType" label={<strong>设备品牌型号</strong>} style={{ marginBottom: deviceBrandModelType === '0' ? 0 : 4 }}>
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            <Radio value="1">自定义</Radio>
+                        </Radio.Group>
+                    </Form.Item>
+                    {deviceBrandModelType === '1' && <div className={`${style.newSpace_bottom}`} style={{ marginBottom: 6 }}>
+                        <Form.Item
+                            name={'deviceBrandModelList'}
+                            rules={[
+                                { required: true, message: '请选择设备品牌' },
+                                { type: 'array', max: 400, message: '设备品牌最多选择400个设备型号' }
+                            ]}
+                            style={{ marginBottom: 10 }}
+                        >
+                            <TreeSelect
+                                placeholder="请选择"
+                                showSearch={true}
+                                maxTagCount={20}
+                                treeCheckable={true}
+                                showCheckedStrategy={TreeSelect.SHOW_CHILD}
+                                treeData={modelList}
+                                style={{ width: '100%' }}
+                                allowClear
+                                filterTreeNode={(inputValue: string, treeNode: any) => {
+                                    if (treeNode.title.includes(inputValue)) {
+                                        return true
+                                    } else {
+                                        return false
+                                    }
+                                }}
+                            />
+                        </Form.Item>
+                        <Form.Item name='isExcludedDeviceBrandModel' valuePropName="checked">
+                            <Checkbox>排除所选设备的用户</Checkbox>
+                        </Form.Item>
+                    </div>}
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item name="userOsType" label={<strong>操作系统版本</strong>} style={{ marginBottom: 0 }}>
+                        <Radio.Group>
+                            <Radio value="0">不限</Radio>
+                            <Radio value="1">自定义</Radio>
+                        </Radio.Group>
+                    </Form.Item>
+                    {userOsType === '1' && <div className={`${style.newSpace_bottom}`}>
+                        <Form.Item
+                            name={'os'}
+                            style={{ marginBottom: 10 }}
+                            rules={[
+                                { required: true, message: '请选择操作系统版本' },
+                                { type: 'array', max: 100, message: '操作系统版本最多选择100个设备型号' }
+                            ]}
+                        >
+                            <TreeSelect
+                                placeholder="请选择"
+                                showSearch={true}
+                                maxTagCount={10}
+                                treeCheckable={true}
+                                showCheckedStrategy={TreeSelect.SHOW_PARENT}
+                                treeData={osList}
+                                style={{ width: '100%' }}
+                                allowClear
+                                filterTreeNode={(inputValue: string, treeNode: any) => {
+                                    if (treeNode.title.includes(inputValue)) {
+                                        return true
+                                    } else {
+                                        return false
+                                    }
+                                }}
+                            />
+                        </Form.Item>
+                        <Form.Item name='isExcludedOs' valuePropName="checked">
+                            <Checkbox>排除所选操作系统版本的用户</Checkbox>
+                        </Form.Item>
+                    </div>}
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item
+                        name="devicePrice"
+                        label={<strong>设备价格</strong>}
+                        style={{ marginBottom: 0 }}
+                        getValueFromEvent={(e: string[]) => {
+                            if (e.length > 1 && !devicePrice.includes('0') && e.includes('0')) {
+                                return ['0'];
+                            }
+                            return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
+                        }}
+                    >
+                        <Checkbox.Group
+                            options={[
+                                { label: '不限', value: '0' },
+                                ...Object.keys(DEVICE_PRICE_ENUM)?.map(key => ({ label: DEVICE_PRICE_ENUM[key], value: key }))
+                            ]}
+                        />
+                    </Form.Item>
+                </div>
+
+                <div className={style.newSpace}>
+                    <Form.Item
+                        name="wechatAdBehaviorType"
+                        label={<strong>微信再营销</strong>}
+                        style={{ marginBottom: 0 }}
+                        getValueFromEvent={(e: string[]) => {
+                            if (e.length > 1 && !wechatAdBehaviorType.includes('0') && e.includes('0')) {
+                                return ['0'];
+                            }
+                            return e.length > 0 ? (e.length > 1 && e.includes('0') ? e.filter(item => item !== '0') : e) : ['0'];
+                        }}
+                    >
+                        <Checkbox.Group
+                            options={[
+                                { label: '不限', value: '0' },
+                                { label: '再营销', value: 'actions' },
+                                { label: '排除营销', value: 'excludedActions' }
+                            ]}
+                        />
+                    </Form.Item>
+                    {(wechatAdBehaviorType && !wechatAdBehaviorType?.includes('0')) && <div className={`${style.newSpace_bottom}`}>
+                        {wechatAdBehaviorType.includes('actions') && <>
+                            <Title level={5} style={{ fontSize: 14 }}>再营销</Title>
+                            <Form.Item style={{ marginBottom: 10 }} name={['wechatAdBehavior', 'actions']}>
+                                <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_ENUM).filter(item => item !== 'WECHAT_WORK_CONTACTS_ADDED').map(key => ({ label: WECHAT_AD_NEHAVIOR_ENUM[key], value: key, disabled: excludedActions?.some((k: string) => k === key) }))} />
+                            </Form.Item>
+                        </>}
+                        {wechatAdBehaviorType.includes('excludedActions') && <>
+                            <Title level={5} style={{ fontSize: 14 }}>排除营销</Title>
+                            <Form.Item name={['wechatAdBehavior', 'excludedActions']}>
+                                <Checkbox.Group options={Object.keys(WECHAT_AD_NEHAVIOR_ENUM).map(key => ({ label: WECHAT_AD_NEHAVIOR_ENUM[key], value: key, disabled: actions?.some((k: string) => k === key) }))} />
+                            </Form.Item>
+                        </>}
+                    </div>}
+                </div>
+            </Card>
+            <Card
+                title={<strong style={{ fontSize: 18 }}>定向设置</strong>}
+                className="cardResetCss"
+            >
+                <Form.Item
+                    label={<strong>定向模板名称</strong>}
+                    name='targetingName'
+                    // tooltip="下标、日期时分秒、广告账户创建时默认自带"
+                    rules={[
+                        { required: true, message: '请输入定向模板名称!' },
+                        {
+                            required: true, message: '定向模板名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(_, value) {
+                                let reg = /[&‘’“”/\n\t\f]/ig
+                                if (value && reg.test(value)) {
+                                    return Promise.reject()
+                                }
+                                return Promise.resolve()
+                            }
+                        },
+                        {
+                            required: true, message: '请确保定向模板名称长度不超过30个字(1个汉字等于2个字符)', validator(_, value) {
+                                if (value && txtLength(value) > 30) {
+                                    return Promise.reject()
+                                }
+                                return Promise.resolve()
+                            }
+                        }
+                    ]}
+                >
+                    <InputName placeholder='定向模板名称' style={{ width: 480 }} length={30} />
+                </Form.Item>
+                {!isBackVal && <>
+                    <Form.Item
+                        label={<strong>定向模板描述</strong>}
+                        name='description'
+                    >
+                        <Input.TextArea style={{ width: 480 }} placeholder="定向模板描述" />
+                    </Form.Item>
+                    <Form.Item
+                        label={<strong>关联账户</strong>}
+                        name='accountId'
+                    >
+                        <Select
+                            style={{ width: 480 }}
+                            showSearch
+                            filterOption={(input, option) =>
+                                (option!.children as unknown as string)?.toLowerCase()?.includes(input?.toLowerCase())
+                            }
+                            allowClear
+                            placeholder='请选择媒体账户'
+                        >
+                            {getAllUserAccount?.data?.data?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
+                        </Select>
+                    </Form.Item>
+                </>}
+            </Card>
+            <Form.Item className="submit_pull">
+                <Space>
+                    {isBackVal && !(value && Object.keys(value).length > 0) && <Checkbox checked={isAdd} onChange={(e) => setIsAdd(e.target.checked)}>是否保存到定向模板</Checkbox>}
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" className="modalResetCss" loading={checkTargeting.loading || updateTargeting.loading || addTargeting.loading}>
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(AddTarget)

+ 53 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Target/dataItem.tsx

@@ -0,0 +1,53 @@
+import TargetingTooltip from "@/pages/launchSystemV3/components/TargetingTooltip"
+import { Popover, Tooltip } from "antd"
+import React from "react"
+import style from './index.less'
+import { CloseOutlined, FormOutlined } from "@ant-design/icons"
+
+interface Props {
+    targeting: any
+    geoLocationList?: any,
+    modelList?: any
+    onModify?: (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => void
+    onClear?: (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => void
+}
+const DataItem: React.FC<Props> = ({ targeting, onModify, onClear, geoLocationList, modelList }) => {
+
+
+    return <Popover
+        placement="right"
+        overlayInnerStyle={{ maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' }}
+        mouseEnterDelay={0.5}
+        content={<TargetingTooltip
+            data={targeting}
+            geoLocationList={geoLocationList}
+            modelList={modelList}
+        />}
+    >
+        <div className={style.dataItem}>
+            <span className={style.targetingName}>{targeting?.targetingName}</span>
+            <Tooltip title="修改当前">
+                <FormOutlined
+                    className={style.edit}
+                    onClick={(e) => {
+                        e.stopPropagation()
+                        e.preventDefault()
+                        onModify?.(e)
+                    }}
+                />
+            </Tooltip>
+            <Tooltip title="删除">
+                <CloseOutlined
+                    className={style.close}
+                    onClick={(e) => {
+                        e.stopPropagation()
+                        e.preventDefault()
+                        onClear?.(e)
+                    }}
+                />
+            </Tooltip>
+        </div>
+    </Popover>
+}
+
+export default React.memo(DataItem)

+ 34 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Target/index.less

@@ -0,0 +1,34 @@
+.dataItem {
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 32px;
+    padding-right: 8px;
+    padding-left: 8px;
+    margin-bottom: 4px;
+    font-weight: 400;
+    color: #666;
+    background-color: #f1f1f1;
+    border-radius: 4px;
+    align-items: center;
+    font-size: 12px;
+
+    .targetingName {
+        max-width: 80%;
+        flex: 1;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        margin-bottom: 0;
+    }
+
+    .edit,
+    .close {
+        cursor: pointer;
+        color: #1890ff;
+    }
+    .close {
+        color: #ff0000;
+    }
+}

+ 119 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Target/index.tsx

@@ -0,0 +1,119 @@
+import React, { useContext, useState } from "react"
+import style from '../index.less'
+import { Button, Typography } from "antd"
+import { PlusCircleOutlined, PlusOutlined, RedoOutlined } from "@ant-design/icons"
+import SelectTarget from "./selectTarget"
+import { DispatchAddelivery } from ".."
+import { useModel } from "umi"
+import DataItem from "./dataItem"
+import AddTarget from "./addTarget"
+const { Title } = Typography;
+
+/**
+ * 定向
+ * @returns 
+ */
+const Target: React.FC = () => {
+
+    /***************************************/
+    const { geoLocationList, modelList } = useModel('useLaunchV3.useTargeting')
+    const { addelivery, setAddelivery, clearData } = useContext(DispatchAddelivery)!;
+    const { targeting, adgroups } = addelivery
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    const [addTemVisible, setAddTemVisible] = useState<boolean>(false)
+    const [modifyDta, setModifyDta] = useState<any>()
+    const [modifyLength, setModifyLength] = useState<number>()
+    /***************************************/
+
+
+    return <>
+        <div className={`${style.settingsBody_content_row} ${style.row2}`}>
+            <div className={style.title}>
+                <span>定向 <span className={style.selected}>已选 {targeting?.length || 0}</span></span>
+                {targeting?.length > 0 && <Button type="link" size="small" style={{ fontSize: 11, padding: 0 }} onClick={() => setAddelivery({ ...addelivery, targeting: [] })}><RedoOutlined />清空</Button>}
+            </div>
+            <div className={style.detail}>
+                <div className={style.detail_body}>
+                    {targeting?.length > 0 && <>
+                        <Title level={5} style={{ fontSize: 12 }}>全部相同</Title>
+                        {targeting?.map((item: { targetingName: any; targeting: any, id?: number }, index: number) => <div key={index}>
+                            <DataItem
+                                geoLocationList={geoLocationList}
+                                modelList={modelList}
+                                key={index}
+                                targeting={{ targetingName: item.targetingName, ...item.targeting }}
+                                onClear={() => {
+                                    let newTargeting: any[] = JSON.parse(JSON.stringify(targeting))
+                                    setAddelivery({ ...addelivery, targeting: newTargeting.filter((_, i) => index !== i) })
+                                }}
+                                onModify={() => {
+                                    setModifyLength(index)
+                                    setModifyDta({ targetingName: item?.id ? ('修_' + item.targetingName) : item.targetingName, ...item.targeting })
+                                    setAddTemVisible(true)
+                                }}
+                            />
+                        </div>)}
+                    </>}
+                </div>
+                <div className={style.detail_footer}>
+                    <Button
+                        type="link"
+                        icon={<PlusOutlined />}
+                        style={{ padding: 0, fontSize: 12 }}
+                        onClick={() => {
+                            setModifyDta(undefined)
+                            setAddTemVisible(true)
+                            setModifyLength(undefined)
+                        }}
+                        disabled={!(adgroups && Object.keys(adgroups)?.length > 0)}
+                    >
+                        新增
+                    </Button>
+                    <Button disabled={!(adgroups && Object.keys(adgroups)?.length > 0)} type="link" icon={<PlusCircleOutlined />} style={{ padding: 0, fontSize: 12 }} onClick={() => setAddVisible(true)}>添加</Button>
+                </div>
+            </div>
+        </div>
+
+
+        {/* 添加定向 */}
+        {addVisible && <SelectTarget
+            value={targeting?.filter(item => item?.id)}
+            visible={addVisible}
+            onClose={() => {
+                setAddVisible(false)
+            }}
+            onChange={(target) => {
+                // 获取当前新增的,不是模板复制的全是当前和模板没关系
+                let currentTarget = targeting?.filter((item: { id: any }) => !item?.id) || []
+                setAddelivery({ ...addelivery, targeting: [...target, ...currentTarget] })
+                setAddVisible(false)
+            }}
+        />}
+
+        {/* 新增修改定向模板 */}
+        {addTemVisible && <AddTarget
+            value={modifyDta}
+            isBackVal={true}
+            visible={addTemVisible}
+            onClose={() => {
+                setAddTemVisible(false)
+                setModifyDta(undefined)
+            }}
+            onChange={(newTargeting) => {
+                setAddTemVisible(false)
+                setModifyDta(undefined)
+                let newTarget: any[] = JSON.parse(JSON.stringify(targeting || []))
+                if (modifyLength !== undefined) {
+                    newTarget[modifyLength] = newTargeting
+                } else {
+                    newTarget.push(newTargeting)
+                }
+                setModifyLength(undefined)
+                setAddelivery({ ...addelivery, targeting: newTarget })
+                clearData()
+            }}
+        />}
+    </>
+}
+
+export default React.memo(Target)

+ 203 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Target/selectTarget.tsx

@@ -0,0 +1,203 @@
+import { Button, Card, Drawer, Input, Space, Typography, message } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../index.less'
+import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
+import style from '../index.less'
+import { CheckOutlined, CloseOutlined, PlusOutlined, SearchOutlined, UndoOutlined } from "@ant-design/icons";
+import AddTarget from "./addTarget";
+import { useAjax } from "@/Hook/useAjax";
+import { getTargetingListApi } from "@/services/adqV3";
+import Tables from "@/components/Tables";
+import { TableConfig } from "./tableConfig";
+import { getTargetingGagsApi } from "@/services/adqV3/global";
+import { randomString } from "@/utils/utils";
+const { Title, Text } = Typography;
+
+interface Props {
+    value?: any,
+    visible?: boolean
+    onClose?: () => void
+    onChange?: (value: any) => void
+}
+
+/**
+ * 添加定向
+ * @param param0 
+ * @returns 
+ */
+const SelectTarget: React.FC<Props> = ({ value = [], visible, onChange, onClose }) => {
+
+    /*****************************/
+    const [distributionRule, setDistributionRule] = useState<'1' | '2'>('1')
+    const [addTemVisible, setAddTemVisible] = useState<boolean>(false)
+    const [queryParams, setQueryParams] = useState<PULLIN.GetTargeting>({ pageNum: 1, pageSize: 20 })
+    const [queryParamsNew, setQueryParamsNew] = useState<PULLIN.GetTargeting>({ pageNum: 1, pageSize: 20 })
+
+    const [geoLocationList, setGeoLocationList] = useState<any>({}) // 所有地域列表
+    const [modelList, setModelList] = useState<any>({})  // 所有品牌手机
+    const [modifyDta, setModifyDta] = useState<any>()
+    const [selectedTargetKeys, setSelectedTargetKeys] = useState<any[]>(value)
+
+    const getTargetingGags = useAjax((params) => getTargetingGagsApi(params))
+    const getTargetingList = useAjax((params) => getTargetingListApi(params))
+    /*****************************/
+
+    // 设置地域
+    useEffect(() => {
+        async function handle() {
+            await getTargetingGags.run({ type: 'REGION' }).then(res => {
+                if (res && Array.isArray(res)) {
+                    setGeoLocationList(() => (res as any[])?.reduce((prev: any, cur: { id: number }) => {
+                        prev[cur.id] = cur
+                        return prev
+                    }, {}))
+                }
+            })
+            await getTargetingGags.run({ type: 'DEVICE_BRAND_MODEL' }).then(res => {
+                if (res && Array.isArray(res)) {
+                    setModelList(() => (res as any[])?.reduce((prev: any, cur: { id: number }) => {
+                        prev[cur.id] = cur
+                        return prev
+                    }, {}))
+                }
+            })
+        }
+        handle()
+    }, [])
+
+    useEffect(() => {
+        getTargetingList.run(queryParamsNew)
+    }, [queryParamsNew])
+
+    /** 编辑 复制 */
+    const editHandle = (data: any, isCopy?: boolean) => {
+        const { targetingName, targeting, description, id, accountId } = data
+        let newModifyDta = {
+            ...targeting,
+            targetingName,
+            description,
+            accountId: accountId || undefined
+        }
+        if (!isCopy) {
+            newModifyDta.id = id
+        } else {
+            newModifyDta.targetingName = newModifyDta.targetingName + `_副本${randomString(true, 3, 5)}`
+            newModifyDta.isCopy = isCopy
+        }
+        setModifyDta(newModifyDta)
+        setAddTemVisible(true)
+    }
+
+    const handleOk = () => {
+        if (selectedTargetKeys?.length > 0) {
+            onChange?.(selectedTargetKeys)
+        } else {
+            message.error('请选择定向模板')
+        }
+    }
+
+
+    return <Drawer
+        title={<strong style={{ fontSize: 20 }}>定向模板</strong>}
+        visible={visible}
+        onClose={onClose}
+        width={1200}
+        headerStyle={{ padding: '10px 16px' }}
+        maskClosable={false}
+        className={`modalResetCss targetingSelect`}
+    >
+        <Card className="cardResetCss" bodyStyle={{ padding: 0, height: 'calc(100vh - 136px)', overflow: 'hidden', overflowY: 'auto' }}>
+            <div className="flexColumnStart" style={{ height: '100%' }}>
+                <div className="flexSpaceBetween" style={{ padding: 16 }}>
+                    <Space size={20}>
+                        <Text strong>定向模板分配规则</Text>
+                        <New1Radio
+                            // { label: '按账户选择', value: '2' }
+                            data={[{ label: '全部相同', value: '1' }]}
+                            value={distributionRule}
+                            onChange={(e) => setDistributionRule(e)}
+                        />
+                    </Space>
+                </div>
+                <div className={style.template}>
+                    {distributionRule === '2' && <div className={style.template_left}>
+                        <h4 className={style.title}>媒体账户</h4>
+                        <div onClick={() => { }} className={`${style.accItem}`}>
+                            43151494
+                            <CheckOutlined style={{ color: '#1890ff' }} />
+                        </div>
+                    </div>}
+                    <div className={style.template_center}>
+                        <div className="flexSpaceBetween" style={{ marginBottom: 10 }}>
+                            <Space>
+                                <Input placeholder="请输入定向模板名称" value={queryParams?.targetingName} allowClear onChange={(e) => setQueryParams({ ...queryParams, targetingName: e.target.value, pageNum: 1 })} />
+                                <Button type="primary" icon={<SearchOutlined />} loading={getTargetingList.loading} onClick={() => setQueryParamsNew({ ...queryParams })}>搜索</Button>
+                            </Space>
+                            <Space>
+                                <Button type="primary" icon={<PlusOutlined />} onClick={() => setAddTemVisible(true)}>新增定向模板</Button>
+                            </Space>
+                        </div>
+                        <Tables
+                            columns={TableConfig(geoLocationList, modelList, editHandle)}
+                            dataSource={getTargetingList?.data?.records}
+                            size="small"
+                            loading={getTargetingList?.loading}
+                            scroll={{ y: 320 }}
+                            bordered
+                            rowSelection={{
+                                type: 'checkbox',
+                                selectedRowKeys: selectedTargetKeys?.map((item: any) => item?.id?.toString()),
+                                onChange: (_: React.Key[], selectedRows: any) => {
+                                    setSelectedTargetKeys(selectedRows)
+                                }
+                            }}
+                            total={getTargetingList?.data?.total}
+                            defaultPageSize={20}
+                            current={getTargetingList?.data?.current}
+                            pageSize={getTargetingList?.data?.size}
+                            pageChange={(page: number, pageSize?: number) => {
+                                setQueryParamsNew({ ...queryParamsNew, pageNum: page, pageSize: pageSize as number || 20 })
+                                setQueryParams({ ...queryParams, pageNum: page, pageSize: pageSize as number || 20 })
+                            }}
+                        />
+                    </div>
+                    <div className={style.template_right}>
+                        <div className={`flexSpaceBetween ${style.header}`}>
+                            <Title level={5} style={{ marginBottom: 0 }}>已选:{selectedTargetKeys?.length || 0}</Title>
+                            <Button type="link" style={{ padding: 0, fontSize: 12, lineHeight: 'normal' }} onClick={() => setSelectedTargetKeys([])} icon={<UndoOutlined />}>清空</Button>
+                        </div>
+                        <div className={style.selectTarget}>
+                            {selectedTargetKeys?.map((item: any) => <div key={item.id} className={style.selectTarget__row}>
+                                <Text ellipsis={{ tooltip: true }}>{item.targetingName}</Text>
+                                <Button type="link" className={style.clear} onClick={() => setSelectedTargetKeys(selectedTargetKeys.filter(i => item.id !== i.id))} danger icon={<CloseOutlined />}></Button>
+                            </div>)}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </Card>
+
+        <Card className="cardResetCss" style={{ marginTop: 8 }} bodyStyle={{ display: 'flex', justifyContent: 'center', gap: 10 }}>
+            <Button onClick={onClose}>取消</Button>
+            <Button type="primary" onClick={() => handleOk()}>确定</Button>
+        </Card>
+
+        {/* 新增修改定向模板 */}
+        {addTemVisible && <AddTarget
+            value={modifyDta}
+            visible={addTemVisible}
+            onClose={() => {
+                setAddTemVisible(false)
+                setModifyDta(undefined)
+            }}
+            onChange={() => {
+                setAddTemVisible(false)
+                setModifyDta(undefined)
+                getTargetingList.refresh()
+            }}
+        />}
+
+    </Drawer>
+}
+
+export default React.memo(SelectTarget)

+ 89 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Target/tableConfig.tsx

@@ -0,0 +1,89 @@
+import TargetingTooltip from "@/pages/launchSystemV3/components/TargetingTooltip";
+import { QuestionCircleFilled } from "@ant-design/icons";
+import { Button, Popover, Space, Typography } from "antd";
+import { ColumnsType } from "antd/es/table";
+import React from "react";
+
+export function TableConfig(geoLocationList: any, modelList: any, editHandle: (data: any, isCopy?: boolean) => void): ColumnsType<any> {
+    let arr: ColumnsType<any> = [
+        {
+            title: '定向模板名称',
+            dataIndex: 'targetingName',
+            key: 'targetingName',
+            width: 300,
+            fixed: 'left',
+            render(value, records) {
+                return <div style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
+                    <div style={{ width: 'calc(100% - 20px)' }}><Typography.Text ellipsis={{ tooltip: true }}>{value}</Typography.Text></div>
+                    <Popover
+                        placement="right"
+                        overlayInnerStyle={{ maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' }}
+                        mouseEnterDelay={0.5}
+                        content={<TargetingTooltip
+                            data={records?.targeting}
+                            geoLocationList={geoLocationList}
+                            modelList={modelList}
+                        />}
+                    >
+                        <QuestionCircleFilled />
+                    </Popover>
+                </div>
+            }
+        },
+        {
+            title: '关联账户',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render(value) {
+                return value || '不限'
+            },
+        },
+        {
+            title: '定向模板描述',
+            dataIndex: 'description',
+            key: 'description',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            }
+        },
+        {
+            title: '创建人',
+            dataIndex: 'createByName',
+            key: 'createByName',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render(value) {
+                return value || '--'
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 140,
+            ellipsis: true,
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            fixed: 'right',
+            width: 120,
+            render: (a: any, b: any) => {
+                return <Space wrap>
+                    <Button type="link" style={{ padding: 0 }} onClick={() => { editHandle(b) }} >编辑</Button>
+                    <Button type="link" style={{ padding: 0 }} onClick={() => { editHandle(b, true) }} >复制</Button>
+                </Space>
+            }
+        },
+    ]
+    return arr
+}

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

@@ -0,0 +1,364 @@
+.createAd {
+    border-radius: 8px;
+
+    .cardTitle {
+        font-weight: 600;
+        font-size: 18px;
+    }
+}
+
+.settingsBody {
+    user-select: none;
+    margin-top: 16px;
+    width: 100%;
+    overflow: hidden;
+    overflow-x: auto;
+    --w5: 216px;
+
+    .settingsBody_content {
+        min-width: 1200px;
+        border: 1px solid #f0f0f0;
+        border-left: none;
+        display: flex;
+        box-sizing: border-box;
+
+        >div {
+            display: flex;
+            box-sizing: border-box;
+        }
+    }
+
+
+
+    .settingsBody_content_right {
+        width: calc(100% - (var(--w5) * 2));
+
+        .row1,
+        .row2,
+        .row3 {
+            width: 20%;
+        }
+
+        .row4 {
+            width: 40%;
+        }
+    }
+
+    .settingsBody_content_left {
+        width: calc(var(--w5) * 2);
+        display: flex;
+
+        >div {
+            width: 50%;
+        }
+    }
+
+    .settingsBody_content_row {
+        border-left: 1px solid #f0f0f0;
+        box-sizing: border-box;
+
+        .title {
+            height: 50px;
+            padding: 5px 10px;
+            color: #333;
+            background-color: #fff;
+            border-bottom: 1px solid #ebeef5;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+
+            >span {
+                font-size: 14px;
+                font-weight: 600;
+            }
+
+            .selected {
+                margin-left: 8px;
+                font-size: 12px;
+                font-weight: 500;
+                color: #999;
+            }
+        }
+
+        .detail {
+            position: relative;
+        }
+
+        .ad_config {
+            position: absolute;
+            top: 0;
+            left: 0;
+            display: flex;
+            align-items: center;
+            width: 100%;
+            height: 100%;
+            background: #fff;
+            z-index: 10;
+        }
+
+        .ad_config_item {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 100%;
+            height: 100%;
+            text-align: center;
+            cursor: pointer;
+            background-color: #f5f5f5;
+            font-size: 12px;
+
+            &:hover {
+                background-color: #f2f2f2;
+            }
+        }
+
+
+
+        &:hover .detail_footer {
+            box-shadow: 0 0 4px 1px rgba(0, 0, 0, .08);
+        }
+    }
+}
+
+.detail_body {
+    height: 305px;
+    padding: 12px 6px;
+    overflow: hidden;
+    overflow-y: auto;
+    color: #666;
+    font-family: PingFang SC, Microsoft YaHei UI, Microsoft YaHei, Helvetica Neue, Helvetica, Hiragino Sans GB, Arial, sans-serif;
+
+    >p,
+    .tpP {
+        word-break: break-all;
+        margin-bottom: 0;
+        line-height: 20px;
+        font-size: 12px;
+        font-family: PingFang SC, Microsoft YaHei UI, Microsoft YaHei, Helvetica Neue, Helvetica, Hiragino Sans GB, Arial, sans-serif;
+    }
+
+    .empty_block {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .text {
+        width: 100%;
+        height: 32px;
+        padding-right: 8px;
+        padding-left: 8px;
+        margin-bottom: 4px;
+        font-weight: 400;
+        color: #666;
+        background-color: #f1f1f1;
+        border-radius: 4px;
+        font-size: 12px;
+        line-height: 32px;
+    }
+}
+
+.detail_footer {
+    height: 32px;
+    font-size: 14px;
+    line-height: 32px;
+    user-select: none;
+    font-family: PingFang SC, Microsoft YaHei UI, Microsoft YaHei, Helvetica Neue, Helvetica, Hiragino Sans GB, Arial, sans-serif;
+    display: flex;
+    justify-content: center;
+    gap: 10px;
+}
+
+.detail_body_m {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+
+    .video {
+        display: flex;
+        justify-content: space-between;
+
+        >div {
+            max-width: 80px;
+            max-height: 60px;
+
+            img {
+                max-width: 80px;
+                max-height: 60px;
+            }
+        }
+    }
+
+    .imageList {
+        display: flex;
+        gap: 6px;
+    }
+
+    .cover_image {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background-color: #e6e6e6;
+        border-radius: 6px;
+        width: 80px;
+        height: 60px;
+        overflow: hidden;
+
+        >img {
+            max-width: 96%;
+            max-height: 96%;
+        }
+    }
+}
+
+.bts {
+    margin-top: 10px;
+    display: flex;
+    justify-content: flex-end;
+}
+
+.template {
+    width: 100%;
+    display: flex;
+    border-top: 1px solid #f0f0f0;
+    border-bottom: 1px solid #f0f0f0;
+    height: calc(100% - 68px);
+    overflow: hidden;
+}
+
+.template_left {
+    width: 150px;
+    padding-top: 10px;
+    border-right: 1px solid #f0f0f0;
+    box-sizing: border-box;
+    overflow: hidden;
+    overflow-y: auto;
+
+    .title {
+        font-weight: 700;
+        padding-left: 10px;
+        box-sizing: border-box;
+        border-bottom: 1px solid #f0f0f0;
+        margin: 0;
+        padding-bottom: 7px;
+    }
+
+    .accItem {
+        height: 32px;
+        line-height: 32px;
+        cursor: pointer;
+        padding-left: 10px;
+        padding-right: 10px;
+        box-sizing: border-box;
+        margin-bottom: 1px;
+        box-sizing: border-box;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        &:hover {
+            background-color: #e6f7ff;
+            color: #1890ff;
+        }
+    }
+
+    .select {
+        background-color: #e6f7ff;
+        color: #1890ff;
+        border-right: 2px solid #1890ff;
+    }
+
+    .alreadySelect {
+        background-color: #d1f0ff;
+    }
+}
+
+.template_center {
+    padding: 10px;
+    box-sizing: border-box;
+    flex: 1 0;
+    overflow: hidden;
+}
+
+.template_right {
+    width: 200px;
+    border-left: 1px solid #f0f0f0;
+    box-sizing: border-box;
+    overflow: hidden;
+    overflow-y: auto;
+    height: 100%;
+
+    >.header {
+        border-bottom: 1px solid #f0f0f0;
+        padding: 0 10px;
+        margin-bottom: 1px;
+    }
+
+    .selectTarget {
+        height: calc(100% - 34px);
+        overflow: hidden;
+        overflow-y: auto;
+        padding: 4px;
+        box-sizing: border-box;
+        font-size: 12px;
+        font-family: PingFang SC, Microsoft YaHei UI, Microsoft YaHei, Helvetica Neue, Helvetica, Hiragino Sans GB, Arial, sans-serif;
+    }
+
+    .selectTarget__row {
+        padding: 4px 6px;
+        border-radius: 4px;
+        background-color: #eeeeee;
+        margin-bottom: 4px;
+        position: relative;
+
+        &:hover {
+            background-color: #e5e5e5;
+
+            &>.clear {
+                display: inline-block;
+            }
+        }
+
+        .clear {
+            position: absolute;
+            padding: 0;
+            top: 50%;
+            right: 4px;
+            transform: translateY(-50%);
+            background-color: #fff;
+            font-size: 12px;
+            height: auto;
+            width: 20px;
+            display: none;
+        }
+    }
+}
+
+
+
+.newSpace {
+    padding: 10px 12px;
+
+    .newSpace_top {
+        display: flex;
+        align-items: center;
+    }
+
+    .newSpace_bottom {
+        background-color: #fafafb;
+        padding: 18px 16px 10px;
+        margin-left: 124px;
+    }
+
+    &:not(:last-child) {
+        border-bottom: 1px solid #f0f0f0;
+    }
+
+    .newSpace_title {
+        width: 140px;
+        font-weight: bold;
+        padding-right: 15px;
+    }
+}

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

@@ -0,0 +1,589 @@
+import { Button, Card, Checkbox, Divider, Empty, Modal, Popconfirm, Select, Space, Spin, Table, Tabs, Tag, Tooltip, message } 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"
+import DataSourceModal from "../../components/DataSourceModal"
+import moment from "moment"
+import Dynamic from "./Dynamic"
+import Material from "./Material"
+import MaterialText from "./MaterialText"
+import PageList from "./PageList"
+import { cartesianProduct } from "@/utils/utils"
+import columns from "./tableConfig"
+import SubmitModal from "./submitModal"
+import { createAdgroupTaskApi } from "@/services/adqV3"
+import WechatAccount from "../../components/WechatAccount"
+
+export const DispatchAddelivery = React.createContext<PULLIN.DispatchAddelivery | null>(null);
+
+/**
+ * 创建广告
+ * @returns 
+ */
+const Create: React.FC = () => {
+
+    /*******************************************/
+    const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
+    const { initTargeting } = useModel('useLaunchV3.useTargeting')
+    const [addelivery, setAddelivery] = useState<PULLIN.AddeliveryProps>({ adgroups: {}, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {} })
+    const { marketingAssetOuterSpec, marketingCarrierType } = addelivery.adgroups
+    const [accSearch, setAccSearch] = useState<string>()
+    const [accountCreateLogs, setAccountCreateLogs] = useState<PULLIN.AccountCreateLogsProps[]>([])  // 账户
+    const [goodsVisible, setGoodsVisible] = useState<boolean>(false) // 选择小说弹窗控制
+    const [wechatVisible, setWechatVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
+    const [sourceVisible, setSourceVisible] = useState<boolean>(false) // 选择数据源弹窗控制
+    const [materialData, setMaterialData] = useState<any>({}) // 素材数据
+    const [textData, setTextData] = useState<any>({})
+    const [tableData, setTableData] = useState<any>({})
+    const [activeKey, setActiveKey] = useState<string>()
+    const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
+    const [adCount, setAdCount] = useState<number>(0)
+    const [dynamicCount, setDynamicCount] = useState<number>(0)
+
+    const getGroupList = useAjax(() => getGroupListApi())
+    const createAdgroupTask = useAjax((params) => createAdgroupTaskApi(params))
+    /*******************************************/
+
+    useEffect(() => {
+        console.log('addelivery---->', addelivery)
+        // 获取账户组
+        getGroupList.run()
+        // 获取账户列表
+        getAllUserAccount.run()
+        initTargeting()
+    }, [])
+
+    /** 获取分组里账号 */
+    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?.map((item) => ({ accountId: item?.accountId })))
+                    clearData()
+                    setAddelivery({ adgroups: {}, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {} })
+                } else {
+                    message.error('操作异常')
+                }
+            })
+        } else {
+            setAccountCreateLogs([])
+        }
+    }
+
+    /** 存为预设 */
+    const severBd = () => {
+        // queryForm accountCreateLogs
+        localStorage.setItem('ADQADV3', JSON.stringify({
+            addelivery,
+            accountCreateLogs,
+            materialData,
+            textData
+        }))
+        message.success('存储成功')
+    }
+
+    /** 清除 */
+    const delBdPlan = () => {
+        localStorage.removeItem('ADQADV3')
+        setAccountCreateLogs([])
+        setMaterialData({})
+        setTextData({})
+        setAddelivery({
+            adgroups: {},
+            targeting: [],
+            dynamic: {},
+            dynamicMaterialDTos: {},
+            dynamicCreativesTextDTOS: {}
+        })
+        setTableData({})
+    }
+
+    /**数据回填 */
+    useEffect(() => {
+        let adqAdData = localStorage.getItem('ADQADV3')
+        if (adqAdData) {
+            const { addelivery, accountCreateLogs, materialData, textData } = JSON.parse(adqAdData)
+            if (addelivery?.adgroups) {
+                if (addelivery?.adgroups?.beginDate && moment(addelivery?.adgroups?.beginDate) < moment()) {
+                    addelivery.adgroups.beginDate = moment().format('YYYY-MM-DD')
+                    message.warning('请注意,检测投放开始日期小于今天,已自动改成今天,如需修改,请重新设置')
+                }
+                if (addelivery?.adgroups?.endDate && moment(addelivery?.adgroups?.endDate) < moment()) {
+                    addelivery.adgroups.endDate = moment().format('YYYY-MM-DD')
+                    message.warning('请注意,检测投放结束日期小于今天,已自动改成今天,如需修改,请重新设置')
+                }
+            }
+            setAddelivery({ ...addelivery })
+            setAccountCreateLogs(accountCreateLogs)
+            setMaterialData(materialData)
+            setTextData(textData)
+        }
+    }, [])
+
+
+    const preview = () => {
+        console.log('addelivery------>', addelivery)
+        console.log('accountCreateLogs------>', accountCreateLogs)
+        if (accountCreateLogs?.length === 0) {
+            message.error('请先选择媒体账户')
+            return
+        }
+        const { adgroups, targeting, dynamic, dynamicMaterialDTos, dynamicCreativesTextDTOS } = addelivery
+        if (!(adgroups && Object.keys(adgroups).length)) {
+            message.error('请先配置广告信息')
+            return
+        }
+        if (['MARKETING_TARGET_TYPE_FICTION'].includes(marketingAssetOuterSpec?.marketingTargetType) && !accountCreateLogs?.some(item => item?.productList?.length)) {
+            message.error('请先选择小说')
+            return
+        }
+        if ((['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(marketingAssetOuterSpec?.marketingTargetType) || marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') && !accountCreateLogs?.some(item => item?.wechatChannelList?.length)) {
+            message.error('请先选择公众号')
+            return
+        }
+        if (!(targeting?.length)) {
+            message.error('请先添加定向')
+            return
+        }
+        if (!(dynamic && Object.keys(dynamic).length)) {
+            message.error('请先配置创意')
+            return
+        }
+        if ((materialData && Object.keys(materialData).length) && !(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length)) {
+            message.error('请先配置创意素材')
+            return
+        }
+        if ((textData && Object.keys(textData).length) && !(dynamicCreativesTextDTOS && Object.keys(dynamicCreativesTextDTOS).length)) {
+            message.error('请先配置创意文案')
+            return
+        }
+        if (!accountCreateLogs?.some(item => item?.pageList?.length)) {
+            message.error('请先选择落地页')
+            return
+        }
+
+        let newTableData: any = {}
+        let newAdCount = 0, newdynamicCount = 0
+        accountCreateLogs.forEach(item => {
+            let productList: any[] = []
+            if (['MARKETING_TARGET_TYPE_FICTION'].includes(marketingAssetOuterSpec?.marketingTargetType)) { // 小说
+                productList = item?.productList || []
+            } else if (['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(marketingAssetOuterSpec?.marketingTargetType)) { // 公众号
+                productList = item?.wechatChannelList || []
+            }
+            let data = cartesianProduct(productList, targeting).map(newD => {
+                let [productDto, targetDto, index] = newD
+                let suffix = '_' + item.accountId + '_' + index
+                let dat: any = {
+                    id: item.accountId + '_' + index,
+                    accountId: item.accountId,                    // 账户
+                    userActionSetsList: item.userActionSetsList,  // 数据源
+                    pageListDto: item.pageList,                   // 落地页
+                    productDto,                                   // 商品
+                    targetDto: {                                  // 定向
+                        ...targetDto,
+                        targetingName: targetDto.targetingName + suffix
+                    },
+                    adgroupsDto: {                                // 广告信息
+                        ...adgroups,
+                        adgroupName: adgroups.adgroupName + suffix
+                    },
+                    dynamicDto: dynamic,                          // 创意信息
+                    rowSpan: dynamicMaterialDTos?.dynamicGroup?.length || 1
+                }
+                if (marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') { // 营销载体
+                    dat.marketingCarrierDto = item?.wechatChannelList
+                }
+                return dat
+            })
+            newAdCount += data.length
+            let newData = cartesianProduct(data, dynamicMaterialDTos.dynamicGroup || [{}]).map((item, index) => {
+                let [d1, group] = item
+                return {
+                    ...d1,
+                    id: d1.id + '_' + index,
+                    dynamicGroup: group,
+                    textDto: dynamicCreativesTextDTOS?.dynamicCreativesTextDetailDTOList?.[index % dynamicMaterialDTos.dynamicGroup?.length],                     // 创意文案
+                }
+            })
+            newdynamicCount = newdynamicCount + newData.length
+
+            newTableData[item.accountId] = newData
+        })
+        setAdCount(newAdCount)
+        setDynamicCount(newdynamicCount)
+        setActiveKey(accountCreateLogs?.[0].accountId?.toString())
+        setTableData(newTableData)
+    }
+
+    const onSubmit = (values: any) => {
+        const { adgroups, targeting, dynamic, dynamicMaterialDTos, dynamicCreativesTextDTOS } = addelivery
+
+        let dynamicMaterialDTOS = []
+        if ((materialData && Object.keys(materialData).length && dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length)) {
+            let mType = Object.keys(materialData)[0];
+            dynamicMaterialDTOS = dynamicMaterialDTos.dynamicGroup?.map((item: any) => {
+                if (mType === 'image') {
+                    return [{
+                        type: mType,
+                        valueJson: JSON.stringify({
+                            value: {
+                                imageUrl: item?.image_id?.url,
+                                imageId: item?.image_id?.id,
+                                materialType: item?.image_id?.materialType
+                            }
+                        })
+                    }]
+                } else if (mType === 'image_list' || mType === 'element_story') {
+                    let key = 'image_list'
+                    if (mType === 'element_story') {
+                        key = 'element_story'
+                    }
+                    let list = item?.[key]?.map((l: any) => {
+                        return {
+                            imageUrl: l?.url,
+                            imageId: l?.id,
+                            materialType: l?.materialType
+                        }
+                    })
+                    return [{
+                        type: mType,
+                        valueJson: JSON.stringify({
+                            value: {
+                                list
+                            }
+                        })
+                    }]
+                } else if (['short_video', 'video'].includes(mType)) {
+                    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
+                    }
+                    if (item?.cover_id?.url) {
+                        value.imageUrl = item?.cover_id?.url
+                        value.imageId = item?.cover_id?.id
+                        value.materialCoverType = item?.cover_id?.materialType
+                    }
+                    return [{
+                        type: mType,
+                        valueJson: JSON.stringify({
+                            value
+                        })
+                    }]
+                }
+                return [{
+                    type: mType,
+                    valueJson: ''
+                }]
+            })
+        }
+        let accountIdParamDTOMap: any = {}
+        accountCreateLogs.forEach(item => {
+            let { pageList, productList, userActionSetsList, accountId, wechatChannelList } = item
+
+            let userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
+
+            let map: any = {
+                userActionSetsList: userActionSetsListDto,
+                pageList: pageList?.map((item: { pageId: any }) => item.pageId)
+            }
+            if (productList) {
+                map.productDTOS = productList?.map(item => {
+                    return { productId: item.marketingAssetId }
+                })
+            }
+            if (wechatChannelList) {
+                map.wechatChannelId = wechatChannelList?.[0]?.wechatOfficialAccountId
+            }
+
+            accountIdParamDTOMap[accountId] = map
+        })
+        let params = {
+            ...values,
+            adgroupDTO: adgroups,
+            targetings: targeting.map(item => ({ targetingName: item.targetingName, ...item?.targeting || {} })),
+            dynamicCreativesDTO: dynamic,
+            dynamicCreativesTextDTOS,
+            dynamicMaterialDTOS,
+            accountIdParamDTOMap
+        }
+        // setSubVisible(false)
+        createAdgroupTask.run(params).then(res => {
+            if (res) {
+                Modal.success({
+                    content: '任务提交成功',
+                    bodyStyle: { fontWeight: 700 },
+                    okText: '跳转任务列表',
+                    closable: true,
+                    onOk: () => {
+                        sessionStorage.setItem('CAMPV3', values?.taskName)
+                        window.location.href = '/#/launchSystemV3/tencentAdPutIn/taskList'
+                    },
+                    onCancel: () => {
+                        setSubVisible(false)
+                    }
+                })
+            }
+        })
+    }
+
+    const clearData = () => {
+        setTableData({})
+    }
+
+    return <Space direction="vertical" style={{ width: '100%' }}>
+        <Spin spinning={false}>
+            <Card
+                size="small"
+                title={
+                    <div>
+                        <Space>
+                            <div className={style.cardTitle}>配置区</div>
+                        </Space>
+                    </div>
+                }
+                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, option) => { 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))
+                                                    // setQueryForm({ ...queryForm, adqPageList: [], pageList: [], taskMediaMaps: queryForm?.taskMediaMaps?.map((item: { sysPageId: number }) => ({ ...item, sysPageId: '', accountPageIdMap: {}, cropUserGroupMap: [] })) })
+                                                    // clearData()
+                                                }}
+                                            >{item.accountId}</Tag>)}</span>
+                                    }
+                                >
+                                    <span>+{accountCreateLogs?.length > 1 ? accountCreateLogs.length - 1 : 0}</span>
+                                </Tooltip>
+                            }
+                            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))
+                                                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)))
+                                                }
+                                            }
+                                            // setQueryForm({ ...queryForm, adqPageList: [], pageList: [], taskMediaMaps: queryForm?.taskMediaMaps?.map((item: { sysPageId: number }) => ({ ...item, sysPageId: '', accountPageIdMap: {}, cropUserGroupMap: [] })) })
+                                            setAccountCreateLogs(data?.map((item: any) => ({ accountId: item?.accountId })))
+                                            // clearData()
+                                        }}>全选</Checkbox>
+                                    </Space>
+                                </>
+                            )}
+                        >
+                            {getAllUserAccount?.data?.data?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
+                        </Select>
+                    </Selector>
+
+                    {accountCreateLogs?.length > 0 && <>
+                        {marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.productList?.length)} onClick={() => { setGoodsVisible(true) }}>{accountCreateLogs?.some(item => item?.productList?.length) ? <>重新选择小说 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择小说'}</Button>}
+                        {(marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT' || marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.wechatChannelList?.length)} onClick={() => { setWechatVisible(true) }}>{accountCreateLogs?.some(item => item?.wechatChannelList?.length) ? <>重新选择公众号 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择公众号'}</Button>}
+                        <Button onClick={() => { setSourceVisible(true) }}>精准匹配归因(选填){accountCreateLogs?.some(item => item?.userActionSetsList?.length) && <CheckOutlined style={{ color: '#1890ff' }} />}</Button>
+                    </>}
+                </Space>
+
+                <div className={style.settingsBody}>
+                    <div className={style.settingsBody_content}>
+                        <DispatchAddelivery.Provider
+                            value={{
+                                addelivery,
+                                setAddelivery,
+                                accountCreateLogs,
+                                setAccountCreateLogs,
+                                materialData,
+                                setMaterialData,
+                                textData,
+                                setTextData,
+                                clearData
+                            }}>
+                            <div className={style.settingsBody_content_right}>
+                                {/* 广告信息 */}
+                                <Ad />
+                                {/* 定向 */}
+                                <Target />
+                                {/* 创意 */}
+                                <Dynamic />
+                                {/* 创意素材 */}
+                                <Material />
+                            </div>
+                            <div className={style.settingsBody_content_left}>
+                                {/* 创意文案 */}
+                                <MaterialText />
+                                {/* 落地页 */}
+                                <PageList />
+                            </div>
+                        </DispatchAddelivery.Provider>
+                    </div>
+                </div>
+                <Space className={style.bts} wrap>
+                    <Button type='primary' onClick={severBd}>存为预设</Button>
+                    <Popconfirm
+                        title="确定清空?"
+                        onConfirm={delBdPlan}
+                    >
+                        <Button>清空配置/预设</Button>
+                    </Popconfirm>
+                    <Button type='primary' onClick={preview}><SearchOutlined />预览广告</Button>
+                </Space>
+
+                {/* 选择小说 */}
+                {goodsVisible && <GoodsModal
+                    visible={goodsVisible}
+                    data={accountCreateLogs}
+                    onClose={() => setGoodsVisible(false)}
+                    onChange={(e) => {
+                        setAccountCreateLogs(e);
+                        setGoodsVisible(false);
+                        clearData()
+                    }}
+                />}
+                {/* 选择数据源 */}
+                {sourceVisible && <DataSourceModal
+                    visible={sourceVisible}
+                    data={accountCreateLogs}
+                    onClose={() => setSourceVisible(false)}
+                    onChange={(e) => {
+                        setAccountCreateLogs(e);
+                        setSourceVisible(false);
+                        clearData()
+                    }}
+                />}
+                {/* 选择公众号 */}
+                {wechatVisible && <WechatAccount
+                    visible={wechatVisible}
+                    data={accountCreateLogs}
+                    onClose={() => setWechatVisible(false)}
+                    onChange={(e) => {
+                        setAccountCreateLogs(e);
+                        setWechatVisible(false);
+                        clearData()
+                    }}
+                />}
+            </Card>
+        </Spin>
+
+        <Card
+            className={style.createAd}
+        >
+            {activeKey && tableData && Object.keys(tableData)?.length > 0 ? <div className={style.cardBody}>
+                <Tabs
+                    onChange={(e) => { setActiveKey(e) }}
+                    type="card"
+                    activeKey={activeKey}
+                    tabBarExtraContent={<Space>
+                        <span>广告总数:{adCount}</span>
+                        <span>创意总数:{dynamicCount}</span>
+                        <Button type='primary' onClick={() => {
+                            setSubVisible(true)
+                        }}>提交创建</Button>
+                    </Space>}
+                >
+                    {accountCreateLogs.map(item => <Tabs.TabPane tab={item.accountId} key={item.accountId} />)}
+                </Tabs>
+                <div className={style.content} style={{ marginTop: 20 }}>
+                    <Table
+                        columns={columns()}
+                        dataSource={tableData[activeKey]}
+                        size="small"
+                        bordered
+                        scroll={{ x: 1200 }}
+                        rowKey={'id'}
+                    // rowSelection={{
+                    //     selectedRowKeys: tableSelect?.map((item: any) => item?.myId.toString()),
+                    //     onChange: (selectedRowKeys: React.Key[], selectedRows: any) => {
+                    //         setTableSelect(selectedRows)
+                    //     }
+                    // }}
+                    />
+                </div>
+            </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                <Empty description="请先完成模块配置后,再预览广告计划" />
+            </div>}
+        </Card>
+
+        {/* 提交任务 */}
+        {subVisible && <SubmitModal
+            ajax={createAdgroupTask}
+            visible={subVisible}
+            onChange={(e) => {
+                onSubmit(e)
+            }}
+            onClose={() => {
+                setSubVisible(false)
+            }}
+        />}
+    </Space>
+}
+
+export default Create

+ 53 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/submitModal.tsx

@@ -0,0 +1,53 @@
+import { Form, Input, Modal } from "antd"
+import React, { useState } from "react"
+import moment from "moment"
+import { randomString } from "@/utils/utils"
+
+/**
+ * 设置名称
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: any) => void
+    ajax?: any
+}
+const SubmitModal: React.FC<Props> = (props) => {
+
+    /********************/
+    const { visible, onClose, onChange, ajax } = props
+    const [form] = Form.useForm()
+    const [initialValues] = useState<{ taskName: string }>({ taskName: '任务' + moment().format('MM_DD_HH_mm_ss') + '_' + randomString(true, 3, 5) })
+
+    const handleOk = async () => {
+        form.submit()
+        let data = await form.validateFields()
+        onChange && onChange(data)
+    }
+
+    return <Modal
+        title="提交任务"
+        className="modalResetCss"
+        visible={visible}
+        confirmLoading={ajax?.loading}
+        onOk={handleOk}
+        onCancel={() => { onClose && onClose() }}
+    >
+        <Form
+            name="basicSubmit"
+            form={form}
+            labelCol={{ span: 4 }}
+            wrapperCol={{ span: 20 }}
+            autoComplete="off"
+            initialValues={{ ...initialValues }}
+
+        >
+            <Form.Item label={<strong>任务名称</strong>} name="taskName">
+                <Input placeholder="请输入任务名称" />
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(SubmitModal)

+ 167 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/tableConfig.tsx

@@ -0,0 +1,167 @@
+import { Space, TableProps, Typography } from "antd"
+import React from "react"
+import { OPTIMIZATIONGOAL_ENUM } from "../const";
+const { Text, Title } = Typography;
+import style from './index.less'
+
+const columns = (): TableProps<any>['columns'] => {
+
+    return [
+        {
+            title: '广告',
+            dataIndex: 'adgroup',
+            key: 'adgroup',
+            align: 'center',
+            children: [
+                {
+                    title: '广告名称',
+                    dataIndex: 'adgroupName',
+                    key: 'adgroupName',
+                    width: 250,
+                    render: (_, b) => {
+                        return <Text style={{ fontSize: 12 }}>{b?.adgroupsDto?.adgroupName}</Text>
+                    },
+                    onCell: (record, index = 0) => ({
+                        rowSpan: !(index % record.rowSpan) ? record.rowSpan : 0
+                    }),
+                },
+                {
+                    title: '营销内容',
+                    dataIndex: 'productName',
+                    key: 'productName',
+                    width: 200,
+                    render: (_, b) => {
+                        if (['MARKETING_TARGET_TYPE_FICTION'].includes(b.adgroupsDto?.marketingAssetOuterSpec?.marketingTargetType)) {
+                            return <Space size={0} direction="vertical">
+                                <Text style={{ fontSize: 12 }}>推广产品:{b?.productDto?.marketingAssetName}(产品ID:{b?.productDto?.marketingAssetId})</Text>
+                                {(b.adgroupsDto?.marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' && b?.marketingCarrierDto) && <Text style={{ fontSize: 12 }}>营销载体:{b.marketingCarrierDto.map((item: { wechatOfficialAccountName: any; wechatOfficialAccountId: any; }) => `${item?.wechatOfficialAccountName}(${item?.wechatOfficialAccountId})`)?.toString()}</Text>}
+                                <Text style={{ fontSize: 12 }}>转化归因:{b?.userActionSetsList ? b?.userActionSetsList.map((item: { name: any; }) => item.name).toString() : '暂未配置'}</Text>
+                            </Space>
+                        } else if (['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(b.adgroupsDto?.marketingAssetOuterSpec?.marketingTargetType)) {
+                            return <Space size={0} direction="vertical">
+                                <Text style={{ fontSize: 12 }}>推广产品:微信公众号</Text>
+                                <Text style={{ fontSize: 12 }}>营销载体:微信公众号</Text>
+                                <Text style={{ fontSize: 12 }}>应用:{b?.productDto?.wechatOfficialAccountName}({b?.productDto?.wechatOfficialAccountId})</Text>
+                                <Text style={{ fontSize: 12 }}>转化归因:{b?.userActionSetsList ? b?.userActionSetsList.map((item: { name: any; }) => item.name).toString() : '暂未配置'}</Text>
+                            </Space>
+                        }
+                        return 'ERROR,请联系管理员'
+                    },
+                    onCell: (record, index = 0) => ({
+                        rowSpan: !(index % record.rowSpan) ? record.rowSpan : 0
+                    }),
+                },
+                {
+                    title: '定向',
+                    dataIndex: 'targeting',
+                    key: 'targeting',
+                    width: 170,
+                    render: (_, b) => {
+                        return <Text style={{ fontSize: 12 }}>{b?.targetDto?.targetingName}</Text>
+                    },
+                    onCell: (record, index = 0) => ({
+                        rowSpan: !(index % record.rowSpan) ? record.rowSpan : 0
+                    }),
+                },
+                {
+                    title: '预算与出价',
+                    dataIndex: 'dailyBudget',
+                    key: 'dailyBudget',
+                    width: 170,
+                    render: (_, b) => {
+                        let { optimizationGoal, dailyBudget, bidAmount, bidMode } = b?.adgroupsDto
+                        return <Space size={0} direction="vertical">
+                            <Text style={{ fontSize: 12 }}>广告日预算:{dailyBudget ? dailyBudget + '元/天' : '不限'}</Text>
+                            <Text style={{ fontSize: 12 }}>出价:{bidAmount}元/{optimizationGoal ? OPTIMIZATIONGOAL_ENUM[optimizationGoal] : ['BID_MODE_OCPM', 'BID_MODE_OCPC'].includes(bidMode) ? '千次曝光' : '点击'}</Text>
+                        </Space>
+                    },
+                    onCell: (record, index = 0) => ({
+                        rowSpan: !(index % record.rowSpan) ? record.rowSpan : 0
+                    }),
+                },
+            ]
+        },
+        {
+            title: '创意',
+            dataIndex: 'dynamicDto',
+            key: 'dynamicDto',
+            align: 'center',
+            children: [
+                {
+                    title: '创意名称',
+                    dataIndex: 'dynamicCreativeName',
+                    key: 'dynamicCreativeName',
+                    width: 200,
+                    render: (_, b) => {
+                        return <Text style={{ fontSize: 12 }}>{b?.dynamicDto?.dynamicCreativeName}</Text>
+                    }
+                },
+                {
+                    title: '创意素材',
+                    dataIndex: 'dynamicGroup',
+                    key: 'dynamicGroup',
+                    width: 200,
+                    render: (_, b) => {
+                        let deliveryMode = b?.dynamicDto?.deliveryMode
+                        let dynamicGroup = b?.dynamicGroup
+                        if (dynamicGroup && Object.keys(dynamicGroup).length) {
+                            let keys = Object.keys(dynamicGroup)
+                            if (deliveryMode === "DELIVERY_MODE_CUSTOMIZE") {
+                                return <Text style={{ fontSize: 12, color: '#1890ff' }}>已选{(keys.includes('video_id') || keys.includes('short_video1')) ? '1个视频,0张图片' : keys.includes('image_id') ? '0个视频,1张图片' : (keys.includes('image_list') || keys.includes('element_story') ? '1个组图, 0个视频' : '')}</Text>
+                            } else {
+                                return <Text style={{ fontSize: 12 }}>开发中</Text>
+                            }
+                        } else {
+                            return <Text style={{ fontSize: 12 }}>无需配置</Text>
+                        }
+
+                    }
+                },
+                {
+                    title: '创意文案',
+                    dataIndex: 'textDto',
+                    key: 'textDto',
+                    width: 200,
+                    render: (value, b) => {
+                        console.log('textDto-->', value)
+                        let deliveryMode = b?.dynamicDto?.deliveryMode
+                        if (value && Object.keys(value).length) {
+                            if (deliveryMode === "DELIVERY_MODE_CUSTOMIZE") {
+                                return <div className={style.detail_body} style={{ height: 'auto' }}>
+                                    {Object.keys(value)?.map((key, index: number) => {
+                                        return <div key={index}>
+                                            {key === 'description' ? <>
+                                                <Title level={5} style={{ fontSize: 12 }}>{'文案'}</Title>
+                                                <div className={style.text}><Text ellipsis={{ tooltip: true }}>{value['description']?.toString()}</Text></div>
+                                            </> : key === 'title' ? <>
+                                                <Title level={5} style={{ fontSize: 12 }}>{'标题'}</Title>
+                                                <div className={style.text}><Text ellipsis={{ tooltip: true }}>{value['title']?.toString()}</Text></div>
+                                            </> : null}
+                                        </div>
+                                    })}
+                                </div>
+                            } else {
+                                return <Text style={{ fontSize: 12 }}>开发中</Text>
+                            }
+                        } else {
+                            return <Text style={{ fontSize: 12 }}>无需配置</Text>
+                        }
+
+                    }
+                },
+                {
+                    title: '跳转类型',
+                    dataIndex: 'pageListDto',
+                    key: 'pageListDto',
+                    width: 200,
+                    render: (_, b) => {
+                        let pageListDto = b?.pageListDto
+                        return <Text style={{ fontSize: 12, wordBreak: 'break-all' }}>原生推广页:{pageListDto?.[0]?.pageName}</Text>
+                    }
+                }
+            ]
+        }
+    ]
+}
+
+export default columns

+ 130 - 0
src/pages/launchSystemV3/tencentAdPutIn/index.less

@@ -0,0 +1,130 @@
+.modalResetCss {
+    // font-family: PingFang SC,Microsoft YaHei UI,Microsoft YaHei,Helvetica Neue,Helvetica,Hiragino Sans GB,Arial,sans-serif;
+    --boder: 8px;
+
+    .ant-modal-content {
+        border-radius: var(--boder) !important;
+    }
+
+    .ant-modal-header {
+        border-radius: var(--boder) var(--boder) 0 0;
+        padding: 8px 16px;
+    }
+
+    .ant-modal-body {
+        padding: 16px;
+    }
+
+    .ant-modal-footer {
+        border-radius: 0 0 var(--boder) var(--boder);
+        padding: 4px 8px;
+
+    }
+
+    .ant-modal-close-x {
+        width: 39px;
+        height: 39px;
+        line-height: 39px;
+    }
+
+    .ant-btn {
+        height: 28px;
+        line-height: normal;
+        border-radius: 6px !important;
+        padding: 3px 11px;
+    }
+
+    .ant-space .ant-form-item {
+        margin-bottom: 16px;
+    }
+
+    .ant-picker,
+    .ant-input,
+    .ant-select-selector,
+    .ant-input-affix-wrapper {
+        border-radius: var(--boder) !important;
+    }
+
+    .ant-table-tbody td {
+        font-size: 12px;
+        font-family: PingFang SC,Microsoft YaHei UI,Microsoft YaHei,Helvetica Neue,Helvetica,Hiragino Sans GB,Arial,sans-serif;
+    }
+}
+
+.cardResetCss {
+    --boder: 8px;
+    border-radius: var(--boder);
+
+    .ant-card-head {
+        padding: 0 16px;
+        min-height: auto;
+        border-radius: var(--boder) var(--boder) 0 0;
+    }
+
+    .ant-card-head-title {
+        padding: 8px 0;
+    }
+
+    .ant-card-body {
+        padding: 16px;
+    }
+
+}
+
+.newCss {
+    .ant-form-item-label {
+        padding: 0 !important;
+    }
+}
+
+// .ant-radio-button-wrapper {
+//     height: 28px;
+//     line-height: 28px;
+//     padding: 0 6px;
+// }
+
+.ant-radio-button-wrapper:first-child {
+    border-radius: 6px 0 0 6px !important;
+}
+
+.ant-radio-button-wrapper:last-child {
+    border-radius: 0 6px 6px 0 !important;
+}
+
+.submit_pull {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 4px 8px;
+    background-color: #FFF;
+    border-radius: 0 0 8px 8px;
+    margin-bottom: 0;
+    text-align: right;
+}
+
+.targetingSelect .ant-drawer-body{
+    height: 100%;
+    overflow: hidden;
+    padding: 8px;
+    background-color: rgb(241, 244, 252);
+}
+
+
+.flexSpaceBetween {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.flexStart {
+    display: flex;
+    align-items: center;
+    gap: var(--g, 0);
+}
+
+.flexColumnStart {
+    display: flex;
+    flex-direction: column;
+    gap: var(--g, 0);
+}

+ 1840 - 0
src/pages/launchSystemV3/tencentAdPutIn/rules.ts

@@ -0,0 +1,1840 @@
+/** 创意组件化详情 */
+export const dynamicRules = {
+	"jump_info": {
+		"name": "jump_info",
+		"elementType": "LANDING_PAGE_STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "落地页组件",
+		"parentName": "",
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 3
+		},
+		"path": "/jump_info",
+		"children": {
+			"page_type": {
+				"name": "page_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": true,
+				"description": "落地页类型",
+				"parentName": "jump_info",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "PAGE_TYPE_H5",
+						"description": "自定义落地页"
+					}, {
+						"value": "PAGE_TYPE_XJ_WEB_H5",
+						"description": "蹊径网页落地页"
+					}, {
+						"value": "PAGE_TYPE_WECHAT_MINI_PROGRAM",
+						"description": "微信小程序"
+					}, {
+						"value": "PAGE_TYPE_WECHAT_CANVAS",
+						"description": "原生推广页"
+					}, {
+						"value": "PAGE_TYPE_XJ_QUICK",
+						"description": "蹊径性能版落地页"
+					}, {
+						"value": "PAGE_TYPE_APP_DEEP_LINK",
+						"description": "应用直达"
+					}]
+				},
+				"path": "/jump_info/page_type"
+			},
+			"page_spec": {
+				"name": "page_spec",
+				"elementType": "STRUCT",
+				"fieldType": "STRUCT",
+				"required": false,
+				"description": "落地页数据(详见接口文档)",
+				"parentName": "jump_info",
+				"path": "/jump_info/page_spec"
+			},
+			"backups": {
+				"name": "backups",
+				"elementType": "LANDING_PAGE_STRUCT",
+				"fieldType": "STRUCT_ARRAY",
+				"required": false,
+				"description": "兜底落地页(详见接口文档)",
+				"parentName": "jump_info",
+				"path": "/jump_info/backups"
+			}
+		}
+	},
+	"brand": {
+		"name": "brand",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "品牌形象",
+		"parentName": "",
+		"enumProperty": {
+			"default": ""
+		},
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"brand_image_id": {
+				"name": "brand_image_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "品牌标识图",
+				"parentName": "brand",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"imageRestriction": {
+						"width": 512,
+						"height": 512,
+						"fileSize": 400,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			},
+			"brand_name": {
+				"name": "brand_name",
+				"elementType": "TEXT",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "品牌名称",
+				"parentName": "brand",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"textRestriction": {
+						"minLength": 2,
+						"maxLength": 12,
+						"textPattern": "^[-+·.!:・()‘’()&!:'\\s\\w\\u4e00-\\u9fff]+$"
+					}
+				}
+			},
+			"jump_info": {
+				"name": "jump_info",
+				"elementType": "LANDING_PAGE_STRUCT",
+				"fieldType": "STRUCT",
+				"required": false,
+				"description": "品牌形象跳转",
+				"parentName": "brand",
+				"path": "/brand/jump_info"
+			},
+			"page_type": {
+				"name": "page_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": true,
+				"description": "落地页类型",
+				"parentName": "jump_info",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "PAGE_TYPE_WECHAT_CHANNELS_PROFILE",
+						"description": "视频号"
+					}, {
+						"value": "PAGE_TYPE_H5_PROFILE",
+						"description": "品牌简介页"
+					}, {
+						"value": "PAGE_TYPE_SEARCH_BRAND_AREA",
+						"description": "搜一搜超级品专"
+					}, {
+						"value": "PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL",
+						"description": "公众号"
+					}, {
+						"value": "PAGE_TYPE_NOT_USED",
+						"description": "自定义"
+					}]
+				},
+				"path": "/brand/jump_info/page_type"
+			},
+			"page_spec": {
+				"name": "page_spec",
+				"elementType": "STRUCT",
+				"fieldType": "STRUCT",
+				"required": false,
+				"description": "落地页数据(详见接口文档)",
+				"parentName": "jump_info",
+				"path": "/brand/jump_info/page_spec"
+			}
+		}
+	},
+	"action_button": {
+		"name": "action_button",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "行动按钮组件",
+		"enumProperty": {},
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"button_text": {
+				"name": "button_text",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": true,
+				"description": "按钮文字,当前仅支持“了解更多”,“去逛逛”,“查看详情”,“立即体验”,“立即咨询”,“立即开通”,“立即抢购”,“立即申请”,“立即秒杀”,“立即购买”,“立即预定”,“立即预约”,“立即领取”,“观看直播”,“进入小程序”,“领取优惠”",
+				"parentName": "action_button",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "了解更多",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "去逛逛",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "查看详情",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即体验",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即咨询",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即开通",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即抢购",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即申请",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即秒杀",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即购买",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即预定",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即预约",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即领取",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "观看直播",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "进入小程序",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "领取优惠",
+						"description": "",
+						"category": ""
+					}],
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {}
+			}
+		}
+	},
+	"description": {
+		"name": "description",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "描述组件",
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"children": {
+			"content": {
+				"name": "content",
+				"elementType": "TEXT",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "文案(1-30字)",
+				"parentName": "description",
+				"restriction": {
+					"textRestriction": {
+						"minLength": 1,
+						"maxLength": 30,
+						"textPattern": "^[^\\<\\>\\&'\\\"\\x08\\x09\\x0A\\x0D\\\\]+$"
+					}
+				}
+			}
+		}
+	},
+	"text_link": {
+		"name": "text_link",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": false,
+		"description": "文字链组件",
+		"parentName": "",
+		"arrayProperty": {
+			"minNumber": 0,
+			"maxNumber": 1
+		},
+		"children": {
+			"link_name_type": {
+				"name": "link_name_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": true,
+				"description": "文字链文案",
+				"parentName": "text_link",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "MORE_INFO",
+						"description": "了解更多"
+					}, {
+						"value": "BUY_NOW",
+						"description": "立即购买"
+					}, {
+						"value": "RESERVATION_BUY",
+						"description": "预约购买"
+					}, {
+						"value": "CONSULT_NOW",
+						"description": "立即咨询"
+					}, {
+						"value": "GO_SHOPPING",
+						"description": "去逛逛"
+					}, {
+						"value": "BOOK_DRIVE",
+						"description": "预约试驾"
+					}, {
+						"value": "GET_COUPONS",
+						"description": "领取优惠"
+					}, {
+						"value": "RESERVE_NOW",
+						"description": "立即预约"
+					}, {
+						"value": "MAKE_AN_APPOINTMENT",
+						"description": "预约活动"
+					}, {
+						"value": "WATCH_LIVE",
+						"description": "观看直播"
+					}, {
+						"value": "BOOK_NOW",
+						"description": "立即预定"
+					}, {
+						"value": "APPLY_NOW",
+						"description": "立即申请"
+					}, {
+						"value": "ENTER_MINI_PROGRAM",
+						"description": "进入小程序"
+					}]
+				}
+			},
+			"jump_info": {
+				"name": "jump_info",
+				"elementType": "LANDING_PAGE_STRUCT",
+				"fieldType": "STRUCT",
+				"required": true,
+				"description": "文字链落地页",
+				"parentName": "text_link",
+				"path": "/text_link/jump_info"
+			},
+			"page_type": {
+				"name": "page_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": true,
+				"description": "落地页类型",
+				"parentName": "jump_info",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "PAGE_TYPE_H5",
+						"description": "自定义落地页"
+					}, {
+						"value": "PAGE_TYPE_XJ_WEB_H5",
+						"description": "蹊径网页落地页"
+					}, {
+						"value": "PAGE_TYPE_WECHAT_MINI_PROGRAM",
+						"description": "微信小程序"
+					}, {
+						"value": "PAGE_TYPE_WECHAT_CANVAS",
+						"description": "原生推广页"
+					}, {
+						"value": "PAGE_TYPE_XJ_QUICK",
+						"description": "蹊径性能版落地页"
+					}]
+				},
+				"path": "/text_link/jump_info/page_type"
+			},
+			"page_spec": {
+				"name": "page_spec",
+				"elementType": "STRUCT",
+				"fieldType": "STRUCT",
+				"required": false,
+				"description": "落地页数据(详见接口文档)",
+				"parentName": "jump_info",
+				"path": "/text_link/jump_info/page_spec"
+			}
+		}
+	},
+	"floating_zone": {
+		"name": "floating_zone",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": false,
+		"description": "浮层卡片组件",
+		"parentName": "",
+		"enumProperty": {
+			"default": ""
+		},
+		"arrayProperty": {
+			"minNumber": 0,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"floating_zone_switch": {
+				"name": "floating_zone_switch",
+				"elementType": "BOOLEAN",
+				"fieldType": "BOOLEAN",
+				"required": true,
+				"description": "浮层卡片开关",
+				"parentName": "floating_zone",
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {}
+			},
+			"floating_zone_image_id": {
+				"name": "floating_zone_image_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": false,
+				"description": "创意图片",
+				"parentName": "floating_zone",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"imageRestriction": {
+						"width": 800,
+						"height": 800,
+						"fileSize": 400,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			},
+			"floating_zone_name": {
+				"name": "floating_zone_name",
+				"elementType": "TEXT",
+				"fieldType": "STRING",
+				"required": false,
+				"description": "文案一",
+				"parentName": "floating_zone",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"textRestriction": {
+						"minLength": 1,
+						"maxLength": 10,
+						"textPattern": "^[^\\<\\>\\&'\\\"\\x08\\x09\\x0A\\x0D\\\\]+$"
+					}
+				}
+			},
+			"floating_zone_desc": {
+				"name": "floating_zone_desc",
+				"elementType": "TEXT",
+				"fieldType": "STRING",
+				"required": false,
+				"description": "文案二",
+				"parentName": "floating_zone",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"textRestriction": {
+						"minLength": 1,
+						"maxLength": 14,
+						"textPattern": "^[^\\<\\>\\&'\\\"\\x08\\x09\\x0A\\x0D\\\\]+$"
+					}
+				}
+			},
+			"floating_zone_button_text": {
+				"name": "floating_zone_button_text",
+				"elementType": "TEXT",
+				"fieldType": "STRING",
+				"required": false,
+				"description": "按钮文字,当前仅支持“了解更多”,“去逛逛”,“查看详情”,“立即体验”,“立即咨询”,“立即开通”,“立即抢购”,“立即申请”,“立即秒杀”,“立即购买”,“立即预定”,“立即预约”,“立即领取”,“观看直播”,“进入小程序”,“领取优惠”",
+				"parentName": "floating_zone",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "了解更多",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "去逛逛",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "查看详情",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即体验",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即咨询",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即开通",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即抢购",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即申请",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即秒杀",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即购买",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即预定",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即预约",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "立即领取",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "观看直播",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "进入小程序",
+						"description": "",
+						"category": ""
+					}, {
+						"value": "领取优惠",
+						"description": "",
+						"category": ""
+					}],
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"textRestriction": {
+						"minLength": 1,
+						"maxLength": 10,
+						"textPattern": "^[^\\<\\>\\&'\\\"\\x08\\x09\\x0A\\x0D\\\\]+$"
+					}
+				}
+			},
+			"floating_zone_type": {
+				"name": "floating_zone_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": false,
+				"description": "浮层卡片类型",
+				"parentName": "floating_zone",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "FLOATING_ZONE_TYPE_IMAGE_TEXT",
+						"description": "图文复合类型"
+					}, {
+						"value": "FLOATING_ZONE_TYPE_SINGLE_IMAGE",
+						"description": "单图类型"
+					}, {
+						"value": "FLOATING_ZONE_TYPE_MULTI_BUTTON",
+						"description": "多按钮类型"
+					}, {
+						"value": "FLOATING_ZONE_TYPE_SLIDER_CARD",
+						"description": "轮播卡片类型"
+					}],
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {}
+			},
+			"floating_zone_single_image_id": {
+				"name": "floating_zone_single_image_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": false,
+				"description": "创意图片单图",
+				"parentName": "floating_zone",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"imageRestriction": {
+						"width": 540,
+						"height": 276,
+						"fileSize": 2048,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			}
+		}
+	},
+	"count_down": {
+		"name": "count_down",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": false,
+		"description": "倒计时组件",
+		"enumProperty": {},
+		"arrayProperty": {
+			"minNumber": 0,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"switch": {
+				"name": "switch",
+				"elementType": "BOOLEAN",
+				"fieldType": "BOOLEAN",
+				"required": true,
+				"description": "倒计时开关",
+				"parentName": "count_down",
+				"enumProperty": {},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {}
+			},
+			"expiring_timestamp": {
+				"name": "expiring_timestamp",
+				"elementType": "TEXT",
+				"fieldType": "INTEGER",
+				"required": false,
+				"description": "左下倒计时(选填)",
+				"parentName": "count_down",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"textRestriction": {
+						"minLength": 1,
+						"maxLength": 10,
+						"textPattern": "^[^\\<\\>\\&'\\\"\\x08\\x09\\x0A\\x0D\\\\]+$"
+					}
+				}
+			},
+			"time_type": {
+				"name": "time_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": false,
+				"description": "倒计时时间描述",
+				"parentName": "count_down",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "COUNTDOWN_TIME_START",
+						"description": "距离活动开始"
+					}, {
+						"value": "COUNTDOWN_TIME_END",
+						"description": "距离活动结束"
+					}]
+				},
+				"arrayProperty": {},
+				"structProperty": {}
+			}
+		}
+	},
+	"show_data": {
+		"name": "show_data",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": false,
+		"description": "数据外显组件",
+		"enumProperty": {},
+		"arrayProperty": {
+			"minNumber": 0,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"conversion_data_type": {
+				"name": "conversion_data_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": false,
+				"description": "转化数据类型",
+				"parentName": "show_data",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "GET_COUPONS"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "GET_COUPONS"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "MAKE_AN_APPOINTMENT"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "MAKE_AN_APPOINTMENT"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "RESERVATION_BUY"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "RESERVATION_BUY"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "BUY_NOW"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "BUY_NOW"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "APPLY_NOW"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "APPLY_NOW"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "RESERVE_NOW"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "RESERVE_NOW"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "CONSULT_NOW"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "CONSULT_NOW"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": "BOOK_DRIVE"
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": "BOOK_DRIVE"
+					}, {
+						"value": "CONVERSION_DATA_DEFAULT",
+						"description": "不使用",
+						"category": "WATCH_LIVE"
+					}, {
+						"value": "CONVERSION_DATA_ADMETRIC",
+						"description": "转化数据量",
+						"category": ""
+					}, {
+						"value": "CONVERSION_DATA_PRODUCT_DATA",
+						"description": "商品数据",
+						"category": ""
+					}],
+					"default": ""
+				}
+			},
+			"conversion_target_type": {
+				"name": "conversion_target_type",
+				"elementType": "ENUM",
+				"fieldType": "ENUM",
+				"required": false,
+				"description": "转化数据量类型",
+				"parentName": "show_data",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "CONVERSION_TARGET_GET",
+						"description": "领取",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_BUY",
+						"description": "购买",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_APPLY",
+						"description": "申请",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_CONSULT",
+						"description": "咨询",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_ADD_WECOM",
+						"description": "加企微",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "GO_SHOPPING"
+					}, {
+						"value": "CONVERSION_TARGET_GET",
+						"description": "领取",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_BUY",
+						"description": "购买",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_APPLY",
+						"description": "申请",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_CONSULT",
+						"description": "咨询",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_ADD_WECOM",
+						"description": "加企微",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_DONATION",
+						"description": "捐款",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_GOOD_DEED",
+						"description": "做好事",
+						"category": "MORE_INFO"
+					}, {
+						"value": "CONVERSION_TARGET_GET",
+						"description": "领取",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_BUY",
+						"description": "购买",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_APPLY",
+						"description": "申请",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_CONSULT",
+						"description": "咨询",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_ADD_WECOM",
+						"description": "加企微",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "ENTER_MINI_PROGRAM"
+					}, {
+						"value": "CONVERSION_TARGET_GET",
+						"description": "领取",
+						"category": "GET_COUPONS"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "GET_COUPONS"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "MAKE_AN_APPOINTMENT"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "MAKE_AN_APPOINTMENT"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "RESERVATION_BUY"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "RESERVATION_BUY"
+					}, {
+						"value": "CONVERSION_TARGET_BUY",
+						"description": "购买",
+						"category": "BUY_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "BUY_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_APPLY",
+						"description": "申请",
+						"category": "APPLY_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "APPLY_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "RESERVE_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "RESERVE_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_CONSULT",
+						"description": "咨询",
+						"category": "CONSULT_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_ADD_WECOM",
+						"description": "加企微",
+						"category": "CONSULT_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "CONSULT_NOW"
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": "BOOK_DRIVE"
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": "BOOK_DRIVE"
+					}, {
+						"value": "CONVERSION_TARGET_GET",
+						"description": "领取",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_RESERVE",
+						"description": "预约",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_BUY",
+						"description": "购买",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_APPLY",
+						"description": "申请",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_CONSULT",
+						"description": "咨询",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_LIKE",
+						"description": "想看",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_DONATION",
+						"description": "捐款",
+						"category": ""
+					}, {
+						"value": "CONVERSION_TARGET_GOOD_DEED",
+						"description": "做好事",
+						"category": ""
+					}],
+					"default": ""
+				}
+			}
+		}
+	},
+	"label": {
+		"name": "label",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": false,
+		"description": "标签",
+		"parentName": "",
+		"enumProperty": {
+			"default": ""
+		},
+		"arrayProperty": {
+			"minNumber": 0,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"list": {
+				"name": "list",
+				"elementType": "STRUCT",
+				"fieldType": "STRUCT_ARRAY",
+				"required": true,
+				"description": "标签",
+				"parentName": "label",
+				"enumProperty": {},
+				"arrayProperty": {
+					"minNumber": 1,
+					"maxNumber": 3
+				},
+				"structProperty": {},
+				"restriction": {},
+				"children": {
+					"content": {
+						"name": "content",
+						"elementType": "TEXT",
+						"fieldType": "STRING",
+						"required": true,
+						"description": "标签内容",
+						"parentName": "list",
+						"restriction": {
+							"textRestriction": {
+								"minLength": 2,
+								"maxLength": 15
+							}
+						}
+					},
+					"type": {
+						"name": "type",
+						"elementType": "ENUM",
+						"fieldType": "ENUM",
+						"required": false,
+						"description": "标签类型",
+						"parentName": "list",
+						"enumProperty": {
+							"enumeration": [{
+								"value": "LABEL_TYPE_UNKNOWN",
+								"description": "普通标签"
+							}, {
+								"value": "LABEL_TYPE_PROMOTIONAL",
+								"description": "节点营销标签"
+							}, {
+								"value": "LABEL_TYPE_CUSTOMIZETEXT",
+								"description": "自定义标签"
+							}, {
+								"value": "LABEL_TYPE_ICON",
+								"description": "角标"
+							}, {
+								"value": "LABEL_TYPE_DYNAMIC",
+								"description": "动态标签"
+							}],
+							"default": ""
+						},
+						"arrayProperty": {},
+						"structProperty": {},
+						"restriction": {}
+					}
+				}
+			}
+		}
+	},
+	"end_page": {
+		"name": "end_page",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": false,
+		"description": "视频结束页",
+		"parentName": "",
+		"enumProperty": {
+			"default": ""
+		},
+		"arrayProperty": {
+			"minNumber": 0,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"end_page_desc": {
+				"name": "end_page_desc",
+				"elementType": "TEXT",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "结束页文案",
+				"parentName": "end_page",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"textRestriction": {
+						"minLength": 1,
+						"maxLength": 12,
+						"textPattern": "^[^\\<\\>\\&'\\\"\\x08\\x09\\x0A\\x0D\\\\]+$"
+					}
+				}
+			},
+			"end_page_type": {
+				"name": "end_page_type",
+				"elementType": "ENUM",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "视频结束页类型",
+				"parentName": "end_page",
+				"enumProperty": {
+					"enumeration": [{
+						"value": "END_PAGE_DESCRIPTION_HIGHLIGHT",
+						"description": "突出文案",
+						"category": ""
+					}, {
+						"value": "END_PAGE_AVATAR_NICKNAME_HIGHLIGHT",
+						"description": "突出头像及昵称",
+						"category": ""
+					}],
+					"default": "END_PAGE_AVATAR_NICKNAME_HIGHLIGHT"
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {}
+			}
+		}
+	}
+}
+
+// 视频规则
+export const videoRules = {
+    "videoL": { // 横板视频 16:9
+		"name": "video",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "视频组件",
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"children": {
+			"video_id": {
+				"name": "video_id",
+				"elementType": "VIDEO",
+				"fieldType": "INTEGER",
+				"required": true,
+				"description": "视频",
+				"parentName": "video",
+				"restriction": {
+					"videoRestriction": {
+						"minWidth": 1280,
+						"minHeight": 720,
+						"ratioWidth": 16,
+						"ratioHeight": 9,
+						"fileSize": 512000,
+						"fileFormat": ["MEDIA_TYPE_MP4", "MEDIA_TYPE_MOV", "MEDIA_TYPE_AVI"],
+						"minDuration": 5,
+						"maxDuration": 300
+					}
+				}
+			},
+			"cover_id": {
+				"name": "cover_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "图片",
+				"parentName": "video",
+				"restriction": {
+					"imageRestriction": {
+						"width": 1280,
+						"height": 720,
+						"fileSize": 400,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			}
+		}
+	},
+    "videoP": {  // 竖版视频9:16
+		"name": "video",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "视频组件",
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"children": {
+			"video_id": {
+				"name": "video_id",
+				"elementType": "VIDEO",
+				"fieldType": "INTEGER",
+				"required": true,
+				"description": "视频",
+				"parentName": "video",
+				"restriction": {
+					"videoRestriction": {
+						"minWidth": 720,
+						"minHeight": 1280,
+						"ratioWidth": 9,
+						"ratioHeight": 16,
+						"fileSize": 512000,
+						"fileFormat": ["MEDIA_TYPE_MP4", "MEDIA_TYPE_MOV", "MEDIA_TYPE_AVI"],
+						"minDuration": 5,
+						"maxDuration": 300
+					}
+				}
+			},
+			"cover_id": {
+				"name": "cover_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "图片",
+				"parentName": "video",
+				"restriction": {
+					"imageRestriction": {
+						"width": 720,
+						"height": 1280,
+						"fileSize": 400,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			}
+		}
+	},
+    "short_video": {  // 4:3
+		"name": "short_video",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "短视频组件",
+		"parentName": "",
+		"enumProperty": {
+			"default": ""
+		},
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"structProperty": {},
+		"restriction": {},
+		"children": {
+			"cover_id": {
+				"name": "cover_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": false,
+				"description": "视频封面图",
+				"parentName": "short_video",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"imageRestriction": {
+						"width": 1280,
+						"height": 960,
+						"fileSize": 300,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			},
+			"short_video1": {
+				"name": "short_video1",
+				"elementType": "VIDEO",
+				"fieldType": "INTEGER",
+				"required": true,
+				"description": "视频",
+				"parentName": "short_video",
+				"enumProperty": {
+					"default": ""
+				},
+				"arrayProperty": {},
+				"structProperty": {},
+				"restriction": {
+					"videoRestriction": {
+						"minWidth": 640,
+						"minHeight": 480,
+						"ratioWidth": 4,
+						"ratioHeight": 3,
+						"fileSize": 512000,
+						"fileFormat": ["MEDIA_TYPE_MP4", "MEDIA_TYPE_MOV", "MEDIA_TYPE_AVI"],
+						"minDuration": 5,
+						"maxDuration": 300
+					}
+				}
+			}
+		}
+	}
+}
+
+export const imageRules = {
+    "imageL": { // 横板大图16:9
+		"name": "image",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "图片组件",
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"children": {
+			"image_id": {
+				"name": "image_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "图片",
+				"parentName": "image",
+				"restriction": {
+					"imageRestriction": {
+						"width": 1280,
+						"height": 720,
+						"fileSize": 400,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			}
+		}
+	},
+    "imageP": { // 竖版大图9:16
+		"name": "image",
+		"elementType": "STRUCT",
+		"fieldType": "STRUCT_ARRAY",
+		"required": true,
+		"description": "图片组件",
+		"arrayProperty": {
+			"minNumber": 1,
+			"maxNumber": 1
+		},
+		"children": {
+			"image_id": {
+				"name": "image_id",
+				"elementType": "IMAGE",
+				"fieldType": "STRING",
+				"required": true,
+				"description": "图片",
+				"parentName": "image",
+				"restriction": {
+					"imageRestriction": {
+						"width": 1080,
+						"height": 1920,
+						"fileSize": 400,
+						"fileFormat": ["IMAGE_TYPE_JPG", "IMAGE_TYPE_PNG"]
+					}
+				}
+			}
+		}
+	},
+}
+
+/** 广告配置数据 */
+export const adRules = {
+	"MARKETING_GOAL_BRAND_PROMOTION": {
+		"MARKETING_TARGET_TYPE_APP_ANDROID": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_IOS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_QUICK_APP": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_MINI_PROGRAM_WECHAT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_WORK": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		}
+	},
+	"MARKETING_GOAL_INCREASE_FANS_INTERACTION": {
+		"MARKETING_TARGET_TYPE_FICTION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_MINI_PROGRAM_WECHAT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_SHORT_DRAMA": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 1
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 2,
+					"LIVE_SUB_MODE": 2
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_WORK": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		}
+	},
+	"MARKETING_GOAL_LEAD_RETENTION": {
+		"MARKETING_TARGET_TYPE_ACTIVITY": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_QUICK_APP": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_FICTION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_MINI_PROGRAM_WECHAT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_SHORT_DRAMA": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 1
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 2,
+					"LIVE_SUB_MODE": 2
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_WORK": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		}
+	},
+	"MARKETING_GOAL_PRODUCT_SALES": {
+		"MARKETING_TARGET_TYPE_ACTIVITY": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_ANDROID": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_IOS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_QUICK_APP": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_FICTION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				},
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_MINI_PROGRAM_WECHAT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_PLATFORM_CHANNEL": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_SHORT_DRAMA": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				},
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 1
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 2,
+					"LIVE_SUB_MODE": 2
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_WORK": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		}
+	},
+	"MARKETING_GOAL_USER_GROWTH": {
+		"MARKETING_TARGET_TYPE_ACTIVITY": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_ANDROID": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_IOS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_APP_QUICK_APP": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_FICTION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				},
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_MINI_PROGRAM_WECHAT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_PLATFORM_CHANNEL": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_SHORT_DRAMA": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_APP_ANDROID": {
+					"PRODUCT_TYPE": 12
+				},
+				"MARKETING_CARRIER_TYPE_APP_IOS": {
+					"PRODUCT_TYPE": 19
+				},
+				"MARKETING_CARRIER_TYPE_APP_QUICK_APP": {
+					"PRODUCT_TYPE": 53
+				},
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 1
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_WECHAT_CHANNELS_LIVE_RESERVATION": {
+					"PRODUCT_TYPE": 51,
+					"LIVE_MODE": 2,
+					"LIVE_SUB_MODE": 2
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				},
+				"MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT": {
+					"PRODUCT_TYPE": 23
+				}
+			}
+		},
+		"MARKETING_TARGET_TYPE_WECHAT_WORK": {
+			"MARKETING_SUB_GOAL_UNKNOWN": {
+				"MARKETING_CARRIER_TYPE_JUMP_PAGE": {
+					"PRODUCT_TYPE": 43
+				}
+			}
+		}
+	}
+}

+ 63 - 0
src/pages/launchSystemV3/tencentAdPutIn/taskList/dynamicLog.tsx

@@ -0,0 +1,63 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getDynamicListApi } from "@/services/adqV3"
+import { Button, Card, Space, Table, Tag, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import '../index.less'
+import { columnsDynamicLog } from "./tableConfig"
+const { Title } = Typography;
+
+
+interface Props {
+    record: any
+}
+/**
+ * 创意日志
+ * @returns 
+ */
+const DynamicLog: React.FC<Props> = ({ record }) => {
+
+    /****************************************/
+    const [queryForm, setQueryForm] = useState<PULLIN.GetDynamicV3LogProps>({ pageNum: 1, pageSize: 20 })
+    const getDynamicList = useAjax((params) => getDynamicListApi(params), { formatResult: true })
+    /****************************************/
+
+    useEffect(() => {
+        if (record?.id) {
+            getDynamicList.run({ ...queryForm, adgroupCreateLogId: record?.id })
+        }
+    }, [record?.id, queryForm])
+
+    return <Card
+        hoverable
+        className="cardResetCss"
+    >
+        <Title level={5}>
+            <span>{record?.adgroupName + ' 创意日志'} </span>
+            <Button type="link" onClick={() => getDynamicList.refresh()}>刷新</Button>
+        </Title>
+        <Table
+            columns={columnsDynamicLog()}
+            dataSource={getDynamicList?.data?.data?.records}
+            size="small"
+            loading={getDynamicList?.loading}
+            scroll={{ y: 300 }}
+            bordered
+            rowKey={'dynamicCreativeId'}
+            pagination={{
+                pageSize: queryForm.pageSize,
+                current: queryForm.pageNum,
+                total: getDynamicList?.data?.data?.total || 0,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>
+            }}
+            onChange={(pagination) => {
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                newQueryForm.pageNum = current
+                newQueryForm.pageSize = pageSize
+                setQueryForm(newQueryForm)
+            }}
+        />
+    </Card>
+}
+
+export default React.memo(DynamicLog)

+ 99 - 0
src/pages/launchSystemV3/tencentAdPutIn/taskList/index.tsx

@@ -0,0 +1,99 @@
+import { Button, Card, DatePicker, Input, Table, Tag } from "antd"
+import React, { useEffect, useState } from "react"
+import '../index.less'
+import { SearchOutlined } from "@ant-design/icons"
+import { useAjax } from "@/Hook/useAjax"
+import { getTaskV3ListApi } from "@/services/adqV3"
+import moment from "moment"
+import { useModel } from "umi"
+import columns from "./tableConfig"
+import Log from "./log"
+
+const TaskList: React.FC = () => {
+
+    /****************************************/
+    const { initTargeting, geoLocationList, modelList } = useModel('useLaunchV3.useTargeting')
+
+    const [queryParams, setQueryParams] = useState<PULLIN.GetTaskV3Props>({ pageNum: 1, pageSize: 20 })
+    const [queryParamsNew, setQueryParamsNew] = useState<PULLIN.GetTaskV3Props>({ pageNum: 1, pageSize: 20 })
+    const [logVisible, setLogVisible] = useState<boolean>(false)
+    const [logData, setLogData] = useState<any>({})
+
+    const getTaskV3List = useAjax((params) => getTaskV3ListApi(params), { formatResult: true })
+    /****************************************/
+
+    useEffect(() => {
+        initTargeting()
+        let taskName = sessionStorage.getItem('CAMPV3')
+        if (taskName) {
+            setQueryParamsNew({ ...queryParamsNew, taskName })
+            setQueryParams({ ...queryParamsNew, taskName })
+            sessionStorage.removeItem('CAMPV3')
+        }
+    }, [])
+
+    useEffect(() => {
+        getTaskV3List.run(queryParamsNew)
+    }, [queryParamsNew])
+
+
+    const callback = (data: any, type: 'log' | 'page' | 'copy', allData?: any) => {
+        switch (type) {
+            case 'log':
+                setLogData({ ...data })
+                setLogVisible(true)
+                break
+            case 'copy':
+                console.log('111111111111--->', data);
+                // sessionStorage.setItem('TASKID', data.taskId)
+                // sessionStorage.setItem('LAUNCHMODE', data.putModel)
+                // window.location.href = '/#/launchSystemNew/launchManage/createAd'
+                break
+        }
+    }
+
+    return <Card
+        className="cardResetCss"
+        title={<div className="flexStart" style={{ gap: 8 }}>
+            <Input style={{ width: 180 }} placeholder="请输入任务名称" value={queryParams?.taskName} allowClear onChange={(e) => setQueryParams({ ...queryParams, taskName: e.target.value, pageNum: 1 })} />
+            <DatePicker.RangePicker style={{ width: 250 }} placeholder={['创建开始时间', '创建结束时间']} value={(queryParams?.createTimeMin && queryParams?.createTimeMax) ? [moment(queryParams?.createTimeMin), moment(queryParams?.createTimeMax)] as any : undefined} onChange={(_, option) => setQueryParams({ ...queryParams, createTimeMin: option[0], createTimeMax: option[1], pageNum: 1 })} />
+            <Button type="primary" icon={<SearchOutlined />} onClick={() => setQueryParamsNew({ ...queryParams })}>搜索</Button>
+        </div>}
+    >
+        <Table
+            columns={columns(geoLocationList, modelList, callback)}
+            dataSource={getTaskV3List?.data?.data?.records}
+            size="small"
+            loading={getTaskV3List?.loading}
+            scroll={{ y: 600 }}
+            bordered
+            rowKey={'id'}
+            pagination={{
+                pageSize: queryParamsNew.pageSize,
+                current: queryParamsNew.pageNum,
+                total: getTaskV3List?.data?.data?.total || 0,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>
+            }}
+            onChange={(pagination) => {
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryParamsNew))
+                newQueryForm.pageNum = current
+                newQueryForm.pageSize = pageSize
+                setQueryParamsNew(newQueryForm)
+                setQueryParams(newQueryForm)
+            }}
+        />
+
+        {/* 任务日志 */}
+        {logVisible && <Log
+            visible={logVisible}
+            data={logData}
+            onClose={() =>{
+                setLogData({})
+                setLogVisible(false)
+            }}
+        />}
+    </Card>
+}
+
+export default React.memo(TaskList)

+ 79 - 0
src/pages/launchSystemV3/tencentAdPutIn/taskList/log.tsx

@@ -0,0 +1,79 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getTaskV3LogListApi } from "@/services/adqV3"
+import { Button, Drawer, Space, Table, Tag } from "antd"
+import React, { useEffect, useState } from "react"
+import { columnsLog } from "./tableConfig"
+import DynamicLog from "./dynamicLog"
+
+interface Props {
+    data: any
+    visible?: boolean,
+    onClose?: () => void
+}
+/**
+ * 创建日志
+ * @returns 
+ */
+const Log: React.FC<Props> = (props) => {
+
+    /*****************************/
+    const { data, visible, onClose } = props
+    console.log(data)
+    const { id, taskName } = data
+    const [queryForm, setQueryForm] = useState<PULLIN.GetTaskV3LogProps>({ pageNum: 1, pageSize: 20 })
+
+    const getTaskV3LogList = useAjax((params) => getTaskV3LogListApi(params), { formatResult: true })
+    /*****************************/
+
+    useEffect(() => {
+        getList()
+    }, [queryForm, id])
+
+    /** 获取列表 */
+    const getList = () => {
+        if (id) {
+            getTaskV3LogList.run({ ...queryForm, taskId: id })
+        }
+    }
+
+    return <Drawer
+        bodyStyle={{ padding: 0 }}
+        title={<Space>
+            <span>{taskName + ' 日志'}</span>
+            <Button type="link" onClick={() => getTaskV3LogList.refresh()}>刷新</Button>
+        </Space>}
+        width={1400}
+        placement="right"
+        onClose={() => { onClose && onClose() }}
+        visible={visible}
+    >
+        <Table
+            columns={columnsLog()}
+            dataSource={getTaskV3LogList?.data?.data?.records}
+            size="small"
+            loading={getTaskV3LogList?.loading}
+            scroll={{ y: 600 }}
+            bordered
+            rowKey={'id'}
+            pagination={{
+                pageSize: queryForm.pageSize,
+                current: queryForm.pageNum,
+                total: getTaskV3LogList?.data?.data?.total || 0,
+                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>
+            }}
+            onChange={(pagination) => {
+                let { current, pageSize } = pagination
+                let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                newQueryForm.pageNum = current
+                newQueryForm.pageSize = pageSize
+                setQueryForm(newQueryForm)
+            }}
+            expandable={{
+                expandedRowRender: record => <DynamicLog record={record} />
+            }}
+        />
+    </Drawer>
+}
+
+
+export default React.memo(Log)

+ 359 - 0
src/pages/launchSystemV3/tencentAdPutIn/taskList/tableConfig.tsx

@@ -0,0 +1,359 @@
+import React from "react"
+import { Badge, Popover, Space, TableProps, Typography } from "antd"
+import { AD_STATUS_ENUM, DELIVERY_MODE_ENUM, DYNAMIC_CREATIVE_TYPE_ENUM, MARKETING_GOAL_ENUM } from "../const"
+import TargetingTooltip from "../../components/TargetingTooltip"
+import { QuestionCircleFilled } from "@ant-design/icons"
+import AdgroupTooltip from "../../components/AdgroupTooltip"
+import DynamicTooltip from "../../components/DynamicTooltip"
+import { copy } from "@/utils/utils"
+
+
+const columns = (geoLocationList: any, modelList: any, callback: (data: any, type: 'log' | 'page' | 'copy', allData?: any) => void): TableProps<any>['columns'] => {
+
+    return [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 80,
+            align: 'center',
+            fixed: 'left',
+            render: (_, records) => {
+                return <Space>
+                    <a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { callback(records, 'log', records) }}>日志</a>
+                    {/* <a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { callback({ taskId: b.id, campaignName: b.campaignName,putModel:b.putModel }, 'copy') }}>复制</a> */}
+                </Space>
+            }
+        },
+        {
+            title: '任务广告状态',
+            dataIndex: 'status',
+            key: 'status',
+            width: 100,
+            align: 'center',
+            fixed: 'left',
+            render(value) {
+                return { 0: <Badge status="processing" text={<span style={{ fontSize: 12 }} >执行中</span>} />, 1: <Badge style={{ fontSize: 12 }} status="error" text={<span style={{ fontSize: 12 }} >全部失败</span>} />, 2: <Badge style={{ fontSize: 12 }} status="warning" text={<span style={{ fontSize: 12 }} >部分成功</span>} />, 100: <Badge style={{ fontSize: 12 }} status="success" text={<span style={{ fontSize: 12 }} >全部成功</span>} /> }[value]
+            },
+        },
+        {
+            title: '任务名称',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 120,
+            ellipsis: true,
+            fixed: 'left',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: 'ID',
+            dataIndex: 'id',
+            key: 'id',
+            align: 'center',
+            width: 60,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '广告状态',
+            dataIndex: 'configuredStatus',
+            key: 'configuredStatus',
+            align: 'center',
+            width: 80,
+            render: (_, b) => {
+                let configuredStatus = b?.adgroupDTO?.configuredStatus
+                if (configuredStatus) {
+                    return <span style={{ fontSize: "12px" }}>{AD_STATUS_ENUM[configuredStatus]}</span>
+                } else {
+                    return <span>--</span>
+                }
+            }
+        },
+        {
+            title: '广告详情',
+            dataIndex: 'adgroupDTO',
+            key: 'adgroupDTO',
+            width: 200,
+            ellipsis: true,
+            render(value) {
+                return <div style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
+                    <div style={{ width: 'calc(100% - 20px)' }}><Typography.Text style={{ fontSize: 12 }} ellipsis={{ tooltip: true }}>{value?.adgroupName}</Typography.Text></div>
+                    <Popover
+                        placement="right"
+                        overlayInnerStyle={{ maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' }}
+                        mouseEnterDelay={0.5}
+                        content={<AdgroupTooltip
+                            data={value}
+                        />}
+                    >
+                        <QuestionCircleFilled style={{ fontSize: 12 }} />
+                    </Popover>
+                </div>
+            }
+        },
+        {
+            title: '定向详情',
+            dataIndex: 'targetings',
+            key: 'targetings',
+            width: 300,
+            render(value) {
+                return <div style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 10, overflow: 'hidden', overflowX: 'auto' }}>
+                    {value?.map((item: any, index: number) => <Popover
+                        key={index}
+                        placement="right"
+                        overlayInnerStyle={{ maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' }}
+                        mouseEnterDelay={1}
+                        content={<TargetingTooltip
+                            data={item}
+                            geoLocationList={geoLocationList}
+                            modelList={modelList}
+                        />}
+                    >
+                        <div style={{ width: 80, cursor: 'help' }}><Typography.Text style={{ fontSize: 12 }} ellipsis>{index + 1}、{item?.targetingName || '--'}</Typography.Text></div>
+                    </Popover>)}
+                </div>
+            }
+        },
+        {
+            title: '创意详情',
+            dataIndex: 'dynamicCreativesDTO',
+            key: 'dynamicCreativesDTO',
+            width: 200,
+            ellipsis: true,
+            render(value) {
+                return <div style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
+                    <div style={{ width: 'calc(100% - 20px)' }}><Typography.Text style={{ fontSize: 12 }} ellipsis={{ tooltip: true }}>{value?.dynamicCreativeName}</Typography.Text></div>
+                    <Popover
+                        placement="right"
+                        overlayInnerStyle={{ maxWidth: 350, maxHeight: 350, overflow: 'hidden', overflowY: 'auto' }}
+                        mouseEnterDelay={0.5}
+                        content={<DynamicTooltip
+                            data={value}
+                        />}
+                    >
+                        <QuestionCircleFilled style={{ fontSize: 12 }} />
+                    </Popover>
+                </div>
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 140,
+            render: (value) => {
+                return <span style={{ fontSize: "12px" }}>{value || '--'}</span>
+            }
+        },
+        {
+            title: <span style={{ marginLeft: 10 }}>任务反馈</span>,
+            dataIndex: 'total',
+            key: 'total',
+            width: 480,
+            render: (value, records) => {
+                return <Space style={{ fontSize: "12px", marginLeft: 10 }} size={10}>
+                    <span style={{ fontSize: 12 }}><Badge status="processing" />广告总数:<span style={{ fontWeight: 'bold', color: '#1890ff' }}>{value}</span>条</span>
+                    {<span style={{ fontSize: 12, fontWeight: 'bold' }}><Badge status="success" />广告成功数:<span style={{ fontWeight: 'bold', color: '#52c41a' }}>{records?.successCount || 0}</span>条</span>}
+                    <span style={{ fontSize: 12, fontWeight: 'bold' }}><Badge status="processing" />创意总数:<span style={{ fontWeight: 'bold', color: '#1890ff' }}>{records?.dynamicCreativeCount || 0}</span>条</span>
+                    {<span style={{ fontSize: 12, fontWeight: 'bold' }}><Badge status="success" />创意成功数:<span style={{ fontWeight: 'bold', color: '#52c41a' }}>{records?.dynamicCreativeSuccessCount || 0}</span>条</span>}
+                </Space>
+            }
+        }
+    ]
+}
+
+export default columns
+
+export const columnsLog = (): TableProps<any>['columns'] => {
+    return [
+        {
+            title: '广告账号',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            width: 100,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '广告名称',
+            dataIndex: 'adgroupName',
+            key: 'adgroupName',
+            width: 160,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '定向名称',
+            dataIndex: 'targetingsName',
+            key: 'targetingsName',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '营销目的',
+            dataIndex: 'marketingGoal',
+            key: 'marketingGoal',
+            width: 100,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{MARKETING_GOAL_ENUM[value]}</span>
+            },
+        },
+        {
+            title: '出价',
+            dataIndex: 'bidAmount',
+            key: 'bidAmount',
+            width: 90,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '广告创建状态',
+            dataIndex: 'adgroupStatus',
+            key: 'adgroupStatus',
+            width: 90,
+            align: 'center',
+            render(value) {
+                return { 0: <Badge status="processing" text={<span style={{ fontSize: 12 }}>执行中</span>} />, 1: <Badge status="error" style={{ fontSize: 12 }} text={<span>失败</span>} />, 100: <Badge style={{ fontSize: 12 }} status="success" text={<span>成功</span>} /> }[value]
+            },
+        },
+        {
+            title: <span style={{ marginLeft: 10 }}>任务反馈</span>,
+            dataIndex: 'dynamicCreativeCount',
+            key: 'dynamicCreativeCount',
+            width: 280,
+            render: (value, records) => {
+                return <Space style={{ fontSize: "12px", marginLeft: 10 }} size={10}>
+                    <span style={{ fontSize: 12, fontWeight: 'bold' }}><Badge status="processing" />创意总数:<span style={{ fontWeight: 'bold', color: '#1890ff' }}>{records?.dynamicCreativeCount || 0}</span>条</span>
+                    {<span style={{ fontSize: 12, fontWeight: 'bold' }}><Badge status="success" />创意成功数:<span style={{ fontWeight: 'bold', color: '#52c41a' }}>{records?.dynamicCreativeSuccessCount || 0}</span>条</span>}
+                </Space>
+            }
+        },
+        {
+            title: '错误信息',
+            dataIndex: 'failMsg',
+            key: 'failMsg',
+            width: 180,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+    ]
+}
+
+export const columnsDynamicLog = (): TableProps<any>['columns'] => {
+    return [
+        {
+            title: '创意名称',
+            dataIndex: 'dynamicCreativeName',
+            key: 'dynamicCreativeName',
+            width: 150,
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '创意ID',
+            dataIndex: 'dynamicCreativeId',
+            key: 'dynamicCreativeId',
+            width: 80,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '投放模式',
+            dataIndex: 'deliveryMode',
+            key: 'deliveryMode',
+            width: 80,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{DELIVERY_MODE_ENUM[value]}</span>
+            }
+        },
+        {
+            title: '动态创意类型',
+            dataIndex: 'dynamicCreativeType',
+            key: 'dynamicCreativeType',
+            width: 80,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{DYNAMIC_CREATIVE_TYPE_ENUM[value]}</span>
+            }
+        },
+        {
+            title: '任务ID',
+            dataIndex: 'taskId',
+            key: 'taskId',
+            width: 70,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '广告组ID',
+            dataIndex: 'adgroupId',
+            key: 'adgroupId',
+            width: 80,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '创意模板ID',
+            dataIndex: 'creativeTemplateId',
+            key: 'creativeTemplateId',
+            width: 75,
+            ellipsis: true,
+            align: 'center',
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '创意创建状态',
+            dataIndex: 'creativeStatus',
+            key: 'creativeStatus',
+            width: 80,
+            align: 'center',
+            render(value) {
+                return { 0: <Badge status="processing" text={<span style={{ fontSize: 12 }}>执行中</span>} />, 1: <Badge status="error" style={{ fontSize: 12 }} text={<span>失败</span>} />, 100: <Badge style={{ fontSize: 12 }} status="success" text={<span>成功</span>} /> }[value]
+            },
+        },
+        {
+            title: '失败信息',
+            dataIndex: 'failMsg',
+            key: 'failMsg',
+            width: 280,
+            render: (value) => {
+                return value ? <span style={{ fontSize: 12 }} onClick={() => copy(value)}>{value}</span> : '--'
+            }
+        }
+    ]
+}

+ 125 - 0
src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts

@@ -0,0 +1,125 @@
+declare namespace PULLIN {
+    type OGPParamsProps = {
+        marketingGoal: string,
+        bidMode: string;
+        siteSet: string[];
+        automaticSiteEnabled: boolean,
+        marketingTargetType?: string, 
+        marketingCarrierType?: string,
+    }
+    interface AdReactContent {
+        form: FormInstance<any>
+        OGPParams: OGPParamsProps,
+        setOGPparams: React.Dispatch<React.SetStateAction<OGPParamsProps>>
+    }
+    interface AddeliveryProps {
+        adgroups: any,
+        targeting: any[],
+        dynamic: any,
+        dynamicMaterialDTos: any
+        dynamicCreativesTextDTOS: any
+    }
+    interface DispatchAddelivery {
+        addelivery: AddeliveryProps,
+        setAddelivery: React.Dispatch<React.SetStateAction<AddeliveryProps>>
+        accountCreateLogs: AccountCreateLogsProps[]
+        setAccountCreateLogs: React.Dispatch<React.SetStateAction<AccountCreateLogsProps[]>>
+        materialData: any
+        setMaterialData: React.Dispatch<any>
+        textData: any,
+        setTextData: React.Dispatch<any>,
+        clearData: () => void
+    }
+    type DataType = { label: string | number, value: any, disabled?: boolean }
+    interface FormItemDataProps {
+        id?: any
+        value?: string,
+        onChange?: (value?: string) => void
+    }
+    interface FormItemDataNewProps {
+        data: DataType[]
+        id?: any,
+        value?: any,
+        onChange?: (value?: any) => void
+    }
+    interface FormItemDataArrayProps {
+        id?: number,
+        value?: string[],
+        onChange?: (value?: string[]) => void
+    }
+    interface FormItemDataNew2Props {
+        data: AdcreativeTemplateList[]
+        id?: any,
+        value?: any,
+        onChange?: (value?: any) => void
+    }
+    interface GetTargeting {
+        pageNum: number,
+        pageSize: number,
+        accountId?: number,
+        targetingName?: string,  
+        min?: string, 
+        max?: string,
+    }
+    interface AccountCreateLogsProps { 
+        accountId: number, 
+        userActionSetsList?: any[], 
+        productList?: any[],
+        wechatChannelList?: any[],
+        conversionList?: any, 
+        customAudienceList?: any, 
+        excludedCustomAudienceList?: any, 
+        pageList?: any, 
+        coldStartAudienceList?: any[]
+    }
+    interface DynamicReactContent {
+        form: FormInstance<any>
+        value: any,
+        adgroups: any,
+        creativeComponents: any,
+        setCreativeComponents: React.Dispatch<any>,
+        isUpdate: boolean,
+        setIsUpdate: React.Dispatch<React.SetStateAction<boolean>>
+    }
+    interface AdcreativeTemplateList {
+        creativeSampleImage: string,
+        creativeTemplateAppellation: string,
+        creativeTemplateId: number,
+        creativeTemplateStyle: string,
+        isGeneral: boolean,
+        siteSet: number,
+        label: string | number, 
+        value: any,
+        tooltip?: string, 
+        img?: string
+    }
+    interface GetTaskV3Props {
+        pageNum: number,
+        pageSize: number,
+        taskName?: string,
+        createTimeMin?: string,
+        createTimeMax?: string
+    }
+    interface GetTaskV3LogProps {
+        pageNum: number,
+        pageSize: number,
+        taskId?: string
+    }
+    interface GetDynamicV3LogProps {
+        pageNum: number,
+        pageSize: number,
+        adgroupCreateLogId?: string
+    }
+    type TextDtoProps = {
+        label: string,
+        value: string,
+        required: boolean,
+        restriction: {
+            textRestriction: {
+                minLength: number,
+                maxLength: number,
+                textPattern: string
+            }
+        }
+    }
+}

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

@@ -0,0 +1,273 @@
+import { request } from 'umi';
+import { api } from '../api';
+
+
+/**
+ * 查询优化目标权限
+ * @param data 
+ * @returns 
+ */
+export async function getOptimizationGoalPermissionsV3Api(data: any) {
+    return request(api + `/adq/v3/launch/tools/getOptimizationGoalPermissions`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 获取地理位置
+ * @param data 
+ * @returns 
+ */
+export async function getTargetingGagsApi(data: any) {
+    return request(api + `/adq/v3/launch/tools/getTargetingGags`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 获取小说列表
+ * @param data
+ * @returns
+ */
+export async function getmarketingAssetContentApi(data: {
+    marketingAssetType?: string,
+    marketingAssetName?: string,
+    accountId: number,
+    pageNum: number,
+    pageSize: number,
+    marketingAssetNameList: string[]
+}) {
+    return request(api + `/adq/v3/marketingAssets/marketingAssetContent/get`, {
+        method: 'POST',
+        data,
+    });
+}
+
+/**
+ * 同步小说
+ * @param data
+ * @returns
+ */
+export async function synMarketingAssetContentApi(data: { accountId: number }) {
+    return request(api + `/adq/v3/marketingAssets/marketingAssetContent/sync`, {
+        method: 'POST',
+        data,
+    });
+}
+
+/**
+ * 获取公众号
+ * @param data 
+ * @returns 
+ */
+export async function getWechatOfficialAccountApi(data: { accountId: number }) {
+    return request(api + `/adq/v3/marketingAssets/getWechatOfficialAccounts`, {
+        method: 'POST',
+        data,
+    });
+}
+
+/**
+ * 批量获取公众号
+ * @param data 
+ * @returns 
+ */
+export async function getWechatOfficialAccountsApi(data: { accountIdList: number[] }) {
+    return request(api + `/adq/v3/marketingAssets/getWechatOfficialAccountsBatch`, {
+        method: 'POST',
+        data,
+    });
+}
+
+
+
+/**
+ * 获取数据源
+ * @param data
+ * @returns
+ */
+export async function getDataSourceV3Api(data: { accountIds: number[], promotedObjectType?: string }) {
+    return request(api + `/adq/v3/userActionSets/allByAccount`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 同步数据源
+ * @param data
+ * @returns
+ */
+export async function sysDataSourceV3Api(data: number[]) {
+    return request(api + `/adq/v3/userActionSets/syncByAdAccountId`, {
+        method: 'PATCH',
+        data,
+    });
+}
+
+
+/**
+ * 获取创意规格详情
+ * @param data 
+ * @returns 
+ */
+export async function getCreativeDetailsApi(data: any) {
+    return request(api + `/adq/v3/launch/tools/getCreativeDetails`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 获取创意规格列表
+ * @param data 
+ * @returns 
+ */
+export async function getCreativeTemplateListApi(data: any) {
+    return request(api + `/adq/v3/launch/tools/getCreativeTemplateList`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 文案助手
+ * @param params 
+ * @returns 
+ */
+export async function getTextApi(params: { maxTextLength: number, adAccountId: number }) {
+    return request(api + `/adq/v3/launch/tools/creative/tools/text`, {
+        method: 'GET',
+        params
+    })
+
+}
+
+
+
+/**
+ * 获取ADQ落地页列表
+ * @param adgroupName 广告名称
+ * @param promotedObjectType 广告类型
+ * @param accountId 账户ID
+ */
+export async function getAdqLandingPageListApi(params: {
+    pageNum: number;
+    pageSize: number;
+    accountId?: number;
+    pageName?: string;
+    pageType?: string;
+    pageTemplateId?: string;
+    pageStatus?: string;
+}) {
+    Object.keys(params).forEach(key => {
+        if (!params[key]) {
+            delete params[key]
+        }
+    })
+    return request(api + '/adq/v3/marketingAssets/listWXDownPage', {
+        method: 'POST',
+        data: params,
+    });
+}
+
+/**
+ * 按账号同步落地页
+ * @param adAccountId 本地ID
+ */
+export async function putAdqLandingPageApi(data: any) {
+    return request(api + `/adq/v3/marketingAssets/syncAllWXDownPage`, {
+        method: 'PUT',
+        data
+    });
+}
+
+
+/**
+ * 获取品牌形象列表
+ * @returns 
+ */
+export async function getSysBrandApi() {
+    return request(api + `/adq/sysBrand/allOfUser`, {
+        method: 'GET'
+    })
+}
+
+/**
+ * 新增品牌形象
+ * @param data 
+ * @returns 
+ */
+export async function addSysBrandApi(data: { name: string, brandImgUrl: string }) {
+    return request(api + `/adq/sysBrand`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 修改品牌形象
+ * @param data 
+ * @returns 
+ */
+export async function editSysBrandApi(data: { name: string, brandImgUrl: string, sysBrandId: number }) {
+    const { sysBrandId, ...params } = data
+    return request(api + `/adq/sysBrand/${sysBrandId}`, {
+        method: 'PUT',
+        data: params
+    })
+}
+
+/**
+ * 删除品牌形象
+ * @param data 
+ * @returns 
+ */
+export async function delSysBrandApi(data: { sysBrandId: number }) {
+    const { sysBrandId } = data
+    return request(api + `/adq/sysBrand/${sysBrandId}`, {
+        method: 'DELETE'
+    })
+}
+
+
+/**
+ * 新增头像昵称跳转页
+ * @param data 
+ * @returns 
+ */
+export async function addProfilesApi(data: { profileName: string, imageUrl: string, description: string }) {
+    return request(api + `/adq/v3/marketingAssets/profiles/add`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 获取头像昵称跳转页
+ * @param data 
+ * @returns 
+ */
+export async function getProfilesApi(data: { profileName?: string }) {
+    return request(api + `/adq/v3/marketingAssets/profiles/get`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 删除头像昵称跳转页
+ * @param data 
+ * @returns 
+ */
+export async function delProfilesApi(params: { id: number }) {
+    return request(api + `/adq/v3/marketingAssets/deleteSysProfiles`, {
+        method: 'DELETE',
+        params
+    })
+}

Vissa filer visades inte eftersom för många filer har ändrats