Ver código fonte

RING:创建计划跟进

ringcode 3 anos atrás
pai
commit
4fda9520a6

+ 31 - 0
src/api/index.ts

@@ -954,3 +954,34 @@ export const getStuffPerformanceSum = (
 ): any => {
   return axios("/promoter/statSum", { params: query });
 };
+
+// 建计划模块
+
+/**
+ * 获取账户下的广告组
+ * @param
+ */
+export const getAdCampaigns = (
+  query: {
+    advertiser_id: string | number,
+    campaign_name?: string,
+    page: string | number,
+  }
+): any => {
+  return axios("/ad/campaigns", { params: query });
+};
+// 创建广告组
+/* 创建广告组
+* @param null
+*/
+export const createAdCampaign = (data: {
+  advertiser_id: string | number;
+  budget?: string | number;
+  campaign_name: string;
+  budget_mode: string;
+  landing_type: string;
+  campaign_type: string;
+  marketing_purpose: string;
+}) => {
+  return axios.post("/ad/campaign/create", data);
+};

+ 1 - 0
src/router/async.ts

@@ -195,6 +195,7 @@ export const PlanCreate: RouteConfig = {
     activeMenu: "/put/plan-management",
     noMenu: true
   },
+  hidden: true,
   children: [AccountSelect, GroupSelect, EditPlan, CreativityAdd, PlanUpload],
   component: () => import("@/views/put/plan-create-index.vue")
 };

+ 31 - 0
src/utils/bus.ts

@@ -0,0 +1,31 @@
+// 为保持和vue2版本中使用bus一致,emit,on,off前面都加了$
+class Bus {
+  list: { [key: string]: Array<Function> };
+  constructor() {
+    // 收集订阅信息,调度中心
+    this.list = {};
+  }
+
+  // 订阅
+  $on(name: string, fn: Function) {
+    this.list[name] = this.list[name] || [];
+    this.list[name].push(fn);
+  }
+
+  // 发布
+  $emit(name: string, data?: any) {
+    if (this.list[name]) {
+      this.list[name].forEach((fn: Function) => {
+        fn(data);
+      });
+    }
+  }
+
+  // 取消订阅
+  $off(name: string) {
+    if (this.list[name]) {
+      delete this.list[name];
+    }
+  }
+}
+export default new Bus();

+ 53 - 3
src/views/put/plan-create-index.vue

@@ -49,6 +49,7 @@
 import useApp from "@/hooks/useApp";
 import { defineComponent, reactive, toRefs } from "vue";
 import { onBeforeRouteUpdate } from "vue-router";
