xiongzhu пре 5 година
родитељ
комит
6d80e43d52

+ 45 - 0
src/main/java/com/izouma/dangjian/domain/Commission.java

@@ -0,0 +1,45 @@
+package com.izouma.dangjian.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import java.math.BigDecimal;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class Commission extends BaseEntity {
+
+    @ApiModelProperty("订单编号")
+    private Long orderId;
+
+    @ApiModelProperty("交易金额")
+    private BigDecimal money;
+
+    @ApiModelProperty("返佣比例")
+    private BigDecimal rate;
+
+    @ApiModelProperty("税点")
+    private BigDecimal taxRate;
+
+    @ApiModelProperty("佣金金额")
+    private BigDecimal commissionMoney;
+
+    @ApiModelProperty("返佣比例")
+    private boolean commissionFinish;
+
+    @ApiModelProperty("开票")
+    private boolean invoice;
+
+    @ApiModelProperty("推广链接所属用户ID")
+    private Long invitor;
+
+    @ApiModelProperty("录单员")
+    private String operator;
+}

+ 16 - 0
src/main/java/com/izouma/dangjian/repo/CommissionRepo.java

@@ -0,0 +1,16 @@
+package com.izouma.dangjian.repo;
+
+import com.izouma.dangjian.domain.Commission;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+
+public interface CommissionRepo extends JpaRepository<Commission, Long>, JpaSpecificationExecutor<Commission> {
+    @Query("update Commission t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

+ 1 - 0
src/main/java/com/izouma/dangjian/repo/OrderRepo.java

@@ -22,4 +22,5 @@ public interface OrderRepo extends JpaRepository<Order, Long>, JpaSpecificationE
     @Query("select count(o) from Order o join User u on o.userId = u.id " +
             "where u.invitor = ?1 and o.createdAt >= ?2 and o.createdAt <= ?3")
     long countInvitor(Long userId, LocalDateTime start, LocalDateTime end);
+
 }

+ 1 - 0
src/main/java/com/izouma/dangjian/repo/UserRepo.java

@@ -23,4 +23,5 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
     User findByOpenIdAndDelFalse(String openId);
 
     User findByPhoneAndDelFalse(String phone);
+
 }

+ 2 - 0
src/main/java/com/izouma/dangjian/repo/VisitStatRepo.java

@@ -10,5 +10,7 @@ import java.util.Optional;
 public interface VisitStatRepo extends JpaRepository<VisitStat, Long> {
     Optional<VisitStat> findByUserIdAndDate(Long userId, LocalDate date);
 
+    List<VisitStat> findByDate(LocalDate date);
+
     List<VisitStat> findByUserIdAndDateBetween(Long userId, LocalDate start, LocalDate end);
 }

+ 20 - 0
src/main/java/com/izouma/dangjian/service/CommissionService.java

