panhui 4 лет назад
Родитель
Сommit
bb5c803ec8

+ 34 - 0
src/main/vue/src/mixins/acc.js

@@ -0,0 +1,34 @@
+export default {
+    methods: {
+        accAdd(arg1, arg2) {
+            var r1, r2, m;
+            try {
+                r1 = arg1.toString().split('.')[1].length;
+            } catch (e) {
+                r1 = 0;
+            }
+            try {
+                r2 = arg2.toString().split('.')[1].length;
+            } catch (e) {
+                r2 = 0;
+            }
+            m = Math.pow(10, Math.max(r1, r2));
+            return (arg1 * m + arg2 * m) / m;
+        },
+        getNum(num, needUnit = false) {
+            if (num > 10000) {
+                if (needUnit) {
+                    return (num / 10000).toFixed(2) + '<small>万元</small>';
+                } else {
+                    return (num / 10000).toFixed(2);
+                }
+            } else {
+                if (needUnit) {
+                    return num.toFixed(2) + '<small>元</small>';
+                } else {
+                    return num.toFixed(2);
+                }
+            }
+        }
+    }
+};

+ 9 - 9
src/main/vue/src/router.js

@@ -23,18 +23,18 @@ const router = new Router({
                     name: '404',
                     name: '404',
                     component: () => import(/* webpackChunkName: "404" */ '@/views/404.vue')
                     component: () => import(/* webpackChunkName: "404" */ '@/views/404.vue')
                 },
                 },
-                // {
-                //     path: '/dashboard',
-                //     name: 'dashboard',
-                //     component: () => import(/* webpackChunkName: "404" */ '@/views/Dashboard.vue'),
-                //     meta: {
-                //         title: '首页'
-                //     }
-                // },
                 {
                 {
                     path: '/dashboard',
                     path: '/dashboard',
-                    redirect: 'collectionList'
+                    name: 'dashboard',
+                    component: () => import(/* webpackChunkName: "404" */ '@/views/Dashboard.vue'),
+                    meta: {
+                        title: '首页'
+                    }
                 },
                 },
