mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-19 01:56:38 +08:00
feat(projects): finish page home
This commit is contained in:
parent
62e4da053d
commit
7bd1e47af9
BIN
src/assets/imgs/soybean.jpg
Normal file
BIN
src/assets/imgs/soybean.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
7701
src/assets/svg-icon/soybean.svg
Normal file
7701
src/assets/svg-icon/soybean.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.4 MiB |
13
src/components/custom/soybean-avatar.vue
Normal file
13
src/components/custom/soybean-avatar.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SoybeanAvatar'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="size-72px rd-1/2 overflow-hidden">
|
||||
<img src="@/assets/imgs/soybean.jpg" class="size-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -186,6 +186,11 @@ const local: App.I18n.Schema = {
|
||||
devDep: 'Development Dependency'
|
||||
},
|
||||
home: {
|
||||
greeting: 'Good morning, {userName}, today is another day full of vitality!',
|
||||
weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!',
|
||||
projectCount: 'Project Count',
|
||||
todo: 'Todo',
|
||||
message: 'Message',
|
||||
downloadCount: 'Download Count',
|
||||
registerCount: 'Register Count',
|
||||
schedule: 'Work and rest Schedule',
|
||||
@ -193,9 +198,19 @@ const local: App.I18n.Schema = {
|
||||
work: 'Work',
|
||||
rest: 'Rest',
|
||||
entertainment: 'Entertainment',
|
||||
visit: 'Visit Count',
|
||||
amount: 'Amount',
|
||||
trade: 'Trade Count'
|
||||
visitCount: 'Visit Count',
|
||||
turnover: 'Turnover',
|
||||
dealCount: 'Deal Count',
|
||||
projectNews: {
|
||||
title: 'Project News',
|
||||
moreNews: 'More News',
|
||||
desc1: 'Soybean created the open source project soybean-admin on May 28, 2021!',
|
||||
desc2: 'Yanbowe submitted a bug to soybean-admin, the multi-tab bar will not adapt.',
|
||||
desc3: 'Soybean is ready to do sufficient preparation for the release of soybean-admin!',
|
||||
desc4: 'Soybean is busy writing project documentation for soybean-admin!',
|
||||
desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
|
||||
},
|
||||
creativity: 'Creativity'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
|
@ -186,6 +186,11 @@ const local: App.I18n.Schema = {
|
||||
devDep: '开发依赖'
|
||||
},
|
||||
home: {
|
||||
greeting: '早安,{userName}, 今天又是充满活力的一天!',
|
||||
weatherDesc: '今日多云转晴,20℃ - 25℃!',
|
||||
projectCount: '项目数',
|
||||
todo: '待办',
|
||||
message: '消息',
|
||||
downloadCount: '下载量',
|
||||
registerCount: '注册量',
|
||||
schedule: '作息安排',
|
||||
@ -193,9 +198,19 @@ const local: App.I18n.Schema = {
|
||||
work: '工作',
|
||||
rest: '休息',
|
||||
entertainment: '娱乐',
|
||||
visit: '访问量',
|
||||
amount: '成交额',
|
||||
trade: '成交量'
|
||||
visitCount: '访问量',
|
||||
turnover: '成交额',
|
||||
dealCount: '成交量',
|
||||
projectNews: {
|
||||
title: '项目动态',
|
||||
moreNews: '更多动态',
|
||||
desc1: 'Soybean 在2021年5月28日创建了开源项目 soybean-admin!',
|
||||
desc2: 'Yanbowe 向 soybean-admin 提交了一个bug,多标签栏不会自适应。',
|
||||
desc3: 'Soybean 准备为 soybean-admin 的发布做充分的准备工作!',
|
||||
desc4: 'Soybean 正在忙于为soybean-admin写项目说明文档!',
|
||||
desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!'
|
||||
},
|
||||
creativity: '创意'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
|
21
src/typings/app.d.ts
vendored
21
src/typings/app.d.ts
vendored
@ -370,6 +370,11 @@ declare namespace App {
|
||||
devDep: string;
|
||||
};
|
||||
home: {
|
||||
greeting: string;
|
||||
weatherDesc: string;
|
||||
projectCount: string;
|
||||
todo: string;
|
||||
message: string;
|
||||
downloadCount: string;
|
||||
registerCount: string;
|
||||
schedule: string;
|
||||
@ -377,9 +382,19 @@ declare namespace App {
|
||||
work: string;
|
||||
rest: string;
|
||||
entertainment: string;
|
||||
visit: string;
|
||||
amount: string;
|
||||
trade: string;
|
||||
visitCount: string;
|
||||
turnover: string;
|
||||
dealCount: string;
|
||||
projectNews: {
|
||||
title: string;
|
||||
moreNews: string;
|
||||
desc1: string;
|
||||
desc2: string;
|
||||
desc3: string;
|
||||
desc4: string;
|
||||
desc5: string;
|
||||
};
|
||||
creativity: string;
|
||||
};
|
||||
};
|
||||
form: {
|
||||
|
6
src/typings/components.d.ts
vendored
6
src/typings/components.d.ts
vendored
@ -17,6 +17,7 @@ declare module 'vue' {
|
||||
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
|
||||
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
|
||||
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
|
||||
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
||||
IconLocalLogo: typeof import('~icons/local/logo')['default']
|
||||
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
||||
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
||||
@ -41,21 +42,26 @@ declare module 'vue' {
|
||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NList: typeof import('naive-ui')['NList']
|
||||
NListItem: typeof import('naive-ui')['NListItem']
|
||||
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NTab: typeof import('naive-ui')['NTab']
|
||||
NTabs: typeof import('naive-ui')['NTabs']
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NThing: typeof import('naive-ui')['NThing']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
|
||||
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SoybeanAvatar: typeof import('./../components/custom/soybean-avatar.vue')['default']
|
||||
SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default']
|
||||
SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
|
||||
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
|
||||
|
109
src/views/home/components/card-data.vue
Normal file
109
src/views/home/components/card-data.vue
Normal 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" 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>
|
@ -1,27 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GradientBg'
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
startColor: '#56cdf3',
|
||||
endColor: '#719de3'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
startColor?: string;
|
||||
endColor?: string;
|
||||
}
|
||||
|
||||
const gradientStyle = computed(() => `linear-gradient(to bottom right, ${props.startColor}, ${props.endColor})`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-16px pt-8px pb-4px rd-8px text-white" :style="{ backgroundImage: gradientStyle }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
66
src/views/home/components/header-banner.vue
Normal file
66
src/views/home/components/header-banner.vue
Normal 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:20">
|
||||
<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:4">
|
||||
<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>
|
40
src/views/home/components/project-news.vue
Normal file
40
src/views/home/components/project-news.vue
Normal 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>
|
@ -3,7 +3,9 @@ import { computed, watch } from 'vue';
|
||||
import { $t } from '@/locales';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useEcharts } from '@/hooks/chart/use-echarts';
|
||||
import GradientBg from './components/gradient-bg.vue';
|
||||
import HeaderBanner from './components/header-banner.vue';
|
||||
import CardData from './components/card-data.vue';
|
||||
import ProjectNews from './components/project-news.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
@ -201,50 +203,6 @@ function updatePieLocale() {
|
||||
});
|
||||
}
|
||||
|
||||
interface CardData {
|
||||
key: string;
|
||||
title: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
colors: [string, string];
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const cardData = computed<CardData[]>(() => [
|
||||
{
|
||||
key: 'visit',
|
||||
title: $t('page.home.visit'),
|
||||
value: 1000000,
|
||||
unit: '',
|
||||
colors: ['#ec4786', '#b955a4'],
|
||||
icon: 'ant-design:bar-chart-outlined'
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
title: $t('page.home.amount'),
|
||||
value: 234567.89,
|
||||
unit: '$',
|
||||
colors: ['#865ec0', '#5144b4'],
|
||||
icon: 'ant-design:money-collect-outlined'
|
||||
},
|
||||
{
|
||||
key: 'download',
|
||||
title: $t('page.home.downloadCount'),
|
||||
value: 666666,
|
||||
unit: '',
|
||||
colors: ['#56cdf3', '#719de3'],
|
||||
icon: 'carbon:document-download'
|
||||
},
|
||||
{
|
||||
key: 'trade',
|
||||
title: $t('page.home.trade'),
|
||||
value: 999999,
|
||||
unit: '',
|
||||
colors: ['#fcbc25', '#f68057'],
|
||||
icon: 'ant-design:trademark-circle-outlined'
|
||||
}
|
||||
]);
|
||||
|
||||
async function init() {
|
||||
mockLineData();
|
||||
mockPieData();
|
||||
@ -264,53 +222,32 @@ init();
|
||||
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<HeaderBanner />
|
||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||
<NGi span="24 s:24 m:6">
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<div class="w-full h-360px py-12px">
|
||||
<h3 class="text-16px font-bold">Dashboard</h3>
|
||||
<p class="text-#aaa">Overview Of Lasted Month</p>
|
||||
<h3 class="pt-32px text-24px font-bold">
|
||||
<CountTo prefix="$" :start-value="0" :end-value="7754" />
|
||||
</h3>
|
||||
<p class="text-#aaa">Current Month Earnings</p>
|
||||
<h3 class="pt-32px text-24px font-bold">
|
||||
<CountTo :start-value="0" :end-value="1234" />
|
||||
</h3>
|
||||
<p class="text-#aaa">Current Month Sales</p>
|
||||
<NButton class="mt-24px whitespace-pre-wrap" type="primary">Last Month Summary</NButton>
|
||||
</div>
|
||||
</NCard>
|
||||
</NGi>
|
||||
<NGi span="24 s:24 m:10">
|
||||
<NGi span="24 s:24 m:14">
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<div ref="lineRef" class="h-360px overflow-hidden"></div>
|
||||
</NCard>
|
||||
</NGi>
|
||||
<NGi span="24 s:24 m:8">
|
||||
<NGi span="24 s:24 m:10">
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<div ref="pieRef" class="h-360px"></div>
|
||||
</NCard>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
<NCard :bordered="false" class="card-wrapper">
|
||||
<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 :start-color="item.colors[0]" :end-color="item.colors[1]" 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>
|
||||
<CardData />
|
||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||
<NGi span="24 s:24 m:14">
|
||||
<ProjectNews />
|
||||
</NGi>
|
||||
<NGi span="24 s:24 m:10">
|
||||
<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>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user