| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- <template>
- <div class="app-container home p-6">
- <div class="dashboard-header">
- <div class="dashboard-title">
- <span class="title-text">概览数据看板</span>
- <span class="title-desc">Overview Dashboard</span>
- </div>
- <el-select v-model="activeProject" placeholder="请选择项目" class="project-select" filterable>
- <el-option v-for="opt in projectOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
- </el-select>
- </div>
- <!-- Stats Cards -->
- <el-row :gutter="20" class="kpi-row">
- <el-col :xs="12" :sm="12" :md="6">
- <el-card shadow="hover" class="kpi-card custom-card primary-card">
- <div class="kpi-inner">
- <div class="kpi-content">
- <div class="kpi-label">订单总量</div>
- <div class="kpi-value">
- <span class="num">{{ currentStats.orderCount }}</span>
- <span class="unit">单</span>
- </div>
- </div>
- <div class="kpi-icon-wrapper">
- <el-icon class="kpi-icon"><Tickets /></el-icon>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card shadow="hover" class="kpi-card custom-card success-card">
- <div class="kpi-inner">
- <div class="kpi-content">
- <div class="kpi-label">商品数量</div>
- <div class="kpi-value">
- <span class="num">{{ currentStats.productCount }}</span>
- <span class="unit">件</span>
- </div>
- </div>
- <div class="kpi-icon-wrapper">
- <el-icon class="kpi-icon"><Goods /></el-icon>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card shadow="hover" class="kpi-card custom-card warning-card">
- <div class="kpi-inner">
- <div class="kpi-content">
- <div class="kpi-label">热销商品</div>
- <div class="kpi-value">
- <span class="num">{{ currentStats.hotProductCount }}</span>
- <span class="unit">款</span>
- </div>
- </div>
- <div class="kpi-icon-wrapper">
- <el-icon class="kpi-icon"><PriceTag /></el-icon>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :xs="12" :sm="12" :md="6">
- <el-card shadow="hover" class="kpi-card custom-card danger-card">
- <div class="kpi-inner">
- <div class="kpi-content">
- <div class="kpi-label">售后订单</div>
- <div class="kpi-value">
- <span class="num">{{ currentStats.afterSaleCount }}</span>
- <span class="unit">单</span>
- </div>
- </div>
- <div class="kpi-icon-wrapper">
- <el-icon class="kpi-icon"><Box /></el-icon>
- </div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- Charts Row -->
- <el-row :gutter="20" class="board-row">
- <el-col :xs="24" :md="14">
- <el-card shadow="hover" class="board-card custom-card border-card">
- <template #header>
- <div class="board-card-title">
- <span class="dot primary-dot"></span>
- <span>订单趋势线</span>
- </div>
- </template>
- <div ref="orderTrendRef" class="chart chart-large" />
- </el-card>
- </el-col>
- <el-col :xs="24" :md="10">
- <el-card shadow="hover" class="board-card custom-card border-card">
- <template #header>
- <div class="board-card-title">
- <span class="dot warning-dot"></span>
- <span>售后状态占比</span>
- </div>
- </template>
- <div ref="afterSalePieRef" class="chart chart-large" />
- </el-card>
- </el-col>
- </el-row>
- <!-- Tables Row -->
- <el-row :gutter="20" class="board-row">
- <el-col :xs="24" :md="14">
- <el-card shadow="hover" class="board-card custom-card border-card">
- <template #header>
- <div class="board-card-title">
- <span class="dot success-dot"></span>
- <span>热销商品排行榜 TOP 5</span>
- </div>
- </template>
- <el-table
- :data="currentStats.hotProducts"
- style="width: 100%"
- :header-cell-style="{ background: '#f8f9fa', color: '#333', fontWeight: 'bold' }"
- stripe
- >
- <el-table-column label="排名" type="index" width="80" align="center">
- <template #default="scope">
- <span :class="['rank-badge', `rank-${scope.$index + 1}`]">
- {{ scope.$index + 1 }}
- </span>
- </template>
- </el-table-column>
- <el-table-column label="商品名称" prop="name" show-overflow-tooltip>
- <template #default="scope">
- <span class="product-name">{{ scope.row.name }}</span>
- </template>
- </el-table-column>
- <el-table-column label="近期销量" prop="sales" width="120" align="center">
- <template #default="scope">
- <el-tag type="danger" effect="light" size="small">{{ scope.row.sales }} 件</el-tag>
- </template>
- </el-table-column>
- </el-table>
- </el-card>
- </el-col>
-
- <el-col :xs="24" :md="10">
- <el-card shadow="hover" class="board-card custom-card border-card">
- <template #header>
- <div class="board-card-title">
- <span class="dot danger-dot"></span>
- <span>近期售后动态</span>
- </div>
- <div class="mini-kpi-wrap">
- <div class="mini-kpi">
- <div class="m-label">待处理</div>
- <div class="m-value text-danger">{{ currentStats.afterSalePending }}</div>
- </div>
- <div class="mini-v-divider"></div>
- <div class="mini-kpi">
- <div class="m-label">已完成</div>
- <div class="m-value text-success">{{ currentStats.afterSaleDone }}</div>
- </div>
- </div>
- </template>
-
- <el-table
- :data="currentStats.recentAfterSales"
- style="width: 100%"
- :header-cell-style="{ background: '#f8f9fa' }"
- >
- <el-table-column label="售后单号" prop="no" show-overflow-tooltip min-width="140" />
- <el-table-column label="状态" prop="status" width="90" align="center">
- <template #default="scope">
- <el-tag :type="scope.row.status === '已完成' ? 'success' : 'warning'" size="small">
- {{ scope.row.status }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="申请时间" prop="time" width="110" />
- </el-table>
- </el-card>
- </el-col>
- </el-row>
- </div>
- </template>
- <script setup name="Index" lang="ts">
- import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue';
- import { Box, Goods, PriceTag, Tickets } from '@element-plus/icons-vue';
- import * as echarts from 'echarts';
- type ProjectKey = 'zhongzhi' | 'zhongche';
- const activeProject = ref<ProjectKey>('zhongzhi');
- const projectOptions: Array<{ label: string; value: ProjectKey }> = [
- { label: '中直', value: 'zhongzhi' },
- { label: '中车', value: 'zhongche' }
- ];
- const dashboardData = reactive<Record<ProjectKey, any>>({
- zhongzhi: {
- orderCount: 1280,
- productCount: 3560,
- hotProductCount: 56,
- afterSaleCount: 38,
- afterSalePending: 9,
- afterSaleDone: 29,
- hotProducts: [
- { name: '中直-热销商品型号 2024款', sales: 320 },
- { name: '高配自选升级包 豪华版', sales: 265 },
- { name: '专业定制服务 年卡版', sales: 210 },
- { name: '中直-专属增值服务包', sales: 154 },
- { name: '标准配件套装 (多色)', sales: 112 }
- ],
- recentAfterSales: [
- { no: 'AS20250303001', status: '待处理', time: '03-03 10:20' },
- { no: 'AS20250303002', status: '已完成', time: '03-03 09:12' },
- { no: 'AS20250303003', status: '待处理', time: '03-02 14:35' },
- { no: 'AS20250302004', status: '已完成', time: '03-02 11:21' }
- ],
- orderTrend: {
- xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
- orderSeries: [120, 160, 140, 220, 260, 200, 180],
- amountSeries: [18, 26, 22, 35, 42, 31, 28]
- },
- afterSalePie: [
- { name: '待处理', value: 9 },
- { name: '处理中', value: 6 },
- { name: '已完成', value: 23 }
- ]
- },
- zhongche: {
- orderCount: 980,
- productCount: 2890,
- hotProductCount: 44,
- afterSaleCount: 26,
- afterSalePending: 5,
- afterSaleDone: 21,
- hotProducts: [
- { name: '中车-特供机型 v2', sales: 280 },
- { name: '工业配件标准件包', sales: 230 },
- { name: '中车-定制保养套装', sales: 190 },
- { name: '机电耗材包 (月卡)', sales: 140 },
- { name: '基础版维修备件', sales: 90 }
- ],
- recentAfterSales: [
- { no: 'AS20250303011', status: '已完成', time: '03-03 14:10' },
- { no: 'AS20250303012', status: '待处理', time: '03-02 09:30' },
- { no: 'AS20250303013', status: '已完成', time: '03-01 16:45' },
- { no: 'AS20250302014', status: '待处理', time: '03-01 11:05' }
- ],
- orderTrend: {
- xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
- orderSeries: [80, 110, 95, 140, 170, 150, 130],
- amountSeries: [12, 18, 15, 22, 28, 24, 20]
- },
- afterSalePie: [
- { name: '待处理', value: 5 },
- { name: '处理中', value: 4 },
- { name: '已完成', value: 17 }
- ]
- }
- });
- const currentStats = computed(() => dashboardData[activeProject.value]);
- const orderTrendRef = ref<HTMLDivElement>();
- const afterSalePieRef = ref<HTMLDivElement>();
- let orderTrendChart: echarts.ECharts | undefined;
- let afterSalePieChart: echarts.ECharts | undefined;
- const renderCharts = () => {
- const stats = currentStats.value;
- if (orderTrendRef.value) {
- if (!orderTrendChart) {
- orderTrendChart = echarts.init(orderTrendRef.value);
- }
- orderTrendChart.setOption({
- backgroundColor: 'transparent',
- tooltip: {
- trigger: 'axis',
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
- borderColor: '#eee',
- padding: 10,
- textStyle: { color: '#333' }
- },
- legend: {
- data: ['订单量', '销售额(万)'],
- top: 0
- },
- grid: { left: 40, right: 20, top: 40, bottom: 30, containLabel: true },
- xAxis: {
- type: 'category',
- data: stats.orderTrend.xAxis,
- axisLine: { lineStyle: { color: '#ddd' } },
- axisLabel: { color: '#666' }
- },
- yAxis: [
- {
- type: 'value',
- name: '订单量',
- nameTextStyle: { color: '#999' },
- splitLine: { lineStyle: { type: 'dashed', color: '#eee' } },
- axisLabel: { color: '#666' }
- },
- {
- type: 'value',
- name: '销售额',
- nameTextStyle: { color: '#999' },
- splitLine: { show: false },
- axisLabel: { color: '#666' }
- }
- ],
- series: [
- {
- name: '订单量',
- type: 'line',
- smooth: true,
- symbolSize: 8,
- itemStyle: { color: '#409EFF' },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: 'rgba(64,158,255,0.3)' },
- { offset: 1, color: 'rgba(64,158,255,0)' }
- ])
- },
- data: stats.orderTrend.orderSeries
- },
- {
- name: '销售额(万)',
- type: 'bar',
- yAxisIndex: 1,
- barWidth: 12,
- itemStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: '#36cfc9' },
- { offset: 1, color: '#009688' }
- ]),
- borderRadius: [4, 4, 0, 0]
- },
- data: stats.orderTrend.amountSeries
- }
- ]
- });
- }
- if (afterSalePieRef.value) {
- if (!afterSalePieChart) {
- afterSalePieChart = echarts.init(afterSalePieRef.value);
- }
- afterSalePieChart.setOption({
- backgroundColor: 'transparent',
- tooltip: { trigger: 'item' },
- legend: { bottom: 0, left: 'center', itemWidth: 10, itemHeight: 10, icon: 'circle' },
- color: ['#F56C6C', '#E6A23C', '#67C23A'],
- series: [
- {
- name: '售后状态',
- type: 'pie',
- radius: ['45%', '70%'],
- center: ['50%', '42%'],
- avoidLabelOverlap: false,
- itemStyle: {
- borderRadius: 6,
- borderColor: '#fff',
- borderWidth: 2
- },
- label: {
- show: true,
- formatter: '{b}\n{d}%',
- color: '#666'
- },
- labelLine: { show: true, length: 15, length2: 10 },
- data: stats.afterSalePie
- }
- ]
- });
- }
- };
- const resizeCharts = () => {
- orderTrendChart?.resize();
- afterSalePieChart?.resize();
- };
- watch(activeProject, () => {
- renderCharts();
- });
- onMounted(() => {
- renderCharts();
- window.addEventListener('resize', resizeCharts);
- });
- </script>
- <style lang="scss" scoped>
- .app-container {
- background-color: #f2f5f9;
- min-height: calc(100vh - 84px);
- padding: 20px;
- }
- .dashboard-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 24px;
-
- .dashboard-title {
- display: flex;
- align-items: baseline;
- gap: 12px;
-
- .title-text {
- font-size: 22px;
- font-weight: 700;
- color: #2c3e50;
- letter-spacing: 1px;
- }
-
- .title-desc {
- font-size: 14px;
- color: #909399;
- font-weight: 400;
- letter-spacing: 0.5px;
- }
- }
- .project-select {
- width: 180px;
- :deep(.el-input__wrapper) {
- border-radius: 20px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
- background-color: #fff;
- padding: 2px 15px;
- transition: all 0.3s;
-
- &:hover, &.is-focus {
- box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
- }
- }
- }
- }
- .kpi-row, .board-row {
- margin-bottom: 20px;
- }
- .custom-card {
- border: none;
- border-radius: 12px;
- transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
- background: #fff;
-
- &:hover {
- transform: translateY(-4px);
- box-shadow: 0 10px 20px rgba(0,0,0,0.08);
- }
- }
- /* KPI Cards */
- .kpi-card {
- overflow: hidden;
- position: relative;
-
- .kpi-inner {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24px 20px;
- position: relative;
- z-index: 1;
- }
-
- .kpi-label {
- font-size: 15px;
- color: #fff;
- opacity: 0.9;
- margin-bottom: 8px;
- font-weight: 500;
- }
-
- .kpi-value {
- color: #fff;
- .num {
- font-size: 32px;
- font-weight: 700;
- line-height: 1;
- font-family: 'Rubik', Arial, sans-serif;
- }
- .unit {
- font-size: 14px;
- margin-left: 4px;
- opacity: 0.8;
- }
- }
- .kpi-icon-wrapper {
- width: 60px;
- height: 60px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(255, 255, 255, 0.2);
- backdrop-filter: blur(4px);
-
- .kpi-icon {
- font-size: 30px;
- color: #fff;
- }
- }
- &::after {
- content: '';
- position: absolute;
- right: -20px;
- top: -20px;
- width: 120px;
- height: 120px;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.1);
- z-index: 0;
- }
- }
- .primary-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
- .success-card { background: linear-gradient(135deg, #20E2D7 0%, #F9FEA5 100%); .kpi-label, .kpi-value {color: #333} .kpi-icon {color: #333} .kpi-icon-wrapper {background: rgba(0,0,0,0.05)}}
- .warning-card { background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); }
- .danger-card { background: linear-gradient(135deg, #ff0844 0%, #ffb199 100%); }
- /* Board Cards */
- .border-card {
- box-shadow: 0 4px 12px rgba(0,0,0,0.03);
-
- :deep(.el-card__header) {
- border-bottom: 1px solid #f0f2f5;
- padding: 16px 20px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- :deep(.el-card__body) {
- padding: 20px;
- }
- }
- .board-card-title {
- display: flex;
- align-items: center;
- font-size: 16px;
- font-weight: 600;
- color: #303133;
-
- .dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- margin-right: 10px;
-
- &.primary-dot { background-color: #409EFF; }
- &.warning-dot { background-color: #E6A23C; }
- &.success-dot { background-color: #67C23A; }
- &.danger-dot { background-color: #F56C6C; }
- }
- }
- .chart-large {
- height: 340px;
- width: 100%;
- }
- /* Tables styling */
- .rank-badge {
- display: inline-block;
- width: 24px;
- height: 24px;
- line-height: 24px;
- text-align: center;
- border-radius: 50%;
- font-weight: bold;
- font-size: 12px;
- background-color: #f4f4f5;
- color: #909399;
-
- &.rank-1 { background-color: #f56c6c; color: white; }
- &.rank-2 { background-color: #ff9800; color: white; }
- &.rank-3 { background-color: #e6a23c; color: white; }
- }
- .product-name {
- font-weight: 500;
- color: #303133;
- }
- .mini-kpi-wrap {
- display: flex;
- align-items: center;
- gap: 16px;
- }
- .mini-v-divider {
- width: 1px;
- height: 24px;
- background-color: #ebeef5;
- }
- .mini-kpi {
- display: flex;
- align-items: center;
- gap: 8px;
-
- .m-label {
- font-size: 13px;
- color: #909399;
- }
-
- .m-value {
- font-size: 18px;
- font-weight: 700;
- font-family: 'Rubik', Arial, sans-serif;
- }
-
- .text-danger { color: #f56c6c; }
- .text-success { color: #67c23a; }
- }
- </style>
|