+                // {
+                //     path: '/dashboard',
+                //     redirect: 'collectionList'
+                // },
                 {
                 {
                     path: '/api',
                     path: '/api',
                     name: 'api',
                     name: 'api',

+ 20 - 10
src/main/vue/src/views/Dashboard.vue

@@ -1,7 +1,7 @@
 <template>
 <template>
     <div class="dashboard">
     <div class="dashboard">
         <grid-layout
         <grid-layout
-            style="margin: 0 -10px"
+            style="margin: 0 -10px;"
             :layout="layout"
             :layout="layout"
             :col-num="12"
             :col-num="12"
             :row-height="30"
             :row-height="30"
@@ -22,17 +22,19 @@
                 :i="item.i"
                 :i="item.i"
                 :key="item.i"
                 :key="item.i"
             >
             >
-                <component :is="item.name"></component>
+                <component :is="item.name" :info="info"></component>
             </grid-item>
             </grid-item>
         </grid-layout>
         </grid-layout>
-        <el-button v-if="editable" @click="save">保存</el-button>
-        <el-button v-else @click="editable = true">编辑</el-button>
+        <!-- <el-button v-if="editable" @click="save">保存</el-button>
+        <el-button v-else @click="editable = true">编辑</el-button> -->
     </div>
     </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import { GridLayout, GridItem } from 'vue-grid-layout';
 import { GridLayout, GridItem } from 'vue-grid-layout';
 import UserWidget from '../widgets/UserWidget';
 import UserWidget from '../widgets/UserWidget';
+import NumWidget from '../widgets/NumWidget';
+import PriceWidget from '../widgets/PriceWidget';
 import LineChartWidget from '../widgets/LineChartWidget';
 import LineChartWidget from '../widgets/LineChartWidget';
 import BarChartWidget from '../widgets/BarChartWidget';
 import BarChartWidget from '../widgets/BarChartWidget';
 import PieChartWidget from '../widgets/PieChartWidget';
 import PieChartWidget from '../widgets/PieChartWidget';
@@ -42,15 +44,21 @@ export default {
     data() {
     data() {
         return {
         return {
             layout: [
             layout: [
-                { x: 0, y: 0, w: 6, h: 4, i: '0', name: 'UserWidget' },
-                { x: 6, y: 0, w: 6, h: 4, i: '1', name: 'UserWidget' },
-                { x: 0, y: 4, w: 6, h: 6, i: '2', name: 'BarChartWidget' },
-                { x: 0, y: 10, w: 6, h: 6, i: '3', name: 'LineChartWidget' },
-                { x: 6, y: 4, w: 6, h: 12, i: '4', name: 'PieChartWidget' }
+                { x: 6, y: 0, w: 6, h: 16, i: '5', name: 'LineChartWidget' },
+                { x: 0, y: 0, w: 3, h: 4, i: '2', name: 'PriceWidget' },
+                { x: 3, y: 0, w: 3, h: 4, i: '1', name: 'NumWidget' },
+                // { x: 0, y: 12, w: 6, h: 6, i: '4', name: 'BarChartWidget' },
+                { x: 0, y: 4, w: 6, h: 12, i: '6', name: 'PieChartWidget' }
             ],
             ],
-            editable: false
+            editable: false,
+            info: {}
         };
         };
     },
     },
+    mounted() {
+        this.$http.get('/statistic/total').then(res => {
+            this.info = res;
+        });
+    },
     methods: {
     methods: {
         save() {
         save() {
             console.log(JSON.stringify(this.layout));
             console.log(JSON.stringify(this.layout));
@@ -61,6 +69,8 @@ export default {
         GridLayout,
         GridLayout,
         GridItem,
         GridItem,
         UserWidget,
         UserWidget,
+        NumWidget,
+        PriceWidget,
         LineChartWidget,
         LineChartWidget,
         BarChartWidget,
         BarChartWidget,
         PieChartWidget
         PieChartWidget

+ 119 - 23
src/main/vue/src/widgets/LineChartWidget.vue

@@ -1,49 +1,145 @@
 <template>
 <template>
     <widget-card :bodyStyle="bodyStyle" ref="container">
     <widget-card :bodyStyle="bodyStyle" ref="container">
-        <canvas ref="chart" class="chart"></canvas>
+        <template #header>拉新统计</template>
+        <div class="top" ref="top">
+            <div class="top-content">
+                <div class="top-item">
+                    <div class="text1">今日新增</div>
+                    <div class="text2">{{ today }}</div>
+                </div>
+            </div>
+            <div class="top-content">
+                <div class="top-item">
+                    <div class="text1">累计注册</div>
+                    <div class="text2">{{ info.userNum }}</div>
+                </div>
+            </div>
+        </div>
+        <div class="chart-box">
+            <canvas ref="chart" class="chart"></canvas>
+        </div>
     </widget-card>
     </widget-card>
 </template>
 </template>
 <script>
 <script>
 import WidgetCard from './WidgetCard';
 import WidgetCard from './WidgetCard';
 import VueCharts from 'vue-chartjs';
 import VueCharts from 'vue-chartjs';
+import { format } from 'date-fns';
+import addDays from 'date-fns/esm/addDays';
 
 
 export default {
 export default {
+    props: {
+        info: {
+            type: Object,
+            default: () => {
+                return {};
+            }
+        }
+    },
     data() {
     data() {
         return {
         return {
             myChart: null,
             myChart: null,
             bodyStyle: {
             bodyStyle: {
                 display: 'flex',
                 display: 'flex',
+                flexDirection: 'column',
                 alignItems: 'center'
                 alignItems: 'center'
-            }
+            },
+            today: 0
         };
         };
     },
     },
     mounted() {
     mounted() {
-        this.$refs.chart.width = this.$refs.container.$el.offsetWidth - 20;
-        this.$refs.chart.height = this.$refs.container.$el.offsetHeight - 20;
-        var gradient = this.$refs.chart.getContext('2d').createLinearGradient(0, 0, 0, 200);
+        setTimeout(() => {
+            this.$nextTick(() => {
+                this.$refs.chart.width = this.$refs.container.$el.offsetWidth - 20;
+                this.$refs.chart.height = this.$refs.container.$el.offsetHeight - this.$refs.top.offsetHeight - 100;
+                this.init();
+            });
+        }, 500);
+    },
+    methods: {
+        init() {
+            this.$http.get('/statistic/userTrend', { day: 7 }).then(res => {
+                let start = addDays(new Date(), -6);
+                let days = [];
+                let datas = [];
+                for (let i = 0; i < 7; i++) {
+                    days.push(format(addDays(start, i), 'MM-dd'));
+                    datas.push(res[format(addDays(start, i), 'yyyy-MM-dd')] || 0);
+                }
+
+                this.today = res[format(new Date(), 'yyyy-MM-dd')] || 0;
 
 
-        gradient.addColorStop(0, 'rgba(32,160,255,0.7)');
-        gradient.addColorStop(1, 'rgba(32,160,255,0)');
-        this.myChart = new Chart(this.$refs.chart.getContext('2d'), {
-            type: 'line',
-            data: {
-                labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
-                datasets: [
-                    {
-                        label: 'Data One',
-                        backgroundColor: gradient,
-                        borderColor: '#20a0ff',
-                        borderWidth: '1px',
-                        data: [40, 39, 10, 40, 39, 80, 40]
+                this.myChart = new Chart(this.$refs.chart.getContext('2d'), {
+                    type: 'line',
+                    data: {
+                        labels: days,
+                        datasets: [
+                            {
+                                label: '新增人数',
+                                borderColor: '#409EFF',
+                                backgroundColor: '#409EFF50',
+                                data: datas.map(item => {
+                                    return Number(item);
+                                })
+                            }
+                        ]
+                    },
+                    options: {
+                        title: { text: '7日新增用户趋势', display: true },
+                        legend: { display: false },
+                        responsive: true,
+                        maintainAspectRatio: false
                     }
                     }
-                ]
-            },
-            options: { responsive: true, maintainAspectRatio: false }
-        });
+                });
+            });
+        }
     },
     },
     components: {
     components: {
         WidgetCard
         WidgetCard
     }
     }
 };
 };
 </script>
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.top {
+    margin-bottom: 10px;
+    display: flex;
+    justify-content: space-around;
+    align-self: stretch;
+}
+.top-content {
+    width: 50%;
+    display: flex;
+    justify-content: center;
+    &:nth-child(2) {
+        .top-item {
+            .text2 {
+                color: #4dcc6f;
+            }
+        }
+    }
+}
+.top-item {
+    width: 153px;
+    height: 125px;
+    background: #f5f7fa;
+    border-radius: 16px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    .text1 {
+        font-size: 14px;
+        font-weight: bold;
+        color: #666666;
+        line-height: 20px;
+    }
+
+    .text2 {
+        font-size: 22px;
+        font-weight: bold;
+        color: #409eff;
+        line-height: 29px;
+        margin-top: 2px;
+    }
+}
+</style>

+ 56 - 0
src/main/vue/src/widgets/NumWidget.vue

@@ -0,0 +1,56 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6;"></i>
+        <div class="info">
+            <div class="text">官方购买人数/二手市场(人)</div>
+            <div class="num">{{ info.officialNum }}/{{ info.transferNum }}</div>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+import acc from '../mixins/acc';
+
+export default {
+    props: {
+        info: {
+            type: Object,
+            default: () => {
+                return {};
+            }
+        }
+    },
+    mixins: [acc],
+    computed: {
+        total() {
+            return this.accAdd(this.info.officialNum || 0, this.info.transferNum || 0);
+        }
+    },
+    data() {
+        return {
+            bodyStyle: {
+                display: 'flex',
+                alignItems: 'center'
+            }
+        };
+    },
+    components: {
+        WidgetCard
+    }
+};
+</script>
+<style lang="less" scoped>
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
+</style>

+ 207 - 18
src/main/vue/src/widgets/PieChartWidget.vue

@@ -1,11 +1,43 @@
 <template>
 <template>
     <widget-card :bodyStyle="bodyStyle" ref="container">
     <widget-card :bodyStyle="bodyStyle" ref="container">
-        <canvas ref="chart" class="chart"></canvas>
+        <template #header>
+            <div class="header">
+                <span>销售业绩</span>
+                <el-select
+                    style="width: 120px;"
+                    size="mini"
+                    v-model="value"
+                    @change="changeSelect"
+                    placeholder="请选择"
+                >
+                    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                    </el-option>
+                </el-select>
+            </div>
+        </template>
+        <div class="box-content" ref="box">
+            <div class="box">
+                <div class="text1">官方销售额/二手市场</div>
+                <div class="text2">
+                    <span v-html="getNum(official, true)"></span>/ <span v-html="getNum(transfer, true)"></span>
+                </div>
+            </div>
+            <div class="box">
+                <div class="text1">官方订单/二手市场(单)</div>
+                <div class="text2">{{ officialNum }}/{{ transferNum }}</div>
+            </div>
+        </div>
+        <div class="chart-box">
+            <canvas ref="chart" class="chart"></canvas>
+        </div>
     </widget-card>
     </widget-card>
 </template>
 </template>
 <script>
 <script>
 import WidgetCard from './WidgetCard';
 import WidgetCard from './WidgetCard';
 import VueCharts from 'vue-chartjs';
 import VueCharts from 'vue-chartjs';
+import { format } from 'date-fns';
+import acc from '../mixins/acc';
+import addDays from 'date-fns/esm/addDays';
 
 
 export default {
 export default {
     data() {
     data() {
@@ -13,30 +45,187 @@ export default {
             myChart: null,
             myChart: null,
             bodyStyle: {
             bodyStyle: {
                 display: 'flex',
                 display: 'flex',
-                alignItems: 'center'
-            }
+                alignItems: 'center',
+                justifyContent: ' space-around'
+            },
+            official: 0,
+            transfer: 0,
+            officialNum: 0,
+            transferNum: 0,
+            value: '',
+            options: [],
+            numInfo: {},
+            priceInfo: {}
         };
         };
     },
     },
+    mixins: [acc],
+    computed: {
+        total() {
+            return this.accAdd(this.official, this.transfer);
+        },
+        totalNum() {
+            return this.accAdd(this.officialNum, this.transferNum);
+        }
+    },
     mounted() {
     mounted() {
-        this.$refs.chart.width = this.$refs.container.$el.offsetWidth - 20;
-        this.$refs.chart.height = this.$refs.container.$el.offsetHeight - 20;
-        this.myChart = new Chart(this.$refs.chart.getContext('2d'), {
-            type: 'pie',
-            data: {
-                labels: ['VueJs', 'EmberJs', 'ReactJs', 'AngularJs'],
-                datasets: [
-                    {
-                        backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
-                        data: [40, 20, 80, 10]
+        let options = [];
+        this.value = format(new Date(), 'yyyy-MM-dd');
+        for (let i = 0; i < 7; i++) {
+            options.push({
+                label: i < 2 ? (i === 0 ? '今日' : '昨日') : format(addDays(new Date(), i), 'MM-dd'),
+                value: format(addDays(new Date(), 0 - i), 'yyyy-MM-dd')
+            });
+        }
+        this.options = options;
+        this.$http
+            .get('/statistic/orderNumTrend', {
+                day: 7
+            })
+            .then(res => {
+                this.numInfo = res;
+
+                this.$http
+                    .get('/statistic/orderPriceTrend', {
+                        day: 7
+                    })
+                    .then(res => {
+                        this.priceInfo = res;
+                        this.$refs.chart.width = this.$refs.container.$el.offsetWidth - this.$refs.box.offsetWidth - 40;
+                        this.$refs.chart.height = this.$refs.container.$el.offsetHeight - 100;
+                        this.init(this.value);
+                    });
+            });
+    },
+    methods: {
+        init(value) {
+            this.officialNum = this.numInfo.official ? this.numInfo.official[value] || 0 : 0;
+            this.transferNum = this.numInfo.transfer ? this.numInfo.transfer[value] || 0 : 0;
+            this.official = this.priceInfo.official ? this.priceInfo.official[value] || 0 : 0;
+            this.transfer = this.priceInfo.transfer ? this.priceInfo.transfer[value] || 0 : 0;
+            this.$nextTick(() => {
+                this.myChart = new Chart(this.$refs.chart.getContext('2d'), {
+                    type: 'doughnut',
+                    data: {
+                        labels: ['官方销售额', '二手市场销售额'],
+                        datasets: [
+                            {
+                                title: '销售额',
+                                backgroundColor: ['#FEB30E', '#FF7970'],
+                                data: [this.official, this.transfer]
+                            }
+                        ]
+                    },
+                    options: {
+                        responsive: true,
+                        plugins: {
+                            legend: {
+                                labels: {
+                                    generateLabels: function (chart) {
+                                        // Get the default label list
+                                        const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
+                                        const labelsOriginal = original.call(this, chart);
+
+                                        // Build an array of colors used in the datasets of the chart
+                                        let datasetColors = chart.data.datasets.map(function (e) {
+                                            return e.backgroundColor;
+                                        });
+                                        datasetColors = datasetColors.flat();
+
+                                        // Modify the color and hide state of each label
+                                        labelsOriginal.forEach(label => {
+                                            // There are twice as many labels as there are datasets. This converts the label index into the corresponding dataset index
+                                            label.datasetIndex = (label.index - (label.index % 2)) / 2;
+
+                                            // The hidden state must match the dataset's hidden state
+                                            label.hidden = !chart.isDatasetVisible(label.datasetIndex);
+
+                                            // Change the color to match the dataset
+                                            label.fillStyle = datasetColors[label.index];
+                                        });
+
+                                        return labelsOriginal;
+                                    }
+                                },
+                                onClick: function (mouseEvent, legendItem, legend) {
+                                    // toggle the visibility of the dataset from what it currently is
+                                    legend.chart.getDatasetMeta(
+                                        legendItem.datasetIndex
+                                    ).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);
+                                    legend.chart.update();
+                                }
+                            },
+                            tooltip: {
+                                callbacks: {
+                                    label: function (context) {
+                                        const labelIndex = context.datasetIndex * 2 + context.dataIndex;
+                                        return context.chart.data.labels[labelIndex] + ': ' + context.formattedValue;
+                                    }
+                                }
+                            }
+                        }
                     }
                     }
-                ]
-            },
-            options: { responsive: true, maintainAspectRatio: false }
-        });
+                });
+            });
+        },
+        changeSelect() {
+            this.$nextTick(() => {
+                this.init(this.value);
+            });
+        }
     },
     },
     components: {
     components: {
         WidgetCard
         WidgetCard
     }
     }
 };
 };
 </script>
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.box-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: space-around;
+    align-self: stretch;
+    padding: 30px;
+}
+.box {
+    padding: 0 20px;
+    height: 125px;
+    background: #f5f7fa;
+    border-radius: 16px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    min-width: 153px;
+    box-sizing: border-box;
+    .text1 {
+        font-size: 14px;
+        font-weight: bold;
+        color: #666666;
+        line-height: 20px;
+        white-space: nowrap;
+    }
+    .text2 {
+        color: #feb30e;
+        font-size: 22px;
+        font-weight: bold;
+        line-height: 29px;
+        margin-top: 2px;
+        white-space: nowrap;
+        /deep/small {
+            font-size: 12px;
+        }
+    }
+
+    &:nth-child(2) {
+        .text2 {
+            color: #4dcc6f;
+        }
+    }
+}
+.header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+</style>

+ 55 - 0
src/main/vue/src/widgets/PriceWidget.vue

@@ -0,0 +1,55 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <i class="fa-fw fas fa-shopping-basket fa-3x" style="color: #40c9c6;"></i>
+        <div class="info">
+            <div class="text">官方交易额/二手市场(万元)</div>
+            <div class="num">{{ getNum(info.officialPrice) }}/{{ getNum(info.transferPrice) }}</div>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+import acc from '../mixins/acc';
+export default {
+    props: {
+        info: {
+            type: Object,
+            default: () => {
+                return {};
+            }
+        }
+    },
+    computed: {
+        total() {
+            return this.accAdd(this.info.officialPrice || 0, this.info.transferPrice || 0);
+        }
+    },
+    mixins: [acc],
+    data() {
+        return {
+            bodyStyle: {
+                display: 'flex',
+                alignItems: 'center'
+            }
+        };
+    },
+    components: {
+        WidgetCard
+    }
+};
+</script>
+<style lang="less" scoped>
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
+</style>

+ 10 - 2
src/main/vue/src/widgets/UserWidget.vue

@@ -2,8 +2,8 @@
     <widget-card :bodyStyle="bodyStyle">
     <widget-card :bodyStyle="bodyStyle">
         <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6"></i>
         <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6"></i>
         <div class="info">
         <div class="info">
-            <div class="text">User</div>
-            <div class="num">4,258</div>
+            <div class="text">累计注册人数</div>
+            <div class="num">{{info.userNum}}</div>
         </div>
         </div>
     </widget-card>
     </widget-card>
 </template>
 </template>
@@ -11,6 +11,14 @@
 import WidgetCard from './WidgetCard';
 import WidgetCard from './WidgetCard';
 
 
 export default {
 export default {
+    props:{
+        info:{
+            type:Object,
+            default:()=>{
+                return {}
+            }
+        }
+    },
     data() {
     data() {
         return {
         return {
             bodyStyle: {
             bodyStyle: {