ソースを参照

feat[litemall-admin, litemall-admin-api, litemall-db]: 管理后台支持优惠券管理

Junling Bu 7 年 前
コミット
f6cd92a871

+ 139 - 0
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminCouponController.java

@@ -0,0 +1,139 @@
+package org.linlinjava.litemall.admin.web;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.linlinjava.litemall.admin.annotation.LoginAdmin;
+import org.linlinjava.litemall.core.util.ResponseUtil;
+import org.linlinjava.litemall.core.validator.Order;
+import org.linlinjava.litemall.core.validator.Sort;
+import org.linlinjava.litemall.db.domain.LitemallCoupon;
+import org.linlinjava.litemall.db.domain.LitemallCouponUser;
+import org.linlinjava.litemall.db.domain.LitemallTopic;
+import org.linlinjava.litemall.db.service.LitemallCouponService;
+import org.linlinjava.litemall.db.service.LitemallCouponUserService;
+import org.linlinjava.litemall.db.service.LitemallTopicService;
+import org.linlinjava.litemall.db.util.CouponConstant;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/admin/coupon")
+@Validated
+public class AdminCouponController {
+    private final Log logger = LogFactory.getLog(AdminCouponController.class);
+
+    @Autowired
+    private LitemallCouponService couponService;
+    @Autowired
+    private LitemallCouponUserService couponUserService;
+
+    @GetMapping("/list")
+    public Object list(@LoginAdmin Integer adminId,
+                       String name, Short type, Short status,
+                       @RequestParam(defaultValue = "1") Integer page,
+                       @RequestParam(defaultValue = "10") Integer limit,
+                       @Sort @RequestParam(defaultValue = "add_time") String sort,
+                       @Order @RequestParam(defaultValue = "desc") String order) {
+        if (adminId == null) {
+            return ResponseUtil.unlogin();
+        }
+
+        List<LitemallCoupon> couponList = couponService.querySelective(name, type, status, page, limit, sort, order);
+        int total = couponService.countSelective(name, type, status, page, limit, sort, order);
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", total);
+        data.put("items", couponList);
+
+        return ResponseUtil.ok(data);
+    }
+
+    @GetMapping("/listuser")
+    public Object listuser(@LoginAdmin Integer adminId,
+                       Integer userId, Integer couponId, Short status,
+                       @RequestParam(defaultValue = "1") Integer page,
+                       @RequestParam(defaultValue = "10") Integer limit,
+                       @Sort @RequestParam(defaultValue = "add_time") String sort,
+                       @Order @RequestParam(defaultValue = "desc") String order) {
+        if (adminId == null) {
+            return ResponseUtil.unlogin();
+        }
+
+        List<LitemallCouponUser> couponList = couponUserService.queryList(userId, couponId, status, page, limit, sort, order);
+        int total = couponUserService.countList(userId, couponId, status, page, limit, sort, order);
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", total);
+        data.put("items", couponList);
+
+        return ResponseUtil.ok(data);
+    }
+
+    private Object validate(LitemallCoupon coupon) {
+        String name = coupon.getName();
+        if(StringUtils.isEmpty(name)){
+            return ResponseUtil.badArgument();
+        }
+        return null;
+    }
+
+    @PostMapping("/create")
+    public Object create(@LoginAdmin Integer adminId, @RequestBody LitemallCoupon coupon) {
+        if (adminId == null) {
+            return ResponseUtil.unlogin();
+        }
+        Object error = validate(coupon);
+        if (error != null) {
+            return error;
+        }
+
+        // 如果是兑换码类型,则这里需要生存一个兑换码
+        if (coupon.getType().equals(CouponConstant.TYPE_CODE)){
+            String code = couponService.generateCode();
+            coupon.setCode(code);
+        }
+
+        couponService.add(coupon);
+        return ResponseUtil.ok(coupon);
+    }
+
+    @GetMapping("/read")
+    public Object read(@LoginAdmin Integer adminId, @NotNull Integer id) {
+        if (adminId == null) {
+            return ResponseUtil.unlogin();
+        }
+
+        LitemallCoupon coupon = couponService.findById(id);
+        return ResponseUtil.ok(coupon);
+    }
+
+    @PostMapping("/update")
+    public Object update(@LoginAdmin Integer adminId, @RequestBody LitemallCoupon coupon) {
+        if (adminId == null) {
+            return ResponseUtil.unlogin();
+        }
+        Object error = validate(coupon);
+        if (error != null) {
+            return error;
+        }
+        if (couponService.updateById(coupon) == 0) {
+            return ResponseUtil.updatedDataFailed();
+        }
+        return ResponseUtil.ok(coupon);
+    }
+
+    @PostMapping("/delete")
+    public Object delete(@LoginAdmin Integer adminId, @RequestBody LitemallCoupon coupon) {
+        if (adminId == null) {
+            return ResponseUtil.unlogin();
+        }
+        couponService.deleteById(coupon.getId());
+        return ResponseUtil.ok();
+    }
+
+}

+ 2 - 2
litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminGoodsController.java