@@ -0,0 +1,20 @@
+package com.izouma.dangjian.service;
+
+import com.izouma.dangjian.domain.Commission;
+import com.izouma.dangjian.dto.PageQuery;
+import com.izouma.dangjian.repo.CommissionRepo;
+import com.izouma.dangjian.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class CommissionService {
+
+    private CommissionRepo commissionRepo;
+
+    public Page<Commission> all(PageQuery pageQuery) {
+        return commissionRepo.findAll(JpaUtils.toSpecification(pageQuery, Commission.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 15 - 0
src/main/java/com/izouma/dangjian/service/OrderService.java

@@ -6,8 +6,15 @@ import com.izouma.dangjian.repo.OrderRepo;
 import com.izouma.dangjian.utils.JpaUtils;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.time.LocalDateTime;
+
 @Service
 @AllArgsConstructor
 public class OrderService {
@@ -17,4 +24,12 @@ public class OrderService {
     public Page<Order> all(PageQuery pageQuery) {
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
     }
+
+    public long countOrder(LocalDateTime start, LocalDateTime end) {
+        return orderRepo.count((Specification<Order>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.and(
+                criteriaBuilder.equal(root.get("del"), false),
+                criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), start),
+                criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), end)
+        ));
+    }
 }

+ 16 - 0
src/main/java/com/izouma/dangjian/service/UserService.java

@@ -7,6 +7,7 @@ import com.izouma.dangjian.config.Constants;
 import com.izouma.dangjian.domain.User;
 import com.izouma.dangjian.dto.PageQuery;
 import com.izouma.dangjian.dto.UserRegister;
+import com.izouma.dangjian.enums.AuthorityName;
 import com.izouma.dangjian.exception.BusinessException;
 import com.izouma.dangjian.repo.UserRepo;
 import com.izouma.dangjian.security.Authority;
@@ -25,10 +26,16 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Page;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.*;
 
 @Service
@@ -203,4 +210,13 @@ public class UserService {
         }
         return setPassword(userId, password);
     }
+
+    public long countUser(LocalDateTime start, LocalDateTime end) {
+        return userRepo.count((Specification<User>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder
+                .and(
+                        criteriaBuilder.isNotMember(Authority.get(AuthorityName.ROLE_ADMIN), root.get("authorities")),
+                        criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), start),
+                        criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), end)
+                ));
+    }
 }

+ 24 - 0
src/main/java/com/izouma/dangjian/service/VisitStatService.java

@@ -1,10 +1,12 @@
 package com.izouma.dangjian.service;
 
+import com.izouma.dangjian.domain.Order;
 import com.izouma.dangjian.domain.VisitStat;
 import com.izouma.dangjian.repo.OrderRepo;
 import com.izouma.dangjian.repo.VisitStatRepo;
 import com.izouma.dangjian.utils.DateTimeUtils;
 import lombok.AllArgsConstructor;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
@@ -53,4 +55,26 @@ public class VisitStatService {
         map.put("labels", labels);
         return map;
     }
+
+    public Map<String, Object> stat3(LocalDate start, LocalDate end) {
+        List<Long> visitNum = new ArrayList<>();
+        List<Long> orderNum = new ArrayList<>();
+        List<String> labels = new ArrayList<>();
+        for (int i = 0; i <= ChronoUnit.DAYS.between(start, end); i++) {
+            LocalDate date = start.plusDays(i);
+            labels.add(DateTimeUtils.format(date, "MM-dd"));
+            visitNum.add(visitStatRepo.findByDate(date).stream().mapToLong(VisitStat::getNum).sum());
+            orderNum.add(orderRepo
+                    .count((Specification<Order>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.and(
+                            criteriaBuilder.equal(root.get("del"), false),
+                            criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), date.atStartOfDay()),
+                            criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), date.atTime(23, 59, 59))
+                    )));
+        }
+        Map<String, Object> map = new HashMap<>();
+        map.put("visitNum", visitNum);
+        map.put("orderNum", orderNum);
+        map.put("labels", labels);
+        return map;
+    }
 }

+ 60 - 0
src/main/java/com/izouma/dangjian/web/CommissionController.java

@@ -0,0 +1,60 @@
+package com.izouma.dangjian.web;
+import com.izouma.dangjian.domain.Commission;
+import com.izouma.dangjian.service.CommissionService;
+import com.izouma.dangjian.dto.PageQuery;
+import com.izouma.dangjian.exception.BusinessException;
+import com.izouma.dangjian.repo.CommissionRepo;
+import com.izouma.dangjian.utils.ObjUtils;
+import com.izouma.dangjian.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/commission")
+@AllArgsConstructor
+public class CommissionController extends BaseController {
+    private CommissionService commissionService;
+    private CommissionRepo commissionRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Commission save(@RequestBody Commission record) {
+        if (record.getId() != null) {
+            Commission orig = commissionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return commissionRepo.save(orig);
+        }
+        return commissionRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Commission> all(@RequestBody PageQuery pageQuery) {
+        return commissionService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Commission get(@PathVariable Long id) {
+        return commissionRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        commissionRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Commission> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 7 - 0
src/main/java/com/izouma/dangjian/web/OrderController.java

@@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 
@@ -70,5 +72,10 @@ public class OrderController extends BaseController {
         }});
         return orderService.all(pageQuery);
     }
+
+    @GetMapping("/countOrder")
+    public long countOrder(@RequestParam LocalDate start, @RequestParam LocalDate end) {
+        return orderService.countOrder(start.atStartOfDay(), end.atTime(23, 59, 59));
+    }
 }
 

