|
|
@@ -1,147 +1,628 @@
|
|
|
<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 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">
|
|
|
-const goTarget = (url: string) => {
|
|
|
- window.open(url, '__blank');
|
|
|
-};
|
|
|
-</script>
|
|
|
+import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue';
|
|
|
+import { Box, Goods, PriceTag, Tickets } from '@element-plus/icons-vue';
|
|
|
+import * as echarts from 'echarts';
|
|
|
|
|
|
-<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;
|
|
|
- }
|
|
|
+type ProjectKey = 'zhongzhi' | 'zhongche';
|
|
|
|
|
|
- ul {
|
|
|
- padding: 0;
|
|
|
- margin: 0;
|
|
|
+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 }
|
|
|
+ ]
|
|
|
}
|
|
|
+});
|
|
|
|
|
|
- font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
|
- font-size: 13px;
|
|
|
- color: #676a6c;
|
|
|
- overflow-x: hidden;
|
|
|
+const currentStats = computed(() => dashboardData[activeProject.value]);
|
|
|
|
|
|
- ul {
|
|
|
- list-style-type: none;
|
|
|
- }
|
|
|
+const orderTrendRef = ref<HTMLDivElement>();
|
|
|
+const afterSalePieRef = ref<HTMLDivElement>();
|
|
|
+
|
|
|
+let orderTrendChart: echarts.ECharts | undefined;
|
|
|
+let afterSalePieChart: echarts.ECharts | undefined;
|
|
|
|
|
|
- h4 {
|
|
|
- margin-top: 0px;
|
|
|
+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
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- h2 {
|
|
|
- margin-top: 10px;
|
|
|
- font-size: 26px;
|
|
|
- font-weight: 100;
|
|
|
+ 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();
|
|
|
+};
|
|
|
|
|
|
- p {
|
|
|
- margin-top: 10px;
|
|
|
+watch(activeProject, () => {
|
|
|
+ renderCharts();
|
|
|
+});
|
|
|
|
|
|
- b {
|
|
|
+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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .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;
|
|
|
+ .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);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- .index-style {
|
|
|
- font-size: 48px;
|
|
|
- font-weight: bold;
|
|
|
- letter-spacing: 15px;
|
|
|
- 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 {
|
|
|
- display: flex;
|
|
|
- }
|
|
|
+.kpi-row, .board-row {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
|
|
|
- .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;
|
|
|
- }
|
|
|
- }
|
|
|
+.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);
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- @keyframes typewriter-animation {
|
|
|
- 0% {
|
|
|
- opacity: 0;
|
|
|
- transform: translateY(20px) rotate(-5deg);
|
|
|
- }
|
|
|
- 50% {
|
|
|
- opacity: 0.5;
|
|
|
- transform: translateY(10px) rotate(-2deg);
|
|
|
+/* 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;
|
|
|
}
|
|
|
- 100% {
|
|
|
- opacity: 1;
|
|
|
- transform: translateY(0) rotate(0deg);
|
|
|
+ .unit {
|
|
|
+ font-size: 14px;
|
|
|
+ margin-left: 4px;
|
|
|
+ opacity: 0.8;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- @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);
|
|
|
+ .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>
|