2 Commity a6e0193e2b ... c5729671c9

Autor SHA1 Wiadomość Data
  ringcode c5729671c9 RING:创意跟进 3 lat temu
  ringcode 0f7813631d RING:创意组件完成 3 lat temu

+ 69 - 1
src/api/index.ts

@@ -969,7 +969,7 @@ export const getAdCampaigns = (
     page: string | number,
   }
 ): any => {
-  return axios("/ad/campaigns", { params: query });
+  return axios("/ad/adCampaigns", { params: query });
 };
 // 创建广告组
 /* 创建广告组
@@ -986,3 +986,71 @@ export const createAdCampaign = (data: {
 }) => {
   return axios.post("/ad/campaign/create", data);
 };
+
+/**
+ * 获取定向包
+ * @param
+ */
+export const getPackage = (
+  query: {
+    advertiser_id: string | number,
+    page: string | number,
+  }
+): any => {
+  return axios("/ad/package/get", { params: query });
+};
+
+/* 创建计划
+* @param null
+*/
+export const createPlan = (data: {
+  advertiser_id: any;
+  campaign_id: any;
+  ad_num: string | number;
+  is_template: string | number;
+  name: string;
+  ad_data: object;
+}) => {
+  return axios.post("/ad/campaign/create", data);
+};
+
+/**
+ * 获取计划列表
+ * @param
+ */
+export const getPlanList = (
+  query: {
+    ad_name?: string | number,
+    advertiser_name?: string | number,
+    campaign_name?: string | number,
+    status?: string | number,
+    page: string | number,
+  }
+): any => {
+  return axios("/ad/create/logs", { params: query });
+};
+
+/**
+ * 获取计划模板
+ * @param
+ */
+export const getPlanTemplate = (
+  query: {
+    page: string | number,
+  }
+): any => {
+  return axios("/ad/create/templates", { params: query });
+};
+
+/**
+ * 获取计划媒体定向包
+ * @param
+ */
+export const getMediaPackage = (
+  query: {
+    advertiser_id: any,
+    page: string | number,
+  }
+): any => {
+  return axios("/ad/create/templates", { params: query });
+};

+ 4 - 2
src/plugins/install.ts

@@ -38,7 +38,8 @@ import {
   Breadcrumb,
   Divider,
   Empty,
-  Slider
+  Slider,
+  Pagination
 } from "ant-design-vue";
 
 import VueClipboard3 from "./vue-clipboard";
@@ -90,7 +91,8 @@ const install = (app: App<Element>) => {
     .use(Breadcrumb)
     .use(Divider)
     .use(Empty)
-    .use(Slider);
+    .use(Slider)
+    .use(Pagination);
 };
 
 export default install;

+ 39 - 6
src/views/_pageOptions/table-put.ts

