mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
feat(projects): 分析页更新,添加关于页面
This commit is contained in:
134
src/views/dashboard/analysis/components/BottomPart/index.vue
Normal file
134
src/views/dashboard/analysis/components/BottomPart/index.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<n-grid :x-gap="16" :y-gap="16" :item-responsive="true" responsive="screen">
|
||||
<n-grid-item span="s:24 m:8">
|
||||
<shadow-card class="h-400px p-18px">
|
||||
<n-timeline>
|
||||
<n-timeline-item v-for="item in timelines" :key="item.type" v-bind="item" />
|
||||
</n-timeline>
|
||||
</shadow-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="s:24 m:16">
|
||||
<shadow-card class="h-400px p-18px">
|
||||
<n-data-table size="small" :columns="columns" :data="tableData" />
|
||||
</shadow-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue';
|
||||
import { NGrid, NGridItem, NTimeline, NTimelineItem, NDataTable, NTag } from 'naive-ui';
|
||||
import { ShadowCard } from '@/components';
|
||||
|
||||
interface TimelineData {
|
||||
type: 'default' | 'info' | 'success' | 'warning' | 'error';
|
||||
title: string;
|
||||
content: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
interface TableData {
|
||||
key: number;
|
||||
name: string;
|
||||
age: number;
|
||||
address: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const timelines: TimelineData[] = [
|
||||
{ type: 'default', title: '啊', content: '', time: '2021-10-10 20:46' },
|
||||
{ type: 'success', title: '成功', content: '哪里成功', time: '2021-10-10 20:46' },
|
||||
{ type: 'error', title: '错误', content: '哪里错误', time: '2021-10-10 20:46' },
|
||||
{ type: 'warning', title: '警告', content: '哪里警告', time: '2021-10-10 20:46' },
|
||||
{ type: 'info', title: '信息', content: '是的', time: '2021-10-10 20:46' }
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: 'Age',
|
||||
key: 'age'
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
key: 'address'
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
key: 'tags',
|
||||
render(row: TableData) {
|
||||
const tags = row.tags.map(tagKey => {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px'
|
||||
},
|
||||
type: 'info'
|
||||
},
|
||||
{
|
||||
default: () => tagKey
|
||||
}
|
||||
);
|
||||
});
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const tableData: TableData[] = [
|
||||
{
|
||||
key: 0,
|
||||
name: 'John Brown',
|
||||
age: 32,
|
||||
address: 'New York No. 1 Lake Park',
|
||||
tags: ['nice', 'developer']
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
name: 'Jim Green',
|
||||
age: 42,
|
||||
address: 'London No. 1 Lake Park',
|
||||
tags: ['wow']
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: 'Joe Black',
|
||||
age: 32,
|
||||
address: 'Sidney No. 1 Lake Park',
|
||||
tags: ['cool', 'teacher']
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: 'Soybean',
|
||||
age: 25,
|
||||
address: 'China Shenzhen',
|
||||
tags: ['handsome', 'peogrammer']
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
name: 'John Brown',
|
||||
age: 32,
|
||||
address: 'New York No. 1 Lake Park',
|
||||
tags: ['nice', 'developer']
|
||||
},
|
||||
{
|
||||
key: 5,
|
||||
name: 'Jim Green',
|
||||
age: 42,
|
||||
address: 'London No. 1 Lake Park',
|
||||
tags: ['wow']
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
name: 'Joe Black',
|
||||
age: 32,
|
||||
address: 'Sidney No. 1 Lake Park',
|
||||
tags: ['cool', 'teacher']
|
||||
}
|
||||
];
|
||||
</script>
|
||||
<style scoped></style>
|
@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
|
||||
<n-grid-item v-for="item in cardData" :key="item.title">
|
||||
<n-card :title="item.title" :segmented="{ content: true, footer: true }" size="small" :bordered="false">
|
||||
<template #header-extra>
|
||||
<n-tag :type="item.color">{{ item.action }}</n-tag>
|
||||
</template>
|
||||
<div class="flex justify-between py-4px px-4px">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<count-to v-else prefix="$" :start-value="1" :end-value="item.value" class="text-30px text-[#666]" />
|
||||
</div>
|
||||
<div class="flex justify-between p-8px px-16px">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<span>总{{ item.title }}</span>
|
||||
<count-to prefix="$" :start-value="1" :end-value="item.total" />
|
||||
</template>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NGrid, NGridItem, NCard, NTag, NSkeleton } from 'naive-ui';
|
||||
import { CountTo } from '@/components';
|
||||
|
||||
interface CardData {
|
||||
title: string;
|
||||
value: number;
|
||||
total: number;
|
||||
color: 'default' | 'primary' | 'warning' | 'success' | 'info' | 'error';
|
||||
action: string;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const cardData: CardData[] = [
|
||||
{
|
||||
title: '访问数',
|
||||
value: 2000,
|
||||
total: 120000,
|
||||
color: 'error',
|
||||
action: '月'
|
||||
},
|
||||
{
|
||||
title: '成交额',
|
||||
value: 20000,
|
||||
total: 500000,
|
||||
color: 'success',
|
||||
action: '月'
|
||||
},
|
||||
{
|
||||
title: '下载数',
|
||||
value: 8000,
|
||||
total: 120000,
|
||||
color: 'primary',
|
||||
action: '周'
|
||||
},
|
||||
{
|
||||
title: '成交数',
|
||||
value: 5000,
|
||||
total: 50000,
|
||||
color: 'warning',
|
||||
action: '年'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
<style scoped></style>
|
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="bg-gradient wh-full p-16px text-white">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
/** 渐变开始的颜色 */
|
||||
startColor: string;
|
||||
/** 渐变结束的颜色 */
|
||||
endColor: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
startColor: '#56cdf3',
|
||||
endColor: '#719de3'
|
||||
});
|
||||
|
||||
const gradientStyle = computed(() => `linear-gradient(to bottom right, ${props.startColor}, ${props.endColor})`);
|
||||
</script>
|
||||
<style scoped>
|
||||
.bg-gradient {
|
||||
background-image: v-bind(gradientStyle);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,3 @@
|
||||
import GradientBg from './GradientBg.vue';
|
||||
|
||||
export { GradientBg };
|
75
src/views/dashboard/analysis/components/DataCard/index.vue
Normal file
75
src/views/dashboard/analysis/components/DataCard/index.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||
<n-grid-item v-for="item in cardData" :key="item.id">
|
||||
<shadow-card class="h-100px">
|
||||
<gradient-bg :start-color="item.colors[0]" :end-color="item.colors[1]">
|
||||
<h3 class="text-16px">{{ item.title }}</h3>
|
||||
<div class="flex justify-between pt-12px">
|
||||
<component :is="item.icon" class="text-32px" />
|
||||
<count-to
|
||||
:prefix="item.unit"
|
||||
:start-value="1"
|
||||
:end-value="item.value"
|
||||
class="text-30px text-white dark:text-dark"
|
||||
/>
|
||||
</div>
|
||||
</gradient-bg>
|
||||
</shadow-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { VNodeChild } from 'vue';
|
||||
import { NGrid, NGridItem } from 'naive-ui';
|
||||
import { BarChartOutlined, MoneyCollectOutlined, TrademarkOutlined } from '@vicons/antd';
|
||||
import { DocumentDownload } from '@vicons/carbon';
|
||||
import { ShadowCard, CountTo } from '@/components';
|
||||
import { dynamicIconRender } from '@/utils';
|
||||
import { GradientBg } from './components';
|
||||
|
||||
interface CardData {
|
||||
id: string;
|
||||
title: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
colors: [string, string];
|
||||
icon: () => VNodeChild;
|
||||
}
|
||||
|
||||
const cardData: CardData[] = [
|
||||
{
|
||||
id: 'visit',
|
||||
title: '访问量',
|
||||
value: 1000000,
|
||||
unit: '',
|
||||
colors: ['#ec4786', '#b955a4'],
|
||||
icon: dynamicIconRender(BarChartOutlined)
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
title: '成交额',
|
||||
value: 234567.89,
|
||||
unit: '$',
|
||||
colors: ['#865ec0', '#5144b4'],
|
||||
icon: dynamicIconRender(MoneyCollectOutlined)
|
||||
},
|
||||
{
|
||||
id: 'download',
|
||||
title: '下载数',
|
||||
value: 666666,
|
||||
unit: '',
|
||||
colors: ['#56cdf3', '#719de3'],
|
||||
icon: dynamicIconRender(DocumentDownload)
|
||||
},
|
||||
{
|
||||
id: 'trade',
|
||||
title: '成交数',
|
||||
value: 999999,
|
||||
unit: '',
|
||||
colors: ['#fcbc25', '#f68057'],
|
||||
icon: dynamicIconRender(TrademarkOutlined)
|
||||
}
|
||||
];
|
||||
</script>
|
||||
<style scoped></style>
|
@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="py-16px">
|
||||
<n-grid cols="1 s:2 m:3 l:8 xl:8 2xl:8" responsive="screen" :x-gap="16" :y-gap="8">
|
||||
<n-grid-item v-for="item in navData" :key="item.label">
|
||||
<n-card content-style="padding-top: 0;" size="small" :bordered="false">
|
||||
<template #footer>
|
||||
<n-skeleton v-if="loading" size="medium" />
|
||||
<div v-else class="cursor-pointer">
|
||||
<p class="flex-x-center">
|
||||
<span>
|
||||
<n-icon :size="32" class="flex-1" :color="item.color">
|
||||
<Icon :icon="item.icon" />
|
||||
</n-icon>
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex-x-center">
|
||||
<span>{{ item.label }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NGrid, NGridItem, NCard, NSkeleton, NIcon } from 'naive-ui';
|
||||
import { Icon } from '@iconify/vue';
|
||||
|
||||
interface NavData {
|
||||
label: string;
|
||||
color: string;
|
||||
icon: String;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const navData: NavData[] = [
|
||||
{
|
||||
label: '用户',
|
||||
color: '#69c0ff',
|
||||
icon: 'ant-design:usergroup-add-outlined'
|
||||
},
|
||||
{
|
||||
label: '分析',
|
||||
color: '#69c0ff',
|
||||
icon: 'ant-design:bar-chart-outlined'
|
||||
},
|
||||
{
|
||||
label: '商品',
|
||||
color: '#ff9c6e',
|
||||
icon: 'ant-design:shopping-cart-outlined'
|
||||
},
|
||||
{
|
||||
label: '订单',
|
||||
color: '#b37feb',
|
||||
icon: 'ant-design:account-book-outlined'
|
||||
},
|
||||
{
|
||||
label: '票据',
|
||||
color: '#ffd666',
|
||||
icon: 'ant-design:credit-card-outlined'
|
||||
},
|
||||
{
|
||||
label: '消息',
|
||||
color: '#5cdbd3',
|
||||
icon: 'ant-design:mail-outlined'
|
||||
},
|
||||
{
|
||||
label: '标签',
|
||||
color: '#ff85c0',
|
||||
icon: 'ant-design:tags-outlined'
|
||||
},
|
||||
{
|
||||
label: '配置',
|
||||
color: '#ffc069',
|
||||
icon: 'ant-design:setting-outlined'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
<style scoped></style>
|
152
src/views/dashboard/analysis/components/TopChart/data.json
Normal file
152
src/views/dashboard/analysis/components/TopChart/data.json
Normal file
@ -0,0 +1,152 @@
|
||||
[
|
||||
{
|
||||
"date": "2021/10/1",
|
||||
"type": "下载量",
|
||||
"value": 4623
|
||||
},
|
||||
{
|
||||
"date": "2021/10/1",
|
||||
"type": "注册数",
|
||||
"value": 2208
|
||||
},
|
||||
{
|
||||
"date": "2021/10/2",
|
||||
"type": "下载量",
|
||||
"value": 6145
|
||||
},
|
||||
{
|
||||
"date": "2021/10/2",
|
||||
"type": "注册数",
|
||||
"value": 2016
|
||||
},
|
||||
{
|
||||
"date": "2021/10/3",
|
||||
"type": "下载量",
|
||||
"value": 508
|
||||
},
|
||||
{
|
||||
"date": "2021/10/3",
|
||||
"type": "注册数",
|
||||
"value": 2916
|
||||
},
|
||||
{
|
||||
"date": "2021/10/4",
|
||||
"type": "下载量",
|
||||
"value": 6268
|
||||
},
|
||||
{
|
||||
"date": "2021/10/4",
|
||||
"type": "注册数",
|
||||
"value": 4512
|
||||
},
|
||||
{
|
||||
"date": "2021/10/5",
|
||||
"type": "下载量",
|
||||
"value": 6411
|
||||
},
|
||||
{
|
||||
"date": "2021/10/5",
|
||||
"type": "注册数",
|
||||
"value": 8281
|
||||
},
|
||||
{
|
||||
"date": "2021/10/6",
|
||||
"type": "下载量",
|
||||
"value": 1890
|
||||
},
|
||||
{
|
||||
"date": "2021/10/6",
|
||||
"type": "注册数",
|
||||
"value": 2008
|
||||
},
|
||||
{
|
||||
"date": "2021/10/7",
|
||||
"type": "下载量",
|
||||
"value": 4251
|
||||
},
|
||||
{
|
||||
"date": "2021/10/7",
|
||||
"type": "注册数",
|
||||
"value": 1963
|
||||
},
|
||||
{
|
||||
"date": "2021/10/8",
|
||||
"type": "下载量",
|
||||
"value": 2978
|
||||
},
|
||||
{
|
||||
"date": "2021/10/8",
|
||||
"type": "注册数",
|
||||
"value": 2367
|
||||
},
|
||||
{
|
||||
"date": "2021/10/9",
|
||||
"type": "下载量",
|
||||
"value": 3880
|
||||
},
|
||||
{
|
||||
"date": "2021/10/9",
|
||||
"type": "注册数",
|
||||
"value": 2956
|
||||
},
|
||||
{
|
||||
"date": "2021/10/10",
|
||||
"type": "下载量",
|
||||
"value": 3606
|
||||
},
|
||||
{
|
||||
"date": "2021/10/10",
|
||||
"type": "注册数",
|
||||
"value": 678
|
||||
},
|
||||
{
|
||||
"date": "2021/10/11",
|
||||
"type": "下载量",
|
||||
"value": 4311
|
||||
},
|
||||
{
|
||||
"date": "2021/10/11",
|
||||
"type": "注册数",
|
||||
"value": 3188
|
||||
},
|
||||
{
|
||||
"date": "2021/10/12",
|
||||
"type": "下载量",
|
||||
"value": 4116
|
||||
},
|
||||
{
|
||||
"date": "2021/10/12",
|
||||
"type": "注册数",
|
||||
"value": 3491
|
||||
},
|
||||
{
|
||||
"date": "2021/10/13",
|
||||
"type": "下载量",
|
||||
"value": 6419
|
||||
},
|
||||
{
|
||||
"date": "2021/10/13",
|
||||
"type": "注册数",
|
||||
"value": 2852
|
||||
},
|
||||
{
|
||||
"date": "2021/10/14",
|
||||
"type": "下载量",
|
||||
"value": 1643
|
||||
},
|
||||
{
|
||||
"date": "2021/10/14",
|
||||
"type": "注册数",
|
||||
"value": 4788
|
||||
},
|
||||
{
|
||||
"date": "2021/10/15",
|
||||
"type": "下载量",
|
||||
"value": 445
|
||||
},
|
||||
{
|
||||
"date": "2021/10/15",
|
||||
"type": "注册数",
|
||||
"value": 4319
|
||||
}
|
||||
]
|
127
src/views/dashboard/analysis/components/TopChart/index.vue
Normal file
127
src/views/dashboard/analysis/components/TopChart/index.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<n-grid :x-gap="16" :y-gap="16" :item-responsive="true" responsive="screen">
|
||||
<n-grid-item span="s:24 m:16">
|
||||
<shadow-card class="flex h-360px p-18px">
|
||||
<div class="w-200px h-full py-12px">
|
||||
<h3 class="text-16px font-bold">Dashboard</h3>
|
||||
<p class="text-[#aaa]">Overview Of Lasted Month</p>
|
||||
<h3 class="pt-36px text-24px font-bold">
|
||||
<count-to prefix="$" :start-value="0" :end-value="7754" />
|
||||
</h3>
|
||||
<p class="text-[#aaa]">Current Month Earnings</p>
|
||||
<h3 class="pt-36px text-24px font-bold">
|
||||
<count-to :start-value="0" :end-value="1234" />
|
||||
</h3>
|
||||
<p class="text-[#aaa]">Current Month Sales</p>
|
||||
<n-button class="mt-24px" type="primary">Last Month Summary</n-button>
|
||||
</div>
|
||||
<div ref="lineRef" class="flex-1 h-full"></div>
|
||||
</shadow-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="s:24 m:8">
|
||||
<shadow-card class="h-360px">
|
||||
<div ref="pieRef" class="wh-full"></div>
|
||||
</shadow-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { NGrid, NGridItem, NButton } from 'naive-ui';
|
||||
import { Line, Pie } from '@antv/g2plot';
|
||||
import { ShadowCard, CountTo } from '@/components';
|
||||
import data from './data.json';
|
||||
|
||||
const lineRef = ref<HTMLElement | null>(null);
|
||||
const line = ref<Line | null>(null);
|
||||
const pieRef = ref<HTMLElement | null>(null);
|
||||
const pie = ref<Pie | null>(null);
|
||||
|
||||
function renderLineChart() {
|
||||
line.value = new Line(lineRef.value!, {
|
||||
data,
|
||||
autoFit: true,
|
||||
xField: 'date',
|
||||
yField: 'value',
|
||||
seriesField: 'type',
|
||||
lineStyle: {
|
||||
lineWidth: 4
|
||||
},
|
||||
area: {
|
||||
style: {
|
||||
fill: 'l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff'
|
||||
}
|
||||
},
|
||||
smooth: true,
|
||||
animation: {
|
||||
appear: {
|
||||
animation: 'wave-in',
|
||||
duration: 2000
|
||||
}
|
||||
}
|
||||
});
|
||||
line.value.render();
|
||||
}
|
||||
function renderPieChart() {
|
||||
const data = [
|
||||
{ type: '学习', value: 20 },
|
||||
{ type: '娱乐', value: 10 },
|
||||
{ type: '工作', value: 30 },
|
||||
{ type: '休息', value: 40 }
|
||||
];
|
||||
pie.value = new Pie(pieRef.value!, {
|
||||
appendPadding: 10,
|
||||
data,
|
||||
angleField: 'value',
|
||||
colorField: 'type',
|
||||
radius: 0.8,
|
||||
innerRadius: 0.65,
|
||||
meta: {
|
||||
value: {
|
||||
formatter: v => `${v}%`
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: 'spider',
|
||||
offset: '8%',
|
||||
autoRotate: false,
|
||||
formatter: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
|
||||
style: { fontSize: 18 }
|
||||
},
|
||||
statistic: undefined,
|
||||
pieStyle: {
|
||||
radius: [20]
|
||||
},
|
||||
color: ['#025DF4', '#DB6BCF', '#2498D1', '#FF745A', '#007E99', '#FFA8A8', '#2391FF'],
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
},
|
||||
interactions: [
|
||||
{ type: 'element-selected' },
|
||||
{ type: 'element-active' },
|
||||
{
|
||||
type: 'pie-statistic-active',
|
||||
cfg: {
|
||||
start: [
|
||||
{ trigger: 'element:mouseenter', action: 'pie-statistic:change' },
|
||||
{ trigger: 'legend-item:mouseenter', action: 'pie-statistic:change' }
|
||||
],
|
||||
end: [
|
||||
{ trigger: 'element:mouseleave', action: 'pie-statistic:reset' },
|
||||
{ trigger: 'legend-item:mouseleave', action: 'pie-statistic:reset' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
pie.value.render();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderLineChart();
|
||||
renderPieChart();
|
||||
});
|
||||
</script>
|
||||
<style scoped></style>
|
@ -1,4 +1,5 @@
|
||||
import DataCard from './DataCard.vue';
|
||||
import NavCard from './NavCard.vue';
|
||||
import TopChart from './TopChart/index.vue';
|
||||
import DataCard from './DataCard/index.vue';
|
||||
import BottomPart from './BottomPart/index.vue';
|
||||
|
||||
export { DataCard, NavCard };
|
||||
export { TopChart, DataCard, BottomPart };
|
||||
|
Reference in New Issue
Block a user