vbea пре 2 година
родитељ
комит
cdb7b1c557

+ 83 - 3
Strides-Admin/src/http/api/voucher.js

@@ -1,9 +1,89 @@
-import {get, post} from '../http'
+import {get, put, post, del} from '../http'
 
-const prefix = "";
+const prefix = "dawn/api/v1/";
 
 const voucher = {
-  
+  /**
+   * 向指定用户发放优惠券
+   * @param {Object} data {userPk,voucherId,remarks}
+   */
+  issueVoucher2User(data) {
+    return post(prefix + "issue-voucher-to-single-user", data)
+  },
+  /**
+   * 获取用户列表(模糊查询)
+   * @param {Object} data {email}
+   */
+  getIssueUserOptions(data) {
+    return get(prefix + "user-select", data)
+  },
+  /**
+   * 获取用户优惠券表格(分页)
+   * @param {Object} data {pageNum,pageSize,pageCriteria: {voucherStatus, criteria}}
+   */
+  getIssuanceVoucherPages(data) {
+    return post(prefix + "user-voucher-pages", data)
+  },
+  /**
+   * 获取优惠券兑换码
+   */
+  getVoucherRedemCode() {
+    return get(prefix + "redemption-codes")
+  },
+  /**
+   * 获取优惠券发放方式选择项
+   */
+  getRedemMethodOptions() {
+    return get(prefix + "redemption-method-select")
+  },
+  /**
+   * 获取优惠券表格(分页)
+   * @param {Object} data {pageNum,pageSize,pageCriteria: {criteria}}
+   */
+  getVoucherPages(data) {
+    return post(prefix + "voucher-pages", data)
+  },
+  /**
+   * 获取优惠券选择项(模糊查询)
+   * @param {Object} data {voucherName}
+   */
+  getVoucherOptions(data) {
+    return get(prefix + "voucher-select", data)
+  },
+  /**
+   * 获取优惠券类型选择项
+   */
+  getVoucherTypeOptions() {
+    return get(prefix + "voucher-type-select")
+  },
+  /**
+   * 创建优惠券
+   * @param {Object} data {VoucherForm}
+   */
+  createVoucher(data) {
+    return post(prefix + "vouchers", data)
+  },
+  /**
+   * 更新优惠券
+   * @param {Object} data {VoucherForm}
+   */
+  updateVoucher(data) {
+    return put(prefix + "vouchers", data)
+  },
+  /**
+   * 查看优惠券信息
+   * @param {String} voucherId 优惠券ID
+   */
+  viewVoucher(voucherId) {
+    return get(prefix + "vouchers/" + voucherId)
+  },
+  /**
+   * 删除优惠券
+   * @param {String} voucherId 优惠券ID
+   */
+  deleteVoucher(voucherId) {
+    return del(prefix + "vouchers/" + voucherId)
+  }
 }
 
 export default voucher;

+ 18 - 0
Strides-Admin/src/router/VoucherRouter.js

@@ -38,6 +38,24 @@ export default {
         icon: 'sidebar-submenu-item',
         activeIcon: 'sidebar-submenu-item-active',
       },
+    },
+    {
+      path: '/voucher-management/voucher/add',
+      component: () => import('@/views/voucher/detail'),
+      name: 'voucher-add',
+      meta: {
+        title: 'Create Vouchers',
+        activeMenu: '/voucher-management/voucher'
+      }
+    },
+    {
+      path: '/voucher-management/voucher/update/:id',
+      component: () => import('@/views/voucher/detail'),
+      name: 'voucher-update',
+      meta: {
+        title: 'Update Vouchers',
+        activeMenu: '/voucher-management/voucher'
+      }
     }
   ]
 }

+ 28 - 2
Strides-Admin/src/views/site2/detail.vue

@@ -285,6 +285,23 @@
                 value-format="HH:mm"/>
             </el-form-item>
           </el-col>
+          <el-col :xs="24" :md="12" :lg="8">
+            <el-form-item
+              label="Grace Period:"
+              label-width="140px"
+              prop="gracePeriod"
+              style="position: relative;">
+              <template slot="label">
+                <span>Grace Period:</span>
+                <span class="label-unit">(minutes)</span>
+              </template>
+              <el-input
+                maxlength="4"
+                v-model.number="siteForm.gracePeriod"
+                type="number"
+                class="input-text"/>
+            </el-form-item>
+          </el-col>
           <el-col :xs="24" :md="12" :lg="8">
             <el-form-item
               label="Idle Fee:"
@@ -309,6 +326,7 @@
               </template>
               <el-input
                 maxlength="4"