@@ -1135,24 +1135,24 @@ export const landingCloumn: Array<colunm> = [
 // 计划管理
 export const PlanCloumn: Array<colunm> = [
   {
-    title: "序号",
+    title: "计划名称",
     dataIndex: "id",
-    width: 100,
+    width: 200,
   },
 
   {
-    title: "落地页名称",
+    title: "所属账户",
     dataIndex: "title",
-    width: 100,
+    width: 200,
   },
   {
-    title: "简介",
+    title: "所属广告组",
     dataIndex: "name",
     width: 200,
     ellipsis: true,
   },
   {
-    title: "公众号",
+    title: "状态",
     dataIndex: "gzh_name",
     width: 100,
     slots: {
@@ -1160,6 +1160,17 @@ export const PlanCloumn: Array<colunm> = [
     },
   },
   {
+    title: "状态原因",
+    dataIndex: "name",
+    width: 200,
+    ellipsis: true,
+  },
+  {
+    title: "创建时间",
+    dataIndex: "name",
+    width: 200,
+  },
+  {
     title: "操作",
     dataIndex: "",
     width: 160,
@@ -1168,3 +1179,25 @@ export const PlanCloumn: Array<colunm> = [
     },
   },
 ];
+
+export const PlanTemplateCloumn: Array<colunm> = [
+  {
+    title: "模板名称",
+    dataIndex: "name",
+    width: 200,
+    ellipsis: true,
+  },
+  {
+    title: "创建时间",
+    dataIndex: "name",
+    width: 200,
+  },
+  {
+    title: "操作",
+    dataIndex: "",
+    width: 160,
+    slots: {
+      customRender: "setting",
+    },
+  },
+]

+ 32 - 12
src/views/put/component/check-box.vue

@@ -47,6 +47,12 @@
 import { defineComponent, reactive, toRefs, ref, watch } from "vue";
 import {} from "@/api";
 import { message } from "ant-design-vue";
+import {
+  articleClassify,
+  phoneBrand,
+  workStatus,
+  openDirection,
+} from "./plan-data";
 
 const CheckBox = defineComponent({
   props: ["compType"],
@@ -76,18 +82,32 @@ const CheckBox = defineComponent({
   },
   mounted() {
     this.type = this.$props.compType ?? 1;
-    this.list = [
-      { label: "测试1", value: 1 },
-      { label: "测试2", value: 2 },
-      { label: "测试3", value: 3 },
-      { label: "测试4", value: 4 },
-      { label: "测试5", value: 5 },
-      { label: "测试6", value: 6 },
-      { label: "测试7", value: 7 },
-      { label: "测试8", value: 8 },
-      { label: "测试9", value: 9 },
-      { label: "测试10", value: 10 },
-    ];
+    // this.list = [
+    //   { label: "测试1", value: 1 },
+    //   { label: "测试2", value: 2 },
+    //   { label: "测试3", value: 3 },
+    //   { label: "测试4", value: 4 },
+    //   { label: "测试5", value: 5 },
+    //   { label: "测试6", value: 6 },
+    //   { label: "测试7", value: 7 },
+    //   { label: "测试8", value: 8 },
+    //   { label: "测试9", value: 9 },
+    //   { label: "测试10", value: 10 },
+    // ];
+    switch (this.type) {
+      case 1:
+        this.list = articleClassify;
+        break;
+      case 2:
+        this.list = phoneBrand;
+        break;
+      case 3:
+        this.list = workStatus;
+        break;
+      case 4:
+        this.list = openDirection;
+        break;
+    }
   },
   methods: {
     // 全部清空

+ 412 - 104
src/views/put/component/creativity-content.vue

@@ -1,11 +1,19 @@
 <template>
   <div class="creativity-content">
-    <div class="program-type common-type">
-      <p style="color: gray">
+    <div
+      :class="{
+        'program-type': contentType === 'PROGRAM',
+        'common-type': true,
+      }"
+    >
+      <p style="color: gray" v-show="contentType === 'PROGRAM'">
         图片创意个数:{{
-          count.imageType1 + count.imageType2 + count.imageType2
+          count.imageType1 + count.imageType2 + count.imageType3
         }}/12 视频创意个数: {{ count.videoType1 + count.videoType2 }}/10
       </p>
+      <p style="color: gray" v-show="contentType === 'CUSTOM'">
+        创意个数:{{ creatives.length }}/10
+      </p>
       <div class="type-box">
         <div
           :class="{
@@ -23,7 +31,7 @@
           </p>
         </div>
       </div>
-      <div class="material-box">
+      <div class="material-box" v-if="contentType === 'PROGRAM'">
         <div
           class="material-container"
           v-for="(item, index) in image_list"
@@ -64,8 +72,12 @@
                 item.image_mode === 'CREATIVE_IMAGE_MODE_VIDEO'
               "
             >
+              <video :src="item.video_url"></video>
+              <i class="iconfont icon-ziyuan" v-show="item.video_url"></i>
               <div class="cover" v-show="item.video_cover">
-                <a-button size="small">视频库</a-button>
+                <a-button size="small" @click="selectMaterial(item, 'video')"
+                  >视频库</a-button
+                >
               </div>
             </div>
             <div
@@ -73,6 +85,7 @@
               @mouseenter="item.image_cover = true"
               @mouseleave="item.image_cover = false"
             >
+              <img :src="item.image_url" alt="" />
               <div class="cover" v-show="item.image_cover">
                 <p
                   style="text-align: center"
@@ -81,102 +94,225 @@
                     item.image_mode === 'CREATIVE_IMAGE_MODE_VIDEO'
                   "
                 >
-                  <a-button size="small">生成封面</a-button>
-                  <a-button size="small">图片库</a-button>
-                </p>
-                <a-button size="small" v-else>图片库</a-button>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- <div class="material-container">
-          <div class="type-item video-type video-type1">
-            <i class="iconfont icon-guanbi1"></i>
-            <div class="video-box">
-              <div class="cover">
-                <a-button size="small">视频库</a-button>
-              </div>
-            </div>
-            <div class="image-box">
-              <div class="cover">
-                <p style="text-align: center">
-                  <a-button size="small">生成封面</a-button>
-                  <a-button size="small">图片库</a-button>
-                </p>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="material-container">
-          <div class="type-item video-type video-type2">
-            <i class="iconfont icon-guanbi1"></i>
-            <div class="video-box">
-              <div class="cover">
-                <a-button size="small">视频库</a-button>
-              </div>
-            </div>
-            <div class="image-box">
-              <div class="cover">
-                <p style="text-align: center">
-                  <a-button size="small">生成封面</a-button>
-                  <a-button size="small">图片库</a-button>
+                  <a-button
+                    size="small"
+                    :disabled="!item.video_url"
+                    @click="madeCover(item)"
+                    >生成封面</a-button
+                  >
+                  <a-button size="small" @click="selectMaterial(item, 'image')"
+                    >图片库</a-button
+                  >
                 </p>
+                <a-button
+                  size="small"
+                  v-else
+                  @click="selectMaterial(item, 'image')"
+                  >图片库</a-button
+                >
               </div>
             </div>
           </div>
         </div>
-        <div class="material-container">
-          <div class="type-item image-type image-type1">
-            <i class="iconfont icon-guanbi1"></i>
-            <div class="image-box">
-              <div class="cover">
-                <a-button size="small">图片库</a-button>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="material-container">
-          <div class="type-item image-type image-type2">
-            <i class="iconfont icon-guanbi1"></i>
-            <div class="image-box">
-              <div class="cover">
-                <a-button size="small">图片库</a-button>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="material-container">
-          <div class="type-item image-type image-type3">
-            <i class="iconfont icon-guanbi1"></i>
-            <div class="image-box">
-              <div class="cover">
-                <a-button size="small">图片库</a-button>
-              </div>
-            </div>
-          </div>
-        </div> -->
         <div class="add-btn" @click="addMaterial">
           <i class="iconfont icon-jia1"></i>
           <span>添加</span>
         </div>
       </div>
+      <div class="custom-box" v-else>
+        <div class="no-pane" v-if="creatives.length === 0">
+          <p>
+            <i class="iconfont icon-tupian"></i> <br />
+            <span>点击按钮添加创意</span> <br />
+            <a-button class="add-btn" type="primary" @click="addMaterial"
+              >新增创意</a-button
+            >
+          </p>
+        </div>
+        <div class="panes" v-if="creatives.length > 0">
+          <a-button
+            class="add-btn"
+            type="primary"
+            @click="addMaterial"
+            :disabled="creatives.length === 10"
+            >新增创意</a-button
+          >
+          <a-tabs
+            v-model:activeKey="activeKey"
+            type="editable-card"
+            @edit="onEdit"
+            hide-add
+          >
+            <a-tab-pane
+              v-for="pane in creatives"
+              :key="pane.key"
+              :tab="`${pane.title.substring(0, 3)}${
+                pane.title.length > 3 ? '...' : ''
+              }`"
+              :closable="pane.closable"
+            >
+              <div class="pane-content">
+                <a-form :label-col="{ span: 3 }" :wrapper-col="{ span: 8 }">
+                  <a-form-item label="创意内容">
+                    <!-- 。。。。。。 -->
+                    <div class="material-container">
+                      <div
+                        :class="{
+                          'type-item': true,
+                          'video-type':
+                            pane.image_mode ===
+                              'CREATIVE_IMAGE_MODE_VIDEO_VERTICAL' ||
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_VIDEO',
+                          'video-type1':
+                            pane.image_mode ===
+                            'CREATIVE_IMAGE_MODE_VIDEO_VERTICAL',
+                          'video-type2':
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_VIDEO',
+                          'image-type':
+                            pane.image_mode ===
+                              'CREATIVE_IMAGE_MODE_LARGE_VERTICAL' ||
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_LARGE' ||
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_SMALL',
+                          'image-type1':
+                            pane.image_mode ===
+                            'CREATIVE_IMAGE_MODE_LARGE_VERTICAL',
+                          'image-type2':
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_LARGE',
+                          'image-type3':
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_SMALL',
+                        }"
+                      >
+                        <div
+                          @mouseenter="pane.video_cover = true"
+                          @mouseleave="pane.video_cover = false"
+                          class="video-box"
+                          v-if="
+                            pane.image_mode ===
+                              'CREATIVE_IMAGE_MODE_VIDEO_VERTICAL' ||
+                            pane.image_mode === 'CREATIVE_IMAGE_MODE_VIDEO'
+                          "
+                        >
+                          <video :src="pane.video_url"></video>
+                          <i
+                            class="iconfont icon-ziyuan"
+                            v-show="pane.video_url"
+                          ></i>
+                          <div class="cover" v-show="pane.video_cover">
+                            <a-button
+                              size="small"
+                              @click="selectMaterial(pane, 'video')"
+                              >视频库</a-button
+                            >
+                          </div>
+                        </div>
+                        <div
+                          class="image-box"
+                          @mouseenter="pane.image_cover = true"
+                          @mouseleave="pane.image_cover = false"
+                        >
+                          <img :src="pane.image_url" alt="" />
+                          <div class="cover" v-show="pane.image_cover">
+                            <p
+                              style="text-align: center"
+                              v-if="
+                                pane.image_mode ===
+                                  'CREATIVE_IMAGE_MODE_VIDEO_VERTICAL' ||
+                                pane.image_mode === 'CREATIVE_IMAGE_MODE_VIDEO'
+                              "
+                            >
+                              <a-button
+                                size="small"
+                                :disabled="!pane.video_url"
+                                @click="madeCover(pane)"
+                                >生成封面</a-button
+                              >
+                              <a-button
+                                size="small"
+                                @click="selectMaterial(pane, 'image')"
+                                >图片库</a-button
+                              >
+                            </p>
+                            <a-button
+                              size="small"
+                              v-else
+                              @click="selectMaterial(pane, 'image')"
+                              >图片库</a-button
+                            >
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <!-- 。。。。。。。。。 -->
+                  </a-form-item>
+                  <a-form-item label="创意标题"
+                    ><a-input
+                      style="width: 300px"
+                      placeholder="请输入"
+                      v-model:value="pane.title"
+                      :maxlength="30"
+                    ></a-input
+                  ></a-form-item>
+                </a-form>
+              </div>
+            </a-tab-pane>
+          </a-tabs>
+        </div>
+      </div>
     </div>
+
+    <!-- 选择素材库素材 -->
+    <a-modal
+      v-model:visible="visible"
+      :title="materialType === 'video' ? '选择视频' : '选择图片'"
+      @ok="handleOk"
+      width="720px"
+    >
+      <material-select
+        ref="materialCom"
+        :materialType="materialType"
+        v-if="visible"
+      ></material-select>
+    </a-modal>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive, toRefs, ref } from "vue";
+import { defineComponent, reactive, toRefs, ref, watch } from "vue";
 import {} from "@/api";
 import { message } from "ant-design-vue";
-
+import materialSelect from "./material-select.vue";
 const creativityContent = defineComponent({
-  setup() {
+  props: ["type"],
+  components: { materialSelect },
+  setup(props) {
+    const materialCom: any = ref(null);
     const state = reactive({
+      visible: false, // 选择素材
       contentType: "PROGRAM", // 创意方式 PROGRAM-程序化创意 CUSTOM-自定义创意
       creatives: ref<any[]>([]), // 自定义创意渲染数组
       image_list: ref<any[]>([]), // 程序化创意渲染数组
       currentType: "videoType1", // 当前素材类型 默认竖版视频
+      currentCreativity: {
+        image_mode: "CREATIVE_IMAGE_MODE_SMALL",
+        image_id: 0,
+        video_id: 0,
+        image_url: "",
+        video_url: "",
+        image_cover: false,
+        video_cover: false,
+        delete_cover: false,
+      }, // 当前操作自定义模块
+      currentImage: {
+        image_mode: "",
+        image_id: 0,
+        video_id: 0,
+        image_url: "",
+        video_url: "",
+        image_cover: false,
+        video_cover: false,
+        delete_cover: false,
+      }, // 当前操作程序化模块
+      materialType: "video", // 选取素材类型
       typeList: [
         {
           label: "竖版视频",
@@ -211,36 +347,67 @@ const creativityContent = defineComponent({
         imageType2: 0, // 大图横图
         imageType3: 0, // 小图
       }, // 素材类型计数
+      panes: [
+        // { title: "Tab 1", content: "Content of Tab 1", key: "1" },
+        // { title: "Tab 2", content: "Content of Tab 2", key: "2" },
+        // {
+        //   title: "Tab 3",
+        //   content: "Content of Tab 3",
+        //   key: "3",
+        //   closable: false,
+        // },
+      ],
+      activeKey: 1,
+    });
+    watch(props.type, (newVal, oldVal) => {
+      if (newVal.creative_material_mode !== oldVal.creative_material_mode) {
+        state.contentType =
+          newVal.creative_material_mode === "CUSTOM" ? "CUSTOM" : "PROGRAM";
+        state.creatives = [];
+        state.image_list = [];
+        state.count = {
+          videoType1: 0, // 竖版视频
+          videoType2: 0, // 横板视频
+          imageType1: 0, // 大图竖图
+          imageType2: 0, // 大图横图
+          imageType3: 0, // 小图
+        };
+      }
     });
-    return { ...toRefs(state) };
+    return { ...toRefs(state), materialCom };
+  },
+  mounted() {
+    this.typeChange(this.$props.type);
   },
-  mounted() {},
   methods: {
-    // 程序化-添加创意
+    typeChange(val: any) {
+      this.contentType = val === "CUSTOM" ? "CUSTOM" : "PROGRAM";
+    },
+    // 程序化-添加创意 -自定义
     addMaterial() {
       console.log("添加创意");
+      let mode = "";
+      this.typeList.forEach((item) => {
+        if (item.value === this.currentType) mode = item.mode;
+      });
+      switch (mode) {
+        case "CREATIVE_IMAGE_MODE_VIDEO_VERTICAL":
+          this.count.videoType1++;
+          break;
+        case "CREATIVE_IMAGE_MODE_VIDEO":
+          this.count.videoType2++;
+          break;
+        case "CREATIVE_IMAGE_MODE_LARGE_VERTICAL":
+          this.count.imageType1++;
+          break;
+        case "CREATIVE_IMAGE_MODE_LARGE":
+          this.count.imageType2++;
+          break;
+        case "CREATIVE_IMAGE_MODE_SMALL":
+          this.count.imageType3++;
+          break;
+      }
       if (this.contentType === "PROGRAM") {
-        let mode = "";
-        this.typeList.forEach((item) => {
-          if (item.value === this.currentType) mode = item.mode;
-        });
-        switch (mode) {
-          case "CREATIVE_IMAGE_MODE_VIDEO_VERTICAL":
-            this.count.videoType1++;
-            break;
-          case "CREATIVE_IMAGE_MODE_VIDEO":
-            this.count.videoType2++;
-            break;
-          case "CREATIVE_IMAGE_MODE_LARGE_VERTICAL":
-            this.count.imageType1++;
-            break;
-          case "CREATIVE_IMAGE_MODE_LARGE":
-            this.count.imageType2++;
-            break;
-          case "CREATIVE_IMAGE_MODE_SMALL":
-            this.count.imageType3++;
-            break;
-        }
         console.log(this.count.videoType1);
         this.image_list.push({
           image_mode: mode,
@@ -252,12 +419,45 @@ const creativityContent = defineComponent({
           video_cover: false,
           delete_cover: false,
         });
+      } else {
+        console.log("添加自定义");
+        let key =
+          this.creatives.length === 0
+            ? 1
+            : this.creatives[this.creatives.length - 1].key + 1;
+        this.creatives.push({
+          image_mode: mode, // 素材类型
+          title: "创意" + key, // 创意标题
+          image_id: 0, // 图片id(视频封面id)
+          video_id: 0, // 视频id
+          image_url: "",
+          video_url: "",
+          image_cover: false,
+          video_cover: false,
+          key,
+        });
+        this.currentCreativity = this.creatives[this.creatives.length - 1];
+        this.activeKey = key;
+      }
+    },
+    // 程序化-选取视频/封面
+    selectMaterial(item: any, type: any) {
+      if (this.contentType === "PROGRAM") {
+        this.currentImage = item;
+      } else {
+        this.currentCreativity = item;
       }
+      this.materialType = type;
+      this.visible = true;
     },
     // 程序化-删除创意
     deleteImageListItem(item: any, index: any) {
       this.image_list.splice(index, 1);
-      switch (item.image_mode) {
+      this.countCut(item.image_mode);
+    },
+    // count-cut
+    countCut(mode: any) {
+      switch (mode) {
         case "CREATIVE_IMAGE_MODE_VIDEO_VERTICAL":
           this.count.videoType1--;
           break;
@@ -275,6 +475,67 @@ const creativityContent = defineComponent({
           break;
       }
     },
+    // 生成封面
+    madeCover(val: any) {
+      if (this.contentType === "PROGRAM") {
+        this.currentImage.image_url =
+          this.currentImage.video_url +
+          "?x-oss-process=video/snapshot,t_0,f_jpg,m_fast";
+      } else {
+        this.currentCreativity.image_url =
+          this.currentCreativity.video_url +
+          "?x-oss-process=video/snapshot,t_0,f_jpg,m_fast";
+      }
+      message.success("生成封面成功");
+    },
+    // 自定义-标签操作
+    onEdit(targetKey: string | MouseEvent, action: string) {
+      if (action === "add") {
+        this.addMaterial();
+      } else {
+        this.deleteCreativesItem(targetKey);
+      }
+    },
+    // 自定义-删除创意
+    deleteCreativesItem(val: any) {
+      console.log("del", val);
+      this.creatives.forEach((item, index) => {
+        if (item.key === val) {
+          this.creatives.splice(index, 1);
+          this.countCut(item.image_mode);
+        }
+      });
+    },
+    // 确定素材选择
+    handleOk() {
+      // 程序化选取素材
+      let data = this.materialCom.backMaterial();
+      if (data.flag) {
+        console.log("选中的素材", data.material);
+        if (this.contentType === "PROGRAM") {
+          if (this.materialType === "video") {
+            this.currentImage.video_id = data.material.id;
+            this.currentImage.video_url = data.material.url;
+          } else {
+            this.currentImage.image_id = data.material.id;
+            this.currentImage.image_url = data.material.url;
+          }
+          console.log(this.image_list);
+        } else {
+          if (this.materialType === "video") {
+            this.currentCreativity.video_id = data.material.id;
+            this.currentCreativity.video_url = data.material.url;
+          } else {
+            this.currentCreativity.image_id = data.material.id;
+            this.currentCreativity.image_url = data.material.url;
+          }
+          console.log(this.creatives);
+        }
+        this.visible = false;
+      } else {
+        return message.error("请先选择素材");
+      }
+    },
   },
 });
 // 要传的
@@ -342,7 +603,8 @@ export default creativityContent;
       border: 2px solid rgb(83, 115, 255);
     }
   }
-  .material-box {
+  .material-box,
+  .pane-content {
     overflow: hidden;
     margin-top: 20px;
     padding-bottom: 10px;
@@ -392,6 +654,16 @@ export default creativityContent;
           display: flex;
           align-items: center;
           justify-content: center;
+          img,
+          video {
+            max-width: 100%;
+            max-height: 100%;
+          }
+          .icon-ziyuan {
+            position: absolute;
+            font-size: 20px;
+            z-index: 100;
+          }
         }
         .cover {
           width: 100%;
@@ -403,6 +675,7 @@ export default creativityContent;
           align-items: center;
           justify-content: center;
           background: rgba(0, 0, 0, 0.301);
+          z-index: 1000;
           p {
             line-height: 28px;
           }
@@ -478,5 +751,40 @@ export default creativityContent;
       margin-right: 0;
     }
   }
+  .custom-box {
+    border: 2px solid rgb(240, 240, 240);
+    margin-top: 10px;
+    position: relative;
+    padding-bottom: 10px;
+    .no-pane {
+      width: 100%;
+      height: 300px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      p {
+        text-align: center;
+        line-height: 40px;
+        i {
+          font-size: 40px;
+          color: gray;
+        }
+        span {
+          color: gray;
+        }
+      }
+    }
+    .panes {
+      .add-btn {
+        position: absolute;
+        top: 4px;
+        right: 4px;
+        z-index: 100;
+      }
+      .pane-content {
+        padding: 10px;
+      }
+    }
+  }
 }
 </style>

+ 7 - 2
src/views/put/component/direction-exclusion.vue

@@ -110,9 +110,9 @@
 
 <script lang="ts">
 import { defineComponent, reactive, toRefs, ref } from "vue";
-import {} from "@/api";
+import { getMediaPackage } from "@/api";
 import { message } from "ant-design-vue";
-
+// 1691995913023502
 const DirectionExclusion = defineComponent({
   props: ["compType"],
   setup() {
@@ -177,8 +177,13 @@ const DirectionExclusion = defineComponent({
   mounted() {
     this.type = this.$props.compType === "media" ? "media" : "crowd";
     this.list = JSON.parse(JSON.stringify(this.originList));
+    this.getList();
   },
   methods: {
+    async getList() {
+      let { data } = await getMediaPackage({ advertiser_id: this.$route.query.advertiser_id, page: 999 });
+      console.log("定向包数据", data);
+    },
     // 添加定向
     addDirection(val: any) {
       this.directionList.itemList.push(val);

+ 197 - 0
src/views/put/component/material-select.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="material-select">
+    <a-tabs
+      v-model:activeKey="search.is_public"
+      @change="onSearch"
+      size="small"
+    >
+      <a-tab-pane :key="1" tab="公共素材"></a-tab-pane>
+      <a-tab-pane :key="0" tab="我的上传"></a-tab-pane>
+    </a-tabs>
+    <a-input-search
+      v-model:value="search.name"
+      :placeholder="type === 'video' ? '请输入视频名称' : '请输入图片名称'"
+      style="width: 200px; margin-right: 20px"
+      @search="onSearch"
+    />
+    <a-input-search
+      v-model:value="search.bookname"
+      placeholder="请输入推广书籍"
+      style="width: 200px; margin-right: 20px"
+      @search="onSearch"
+    />
+    <a-input-search
+      v-model:value="search.demander"
+      placeholder="请输入需求方"
+      style="width: 200px; margin-right: 20px"
+      @search="onSearch"
+    />
+    <a-spin :spinning="loading">
+      <div class="content-box">
+        <div
+          :class="{ 'item-box': true, checked: item.checked }"
+          v-for="item in list"
+          :key="item.id"
+          @click.stop="checkedChange(item)"
+        >
+          <a-checkbox
+            class="checkbox"
+            v-model:checked="item.checked"
+            @change="checkedChange(item)"
+          ></a-checkbox>
+          <div class="play-box">
+            <div class="player">
+              <video v-if="item.video_type" :src="item.url"></video>
+              <img v-if="item.image_type" :src="item.url" alt="" />
+            </div>
+          </div>
+          <div class="title-box">{{ item.name }}</div>
+        </div>
+      </div>
+    </a-spin>
+    <a-pagination
+      v-model:current="meta.current_page"
+      :total="meta.total"
+      show-less-items
+      style="float: right; margin: 10px 10px 0 0"
+      size="small"
+      @change="onSearch"
+    />
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs, ref } from "vue";
+import { getVideoList, getImageList } from "@/api";
+import { message } from "ant-design-vue";
+import usePagination from "@/hooks/usePagination";
+
+const materialSelect = defineComponent({
+  props: ["materialType"],
+  setup(props) {
+    let { meta, tablePageOptions } = usePagination();
+    const state = reactive({
+      checkItem: {
+        id: 0,
+      },
+      type: "video",
+      loading: false,
+      search: {
+        is_public: 1, // 1公共素材 0个人素材
+        name: "", // 视频/图片名称
+        bookname: "", // 推广书籍
+        demander: "", // 需求方
+      },
+      list: ref<any[]>([]),
+    });
+    const onSearch = async (page?: any) => {
+      console.log("sss", page);
+      state.loading = true;
+      let param: any = {
+        ...state.search,
+      };
+      delete param.name;
+      if (state.type === "video") {
+        param.video_name = state.search.name;
+      } else {
+        param.image_name = state.search.name;
+      }
+      let api = state.type === "video" ? getVideoList : getImageList;
+      let { data } = await api({
+        page: page ? page : 1,
+        ...param,
+      });
+      console.log("素材库数据", data);
+      state.loading = false;
+      state.list = data.list;
+      if (state.list.length > 0) {
+        state.list.forEach((item) => {
+          item.checked = false;
+        });
+      }
+      meta.value = data.meta;
+    };
+    return { ...toRefs(state), onSearch, meta };
+  },
+  mounted() {
+    this.type = this.$props.materialType;
+    console.log("type", this.type);
+    console.log("meta", this.meta);
+    this.onSearch();
+  },
+  methods: {
+    // 切换选中项
+    checkedChange(val: any) {
+      this.list.forEach((item) => {
+        item.checked = false;
+        if (item.id === val.id) {
+          item.checked = true;
+        }
+      });
+      this.checkItem = val;
+      console.log(this.checkItem);
+    },
+    // 返回选中的素材
+    backMaterial() {
+      if (!this.checkItem.id) {
+        return { flag: false, material: {} };
+      } else {
+        return { flag: true, material: this.checkItem };
+      }
+    },
+  },
+});
+
+export default materialSelect;
+</script>
+<style lang="scss" scoped>
+.material-select {
+  overflow: hidden;
+  .content-box {
+    min-height: 100px;
+    margin-top: 10px;
+    .item-box {
+      width: 200px;
+      height: 160px;
+      border: 1px solid rgb(168, 166, 166);
+      float: left;
+      margin-right: 20px;
+      margin-bottom: 10px;
+      cursor: pointer;
+      position: relative;
+      .checkbox {
+        position: absolute;
+        top: 10px;
+        right: 10px;
+      }
+      .play-box {
+        width: 100%;
+        height: 130px;
+        background: rgb(128, 128, 128);
+        .player {
+          width: 80px;
+          margin: 0 auto;
+          height: 100%;
+          background: rgb(191, 193, 194);
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          img,
+          video {
+            max-width: 100%;
+            max-height: 100%;
+          }
+        }
+      }
+      .title-box {
+        width: 100%;
+        height: 38px;
+        padding-left: 10px;
+      }
+    }
+    .checked {
+      border: 2px solid rgb(4, 77, 187);
+    }
+  }
+}
+</style>

+ 333 - 1
src/views/put/component/plan-data.ts

@@ -21,4 +21,336 @@ export const PriceSlider = {
     },
     label: "不限",
   },
-}
+}
+// 文章分类
+export const articleClassify = [{
+  label: '娱乐',
+  value: 'ENTERTAINMENT',
+}, {
+  label: '社会',
+  value: 'SOCIETY',
+}, {
+  label: '汽车',
+  value: 'CARS',
+}, {
+  label: '国际',
+  value: 'INTERNATIONAL',
+}, {
+  label: '历史',
+  value: 'HISTORY',
+}, {
+  label: '体育',
+  value: 'SPORTS',
+}, {
+  label: '健康',
+  value: 'HEALTH',
+}, {
+  label: '军事',
+  value: 'MILITARY',
+}, {
+  label: '情感',
+  value: 'EMOTION',
+}, {
+  label: '时尚',
+  value: 'FASHION',
+}, {
+  label: '育儿',
+  value: 'PARENTING',
+}, {
+  label: '财经',
+  value: 'FINANCE',
+}, {
+  label: '搞笑',
+  value: 'AMUSEMENT',
+}, {
+  label: '数码',
+  value: 'DIGITAL',
+}, {
+  label: '探索',
+  value: 'EXPLORE',
+}, {
+  label: '旅游',
+  value: 'TRAVEL',
+}, {
+  label: '星座',
+  value: 'CONSTELLATION',
+}, {
+  label: '故事',
+  value: 'STORIES',
+}, {
+  label: '科技',
+  value: 'TECHNOLOGY',
+}, {
+  label: '美食',
+  value: 'GOURMET',
+}, {
+  label: '文化',
+  value: 'CULTURE',
+}, {
+  label: '家居',
+  value: 'HOME',
+}, {
+  label: '电影',
+  value: 'MOVIE',
+}, {
+  label: '宠物',
+  value: 'PETS',
+}, {
+  label: '游戏',
+  value: 'GAMES',
+}, {
+  label: '瘦身',
+  value: 'WEIGHT_LOSING',
+}, {
+  label: '奇葩',
+  value: 'FREAK',
+}, {
+  label: '教育',
+  value: 'EDUCATION',
+}, {
+  label: '房产',
+  value: 'ESTATE',
+}, {
+  label: '科学',
+  value: 'SCIENCE',
+}, {
+  label: '摄影',
+  value: 'PHOTOGRAPHY',
+}, {
+  label: '养生',
+  value: 'REGIMEN',
+}, {
+  label: '美文',
+  value: 'ESSAY',
+}, {
+  label: '收藏',
+  value: 'COLLECTION',
+}, {
+  label: '动漫',
+  value: 'ANIMATION',
+}, {
+  label: '漫画',
+  value: 'COMICS',
+}, {
+  label: '小窍门',
+  value: 'TIPS',
+}, {
+  label: '设计',
+  value: 'DESIGN',
+}, {
+  label: '本地',
+  value: 'LOCAL',
+}, {
+  label: '法制',
+  value: 'LAWS',
+}, {
+  label: '政务',
+  value: 'GOVERNMENT',
+}, {
+  label: '商业',
+  value: 'BUSINESS',
+}, {
+  label: '职场',
+  value: 'WORKPLACE',
+}, {
+  label: '辟谣',
+  value: 'RUMOR_CRACKER',
+}, {
+  label: '毕业生',
+  value: 'GRADUATES',
+}]
+
+// 手机品牌
+export const phoneBrand = [{
+  label: '荣耀',
+  value: 'HONOR'
+}, {
+  label: '苹果',
+  value: 'APPLE'
+}, {
+  label: '华为',
+  value: 'HUAWEI'
+}, {
+  label: '小米',
+  value: 'XIAOMI'
+}, {
+  label: '三星',
+  value: 'SAMSUNG'
+}, {
+  label: 'OPPO',
+  value: 'OPPO'
+}, {
+  label: 'VIVO',
+  value: 'VIVO'
+}, {
+  label: '魅族',
+  value: 'MEIZU'
+}, {
+  label: '金立',
+  value: 'GIONEE'
+}, {
+  label: '酷派',
+  value: 'COOLPAD'
+}, {
+  label: '联想',
+  value: 'LENOVO'
+}, {
+  label: '乐视',
+  value: 'LETV'
+}, {
+  label: '中兴',
+  value: 'ZTE'
+}, {
+  label: '中国移动',
+  value: 'CHINAMOBILE'
+}, {
+  label: 'HTC',
+  value: 'HTC'
+}, {
+  label: '小辣椒',
+  value: 'PEPPER'
+}, {
+  label: '努比亚',
+  value: 'NUBIA'
+}, {
+  label: '海信',
+  value: 'HISENSE'
+}, {
+  label: '奇酷',
+  value: 'QIKU'
+}, {
+  label: 'TCL',
+  value: 'TCL'
+}, {
+  label: '索尼',
+  value: 'SONY'
+}, {
+  label: '锤子手机',
+  value: 'SMARTISAN'
+}, {
+  label: '360手机',
+  value: '360'
+}, {
+  label: '一加手机',
+  value: 'ONEPLUS'
+}, {
+  label: 'LG',
+  value: 'LG'
+}, {
+  label: '摩托罗拉',
+  value: 'MOTO'
+}, {
+  label: '诺基亚',
+  value: 'NOKIA'
+}, {
+  label: '谷歌',
+  value: 'GOOGLE'
+}]
+
+// 职业状态
+export const workStatus = [{
+  label: '大学生',
+  value: 'COLLEGE_STUDENT'
+}, {
+  label: '教师',
+  value: 'TEACHER'
+}, {
+  label: 'IT',
+  value: 'IT'
+}, {
+  label: '公务员',
+  value: 'CIVIL_SERVANTS'
+}, {
+  label: '金融',
+  value: 'FINANCIAL'
+}, {
+  label: '医务人员',
+  value: 'MEDICAL_STAFF'
+},]
+
+// 可开放定向
+export const openDirection = [{
+  label: '地域',
+  value: 'REGION'
+}, {
+  label: '性别',
+  value: 'GENDER'
+}, {
+  label: '年龄',
+  value: 'AGE'
+}, {
+  label: '兴趣分类',
+  value: 'AD_TAG'
+}, {
+  label: '兴趣关键词',
+  value: 'INTEREST_TAG'
+}, {
+  label: '自定人群-定向',
+  value: 'CUSTOM_AUDIENCE'
+}, {
+  label: '行为兴趣',
+  value: 'INTEREST_ACTION'
+},]
+
+// part2-投放媒体选项
+export const LaunchMedia = [{
+  label: '头条信息流',
+  value: 'INVENTORY_FEED'
+}, {
+  label: '西瓜信息流',
+  value: 'INVENTORY_VIDEO_FEED'
+}, {
+  label: '火山信息流',
+  value: 'INVENTORY_HOTSOON_FEED'
+}, {
+  label: '抖音信息流',
+  value: 'INVENTORY_AWEME_FEED'
+}, {
+  label: '穿山甲',
+  value: 'INVENTORY_UNION_SLOT'
+}, {
+  label: 'ohayoo精品游戏',
+  value: 'UNION_BOUTIQUE_GAME'
+}, {
+  label: '穿山甲开屏广告',
+  value: 'INVENTORY_UNION_SPLASH_SLOT'
+}, {
+  label: '搜索广告——抖音位',
+  value: 'INVENTORY_AWEME_SEARCH'
+}, {
+  label: '搜索广告——头条位',
+  value: 'INVENTORY_SEARCH'
+}, {
+  label: '通投智选',
+  value: 'INVENTORY_UNIVERSAL'
+}, {
+  label: '轻颜相机',
+  value: 'INVENTORY_BEAUTY'
+}, {
+  label: '皮皮虾',
+  value: 'INVENTORY_PIPIXIA'
+}, {
+  label: '懂车帝',
+  value: 'INVENTORY_AUTOMOBILE'
+}, {
+  label: '好好学习',
+  value: 'INVENTORY_STUDY'
+}, {
+  label: 'faceu',
+  value: 'INVENTORY_FACE_U'
+}, {
+  label: '番茄小说',
+  value: 'INVENTORY_TOMATO_NOVEL'
+}]
+
+// part2-投放场景选项
+export const LaunchScene = [{
+  label: '沉浸式竖版视频场景',
+  value: 'VIDEO_SCENE'
+}, {
+  label: '信息流场景',
+  value: 'FEED_SCENE'
+}, {
+  label: '视频后贴和尾帧场景',
+  value: 'TAIL_SCENE'
+},]

+ 221 - 0
src/views/put/component/push-card.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="push-card">
+    <a-form :label-col="{ span: 3 }" :wrapper-col="{ span: 8 }">
+      <a-form-item label="卡片主图">
+        <div class="add-btn" @click="addMaterial">
+          <!-- <i class="iconfont icon-jia1"></i> -->
+          <img
+            v-if="card.product_image_url"
+            :src="card.product_image_url"
+            alt=""
+          />
+          <a-button size="small" v-else>图片库</a-button>
+        </div>
+      </a-form-item>
+      <a-form-item label="卡片标题">
+        <a-input
+          v-model:value="card.product_description"
+          style="width: 300px"
+          placeholder="请输入"
+          :maxlength="7"
+        ></a-input>
+      </a-form-item>
+      <a-form-item label="推广卖点">
+        <p
+          v-for="(item, index) in points_list"
+          :key="index"
+          style="width: 600px"
+        >
+          <a-input
+            placeholder="请输入"
+            :maxlength="9"
+            v-model:value="item.title"
+            style="width: 300px; margin-right: 10px"
+          ></a-input>
+          <span v-show="index === 0"
+            ><a-button
+              :disabled="points_list.length === 10"
+              style="margin-right: 10px"
+              type="primary"
+              @click="addTitle"
+              >添加推广卖点</a-button
+            >
+            <span style="color: gray"
+              >推广卖点个数:{{ points_list.length }}/10</span
+            ></span
+          >
+          <i
+            v-show="index !== 0"
+            class="iconfont icon-shanchu"
+            style="color: gray; cursor: pointer"
+            @click="deleteTitle(index)"
+          ></i>
+        </p>
+      </a-form-item>
+      <a-form-item label="行动号召">
+        <a-radio-group v-model:value="card.enable_personal_action">
+          <a-radio-button :value="true">开启智能优选</a-radio-button>
+          <a-radio-button :value="false">关闭智能优选</a-radio-button>
+        </a-radio-group>
+        <span
+          v-show="card.enable_personal_action"
+          style="
+            display: inline-block;
+            width: 400px;
+            font-size: 12px;
+            color: gray;
+            line-height: 16px;
+            padding-left: 10px;
+          "
+          >系统将基于字节领先的技术能力和实时的投放数据,在每次抖音广告显示
+          时,挑选最有利于转化的文案进行显示,帮助您提升转化能力。</span
+        >
+      </a-form-item>
+      <a-form-item label="文案">
+        <a-select
+          placeholder="请选择"
+          v-model:value="card.text"
+          style="width: 300px"
+        >
+          <a-select-option value="下一章">下一章</a-select-option>
+          <a-select-option value="去阅读">去阅读</a-select-option>
+          <a-select-option value="更多精彩">更多精彩</a-select-option>
+          <a-select-option value="了解更多">了解更多</a-select-option>
+          <a-select-option value="阅读全文">阅读全文</a-select-option>
+          <a-select-option value="查看详情">查看详情</a-select-option>
+        </a-select>
+        <span
+          v-show="card.enable_personal_action"
+          style="
+            display: inline-block;
+            width: 200px;
+            font-size: 12px;
+            color: gray;
+            line-height: 16px;
+            padding-left: 10px;
+          "
+          >在无法使用智能优选的流量场景下,将 使用上方文案展示给用户。</span
+        >
+        <span
+          v-show="!card.enable_personal_action"
+          style="
+            display: inline-block;
+            width: 200px;
+            font-size: 12px;
+            color: gray;
+            line-height: 16px;
+            padding-left: 10px;
+          "
+          >关闭智能优选后,将对所有用户展示您 选择的文案</span
+        >
+      </a-form-item>
+    </a-form>
+    <!-- 选择素材库素材 -->
+    <a-modal
+      v-model:visible="visible"
+      title="选择图片"
+      @ok="handleOk"
+      width="720px"
+    >
+      <material-select
+        ref="materialCom"
+        :materialType="'image'"
+        v-if="visible"
+      ></material-select>
+    </a-modal>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs, ref } from "vue";
+import {} from "@/api";
+import { message } from "ant-design-vue";
+import materialSelect from "./material-select.vue";
+
+const PushCard = defineComponent({
+  components: { materialSelect },
+  setup() {
+    const materialCom: any = ref(null);
+    const state = reactive({
+      visible: false,
+      card: {
+        product_image_id: 0, // 主图id
+        product_image_url: "", // 主图url 渲染用
+        product_description: "", // 卡片标题
+        product_selling_points: ref<any[]>([]), // 推广卖点数组
+        enable_personal_action: true, // 行动号召(智能优选
+        text: "查看详情", // 文案
+      },
+      points_list: [{ title: "" }],
+    });
+    return { ...toRefs(state), materialCom };
+  },
+  mounted() {},
+  methods: {
+    // 选取主图
+    addMaterial() {
+      this.visible = true;
+    },
+    // 确认选取主图
+    handleOk() {
+      // 程序化选取素材
+      let data = this.materialCom.backMaterial();
+      if (data.flag) {
+        this.card.product_image_id = data.material.id;
+        this.card.product_image_url = data.material.url;
+        this.visible = false;
+      } else {
+        return message.error("请先选择图片");
+      }
+    },
+    // 新增推广卖点
+    addTitle() {
+      this.points_list.push({ title: "" });
+    },
+    // 程序化-删除卖点
+    deleteTitle(index: any) {
+      this.points_list.splice(index, 1);
+    },
+  },
+});
+
+export default PushCard;
+</script>
+<style lang="scss" scoped>
+.push-card {
+  width: 800px;
+  min-height: 200px;
+  border: 2px solid rgb(230, 230, 230);
+  border-radius: 4px;
+  padding: 10px;
+  .add-btn {
+    width: 100px;
+    height: 100px;
+    background: rgb(243, 243, 243);
+    border: 2px dashed rgb(200, 200, 200);
+    border-radius: 4px;
+    text-align: center;
+    float: left;
+    cursor: pointer;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    img {
+      max-height: 100%;
+      max-width: 100%;
+    }
+    // i {
+    //   font-size: 20px;
+    //   color: gray;
+    //   display: block;
+    //   line-height: 20px;
+    //   margin-top: 36px;
+    // }
+    // span {
+    //   display: block;
+    //   font-size: 13px;
+    //   color: gray;
+    // }
+  }
+}
+</style>

+ 15 - 4
src/views/put/plan-create-index.vue

@@ -22,7 +22,12 @@
             />
           </a-steps>
         </div>
-        <router-view></router-view>
+        <router-view v-slot="{ Component }">
+          <keep-alive>
+            <component :is="Component" />
+          </keep-alive>
+        </router-view>
+        <!-- <router-view></router-view> -->
         <div class="steps-action">
           <a-button v-if="current === 0" @click="prev">取消</a-button>
           <a-button v-if="current > 0" @click="prev">上一步</a-button>
@@ -113,7 +118,7 @@ const PlanCreate = defineComponent({
     Bus.$on("stepOneBack", (val: any) => {
       // 第一步校验完成 携带account_id跳转创建广告组页面
       this.$router.push(
-        "/put/plan-create/" + this.routerList[1] + "?account_id=" + val
+        "/put/plan-create/" + this.routerList[1] + "?advertiser_id=" + val
       );
       console.log("STEP1+1");
       if (this.current === 0) this.current++;
@@ -122,7 +127,12 @@ const PlanCreate = defineComponent({
     Bus.$on("stepTwoBack", (val: any) => {
       // 第一步校验完成 携带account_id跳转创建广告组页面
       this.$router.push(
-        "/put/plan-create/" + this.routerList[2] + "?campaign_id=" + val
+        "/put/plan-create/" +
+          this.routerList[2] +
+          "?campaign_id=" +
+          val.campaign_id +
+          "&advertiser_id=" +
+          val.advertiser_id
       );
       console.log("STEP2+1");
       if (this.current === 1) this.current++;
@@ -168,7 +178,8 @@ const PlanCreate = defineComponent({
       console.log("减啊");
       this.current--;
       window.localStorage.setItem("current-step", String(this.current));
-      this.$router.push("/put/plan-create/" + this.routerList[this.current]);
+      // this.$router.push("/put/plan-create/" + this.routerList[this.current]);
+      this.$router.go(-1);
     },
     setCurrent() {
       window.localStorage.setItem("current-step", String(this.current));

+ 200 - 3
src/views/put/plan-create/creativity-add.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="creativity-add">
-    <div class="part-box">
+    <div class="part-box" style="padding-bottom: 20px">
       <h3 class="title">添加创意</h3>
       <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
         <a-form-item label="创意方式">
@@ -10,12 +10,129 @@
           </a-radio-group>
         </a-form-item>
         <a-form-item label="创意内容">
-          <creativity-content></creativity-content>
+          <creativity-content :type="creativityAdd"></creativity-content>
+        </a-form-item>
+        <a-form-item
+          label="创意标题"
+          v-show="creativityAdd.creative_material_mode === 'STATIC_ASSEMBLE'"
+        >
+          <p
+            v-for="(item, index) in creativityAdd.title_list"
+            :key="index"
+            style="width: 700px"
+          >
+            <a-input
+              placeholder="请输入"
+              :maxlength="30"
+              v-model:value="item.title"
+              style="width: 300px; margin-right: 10px"
+            ></a-input>
+            <span v-show="index === 0"
+              ><a-button
+                :disabled="creativityAdd.title_list.length === 10"
+                style="margin-right: 10px"
+                type="primary"
+                @click="addTitle"
+                >添加创意标题</a-button
+              >
+              <span style="color: gray"
+                >创意标题个数:{{ creativityAdd.title_list.length }}/10</span
+              ></span
+            >
+            <i
+              v-show="index !== 0"
+              class="iconfont icon-shanchu"
+              style="color: gray; cursor: pointer"
+              @click="deleteTitle(index)"
+            ></i>
+          </p>
+        </a-form-item>
+        <a-form-item label="推广卡片">
+          <push-card></push-card>
+        </a-form-item>
+        <a-form-item label="来源">
+          <a-input
+            v-model:value="creativityAdd.source"
+            :maxlength="10"
+            placeholder="请输入"
+            style="width: 300px"
+          ></a-input>
+        </a-form-item>
+        <a-form-item label="自动生成视频素材">
+          <a-switch
+            checked-children="开"
+            un-checked-children="关"
+            v-model:checked="creativityAdd.creative_auto_generate_switch"
+          />
+        </a-form-item>
+        <a-form-item label="广告评论">
+          <a-switch
+            checked-children="开"
+            un-checked-children="关"
+            :disabled="true"
+            v-model:checked="creativityAdd.is_comment_disable"
+          />
+        </a-form-item>
+        <a-form-item label="客户端视频下载">
+          <a-switch
+            checked-children="开"
+            un-checked-children="关"
+            v-model:checked="creativityAdd.ad_download_status"
+          />
+        </a-form-item>
+        <a-form-item label="创意展现">
+          <a-radio-group v-model:value="creativityAdd.creative_display_mode">
+            <a-radio value="CREATIVE_DISPLAY_MODE_CTR">优选模式</a-radio>
+            <a-radio value="CREATIVE_DISPLAY_MODE_RANDOM">轮播模式</a-radio>
+          </a-radio-group>
         </a-form-item>
       </a-form>
     </div>
     <div class="part-box">
       <h3 class="title">创意分类</h3>
+      <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
+        <a-form-item label="创意分类">
+          <a-cascader
+            v-model:value="kind"
+            :options="[]"
+            placeholder="请选择"
+            style="width: 500px"
+          />
+        </a-form-item>
+        <a-form-item label="创意标签">
+          <a-input
+            addon-after="添加(回车)"
+            v-model:value="label"
+            style="width: 500px"
+            @keyup.enter="addKeyWords"
+            :maxlength="10"
+            :disabled="creativityAdd.ad_keywords.length === 20"
+          />
+          <div class="label-box">
+            <div class="title-box">
+              <h3>
+                已添加<span>({{ creativityAdd.ad_keywords.length }}/20)</span>
+              </h3>
+              <a-button type="link" @click="creativityAdd.ad_keywords=[]"
+                >全部清空</a-button
+              >
+            </div>
+            <div class="label-container">
+              <p
+                class="label-item"
+                v-for="(item, index) in creativityAdd.ad_keywords"
+                :key="index"
+              >
+                <span :title="item">{{ item }}</span>
+                <i
+                  class="iconfont icon-guanbi"
+                  @click="creativityAdd.ad_keywords.splice(index, 1)"
+                ></i>
+              </p>
+            </div>
+          </div>
+        </a-form-item>
+      </a-form>
     </div>
   </div>
 </template>
@@ -26,11 +143,14 @@ import {} from "@/api";
 import Bus from "@/utils/bus";
 import { message } from "ant-design-vue";
 import creativityContent from "../component/creativity-content.vue";
+import PushCard from "../component/push-card.vue";
 
 const CreativityAdd = defineComponent({
-  components: { creativityContent },
+  components: { creativityContent, PushCard },
   setup() {
     const state = reactive({
+      label: "", // 创意标签输入框
+      kind: ref<any[]>([]), // 创意分类
       creativityAdd: {
         creative_material_mode: "STATIC_ASSEMBLE", // 创意方式,当值为"STATIC_ASSEMBLE"表示程序化创意,其他情况不传字段
         creatives: ref<any[]>([]), //自定义素材信息, 最多支持10个创意。首选投放位置和创意类型决定素材规格。当为程序化创意时,该字段不填数据,值为[]
@@ -39,18 +159,32 @@ const CreativityAdd = defineComponent({
             title: "", // 标题
           },
         ], // 程序化创意时传 自定义不传
+        // 卡片以下
+        source: "",
+        creative_display_mode: "CREATIVE_DISPLAY_MODE_CTR", // 创意展现方式
+        creative_auto_generate_switch: false, // 自动生成素材0-关闭 1-开启
+        is_comment_disable: true, // 广告评论0-关闭 1-开启
+        ad_download_status: true, // 客户端下载视频0-关闭 1-开启
+        // 创意分类
+        third_industry_id: 0, // 创意分类 三级级联选择器
+        ad_keywords: ref<any[]>([]), // 创意标签string[]
       },
       creativityClassify: {},
     });
     return { ...toRefs(state), labelCol: { span: 3 }, wrapperCol: { span: 8 } };
   },
   mounted() {
+    this.getClassify();
     Bus.$on("stepFourCheck", (val?: any) => {
       console.log("Step4Check");
       Bus.$emit("stepFourBack");
     });
   },
   methods: {
+    // 获取创意分类
+    getClassify() {
+      console.log("获取分类");
+    },
     // 提交前数据处理
     beforeCommit() {
       let data: any = { ...this.creativityAdd };
@@ -64,6 +198,24 @@ const CreativityAdd = defineComponent({
       if (!creativityOrigin.creative_material_mode)
         creativityOrigin.creative_material_mode = "CUSTOM";
     },
+    // 程序化-新增标题
+    addTitle() {
+      this.creativityAdd.title_list.push({ title: "" });
+    },
+    // 程序化-删除标题
+    deleteTitle(index: any) {
+      this.creativityAdd.title_list.splice(index, 1);
+    },
+    // 添加创意标签
+    addKeyWords() {
+      if (!this.label) return message.error("标签内容不能为空");
+      if (this.label.length > 10)
+        return message.error("标签长度不能超过10个字符");
+      if (this.creativityAdd.ad_keywords.includes(this.label))
+        return message.error("标签已存在");
+      this.creativityAdd.ad_keywords.push(this.label);
+      this.label = "";
+    },
   },
   beforeUnmount() {
     Bus.$off("stepFourCheck");
@@ -79,5 +231,50 @@ export default CreativityAdd;
     font-weight: bold;
     padding-left: 10px;
   }
+  .label-box {
+    width: 500px;
+    height: 200px;
+    border: 1px solid rgb(230, 230, 230);
+    margin-top: 10px;
+    .title-box {
+      width: 100%;
+      height: 36px;
+      line-height: 36px;
+      display: flex;
+      justify-content: space-between;
+      padding: 0 0 0 10px;
+      border-bottom: 1px solid rgb(230, 230, 230);
+    }
+    .label-container {
+      height: 163px;
+      width: 100%;
+      padding: 10px;
+      overflow: auto;
+      .label-item {
+        width: 220px;
+        height: 24px;
+        padding: 0 4px;
+        line-height: 24px;
+        background: rgb(241, 241, 241);
+        border: 1px solid rgb(223, 220, 220);
+        float: left;
+        margin-right: 10px;
+        margin-bottom: 10px;
+        span {
+          display: inline-block;
+          width: 180px;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          overflow: hidden;
+          word-break: break-all;
+        }
+        i {
+          float: right;
+          font-size: 13px;
+          cursor: pointer;
+        }
+      }
+    }
+  }
 }
 </style>

+ 19 - 9
src/views/put/plan-create/group-select.vue

@@ -70,13 +70,16 @@
           <div class="table-title"><h3>广告组名称</h3></div>
           <div class="list-box">
             <a-radio-group v-model:value="form.campaign_id">
-              <a-radio :style="radioStyle" :value="1"
+              <!-- <a-radio :style="radioStyle" :value="1"
                 >广告组1 <span class="desc">测试广告组</span></a-radio
-              >
-              <a-radio :style="radioStyle" :value="2"
-                >广告组2 <span class="desc">测试广告组</span></a-radio
-              >
+              > -->
+              <a-radio
+                :style="radioStyle"
+                v-for="(item, index) in []"
+                :key="index"
+              ></a-radio>
             </a-radio-group>
+            <a-empty></a-empty>
           </div>
         </div>
       </div>
@@ -112,6 +115,7 @@ const GroupSelect = defineComponent({
       groupNameSearch: "", // 搜索广告组名称
       groupList: ref<any[]>([]),
       groupListOrigin: ref<any[]>([]),
+      campaign_id: 0,
     });
     const radioStyle = reactive({
       display: "block",
@@ -209,7 +213,7 @@ const GroupSelect = defineComponent({
       this.form = JSON.parse(
         String(window.localStorage.getItem("plan-create-campaign"))
       );
-    // this.getGroups();
+    this.getGroups();
     Bus.$on("stepTwoCheck", (val?: any) => {
       let data: any = {};
       if (val) {
@@ -222,7 +226,10 @@ const GroupSelect = defineComponent({
           "plan-create-campaign",
           JSON.stringify(this.form)
         );
-        Bus.$emit("stepTwoBack", data.campaign_id);
+        Bus.$emit("stepTwoBack", {
+          campaign_id: data.campaign_id,
+          advertiser_id: this.$route.query.advertiser_id,
+        });
       }
     });
   },
@@ -235,6 +242,7 @@ const GroupSelect = defineComponent({
     check() {
       if (window.localStorage.getItem("plan-create-campaign")) {
         if (
+          this.campaign_id !== 0 &&
           this.isObjEqual(
             JSON.parse(
               String(window.localStorage.getItem("plan-create-campaign"))
@@ -265,12 +273,13 @@ const GroupSelect = defineComponent({
             createAdCampaign(param)
               .then(({ data }) => {
                 console.log("创建广告组返回", data);
+                this.campaign_id = data.campaign_id;
                 campaign_id = data.campaign_id;
                 this.form.campaign_id = campaign_id;
                 message.success("广告组创建成功");
                 Bus.$emit("stepTwoCheck", {
                   flag: true,
-                  campaign_id,
+                  campaign_id: campaign_id,
                 });
               })
               .catch((error: any) => {
@@ -293,9 +302,10 @@ const GroupSelect = defineComponent({
     },
     async getGroups() {
       let { data } = await getAdCampaigns({
-        advertiser_id: String(this.$route.query.account_id),
+        advertiser_id: String(this.$route.query.advertiser_id),
         page: 999,
       });
+      console.log("获取广告组", data);
     },
     // 生成广告组名称
     makeGroupName() {

+ 275 - 104
src/views/put/plan-create/plan-edit.vue

@@ -6,8 +6,8 @@
       <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
         <a-form-item label="转化跟踪方式">
           <a-select placeholder="请选择">
-            <a-select-option value="shanghai">Zone one</a-select-option>
-            <a-select-option value="beijing">Zone two</a-select-option>
+            <a-select-option value="XIANSUOTONG">线索通</a-select-option>
+            <a-select-option value="XIANSUOTONG_API">线索同API</a-select-option>
           </a-select>
         </a-form-item>
         <a-form-item label="优化目标">
@@ -33,12 +33,49 @@
       <h1>设置投放位置</h1>
       <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
         <a-form-item label="广告位置">
-          <a-radio-group>
-            <a-radio value="1">系统优选广告位</a-radio>
-            <a-radio value="2">首选媒体</a-radio>
-            <a-radio value="3">首选场景</a-radio>
+          <a-radio-group v-model:value="plan.inventory_catalog">
+            <a-radio value="SMART">系统优选广告位</a-radio>
+            <a-radio value="MANUAL">首选媒体</a-radio>
+            <a-radio value="SCENE">首选场景</a-radio>
           </a-radio-group>
         </a-form-item>
+        <a-form-item
+          label="投放媒体"
+          v-show="plan.inventory_catalog === 'MANUAL'"
+        >
+          <a-select
+            mode="multiple"
+            size="default"
+            placeholder="请选择"
+            v-model:value="plan.inventory_type"
+            style="width: 400px"
+          >
+            <a-select-option
+              v-for="item in optionsList.mediaList"
+              :key="item.value"
+            >
+              {{ item.label }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item
+          label="投放场景"
+          v-show="plan.inventory_catalog === 'SCENE'"
+        >
+          <a-select
+            size="default"
+            placeholder="请选择"
+            v-model:value="plan.scene_inventory"
+            style="width: 400px"
+          >
+            <a-select-option
+              v-for="item in optionsList.sceneList"
+              :key="item.value"
+            >
+              {{ item.label }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
         <a-form-item label="搜索快投关键词">
           <a-switch v-model:checked="plan.feed_delivery_search" />
         </a-form-item>
@@ -48,13 +85,34 @@
     <!-- part3------用户定向 -->
     <div class="part-box">
       <h1>用户定向</h1>
-      <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
+      <a-form
+        :label-col="labelCol"
+        :wrapper-col="wrapperCol"
+        style="margin-bottom: 20px"
+      >
         <a-form-item label="定向方式">
           <a-radio-group v-model:value="userTarget.directType">
             <a-radio value="BUILD">新建定向</a-radio>
             <a-radio value="SELECT">选择已有定向包</a-radio>
           </a-radio-group>
         </a-form-item>
+        <a-form-item
+          label="选择已有定向包"
+          v-if="userTarget.directType === 'SELECT'"
+        >
+          <a-select placeholder="请选择">
+            <a-select-option
+              v-for="(item, index) in []"
+              :key="index"
+            ></a-select-option>
+          </a-select>
+        </a-form-item>
+      </a-form>
+      <a-form
+        :label-col="labelCol"
+        :wrapper-col="wrapperCol"
+        v-if="userTarget.directType === 'BUILD'"
+      >
         <!-- 地域组件 -->
         <a-form-item label="地域">
           <!-- 组件数据接入DDD -->
@@ -68,7 +126,7 @@
           </a-radio-group>
         </a-form-item>
         <a-form-item label="年龄">
-          <a-checkbox-group v-model:value="plan.age">
+          <a-checkbox-group v-model:value="plan.age" style="width: 700px">
             <a-checkbox value="NONE">不限</a-checkbox>
             <a-checkbox value="AGE_BETWEEN_18_23">18-23</a-checkbox>
             <a-checkbox value="AGE_BETWEEN_24_30">24-30</a-checkbox>
@@ -84,7 +142,10 @@
             <a-radio-button value="CUSTOM">自定义人群</a-radio-button>
           </a-radio-group>
           <!-- 组件数据待接入DDD -->
-          <direction-exclusion :compType="'crowd'"></direction-exclusion>
+          <direction-exclusion
+            :compType="'crowd'"
+            v-if="userTarget.crowdType === 'CUSTOM'"
+          ></direction-exclusion>
         </a-form-item>
         <a-form-item label="行为兴趣">
           <a-radio-group v-model:value="plan.interest_action_mode">
@@ -93,14 +154,17 @@
           </a-radio-group>
         </a-form-item>
         <!-- 媒体定向 -->
-        <a-form-item label="媒体定向" v-model:value="userTarget.mediaTarget">
-          <a-radio-group>
+        <a-form-item label="媒体定向">
+          <a-radio-group v-model:value="userTarget.mediaTarget">
             <a-radio-button value="NONE">不限</a-radio-button>
             <a-radio-button value="1" disabled>游戏优质媒体</a-radio-button>
             <a-radio-button value="CUSTOM">自定义</a-radio-button>
           </a-radio-group>
           <!-- 组件数据接入DDD -->
-          <direction-exclusion :compType="'media'"></direction-exclusion>
+          <direction-exclusion
+            :compType="'media'"
+            v-if="userTarget.mediaTarget === 'CUSTOM'"
+          ></direction-exclusion>
         </a-form-item>
         <a-form-item label="平台">
           <a-checkbox-group v-model:value="plan.platform">
@@ -163,7 +227,10 @@
             <a-radio-button value="CLASSIFY">文章分类</a-radio-button>
           </a-radio-group>
           <!-- 组件数据接入DDD -->
-          <check-box :compType="1"></check-box>
+          <check-box
+            :compType="1"
+            v-if="userTarget.articleType === 'CLASSIFY'"
+          ></check-box>
         </a-form-item>
         <a-form-item label="运营商">
           <a-checkbox-group v-model:value="plan.carrier">
@@ -174,7 +241,10 @@
           </a-checkbox-group>
         </a-form-item>
         <a-form-item label="新用户">
-          <a-checkbox-group v-model:value="plan.activate_type">
+          <a-checkbox-group
+            v-model:value="plan.activate_type"
+            style="width: 600px"
+          >
             <a-checkbox value="NONE">不限</a-checkbox>
             <a-checkbox value="WITH_IN_A_MONTH">一个月以内</a-checkbox>
             <a-checkbox value="ONE_MONTH_2_THREE_MONTH"
@@ -190,53 +260,59 @@
             <a-radio-button value="BRAND">手机品牌</a-radio-button>
           </a-radio-group>
           <!-- 组件数据接入DDD -->
-          <check-box :compType="2"></check-box>
+          <check-box
+            :compType="2"
+            v-if="userTarget.deviceBrand === 'BRAND'"
+          ></check-box>
         </a-form-item>
         <a-form-item label="手机价格">
-          <a-radio-group>
-            <a-radio-button value="0">不限</a-radio-button>
-            <a-radio-button value="1">自定义</a-radio-button>
+          <a-radio-group v-model:value="userTarget.phonePrice">
+            <a-radio-button value="NONE">不限</a-radio-button>
+            <a-radio-button value="CUSTOM">自定义</a-radio-button>
           </a-radio-group>
-          <a-slider
-            range
-            :marks="phonePriceMarks"
-            :max="11000"
-            :min="0"
-            :step="null"
-            v-model:value="plan.launch_price"
-            style="width: 410px"
-          />
-          <span
-            style="position: relative; top: -54px; left: 450px"
-            v-if="plan.launch_price[0] > 0 && plan.launch_price[1] < 11000"
-            >{{ plan.launch_price[0] }}元~{{ plan.launch_price[1] }}元</span
-          >
-          <span
-            style="position: relative; top: -54px; left: 450px"
-            v-if="
-              (plan.launch_price[0] === 0 && plan.launch_price[1] === 11000) ||
-              plan.launch_price[0] == plan.launch_price[1]
-            "
-            >不限</span
-          >
-          <span
-            style="position: relative; top: -54px; left: 450px"
-            v-if="
-              plan.launch_price[0] === 0 &&
-              plan.launch_price[1] < 11000 &&
-              plan.launch_price[1] !== 0
-            "
-            >{{ plan.launch_price[1] }}元以内</span
-          >
-          <span
-            style="position: relative; top: -54px; left: 450px"
-            v-if="
-              plan.launch_price[0] > 0 &&
-              plan.launch_price[1] === 11000 &&
-              plan.launch_price[0] !== 11000
-            "
-            >{{ plan.launch_price[0] }}元以上</span
-          >
+          <div v-if="userTarget.phonePrice === 'CUSTOM'">
+            <a-slider
+              range
+              :marks="phonePriceMarks"
+              :max="11000"
+              :min="0"
+              :step="null"
+              v-model:value="plan.launch_price"
+              style="width: 410px"
+            />
+            <span
+              style="position: relative; top: -54px; left: 450px"
+              v-if="plan.launch_price[0] > 0 && plan.launch_price[1] < 11000"
+              >{{ plan.launch_price[0] }}元~{{ plan.launch_price[1] }}元</span
+            >
+            <span
+              style="position: relative; top: -54px; left: 450px"
+              v-if="
+                (plan.launch_price[0] === 0 &&
+                  plan.launch_price[1] === 11000) ||
+                plan.launch_price[0] == plan.launch_price[1]
+              "
+              >不限</span
+            >
+            <span
+              style="position: relative; top: -54px; left: 450px"
+              v-if="
+                plan.launch_price[0] === 0 &&
+                plan.launch_price[1] < 11000 &&
+                plan.launch_price[1] !== 0
+              "
+              >{{ plan.launch_price[1] }}元以内</span
+            >
+            <span
+              style="position: relative; top: -54px; left: 450px"
+              v-if="
+                plan.launch_price[0] > 0 &&
+                plan.launch_price[1] === 11000 &&
+                plan.launch_price[0] !== 11000
+              "
+              >{{ plan.launch_price[0] }}元以上</span
+            >
+          </div>
         </a-form-item>
         <!-- 职业状态 -->
         <a-form-item label="职业状态">
@@ -245,13 +321,23 @@
             <a-radio-button value="STATUS">职业状态</a-radio-button>
           </a-radio-group>
           <!-- 组件数据待接入DDD -->
-          <check-box :compType="3"></check-box>
+          <check-box
+            :compType="3"
+            v-if="userTarget.workStatus === 'STATUS'"
+          ></check-box>
         </a-form-item>
         <!-- 智能放量 -->
         <a-form-item label="智能放量">
-          <a-switch />
-          <check-box :compType="4"></check-box>
+          <a-switch
+            v-model:checked="plan.auto_extend_enabled"
+            checked-children="开"
+            un-checked-children="关"
+          />
+          <check-box :compType="4" v-if="plan.auto_extend_enabled"></check-box>
         </a-form-item>
+        <p style="line-height: 40px; width: 700px; text-align: right">
+          <a-button @click="saveDirectPackage">保存为定向包</a-button>
+        </p>
       </a-form>
     </div>
 
@@ -260,33 +346,42 @@
       <h1>预算与出价</h1>
       <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
         <a-form-item label="投放场景">
-          <a-radio-group>
-            <a-radio-button value="0">常规投放</a-radio-button>
-            <a-radio-button value="1">放量投放</a-radio-button>
+          <a-radio-group v-model:value="plan.smart_bid_type">
+            <a-radio value="SMART_BID_CUSTOM">常规投放</a-radio>
+            <a-radio value="SMART_BID_CONSERVATIVE">放量投放</a-radio>
           </a-radio-group>
           <span style="font-size: 13px; color: gray"
             >控制成本,尽量消耗完预算</span
           >
         </a-form-item>
         <a-form-item label="竞价策略">
-          <a-radio-group>
-            <a-radio-button value="0">优先跑量</a-radio-button>
-            <a-radio-button value="1">均衡投放</a-radio-button>
-            <a-radio-button value="2">控制成本上限</a-radio-button>
+          <a-radio-group v-model:value="plan.flow_control_mode">
+            <a-radio value="FLOW_CONTROL_MODE_FAST">优先跑量</a-radio>
+            <a-radio value="FLOW_CONTROL_MODE_SMOOTH">均衡投放</a-radio>
+            <a-radio value="FLOW_CONTROL_MODE_BALANCE">控制成本上限</a-radio>
           </a-radio-group>
         </a-form-item>
         <!-- 预算 -->
         <a-form-item label="投放时间">
-          <a-radio-group>
-            <a-radio-button value="0">从今天起长期投放</a-radio-button>
-            <a-radio-button value="1">设置开始和结束时间</a-radio-button>
+          <a-radio-group v-model:value="plan.schedule_type">
+            <a-radio value="SCHEDULE_FROM_NOW">从今天起长期投放</a-radio>
+            <a-radio value="SCHEDULE_START_END">设置开始和结束时间</a-radio>
           </a-radio-group>
-          <time-schedule style="width: 800px; margin-top: 10px"></time-schedule>
+        </a-form-item>
+        <a-form-item label="投放时段">
+          <a-radio-group v-model:value="userTarget.timeDuration">
+            <a-radio value="NONE">不限</a-radio>
+            <a-radio value="CUSTOM">指定时间段</a-radio>
+          </a-radio-group>
+          <time-schedule
+            style="width: 800px; margin-top: 10px"
+            v-if="userTarget.timeDuration === 'CUSTOM'"
+          ></time-schedule>
         </a-form-item>
         <!-- 投放时段 -->
         <a-form-item label="付费方式">
-          <a-radio-group>
-            <a-radio value="0">按展示付费(oCPM)</a-radio>
+          <a-radio-group v-model:value="plan.pricing">
+            <a-radio value="PRICING_OCPM">按展示付费(oCPM)</a-radio>
           </a-radio-group>
         </a-form-item>
         <a-form-item label="目标转化出价">
@@ -301,19 +396,28 @@
       <h1>第三方检测链</h1>
       <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
         <a-form-item label="展示">
-          <a-input placeholder="选填" />
+          <a-input placeholder="选填" v-model:value="plan.track_url" />
         </a-form-item>
         <a-form-item label="有效触点">
-          <a-input placeholder="选填" />
+          <a-input placeholder="选填" v-model:value="plan.action_track_url" />
         </a-form-item>
         <a-form-item label="视频播放">
-          <a-input placeholder="选填" />
+          <a-input
+            placeholder="选填"
+            v-model:value="plan.video_play_track_url"
+          />
         </a-form-item>
         <a-form-item label="视频播完">
-          <a-input placeholder="选填" />
+          <a-input
+            placeholder="选填"
+            v-model:value="plan.video_play_done_track_url"
+          />
         </a-form-item>
         <a-form-item label="视频有效播放">
-          <a-input placeholder="选填" />
+          <a-input
+            placeholder="选填"
+            v-model:value="plan.video_play_effective_track_url"
+          />
         </a-form-item>
       </a-form>
     </div>
@@ -323,10 +427,10 @@
       <h1>计划名称</h1>
       <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
         <a-form-item label="计划名称">
-          <a-input placeholder="选填" />
+          <a-input placeholder="选填" v-model:value="plan.name" />
         </a-form-item>
         <a-form-item label="创建数量">
-          <a-input placeholder="选填" />
+          <a-input placeholder="选填" v-model:value="plan.number" />
         </a-form-item>
       </a-form>
     </div>
@@ -335,14 +439,14 @@
 
 <script lang="ts">
 import { defineComponent, reactive, toRefs, ref } from "vue";
-import {} from "@/api";
+import { createPlan } from "@/api";
 import Bus from "@/utils/bus";
 import TimeSchedule from "../component/time-schedule.vue";
 import Location from "../component/location-auto-release.vue";
 import DirectionExclusion from "../component/direction-exclusion.vue";
 import CheckBox from "../component/check-box.vue";
 import { message } from "ant-design-vue";
-import { PriceSlider } from "../component/plan-data";
+import { PriceSlider, LaunchMedia, LaunchScene } from "../component/plan-data";
 
 const PlanEdit = defineComponent({
   components: {
@@ -361,53 +465,70 @@ const PlanEdit = defineComponent({
         mediaTarget: "NONE", // 媒体定向 不限-NONE 自定义-CUSTOM
         articleType: "NONE", // 文章分类 不限-NONE 分类-CLASSIFY
         deviceBrand: "NONE", // 手机品牌 不限-NONE 品牌-BRAND
-        workStatus: 'NONE', // 职业状态 不限-NONE 状态-STATUS
+        workStatus: "NONE", // 职业状态 不限-NONE 状态-STATUS
+        phonePrice: "NONE", // 手机价格 不限-NONE 自定义-CUNSTOM
+        timeDuration: "NONE", // 投放时段 不限-NONE 指定时段-CUSTOM
       },
       plan: {
         // part1 优化目标----------------------------------------------------
-        external_url: "", // 落地页
+        external_url: undefined, // 落地页
         open_url: "", // 直达链接内容
         // part2 投放位置----------------------------------------------------
+        inventory_catalog: "SMART", // 广告位置
+        inventory_type: ref<any[]>([]), // 投放媒体-广告位置是首选媒体时必填 否则删除
+        scene_inventory: "", // 投放场景-广告位置时首选场景时必填 否则删除
         feed_delivery_search: true, // 搜索快投关键词
         // part3 用户定向----------------------------------------------------
         district: "", // 地域类型
         city: ref<any[]>([]), // 选中的城市或区县
-        gender: "", // 性别
-        age: ref<any[]>([]), // 年龄
+        gender: "NONE", // 性别
+        age: ref<any[]>(["NONE"]), // 年龄
         retargeting_tags_include: ref<any[]>([]), // 定向人群
         retargeting_tags_exclude: ref<any[]>([]), //排除人群
-        interest_action_mode: "", // 行为兴趣
+        interest_action_mode: "UNLIMITED", // 行为兴趣
         superior_popularity_type: "", // 媒体定向类型 、、 选择自定义媒体包时本字段不传
         flow_package: ref<any[]>([]), // 定向流量包
         exclude_flow_package: ref<any[]>([]), // 排除流量包
-        platform: ref<any[]>([]), // 平台-字符串数组
-        device_type: ref<any[]>([]), // 设备类型 不限不传
-        ac: ref<any[]>([]), // 网络
+        platform: ref<any[]>(["NONE"]), // 平台-字符串数组
+        device_type: ref<any[]>(["NONE"]), // 设备类型 不限不传
+        ac: ref<any[]>(["unknown"]), // 网络
         hide_if_exists: 0, // 已安装用户 0-不限 1-过滤 2-定向
         hide_if_converted: "AD", // 过滤已转化用户 默认AD广告计划
-        converted_time_duration: "", // 过滤时间
+        converted_time_duration: "CUSTOMER", // 过滤时间
         article_category: ref<any[]>([]), // 文章分类
-        carrier: ref<any[]>([]), // 运营商
-        activate_type: ref<any[]>([]), // 新用户
+        carrier: ref<any[]>(["NONE"]), // 运营商
+        activate_type: ref<any[]>(["NONE"]), // 新用户
         device_brand: ref<any[]>([]), // 手机品牌
         launch_price: ref<any[]>([0, 11000]), // 手机价格-number[]
         career: ref<any[]>([]), // 职业状态-string[]
-        auto_extend_enabled: 0, // 智能放量
+        auto_extend_enabled: false, // 智能放量
+        auto_extend_targets: ref<any[]>([]), // 可开放定向
         // part4 预算与出价---------------------------------------
-        smart_bid_type: "", // 投放场景
-        flow_control_mode: "", // 竞价策略
+        smart_bid_type: "SMART_BID_CUSTOM", // 投放场景
+        flow_control_mode: "FLOW_CONTROL_MODE_FAST", // 竞价策略
         budget_mode: "", // 预算类型
         budget: 0, // 预算
-        schedule_type: "", // 投放时间类型
+        schedule_type: "SCHEDULE_FROM_NOW", // 投放时间类型
         start_time: "", // 投放开始时间
         end_time: "", // 投放结束时间
         schedule_time: "", // 投放时段
-        pricing: "", // 付费方式
+        pricing: "PRICING_OCPM", // 付费方式
         cpa_bid: "", // 目标转化出价
         // part5 第三方检测---------------------------------------
+        track_url: "", // 展示
+        action_track_url: "", // 有效触点
+        video_play_track_url: "", // 视频播放
+        video_play_done_track_url: "", // 视频播完
+        video_play_effective_track_url: "", // 有效播放
         // part6 计划名称---------------------------------------
+        name: "", // 计划名称
+        number: 1, // 创建数量
       },
       phonePriceMarks: PriceSlider, //手机价格区间
+      optionsList: {
+        mediaList: LaunchMedia, // 投放媒体
+        sceneList: LaunchScene, // 投放场景
+      },
     });
     return {
       ...toRefs(state),
@@ -419,6 +540,8 @@ const PlanEdit = defineComponent({
     Bus.$on("stepThreeCheck", (val?: any) => {
       console.log("Step3Check");
       Bus.$emit("stepThreeBack");
+      // console.log("接口");
+      // this.beforeCommit();
     });
   },
   methods: {
@@ -449,6 +572,37 @@ const PlanEdit = defineComponent({
       if (this.userTarget.deviceBrand === "NONE") delete data.device_brand;
       // 7.职位状态为不限时 删除字段career
       if (this.userTarget.workStatus === "NONE") delete data.career;
+      // 8.广告位置不是首选媒体时 删除投放媒体字段 不是首选场景-删除投放场景字段
+      if (data.inventory_catalog !== "MANUAL") delete data.inventory_type;
+      if (data.inventory_catalog !== "SCENE") delete data.scene_inventory;
+      // 9.手机价格为不限时 删除字段launch_price
+      if (this.userTarget.phonePrice === "NONE") delete data.launch_price;
+      // 10.智能放量开关auto_extend_enabled false=> 0 true=>1 关闭智能放量 删除开放定向字段
+      data.auto_extend_enabled = this.numAndBool(data.auto_extend_enabled);
+      if (!data.auto_extend_enabled) delete data.auto_extend_targets;
+      // 11.投放时段-timeDuration-NONE时 删除schedule_time
+      if (this.userTarget.timeDuration === "NONE") delete data.schedule_time;
+      // 12.删除plan.name plan.number
+      delete data.name;
+      delete data.number;
+
+      // 调用接口
+      this.savePlan(data);
+    },
+    // 保存数据
+    async savePlan(plan_data: any) {
+      let advertiser_id = this.$route.query.advertiser_id;
+      let campaign_id = this.$route.query.campaign_id;
+      let param = {
+        advertiser_id,
+        campaign_id,
+        ad_num: this.plan.number,
+        is_template: 1,
+        name: this.plan.name,
+        ad_data: { ...plan_data },
+      };
+      console.log("新建计划数据", param);
+      let { data } = await createPlan(param);
     },
     // 回显之前处理数据
     beforeBackShow(planOrigin: any) {
@@ -478,14 +632,31 @@ const PlanEdit = defineComponent({
         ? "CLASSIFY"
         : "NONE";
       // 6.根据否有device_brand字段判断页面变量手机品牌
-      this.userTarget.deviceBrand = planOrigin.device_brand
-        ? "BRAND"
-        : "NONE";
+      this.userTarget.deviceBrand = planOrigin.device_brand ? "BRAND" : "NONE";
       // 7.根据否有career字段判断页面变量职业状态
-      this.userTarget.workStatus = planOrigin.career
-        ? "STATUS"
+      this.userTarget.workStatus = planOrigin.career ? "STATUS" : "NONE";
+      // 8.根据否有launch_price字段判断页面变量手机价格
+      this.userTarget.phonePrice = planOrigin.launch_price ? "CUSTOM" : "NONE";
+      // 9.根据auto_extend_enabled字段转化trur/false渲染
+      planOrigin.auto_extend_enabled = this.numAndBool(
+        planOrigin.auto_extend_enabled
+      );
+      // 10.根据否有schedule_time字段判断页面变量投放时段
+      this.userTarget.timeDuration = planOrigin.schedule_time
+        ? "CUSTOM"
         : "NONE";
     },
+    // 用户定向保存为定向包
+    saveDirectPackage() {
+      console.log("保存定向包");
+    },
+    // 工具函数 0=>false 1=>true false=>0 true=>1
+    numAndBool(val: any) {
+      if (val === 0) return false;
+      if (val === 1) return true;
+      if (val === true) return 1;
+      if (val === false) return 0;
+    },
   },
   beforeUnmount() {
     Bus.$off("stepThreeCheck");

+ 42 - 6
src/views/put/plan-management.vue

@@ -2,9 +2,7 @@
   <div class="plan-management">
     <div class="title-box-common">
       <h3>计划管理</h3>
-      <a-button type="primary" @click="$router.push('/put/plan-create')"
-        >新建</a-button
-      >
+      <a-button type="primary" @click="visible = true">新建</a-button>
     </div>
     <div class="padding-box-common">
       <div class="search-box">
@@ -60,14 +58,34 @@
         ></a-table>
       </div>
     </div>
+    <a-modal
+      v-model:visible="visible"
+      title="选择模板"
+      @ok="handleOk"
+      okText="直接创建"
+      width="1000px"
+    >
+      <a-input-search
+        style="width: 200px; margin-bottom: 10px"
+        placeholder="请输入模板名称"
+      ></a-input-search>
+      <a-table
+        bordered
+        :data-source="list"
+        :columns="templateColumns"
+        rowKey="id"
+        :loading="loading"
+        :pagination="tablePageOptions"
+      ></a-table>
+    </a-modal>
   </div>
 </template>
 
 <script lang="ts">
 import { defineComponent, reactive, toRefs } from "vue";
 import usePagination from "@/hooks/usePagination";
-import {} from "@/api";
-import { PlanCloumn } from "../_pageOptions/table-put";
+import { getPlanList, getPlanTemplate } from "@/api";
+import { PlanCloumn, PlanTemplateCloumn } from "../_pageOptions/table-put";
 import { message } from "ant-design-vue";
 
 const PlanManagement = defineComponent({
@@ -80,12 +98,17 @@ const PlanManagement = defineComponent({
         group: "",
         status: "",
       },
+      visible: false,
       list: [],
       columns: PlanCloumn,
+      templateColumns: PlanTemplateCloumn,
     });
     return { ...toRefs(state), meta, tablePageOptions, loading };
   },
-  mounted() {},
+  mounted() {
+    this.getList();
+    this.getTemplate();
+  },
   methods: {
     // 重置
     resetSearch() {
@@ -94,6 +117,19 @@ const PlanManagement = defineComponent({
       this.search.group = "";
       this.search.status = "";
     },
+    // 获取计划列表
+    async getList() {
+      let { data } = await getPlanList({ page: 1 });
+      console.log("计划列表", data);
+    },
+    async getTemplate() {
+      let { data } = await getPlanTemplate({ page: 1 });
+      console.log("计划模板", data);
+    },
+    // 直接创建
+    handleOk() {
+      this.$router.push("/put/plan-create");
+    },
   },
 });