@@ -185,10 +185,10 @@ public class AdminGoodsController {
         // 检查是否存在购物车商品或者订单商品
         // 如果存在则拒绝修改商品。
         if(orderGoodsService.checkExist(id)){
-            return ResponseUtil.fail(GOODS_UPDATE_NOT_ALLOWED, "商品已经在购物车中,不能修改");
+            return ResponseUtil.fail(GOODS_UPDATE_NOT_ALLOWED, "商品已经在订单中,不能修改");
         }
         if(cartService.checkExist(id)){
-            return ResponseUtil.fail(GOODS_UPDATE_NOT_ALLOWED, "商品已经在订单中,不能修改");
+            return ResponseUtil.fail(GOODS_UPDATE_NOT_ALLOWED, "商品已经在购物车中,不能修改");
         }
 
         // 开启事务管理

+ 49 - 0
litemall-admin/src/api/coupon.js

@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+
+export function listCoupon(query) {
+  return request({
+    url: '/coupon/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function createCoupon(data) {
+  return request({
+    url: '/coupon/create',
+    method: 'post',
+    data
+  })
+}
+
+export function readCoupon(id) {
+  return request({
+    url: '/coupon/read',
+    method: 'get',
+    params: { id }
+  })
+}
+
+export function updateCoupon(data) {
+  return request({
+    url: '/coupon/update',
+    method: 'post',
+    data
+  })
+}
+
+export function deleteCoupon(data) {
+  return request({
+    url: '/coupon/delete',
+    method: 'post',
+    data
+  })
+}
+
+export function listCouponUser(query) {
+  return request({
+    url: '/coupon/listuser',
+    method: 'get',
+    params: query
+  })
+}

+ 14 - 1
litemall-admin/src/router/index.js

@@ -227,7 +227,20 @@ export const asyncRouterMap = [
         path: 'ad',
         component: () => import('@/views/promotion/ad'),
         name: 'ad',
-        meta: { title: '广告列表', noCache: true }
+        meta: { title: '广告管理', noCache: true }
+      },
+      {
+        path: 'coupon',
+        component: () => import('@/views/promotion/coupon'),
+        name: 'coupon',
+        meta: { title: '优惠券管理', noCache: true }
+      },
+      {
+        path: 'couponDetail',
+        component: () => import('@/views/promotion/couponDetail'),
+        name: 'couponDetail',
+        meta: { title: '优惠券详情', noCache: true },
+        hidden: true
       },
       {
         path: 'topic',

+ 453 - 0
litemall-admin/src/views/promotion/coupon.vue

@@ -0,0 +1,453 @@
+<template>
+  <div class="app-container">
+
+    <!-- 查询和其他操作 -->
+    <div class="filter-container">
+      <el-input v-model="listQuery.name" clearable class="filter-item" style="width: 200px;" placeholder="请输入优惠券标题"/>
+      <el-select v-model="listQuery.type" clearable style="width: 200px" class="filter-item" placeholder="请选择优惠券类型">
+        <el-option v-for="type in typeOptions" :key="type.value" :label="type.label" :value="type.value"/>
+      </el-select>
+      <el-select v-model="listQuery.status" clearable style="width: 200px" class="filter-item" placeholder="请选择优惠券状态">
+        <el-option v-for="type in statusOptions" :key="type.value" :label="type.label" :value="type.value"/>
+      </el-select>
+      <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
+      <el-button class="filter-item" type="primary" icon="el-icon-edit" @click="handleCreate">添加</el-button>
+      <el-button :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">导出</el-button>
+    </div>
+
+    <!-- 查询结果 -->
+    <el-table v-loading="listLoading" :data="list" size="small" element-loading-text="正在查询中。。。" border fit highlight-current-row>
+
+      <el-table-column align="center" label="优惠券ID" prop="id" sortable/>
+
+      <el-table-column align="center" label="优惠券名称" prop="name"/>
+
+      <el-table-column align="center" label="介绍" prop="desc"/>
+
+      <el-table-column align="center" label="标签" prop="tag"/>
+
+      <el-table-column align="center" label="最低消费" prop="min">
+        <template slot-scope="scope">满{{ scope.row.min }}元可用</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="满减金额" prop="discount">
+        <template slot-scope="scope">减免{{ scope.row.discount }}元</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="每人限领" prop="limit">
+        <template slot-scope="scope">{{ scope.row.limit != 0 ? scope.row.limit : "不限" }}</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="商品使用范围" prop="goodsType">
+        <template slot-scope="scope">{{ scope.row.goodsType | formatGoodsType }}</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="优惠券类型" prop="type">
+        <template slot-scope="scope">{{ scope.row.type | formatType }}</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="优惠券数量" prop="total">
+        <template slot-scope="scope">{{ scope.row.total != 0 ? scope.row.total : "不限" }}</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="状态" prop="status">
+        <template slot-scope="scope">{{ scope.row.status | formatStatus }}</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="操作" width="300" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button type="primary" size="mini" @click="handleDetail(scope.row)">详情</el-button>
+          <el-button type="info" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
+          <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+    <!-- 添加或修改对话框 -->
+    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
+      <el-form ref="dataForm" :rules="rules" :model="dataForm" status-icon label-position="left" label-width="100px" style="width: 400px; margin-left:50px;">
+        <el-form-item label="优惠券名称" prop="name">
+          <el-input v-model="dataForm.name"/>
+        </el-form-item>
+        <el-form-item label="介绍" prop="desc">
+          <el-input v-model="dataForm.desc"/>
+        </el-form-item>
+        <el-form-item label="标签" prop="tag">
+          <el-input v-model="dataForm.tag"/>
+        </el-form-item>
+        <el-form-item label="最低消费" prop="min">
+          <el-input v-model="dataForm.min">
+            <template slot="append">元</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="满减金额" prop="discount">
+          <el-input v-model="dataForm.discount">
+            <template slot="append">元</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="每人限领" prop="limit">
+          <el-input v-model="dataForm.limit">
+            <template slot="append">张</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="分发类型" prop="type">
+          <el-select v-model="dataForm.type">
+            <el-option
+              v-for="type in typeOptions"
+              :key="type.value"
+              :label="type.label"
+              :value="type.value"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="优惠券数量" prop="total">
+          <el-input v-model="dataForm.total">
+            <template slot="append">张</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="有效期">
+          <el-radio-group v-model="dataForm.timeType">
+            <el-radio-button :label="0">领券相对天数</el-radio-button>
+            <el-radio-button :label="1">指定绝对时间</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-show="dataForm.timeType === 0">
+          <el-input v-model="dataForm.days">
+            <template slot="append">天</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item v-show="dataForm.timeType === 1">
+          <el-col :span="11">
+            <el-date-picker v-model="dataForm.startTime" type="datetime" placeholder="选择日期" style="width: 100%;"/>
+          </el-col>
+          <el-col :span="2" class="line">至</el-col>
+          <el-col :span="11">
+            <el-date-picker v-model="dataForm.endTime" type="datetime" placeholder="选择日期" style="width: 100%;"/>
+          </el-col>
+        </el-form-item>
+        <el-form-item label="商品限制范围">
+          <el-radio-group v-model="dataForm.goodsType">
+            <el-radio-button :label="0">全场通用</el-radio-button>
+            <el-radio-button :label="1">指定分类</el-radio-button>
+            <el-radio-button :label="2">指定商品</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-show="dataForm.goodsType === 1">
+          目前不支持
+        </el-form-item>
+        <el-form-item v-show="dataForm.goodsType === 2">
+          目前不支持
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取消</el-button>
+        <el-button v-if="dialogStatus=='create'" type="primary" @click="createData">确定</el-button>
+        <el-button v-else type="primary" @click="updateData">确定</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #20a0ff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 120px;
+  height: 120px;
+  line-height: 120px;
+  text-align: center;
+}
+.avatar {
+  width: 120px;
+  height: 120px;
+  display: block;
+}
+</style>
+
+<script>
+import { listCoupon, createCoupon, updateCoupon, deleteCoupon } from '@/api/coupon'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+
+const defaultTypeOptions = [
+  {
+    label: '通用领券',
+    value: 0
+  },
+  {
+    label: '注册赠券',
+    value: 1
+  },
+  {
+    label: '兑换码',
+    value: 2
+  }
+]
+
+const defaultStatusOptions = [
+  {
+    label: '正常',
+    value: 0
+  },
+  {
+    label: '已过期',
+    value: 1
+  },
+  {
+    label: '已下架',
+    value: 2
+  }
+]
+
+export default {
+  name: 'Coupon',
+  components: { Pagination },
+  filters: {
+    formatType(type) {
+      for (let i = 0; i < defaultTypeOptions.length; i++) {
+        if (type === defaultTypeOptions[i].value) {
+          return defaultTypeOptions[i].label
+        }
+      }
+      return ''
+    },
+    formatGoodsType(goodsType) {
+      if (goodsType === 0) {
+        return '全场通用'
+      } else if (goodsType === 1) {
+        return '指定分类'
+      } else {
+        return '指定商品'
+      }
+    },
+    formatStatus(status) {
+      if (status === 0) {
+        return '正常'
+      } else if (status === 1) {
+        return '已过期'
+      } else {
+        return '已下架'
+      }
+    }
+  },
+  data() {
+    return {
+      typeOptions: Object.assign({}, defaultTypeOptions),
+      statusOptions: Object.assign({}, defaultStatusOptions),
+      list: undefined,
+      total: 0,
+      listLoading: true,
+      listQuery: {
+        page: 1,
+        limit: 20,
+        name: undefined,
+        type: undefined,
+        status: undefined,
+        sort: 'add_time',
+        order: 'desc'
+      },
+      dataForm: {
+        id: undefined,
+        name: undefined,
+        desc: undefined,
+        tag: undefined,
+        total: 0,
+        discount: 0,
+        min: 0,
+        limit: 1,
+        type: 0,
+        status: 0,
+        goodsType: 0,
+        goodsValue: [],
+        timeType: 0,
+        days: 0,
+        startTime: null,
+        endTime: null
+      },
+      dialogFormVisible: false,
+      dialogStatus: '',
+      textMap: {
+        update: '编辑',
+        create: '创建'
+      },
+      rules: {
+        name: [
+          { required: true, message: '优惠券标题不能为空', trigger: 'blur' }
+        ]
+      },
+      downloadLoading: false
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    getList() {
+      this.listLoading = true
+      listCoupon(this.listQuery)
+        .then(response => {
+          this.list = response.data.data.items
+          this.total = response.data.data.total
+          this.listLoading = false
+        })
+        .catch(() => {
+          this.list = []
+          this.total = 0
+          this.listLoading = false
+        })
+    },
+    handleFilter() {
+      this.listQuery.page = 1
+      this.getList()
+    },
+    resetForm() {
+      this.dataForm = {
+        id: undefined,
+        name: undefined,
+        desc: undefined,
+        tag: undefined,
+        total: 0,
+        discount: 0,
+        min: 0,
+        limit: 1,
+        type: 0,
+        status: 0,
+        goodsType: 0,
+        goodsValue: [],
+        timeType: 0,
+        days: 0,
+        startTime: null,
+        endTime: null
+      }
+    },
+    handleCreate() {
+      this.resetForm()
+      this.dialogStatus = 'create'
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    createData() {
+      this.$refs['dataForm'].validate(valid => {
+        if (valid) {
+          createCoupon(this.dataForm)
+            .then(response => {
+              this.list.unshift(response.data.data)
+              this.dialogFormVisible = false
+              this.$notify.success({
+                title: '成功',
+                message: '创建优惠券成功'
+              })
+            })
+            .catch(response => {
+              this.$notify.error({
+                title: '失败',
+                message: response.data.errmsg
+              })
+            })
+        }
+      })
+    },
+    handleUpdate(row) {
+      this.dataForm = Object.assign({}, row)
+
+      this.dialogStatus = 'update'
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        if (this.dataForm.days === 0) {
+          this.dataForm.daysType = 1
+        } else {
+          this.dataForm.daysType = 0
+        }
+        this.$refs['dataForm'].clearValidate()
+      })
+    },
+    updateData() {
+      this.$refs['dataForm'].validate(valid => {
+        if (valid) {
+          updateCoupon(this.dataForm)
+            .then(() => {
+              for (const v of this.list) {
+                if (v.id === this.dataForm.id) {
+                  const index = this.list.indexOf(v)
+                  this.list.splice(index, 1, this.dataForm)
+                  break
+                }
+              }
+              this.dialogFormVisible = false
+              this.$notify.success({
+                title: '成功',
+                message: '更新优惠券成功'
+              })
+            })
+            .catch(response => {
+              this.$notify.error({
+                title: '失败',
+                message: response.data.errmsg
+              })
+            })
+        }
+      })
+    },
+    handleDelete(row) {
+      deleteCoupon(row)
+        .then(response => {
+          this.$notify.success({
+            title: '成功',
+            message: '删除优惠券成功'
+          })
+          const index = this.list.indexOf(row)
+          this.list.splice(index, 1)
+        })
+        .catch(response => {
+          this.$notify.error({
+            title: '失败',
+            message: response.data.errmsg
+          })
+        })
+    },
+    handleDetail(row) {
+      this.$router.push({ path: '/promotion/couponDetail', query: { id: row.id }})
+    },
+    handleDownload() {
+      this.downloadLoading = true
+      import('@/vendor/Export2Excel').then(excel => {
+        const tHeader = [
+          '优惠券ID',
+          '名称',
+          '内容',
+          '标签',
+          '最低消费',
+          '减免金额',
+          '每人限领',
+          '优惠券数量'
+        ]
+        const filterVal = [
+          'id',
+          'name',
+          'desc',
+          'tag',
+          'min',
+          'discount',
+          'limit',
+          'total'
+        ]
+        excel.export_json_to_excel2(tHeader, this.list, filterVal, '优惠券信息')
+        this.downloadLoading = false
+      })
+    }
+  }
+}
+</script>

+ 248 - 0
litemall-admin/src/views/promotion/couponDetail.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="app-container">
+
+    <div class="table-layout">
+      <el-row>
+        <el-col :span="4" class="table-cell-title">名称</el-col>
+        <el-col :span="4" class="table-cell-title">介绍名称</el-col>
+        <el-col :span="4" class="table-cell-title">标签</el-col>
+        <el-col :span="4" class="table-cell-title">优惠券类型</el-col>
+        <el-col :span="4" class="table-cell-title">最低消费</el-col>
+        <el-col :span="4" class="table-cell-title">优惠面值</el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="4" class="table-cell">{{ coupon.name }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.desc }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.tag }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.type | formatType }}</el-col>
+        <el-col :span="4" class="table-cell">满{{ coupon.min }}元可用</el-col>
+        <el-col :span="4" class="table-cell">减免{{ coupon.discount }}元</el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="4" class="table-cell-title">每人限额</el-col>
+        <el-col :span="4" class="table-cell-title">当前状态</el-col>
+        <el-col :span="4" class="table-cell-title">商品范围</el-col>
+        <el-col :span="4" class="table-cell-title">有效期</el-col>
+        <el-col :span="4" class="table-cell-title">优惠兑换码</el-col>
+        <el-col :span="4" class="table-cell-title">发行数量</el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="4" class="table-cell">{{ coupon.limit }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.status | formatStatus }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.goodsType | formatGoodsType }}</el-col>
+        <el-col :span="4" class="table-cell">{{ getTimeScope() }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.code }}</el-col>
+        <el-col :span="4" class="table-cell">{{ coupon.total === 0 ? "不限" : coupon.total }}</el-col>
+      </el-row>
+    </div>
+
+    <!-- 查询操作 -->
+    <div class="filter-container">
+      <el-input v-model="listQuery.userId" clearable class="filter-item" style="width: 200px;" placeholder="请输入用户ID"/>
+      <el-select v-model="listQuery.status" clearable style="width: 200px" class="filter-item" placeholder="请选择使用状态">
+        <el-option v-for="type in useStatusOptions" :key="type.value" :label="type.label" :value="type.value"/>
+      </el-select>
+      <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
+    </div>
+
+    <!-- 查询结果 -->
+    <el-table v-loading="listLoading" :data="list" size="small" element-loading-text="正在查询中。。。" border fit highlight-current-row>
+
+      <el-table-column align="center" label="用户优惠券ID" prop="id" sortable/>
+
+      <el-table-column align="center" label="用户ID" prop="userId"/>
+
+      <el-table-column align="center" label="领取时间" prop="addTime"/>
+
+      <el-table-column align="center" label="使用状态" prop="status">
+        <template slot-scope="scope">{{ scope.row.status | formatUseStatus }}</template>
+      </el-table-column>
+
+      <el-table-column align="center" label="订单ID" prop="orderId"/>
+
+      <el-table-column align="center" label="使用时间" prop="usedTime"/>
+
+    </el-table>
+
+    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
+
+  </div>
+</template>
+
+<script>
+import { readCoupon, listCouponUser } from '@/api/coupon'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+
+const defaultTypeOptions = [
+  {
+    label: '通用领券',
+    value: 0
+  },
+  {
+    label: '注册赠券',
+    value: 1
+  },
+  {
+    label: '兑换码',
+    value: 2
+  }
+]
+
+const defaultUseStatusOptions = [
+  {
+    label: '未使用',
+    value: 0
+  },
+  {
+    label: '已使用',
+    value: 1
+  },
+  {
+    label: '已过期',
+    value: 2
+  },
+  {
+    label: '已下架',
+    value: 3
+  }
+]
+
+export default {
+  name: 'CouponDetail',
+  components: { Pagination },
+  filters: {
+    formatType(type) {
+      for (let i = 0; i < defaultTypeOptions.length; i++) {
+        if (type === defaultTypeOptions[i].value) {
+          return defaultTypeOptions[i].label
+        }
+      }
+      return ''
+    },
+    formatGoodsType(goodsType) {
+      if (goodsType === 0) {
+        return '全场通用'
+      } else if (goodsType === 1) {
+        return '指定分类'
+      } else {
+        return '指定商品'
+      }
+    },
+    formatStatus(status) {
+      if (status === 0) {
+        return '正常'
+      } else if (status === 1) {
+        return '已过期'
+      } else {
+        return '已下架'
+      }
+    },
+    formatUseStatus(status) {
+      if (status === 0) {
+        return '未使用'
+      } else if (status === 1) {
+        return '已使用'
+      } else if (status === 3) {
+        return '已过期'
+      } else {
+        return '已下架'
+      }
+    }
+  },
+  data() {
+    return {
+      typeOptions: Object.assign({}, defaultTypeOptions),
+      useStatusOptions: Object.assign({}, defaultUseStatusOptions),
+      coupon: {},
+      list: undefined,
+      total: 0,
+      listLoading: true,
+      listQuery: {
+        page: 1,
+        limit: 20,
+        couponId: 0,
+        userId: undefined,
+        status: undefined,
+        sort: 'add_time',
+        order: 'desc'
+      },
+      downloadLoading: false
+    }
+  },
+  created() {
+    this.init()
+  },
+  methods: {
+    init: function() {
+      if (this.$route.query.id == null) {
+        return
+      }
+      readCoupon(this.$route.query.id).then(response => {
+        this.coupon = response.data.data
+      })
+      this.listQuery.couponId = this.$route.query.id
+      this.getList()
+    },
+    getList() {
+      this.listLoading = true
+      listCouponUser(this.listQuery)
+        .then(response => {
+          this.list = response.data.data.items
+          this.total = response.data.data.total
+          this.listLoading = false
+        })
+        .catch(() => {
+          this.list = []
+          this.total = 0
+          this.listLoading = false
+        })
+    },
+    handleFilter() {
+      this.listQuery.page = 1
+      this.getList()
+    },
+    getTimeScope() {
+      if (this.coupon.timeType === 0) {
+        return '领取' + this.coupon.days + '天有效'
+      } else if (this.coupon.timeType === 1) {
+        return '自' + this.coupon.startTime + '至' + this.coupon.endTime + '有效'
+      } else {
+        return '未知'
+      }
+    },
+    getGoodsScope() {
+    }
+  }
+}
+</script>
+<style scoped>
+  .filter-container {
+    margin-top: 20px;
+  }
+
+  .table-layout {
+    margin-top: 20px;
+    border-left: 1px solid #DCDFE6;
+    border-top: 1px solid #DCDFE6;
+  }
+  .table-cell {
+    height: 60px;
+    line-height: 40px;
+    border-right: 1px solid #DCDFE6;
+    border-bottom: 1px solid #DCDFE6;
+    padding: 10px;
+    font-size: 14px;
+    color: #606266;
+    text-align: center;
+    overflow: hidden;
+  }
+  .table-cell-title {
+    border-right: 1px solid #DCDFE6;
+    border-bottom: 1px solid #DCDFE6;
+    padding: 10px;
+    background: #F2F6FC;
+    text-align: center;
+    font-size: 14px;
+    color: #303133;
+  }
+</style>