+import Bus from "@/utils/bus";
 
 const PlanCreate = defineComponent({
   setup(props, context) {
@@ -79,18 +80,67 @@ const PlanCreate = defineComponent({
     });
     return { ...toRefs(state), changeTab };
   },
-  mounted() {},
+  beforeRouteLeave(to: any, from, next) {
+    console.log("离开index", to);
+    window.localStorage.removeItem("plan-ad-account");
+    window.localStorage.removeItem("current-step");
+    window.localStorage.removeItem("plan-create-campaign");
+    next();
+    // if (to.href.indexOf("plan-create/group-select") > -1) return next();
+    // Modal.confirm({
+    //   title: "确认离开当前页面吗?",
+    //   icon: createVNode(ExclamationCircleOutlined),
+    //   content: createVNode(
+    //     "div",
+    //     { style: "color:gray;" },
+    //     "未保存内容将会丢失"
+    //   ),
+    //   onOk() {
+    //     window.localStorage.removeItem("plan-ad-account");
+    //     next();
+    //   },
+    //   onCancel() {
+    //     next(false);
+    //   },
+    //   class: "test",
+    // });
+  },
+  mounted() {
+    if (window.localStorage.getItem("current-step"))
+      this.current = Number(window.localStorage.getItem("current-step"));
+    Bus.$on("stepOneBack", (val: any) => {
+      // 第一步校验完成 携带account_id跳转创建广告组页面
+      this.$router.push(
+        "/put/plan-create/" + this.routerList[1] + "?account_id=" + val
+      );
+      this.current++;
+      window.localStorage.setItem("current-step", String(this.current));
+    });
+    Bus.$on("stepTwoBack", (val: any) => {
+      // 第一步校验完成 携带account_id跳转创建广告组页面
+      this.$router.push(
+        "/put/plan-create/" + this.routerList[2] + "?campaign_id=" + val
+      );
+      this.current++;
+      window.localStorage.setItem("current-step", String(this.current));
+    });
+  },
   methods: {
     next() {
-      this.current++;
-      this.$router.push("/put/plan-create/" + this.routerList[this.current]);
+      if (this.current === 0) return Bus.$emit("stepOneCheck");
+      if (this.current === 1) return Bus.$emit("stepTwoCheck");
+      if (this.current === 2) return Bus.$emit("stepThreeCheck");
+      if (this.current === 3) return Bus.$emit("stepFourCheck");
+      if (this.current === 4) return Bus.$emit("stepFiveCheck");
     },
     prev() {
       if (this.current === 0) {
         this.$router.push("/put/plan-management");
         return;
       }
+      console.log("减啊");
       this.current--;
+      window.localStorage.setItem("current-step", String(this.current));
       this.$router.push("/put/plan-create/" + this.routerList[this.current]);
     },
   },

+ 147 - 11
src/views/put/plan-create/account-select.vue

@@ -1,45 +1,154 @@
 <template>
   <div class="account-select">
     <a-input-search
-      v-model:value="ad_account"
+      v-model:value="adAccountSearch"
       placeholder="请输入广告主账号"
       style="width: 200px"
-      @search="onSearch"
+      @input="onSearch"
     />
     <div style="height: 10px"></div>
     <div class="common-box">
       <div class="title-box"><h3>头条账号</h3></div>
-      <div class="list-box"></div>
+      <div class="list-box">
+        <a-radio-group @change="getAdAccountList">
+          <a-radio
+            v-for="item in toutiaoAccountList"
+            :key="item.account_id"
+            :value="item"
+            :title="item.account_email"
+            >{{ item.account_email }}</a-radio
+          >
+        </a-radio-group>
+      </div>
     </div>
     <div class="common-box">
       <div class="title-box"><h3>广告主账号</h3></div>
-      <div class="list-box"></div>
+      <div class="list-box">
+        <a-empty v-if="adAccountList.length === 0" />
+
+        <a-radio-group v-else v-model:value="selectedAccount">
+          <a-radio
+            v-for="item in adAccountList"
+            :key="item.advertiser_id"
+            :value="item"
+            :title="item.advertiser_name"
+            >{{ item.advertiser_name }}</a-radio
+          >
+        </a-radio-group>
+      </div>
     </div>
     <div class="common-box">
       <div class="title-box"><h3>已选</h3></div>
       <div class="list-box">
-        <a-empty />
+        <a-empty v-if="!selectedAccount.advertiser_id" />
+        <p v-else>
+          <span
+            class="selected-account"
+            :title="selectedAccount.advertiser_name"
+            >{{ selectedAccount.advertiser_name }}</span
+          ><i class="iconfont icon-guanbi" @click="selectedAccount = {}"></i>
+        </p>
+        <div
+          style="
+            height: 30px;
+            line-height: 30px;
+            text-align: center;
+            color: red;
+          "
+          v-show="warningFlag && !selectedAccount.advertiser_id"
+        >
+          请选择要投放的广告主账号
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive, toRefs } from "vue";
-import {} from "@/api";
-import { message } from "ant-design-vue";
+import { defineComponent, reactive, toRefs, ref, createVNode } from "vue";
+import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
+import { getAdvertiser } from "@/api";
+import { message, Modal } from "ant-design-vue";
+import Bus from "@/utils/bus";
 
 const AccountSelect = defineComponent({
   setup() {
     const state = reactive({
-      ad_account: "",
+      adAccountSearch: "", // 搜索框
+      toutiaoAccountList: ref<any[]>([]), // 一级头条账号列表
+      adAccountList: ref<any[]>([]), // 二级广告主列表
+      adAccountListOrigin: ref<any[]>([]), // 二级广告主列表
+      selectedAccount: { advertiser_id: 0 }, // 选中的账号
+      account_id: 0, // 最终选择的广告主账户id
+      warningFlag: false,
     });
     return { ...toRefs(state) };
   },
-  mounted() {},
+  beforeRouteLeave(to: any, from, next) {
+    console.log("离开", to);
+    if (to.href.indexOf("plan-create/group-select") > -1) return next();
+    Modal.confirm({
+      title: "确认离开当前页面吗?",
+      icon: createVNode(ExclamationCircleOutlined),
+      content: createVNode(
+        "div",
+        { style: "color:gray;" },
+        "未保存内容将会丢失"
+      ),
+      onOk() {
+        window.localStorage.removeItem("plan-ad-account");
+        next();
+      },
+      onCancel() {
+        next(false);
+      },
+      class: "test",
+    });
+  },
+  mounted() {
+    this.getList();
+    if (window.localStorage.getItem("plan-ad-account"))
+      this.selectedAccount = JSON.parse(
+        String(window.localStorage.getItem("plan-ad-account"))
+      );
+    Bus.$on("stepOneCheck", () => {
+      let data = this.check();
+      if (data.flag) Bus.$emit("stepOneBack", data.account_id);
+    });
+  },
   methods: {
+    // 跳转前校验页面
+    check() {
+      if (!this.selectedAccount.advertiser_id) {
+        this.warningFlag = true;
+        message.warning("请选择要投放的广告主账号");
+        return { flag: false, account_id: "" };
+      } else {
+        window.localStorage.setItem(
+          "plan-ad-account",
+          JSON.stringify(this.selectedAccount)
+        );
+        return { flag: true, account_id: this.selectedAccount.advertiser_id };
+      }
+    },
     onSearch() {
-      console.log("搜索");
+      this.adAccountList = this.adAccountListOrigin.filter((item: any) => {
+        return item.advertiser_name.indexOf(this.adAccountSearch) > -1;
+      });
+    },
+    // 获取头条账户
+    async getList() {
+      let { data } = await getAdvertiser();
+      console.log("头条账号列表", data);
+      this.toutiaoAccountList = data;
+    },
+    // 选择头条账户获取广告主账户
+    getAdAccountList(item: any) {
+      console.log(item.target.value.advertises);
+      this.adAccountList = item.target.value.advertises;
+      this.adAccountListOrigin = JSON.parse(
+        JSON.stringify(item.target.value.advertises)
+      );
     },
   },
 });
@@ -66,6 +175,33 @@ export default AccountSelect;
       height: 260px;
       padding: 10px;
       overflow: auto;
+      .ant-radio-wrapper,
+      .selected-account {
+        display: block;
+        height: 40px;
+        line-height: 40px;
+        width: 200px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+      p {
+        background: rgb(246, 246, 246);
+        height: 30px;
+        line-height: 30px;
+        border: 1px solid rgb(207, 207, 207);
+        cursor: pointer;
+        i {
+          font-size: 13px;
+        }
+        .selected-account {
+          height: 30px;
+          line-height: 30px;
+          padding: 0 4px;
+          width: 190px;
+          float: left;
+        }
+      }
     }
   }
 }

