Browse Source

feat: 数据概览

xiansin 2 years ago
parent
commit
eb3d544e4e

+ 41 - 0
server/_ide_helper_models.php

xqd
@@ -0,0 +1,41 @@
+<?php
+
+// @formatter:off
+/**
+ * A helper file for your Eloquent Models
+ * Copy the phpDocs from this file to the correct Model,
+ * And remove them from this file, to prevent double declarations.
+ *
+ * @author Barry vd. Heuvel <barryvdh@gmail.com>
+ */
+
+
+namespace App\Models{
+/**
+ * App\Models\StatProduct
+ *
+ * @property int $id
+ * @property string $product_id 产品ID
+ * @property string $user_id 用户ID
+ * @property \Illuminate\Support\Carbon|null $updated_at
+ * @property \Illuminate\Support\Carbon|null $deleted_at
+ * @property \Illuminate\Support\Carbon|null $created_at
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct newModelQuery()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct newQuery()
+ * @method static \Illuminate\Database\Query\Builder|StatProduct onlyTrashed()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct query()
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereCreatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereDeletedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereProductId($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereUpdatedAt($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StatProduct whereUserId($value)
+ * @method static \Illuminate\Database\Query\Builder|StatProduct withTrashed()
+ * @method static \Illuminate\Database\Query\Builder|StatProduct withoutTrashed()
+ * @mixin \Eloquent
+ * @property-read \App\Models\Product|null $product
+ * @property-read \App\Models\User|null $user
+ */
+//	class StatProduct extends \Eloquent {}
+}
+

+ 192 - 2
server/app/Admin/Controllers/DashBoardController.php

xqd
@@ -4,21 +4,211 @@ namespace App\Admin\Controllers;
 
 use App\Admin\Metrics\Examples;
 use App\Http\Controllers\Controller;
+use App\Models\Account;
+use App\Models\ProductCategory;
+use App\Models\Showroom;
+use App\Models\StatProduct;
+use App\Models\StatProductDownload;
+use App\Models\StatShowroom;
+use Carbon\Carbon;
+use Dcat\Admin\Admin;
 use Dcat\Admin\Http\Controllers\Dashboard;
+use Dcat\Admin\Http\JsonResponse;
 use Dcat\Admin\Layout\Column;
 use Dcat\Admin\Layout\Content;
 use Dcat\Admin\Layout\Row;
 use Dcat\Admin\Widgets\Card;
