浏览代码

✨ feat(landing): 落地页步骤一

晓晓晓晓丶vv 4 年之前
父节点
当前提交
769b4c6149

+ 17 - 1
src/api/index.ts

@@ -512,7 +512,7 @@ export const getLandingOfficials = (): AxiosPromise<IList<IGZHItem>> => {
  * 获取落地页域名列表
  * @returns
  */
-export const getLandingDomains = (): AxiosPromise<IDomainItem[]> => {
+export const getLandingDomains = (): AxiosPromise<IList<IDomainItem>> => {
   return axios("/landingPage/domain");
 };
 
@@ -534,3 +534,19 @@ export const getLandingBooks = (
     }
   });
 };
+
+/**
+ * 图片上传
+ * @param file
+ * @param type
+ * @returns
+ */
+export const onUpload = (
+  file: File,
+  type: string
+): AxiosPromise<{ url: string }> => {
+  const formData = new FormData();
+  formData.append("file", file);
+  formData.append("type", type);
+  return axios.post("/landingPage/upload", formData);
+};

+ 110 - 0
src/components/image-upload/index.vue

@@ -0,0 +1,110 @@
+<template>
+  <a-upload :show-upload-list="false" :custom-request="onFileChange">
+    <div class="cover-upload">
+      <img v-if="cover" :src="cover" alt="" />
+      <div class="cover-edit-wrap">
+        <template v-if="uploading">
+          <LoadingOutlined />
+        </template>
+        <template v-else>
+          <div class="add">
+            <PlusOutlined />
+          </div>
+        </template>
+      </div>
+    </div>
+  </a-upload>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive, toRefs } from "vue";
+import { message } from "ant-design-vue";
+import { LoadingOutlined, PlusOutlined } from "@ant-design/icons-vue";
+
+import { onUpload } from "@/api";
+
+const ImageUpload = defineComponent({
+  name: "ImageUpload",
+  components: {
+    LoadingOutlined,
+    PlusOutlined
+  },
+  props: {
+    type: {
+      type: String,
+      default: () => ""
+    },
+    name: {
+      type: String,
+      required: true,
+      default: () => ""
+    },
+    value: {
+      type: String,
+      default: () => ""
+    }
+  },
+  emits: ["change"],
+  setup(props, { emit }) {
+    const state = reactive({
+      uploading: false,
+      cover: props.value ?? ""
+    });
+
+    const onFileChange = async (file: { file: File }) => {
+      try {
+        state.uploading = true;
+        let tempFile = file.file;
+        const { data } = await onUpload(tempFile, props.type ?? "");
+        state.cover = data.url;
+        emit("change", { url: state.cover, type: props.name });
+      } catch (error) {
+        message.error(error.msg ?? "系统错误");
+      } finally {
+        state.uploading = false;
+      }
+    };
+
+    return { ...toRefs(state), onFileChange };
+  }
+});
+
+export default ImageUpload;
+</script>
+
+<style lang="scss" scoped>
+.cover-upload {
+  font-size: 0;
+  position: relative;
+  border-radius: 6px;
+  width: 128px;
+  height: 128px;
+  overflow: hidden;
+
+  img {
+    width: 128px;
+    height: 128px;
+  }
+
+  .cover-edit-wrap {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0;
+    top: 0;
+    background: rgba($color: #000, $alpha: 0.3);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 18px;
+    color: #fff;
+    cursor: pointer;
+    transition: opacity 0.3s;
+    opacity: 1;
+
+    // &:hover {
+    //   opacity: 1;
+    // }
+  }
+}
+</style>

+ 4 - 2
src/plugins/install.ts

@@ -23,7 +23,8 @@ import {
   InputNumber,
   Affix,
   Descriptions,
-  Steps
+  Steps,
+  Upload
 } from "ant-design-vue";
 
 import VueClipboard3 from "./vue-clipboard";
@@ -63,7 +64,8 @@ const install = (app: App<Element>) => {
     .use(Modal)
     .use(Descriptions)
     .use(Drawer)
-    .use(Steps);
+    .use(Steps)
+    .use(Upload);
 };
 
 export default install;

+ 2 - 1
src/types/api.d.ts

@@ -412,7 +412,8 @@ interface AccountPlanConfig {
 interface IGZHItem {
   gzh_name: string;
   gzh_code: string;
-  channel_id: number;
+  gzh_img: string;
+  sub_img: string;
 }
 
 interface IDomainItem {

+ 16 - 4
src/views/put/landing/add.vue

@@ -8,14 +8,18 @@
         </a-steps>
       </div>
       <div class="step-content-container">
-        <component :is="stepComponent[stepCurrent]" />
+        <component
+          :is="stepComponent[stepCurrent]"
+          :content="forms"
+          @next="onTapNext"
+        />
       </div>
     </div>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive, toRefs, createVNode } from "vue";
+import { defineComponent, reactive, toRefs, createVNode, ref } from "vue";
 import { onBeforeRouteLeave } from "vue-router";
 import { Modal } from "ant-design-vue";
 import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
@@ -32,7 +36,8 @@ const LandingAddPage = defineComponent({
   setup() {
     const state = reactive({
       stepCurrent: 0,
-      stepComponent: ["step-one", "step-two"]
+      stepComponent: ["step-one", "step-two"],
+      forms: ref<Record<string, any>>({})
     });
 
     onBeforeRouteLeave((_, __, next) => {
@@ -46,8 +51,15 @@ const LandingAddPage = defineComponent({
       });
     });
 
+    const onTapNext = (content: Record<string, any>) => {
+      state.stepCurrent++;
+      state.forms = content;
+      console.log(state.forms);
+    };
+
     return {
-      ...toRefs(state)
+      ...toRefs(state),
+      onTapNext
     };
   }
 });

+ 114 - 16
src/views/put/landing/stepComp/step-one.vue

@@ -5,10 +5,10 @@
       :wrapper-col="{ offset: 1, span: 12 }"
       :colon="false"
     >
-      <a-form-item label="落地页名称">
+      <a-form-item label="落地页名称" v-bind="validateInfos.title">
         <a-input placeholder="请输入落地页名称" v-model:value="forms.title" />
       </a-form-item>
-      <a-form-item label="公众号">
+      <a-form-item label="公众号" v-bind="validateInfos.gzh_name">
         <a-select
           placeholder="请选择公众号"
           v-model:value="forms.gzh_name"
@@ -24,7 +24,7 @@
       <a-form-item label="微信号">
         <a-input placeholder="请输入微信号" disabled :value="forms.gzh_code" />
       </a-form-item>
-      <a-form-item label="域名">
+      <a-form-item label="域名" v-bind="validateInfos.domain">
         <a-select
           placeholder="请选择域名"
           v-model:value="forms.domain"
@@ -37,7 +37,7 @@
           </template>
         </a-select>
       </a-form-item>
-      <a-form-item label="推广书籍">
+      <a-form-item label="推广书籍" v-bind="validateInfos.bid">
         <a-select placeholder="请选择需要推广的书籍" v-model:value="forms.bid">
           <template v-for="book in books" :key="book.bid">
             <a-select-option :value="book.bid">
@@ -57,10 +57,13 @@
           <a-select-option value="2">2</a-select-option>
         </a-select> -->
       </a-form-item>
-      <a-form-item label="渠道">
-        <a-select placeholder="请选择渠道">
-          <a-select-option value="1">1</a-select-option>
-          <a-select-option value="2">2</a-select-option>
+      <a-form-item label="渠道" v-bind="validateInfos.link_source">
+        <a-select placeholder="请选择渠道" v-model:value="forms.link_source">
+          <a-select-option value="uc">UC</a-select-option>
+          <a-select-option value="iqiyi">爱奇艺</a-select-option>
+          <a-select-option value="tiktok">抖音</a-select-option>
+          <a-select-option value="vivo">Vivo</a-select-option>
+          <a-select-option value="baidu">百度</a-select-option>
         </a-select>
       </a-form-item>
       <a-form-item label="公众号后缀">
@@ -75,26 +78,53 @@
           <a-radio value="xcx">小程序</a-radio>
         </a-radio-group>
       </a-form-item>
-      <a-form-item label="关注图片"></a-form-item>
-      <a-form-item label="公众号头像"></a-form-item>
+      <a-form-item label="关注图片" v-bind="validateInfos.sub_img">
+        <image-upload
+          name="sub_img"
+          type="关注图片"
+          :value="forms.sub_img"
+          @change="onUploadChange"
+        />
+      </a-form-item>
+      <a-form-item label="公众号头像">
+        <image-upload
+          name="gzh_img"
+          type="公众号头像"
+          :value="forms.gzh_img"
+          @change="onUploadChange"
+        />
+      </a-form-item>
       <a-form-item label="简介">
         <a-textarea v-model="forms.name"></a-textarea>
       </a-form-item>
+      <a-form-item :wrapper-col="{ offset: 3 }">
+        <a-button @click="onBack" style="margin-right: 10px">返回</a-button>
+        <a-button type="primary" @click="onNextStep">下一步</a-button>
+      </a-form-item>
     </a-form>
   </div>
 </template>
 
 <script lang="ts">
 import { defineComponent, reactive, ref, toRefs } from "vue";
+import { useForm } from "@ant-design-vue/use";
+import { useRouter } from "vue-router";
+
+import ImageUpload from "@/components/image-upload/index.vue";
 
 import { getLandingBooks, getLandingDomains, getLandingOfficials } from "@/api";
 import { IDomainItem, IGZHItem, IPBookItem } from "@/types/api";
 
 const StepOne = defineComponent({
   name: "StepOne",
+  components: {
+    ImageUpload
+  },
   props: {},
-  emits: [],
+  emits: ["next"],
   setup(props, { emit }) {
+    const router = useRouter();
+
     const state = reactive({
       officials: ref<IGZHItem[]>([]),
       domains: ref<IDomainItem[]>([]),
@@ -105,10 +135,9 @@ const StepOne = defineComponent({
         gzh_code: "",
         gzh_name: "",
         bid: "",
-        channel_id: "",
         domain: "",
         company_name: "",
-        link_source: "",
+        link_source: "uc",
         gzh_suffix_mode: 0,
         gzh_suffix: "",
         jump_type: "copy_name",
@@ -118,6 +147,43 @@ const StepOne = defineComponent({
       }
     });
 
+    const formsRules = reactive({
+      title: [{ required: true, trigger: "blur", message: "请输入落地页名称" }],
+      gzh_name: [
+        { required: true, trigger: "change", message: "请选择公众号" }
+      ],
+      domain: [
+        {
+          required: true,
+          trigger: "change",
+          message: "请选择域名"
+        }
+      ],
+      bid: [
+        {
+          required: true,
+          type: "number",
+          trigger: "change",
+          message: "请选择推广书籍"
+        }
+      ],
+      link_source: [
+        {
+          required: true,
+          trigger: "change",
+          message: "请选择渠道"
+        }
+      ],
+      sub_img: [
+        {
+          required: true,
+          message: "请上传关注图片"
+        }
+      ]
+    });
+
+    const { validate, validateInfos } = useForm(state.forms, formsRules);
+
     const initConfigData = async () => {
       try {
         const [
@@ -130,10 +196,11 @@ const StepOne = defineComponent({
           getLandingBooks()
         ]);
         state.officials = official.list;
-        state.domains = domains;
+        state.domains = domains.list;
         state.books = books.list;
       } catch (error) {
         console.log("error happened in initLandingConfig:", error.message);
+        router.back();
       }
     };
 
@@ -141,7 +208,9 @@ const StepOne = defineComponent({
 
     const onGzhChange = (name: string) => {
       const target = state.officials.find((gzh) => gzh.gzh_name === name);
-      state.forms.channel_id = target!.channel_id.toString() ?? "";
+      // state.forms.channel_id = target!.channel_id.toString() ?? "";
+      state.forms.gzh_img = target!.gzh_img ?? "";
+      state.forms.sub_img = target!.sub_img ?? "";
       state.forms.gzh_code = target!.gzh_code ?? "";
     };
 
@@ -150,7 +219,36 @@ const StepOne = defineComponent({
       state.forms.company_name = target!.company_name ?? "";
     };
 
-    return { ...toRefs(state), onGzhChange, onDomainChange };
+    const onUploadChange = (result: {
+      url: string;
+      type: "sub_img" | "gzh_img";
+    }) => {
+      state.forms[result.type] = result.url;
+      console.log(state.forms);
+    };
+
+    const onBack = () => {
+      router.replace("/put/landing");
+    };
+
+    const onNextStep = async () => {
+      try {
+        await validate();
+        emit("next", state.forms);
+      } catch (error) {
+        console.log(error);
+      }
+    };
+
+    return {
+      ...toRefs(state),
+      validateInfos,
+      onGzhChange,
+      onDomainChange,
+      onUploadChange,
+      onBack,
+      onNextStep
+    };
   }
 });