+ 7 - 0
src/main/java/com/izouma/dangjian/web/UserController.java

@@ -22,6 +22,8 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Collections;
 import java.util.List;
 
@@ -141,4 +143,9 @@ public class UserController extends BaseController {
         ObjUtils.merge(orig, user);
         return userRepo.save(orig);
     }
+
+    @GetMapping("/countUser")
+    public long countUser(@RequestParam LocalDate start, @RequestParam LocalDate end) {
+        return userService.countUser(start.atStartOfDay(), end.atTime(23, 59, 59));
+    }
 }

+ 5 - 0
src/main/java/com/izouma/dangjian/web/VisitStatController.java

@@ -31,4 +31,9 @@ public class VisitStatController {
     public Map<String, Object> stat2(@RequestParam LocalDate start, @RequestParam LocalDate end) {
         return visitStatService.stat2(SecurityUtils.getAuthenticatedUser().getId(), start, end);
     }
+
+    @GetMapping("/stat3")
+    public Map<String, Object> stat3(@RequestParam LocalDate start, @RequestParam LocalDate end) {
+        return visitStatService.stat3(start, end);
+    }
 }

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
src/main/resources/genjson/Commission.json


+ 2 - 2
src/main/vue/package.json

@@ -16,7 +16,7 @@
     "@tinymce/tinymce-vue": "^3.2.1",
     "axios": "^0.19.2",
     "babel-polyfill": "^6.26.0",
-    "chart.js": "^2.8.0",
+    "chart.js": "^2.9.4",
     "clipboard": "^2.0.6",
     "core-js": "^3.6.5",
     "date-fns": "^2.14.0",
@@ -30,7 +30,7 @@
     "vue": "^2.6.11",
     "vue-avatar-cropper": "^1.0.5",
     "vue-axios": "^2.1.5",
-    "vue-chartjs": "^3.5.0",
+    "vue-chartjs": "^3.5.1",
     "vue-grid-layout": "^2.3.7",
     "vue-i18n": "^8.18.2",
     "vue-router": "^3.3.4",

+ 10 - 0
src/main/vue/src/components/chart.js

@@ -0,0 +1,10 @@
+import { Line, mixins } from 'vue-chartjs';
+const { reactiveProp } = mixins;
+export default {
+    extends: Line,
+    props: ['options'],
+    mixins: [reactiveProp],
+    mounted() {
+        this.renderChart(this.chartData, this.options);
+    }
+};

+ 16 - 0
src/main/vue/src/router.js

@@ -166,6 +166,22 @@ const router = new Router({
                     meta: {
                         title: '订单管理'
                     }
+                },
+                {
+                    path: '/commissionEdit',
+                    name: 'CommissionEdit',
+                    component: () => import(/* webpackChunkName: "commissionEdit" */ '@/views/CommissionEdit.vue'),
+                    meta: {
+                        title: '佣金管理编辑'
+                    }
+                },
+                {
+                    path: '/commissionList',
+                    name: 'CommissionList',
+                    component: () => import(/* webpackChunkName: "commissionList" */ '@/views/CommissionList.vue'),
+                    meta: {
+                        title: '佣金管理'
+                    }
                 }
                 /**INSERT_LOCATION**/
             ]

