|
|
@@ -1,26 +1,23 @@
|
|
|
<!-- 数据统计页面 -->
|
|
|
<template>
|
|
|
<div class="page-container">
|
|
|
- <!-- 统计卡片 -->
|
|
|
- <el-row :gutter="16" class="stats-row">
|
|
|
- <el-col :xs="12" :sm="8" :md="4" v-for="stat in statsData" :key="stat.key">
|
|
|
- <div class="stats-card" :style="{ borderLeftColor: stat.color }">
|
|
|
- <div class="stats-info">
|
|
|
- <div class="stats-num" :style="{ color: stat.color }">{{ stat.value }}</div>
|
|
|
- <div class="stats-label">{{ stat.label }}</div>
|
|
|
- </div>
|
|
|
- <div class="stats-icon" :style="{ backgroundColor: stat.bgColor }">
|
|
|
- <el-icon :size="24" :style="{ color: stat.color }">
|
|
|
- <component :is="stat.icon" />
|
|
|
- </el-icon>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 筛选条件 -->
|
|
|
- <div class="filter-bar">
|
|
|
- <el-form :inline="true" :model="filterForm">
|
|
|
+ <header class="page-head">
|
|
|
+ <h1>数据统计</h1>
|
|
|
+ <p class="page-desc">统一查看订单趋势、区域分布、商品排行与运营明细。</p>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <section class="panel">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>
|
|
|
+ <span class="title-ic" aria-hidden="true">
|
|
|
+ <el-icon><Operation /></el-icon>
|
|
|
+ </span>
|
|
|
+ 统计筛选
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+ <div class="filter-block">
|
|
|
+ <el-form :model="filterForm" class="filter-form" @submit.prevent>
|
|
|
+ <div class="filter-grid">
|
|
|
<el-form-item label="时间范围">
|
|
|
<el-date-picker
|
|
|
v-model="filterForm.dateRange"
|
|
|
@@ -29,7 +26,6 @@
|
|
|
start-placeholder="开始日期"
|
|
|
end-placeholder="结束日期"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
- @change="handleFilterChange"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="区域">
|
|
|
@@ -37,74 +33,92 @@
|
|
|
v-model="filterForm.regionId"
|
|
|
placeholder="请选择区域"
|
|
|
clearable
|
|
|
- @change="handleFilterChange"
|
|
|
@visible-change="(open) => open && loadRegions()"
|
|
|
>
|
|
|
<el-option v-for="r in regionList" :key="r.id" :label="r.name" :value="r.id" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
+ </div>
|
|
|
+ <div class="filter-actions-row">
|
|
|
+ <span class="filter-hint">按时间和区域筛选统计口径。</span>
|
|
|
+ <div class="filter-actions-end">
|
|
|
+ <el-button class="btn-ghost" @click="handleReset">重置</el-button>
|
|
|
+ <el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</el-form>
|
|
|
</div>
|
|
|
+ </section>
|
|
|
|
|
|
- <!-- 图表区域 -->
|
|
|
- <el-row :gutter="16" class="chart-row">
|
|
|
- <el-col :span="24">
|
|
|
- <div class="card">
|
|
|
- <div class="card-header">
|
|
|
- <span class="card-title">订单趋势</span>
|
|
|
+ <section class="panel panel-block">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>运营概览</h2>
|
|
|
+ </div>
|
|
|
+ <div class="stats-grid">
|
|
|
+ <div class="stat-item" v-for="stat in statsData" :key="stat.key">
|
|
|
+ <div class="stat-main">
|
|
|
+ <div class="stat-num" :style="{ color: stat.color }">{{ stat.value }}</div>
|
|
|
+ <div class="stat-label">{{ stat.label }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon" :style="{ backgroundColor: stat.bgColor }">
|
|
|
+ <el-icon :size="22" :style="{ color: stat.color }">
|
|
|
+ <component :is="stat.icon" />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <section class="panel panel-block">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>订单趋势</h2>
|
|
|
+ <div class="tools">
|
|
|
<el-radio-group v-model="orderTrendType" size="small" @change="handleTrendTypeChange">
|
|
|
<el-radio-button label="day">按日</el-radio-button>
|
|
|
<el-radio-button label="week">按周</el-radio-button>
|
|
|
<el-radio-button label="month">按月</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
- </div>
|
|
|
- <div class="chart-container" ref="orderTrendChart">
|
|
|
- <div class="chart-placeholder">
|
|
|
- <el-icon :size="48" color="#c0c4cc"><DataLine /></el-icon>
|
|
|
- <p>订单趋势图表</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <el-row :gutter="16" class="chart-row">
|
|
|
- <el-col :xs="24" :sm="12">
|
|
|
- <div class="card">
|
|
|
- <div class="card-header">
|
|
|
- <span class="card-title">区域订单分布</span>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container" ref="orderTrendChart">
|
|
|
+ <div class="chart-placeholder">
|
|
|
+ <el-icon :size="48" color="#c0c4cc"><DataLine /></el-icon>
|
|
|
+ <p>订单趋势图表</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <div class="chart-split-grid">
|
|
|
+ <section class="panel panel-block">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>区域订单分布</h2>
|
|
|
+ </div>
|
|
|
<div class="chart-container" ref="regionChart">
|
|
|
<div class="chart-placeholder">
|
|
|
<el-icon :size="48" color="#c0c4cc"><PieChart /></el-icon>
|
|
|
<p>区域订单分布图表</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </section>
|
|
|
+ <section class="panel panel-block">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>订单状态分布</h2>
|
|
|
</div>
|
|
|
- </el-col>
|
|
|
- <el-col :xs="24" :sm="12">
|
|
|
- <div class="card">
|
|
|
- <div class="card-header">
|
|
|
- <span class="card-title">订单状态分布</span>
|
|
|
- </div>
|
|
|
<div class="chart-container" ref="statusChart">
|
|
|
<div class="chart-placeholder">
|
|
|
<el-icon :size="48" color="#c0c4cc"><Histogram /></el-icon>
|
|
|
<p>订单状态分布图表</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <el-row :gutter="16" class="chart-row">
|
|
|
- <el-col :span="24">
|
|
|
- <div class="card">
|
|
|
- <div class="card-header">
|
|
|
- <span class="card-title">商品销量排行 TOP10</span>
|
|
|
- </div>
|
|
|
- <div class="rank-list">
|
|
|
- <el-table :data="productRank" stripe size="small">
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <section class="panel panel-block">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>商品销量排行 TOP10</h2>
|
|
|
+ </div>
|
|
|
+ <div class="list-body list-wrap">
|
|
|
+ <el-table :data="productRank" stripe size="small" class="list-table">
|
|
|
<el-table-column prop="rank" label="排名" min-width="80" align="center">
|
|
|
<template #default="{ $index }">
|
|
|
<span class="rank-badge" :class="{ 'rank-top3': $index < 3 }">{{ $index + 1 }}</span>
|
|
|
@@ -125,22 +139,23 @@
|
|
|
<el-progress :percentage="row.percentage" :color="getProgressColor(row.percentage)" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- </el-table>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
|
|
|
<!-- 运营数据表格 -->
|
|
|
- <div class="data-table card">
|
|
|
- <div class="card-header">
|
|
|
- <span class="card-title">运营数据明细</span>
|
|
|
- <el-button type="primary" size="small" @click="handleExport">
|
|
|
+ <section class="panel panel-block">
|
|
|
+ <div class="panel-top">
|
|
|
+ <h2>运营数据明细</h2>
|
|
|
+ <div class="tools">
|
|
|
+ <el-button class="btn-main" type="primary" @click="handleExport">
|
|
|
<el-icon><Download /></el-icon>
|
|
|
导出数据
|
|
|
</el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <el-table :data="operationData" stripe>
|
|
|
+ <div class="list-body list-wrap">
|
|
|
+ <el-table :data="operationData" stripe class="list-table">
|
|
|
<el-table-column prop="date" label="日期" min-width="120" />
|
|
|
<el-table-column prop="orderCount" label="订单数" min-width="100" align="center" />
|
|
|
<el-table-column prop="orderAmount" label="订单金额(元)" min-width="120" align="right">
|
|
|
@@ -160,14 +175,15 @@
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
- import { UserFilled, Shop, ShoppingCart, Van, Money, DataLine, PieChart, Histogram, Download } from '@element-plus/icons-vue'
|
|
|
+ import { UserFilled, Shop, ShoppingCart, Van, Money, DataLine, PieChart, Histogram, Download, Operation } from '@element-plus/icons-vue'
|
|
|
import { fetchActiveRegions } from '@/api/admin'
|
|
|
|
|
|
defineOptions({ name: 'Statistics' })
|
|
|
@@ -213,6 +229,16 @@
|
|
|
loadStatistics()
|
|
|
}
|
|
|
|
|
|
+ const handleSearch = () => {
|
|
|
+ loadStatistics()
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleReset = () => {
|
|
|
+ filterForm.dateRange = []
|
|
|
+ filterForm.regionId = ''
|
|
|
+ loadStatistics()
|
|
|
+ }
|
|
|
+
|
|
|
const handleTrendTypeChange = () => {
|
|
|
loadStatistics()
|
|
|
}
|
|
|
@@ -241,83 +267,59 @@
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-.page-container {
|
|
|
- padding: 20px;
|
|
|
- background: #f5f7fa;
|
|
|
+.panel-block {
|
|
|
+ margin-top: 16px;
|
|
|
}
|
|
|
|
|
|
-.stats-row {
|
|
|
- margin-bottom: 16px;
|
|
|
+.stats-grid {
|
|
|
+ padding: 14px 16px 16px;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
|
+ gap: 12px;
|
|
|
}
|
|
|
|
|
|
-.stats-card {
|
|
|
+.stat-item {
|
|
|
+ border: 1px solid var(--mint-border, #d9e8e2);
|
|
|
+ border-radius: 10px;
|
|
|
background: #fff;
|
|
|
- border-radius: 12px;
|
|
|
- padding: 20px;
|
|
|
+ padding: 14px;
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
justify-content: space-between;
|
|
|
- margin-bottom: 16px;
|
|
|
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
|
|
- border-left: 4px solid;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
-.stats-info {
|
|
|
+.stat-main {
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
-.stats-num {
|
|
|
- font-size: 24px;
|
|
|
+.stat-num {
|
|
|
+ font-size: 22px;
|
|
|
font-weight: 700;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
-.stats-label {
|
|
|
+.stat-label {
|
|
|
font-size: 13px;
|
|
|
color: #909399;
|
|
|
}
|
|
|
|
|
|
-.stats-icon {
|
|
|
- width: 48px;
|
|
|
- height: 48px;
|
|
|
- border-radius: 12px;
|
|
|
+.stat-icon {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 10px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
-.filter-bar {
|
|
|
- background: #fff;
|
|
|
- padding: 16px 20px;
|
|
|
- border-radius: 8px;
|
|
|
- margin-bottom: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.chart-row {
|
|
|
- margin-bottom: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 12px;
|
|
|
- padding: 20px;
|
|
|
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
|
|
-}
|
|
|
-
|
|
|
-.card-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-.card-title {
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
- color: #303133;
|
|
|
+.chart-split-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 16px;
|
|
|
}
|
|
|
|
|
|
.chart-container {
|
|
|
+ padding: 0 16px 16px;
|
|
|
min-height: 300px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -334,10 +336,6 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.rank-list {
|
|
|
- padding: 0 8px;
|
|
|
-}
|
|
|
-
|
|
|
.rank-badge {
|
|
|
display: inline-block;
|
|
|
width: 24px;
|
|
|
@@ -365,7 +363,21 @@
|
|
|
color: #909399;
|
|
|
}
|
|
|
|
|
|
-.data-table {
|
|
|
- margin-top: 16px;
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .stats-grid {
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 900px) {
|
|
|
+ .chart-split-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 760px) {
|
|
|
+ .stats-grid {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|