+use Dcat\Admin\Widgets\Tab;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Query\Builder;
 
 class DashBoardController extends Controller
 {
     public function view(Content $content)
     {
-        return $content->body('<div style="text-align: center; font-size: 28px; font-weight: 300">欢迎进入极创社管理后台</div>');
+        if(request()->isMethod('post')){
+            $isProduct = request()->input('product', 1);
+            if($isProduct){
+                return $this->viewerProduct();
+            }
+            return $this->viewerShowroom();
+        }
+        $isProduct = \request('product',1);
+        if ($isProduct) {
+            $view = $this->viewerProduct();
+        }else{
+            $view = $this->viewerShowroom();
+        }
+        return $content->body($view);
     }
 
+
     public function download(Content $content)
     {
-        return $content->body(view('admin.welcome'));
+        if(request()->isMethod('post')){
+            return $this->queryData('download');
+        }
+
+        $categories = ProductCategory::with(['products:id,name,cate_id'])->get();
+        $data = [
+            'categories' => $categories
+        ];
+        static::loadJS('download');
+        return $content->body(view('admin.dashboard.download', $data));
+    }
+
+    private function viewerShowroom()
+    {
+        if(request()->isMethod('post')){
+            return $this->queryData('product');
+        }
+        static::loadJS('showroom');
+        $showrooms = Showroom::all();
+        $data = [
+            'showrooms' => $showrooms
+        ];
+        return view('admin.dashboard.showroom', $data);
+    }
+
+    private function viewerProduct()
+    {
+        if(request()->isMethod('post')){
+            return $this->queryData('product');
+        }
+        static::loadJS('product');
+        $categories = ProductCategory::with(['products:id,name,cate_id'])->get();
+        $data = [
+            'categories' => $categories
+        ];
+        return view('admin.dashboard.product', $data);
+    }
+
+    private function queryData($type): JsonResponse
+    {
+        $startAt = \request()->input('startAt','');
+        $endAt = \request()->input('endAt','');
+        $productIds = \request()->input('productIds',[]);
+        $name = \request()->input('name','');
+        $dates = $this->getDays($startAt, $endAt);
+
+        $userIds = $this->getUserIds();
+
+        if(empty($productIds) || ($name && empty($userIds))){
+            return Admin::json()->data(['dates' => $dates])->success('');
+        }
+
+
+        if($type == 'download' || $type == 'product'){
+            $model = $type == 'download' ? app(StatProductDownload::class) :app(StatProduct::class);
+            $model = $model->with([
+                'product' => function($query) use ($productIds){
+                    /* @var Builder $query*/
+                    $query->when($productIds, function ($query) use ($productIds){
+                        /* @var Builder $query*/
+                        return $query->whereIn('id', $productIds);
+                    });
+                },
+            ])->selectRaw(
+                'product_id,DATE_FORMAT(created_at, "%Y-%m-%d") as date, count(*) as count'
+            );
+        }else{
+            $model = StatShowroom::with([
+                'showroom' => function($query) use ($productIds){
+                    /* @var Builder $query*/
+                    $query->when($productIds, function ($query) use ($productIds){
+                        /* @var Builder $query*/
+                        return $query->whereIn('id', $productIds);
+                    });
+                },
+            ])->selectRaw(
+                'showroom_id,showroom_id as product_id,DATE_FORMAT(created_at, "%Y-%m-%d") as date, count(*) as count'
+            );
+        }
+
+        /* @var Model $model*/
+        $lists = $model->when($startAt, function ($query) use ($startAt){
+            return $query->where('created_at', '>=',$startAt);
+        })->when($endAt, function ($query) use ($endAt){
+            return $query->where('created_at', '<=',$endAt);
+        })->when($userIds, function ($query) use ($userIds){
+            return $query->whereIn('user_id', $userIds);
+        })->groupBy(['product_id','date'])
+            ->get();
+
+        $statData = [];
+        foreach ($lists as $list){
+            $statData[$list->date][$list->product_id] = $list->toArray();
+        }
+
+
+        $analyzeData = [];
+        foreach ($productIds as $id){
+            foreach ($dates as $date){
+                $analyzeData[$id][] = $statData[$date][$id]['count'] ?? 0;
+            }
+        }
+        $data = [
+            'dates' => $dates,
+        ];
+        foreach ($analyzeData as $key => $value){
+            $data['analyze_'.$key] = $value;
+        }
+
+        return Admin::json()->data($data)->success('');
+
+    }
+
+    private static function getDays($startAt, $endAt): array
+    {
+        $startAt = $startAt ?: Carbon::today()->subDays(30)->toDateString();
+        $endAt = $endAt ?: Carbon::today()->toDateString();
+        $daysPeriod = Carbon::parse($startAt)->daysUntil($endAt);
+        return iterator_to_array(
+            $daysPeriod->map(function ($day){
+                return Carbon::make($day)->toDateString();
+            })
+        );
+    }
+
+    private function getUserIds(): array
+    {
+        $name = \request()->input('name','');
+        if(empty($name)) return [];
+        $account = Account::with([
+            'user:id,account_id'
+        ])->where('user_name', 'Like', "%$name%")->get();
+        return iterator_to_array(
+            $account->map(function ($row){
+                return $row->user->id;
+            })
+        );
+    }
+
+    private static function loadJS($type)
+    {
+        \Admin::css([
+            'vendor/dcat-admin/dcat/plugins/bootstrap-datetimepicker/bootstrap-datetimepicker.min.css'
+        ]);
+        $analyzeJS = '/static/js/analyze.js';
+        $analyzeJS = $analyzeJS.'?t='.fileatime(public_path($analyzeJS));
+
+        \Admin::js([
+            '/vendor/dcat-admin/dcat/plugins/moment/moment-with-locales.min.js',
+            '/vendor/dcat-admin/dcat/plugins/bootstrap-datetimepicker/bootstrap-datetimepicker.min.js',
+            '/static/js/echarts.common.min.js',
+            $analyzeJS,
+        ]);
+
+        if(is_array($type)){
+            foreach ($type as $item){
+                $pageJS = "/static/js/analyze.{$item}.js";
+                $pageJS = $pageJS.'?t='.fileatime(public_path($pageJS));
+                \Admin::js($pageJS);
+            }
+        }else{
+            $pageJS = "/static/js/analyze.{$type}.js";
+            $pageJS = $pageJS.'?t='.fileatime(public_path($pageJS));
+            \Admin::js($pageJS);
+        }
     }
 }

+ 2 - 0
server/app/Admin/Controllers/ProductController.php