+ 385 - 8
src/views/put/plan-create/group-select.vue

@@ -1,22 +1,399 @@
 <template>
-  <div class="group-select">选择广告组</div>
+  <div class="group-select">
+    <a-form
+      ref="formCheck"
+      :label-col="{ span: 2 }"
+      :wrapper-col="{ span: 8 }"
+      :rules="rules"
+      :model="form"
+    >
+      <a-form-item label="创建方式">
+        <a-radio-group v-model:value="form.build_mode">
+          <a-radio value="1">新建广告组</a-radio>
+          <a-radio value="2">已有广告组</a-radio>
+        </a-radio-group>
+      </a-form-item>
+      <div v-if="form.build_mode === '1'">
+        <a-form-item label="营销链路" name="marketing_purpose">
+          <a-radio-group
+            v-model:value="form.marketing_purpose"
+            @change="makeGroupName"
+          >
+            <a-radio-button value="CONVERSION">行动转化</a-radio-button>
+            <a-radio-button value="INTENTION">用户意向</a-radio-button>
+            <a-radio-button value="ACKNOWLEDGE">品牌认知</a-radio-button>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item name="landing_type" style="margin-left: 100px">
+          <a-radio-group
+            v-model:value="form.landing_type"
+            @change="makeGroupName"
+          >
+            <a-radio-button value="LINK">销售线索收集</a-radio-button>
+            <a-radio-button value="APP">应用推广</a-radio-button>
+          </a-radio-group></a-form-item
+        >
+        <a-form-item label="广告类型" name="campaign_type">
+          <a-select v-model:value="form.campaign_type" placeholder="请选择">
+            <a-select-option value="FEED">所有广告</a-select-option>
+            <a-select-option value="SEARCH">搜索广告</a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item label="广告组预算">
+          <a-radio-group v-model:value="form.budget_mode">
+            <a-radio value="BUDGET_MODE_INFINITE">不限</a-radio>
+            <a-radio value="BUDGET_MODE_DAY">指定预算</a-radio>
+          </a-radio-group>
+        </a-form-item>
+        <a-form-item
+          label="日预算"
+          name="budget"
+          v-if="form.budget_mode === 'BUDGET_MODE_DAY'"
+        >
+          <a-input addon-after="元" type="number" v-model:value="form.budget" />
+        </a-form-item>
+        <a-form-item label="广告组名称" name="campaign_name">
+          <a-input v-model:value="form.campaign_name" :maxlength="50" />
+        </a-form-item>
+      </div>
+      <div v-else class="ad-group-box">
+        <div class="title-box"><h3>选择广告组</h3></div>
+        <div class="main-box">
+          <div class="search-box">
+            <a-input-search
+              v-model:value="groupNameSearch"
+              placeholder="请输入广告组名称"
+              style="width: 200px"
+              @search="onSearch"
+            />
+          </div>
+          <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"
+                >广告组1 <span class="desc">测试广告组</span></a-radio
+              >
+              <a-radio :style="radioStyle" :value="2"
+                >广告组2 <span class="desc">测试广告组</span></a-radio
+              >
+            </a-radio-group>
+          </div>
+        </div>
+      </div>
+    </a-form>
+  </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive, toRefs } from "vue";
-import {} from "@/api";
-import { message } from "ant-design-vue";
+import { defineComponent, reactive, toRefs, ref, createVNode } from "vue";
+import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
+import { getAdCampaigns, createAdCampaign } from "@/api";
+import { message, Modal } from "ant-design-vue";
+import Bus from "@/utils/bus";
 
 const GroupSelect = defineComponent({
   setup() {
-    const state = reactive({});
-    return { ...toRefs(state) };
+    const formCheck = ref();
+    const state = reactive({
+      form: {
+        build_mode: "1", // 创建方式
+        landing_type: "", // 广告链路 广告组推广目的
+        marketing_purpose: "", // 营销目的
+        campaign_type: undefined, // 广告类型
+        budget_mode: "BUDGET_MODE_INFINITE", // 广告组预算
+        budget: "", // 日预算
+        campaign_name: "", // 广告组名称
+        campaign_id: 0, // 选择的已有广告组
+      },
+      groupNameSearch: "", // 搜索广告组名称
+      groupList: ref<any[]>([]),
+      groupListOrigin: ref<any[]>([]),
+    });
+    const radioStyle = reactive({
+      display: "block",
+      height: "40px",
+      lineHeight: "40px",
+      width: "580px",
+      padding: "0 10px",
+      borderBottom: "1px solid rgb(230, 230, 230)",
+    });
+    const rules = {
+      build_mode: [
+        {
+          required: false,
+          message: "请选择",
+          trigger: "change",
+        },
+      ],
+      landing_type: [
+        {
+          required: true,
+          message: "请选择",
+          trigger: "change",
+        },
+      ],
+      marketing_purpose: [
+        {
+          required: true,
+          message: "请选择",
+          trigger: "change",
+        },
+      ],
+      campaign_type: [
+        {
+          required: true,
+          message: "请选择广告类型",
+          trigger: "change",
+        },
+      ],
+      campaign_name: [
+        {
+          required: true,
+          message: "请输入广告组名称",
+          trigger: "blur",
+        },
+      ],
+      budget: [
+        {
+          required: true,
+          message: "请输入广告组预算",
+          trigger: "blur",
+        },
+      ],
+    };
+    // const check = () => {
+    //   // formCheck.value
+    //   //   .validate()
+    //   //   .then(() => {
+    //   //     console.log("values");
+    //   //   })
+    //   //   .catch((error: any) => {
+    //   //     console.log("error", error);
+    //   //   });
+    //   console.log("Form", state.form);
+    //   if (state.form.build_mode === "1") {
+    //     // 请求创建广告主接口
+    //     let param: any = { ...state.form,advertiser_id: };
+    //     delete param.build_mode;
+    //     if (param.budget_mode === "BUDGET_MODE_INFINITE") delete param.budget;
+    //   }
+
+    //   // 返回处理
+    //   let data = { flag: false, campaign_id: 0 };
+    //   return data;
+    // };
+    return { ...toRefs(state), radioStyle, rules, formCheck };
+  },
+  beforeRouteLeave(to: any, from, next) {
+    console.log("离开", to);
+    if (
+      to.href.indexOf("plan-create/account-select") > -1 ||
+      to.href.indexOf("plan-create/plan-edit") > -1
+    ) {
+      window.localStorage.setItem(
+        "plan-create-campaign",
+        JSON.stringify(this.form)
+      );
+      return next();
+    }
+    Modal.confirm({
+      title: "确认离开当前页面吗?",
+      icon: createVNode(ExclamationCircleOutlined),
+      content: createVNode(
+        "div",
+        { style: "color:gray;" },
+        "未保存内容将会丢失"
+      ),
+      onOk() {
+        next();
+      },
+      onCancel() {
+        next(false);
+      },
+      class: "test",
+    });
+  },
+  mounted() {
+    if (window.localStorage.getItem("plan-create-campaign"))
+      this.form = JSON.parse(
+        String(window.localStorage.getItem("plan-create-campaign"))
+      );
+    console.log("REF", this.formCheck);
+    // this.getGroups();
+    Bus.$on("stepTwoCheck", () => {
+      let data = this.check();
+      console.log(data);
+      if (data.flag) {
+        window.localStorage.setItem(
+          "plan-create-campaign",
+          JSON.stringify(this.form)
+        );
+        Bus.$emit("stepTwoBack", data.campaign_id);
+      }
+    });
+  },
+  methods: {
+    // 搜索广告组名称
+    onSearch() {
+      console.log("搜索广告组", this.groupNameSearch);
+    },
+    // step2创建广告组页面校验
+    check() {
+      let campaign_id = 0;
+      console.log("Form", this.form);
+      if (this.form.build_mode === "1") {
+        // 校验表单
+        let formV: any = this.$refs.formCheck;
+        console.log("你们", this.$refs.formCheck);
+        formV
+          .validate()
+          .then(() => {
+            // 请求创建广告主接口
+            let param: any = {
+              ...this.form,
+              advertiser_id: this.$route.query.account_id,
+            };
+            delete param.build_mode;
+            delete param.campaign_id;
+            if (param.budget_mode === "BUDGET_MODE_INFINITE")
+              delete param.budget;
+            console.log(param);
+
+            // 接口
+            createAdCampaign(param)
+              .then(({ data }) => {
+                console.log("创建广告组返回", data);
+                campaign_id = data.campaign_id;
+                message.success("广告组创建成功");
+              })
+              .catch((error: any) => {
+                console.log("error", error);
+              });
+          })
+          .catch((error: any) => {
+            console.log("error", error);
+          });
+      } else {
+        console.log("逸轩广告组", this.form.campaign_id);
+        if (!this.form.campaign_id) {
+          message.warning("请选择广告组");
+        } else {
+          campaign_id = this.form.campaign_id;
+        }
+      }
+      // 返回处理
+      let data = { flag: campaign_id ? true : false, campaign_id: campaign_id };
+      return data;
+    },
+    async getGroups() {
+      let { data } = await getAdCampaigns({
+        advertiser_id: String(this.$route.query.account_id),
+        page: 999,
+      });
+    },
+    // 生成广告组名称
+    makeGroupName() {
+      if (!this.form.landing_type || !this.form.marketing_purpose) return;
+      let langdingTypeName = "",
+        marketingPurposeName = "";
+      let nameArr: any = [
+        { name: "销售线索收集", value: "LINK" },
+        { name: "应用推广", value: "APP" },
+        { name: "行动转化", value: "CONVERSION" },
+        { name: "用户意向", value: "INTENTION" },
+        { name: "品牌认知", value: "ACKNOWLEDGE" },
+      ];
+      nameArr.forEach((item: any) => {
+        if (this.form.landing_type === item.value) langdingTypeName = item.name;
+        if (this.form.marketing_purpose === item.value)
+          marketingPurposeName = item.name;
+      });
+      let time = this.getNowFormatDate();
+      this.form.campaign_name = `${marketingPurposeName}_${langdingTypeName}_${time}`;
+      console.log(this.form.campaign_name);
+    },
+    // 获取当前时间
+    getNowFormatDate() {
+      var date = new Date();
+      var seperator1 = "-";
+      var seperator2 = ":";
+      var month: any = date.getMonth() + 1;
+      var strDate: any = date.getDate();
+      var strHours: any = date.getHours();
+      var strMinutes: any = date.getMinutes();
+      var strSeconds: any = date.getSeconds();
+      if (month >= 1 && month <= 9) {
+        month = "0" + month;
+      }
+      if (strDate >= 0 && strDate <= 9) {
+        strDate = "0" + strDate;
+      }
+      if (strHours >= 0 && strHours <= 9) {
+        strHours = "0" + strHours;
+      }
+      if (strMinutes >= 0 && strMinutes <= 9) {
+        strMinutes = "0" + strMinutes;
+      }
+      if (strSeconds >= 0 && strSeconds <= 9) {
+        strSeconds = "0" + strSeconds;
+      }
+      var currentdate =
+        date.getFullYear() +
+        seperator1 +
+        month +
+        seperator1 +
+        strDate +
+        " " +
+        strHours +
+        seperator2 +
+        strMinutes +
+        seperator2 +
+        strSeconds;
+      currentdate = currentdate.replace(/-/g, "_").replace(/ /g, "_");
+      currentdate = currentdate.slice(5, 19);
+      return currentdate;
+    },
   },
-  mounted() {},
-  methods: {},
 });
 
 export default GroupSelect;
 </script>
 <style lang="scss" scoped>
+@import "@/assets/common-style/scroll-bar.scss";
+.group-select {
+  .ad-group-box {
+    width: 600px;
+    height: 350px;
+    border: 1px solid rgb(230, 230, 230);
+    margin-left: 100px;
+    .title-box {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid rgb(230, 230, 230);
+      padding-left: 10px;
+    }
+    .main-box {
+      padding: 10px;
+      .table-title {
+        border: 1px solid rgb(230, 230, 230);
+        background: rgba(241, 241, 241, 0.548);
+        height: 40px;
+        line-height: 40px;
+        padding-left: 10px;
+        margin: 10px 0;
+      }
+      .list-box {
+        height: 200px;
+        overflow: auto;
+        .desc {
+          float: right;
+          margin-right: 10px;
+          display: block;
+          width: 200px;
+          color: rgb(167, 163, 163);
+        }
+      }
+    }
+  }
+}
 </style>