|
|
@@ -1,147 +1,414 @@
|
|
|
<template>
|
|
|
<div class="app-container home">
|
|
|
- <!-- <el-divider /> -->
|
|
|
- <div class="index-style">
|
|
|
- <div class="typewriter-container">
|
|
|
- <span v-for="(char, index) in 'welcome!'" :key="index" :style="{ animationDelay: `${index * 0.5}s` }" class="typewriter-char">{{
|
|
|
- char
|
|
|
- }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <!-- 顶部数据概览卡片 -->
|
|
|
+ <el-row :gutter="20" class="panel-group">
|
|
|
+ <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
|
|
+ <el-card shadow="hover" class="box-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="card-title">今日订单(笔)</span>
|
|
|
+ <el-tag type="primary">日</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="card-value">1,245</div>
|
|
|
+ <div class="card-footer">
|
|
|
+ <span class="trend"
|
|
|
+ >较昨日
|
|
|
+ <span class="up"
|
|
|
+ ><el-icon><CaretTop /></el-icon>12.5%</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
|
|
+ <el-card shadow="hover" class="box-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="card-title">待发货订单(笔)</span>
|
|
|
+ <el-tag type="warning">待办</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="card-value">342</div>
|
|
|
+ <div class="card-footer">
|
|
|
+ <span class="trend"
|
|
|
+ >较昨日
|
|
|
+ <span class="down"
|
|
|
+ ><el-icon><CaretBottom /></el-icon>5.2%</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
|
|
+ <el-card shadow="hover" class="box-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="card-title">本月成交额(元)</span>
|
|
|
+ <el-tag type="success">月</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="card-value">¥89,450.00</div>
|
|
|
+ <div class="card-footer">
|
|
|
+ <span class="trend"
|
|
|
+ >同比上月
|
|
|
+ <span class="up"
|
|
|
+ ><el-icon><CaretTop /></el-icon>8.1%</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
|
|
|
+ <el-card shadow="hover" class="box-card">
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="card-title">售后退款订单(笔)</span>
|
|
|
+ <el-tag type="danger">售后</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="card-value">28</div>
|
|
|
+ <div class="card-footer">
|
|
|
+ <span class="trend"
|
|
|
+ >较昨日
|
|
|
+ <span class="up"
|
|
|
+ ><el-icon><CaretTop /></el-icon>2.0%</span
|
|
|
+ ></span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 中部图表区域 -->
|
|
|
+ <el-row :gutter="20" style="margin-top: 20px">
|
|
|
+ <el-col :span="16">
|
|
|
+ <el-card shadow="hover" class="chart-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header-flex">
|
|
|
+ <span class="title">近七日订单趋势</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 柱状图 -->
|
|
|
+ <div ref="chartRef" style="height: 350px"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover" class="chart-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header-flex">
|
|
|
+ <span class="title">平台订单占比</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 饼图 -->
|
|
|
+ <div ref="pieRef" style="height: 350px"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 底部最新订单列表 -->
|
|
|
+ <el-row :gutter="20" style="margin-top: 20px">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-card shadow="hover" class="table-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header-flex">
|
|
|
+ <span class="title">最新订单动态</span>
|
|
|
+ <el-button link type="primary">查看全部</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-table :data="recentOrders" style="width: 100%" max-height="400">
|
|
|
+ <el-table-column prop="orderNo" label="订单编号" width="180" />
|
|
|
+ <el-table-column prop="customerName" label="客户名称" width="150" />
|
|
|
+ <el-table-column prop="platform" label="来源平台" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag size="small" :type="scope.row.platform === '供应商' ? 'danger' : 'success'">
|
|
|
+ {{ scope.row.platform }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="amount" label="订单金额(元)" width="150" />
|
|
|
+ <el-table-column prop="status" label="订单状态" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="getStatusType(scope.row.status)" effect="light">{{ scope.row.status }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createTime" label="创建时间" min-width="160" />
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
+ <template>
|
|
|
+ <el-button link type="primary" size="small">详情</el-button>
|
|
|
+ <!-- <el-button link type="primary" size="small">跟踪</el-button> -->
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="Index" lang="ts">
|
|
|
-const goTarget = (url: string) => {
|
|
|
- window.open(url, '__blank');
|
|
|
-};
|
|
|
-</script>
|
|
|
+import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+import { CaretTop, CaretBottom } from '@element-plus/icons-vue';
|
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
-.home {
|
|
|
- blockquote {
|
|
|
- padding: 10px 20px;
|
|
|
- margin: 0 0 20px;
|
|
|
- font-size: 17.5px;
|
|
|
- border-left: 5px solid #eee;
|
|
|
- }
|
|
|
- hr {
|
|
|
- margin-top: 20px;
|
|
|
- margin-bottom: 20px;
|
|
|
- border: 0;
|
|
|
- border-top: 1px solid #eee;
|
|
|
- }
|
|
|
- .col-item {
|
|
|
- margin-bottom: 20px;
|
|
|
+// 图表 refs
|
|
|
+const chartRef = ref<HTMLElement | null>(null);
|
|
|
+const pieRef = ref<HTMLElement | null>(null);
|
|
|
+let chartInstance: echarts.ECharts | null = null;
|
|
|
+let pieInstance: echarts.ECharts | null = null;
|
|
|
+
|
|
|
+// 模拟订单数据
|
|
|
+const recentOrders = ref([
|
|
|
+ { orderNo: 'ORD202310100001', customerName: '张先生', platform: '供应商', amount: '1,299.00', status: '待发货', createTime: '2023-10-10 10:23:05' },
|
|
|
+ {
|
|
|
+ orderNo: 'ORD202310100002',
|
|
|
+ customerName: '李四科技',
|
|
|
+ platform: '伙伴商',
|
|
|
+ amount: '8,950.00',
|
|
|
+ status: '已发货',
|
|
|
+ createTime: '2023-10-10 09:15:30'
|
|
|
+ },
|
|
|
+ { orderNo: 'ORD202310090015', customerName: '王五', platform: '大客户', amount: '399.00', status: '已完成', createTime: '2023-10-09 16:45:11' },
|
|
|
+ {
|
|
|
+ orderNo: 'ORD202310090008',
|
|
|
+ customerName: '赵六实业',
|
|
|
+ platform: '供应商',
|
|
|
+ amount: '12,500.00',
|
|
|
+ status: '已关闭',
|
|
|
+ createTime: '2023-10-09 11:20:00'
|
|
|
+ },
|
|
|
+ { orderNo: 'ORD202310080034', customerName: '孙七', platform: '普通客户', amount: '59.90', status: '部分发货', createTime: '2023-10-08 20:11:45' },
|
|
|
+ {
|
|
|
+ orderNo: 'ORD202310080002',
|
|
|
+ customerName: '华润商贸',
|
|
|
+ platform: '伙伴商',
|
|
|
+ amount: '45,000.00',
|
|
|
+ status: '已完成',
|
|
|
+ createTime: '2023-10-08 14:30:12'
|
|
|
}
|
|
|
+]);
|
|
|
|
|
|
- ul {
|
|
|
- padding: 0;
|
|
|
- margin: 0;
|
|
|
+// 订单状态颜色映射
|
|
|
+const getStatusType = (status: string) => {
|
|
|
+ switch (status) {
|
|
|
+ case '待发货':
|
|
|
+ return 'warning';
|
|
|
+ case '已发货':
|
|
|
+ return 'primary';
|
|
|
+ case '部分发货':
|
|
|
+ return 'primary';
|
|
|
+ case '已完成':
|
|
|
+ return 'success';
|
|
|
+ case '已关闭':
|
|
|
+ return 'info';
|
|
|
+ default:
|
|
|
+ return 'info';
|
|
|
}
|
|
|
+};
|
|
|
|
|
|
- font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
|
- font-size: 13px;
|
|
|
- color: #676a6c;
|
|
|
- overflow-x: hidden;
|
|
|
+// 初始化柱状图
|
|
|
+const initBarChart = () => {
|
|
|
+ if (!chartRef.value) return;
|
|
|
+ chartInstance = echarts.init(chartRef.value);
|
|
|
+ chartInstance.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: { type: 'shadow' }
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: [
|
|
|
+ {
|
|
|
+ type: 'category',
|
|
|
+ data: ['10-04', '10-05', '10-06', '10-07', '10-08', '10-09', '10-10'],
|
|
|
+ axisTick: { alignWithLabel: true },
|
|
|
+ axisLine: { lineStyle: { color: '#909399' } }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ splitLine: { lineStyle: { type: 'dashed' } }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '新增订单数',
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: '40%',
|
|
|
+ data: [120, 200, 150, 80, 70, 110, 130],
|
|
|
+ itemStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: '#83bff6' },
|
|
|
+ { offset: 0.5, color: '#188df0' },
|
|
|
+ { offset: 1, color: '#188df0' }
|
|
|
+ ]),
|
|
|
+ borderRadius: [4, 4, 0, 0]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
- ul {
|
|
|
- list-style-type: none;
|
|
|
- }
|
|
|
+// 初始化饼图
|
|
|
+const initPieChart = () => {
|
|
|
+ if (!pieRef.value) return;
|
|
|
+ pieInstance = echarts.init(pieRef.value);
|
|
|
+ pieInstance.setOption({
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: '{a} <br/>{b}: {c}笔 ({d}%)'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'horizontal',
|
|
|
+ bottom: 'bottom'
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '订单来源平台',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['45%', '70%'],
|
|
|
+ avoidLabelOverlap: false,
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: 8,
|
|
|
+ borderColor: '#fff',
|
|
|
+ borderWidth: 2
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ show: false,
|
|
|
+ position: 'center'
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ label: {
|
|
|
+ show: true,
|
|
|
+ fontSize: 20,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ formatter: '{b}\n{d}%'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ labelLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ data: [
|
|
|
+ { value: 1048, name: '供应商', itemStyle: { color: '#f56c6c' } },
|
|
|
+ { value: 735, name: '伙伴商', itemStyle: { color: '#e6a23c' } },
|
|
|
+ { value: 580, name: '大客户', itemStyle: { color: '#409eff' } },
|
|
|
+ { value: 484, name: '普通客户', itemStyle: { color: '#67c23a' } },
|
|
|
+ { value: 300, name: '其他渠道', itemStyle: { color: '#909399' } }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
- h4 {
|
|
|
- margin-top: 0px;
|
|
|
- }
|
|
|
+// 自适应图表大小
|
|
|
+const resizeCharts = () => {
|
|
|
+ chartInstance?.resize();
|
|
|
+ pieInstance?.resize();
|
|
|
+};
|
|
|
|
|
|
- h2 {
|
|
|
- margin-top: 10px;
|
|
|
- font-size: 26px;
|
|
|
- font-weight: 100;
|
|
|
- }
|
|
|
+onMounted(() => {
|
|
|
+ nextTick(() => {
|
|
|
+ initBarChart();
|
|
|
+ initPieChart();
|
|
|
+ window.addEventListener('resize', resizeCharts);
|
|
|
+ });
|
|
|
+});
|
|
|
|
|
|
- p {
|
|
|
- margin-top: 10px;
|
|
|
+onUnmounted(() => {
|
|
|
+ window.removeEventListener('resize', resizeCharts);
|
|
|
+ chartInstance?.dispose();
|
|
|
+ pieInstance?.dispose();
|
|
|
+});
|
|
|
+</script>
|
|
|
|
|
|
- b {
|
|
|
- font-weight: 700;
|
|
|
- }
|
|
|
- }
|
|
|
+<style lang="scss" scoped>
|
|
|
+.app-container {
|
|
|
+ padding: 20px;
|
|
|
+ background-color: #f5f7f9;
|
|
|
+ min-height: calc(100vh - 84px);
|
|
|
+}
|
|
|
+
|
|
|
+.card-panel-col {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
|
|
|
- .update-log {
|
|
|
- ol {
|
|
|
- display: block;
|
|
|
- list-style-type: decimal;
|
|
|
- margin-block-start: 1em;
|
|
|
- margin-block-end: 1em;
|
|
|
- margin-inline-start: 0;
|
|
|
- margin-inline-end: 0;
|
|
|
- padding-inline-start: 40px;
|
|
|
+.box-card {
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ .card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ .card-title {
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
}
|
|
|
- .index-style {
|
|
|
- font-size: 48px;
|
|
|
- font-weight: bold;
|
|
|
- letter-spacing: 15px;
|
|
|
+ .card-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
+ .card-footer {
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+ padding-top: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
display: flex;
|
|
|
- justify-content: center;
|
|
|
align-items: center;
|
|
|
- height: 300px;
|
|
|
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
|
- border-radius: 10px;
|
|
|
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
- .typewriter-container {
|
|
|
+ .trend {
|
|
|
display: flex;
|
|
|
- }
|
|
|
+ align-items: center;
|
|
|
+ gap: 5px;
|
|
|
|
|
|
- .typewriter-char {
|
|
|
- opacity: 0;
|
|
|
- transform: translateY(20px) rotate(-5deg);
|
|
|
- animation:
|
|
|
- typewriter-animation 0.8s ease forwards,
|
|
|
- pulse 2s ease-in-out infinite 1s;
|
|
|
- color: #2d8cf0;
|
|
|
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
|
|
- transition: color 0.3s ease;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- color: #f06292;
|
|
|
- transform: scale(1.1);
|
|
|
- animation-play-state: paused;
|
|
|
+ .up {
|
|
|
+ color: #f56c6c;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ .down {
|
|
|
+ color: #67c23a;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- @keyframes typewriter-animation {
|
|
|
- 0% {
|
|
|
- opacity: 0;
|
|
|
- transform: translateY(20px) rotate(-5deg);
|
|
|
- }
|
|
|
- 50% {
|
|
|
- opacity: 0.5;
|
|
|
- transform: translateY(10px) rotate(-2deg);
|
|
|
- }
|
|
|
- 100% {
|
|
|
- opacity: 1;
|
|
|
- transform: translateY(0) rotate(0deg);
|
|
|
+.chart-card {
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ .card-header-flex {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ .title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- @keyframes pulse {
|
|
|
- 0% {
|
|
|
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
|
|
- transform: scale(1);
|
|
|
- }
|
|
|
- 50% {
|
|
|
- text-shadow:
|
|
|
- 0 0 15px rgba(45, 140, 240, 0.8),
|
|
|
- 0 0 30px rgba(45, 140, 240, 0.4);
|
|
|
- transform: scale(1.05);
|
|
|
- }
|
|
|
- 100% {
|
|
|
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
|
|
- transform: scale(1);
|
|
|
+.table-card {
|
|
|
+ border-radius: 8px;
|
|
|
+ border: none;
|
|
|
+ .card-header-flex {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ .title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// 覆盖 Element Plus 默认的卡片头部 padding
|
|
|
+:deep(.el-card__header) {
|
|
|
+ padding: 15px 20px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+}
|
|
|
</style>
|