+ 222 - 0
src/main/vue/src/views/CommissionEdit.vue

@@ -0,0 +1,222 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)">取消</el-button>
+            <el-button @click="del" :loading="$store.state.fetchingData" type="danger" v-if="formData.id">
+                删除
+            </el-button>
+            <el-button @click="onSave" :loading="$store.state.fetchingData" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form
+                    :model="formData"
+                    :rules="rules"
+                    ref="form"
+                    label-width="150px"
+                    label-position="right"
+                    size="small"
+                    style="max-width: 500px;"
+                >
+                    <el-form-item prop="orderId" label="订单编号">
+                        <el-select
+                            v-model="formData.orderId"
+                            clearable
+                            filterable
+                            placeholder="请选择"
+                            @change="selectOrder"
+                        >
+                            <el-option v-for="item in orders" :key="item.value" :label="item.id" :value="item.id">
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="money" label="交易金额">
+                        <el-input-number type="number" v-model="formData.money"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="rate" label="返佣比例">
+                        <el-input-number type="number" v-model="formData.rate"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="taxRate" label="税点">
+                        <el-input-number type="number" v-model="formData.taxRate"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="commissionMoney" label="佣金金额">
+                        <el-input-number type="number" v-model="formData.commissionMoney"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="commissionFinish" label="返佣比例">
+                        <el-switch v-model="formData.commissionFinish"></el-switch>
+                    </el-form-item>
+                    <el-form-item prop="invoice" label="开票">
+                        <el-switch v-model="formData.invoice"></el-switch>
+                    </el-form-item>
+                    <el-form-item prop="invitor" label="推广链接所属用户ID">
+                        <el-input v-model="formData.invitor" disabled></el-input>
+                    </el-form-item>
+                    <el-form-item prop="operator" label="录单员">
+                        <el-input v-model="formData.operator"></el-input>
+                    </el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary">保存 </el-button>
+                        <el-button @click="onDelete" :loading="saving" type="danger" v-if="formData.id"
+                            >删除
+                        </el-button>
+                        <el-button @click="$router.go(-1)">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    name: 'CommissionEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('commission/get/' + this.$route.query.id)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+        this.$http
+            .post('/order/all', { size: 1000, query: { del: false } }, { body: 'json' })
+            .then(res => {
+                this.orders = res.content;
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {},
+            rules: {
+                orderId: [
+                    {
+                        required: true,
+                        message: '请输入订单编号',
+                        trigger: 'blur'
+                    }
+                ],
+                money: [
+                    {
+                        required: true,
+                        message: '请输入订单编号',
+                        trigger: 'blur'
+                    }
+                ],
+                rate: [
+                    {
+                        required: true,
+                        message: '请输入返佣比例',
+                        trigger: 'blur'
+                    }
+                ],
+                taxRate: [
+                    {
+                        required: true,
+                        message: '请输入税点',
+                        trigger: 'blur'
+                    }
+                ],
+                commissionMoney: [
+                    {
+                        required: true,
+                        message: '请输入佣金金额',
+                        trigger: 'blur'
+                    }
+                ],
+                commissionFinish: [
+                    {
+                        required: true,
+                        message: '请输入返佣比例',
+                        trigger: 'blur'
+                    }
+                ],
+                invoice: [
+                    {
+                        required: true,
+                        message: '请输入开票',
+                        trigger: 'blur'
+                    }
+                ],
+                invitor: [
+                    {
+                        required: true,
+                        message: '请输入推广链接所属用户ID',
+                        trigger: 'blur'
+                    }
+                ],
+                operator: [
+                    {
+                        required: true,
+                        message: '请输入录单员',
+                        trigger: 'blur'
+                    }
+                ]
+            },
+            orders: []
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+
+            this.saving = true;
+            this.$http
+                .post('/commission/save', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/commission/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error((e || {}).error || '删除失败');
+                    }
+                });
+        },
+        selectOrder(e) {
+            console.log(e);
+            let userId = (this.orders.find(i => i.id === e) || {}).userId;
+            console.log(userId);
+            if (userId) {
+                this.$http.get(`/user/get/${userId}`).then(res => {
+                    this.$set(this.formData, 'invitor', res.invitor);
+                });
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 178 - 0
src/main/vue/src/views/CommissionList.vue

@@ -0,0 +1,178 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" :loading="downloading" class="filter-item">
+                新增
+            </el-button>
+            <el-button @click="download" icon="el-icon-upload2" :loading="downloading" class="filter-item">
+                导出
+            </el-button>
+        </page-title>
+        <div class="filters-container">
+            <el-input
+                placeholder="搜索..."
+                v-model="search"
+                clearable
+                class="filter-item search"
+                @keyup.enter.native="getData"
+            >
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="orderId" label="订单编号"> </el-table-column>
+            <el-table-column prop="money" label="交易金额"> </el-table-column>
+            <el-table-column prop="rate" label="返佣比例"> </el-table-column>
+            <el-table-column prop="taxRate" label="税点"> </el-table-column>
+            <el-table-column prop="commissionMoney" label="佣金金额"> </el-table-column>
+            <el-table-column prop="commissionFinish" label="返佣比例">
+                <template slot-scope="{ row }">
+                    <el-tag :type="row.commissionFinish ? '' : 'info'">{{ row.commissionFinish }}</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="invoice" label="开票">
+                <template slot-scope="{ row }">
+                    <el-tag :type="row.invoice ? '' : 'info'">{{ row.invoice }}</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="invitor" label="推广链接所属用户ID"> </el-table-column>
+            <el-table-column prop="operator" label="录单员"> </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" min-width="150">
+                <template slot-scope="{ row }">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                    <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+    name: 'CommissionList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/commission/all',
+            downloading: false
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        beforeGetData() {
+            return { search: this.search };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/commissionEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/commissionEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/commission/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/commission/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 163 - 37
src/main/vue/src/views/Dashboard.vue

@@ -1,44 +1,92 @@
 <template>
     <div class="dashboard">
-        <grid-layout
-            style="margin: 0 -10px;"
-            :layout="layout"
-            :col-num="12"
-            :row-height="30"
-            :is-draggable="editable"
-            :is-resizable="editable"
-            :is-mirrored="false"
-            :vertical-compact="true"
-            :margin="[10, 10]"
-            :use-css-transforms="true"
-        >
-            <grid-item
-                v-for="item in layout"
-                class="widget-wrapper"
-                :x="item.x"
-                :y="item.y"
-                :w="item.w"
-                :h="item.h"
-                :i="item.i"
-                :key="item.i"
-            >
-                <component :is="item.name"></component>
-            </grid-item>
-        </grid-layout>
-        <el-button v-if="editable" @click="save">保存</el-button>
-        <el-button v-else @click="editable = true">编辑</el-button>
+        <el-row :gutter="20">
+            <el-col :span="24" class="filters">
+                <el-radio-group v-model="type" size="small">
+                    <el-radio-button :label="1">上月</el-radio-button>
+                    <el-radio-button :label="2">本月</el-radio-button>
+                    <el-radio-button :label="3">自定义</el-radio-button>
+                </el-radio-group>
+                <el-date-picker
+                    v-model="range"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    value-format="yyyy-MM-dd"
+                    :disabled="type !== 3"
+                >
+                </el-date-picker>
+            </el-col>
+        </el-row>
+        <el-row :gutter="20">
+            <el-col :span="24">
+                <widget-card
+                    :bodyStyle="{
+                        display: 'flex',
+                        alignItems: 'center'
+                    }"
+                    ref="container"
+                >
+                    <div style="width:100%;">
+                        <chart
+                            :chart-data="chartData"
+                            :options="{ responsive: true, aspectRatio: 1, maintainAspectRatio: false }"
+                            :height="null"
+                            :width="null"
+                            v-if="chartData"
+                        ></chart>
+                    </div>
+                </widget-card>
+            </el-col>
+        </el-row>
+        <el-row :gutter="20">
+            <el-col :span="12">
+                <widget-card
+                    :bodyStyle="{
+                        display: 'flex',
+                        alignItems: 'center'
+                    }"
+                >
+                    <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6;"></i>
+                    <div class="info">
+                        <div class="text">注册用户</div>
+                        <div class="num">{{ userNum }}</div>
+                    </div>
+                </widget-card>
+            </el-col>
+            <el-col :span="12">
+                <widget-card
+                    :bodyStyle="{
+                        display: 'flex',
+                        alignItems: 'center'
+                    }"
+                >
+                    <i class="fa-fw fas fa-list-alt fa-3x" style="color: #40c9c6;"></i>
+                    <div class="info">
+                        <div class="text">订单数</div>
+                        <div class="num">{{ orderNum }}</div>
+                    </div>
+                </widget-card>
+            </el-col>
+        </el-row>
     </div>
 </template>
 
 <script>
