refactor(projects): perf page home

This commit is contained in:
Soybean
2024-01-26 01:51:06 +08:00
parent 123fd4f96c
commit 4c61c6ff3c
7 changed files with 288 additions and 222 deletions

View File

@@ -0,0 +1,109 @@
<script setup lang="ts">
import { computed } from 'vue';
import { createReusableTemplate } from '@vueuse/core';
import { $t } from '@/locales';
defineOptions({
name: 'CardData'
});
interface CardData {
key: string;
title: string;
value: number;
unit: string;
color: {
start: string;
end: string;
};
icon: string;
}
const cardData = computed<CardData[]>(() => [
{
key: 'visitCount',
title: $t('page.home.visitCount'),
value: 9725,
unit: '',
color: {
start: '#ec4786',
end: '#b955a4'
},
icon: 'ant-design:bar-chart-outlined'
},
{
key: 'turnover',
title: $t('page.home.turnover'),
value: 1026,
unit: '$',
color: {
start: '#865ec0',
end: '#5144b4'
},
icon: 'ant-design:money-collect-outlined'
},
{
key: 'downloadCount',
title: $t('page.home.downloadCount'),
value: 970925,
unit: '',
color: {
start: '#56cdf3',
end: '#719de3'
},
icon: 'carbon:document-download'
},
{
key: 'dealCount',
title: $t('page.home.dealCount'),
value: 9527,
unit: '',
color: {
start: '#fcbc25',
end: '#f68057'
},
icon: 'ant-design:trademark-circle-outlined'
}
]);
interface GradientBgProps {
gradientColor: string;
}
const [DefineGradientBg, GradientBg] = createReusableTemplate<GradientBgProps>();
function getGradientColor(color: CardData['color']) {
return `linear-gradient(to bottom right, ${color.start}, ${color.end})`;
}
</script>
<template>
<NCard :bordered="false" size="small" class="card-wrapper">
<!-- define component start: GradientBg -->
<DefineGradientBg v-slot="{ $slots, gradientColor }">
<div class="px-16px pt-8px pb-4px rd-8px text-white" :style="{ backgroundImage: gradientColor }">
<component :is="$slots.default" />
</div>
</DefineGradientBg>
<!-- define component end: GradientBg -->
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi v-for="item in cardData" :key="item.key">
<GradientBg :gradient-color="getGradientColor(item.color)" class="flex-1">
<h3 class="text-16px">{{ item.title }}</h3>
<div class="flex justify-between pt-12px">
<SvgIcon :icon="item.icon" class="text-32px" />
<CountTo
:prefix="item.unit"
:start-value="1"
:end-value="item.value"
class="text-30px text-white dark:text-dark"
/>
</div>
</GradientBg>
</NGi>
</NGrid>
</NCard>
</template>
<style scoped></style>

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
import { $t } from '@/locales';
defineOptions({
name: 'CreativityBanner'
});
</script>
<template>
<NCard :title="$t('page.home.creativity')" :bordered="false" size="small" class="card-wrapper h-full">
<div class="flex-center h-full">
<IconLocalBanner class="text-400px sm:text-320px text-primary" />
</div>
</NCard>
</template>
<style scoped></style>

View File

@@ -0,0 +1,66 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useAuthStore } from '@/store/modules/auth';
defineOptions({
name: 'HeaderBanner'
});
const appStore = useAppStore();
const authStore = useAuthStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
interface StatisticData {
id: number;
label: string;
value: string;
}
const statisticData = computed<StatisticData[]>(() => [
{
id: 0,
label: $t('page.home.projectCount'),
value: '25'
},
{
id: 1,
label: $t('page.home.todo'),
value: '4/16'
},
{
id: 2,
label: $t('page.home.message'),
value: '12'
}
]);
</script>
<template>
<NCard :bordered="false" class="card-wrapper">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:18">
<div class="flex-y-center">
<div class="shrink-0 size-72px rd-1/2 overflow-hidden">
<img src="@/assets/imgs/soybean.jpg" class="size-full" />
</div>
<div class="pl-12px">
<h3 class="text-18px font-semibold">
{{ $t('page.home.greeting', { userName: authStore.userInfo.userName }) }}
</h3>
<p class="leading-30px text-#999">{{ $t('page.home.weatherDesc') }}</p>
</div>
</div>
</NGi>
<NGi span="24 s:24 m:6">
<NSpace :size="24" justify="end">
<NStatistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item" />
</NSpace>
</NGi>
</NGrid>
</NCard>
</template>
<style scoped></style>