+ 74 - 0
litemall-db/src/main/java/org/linlinjava/litemall/db/domain/LitemallCoupon.java

@@ -135,6 +135,24 @@ public class LitemallCoupon {
     /**
      *
      * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column litemall_coupon.code
+     *
+     * @mbg.generated
+     */
+    private String code;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
+     * This field corresponds to the database column litemall_coupon.time_type
+     *
+     * @mbg.generated
+     */
+    private Short timeType;
+
+    /**
+     *
+     * This field was generated by MyBatis Generator.
      * This field corresponds to the database column litemall_coupon.days
      *
      * @mbg.generated
@@ -476,6 +494,54 @@ public class LitemallCoupon {
 
     /**
      * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column litemall_coupon.code
+     *
+     * @return the value of litemall_coupon.code
+     *
+     * @mbg.generated
+     */
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column litemall_coupon.code
+     *
+     * @param code the value for litemall_coupon.code
+     *
+     * @mbg.generated
+     */
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method returns the value of the database column litemall_coupon.time_type
+     *
+     * @return the value of litemall_coupon.time_type
+     *
+     * @mbg.generated
+     */
+    public Short getTimeType() {
+        return timeType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
+     * This method sets the value of the database column litemall_coupon.time_type
+     *
+     * @param timeType the value for litemall_coupon.time_type
+     *
+     * @mbg.generated
+     */
+    public void setTimeType(Short timeType) {
+        this.timeType = timeType;
+    }
+
+    /**
+     * This method was generated by MyBatis Generator.
      * This method returns the value of the database column litemall_coupon.days
      *
      * @return the value of litemall_coupon.days
@@ -642,6 +708,8 @@ public class LitemallCoupon {
         sb.append(", status=").append(status);
         sb.append(", goodsType=").append(goodsType);
         sb.append(", goodsValue=").append(goodsValue);
+        sb.append(", code=").append(code);
+        sb.append(", timeType=").append(timeType);
         sb.append(", days=").append(days);
         sb.append(", startTime=").append(startTime);
         sb.append(", endTime=").append(endTime);
@@ -682,6 +750,8 @@ public class LitemallCoupon {
             && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
             && (this.getGoodsType() == null ? other.getGoodsType() == null : this.getGoodsType().equals(other.getGoodsType()))
             && (Arrays.equals(this.getGoodsValue(), other.getGoodsValue()))
+            && (this.getCode() == null ? other.getCode() == null : this.getCode().equals(other.getCode()))
+            && (this.getTimeType() == null ? other.getTimeType() == null : this.getTimeType().equals(other.getTimeType()))
             && (this.getDays() == null ? other.getDays() == null : this.getDays().equals(other.getDays()))
             && (this.getStartTime() == null ? other.getStartTime() == null : this.getStartTime().equals(other.getStartTime()))
             && (this.getEndTime() == null ? other.getEndTime() == null : this.getEndTime().equals(other.getEndTime()))
@@ -712,6 +782,8 @@ public class LitemallCoupon {
         result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
         result = prime * result + ((getGoodsType() == null) ? 0 : getGoodsType().hashCode());
         result = prime * result + (Arrays.hashCode(getGoodsValue()));
+        result = prime * result + ((getCode() == null) ? 0 : getCode().hashCode());
+        result = prime * result + ((getTimeType() == null) ? 0 : getTimeType().hashCode());
         result = prime * result + ((getDays() == null) ? 0 : getDays().hashCode());
         result = prime * result + ((getStartTime() == null) ? 0 : getStartTime().hashCode());
         result = prime * result + ((getEndTime() == null) ? 0 : getEndTime().hashCode());
@@ -752,6 +824,8 @@ public class LitemallCoupon {
         status("status", "status", "SMALLINT", true),
         goodsType("goods_type", "goodsType", "SMALLINT", false),
         goodsValue("goods_value", "goodsValue", "VARCHAR", false),
+        code("code", "code", "VARCHAR", false),
+        timeType("time_type", "timeType", "SMALLINT", false),
         days("days", "days", "SMALLINT", true),
         startTime("start_time", "startTime", "TIMESTAMP", false),
         endTime("end_time", "endTime", "TIMESTAMP", false),

+ 274 - 0
litemall-db/src/main/java/org/linlinjava/litemall/db/domain/LitemallCouponExample.java

@@ -1897,6 +1897,280 @@ public class LitemallCouponExample {
             return (Criteria) this;
         }
 
+        public Criteria andCodeIsNull() {
+            addCriterion("code is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeIsNotNull() {
+            addCriterion("code is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeEqualTo(String value) {
+            addCriterion("code =", value, "code");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andCodeEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("code = ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeNotEqualTo(String value) {
+            addCriterion("code <>", value, "code");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andCodeNotEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("code <> ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeGreaterThan(String value) {
+            addCriterion("code >", value, "code");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andCodeGreaterThanColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("code > ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeGreaterThanOrEqualTo(String value) {
+            addCriterion("code >=", value, "code");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andCodeGreaterThanOrEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("code >= ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeLessThan(String value) {
+            addCriterion("code <", value, "code");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andCodeLessThanColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("code < ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeLessThanOrEqualTo(String value) {
+            addCriterion("code <=", value, "code");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andCodeLessThanOrEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("code <= ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeLike(String value) {
+            addCriterion("code like", value, "code");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeNotLike(String value) {
+            addCriterion("code not like", value, "code");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeIn(List<String> values) {
+            addCriterion("code in", values, "code");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeNotIn(List<String> values) {
+            addCriterion("code not in", values, "code");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeBetween(String value1, String value2) {
+            addCriterion("code between", value1, value2, "code");
+            return (Criteria) this;
+        }
+
+        public Criteria andCodeNotBetween(String value1, String value2) {
+            addCriterion("code not between", value1, value2, "code");
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeIsNull() {
+            addCriterion("time_type is null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeIsNotNull() {
+            addCriterion("time_type is not null");
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeEqualTo(Short value) {
+            addCriterion("time_type =", value, "timeType");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andTimeTypeEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("time_type = ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeNotEqualTo(Short value) {
+            addCriterion("time_type <>", value, "timeType");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andTimeTypeNotEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("time_type <> ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeGreaterThan(Short value) {
+            addCriterion("time_type >", value, "timeType");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andTimeTypeGreaterThanColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("time_type > ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeGreaterThanOrEqualTo(Short value) {
+            addCriterion("time_type >=", value, "timeType");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andTimeTypeGreaterThanOrEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("time_type >= ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeLessThan(Short value) {
+            addCriterion("time_type <", value, "timeType");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andTimeTypeLessThanColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("time_type < ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeLessThanOrEqualTo(Short value) {
+            addCriterion("time_type <=", value, "timeType");
+            return (Criteria) this;
+        }
+
+        /**
+         * This method was generated by MyBatis Generator.
+         * This method corresponds to the database table litemall_coupon
+         *
+         * @mbg.generated
+         * @project https://github.com/itfsw/mybatis-generator-plugin
+         */
+        public Criteria andTimeTypeLessThanOrEqualToColumn(LitemallCoupon.Column column) {
+            addCriterion(new StringBuilder("time_type <= ").append(column.getEscapedColumnName()).toString());
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeIn(List<Short> values) {
+            addCriterion("time_type in", values, "timeType");
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeNotIn(List<Short> values) {
+            addCriterion("time_type not in", values, "timeType");
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeBetween(Short value1, Short value2) {
+            addCriterion("time_type between", value1, value2, "timeType");
+            return (Criteria) this;
+        }
+
+        public Criteria andTimeTypeNotBetween(Short value1, Short value2) {
+            addCriterion("time_type not between", value1, value2, "timeType");
+            return (Criteria) this;
+        }
+
         public Criteria andDaysIsNull() {
             addCriterion("`days` is null");
             return (Criteria) this;

+ 8 - 2
litemall-db/src/main/java/org/linlinjava/litemall/db/service/CouponVerifyService.java

@@ -33,18 +33,24 @@ public class CouponVerifyService {
         }
 
         // 检查是否超期
+        Short timeType = coupon.getTimeType();
         Short days = coupon.getDays();
         LocalDateTime now = LocalDateTime.now();
-        if (days == 0) {
+        if (timeType.equals(CouponConstant.TIME_TYPE_TIME)) {
             if (now.isBefore(coupon.getStartTime()) || now.isAfter(coupon.getEndTime())) {
                 return null;
             }
-        } else {
+        }
+        else if(timeType.equals(CouponConstant.TIME_TYPE_DAYS)) {
             LocalDateTime expired = couponUser.getAddTime().plusDays(days);
             if (now.isAfter(expired)) {
                 return null;
             }
         }
+        else {
+            return null;
+        }
+
         // 检测商品是否符合
         // TODO 目前仅支持全平台商品,所以不需要检测
         Short goodType = coupon.getGoodsType();

+ 105 - 0
litemall-db/src/main/java/org/linlinjava/litemall/db/service/LitemallCouponService.java

@@ -1,5 +1,6 @@
 package org.linlinjava.litemall.db.service;
 
+import com.alibaba.druid.util.StringUtils;
 import com.github.pagehelper.PageHelper;
 import org.linlinjava.litemall.db.dao.LitemallCouponMapper;
 import org.linlinjava.litemall.db.domain.LitemallCoupon;
@@ -10,7 +11,9 @@ import org.linlinjava.litemall.db.util.CouponConstant;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Random;
 
 @Service
 public class LitemallCouponService {
@@ -52,6 +55,22 @@ public class LitemallCouponService {
         return couponMapper.selectByPrimaryKey(id);
     }
 
+
+    public LitemallCoupon findByCode(String code) {
+        LitemallCouponExample example = new LitemallCouponExample();
+        example.or().andCodeEqualTo(code).andTypeEqualTo(CouponConstant.TYPE_CODE).andStatusEqualTo(CouponConstant.STATUS_NORMAL).andDeletedEqualTo(false);
+        List<LitemallCoupon> couponList =  couponMapper.selectByExample(example);
+        if(couponList.size() > 1){
+            throw new RuntimeException("");
+        }
+        else if(couponList.size() == 0){
+            return null;
+        }
+        else {
+            return couponList.get(0);
+        }
+    }
+
     /**
      * 查询新用户注册优惠券
      *
@@ -62,4 +81,90 @@ public class LitemallCouponService {
         example.or().andTypeEqualTo(CouponConstant.TYPE_REGISTER).andStatusEqualTo(CouponConstant.STATUS_NORMAL).andDeletedEqualTo(false);
         return couponMapper.selectByExample(example);
     }
+
+    public List<LitemallCoupon> querySelective(String name, Short type, Short status, Integer page, Integer limit, String sort, String order) {
+        LitemallCouponExample example = new LitemallCouponExample();
+        LitemallCouponExample.Criteria criteria = example.createCriteria();
+
+        if (!StringUtils.isEmpty(name)) {
+            criteria.andNameLike("%" + name + "%");
+        }
+        if (type != null) {
+            criteria.andTypeEqualTo(type);
+        }
+        if (status != null) {
+            criteria.andStatusEqualTo(status);
+        }
+        criteria.andDeletedEqualTo(false);
+
+        if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
+            example.setOrderByClause(sort + " " + order);
+        }
+
+        PageHelper.startPage(page, limit);
+        return couponMapper.selectByExample(example);
+    }
+
+    public int countSelective(String name, Short type, Short status, Integer page, Integer limit, String sort, String order) {
+        LitemallCouponExample example = new LitemallCouponExample();
+        LitemallCouponExample.Criteria criteria = example.createCriteria();
+
+        if (!StringUtils.isEmpty(name)) {
+            criteria.andNameLike("%" + name + "%");
+        }
+        if (type != null) {
+            criteria.andTypeEqualTo(type);
+        }
+        if (status != null) {
+            criteria.andStatusEqualTo(status);
+        }
+        criteria.andDeletedEqualTo(false);
+
+        if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
+            example.setOrderByClause(sort + " " + order);
+        }
+
+        return (int)couponMapper.countByExample(example);
+    }
+
+    public void add(LitemallCoupon coupon) {
+        coupon.setAddTime(LocalDateTime.now());
+        coupon.setUpdateTime(LocalDateTime.now());
+        couponMapper.insertSelective(coupon);
+    }
+
+    public int updateById(LitemallCoupon coupon) {
+        coupon.setUpdateTime(LocalDateTime.now());
+        return couponMapper.updateByPrimaryKeySelective(coupon);
+    }
+
+    public void deleteById(Integer id) {
+        couponMapper.logicalDeleteByPrimaryKey(id);
+    }
+
+    private String getRandomNum(Integer num) {
+        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+        base += "0123456789";
+
+        Random random = new Random();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < num; i++) {
+            int number = random.nextInt(base.length());
+            sb.append(base.charAt(number));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 生成优惠码
+     *
+     * @return 可使用优惠码
+     */
+    public String generateCode() {
+        String code = getRandomNum(8);
+        while(findByCode(code) != null){
+            code = getRandomNum(8);
+        }
+        return code;
+    }
 }

+ 26 - 0
litemall-db/src/main/java/org/linlinjava/litemall/db/service/LitemallCouponUserService.java

@@ -60,6 +60,32 @@ public class LitemallCouponUserService {
         return couponUserMapper.selectByExample(example);
     }
 
+
+    public int countList(Integer userId, Integer couponId, Short status, Integer page, Integer size, String sort, String order) {
+        LitemallCouponUserExample example = new LitemallCouponUserExample();
+        LitemallCouponUserExample.Criteria criteria = example.createCriteria();
+        if (userId != null) {
+            criteria.andUserIdEqualTo(userId);
+        }
+        if(couponId != null){
+            criteria.andCouponIdEqualTo(couponId);
+        }
+        if (status != null) {
+            criteria.andStatusEqualTo(status);
+        }
+        criteria.andDeletedEqualTo(false);
+
+        if (!StringUtils.isEmpty(sort) && !StringUtils.isEmpty(order)) {
+            example.setOrderByClause(sort + " " + order);
+        }
+
+        if (!StringUtils.isEmpty(page) && !StringUtils.isEmpty(size)) {
+            PageHelper.startPage(page, size);
+        }
+
+        return (int)couponUserMapper.countByExample(example);
+    }
+
     public List<LitemallCouponUser> queryAll(Integer userId, Integer couponId) {
         return queryList(userId, couponId, CouponUserConstant.STATUS_USABLE, null, null, "add_time", "desc");
     }

+ 4 - 0
litemall-db/src/main/java/org/linlinjava/litemall/db/util/CouponConstant.java

@@ -3,6 +3,7 @@ package org.linlinjava.litemall.db.util;
 public class CouponConstant {
     public static final Short TYPE_COMMON = 0;
     public static final Short TYPE_REGISTER = 1;
+    public static final Short TYPE_CODE = 2;
 
     public static final Short GOODS_TYPE_ALL = 0;
     public static final Short GOODS_TYPE_CATEGORY = 1;
@@ -11,4 +12,7 @@ public class CouponConstant {
     public static final Short STATUS_NORMAL = 0;
     public static final Short STATUS_EXPIRED = 1;
     public static final Short STATUS_OUT = 2;
+
+    public static final Short TIME_TYPE_DAYS = 0;
+    public static final Short TIME_TYPE_TIME = 1;
 }

+ 44 - 10
litemall-db/src/main/resources/org/linlinjava/litemall/db/dao/LitemallCouponMapper.xml

@@ -18,6 +18,8 @@
     <result column="status" jdbcType="SMALLINT" property="status" />
     <result column="goods_type" jdbcType="SMALLINT" property="goodsType" />
     <result column="goods_value" jdbcType="VARCHAR" property="goodsValue" typeHandler="org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler" />
+    <result column="code" jdbcType="VARCHAR" property="code" />
+    <result column="time_type" jdbcType="SMALLINT" property="timeType" />
     <result column="days" jdbcType="SMALLINT" property="days" />
     <result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
     <result column="end_time" jdbcType="TIMESTAMP" property="endTime" />
@@ -135,7 +137,8 @@
       This element is automatically generated by MyBatis Generator, do not modify.
     -->
     id, `name`, `desc`, tag, total, discount, `min`, `limit`, `type`, `status`, goods_type, 
-    goods_value, `days`, start_time, end_time, add_time, update_time, deleted
+    goods_value, code, time_type, `days`, start_time, end_time, add_time, update_time, 
+    deleted
   </sql>
   <select id="selectByExample" parameterType="org.linlinjava.litemall.db.domain.LitemallCouponExample" resultMap="BaseResultMap">
     <!--
@@ -173,7 +176,8 @@
       </when>
       <otherwise>
         id, `name`, `desc`, tag, total, discount, `min`, `limit`, `type`, `status`, goods_type, 
-          goods_value, `days`, start_time, end_time, add_time, update_time, deleted
+          goods_value, code, time_type, `days`, start_time, end_time, add_time, update_time, 
+          deleted
       </otherwise>
     </choose>
     from litemall_coupon
@@ -233,7 +237,8 @@
       </when>
       <otherwise>
         id, `name`, `desc`, tag, total, discount, `min`, `limit`, `type`, `status`, goods_type, 
-          goods_value, `days`, start_time, end_time, add_time, update_time, deleted
+          goods_value, code, time_type, `days`, start_time, end_time, add_time, update_time, 
+          deleted
       </otherwise>
     </choose>
     from litemall_coupon
@@ -269,16 +274,16 @@
       total, discount, `min`, 
       `limit`, `type`, `status`, 
       goods_type, goods_value, 
-      `days`, start_time, end_time, 
-      add_time, update_time, deleted
-      )
+      code, time_type, `days`, 
+      start_time, end_time, add_time, 
+      update_time, deleted)
     values (#{name,jdbcType=VARCHAR}, #{desc,jdbcType=VARCHAR}, #{tag,jdbcType=VARCHAR}, 
       #{total,jdbcType=INTEGER}, #{discount,jdbcType=DECIMAL}, #{min,jdbcType=DECIMAL}, 
       #{limit,jdbcType=SMALLINT}, #{type,jdbcType=SMALLINT}, #{status,jdbcType=SMALLINT}, 
       #{goodsType,jdbcType=SMALLINT}, #{goodsValue,jdbcType=VARCHAR,typeHandler=org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler}, 
-      #{days,jdbcType=SMALLINT}, #{startTime,jdbcType=TIMESTAMP}, #{endTime,jdbcType=TIMESTAMP}, 
-      #{addTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{deleted,jdbcType=BIT}
-      )
+      #{code,jdbcType=VARCHAR}, #{timeType,jdbcType=SMALLINT}, #{days,jdbcType=SMALLINT}, 
+      #{startTime,jdbcType=TIMESTAMP}, #{endTime,jdbcType=TIMESTAMP}, #{addTime,jdbcType=TIMESTAMP}, 
+      #{updateTime,jdbcType=TIMESTAMP}, #{deleted,jdbcType=BIT})
   </insert>
   <insert id="insertSelective" parameterType="org.linlinjava.litemall.db.domain.LitemallCoupon">
     <!--
@@ -323,6 +328,12 @@
       <if test="goodsValue != null">
         goods_value,
       </if>
+      <if test="code != null">
+        code,
+      </if>
+      <if test="timeType != null">
+        time_type,
+      </if>
       <if test="days != null">
         `days`,
       </if>
@@ -376,6 +387,12 @@
       <if test="goodsValue != null">
         #{goodsValue,jdbcType=VARCHAR,typeHandler=org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler},
       </if>
+      <if test="code != null">
+        #{code,jdbcType=VARCHAR},
+      </if>
+      <if test="timeType != null">
+        #{timeType,jdbcType=SMALLINT},
+      </if>
       <if test="days != null">
         #{days,jdbcType=SMALLINT},
       </if>
@@ -449,6 +466,12 @@
       <if test="record.goodsValue != null">
         goods_value = #{record.goodsValue,jdbcType=VARCHAR,typeHandler=org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler},
       </if>
+      <if test="record.code != null">
+        code = #{record.code,jdbcType=VARCHAR},
+      </if>
+      <if test="record.timeType != null">
+        time_type = #{record.timeType,jdbcType=SMALLINT},
+      </if>
       <if test="record.days != null">
         `days` = #{record.days,jdbcType=SMALLINT},
       </if>
@@ -490,6 +513,8 @@
       `status` = #{record.status,jdbcType=SMALLINT},
       goods_type = #{record.goodsType,jdbcType=SMALLINT},
       goods_value = #{record.goodsValue,jdbcType=VARCHAR,typeHandler=org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler},
+      code = #{record.code,jdbcType=VARCHAR},
+      time_type = #{record.timeType,jdbcType=SMALLINT},
       `days` = #{record.days,jdbcType=SMALLINT},
       start_time = #{record.startTime,jdbcType=TIMESTAMP},
       end_time = #{record.endTime,jdbcType=TIMESTAMP},
@@ -540,6 +565,12 @@
       <if test="goodsValue != null">
         goods_value = #{goodsValue,jdbcType=VARCHAR,typeHandler=org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler},
       </if>
+      <if test="code != null">
+        code = #{code,jdbcType=VARCHAR},
+      </if>
+      <if test="timeType != null">
+        time_type = #{timeType,jdbcType=SMALLINT},
+      </if>
       <if test="days != null">
         `days` = #{days,jdbcType=SMALLINT},
       </if>
@@ -578,6 +609,8 @@
       `status` = #{status,jdbcType=SMALLINT},
       goods_type = #{goodsType,jdbcType=SMALLINT},
       goods_value = #{goodsValue,jdbcType=VARCHAR,typeHandler=org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler},
+      code = #{code,jdbcType=VARCHAR},
+      time_type = #{timeType,jdbcType=SMALLINT},
       `days` = #{days,jdbcType=SMALLINT},
       start_time = #{startTime,jdbcType=TIMESTAMP},
       end_time = #{endTime,jdbcType=TIMESTAMP},
@@ -618,7 +651,8 @@
       </when>
       <otherwise>
         id, `name`, `desc`, tag, total, discount, `min`, `limit`, `type`, `status`, goods_type, 
-          goods_value, `days`, start_time, end_time, add_time, update_time, deleted
+          goods_value, code, time_type, `days`, start_time, end_time, add_time, update_time, 
+          deleted
       </otherwise>
     </choose>
     from litemall_coupon