-import { GridLayout, GridItem } from 'vue-grid-layout';
+/* eslint-disable vue/no-unused-components */
 import UserWidget from '../widgets/UserWidget';
 import LineChartWidget from '../widgets/LineChartWidget';
 import BarChartWidget from '../widgets/BarChartWidget';
 import PieChartWidget from '../widgets/PieChartWidget';
-
+import { addDays, addMonths, startOfDay, endOfDay, startOfMonth, endOfMonth, format, parse } from 'date-fns';
+import widgetCard from '../widgets/WidgetCard';
+import chart from '../components/chart';
 export default {
-    created() {},
+    created() {
+        this.range = [format(startOfMonth(new Date()), 'yyyy-MM-dd'), format(endOfDay(new Date()), 'yyyy-MM-dd')];
+        this.getData();
+    },
     data() {
         return {
             layout: [
@@ -48,22 +96,78 @@ export default {
                 { x: 0, y: 10, w: 6, h: 6, i: '3', name: 'LineChartWidget' },
                 { x: 6, y: 4, w: 6, h: 12, i: '4', name: 'PieChartWidget' }
             ],
-            editable: false
+            editable: false,
+            type: 2,
+            range: null,
+            chartData: null,
+            userNum: 0,
+            orderNum: 0
         };
     },
     methods: {
         save() {
             console.log(JSON.stringify(this.layout));
             this.editable = false;
+        },
+        getData() {
+            this.$http.get('/user/countUser', { start: this.range[0], end: this.range[1] }).then(res => {
+                this.userNum = res;
+            });
+            this.$http.get('/order/countOrder', { start: this.range[0], end: this.range[1] }).then(res => {
+                this.orderNum = res;
+            });
+            this.$http.get('/visitStat/stat3', { start: this.range[0], end: this.range[1] }).then(res => {
+                this.chartData = {
+                    labels: res.labels,
+                    datasets: [
+                        {
+                            label: '访问',
+                            backgroundColor: 'rgba(255, 99, 132, 0.5)',
+                            borderColor: 'rgb(255, 99, 132)',
+                            borderWidth: 2,
+                            fill: false,
+                            data: res.visitNum
+                        },
+                        {
+                            label: '订单',
+                            backgroundColor: 'rgb(54, 162, 235, 0.5)',
+                            borderColor: 'rgb(54, 162, 235)',
+                            borderWidth: 2,
+                            fill: false,
+                            data: res.orderNum
+                        }
+                    ]
+                };
+            });
         }
     },
     components: {
-        GridLayout,
-        GridItem,
-        UserWidget,
-        LineChartWidget,
-        BarChartWidget,
-        PieChartWidget
+        widgetCard,
+        chart,
+        UserWidget
+        // LineChartWidget,
+        // BarChartWidget,
+        // PieChartWidget
+    },
+    watch: {
+        type(val) {
+            if (val === 1) {
+                this.range = [
+                    format(startOfMonth(addMonths(new Date(), -1)), 'yyyy-MM-dd'),
+                    format(endOfMonth(addMonths(new Date(), -1)), 'yyyy-MM-dd')
+                ];
+            } else if (val === 2) {
+                this.range = [
+                    format(startOfMonth(new Date()), 'yyyy-MM-dd'),
+                    format(endOfDay(new Date()), 'yyyy-MM-dd')
+                ];
+            }
+        },
+        range(val) {
+            if (val && val.length === 2) {
+                this.getData();
+            }
+        }
     }
 };
 </script>
@@ -79,4 +183,26 @@ export default {
         flex-grow: 1;
     }
 }