View File

@@ -0,0 +1,151 @@
<script setup lang="ts">
import { watch } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useEcharts } from '@/hooks/chart/use-echarts';
defineOptions({
name: 'LineChart'
});
const appStore = useAppStore();
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: [$t('page.home.downloadCount'), $t('page.home.registerCount')]
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [] as string[]
},
yAxis: {
type: 'value'
},
series: [
{
color: '#8e9dff',
name: $t('page.home.downloadCount'),
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#8e9dff'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [] as number[]
},
{
color: '#26deca',
name: $t('page.home.registerCount'),
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#26deca'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: []
}
]
}));
async function mockData() {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
updateOptions(opts => {
opts.xAxis.data = ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00'];
opts.series[0].data = [4623, 6145, 6268, 6411, 1890, 4251, 2978, 3880, 3606, 4311];
opts.series[1].data = [2208, 2016, 2916, 4512, 8281, 2008, 1963, 2367, 2956, 678];
return opts;
});
}
function updateLocale() {
updateOptions((opts, factory) => {
const originOpts = factory();
opts.legend.data = originOpts.legend.data;
opts.series[0].name = originOpts.series[0].name;
opts.series[1].name = originOpts.series[1].name;
return opts;
});
}
async function init() {
mockData();
}
watch(
() => appStore.locale,
() => {
updateLocale();
}
);
// init
init();
</script>
<template>
<NCard :bordered="false" class="card-wrapper">
<div ref="domRef" class="h-360px overflow-hidden"></div>
</NCard>
</template>
<style scoped></style>

View File

@@ -0,0 +1,109 @@
<script setup lang="ts">
import { watch } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useEcharts } from '@/hooks/chart/use-echarts';
defineOptions({
name: 'PieChart'
});
const appStore = useAppStore();
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: 'item'
},
legend: {
bottom: '1%',
left: 'center',
itemStyle: {
borderWidth: 0
}
},
series: [
{
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
name: $t('page.home.schedule'),
type: 'pie',
radius: ['45%', '75%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 1
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '12'
}
},
labelLine: {
show: false
},
data: [] as { name: string; value: number }[]
}
]
}));
async function mockData() {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
updateOptions(opts => {
opts.series[0].data = [
{ name: $t('page.home.study'), value: 20 },
{ name: $t('page.home.entertainment'), value: 10 },
{ name: $t('page.home.work'), value: 40 },
{ name: $t('page.home.rest'), value: 30 }
];
return opts;
});
}
function updateLocale() {
updateOptions((opts, factory) => {
const originOpts = factory();
opts.series[0].name = originOpts.series[0].name;
opts.series[0].data = [
{ name: $t('page.home.study'), value: 20 },
{ name: $t('page.home.entertainment'), value: 10 },
{ name: $t('page.home.work'), value: 40 },
{ name: $t('page.home.rest'), value: 30 }
];
return opts;
});
}
async function init() {
mockData();
}
watch(
() => appStore.locale,
() => {
updateLocale();
}
);
// init
init();
</script>
<template>
<NCard :bordered="false" class="card-wrapper">
<div ref="domRef" class="h-360px overflow-hidden"></div>
</NCard>
</template>
<style scoped></style>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@/locales';
defineOptions({
name: 'ProjectNews'
});
interface NewsItem {
id: number;
content: string;
time: string;
}
const newses = computed<NewsItem[]>(() => [
{ id: 1, content: $t('page.home.projectNews.desc1'), time: '2021-05-28 22:22:22' },
{ id: 2, content: $t('page.home.projectNews.desc2'), time: '2021-10-27 10:24:54' },
{ id: 3, content: $t('page.home.projectNews.desc3'), time: '2021-10-31 22:43:12' },
{ id: 4, content: $t('page.home.projectNews.desc4'), time: '2021-11-03 20:33:31' },
{ id: 5, content: $t('page.home.projectNews.desc5'), time: '2021-11-07 22:45:32' }
]);
</script>
<template>
<NCard :title="$t('page.home.projectNews.title')" :bordered="false" size="small" segmented class="card-wrapper">
<template #header-extra>
<a class="text-primary" href="javascript:;">{{ $t('page.home.projectNews.moreNews') }}</a>
</template>
<NList>
<NListItem v-for="item in newses" :key="item.id">
<template #prefix>
<SoybeanAvatar class="size-48px!" />
</template>
<NThing :title="item.content" :description="item.time" />
</NListItem>
</NList>
</NCard>
</template>
<style scoped></style>