Bläddra i källkod

Merge remote-tracking branch 'origin/dev0518' into dev

# Conflicts:
#	src/api/coupon.js
ChengYuXuan 5 år sedan
förälder
incheckning
545128c954

+ 71 - 1
src/api/coupon.js

@@ -1,5 +1,75 @@
-import { COUPON_MODULE } from "@/api/_prefix";
 import axios from "axios";
+import { COUPON_MODULE } from "./_prefix";
+
+/**
+ * 创建优惠策略
+ * @param {*} payload
+ * @returns
+ */
+export const createCoupon = payload => {
+  return axios.post(`${COUPON_MODULE}/create`, payload).then(res => {
+    return res.data;
+  })
+}
+
+/**
+ * 发放优惠券
+ * @param {*} payload
+ * @returns
+ */
+export const deliverCoupon = payload => {
+  return axios.post(`${COUPON_MODULE}/deliver`, payload).then(res => {
+    return res.data;
+  })
+}
+
+/**
+ * 获取可以发布的优惠券
+ * @returns
+ */
+export const getCouponList = () => {
+  return axios.get(`${COUPON_MODULE}/getAll`).then(res => {
+    return res.data;
+  });
+  // return Promise.resolve([
+  //   {
+  //     "id": 8,
+  //     "type": "减价型",
+  //     "scope": "单个课程",
+  //     "name": "测试满减优惠",
+  //     "description": "测试满减优惠",
+  //     "metadata": "{\"threshold\":1,\"cutDown\":1,\"courseId\":1}",
+  //     "startTime": "2021-05-19 00:00:00",
+  //     "endTime": "2021-05-20 00:00:00",
+  //     "valid": true,
+  //     "sharable": true
+  //   },
+  //   {
+  //     "id": 1,
+  //     "type": "减价型",
+  //     "scope": "单个课程",
+  //     "name": "测试满减优惠",
+  //     "description": "测试满减优惠",
+  //     "metadata": "{\"threshold\":1,\"cutDown\":1,\"courseId\":1}",
+  //     "startTime": "2021-05-19 00:00:00",
+  //     "endTime": "2021-05-20 00:00:00",
+  //     "valid": true,
+  //     "sharable": true
+  //   },
+  //   {
+  //     "id": 4,
+  //     "type": "减价型",
+  //     "scope": "单个课程",
+  //     "name": "测试满减优惠",
+  //     "description": "测试满减优惠",
+  //     "metadata": "{\"threshold\":1,\"cutDown\":1,\"courseId\":1}",
+  //     "startTime": "2021-05-19 00:00:00",
+  //     "endTime": "2021-05-20 00:00:00",
+  //     "valid": true,
+  //     "sharable": true
+  //   }
+  // ])
+}
 
 export const getAvailableCouponsForOrder = orderId => {
   return axios.get(`${COUPON_MODULE}/getForOrder`, {

+ 15 - 3
src/api/user.js

@@ -43,9 +43,21 @@ export const login = payload => {
   return axios.post(`${USER_MODULE}/login`, { phone, password }).then(res => {
     return res.data;
   });
-}
-
-
+  // return Promise.resolve({  
+  //   "code": 1,
+  //   "msg": "账号登录成功!",
+  //   "data": {
+  //     "id": 3,
+  //     "uname": "小张同学",
+  //     "phone": "102123456789",
+  //     "password": null,
+  //     "picture": "1.png",
+  //     "balance": 0,
+  //     "userRole": "ADMIN",
+  //     "createTime": "2021-03-13T01:45:48.246+00:00"
+  //   }
+  // });
+} 
 
 /**
  *  获取用户 GET /user/{uid}

+ 97 - 0
src/components/CouponItem.vue

@@ -0,0 +1,97 @@
+<template>
+  <v-card width="380" class="ma-4 pa-2 blue lighten-2">
+    <v-card-title class="headline">
+      {{ name }}
+      <v-chip small class="ml-4">
+        {{ type }}
+      </v-chip>
+    </v-card-title>
+
+    <v-card-text class="text">
+      <v-row class="pa-2 pl-4"> 优惠ID: {{ id }} </v-row>
+      <v-row class="pa-2 pl-4"> 优惠描述: {{ text }} </v-row>
+      <v-row class="pa-2 pl-4"> 开始时间: {{ startTime }} </v-row>
+      <v-row class="pa-2 pl-4"> 结束时间: {{ endTime }} </v-row>
+      <v-row class="pa-2 pl-4"> 详细策略: {{ metadata }} </v-row>
+      <v-row class="pa-2 pl-4">
+        是否可与其他优惠同用: {{ sharable ? "是" : "否" }}
+      </v-row>
+    </v-card-text>
+
+    <v-card-actions>
+      <v-btn text @click="handleDeliver">发放优惠券</v-btn>
+    </v-card-actions>
+  </v-card>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+
+export default Vue.extend({
+  name: "CouponItem",
+  props: {
+    id: {
+      type: Number,
+      default: 0,
+    },
+    type: {
+      type: String,
+      default: "",
+    },
+    scope: {
+      type: String,
+      default: "",
+    },
+    name: {
+      type: String,
+      default: "",
+    },
+    description: {
+      type: String,
+      default: "",
+    },
+    metadata: {
+      type: String,
+      default: "",
+    },
+    startTime: {
+      type: String,
+      default: "",
+    },
+    endTime: {
+      type: String,
+      default: "",
+    },
+    valid: {
+      type: Boolean,
+      default: false,
+    },
+    sharable: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    handleDeliver: function() {
+      this.$emit("deliver-coupon", this.id);
+    },
+  },
+  computed: {
+    text: function() {
+      return this.description.length < 60
+        ? this.description
+        : this.description.substring(0, 60) + "...";
+    },
+  },
+});
+</script>
+
+<style scoped>
+.text {
+  min-height: 85px;
+  overflow: hidden;
+}
+</style>

+ 77 - 0
src/layouts/adminLayout.vue

@@ -0,0 +1,77 @@
+<template>
+  <div>
+    <v-app-bar color="deep-purple lighten-3" dense dark>
+      <v-app-bar-nav-icon @click="direct('/admin')">
+        <v-icon>mdi-home</v-icon></v-app-bar-nav-icon
+      >
+
+      <v-toolbar-title @click="direct('/admin')" class="cursor">
+        Course Learning Admin
+      </v-toolbar-title>
+
+      <v-spacer></v-spacer>
+
+      <v-menu left bottom>
+        <template v-slot:activator="{ on, attrs }">
+          <v-btn icon v-bind="attrs" v-on="on">
+            <v-icon>mdi-dots-vertical</v-icon>
+          </v-btn>
+        </template>
+
+        <v-list>
+          <v-list-item
+            v-for="opt in optionList"
+            :key="opt.optionName"
+            @click="direct(opt.link)"
+          >
+            <v-list-item-title>{{ opt.optionName }}</v-list-item-title>
+          </v-list-item>
+        </v-list>
+      </v-menu>
+    </v-app-bar>
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: "AdminLayout",
+  data() {
+    return {
+      optionList: [
+        {
+          optionName: "优惠管理",
+          link: "/admin/coupon",
+        },
+        {
+          optionName: "登出",
+          link: "/",
+        },
+      ],
+    };
+  },
+  methods: {
+    direct(link) {
+      if (link === "/") {
+        this.logout();
+      }
+      if (this.$route.path !== link) {
+        this.$router.push(link);
+      }
+    },
+
+    logout() {
+      window.localStorage.removeItem("userId");
+      window.localStorage.removeItem("userPhone");
+      window.localStorage.removeItem("username");
+      window.localStorage.removeItem("userRole");
+    },
+  },
+};
+</script>
+
+<style scoped>
+.cursor {
+  cursor: pointer;
+}
+</style>

+ 34 - 1
src/router/index.js

@@ -1,5 +1,10 @@
 import Vue from "vue";
 import VueRouter from "vue-router";
+// admin
+import AdminLayout from "@/layouts/adminLayout.vue";
+import CouponList from "@/views/admin/CouponList.vue";
+import CouponCreate from "@/views/admin/CouponCreate.vue";
+import CouponDeliver from "@/views/admin/CouponDeliver.vue";
 // teacher
 import TeacherLayout from "@/layouts/teacherLayout.vue";
 import CourseList from "@/views/teacher/CourseList.vue";
@@ -13,7 +18,7 @@ import UserCenter from "@/views/student/UserCenter.vue";
 import HistoryOrder from "@/views/student/HistoryOrder.vue";
 import CourseStudy from "@/views/student/CourseStudy.vue";
 import CoursePeek from "@/views/student/CoursePeek.vue";
-// defualt
+// default
 import DefaultLayout from "@/layouts/defaultLayout.vue";
 import Login from "@/views/Login.vue";
 import Register from "@/views/Register.vue";
@@ -46,6 +51,34 @@ const routes = [
       }
     ]
   },
+  {
+    path: "/admin",
+    name: 'Admin',
+    component: AdminLayout,
+    children: [
+      {
+        path: "/",
+        name: "CouponList",
+        redirect: 'coupon'
+      },
+      {
+        path: "coupon",
+        name: "CouponList",
+        exact: true,
+        component: CouponList
+      },
+      {
+        path: "coupon/create",
+        name: "CouponCreate",
+        component: CouponCreate
+      },
+      {
+        path: "coupon/deliver",
+        name: "CouponDeliver",
+        component: CouponDeliver
+      },
+    ],
+  },
   {
     path: "/teacher",
     name: "Teacher",

+ 5 - 2
src/views/Login.vue

@@ -83,11 +83,14 @@ export default {
             setTimeout(() => {
               this.$router.push("/student");
             }, 800);
-          }
-          if (res.data.userRole === "TEACHER") {
+          } else if (res.data.userRole === "TEACHER") {
             setTimeout(() => {
               this.$router.push("/teacher");
             }, 800);
+          } else if (res.data.userRole === "ADMIN") {
+            setTimeout(() => {
+              this.$router.push("/admin");
+            }, 800);
           }
         } else {
           this.msg = res.msg;

+ 172 - 0
src/views/admin/CouponCreate.vue

@@ -0,0 +1,172 @@
+<template>
+  <div>
+    <!-- alert -->
+    <v-alert
+      class="alert"
+      outlined
+      type="success"
+      text
+      v-show="showSuccessDialog"
+      transition="scroll-y-transition"
+    >
+      优惠创建成功!
+    </v-alert>
+    <!-- alert -->
+    <v-alert
+      class="alert"
+      outlined
+      type="warning"
+      text
+      v-show="showFailDialog"
+      transition="scroll-y-transition"
+    >
+      {{ msg }}
+    </v-alert>
+    <v-container class="pl-16 pr-16">
+      <form class="pa-12 grey lighten-5 mt-8">
+        <v-text-field v-model="couponInfo.name" label="优惠名称"></v-text-field>
+        <v-select
+          :items="typeList"
+          v-model="couponInfo.type"
+          label="优惠分类"
+        ></v-select>
+        <v-text-field
+          v-show="couponInfo.type === 'DISCOUNT'"
+          v-model="meta.discount"
+          label="折扣数额(0 ~ 1)"
+        ></v-text-field>
+        <v-text-field
+          v-show="couponInfo.type === 'CUT_DOWN'"
+          v-model="meta.threshold"
+          label="满减门槛"
+        ></v-text-field>
+        <v-text-field
+          v-show="couponInfo.type === 'CUT_DOWN'"
+          v-model="meta.cutDown"
+          label="满减金额"
+        ></v-text-field>
+        <v-select
+          :items="scopeList"
+          v-model="couponInfo.scope"
+          label="使用范围"
+        ></v-select>
+        <v-text-field
+          v-show="couponInfo.scope === 'SINGLE'"
+          v-model="meta.teacherId"
+          label="课程ID"
+        ></v-text-field>
+        <v-text-field
+          v-show="couponInfo.scope === 'TEACHER'"
+          v-model="meta.teacherId"
+          label="教师ID"
+        ></v-text-field>
+        <v-text-field
+          v-show="couponInfo.scope === 'SCHOOL'"
+          v-model="meta.teacherId"
+          label="教师ID"
+        ></v-text-field>
+        <v-text-field
+          v-model="couponInfo.description"
+          label="优惠说明"
+        ></v-text-field>
+        <v-text-field
+          v-model="couponInfo.startTime"
+          label="开始时间"
+          messages="YYYY-MM-DD HH:MM:SS"
+        ></v-text-field>
+        <v-text-field
+          v-model="couponInfo.endTime"
+          label="结束时间"
+          messages="YYYY-MM-DD HH:MM:SS"
+        ></v-text-field>
+        <v-switch
+          v-model="couponInfo.sharable"
+          :label="`是否可与其他优惠同时使用: ${couponInfo.sharable.toString()}`"
+        ></v-switch>
+        <v-btn class="ml-0 mt-8 info" @click="submit">
+          确认
+        </v-btn>
+      </form>
+    </v-container>
+  </div>
+</template>
+
+<script>
+import { createCoupon } from "@/api/coupon";
+
+export default {
+  name: "CouponCreate",
+
+  data() {
+    return {
+      typeList_: ["CUT_DOWN", "DISCOUNT"],
+      typeList: ["满减", "折扣"],
+      scopeList_: ["SINGLE", "TEACHER", "SCHOOL", "COMMON"],
+      scopeList: [
+        "单一课程",
+        "指定教师的课程",
+        "指定学习的课程",
+        "全站课程通用",
+      ],
+      couponInfo: {
+        type: "满减", // "DISCOUNT"
+        scope: "单一课程", // "TEACHER" "COMMON"
+        name: "",
+        description: "",
+        metadata: "",
+        startTime: "",
+        endTime: "",
+        sharable: false,
+      },
+      meta: {
+        threshold: 0,
+        cutDown: 0,
+        discount: 0,
+        courseId: "",
+        teacherId: "",
+        schoolId: "",
+      },
+
+      showSuccessDialog: false,
+      showFailDialog: false,
+      msg: "",
+    };
+  },
+
+  methods: {
+    submit() {
+      const payload = {
+        ...this.couponInfo,
+        type: this.typeList[this.typeList_.indexOf(this.couponInfo.deliver)],
+        scope: this.scopeList[this.scopeList_.indexOf(this.couponInfo.scope)],
+        metadata: JSON.stringify(this.meta),
+      };
+      console.log(payload);
+      createCoupon(payload).then((res) => {
+        console.log(res);
+        if (res.code === 1) {
+          this.showSuccessDialog = true;
+          setTimeout(() => {
+            this.showSuccessDialog = false;
+          }, 1000);
+        } else {
+          this.showFailDialog = true;
+          this.msg = res.msg;
+          setTimeout(() => {
+            this.showFailDialog = false;
+          }, 1000);
+        }
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.alert {
+  position: fixed;
+  left: 50%;
+  top: 100px;
+  z-index: 999;
+}
+</style>

+ 55 - 0
src/views/admin/CouponDeliver.vue

@@ -0,0 +1,55 @@
+<template>
+  <div>
+    <v-container class="ma-8 pa-4">
+      <v-row>
+        <router-link :to="`/admin/coupon/create`">
+          <v-btn class="ma-4 mb-8" fab dark color="indigo">
+            <v-icon dark large>
+              mdi-plus
+            </v-icon>
+          </v-btn>
+        </router-link>
+      </v-row>
+      <v-row>
+        <coupon-item
+          v-for="c in couponList"
+          :key="c.id"
+          :id="c.id"
+          :type="c.type"
+          :scope="c.scope"
+          :name="c.name"
+          :description="c.description"
+          :metadata="c.metadata"
+          :startTime="c.startTime"
+          :endTime="c.endTime"
+          :vaild="c.vaild"
+          :sharable="c.sharable"
+        >
+        </coupon-item>
+      </v-row>
+    </v-container>
+  </div>
+</template>
+
+<script>
+import CouponItem from "@/components/CouponItem.vue";
+import { getCouponList } from "@/api/coupon";
+
+export default {
+  name: "CouponDeliver",
+  components: {
+    CouponItem,
+  },
+  data() {
+    return {
+      colorList: ["#26A69A", "#00B0FF", "#5C6BC0", "#FFB300", "#E57373"],
+      couponList: null,
+    };
+  },
+  mounted() {
+    getCouponList().then((res) => {
+      this.couponList = res || [];
+    });
+  },
+};
+</script>

+ 160 - 0
src/views/admin/CouponList.vue

@@ -0,0 +1,160 @@
+<template>
+  <div>
+    <!-- alert -->
+    <v-alert
+      class="alert"
+      outlined
+      type="success"
+      text
+      v-show="showSuccessDialog"
+      transition="scroll-y-transition"
+    >
+      优惠发放成功!
+    </v-alert>
+    <!-- alert -->
+    <v-alert
+      class="alert"
+      outlined
+      type="warning"
+      text
+      v-show="showFailDialog"
+      transition="scroll-y-transition"
+    >
+      {{ msg }}
+    </v-alert>
+
+    <v-container class="ma-8 pa-4">
+      <v-row>
+        <router-link :to="`/admin/coupon/create`">
+          <v-btn class="ma-4 mb-8" fab dark color="indigo">
+            <v-icon dark large>
+              mdi-plus
+            </v-icon>
+          </v-btn>
+        </router-link>
+      </v-row>
+      <v-row>
+        <coupon-item
+          v-for="c in couponList"
+          :key="c.id"
+          :id="c.id"
+          :type="c.type"
+          :scope="c.scope"
+          :name="c.name"
+          :description="c.description"
+          :metadata="c.metadata"
+          :startTime="c.startTime"
+          :endTime="c.endTime"
+          :vaild="c.vaild"
+          :sharable="c.sharable"
+          @deliver-coupon="handleDeliverClick"
+        >
+        </coupon-item>
+      </v-row>
+    </v-container>
+
+    <!-- 分发对话框 -->
+    <v-dialog v-model="showDeliverDialog" width="500">
+      <form class="pa-12 grey lighten-5 mt-8">
+        <v-select
+          :items="deliverList"
+          v-model="deliver"
+          label="分发方式"
+        ></v-select>
+        <v-text-field
+          v-show="deliver === 'SINGLE'"
+          v-model="userId"
+          label="用户ID"
+        ></v-text-field>
+        <v-text-field
+          v-show="deliver === 'COURSE_LIKE'"
+          v-model="userId"
+          label="课程ID"
+        ></v-text-field>
+        <v-switch
+          v-model="canDuplicate"
+          :label="
+            `是否可以发放给已经收到过该优惠券的用户: ${canDuplicate.toString()}`
+          "
+        ></v-switch>
+        <v-text-field v-model="nums" label="发放数量"></v-text-field>
+      </form>
+      <v-btn class="ma-0 info" @click="handleDeliverCoupon">
+        确认
+      </v-btn>
+    </v-dialog>
+  </div>
+</template>
+
+<script>
+import CouponItem from "@/components/CouponItem.vue";
+import { getCouponList, deliverCoupon } from "@/api/coupon";
+
+export default {
+  name: "CouponList",
+  components: {
+    CouponItem,
+  },
+  data() {
+    return {
+      couponList: [],
+
+      couponId: "",
+      deliverList_: ["SINGLE", "COURSE_LIKE", "ALL"],
+      deliverList: ["指定单一用户", "为某课程点赞的用户", "所有用户"],
+      deliver: "SINGLE",
+      userId: "",
+      courseId: "",
+      canDuplicate: false,
+      nums: 1,
+
+      showDeliverDialog: false,
+      showSuccessDialog: false,
+      showFailDialog: false,
+      msg: "",
+    };
+  },
+  mounted() {
+    getCouponList().then((res) => {
+      this.couponList = res || [];
+    });
+  },
+  methods: {
+    handleDeliverClick: function(couponId) {
+      console.log("****", couponId);
+      this.couponId = couponId;
+      this.showDeliverDialog = true;
+    },
+
+    handleDeliverCoupon() {
+      const extra = {
+        userId: this.userId,
+        courseId: this.courseId,
+      };
+      const payload = {
+        couponId: this.couponId,
+        deliver: this.deliverList[this.deliverList_.indexOf(this.deliver)],
+        canDuplicate: this.canDuplicate,
+        nums: this.nums,
+        extra: JSON.stringify(extra),
+      };
+      console.log(payload);
+      deliverCoupon(payload).then((res) => {
+        console.log(res);
+        if (res.code === 1) {
+          this.showSuccessDialog = true;
+          setTimeout(() => {
+            this.showSuccessDialog = false;
+          }, 1000);
+        } else {
+          this.showFailDialog = true;
+          this.msg = res.msg;
+          setTimeout(() => {
+            this.showFailDialog = false;
+          }, 1000);
+        }
+      });
+    },
+  },
+};
+</script>