+                type="number"
                 v-model.number="siteForm.everyMinute"
                 class="input-text"/>
             </el-form-item>
@@ -434,6 +452,7 @@ export default {
         effectiveDate: "",//idle Fee
         effectiveFrom: "",//idle Fee
         effectiveTo: "",//idle Fee
+        gracePeriod: "",//idle Fee
         //预订部分
         enableReservation: false,//预订
         timeLimit: null,//预订
@@ -546,6 +565,11 @@ export default {
           trigger: 'change',
           message: 'Effective To is required'
         },
+        gracePeriod: {
+          required: true,
+          trigger: 'change',
+          message: 'Grace Period is required'
+        },
         everyMinute: [{
           required: true,
           trigger: 'blur',
@@ -998,9 +1022,11 @@ export default {
   }
   
   .label-unit {
-    left: 20px;
-    bottom: -13px;
+    left: 0;
+    bottom: 0;
+    width: 125px;
     font-size: 12px;
+    line-height: 1;
     position: absolute;
   }
   

+ 215 - 0
Strides-Admin/src/views/voucher/DialogIssue.vue

@@ -0,0 +1,215 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    title="ISSUE VOUCHER"
+    :before-close="onHide">
+    <el-form
+      ref="dialogForm"
+      :model="form"
+      :rules="rules"
+      class="filter-container"
+      label-position="right"
+      label-width="140px">
+      <el-form-item
+        label="Select User:"
+        prop="userPk">
+        <el-select
+          v-model="form.userPk"
+          class="dialog-form-item"
+          filterable
+          remote
+          clearable
+          :remote-method="getUserOptions"
+          placeholder="Search user email">
+          <el-option
+            v-for="(item,index) in options.user"
+            :key="index"
+            :label="item.email"
+            :value="item.userPk"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        label="Select Voucher:"
+        prop="voucherId">
+        <el-select
+          v-model="form.voucherId"
+          class="dialog-form-item"
+          filterable
+          remote
+          clearable
+          :remote-method="getVoucherOptions"
+          placeholder="Search voucher name">
+          <el-option
+            v-for="(item,index) in options.voucher"
+            :key="index"
+            :label="item.voucherName"
+            :value="item.voucherId"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item
+        label="Remarks:">
+        <el-input
+          class="dialog-form-item"
+          v-model="form.remarks"
+          type="textarea"
+          maxlength="300"
+          :autosize="autoSize"/>
+      </el-form-item>
+      <div class="dialog-button">
+        <el-button
+          type="cancel"
+          @click="onHide">
+          CANCEL
+        </el-button>
+        <el-button
+          type="primary"
+          :loading="btnLoading"
+          @click="onSubmit">
+          SUBMIT
+        </el-button>
+      </div>
+    </el-form>
+  </el-dialog>
+</template>
+
+<script>
+import api from '../../http/api/voucher.js';
+export default {
+  name: "DialogIssue",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      form: {
+        userPk: "",
+        voucherId: "",
+        remarks: ""
+      },
+      rules: {
+        userPk: {
+          required: true,
+          trigger: "change",
+          message: "Please select user"
+        },
+        voucherId: {
+          required: true,
+          trigger: "change",
+          message: "Please select voucher"
+        }
+      },
+      options: {
+        user: [],
+        voucher: []
+      },
+      autoSize: {
+        minRows: 3,
+        maxRows: 8
+      },
+      btnLoading: false
+    };
+  },
+  watch: {
+    visible: {
+      handler(n, o) {
+        if (n) {
+          this.form.userPk = "";
+          this.form.voucherId = "";
+          this.form.remarks = "";
+          this.$nextTick(() => {
+            this.$refs.dialogForm.clearValidate();
+          })
+        }
+      }
+    }
+  },
+  mounted() {
+    this.getUserOptions();
+    this.getVoucherOptions();
+  },
+  methods: {
+    onHide(e, a) {
+      this.$emit("hide", a)
+    },
+    getUserOptions(word) {
+      api.getIssueUserOptions({
+        email: word
+      }).then(res => {
+        if (res.data) {
+          this.options.user = res.data
+        } else {
+          this.options.user = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.user = []
+      })
+    },
+    getVoucherOptions(word) {
+      api.getVoucherOptions({
+        voucherName: word
+      }).then(res => {
+        if (res.data) {
+          this.options.voucher = res.data
+        } else {
+          this.options.voucher = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.voucher = []
+      })
+    },
+    onSubmit() {
+      this.$refs.dialogForm.validate(result => {
+        if (result) {
+          this.onIssuance();
+        }
+      })
+    },
+    onIssuance() {
+      this.btnLoading = true;
+      api.issueVoucher2User(this.form).then(res => {
+        this.$message({
+          type: 'success',
+          message: "Successfully issued"
+        })
+        this.onHide(false, true)
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      }).finally(() => {
+        this.btnLoading = false;
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.dialog-form-item {
+  width: 100%;
+  max-width: 350px;
+}
+.dialog-form-item >>> .el-textarea__inner {
+  font-family: sans-serif;
+}
+.dialog-button {
+  display: flex;
+  align-items: center;
+  padding-top: 30px;
+  justify-content: center;
+}
+</style>

+ 661 - 7
Strides-Admin/src/views/voucher/detail.vue

@@ -1,25 +1,679 @@
 <template>
-  <div>
-    
+  <div class="container" v-loading="loading.page">
+    <el-form
+      ref="form"
+      :model="form"
+      :rules="rules"
+      label-width="170px"
+      label-position="right">
+      <div class="content">
+        <div class="section-title">VOUCHER DETAILS</div>
+        <div class="flexcr">
+          <el-form-item
+            label="Name:"
+            prop="voucherName"
+            class="flex1">
+            <el-input
+              class="add-text"
+              v-model="form.voucherName"
+              maxlength="50"/>
+          </el-form-item>
+          <el-form-item
+            label="Validity Period:"
+            prop="voucherName"
+            class="flex1">
+            <el-date-picker
+              v-model="form.validityPeriod"
+              type="daterange"
+              value-format="yyyy-MM-dd"
+              start-placeholder="Start Date"
+              end-placeholder="End Date"
+              clearable
+              class="add-text"/>
+          </el-form-item>
+        </div>
+        <div class="flexcr">
+          <el-form-item
+            label="Description:"
+            prop="voucherDesc"
+            class="flex1">
+            <el-input
+              class="add-text"
+              v-model="form.voucherDesc"
+              type="textarea"
+              maxlength="500"
+              :autosize="autoSize"/>
+          </el-form-item>
+          <div class="flex1">
+            <el-form-item
+              label="Quantity:"
+              prop="voucherQuantity">
+              <el-input
+                class="add-text"
+                v-model="form.voucherQuantity"
+                type="number"
+                min="1"
+                max="999999"
+                maxlength="6"/>
+            </el-form-item>
+            <el-form-item
+              label="Redemption Method:"
+              prop="redemptionMethod"
+              class="flex1">
+              <el-select
+                class="add-text"
+                v-model="form.redemptionMethod">
+                <el-option
+                  v-for="(item, index) in options.redemp"
+                  :key="index"
+                  :label="item"
+                  :value="item"/>
+              </el-select>
+            </el-form-item>
+          </div>
+        </div>
+        <div class="flexcr">
+          <el-form-item
+            label="Voucher Type:"
+            prop="voucherType"
+            class="flex1">
+            <el-select
+              class="add-text"
+              v-model="form.voucherType">
+              <el-option
+                v-for="(item, index) in options.type"
+                :key="index"
+                :label="item"
+                :value="item"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Points Value:"
+            prop="purchasePoints"
+            class="flex1">
+            <el-input
+              class="add-text"
+              v-model="form.purchasePoints"
+              type="number"
+              min="0"
+              max="999999"
+              maxlength="6"/>
+          </el-form-item>
+        </div>
+      </div>
+      <div class="content" v-if="form.voucherType !== 'Free Charging'">
+        <div class="section-title">VOUCHER CONFIGURATION</div>
+        <div class="flexcr">
+          <el-form-item
+            :label="'Set ' + (form.voucherType || 'Voucher Value')"
+            prop="voucherValue"
+            class="flex1">
+            <el-input
+              class="add-text"
+              v-model="form.voucherValue"
+              type="number"
+              min="0"
+              max="999999"
+              maxlength="6"/>
+          </el-form-item>
+          <el-form-item
+            label="Redemption Code:"
+            prop="redemptionCode"
+            class="flex1">
+            <el-input
+              class="add-text"
+              v-model="form.redemptionCode"
+              readonly>
+              <el-button
+                slot="append"
+                icon="el-icon-refresh"
+                :loading="loading.redem"
+                title="Refresh New Redemption Code"
+                @click="refreshVoucherCode"/>
+            </el-input>
+          </el-form-item>
+        </div>
+      </div>
+      <div class="content">
+        <div class="section-title">VOUCHER CONDITIONS</div>
+        <div class="flexcr">
+          <el-form-item
+            label="Country:"
+            prop="countryCode"
+            class="flex1">
+            <el-select
+              class="add-text"
+              v-model="form.countryCode">
+              <el-option
+                v-for="item in options.country"
+                :key="item.value"
+                :label="item.name"
+                :value="item.value"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            label="Specific Sites:"
+            prop="sitePks"
+            class="flex1">
+            <el-select
+              class="add-text"
+              v-model="form.sitePks"
+              filterable
+              remote
+              multiple
+              clearable
+              :remote-method="getAllSite"
+              placeholder="Select with search">
+              <el-option
+                v-for="(item, index) in options.site"
+                :key="index"
+                :label="item.siteName"
+                :value="item.sitePk"/>
+            </el-select>
+          </el-form-item>
+        </div>
+        <div class="flexcr">
+          <el-form-item
+            label="Time Period:"
+            prop="timePeriod"
+            class="flex1">
+            <el-time-picker
+              v-model="form.timePeriod"
+              is-range
+              format="HH:mm"
+              value-format="HH:mm"
+              start-placeholder="Start Time"
+              end-placeholder="End Time"
+              clearable
+              class="add-text"
+              @change="changeTimePeriod"/>
+          </el-form-item>
+          <el-form-item
+            label="Charge Types:"
+            prop="chargeType"
+            class="flex1">
+            <el-select
+              class="add-text"
+              v-model="form.chargeType"
+              clearable>
+              <el-option
+                v-for="(item, index) in options.charge"
+                :key="index"
+                :label="item"
+                :value="item"/>
+            </el-select>
+          </el-form-item>
+        </div>
+        <div class="flexcr">
+          <el-form-item
+            label="Spend Range:"
+            prop="minSpend"
+            class="flex1">
+            <div class="add-text flexc">
+              <el-input
+                class="flex1"
+                v-model="form.minSpend"
+                type="number"
+                min="0"
+                max="99999"
+                maxlength="6"
+                placeholder="Minimum Spend"/>
+              <span style="padding: 0 5px;">-</span>
+              <el-input
+                class="flex1"
+                v-model="form.maxSpend"
+                type="number"
+                min="1"
+                max="999999"
+                maxlength="6"
+                placeholder="Maximum Spend"/>
+            </div>
+          </el-form-item>
+          <el-form-item
+            label="Day Selection:"
+            prop="days"
+            class="flex1">
+            <el-select
+              class="add-text"
+              v-model="form.days"
+              multiple
+              clearable>
+              <el-option
+                v-for="(item, index) in options.days"
+                :key="index"
+                :label="item"
+                :value="item"/>
+            </el-select>
+          </el-form-item>
+        </div>
+      </div>
+      <div class="content flexcr">
+        <div class="buttons">
+          <el-button
+            @click="onBack"
+            type="primary"
+            class="cancel-button">
+            CANCEL
+          </el-button>
+          <el-button
+            @click="onClickSave"
+            type="primary"
+            :loading="loading.save">
+            &nbsp; &nbsp;SAVE&nbsp; &nbsp;
+          </el-button>
+        </div>
+        <div class="update-by" v-if="isEdit">
+          <span
+            class="add-text"
+            :title='"CREATED BY " + form.createdBy + " ON " + form.createdOn'>
+            LAST UPDATED BY {{form.updatedBy}} TIMESTAMP: {{form.updatedOn}}
+          </span>
+        </div>
+      </div>
+    </el-form>
   </div>
 </template>
 
 <script>
+import api from '../../http/api/voucher.js';
+import site from '../../http/api/site'
+import settings from '../../settings.js'
 export default {
   data() {
     return {
-      
+      loading: {
+        page: true,
+        save: false,
+        redem: false
+      },
+      isEdit: false,
+      form: {
+        voucherId: "",
+        voucherName: "",
+        voucherDesc: "",
+        voucherType: "",
+        datePeriodStart: "",
+        datePeriodEnd: "",
+        voucherQuantity: "1",
+        redemptionMethod: "",
+        purchasePoints: "",
+        voucherValue: "",
+        redemptionCode: "",
+        countryCode: settings.defaultCountry,
+        timePeriodStart: "",
+        timePeriodEnd: "",
+        minSpend: "",
+        maxSpend: "",
+        chargeType: "",
+        sitePks: [],
+        days: [],
+        timePeriod: ["",""],
+        validityPeriod: []
+      },
+      options: {
+        days: [],
+        site: [],
+        type: [],
+        charge: ["AC", "DC"],
+        redemp: [],
+        country: []
+      },
+      rules: {
+        voucherName: {
+          required: true,
+          trigger: "blur",
+          message: "Please input voucher name",
+        },
+        validityPeriod: {
+          required: true,
+          trigger: "change",
+          message: "Please select validity date"
+        },
+        voucherQuantity: {
+          required: true,
+          trigger: "blur",
+          message: "Please input quantity",
+        },
+        redemptionMethod: {
+          required: true,
+          trigger: "change",
+          message: "Please select redemption method"
+        },
+        voucherType: {
+          required: true,
+          trigger: "change",
+          message: "Please select voucher type"
+        },
+        purchasePoints: {
+          required: true,
+          trigger: "blur",
+          message: "Please input points value",
+        },
+        voucherValue: {
+          required: true,
+          trigger: "blur",
+          message: "Please input voucher value",
+        },
+        redemptionCode: {
+          required: true,
+          trigger: "change",
+          message: "Please click the refresh button on the right to generate code",
+        },
+        sitePks: {
+          required: true,
+          trigger: "change",
+          message: "Please select sites"
+        },
+        timePeriod: {
+          required: true,
+          trigger: "change",
+          validator: (rule, value, callback) => {
+            if (value && value.length > 0) {
+              if (value[0] && value[1]) {
+                callback()
+              } else {
+                callback("Please select time period")
+              }
+            } else {
+              callback("Please select time period")
+            }
+          }
+        },
+      },
+      autoSize: {
+        minRows: 4,
+        maxRows: 4
+      }
     };
   },
   created() {
-    
+    this.getCountryOptions();
+    if (this.$route.params.id) {
+      this.isEdit = true;
+      this.getVoucherDetail();
+    }
   },
   methods: {
-    
+    init() {
+      if (this.form.datePeriodStart && this.form.datePeriodEnd) {
+        this.form.validityPeriod = [
+          this.form.datePeriodStart,
+          this.form.datePeriodEnd
+        ]
+      }
+      if (this.form.timePeriodStart && this.form.timePeriodEnd) {
+        this.form.timePeriod = [
+          this.form.timePeriodStart,
+          this.form.timePeriodEnd
+        ]
+      }
+      this.changeTimePeriod();
+    },
+    onBack() {
+      this.$nextTick(() => {
+        this.$router.replace({
+          path: "/voucher-management/voucher"
+        })
+      })
+    },
+    getCountryOptions() {
+      this.options.days = [];
+      for (let i = 1; i <= 31; i++) {
+        this.options.days.push(i);
+      }
+      site.getCountryList().then(res => {
+        if (res.data) {
+          this.options.country = res.data
+        } else {
+          this.options.country = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.country = []
+      }).finally(() => {
+        this.getRedemTypeOptions();
+        this.getVoucherTypeOptions();
+        this.getAllSite();
+      })
+    },
+    getRedemTypeOptions() {
+      api.getRedemMethodOptions().then(res => {
+        if (res.data) {
+          this.options.redemp = res.data
+        } else {
+          this.options.redemp = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.redemp = []
+      })
+    },
+    getVoucherTypeOptions() {
+      api.getVoucherTypeOptions().then(res => {
+        if (res.data) {
+          this.options.type = res.data
+        } else {
+          this.options.type = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.type = []
+      }).finally(() => {
+        this.loading.page = false;
+      })
+    },
+    getAllSite(word) {
+      site.getAllSiteList({siteName: word}).then(res => {
+        if (res.data && res.data.length > 0) {
+          this.options.site = res.data
+        } else {
+          this.options.site = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.site = []
+      });
+    },
+    getChargeTypeOptions() {
+      site.getChargeTypeList().then(res => {
+        if (res.data) {
+          this.options.charge = res.data
+        } else {
+          this.options.charge = []
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.options.charge = []
+      });
+    },
+    getVoucherDetail() {
+      api.viewVoucher(this.$route.params.id).then(res => {
+        if (res.data) {
+          this.form = res.data;
+          this.init();
+        }
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      }).finally(() => {
+        this.loading.page = false;
+      })
+    },
+    refreshVoucherCode() {
+      this.loading.redem = true;
+      api.getVoucherRedemCode().then(res => {
+        if (res.data) {
+          this.form.redemptionCode = res.data
+        }
+      }).catch(() => {
+        this.$message({
+          message: err,
+          type: 'error',
+          duration: 3000,
+        })
+        this.form.redemptionCode = ""
+      }).finally(() => {
+        this.loading.redem = false;
+      })
+    },
+    changeTimePeriod() {
+      if (this.form.timePeriod == null || this.form.timePeriod == undefined) {
+        this.form.timePeriod = ["", ""];
+      }
+    },
+    onClickSave() {
+      this.$refs.form.validate(result => {
+        if (result) {
+          this.loading.save = true;
+          if (this.form.validityPeriod.length == 2) {
+            this.form.datePeriodStart = this.form.validityPeriod[0]
+            this.form.datePeriodEnd = this.form.validityPeriod[1]
+          }
+          if (this.form.timePeriod.length == 2) {
+            this.form.timePeriodStart = this.form.timePeriod[0]
+            this.form.timePeriodEnd = this.form.timePeriod[1]
+          }
+          this.loading.save = true;
+          this.isEdit ? this.updateVoucher() : this.createVoucher();
+        }
+      });
+    },
+    createVoucher() {
+      api.createVoucher(this.form).then(res => {
+        this.$message({
+          type: 'success',
+          message: "Successfully added"
+        });
+        this.onBack();
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        });
+      }).finally(() => {
+        this.loading.save = false;
+      });
+    },
+    updateVoucher() {
+      api.updateVoucher(this.form).then(res => {
+        this.$message({
+          type: 'success',
+          message: "Successfully updated"
+        });
+        this.onBack();
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        });
+      }).finally(() => {
+        this.loading.save = false;
+      });
+    }
   }
 }
 </script>
 
-<style scoped>
-
+<style lang="scss" scoped>
+  @import '../../styles/variables.scss';
+  .container {
+    width: 100%;
+    padding: 20px 60px;
+    min-height: $mainAppMinHeight;
+    background-color: #F0F5FC;
+  }
+  .content {
+    margin: 0 8px 16px;
+    padding: 15px 80px;
+    border-radius: 6px;
+    background-color: white;
+  }
+  
+  .section-title {
+    color: #333;
+    margin-top: 20px;
+    margin-bottom: 30px;
+    font-size: 15px;
+    user-select: none;
+    line-height: 24px;
+    font-weight: bold;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+  .add-text {
+    width: 100%;
+    min-width: 150px;
+    max-width: 300px;
+  }
+  .add-text ::v-deep .el-textarea__inner {
+    font-family: sans-serif;
+  }
+  .form-photo {
+    flex: 1;
+    ::v-deep .el-form-item__label {
+      padding: 12px;
+      line-height: 16px;
+    }
+    .photo-uploader {
+      margin-right: 10px;
+      .uploader-image {
+        width: 180px;
+        height: 120px;
+        text-align: left;
+      }
+      ::v-deep img {
+        object-fit: cover;
+      }
+      .avatar-uploader-icon {
+        border: 1px dashed #d9d9d9;
+        border-radius: 6px;
+        cursor: pointer;
+        font-size: 28px;
+        color: #8c939d;
+        width: 120px;
+        height: 120px;
+        line-height: 120px;
+        text-align: center;
+      }
+    }
+  }
+  .hr {
+    height: 2px;
+    margin: 10px -40px;
+    background-color: #F0F5FC;
+  }
+  .buttons {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+  @media screen and (max-width: 500px) {
+    .container {
+      padding: 0px;
+    }
+    .content {
+      padding: 15px 30px;
+    }
+  }
 </style>

+ 100 - 23
Strides-Admin/src/views/voucher/index.vue

@@ -4,7 +4,6 @@
       <div class="filter-flex-button">
         <el-button
           type="primary"
-          icon="el-icon-plus"
           @click="onClickAdd">
           CREATE VOUCHER
         </el-button>
@@ -16,51 +15,73 @@
       <el-table-column
         label="Voucher ID"
         align="center"
-        prop="rfidNumber"
-        min-width="120"/>
+        prop="voucherId"
+        min-width="140"/>
       <el-table-column
         label="Voucher Name"
         align="center"
-        prop="rfidNumber"
-        min-width="120"/>
+        prop="voucherName"
+        min-width="180"/>
       <el-table-column
         label="Validity Period"
-        align="center"
+        align="left"
         prop="rfidNumber"
-        min-width="120"/>
+        min-width="130">
+        <template slot-scope="{row}">
+          <div>Start: {{row.periodStart}}</div>
+          <div>End: {{row.periodEnd}}</div>
+        </template>
+      </el-table-column>
       <el-table-column
         label="Quantity"
         align="center"
-        prop="rfidNumber"
-        min-width="120"/>
+        prop="voucherQuantity"
+        min-width="100"/>
       <el-table-column
         label="Voucher Type"
         align="center"
-        prop="rfidNumber"
+        prop="voucherType"
         min-width="120"/>
       <el-table-column
         label="Redemption Method"
         align="center"
-        prop="rfidNumber"
-        min-width="160"/>
+        prop="redemptionMethod"
+        min-width="180"/>
       <el-table-column
         label="Status"
         align="center"
-        prop="rfidNumber"
-        min-width="100"/>
+        prop="dataStatus"
+        min-width="80"/>
       <el-table-column
         label="Action"
         align="center"
-        prop="rfidNumber"
-        min-width="80">
-        
+        width="80"
+        v-if="!$route.meta.onlyView">
+        <template v-slot="{ row }">
+          <el-dropdown
+            class="action-dropdown"
+            @command="(v) => handleCommand(v, row)"
+            v-if="row.dataStatus != 'Inactive'">
+            <i class="el-icon-more icon-action"></i>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item
+                command="onClickEdit">
+                Edit
+              </el-dropdown-item>
+              <el-dropdown-item
+                command="onClickDelete">
+                Delete
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
       </el-table-column>
     </el-table>
     <div class="right">
       <Pagination
         v-show="table.total > 0"
         :total="table.total"
-        :page.sync="params.pageNo"
+        :page.sync="params.pageNum"
         :limit.sync="params.pageSize"
         @pagination="getTableData" />
     </div>
@@ -68,11 +89,13 @@
 </template>
 
 <script>
+import api from '../../http/api/voucher.js';
+import Pagination from '@/components/Pagination'
 export default {
   data() {
     return {
       params: {
-        pageNo: 1,
+        pageNum: 1,
         pageSize: 10,
         pageVo: {
           criteria: "",
@@ -85,19 +108,73 @@ export default {
       },
     };
   },
+  components: {Pagination},
   created() {
-    
+    this.toSearch();
   },
   methods: {
     toSearch() {
-      this.params.pageNo = 1;
+      this.params.pageNum = 1;
       this.getTableData();
     },
     getTableData() {
-      
+      this.table.loading = true;
+      api.getVoucherPages(this.params).then(res => {
+        if (res.data && res.data.totalRow) {
+          this.table.data = res.data.records
+          this.table.total = res.data.totalRow
+        } else {
+          this.table.data = []
+          this.table.total = 0
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+        this.table.data = []
+        this.table.total = 0
+      }).finally(() => {
+        this.table.loading = false
+      })
+    },
+    handleCommand(cb, item) {
+      this[cb](item)
     },
     onClickAdd() {
-      
+      this.$router.push({
+        path: "/voucher-management/voucher/add"
+      })
+    },
+    onClickEdit(row) {
+      this.$router.push({
+        path: "/voucher-management/voucher/update/" + row.voucherId
+      })
+    },
+    onClickDelete(row) {
+      this.$confirm('Are you sure you want to delete this voucher?', 'Delete', {
+        confirmButtonText: 'Confirm',
+        cancelButtonText: 'Cancel',
+        type: 'warning'
+      }).then(res => {
+        this.deleteVoucher(row.voucherId);
+      })
+    },
+    deleteVoucher(id) {
+      this.table.loading = true;
+      api.deleteVoucher(id).then(res => {
+        this.$message({
+          type: 'success',
+          message: "Delete success."
+        })
+        this.getTableData()
+      }).catch(err => {
+        this.$message({
+          type: 'error',
+          message: err
+        })
+        this.table.loading = false;
+      })
     }
   }
 }

+ 118 - 5
Strides-Admin/src/views/voucher/issuance.vue

@@ -1,21 +1,134 @@
 <template>
-  <div>
-    
+  <div class="app-container">
+    <div class="filter-container filter-view">
+      <div class="filter-flex-button">
+        <el-button
+          type="primary"
+          @click="onClickIssue">
+          ISSUE VOUCHER
+        </el-button>
+      </div>
+    </div>
+    <el-table
+      v-loading="table.loading"
+      :data="table.data">
+      <el-table-column
+        label="User E-mail"
+        align="center"
+        prop="userEmail"
+        min-width="120"/>
+      <el-table-column
+        label="User"
+        align="center"
+        prop="userName"
+        min-width="120"/>
+      <el-table-column
+        label="Voucher ID"
+        align="center"
+        prop="voucherId"
+        min-width="140"/>
+      <el-table-column
+        label="Voucher Name"
+        align="center"
+        prop="voucherName"
+        min-width="150"/>
+      <el-table-column
+        label="Voucher Type"
+        align="center"
+        prop="voucherType"
+        min-width="120"/>
+      <el-table-column
+        label="Redemption Method"
+        align="center"
+        prop="redemptionMethod"
+        min-width="160"/>
+      <el-table-column
+        label="DateTime"
+        align="center"
+        prop="dateTime"
+        min-width="120"/>
+      <el-table-column
+        label="Status"
+        align="center"
+        prop="dataStatus"
+        min-width="80"/>
+    </el-table>
+    <div class="right">
+      <Pagination
+        v-show="table.total > 0"
+        :total="table.total"
+        :page.sync="params.pageNum"
+        :limit.sync="params.pageSize"
+        @pagination="getTableData" />
+    </div>
+    <DialogIssue
+      :visible="showIssueDialog"
+      @hide="hideIssueDialog"/>
   </div>
 </template>
 
 <script>
+import api from '../../http/api/voucher.js';
+import Pagination from '@/components/Pagination';
+import DialogIssue from './DialogIssue.vue';
 export default {
   data() {
     return {
-      
+      params: {
+        pageNum: 1,
+        pageSize: 10,
+        pageVo: {
+          criteria: "",
+          voucherStatus: "To Be Used"
+        }
+      },
+      table: {
+        data: [],
+        total: 0,
+        loading: false
+      },
+      showIssueDialog: false
     };
   },
+  components: {DialogIssue, Pagination},
   created() {
-    
+    this.toSearch();
   },
   methods: {
-    
+    toSearch() {
+      this.params.pageNum = 1;
+      this.getTableData();
+    },
+    getTableData() {
+      this.table.loading = true;
+      api.getIssuanceVoucherPages(this.params).then(res => {
+        if (res.data && res.data.totalRow) {
+          this.table.data = res.data.records
+          this.table.total = res.data.totalRow
+        } else {
+          this.table.data = []
+          this.table.total = 0
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+        this.table.data = []
+        this.table.total = 0
+      }).finally(() => {
+        this.table.loading = false
+      })
+    },
+    onClickIssue() {
+      this.showIssueDialog = true;
+    },
+    hideIssueDialog(e) {
+      this.showIssueDialog = false;
+      if (e) {
+        this.toSearch();
+      }
+    }
   }
 }
 </script>

+ 98 - 5
Strides-Admin/src/views/voucher/usage.vue

@@ -1,21 +1,114 @@
 <template>
-  <div>
-    
+  <div class="app-container">
+    <div class="filter-container filter-view">
+      
+    </div>
+    <el-table
+      v-loading="table.loading"
+      :data="table.data">
+      <el-table-column
+        label="User E-mail"
+        align="center"
+        prop="userEmail"
+        min-width="120"/>
+      <el-table-column
+        label="User"
+        align="center"
+        prop="userName"
+        min-width="120"/>
+      <el-table-column
+        label="Voucher ID"
+        align="center"
+        prop="voucherId"
+        min-width="140"/>
+      <el-table-column
+        label="Voucher Name"
+        align="center"
+        prop="voucherName"
+        min-width="150"/>
+      <el-table-column
+        label="Voucher Type"
+        align="center"
+        prop="voucherType"
+        min-width="120"/>
+      <el-table-column
+        label="Redemption Method"
+        align="center"
+        prop="redemptionMethod"
+        min-width="160"/>
+      <el-table-column
+        label="DateTime"
+        align="center"
+        prop="dateTime"
+        min-width="120"/>
+      <el-table-column
+        label="Status"
+        align="center"
+        prop="dataStatus"
+        min-width="80"/>
+    </el-table>
+    <div class="right">
+      <Pagination
+        v-show="table.total > 0"
+        :total="table.total"
+        :page.sync="params.pageNum"
+        :limit.sync="params.pageSize"
+        @pagination="getTableData" />
+    </div>
   </div>
 </template>
 
 <script>
+import api from '../../http/api/voucher.js';
+import Pagination from '@/components/Pagination'
 export default {
   data() {
     return {
-      
+      params: {
+        pageNum: 1,
+        pageSize: 10,
+        pageVo: {
+          criteria: "",
+          voucherStatus: "Used"
+        }
+      },
+      table: {
+        data: [],
+        total: 0,
+        loading: false
+      }
     };
   },
+  components: {Pagination},
   created() {
-    
+    this.toSearch();
   },
   methods: {
-    
+    toSearch() {
+      this.params.pageNum = 1;
+      this.getTableData();
+    },
+    getTableData() {
+      this.table.loading = true;
+      api.getIssuanceVoucherPages(this.params).then(res => {
+        if (res.data && res.data.totalRow) {
+          this.table.data = res.data.records
+          this.table.total = res.data.totalRow
+        } else {
+          this.table.data = []
+          this.table.total = 0
+        }
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+        this.table.data = []
+        this.table.total = 0
+      }).finally(() => {
+        this.table.loading = false
+      })
+    }
   }
 }
 </script>