+.filters {
+    .flex();
+    .el-date-editor {
+        margin-left: 15px;
+    }
+}
+.el-row {
+    margin-bottom: 20px;
+}
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
 </style>

+ 17 - 8
src/main/vue/yarn.lock

@@ -928,6 +928,13 @@
   resolved "https://registry.npm.taobao.org/@tinymce/tinymce-vue/download/@tinymce/tinymce-vue-3.2.2.tgz#a16500fbeebfec4ae43f4e9fe5bd739aeb66a3d6"
   integrity sha1-oWUA++6/7ErkP06f5b1zmutmo9Y=
 
+"@types/chart.js@^2.7.55":
+  version "2.9.31"
+  resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.31.tgz#e8ebc7ed18eb0e5114c69bd46ef8e0037c89d39d"
+  integrity sha512-hzS6phN/kx3jClk3iYqEHNnYIRSi4RZrIGJ8CDLjgatpHoftCezvC44uqB3o3OUm9ftU1m7sHG8+RLyPTlACrA==
+  dependencies:
+    moment "^2.10.2"
+
 "@types/color-name@^1.1.1":
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/@types/color-name/download/@types/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@@ -2378,10 +2385,10 @@ chardet@^0.7.0:
   resolved "https://registry.npm.taobao.org/chardet/download/chardet-0.7.0.tgz?cache=0&sync_timestamp=1588893413176&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchardet%2Fdownload%2Fchardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=
 