xqd
@@ -90,6 +90,8 @@ class="img img-thumbnail">';
                     return ProductCategory::select(['id','name'])->get()->pluck('name','id')->toArray();
                 })->width(2);
                 $filter->like('name')->width(2);
+
+                $filter->between('name')->datetime()->width(4);
             });
 
             $grid->batchActions([new BatchProduct()]);

+ 2 - 0
server/app/Admin/routes.php

xqd
@@ -14,8 +14,10 @@ Route::group([
 
     $router->get('/', 'HomeController@index');
     //
+    $router->post('dashboard/view', 'DashBoardController@view');
     $router->get('dashboard/view', 'DashBoardController@view');
     // 下载数据
+    $router->post('dashboard/download', 'DashBoardController@download');
     $router->get('dashboard/download', 'DashBoardController@download');
 
     // 账号

+ 8 - 1
server/app/Models/Account.php

xqd xqd xqd
@@ -5,6 +5,7 @@ namespace App\Models;
 use Dcat\Admin\Traits\HasDateTimeFormatter;
 
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
 /**
  * App\Models\Account
@@ -31,6 +32,7 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Eloquent\Builder|Account whereUpdatedAt($value)
  * @method static \Illuminate\Database\Eloquent\Builder|Account whereUserName($value)
  * @mixin \Eloquent
+ * @property-read \App\Models\User|null $user
  */
 class Account extends Model
 {
@@ -39,6 +41,11 @@ class Account extends Model
     protected $table = 'accounts';
 
     protected $hidden = [
-        'password','updated_at','id','created_at'
+        'password','updated_at','created_at'
     ];
+
+    public function user(): BelongsTo
+    {
+        return $this->belongsTo(User::class,'id','account_id');
+    }
 }

+ 10 - 1
server/app/Models/ProductCategory.php

xqd xqd xqd
@@ -3,6 +3,8 @@
 namespace App\Models;
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 
@@ -35,6 +37,8 @@ use Illuminate\Database\Eloquent\Model;
  * @property-read ProductCategory|null $parent
  * @property int $is_opened 是否启用
  * @method static \Illuminate\Database\Eloquent\Builder|ProductCategory whereIsOpened($value)
+ * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Product[] $products
+ * @property-read int|null $products_count
  */
 class ProductCategory extends Model
 {
@@ -43,9 +47,14 @@ class ProductCategory extends Model
 
     protected $table = 'product_categories';
 
-    public function parent(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+    public function parent(): BelongsTo
     {
         return $this->belongsTo(ProductCategory::class,'pid','id');
     }
 
+    public function products(): HasMany
+    {
+        return $this->hasMany(Product::class,'cate_id','id');
+    }
+
 }

+ 14 - 1
server/app/Models/StatProduct.php

xqd xqd xqd
@@ -3,6 +3,7 @@
 namespace App\Models;
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 
@@ -28,6 +29,8 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Query\Builder|StatProduct withTrashed()
  * @method static \Illuminate\Database\Query\Builder|StatProduct withoutTrashed()
  * @mixin \Eloquent
+ * @property-read \App\Models\Product|null $product
+ * @property-read \App\Models\User|null $user
  */
 class StatProduct extends Model
 {
@@ -35,5 +38,15 @@ class StatProduct extends Model
     use SoftDeletes;
 
     protected $table = 'stat_products';
-    
+
+    public function product(): BelongsTo
+    {
+        return $this->belongsTo(Product::class,'product_id');
+    }
+
+    public function user(): BelongsTo
+    {
+        return $this->belongsTo(user::class,'user_id');
+    }
+
 }

+ 14 - 1
server/app/Models/StatProductDownload.php

xqd xqd xqd
@@ -3,6 +3,7 @@
 namespace App\Models;
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 
@@ -28,6 +29,8 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Query\Builder|StatProductDownload withTrashed()
  * @method static \Illuminate\Database\Query\Builder|StatProductDownload withoutTrashed()
  * @mixin \Eloquent
+ * @property-read \App\Models\Product|null $product
+ * @property-read \App\Models\User|null $user
  */
 class StatProductDownload extends Model
 {
@@ -35,5 +38,15 @@ class StatProductDownload extends Model
     use SoftDeletes;
 
     protected $table = 'stat_product_download';
-    
+
+    public function product(): BelongsTo
+    {
+        return $this->belongsTo(Product::class,'product_id','id');
+    }
+
+    public function user(): BelongsTo
+    {
+        return $this->belongsTo(user::class,'user_id','id');
+    }
+
 }

+ 14 - 1
server/app/Models/StatShowroom.php

xqd xqd xqd
@@ -3,6 +3,7 @@
 namespace App\Models;
 
 use Dcat\Admin\Traits\HasDateTimeFormatter;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Database\Eloquent\Model;
 
@@ -28,6 +29,8 @@ use Illuminate\Database\Eloquent\Model;
  * @method static \Illuminate\Database\Query\Builder|StatShowroom withTrashed()
  * @method static \Illuminate\Database\Query\Builder|StatShowroom withoutTrashed()
  * @mixin \Eloquent
+ * @property-read \App\Models\Showroom|null $showroom
+ * @property-read \App\Models\User|null $user
  */
 class StatShowroom extends Model
 {
@@ -35,5 +38,15 @@ class StatShowroom extends Model
     use SoftDeletes;
 
     protected $table = 'stat_showrooms';
-    
+
+    public function showroom(): BelongsTo
+    {
+        return $this->belongsTo(Showroom::class,'showroom_id');
+    }
+
+    public function user(): BelongsTo
+    {
+        return $this->belongsTo(user::class,'user_id');
+    }
+
 }

+ 11 - 0
server/public/static/js/analyze.download.js

xqd
@@ -0,0 +1,11 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/10/3.
+ */
+AnalyzeChart(document.getElementById("analyze"), {
+    btnName: '.btn-product',
+    searchBtn: '.search',
+    formName: '.product-form',
+    url: '/admin/dashboard/download',
+    yAxisName: '产品下载量',
+    nameSuffix: '下载量'
+})

+ 194 - 0
server/public/static/js/analyze.js

xqd
@@ -0,0 +1,194 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/10/3.
+ */
+let AnalyzeChart = (function () {
+    const version = "0.0.1";
+
+    let chartInstance = '';
+
+    let AnalyzeChart = function (selector, options) {
+        return new AnalyzeChart.fn.init(selector, options);
+    };
+
+    AnalyzeChart.fn = AnalyzeChart.prototype = {
+        AnalyzeChart: version,
+        options: {btnName: '', searchBtn: '', url: '', yAxisName: '',nameSuffix: '',formName:''},
+        constructor: AnalyzeChart,
+        init: function (selector, options) {
+            this.options = options
+            chartInstance = echarts.init(selector)
+            this.buttonEvent()
+        },
+        buttonEvent() {
+            let _this = this
+            $(`${_this.options.btnName}`).bind("click", function () {
+                $(this).toggleClass('btn-primary').toggleClass('btn-default')
+                _this.render()
+            })
+            $('.datepicker').datetimepicker({
+                locale: 'zh-CN',
+                format: 'YYYY-MM-DD',
+            });
+            $(`${_this.options.searchBtn}`).bind('click', function () {
+                _this.render()
+            })
+        },
+        render() {
+            let data = this.getLegend();
+            let categoryData = data.categoryData;
+            let legendData = data.legendData;
+            let option = this.chartOptions(legendData);
+            chartInstance.setOption(option, true);
+            this.query(legendData, categoryData)
+        },
+        query(legendData, categoryData) {
+            chartInstance.showLoading()
+            this.getData().then(res => {
+                let {data} = res
+                let series = [];
+                legendData.forEach((item, index) => {
+                    let temp = {
+                        name: item.name,
+                        type: item.type,
+                        data: data[categoryData[index]]
+                    };
+                    series.push(temp);
+                });
+
+                chartInstance.setOption({
+                    xAxis: [{
+                        data: data.dates,
+                        axisPointer: {
+                            type: 'shadow'
+                        },
+                        splitLine: {
+                            show: true
+                        }
+                    }],
+                    series: series
+                });
+                chartInstance.hideLoading();
+            });
+        },
+        getLegend() {
+            let categoryData = [];
+            let legendData = [];
+            let _this = this
+            $(`${_this.options.btnName}.btn-primary`).each(function () {
+                let id = $(this).data("id");
+                let name = $(this).text();
+                let names = {name: `[${name}]${_this.options.nameSuffix}`, type: 'line', barMaxWidth: 30, average: false};
+                categoryData.push(`analyze_${id}`);
+                legendData.push(names);
+            });
+            return {legendData, categoryData}
+        },
+        chartOptions(legends) {
+            let seriesData = [];
+            let legendSelected = {};
+            let colors = ["#0095F3", "#95E04A", "#605EEB", "#4ACDBB", "#FFD480"];
+            for (let item of legends) {
+                let series = {
+                    name: item.name,
+                    type: item.type !== undefined ? item.type : 'line',
+                    barWidth: item.barWidth !== undefined ? item.barWidth : '',
+                    yAxisIndex: item.yAxisIndex !== undefined ? item.yAxisIndex : 0,
+                    smooth: true,
+                    data: [],
+                };
+                legendSelected[item.name] = item.show !== undefined ? item.show : true;
+                if (item.average) {
+                    series['markLine'] = {
+                        data: [
+                            {type: 'average', name: '平均值'}
+                        ]
+                    }
+                }
+                seriesData.push(series);
+            }
+            return {
+                animation: true,
+                tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {            // 坐标轴指示器,坐标轴触发有效
+                        type: 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
+                    },
+                },
+                toolbox: {
+                    feature: {
+                        // dataView: {show: true, readOnly: false},
+                        magicType: {show: true, type: ['line', 'bar']},
+                        restore: {show: true},
+                        saveAsImage: {show: true}
+                    }
+                },
+                /*legend: {
+                    type :'scroll',
+                    left:50,
+                    right:50,
+                    height:500,
+                    data: legend_data,
+                    selected:legend_selected,
+                },*/
+                grid: {
+                    left: '4%',
+                    right: '4%',
+                    containLabel: true
+                },
+                dataZoom: [{
+                    type: 'slider',
+                    show: true,
+                    xAxisIndex: 0,
+                    yAxisIndex: 1,
+                    start: 0,
+                    end: 100,
+                }, {
+                    type: 'inside',
+                    xAxisIndex: 0,
+                    yAxisIndex: 1,
+                    start: 0,
+                    end: 100
+                }],
+                xAxis: {
+                    type: 'category',
+                    data: [],
+                    //boundaryGap: false
+                },
+                yAxis: [
+                    {
+                        name: this.options.yAxisName,
+                        type: 'value',
+                        axisLabel: {
+                            formatter: '{value} '
+                        }
+                    }
+                ],
+                series: seriesData,
+                //color: colors
+            };
+        },
+        getData() {
+            let productIds = [];
+            let _this = this
+            $(`${this.options.btnName}.btn-primary`).each((index, obj) => {
+                productIds.push($(obj).data('id'))
+            })
+            let startAt = $(`${_this.options.formName}  #filter-column-name-start`).val();
+            let endAt = $(`${_this.options.formName} #filter-column-name-end`).val();
+            let name = $(`${_this.options.formName}  .filter-column-name`).val();
+            let data = {
+                productIds,
+                startAt,
+                endAt,
+                name
+            }
+            return $.post({
+                url: this.options.url,
+                data
+            })
+        },
+    }
+    AnalyzeChart.fn.init.prototype = AnalyzeChart.fn;
+
+    return AnalyzeChart;
+})()

+ 194 - 0
server/public/static/js/analyze.js.bak

xqd
@@ -0,0 +1,194 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/10/3.
+ */
+let statDown = (function () {
+    let chartInstance = '';
+    let dataUrl = '';
+    let btnName = '';
+    let searchBtnName = '';
+
+    statDown.init = {
+        _init(options) {
+            chartInstance = echarts.init(document.getElementById(options.dom))
+            dataUrl = options.url
+            btnName = options.btnName
+            searchBtnName = options.searchBtnName
+
+            statDown.buttonEvent._init()
+        }
+    }
+    statDown.buttonEvent = {
+        _init() {
+            $(btnName).bind("click", function () {
+                $(this).toggleClass('btn-primary').toggleClass('btn-default')
+                statDown.chart.render()
+            })
+            $('.datepicker').datetimepicker({
+                locale: 'zh-CN',
+                format: 'YYYY-MM-DD',
+            });
+            $(searchBtnName).bind('click', function () {
+                statDown.chart.render()
+            })
+        },
+    }
+
+    statDown.query = {
+        getData() {
+            let productIds = [];
+            $(`${btnName}.btn-primary`).each((index, obj) => {
+                productIds.push($(obj).data('id'))
+            })
+            let startAt = $("#filter-column-name-start").val();
+            let endAt = $("#filter-column-name-end").val();
+            let name = $(".filter-column-name").val();
+            let data = {
+                productIds,
+                startAt,
+                endAt,
+                name
+            }
+            return $.post({
+                url: dataUrl,
+                data
+            })
+        },
+    };
+    statDown.chart = {
+        render() {
+            let data = this.getLegend();
+            let categoryData = data.categoryData;
+            let legendData = data.legendData;
+            let option = this.options(legendData);
+            chartInstance.setOption(option, true);
+            this.query(legendData, categoryData)
+        },
+        query(legendData, categoryData) {
+            chartInstance.showLoading()
+            statDown.query.getData().then(res => {
+                let {data} = res
+                let series = [];
+                legendData.forEach((item, index) => {
+                    let temp = {
+                        name: item.name,
+                        type: item.type,
+                        data: data[categoryData[index]]
+                    };
+                    series.push(temp);
+                });
+
+                chartInstance.setOption({
+                    xAxis: [{
+                        data: data.dates,
+                        axisPointer: {
+                            type: 'shadow'
+                        },
+                        splitLine: {
+                            show: true
+                        }
+                    }],
+                    series: series
+                });
+                chartInstance.hideLoading();
+            });
+        },
+        getLegend() {
+            let categoryData = [];
+            let legendData = [];
+            $(".btn-product.btn-primary").each(function () {
+                let id = $(this).data("id");
+                let name = $(this).text();
+                let names = {name: `[${name}]下载数量`, type: 'line', barMaxWidth: 30, average: false};
+                categoryData.push(`product_download_num_${id}`);
+                legendData.push(names);
+            });
+            return {legendData, categoryData}
+        },
+        options(legends) {
+            let seriesData = [];
+            let legendSelected = {};
+            let colors = ["#0095F3", "#95E04A", "#605EEB", "#4ACDBB", "#FFD480"];
+            for (let item of legends) {
+                let series = {
+                    name: item.name,
+                    type: item.type !== undefined ? item.type : 'line',
+                    barWidth: item.barWidth !== undefined ? item.barWidth : '',
+                    yAxisIndex: item.yAxisIndex !== undefined ? item.yAxisIndex : 0,
+                    smooth: true,
+                    data: [],
+                };
+                legendSelected[item.name] = item.show !== undefined ? item.show : true;
+                if (item.average) {
+                    series['markLine'] = {
+                        data: [
+                            {type: 'average', name: '平均值'}
+                        ]
+                    }
+                }
+                seriesData.push(series);
+            }
+            return {
+                animation: true,
+                tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {            // 坐标轴指示器,坐标轴触发有效
+                        type: 'shadow'        // 默认为直线,可选为:'line' | 'shadow'
+                    },
+                },
+                toolbox: {
+                    feature: {
+                        // dataView: {show: true, readOnly: false},
+                        magicType: {show: true, type: ['line', 'bar']},
+                        restore: {show: true},
+                        saveAsImage: {show: true}
+                    }
+                },
+                /*legend: {
+                    type :'scroll',
+                    left:50,
+                    right:50,
+                    height:500,
+                    data: legend_data,
+                    selected:legend_selected,
+                },*/
+                grid: {
+                    left: '4%',
+                    right: '4%',
+                    containLabel: true
+                },
+                dataZoom: [{
+                    type: 'slider',
+                    show: true,
+                    xAxisIndex: 0,
+                    yAxisIndex: 1,
+                    start: 0,
+                    end: 100,
+                }, {
+                    type: 'inside',
+                    xAxisIndex: 0,
+                    yAxisIndex: 1,
+                    start: 0,
+                    end: 100
+                }],
+                xAxis: {
+                    type: 'category',
+                    data: [],
+                    //boundaryGap: false
+                },
+                yAxis: [
+                    {
+                        name: '产品下载量',
+                        type: 'value',
+                        axisLabel: {
+                            formatter: '{value} '
+                        }
+                    }
+                ],
+                series: seriesData,
+                //color: colors
+            };
+        }
+    }
+
+})
+

+ 12 - 0
server/public/static/js/analyze.product.js

xqd
@@ -0,0 +1,12 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/10/3.
+ */
+AnalyzeChart(document.getElementById("analyze"), {
+    btnName: '.btn-product',
+    searchBtn: '.product-search',
+    formName: '.product-form',
+    url: '/admin/dashboard/view?product=1',
+    yAxisName: '产品浏览量',
+    nameSuffix: '浏览量'
+})
+

+ 11 - 0
server/public/static/js/analyze.showroom.js

xqd
@@ -0,0 +1,11 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2022/10/3.
+ */
+AnalyzeChart(document.getElementById("showroom_analyze"), {
+    btnName: '.btn-showroom',
+    searchBtn: '.showroom-search',
+    formName: '.showroom-form',
+    url: '/admin/dashboard/view?product=0',
+    yAxisName: '展厅浏览量',
+    nameSuffix: '浏览量'
+})

File diff suppressed because it is too large
+ 34 - 0
server/public/static/js/echarts.common.min.js


+ 55 - 0
server/resources/views/admin/dashboard/download.blade.php

xqd
@@ -0,0 +1,55 @@
+<style type="text/css">
+    .container-fluid{
+        background: #fff;
+        padding-bottom: 50px;
+    }
+    .form-inline{
+        padding-top: 15px;
+    }
+</style>
+
+<div class="container-fluid">
+    <form class="form-inline product-form" action="#">
+        <div class="form-group col-sm-3">
+            <div class="input-group input-group-sm">
+                <div class="input-group-prepend">
+                    <span class="input-group-text bg-white text-capitalize"><b>时间</b>&nbsp;<i class="feather icon-calendar"></i></span>
+                </div>
+                <input autocomplete="off" type="text" class="form-control datepicker" id="filter-column-name-start" placeholder="时间" name="name[start]" value="">
+                <span class="input-group-addon" style="border-left: 0; border-right: 0;">To</span>
+                <input autocomplete="off" type="text" class="form-control datepicker" id="filter-column-name-end" placeholder="时间" name="name[end]" value="">
+            </div>
+        </div>
+        <div class="form-group">
+            <div class="input-group input-group-sm">
+                <div class="input-group-prepend">
+                    <span class="input-group-text bg-white text-capitalize"><b>用户名</b></span>
+                </div>
+                <input type="text" class="form-control filter-column-name" placeholder="用户名" name="name" value="">
+            </div>
+        </div>
+        <button class="btn btn-primary btn-sm btn-mini search" type="button" style="margin-left: 12px">
+            <i class="feather icon-search"></i><span class="d-none d-sm-inline">&nbsp;&nbsp;搜索</span>
+        </button>
+    </form>
+    <ul class="nav nav-tabs" role="tablist">
+        @foreach ($categories as $key => $category)
+            <li role="presentation" class="nav-item">
+                <a href="#cate{{ $category->id }}" class="nav-link  {{$key == 0 ? 'active' :''}}"  data-toggle="tab">{{ $category->name }}</a>
+            </li>
+        @endforeach
+    </ul>
+    <div class="tab-content">
+        @foreach ($categories as $key => $category)
+        <div role="tabpanel" class="tab-pane {{$key == 0 ? 'active' :''}}" id="cate{{$category->id}}">
+            @foreach ($category->products as $product)
+            <button type="button" class="btn btn-default btn-product" data-id="{{$product->id}}" style="margin: 5px 0;">{{$product->name}}</button>
+            @endforeach
+        </div>
+        @endforeach
+    </div>
+
+    <div class="col-md-12" style="margin: 0;padding: 0;">
+        <div id="analyze" style="height: 660px;"></div>
+    </div>
+</div>

+ 85 - 0
server/resources/views/admin/dashboard/product.blade.php

xqd
@@ -0,0 +1,85 @@
+<style type="text/css">
+    .container-fluid{
+        background: #fff;
+        padding-bottom: 50px;
+    }
+    .form-inline{
+        padding-top: 15px;
+    }
+</style>
+
+<style type="text/css">
+    .container-fluid{
+        background: #fff;
+        padding-bottom: 50px;
+    }
+    .form-inline{
+        padding-top: 15px;
+    }
+    .tab-content.showroom{
+        margin-top: 20px;
+        padding: 10px 0;
+        border-top: 1px solid #f4f4f4;
+    }
+</style>
+<div class=" card" style=";padding:.25rem .4rem .4rem">
+    <ul class="nav nav-tabs " role="tablist">
+        <li class="nav-item">
+            <a href="#product_pane" class="nav-link active">产品查看数据</a>
+        </li>
+        <li class="nav-item">
+            <a href="/admin/dashboard/view?product=0" class="nav-link" data-toggle="tab">展厅查看数据</a>
+        </li>
+        <li class="nav-item pull-right header"></li>
+    </ul>
+
+    <div class="tab-content" style="">
+        <div class="tab-pane active" id="product_pane">
+            <div class="container-fluid">
+                <form class="form-inline product-form" action="#">
+                    <div class="form-group col-sm-3">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text bg-white text-capitalize"><b>时间</b>&nbsp;<i class="feather icon-calendar"></i></span>
+                            </div>
+                            <input autocomplete="off" type="text" class="form-control datepicker" id="filter-column-name-start" placeholder="时间" name="name[start]" value="">
+                            <span class="input-group-addon" style="border-left: 0; border-right: 0;">To</span>
+                            <input autocomplete="off" type="text" class="form-control datepicker" id="filter-column-name-end" placeholder="时间" name="name[end]" value="">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text bg-white text-capitalize"><b>用户名</b></span>
+                            </div>
+                            <input type="text" class="form-control filter-column-name" placeholder="用户名" name="name" value="">
+                        </div>
+                    </div>
+                    <button class="btn btn-primary btn-sm btn-mini product-search" type="button" style="margin-left: 12px">
+                        <i class="feather icon-search"></i><span class="d-none d-sm-inline">&nbsp;&nbsp;搜索</span>
+                    </button>
+                </form>
+                <ul class="nav nav-tabs" role="tablist">
+                    @foreach ($categories as $key => $category)
+                        <li role="presentation" class="nav-item">
+                            <a href="#cate{{ $category->id }}" class="nav-link  {{$key == 0 ? 'active' :''}}"  data-toggle="tab">{{ $category->name }}</a>
+                        </li>
+                    @endforeach
+                </ul>
+                <div class="tab-content">
+                    @foreach ($categories as $key => $category)
+                        <div role="tabpanel" class="tab-pane {{$key == 0 ? 'active' :''}}" id="cate{{$category->id}}">
+                            @foreach ($category->products as $product)
+                                <button type="button" class="btn btn-default btn-product" data-id="{{$product->id}}" style="margin: 5px 0;">{{$product->name}}</button>
+                            @endforeach
+                        </div>
+                    @endforeach
+                </div>
+
+                <div class="col-md-12" style="margin: 0;padding: 0;">
+                    <div id="analyze" style="height: 660px;"></div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 70 - 0
server/resources/views/admin/dashboard/showroom.blade.php

xqd
@@ -0,0 +1,70 @@
+<style type="text/css">
+    .container-fluid{
+        background: #fff;
+        padding-bottom: 50px;
+    }
+    .form-inline{
+        padding-top: 15px;
+    }
+    .tab-content.showroom{
+        margin-top: 20px;
+        padding: 10px 0;
+        border-top: 1px solid #f4f4f4;
+    }
+</style>
+<div class=" card" style=";padding:.25rem .4rem .4rem">
+    <ul class="nav nav-tabs " role="tablist">
+        <li class="nav-item">
+            <a href="/admin/dashboard/view?product=1" class="nav-link">产品查看数据</a>
+        </li>
+        <li class="nav-item">
+            <a href="#showroom_pane" class="nav-link active" data-toggle="tab">展厅查看数据</a>
+        </li>
+        <li class="nav-item pull-right header"></li>
+    </ul>
+
+    <div class="tab-content" style="">
+        <div class="tab-pane active" id="showroom_pane">
+            <div class="container-fluid">
+                <form class="form-inline showroom-form" action="#">
+                    <div class="form-group col-sm-3">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text bg-white text-capitalize"><b>时间</b>&nbsp;<i class="feather icon-calendar"></i></span>
+                            </div>
+                            <input autocomplete="off" type="text" class="form-control datepicker" id="filter-column-name-start" placeholder="时间" name="name[start]" value="">
+                            <span class="input-group-addon" style="border-left: 0; border-right: 0;">To</span>
+                            <input autocomplete="off" type="text" class="form-control datepicker" id="filter-column-name-end" placeholder="时间" name="name[end]" value="">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <div class="input-group input-group-sm">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text bg-white text-capitalize"><b>用户名</b></span>
+                            </div>
+                            <input type="text" class="form-control filter-column-name" placeholder="用户名" name="name" value="">
+                        </div>
+                    </div>
+                    <button class="btn btn-primary btn-sm btn-mini showroom-search" type="button" style="margin-left: 12px">
+                        <i class="feather icon-search"></i><span class="d-none d-sm-inline">&nbsp;&nbsp;搜索</span>
+                    </button>
+                </form>
+                <div class="tab-content showroom">
+                    <div role="tabpanel" class="tab-pane active">
+                        @foreach ($showrooms as $showroom)
+                            <button type="button" class="btn btn-default btn-showroom" data-id="{{$showroom->id}}" style="margin: 5px 0;">{{$showroom->name}}</button>
+                        @endforeach
+                    </div>
+                </div>
+
+
+                <div class="col-md-12" style="margin: 0;padding: 0;">
+                    <div id="showroom_analyze" style="height: 660px;"></div>
+                </div>
+            </div>
+
+        </div>
+
+    </div>
+</div>
+

+ 0 - 3
server/resources/views/admin/welcome.blade.php

xqd
@@ -1,3 +0,0 @@
-<span>数据下载</span>
-<script>
-</script>

Some files were not shown because too many files changed in this diff