-chart.js@^2.8.0:
-  version "2.9.3"
-  resolved "https://registry.npm.taobao.org/chart.js/download/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
-  integrity sha1-rjiEEU2v04G8YA9bNaGJE4qsHvc=
+chart.js@^2.9.4:
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684"
+  integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==
   dependencies:
     chartjs-color "^2.1.0"
     moment "^2.10.2"
@@ -10138,10 +10145,12 @@ vue-axios@^2.1.5:
   resolved "https://registry.npm.taobao.org/vue-axios/download/vue-axios-2.1.5.tgz#1af4bf1218ed71309c76afb38d0f683e312c24a7"
   integrity sha1-GvS/EhjtcTCcdq+zjQ9oPjEsJKc=
 
-vue-chartjs@^3.5.0:
-  version "3.5.0"
-  resolved "https://registry.npm.taobao.org/vue-chartjs/download/vue-chartjs-3.5.0.tgz#edd0c2be94c521bcbc5357c24afb9f3560855f84"
-  integrity sha1-7dDCvpTFIby8U1fCSvufNWCFX4Q=
+vue-chartjs@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-3.5.1.tgz#d25e845708f7744ae51bed9d23a975f5f8fc6529"
+  integrity sha512-foocQbJ7FtveICxb4EV5QuVpo6d8CmZFmAopBppDIGKY+esJV8IJgwmEW0RexQhxqXaL/E1xNURsgFFYyKzS/g==
+  dependencies:
+    "@types/chart.js" "^2.7.55"
 
 vue-cli-plugin-style-resources-loader@^0.1.4:
   version "0.1.4"

Неке датотеке нису приказане због велике количине промена