mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-22 03:26:38 +08:00
feat(projects): page home & perf useEcharts
This commit is contained in:
parent
0fae9932ac
commit
62e4da053d
75
src/components/custom/count-to.vue
Normal file
75
src/components/custom/count-to.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { TransitionPresets, useTransition } from '@vueuse/core';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'CountTo'
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
startValue: 0,
|
||||||
|
endValue: 2024,
|
||||||
|
autoplay: true,
|
||||||
|
decimals: 0,
|
||||||
|
prefix: '',
|
||||||
|
suffix: '',
|
||||||
|
separator: ',',
|
||||||
|
decimal: '.'
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
startValue?: number;
|
||||||
|
endValue?: number;
|
||||||
|
autoplay?: boolean;
|
||||||
|
decimals?: number;
|
||||||
|
prefix?: string;
|
||||||
|
suffix?: string;
|
||||||
|
separator?: string;
|
||||||
|
decimal?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = ref(0);
|
||||||
|
|
||||||
|
const outputValue = useTransition(source, {
|
||||||
|
disabled: false,
|
||||||
|
duration: 1500,
|
||||||
|
transition: TransitionPresets.easeOutCubic
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = computed(() => formatValue(outputValue.value));
|
||||||
|
|
||||||
|
function formatValue(num: number) {
|
||||||
|
const { decimals, decimal, separator, suffix, prefix } = props;
|
||||||
|
|
||||||
|
let number = num.toFixed(decimals);
|
||||||
|
number = String(number);
|
||||||
|
|
||||||
|
const x = number.split('.');
|
||||||
|
let x1 = x[0];
|
||||||
|
const x2 = x.length > 1 ? decimal + x[1] : '';
|
||||||
|
const rgx = /(\d+)(\d{3})/;
|
||||||
|
if (separator) {
|
||||||
|
while (rgx.test(x1)) {
|
||||||
|
x1 = x1.replace(rgx, `$1${separator}$2`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix + x1 + x2 + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.startValue, () => props.endValue],
|
||||||
|
() => {
|
||||||
|
if (props.autoplay) {
|
||||||
|
source.value = props.endValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span>{{ value }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -1,5 +1,4 @@
|
|||||||
import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
|
import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
|
||||||
import type { ComputedRef, Ref } from 'vue';
|
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from 'echarts/core';
|
||||||
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
|
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
|
||||||
import type {
|
import type {
|
||||||
@ -31,6 +30,7 @@ import type {
|
|||||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
import { useElementSize } from '@vueuse/core';
|
import { useElementSize } from '@vueuse/core';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
|
||||||
export type ECOption = echarts.ComposeOption<
|
export type ECOption = echarts.ComposeOption<
|
||||||
| BarSeriesOption
|
| BarSeriesOption
|
||||||
@ -70,58 +70,95 @@ echarts.use([
|
|||||||
|
|
||||||
interface ChartHooks {
|
interface ChartHooks {
|
||||||
onRender?: (chart: echarts.ECharts) => void | Promise<void>;
|
onRender?: (chart: echarts.ECharts) => void | Promise<void>;
|
||||||
|
onUpdated?: (chart: echarts.ECharts) => void | Promise<void>;
|
||||||
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
|
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* use echarts
|
* use echarts
|
||||||
*
|
*
|
||||||
* @param options echarts options
|
* @param optionsFactory echarts options factory function
|
||||||
* @param darkMode dark mode
|
* @param darkMode dark mode
|
||||||
*/
|
*/
|
||||||
export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedRef<boolean>, hooks?: ChartHooks) {
|
export function useEcharts<T extends ECOption>(
|
||||||
|
optionsFactory: () => T,
|
||||||
|
hooks: ChartHooks = {
|
||||||
|
onRender(chart) {
|
||||||
|
chart.showLoading();
|
||||||
|
},
|
||||||
|
onUpdated(chart) {
|
||||||
|
chart.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
const scope = effectScope();
|
const scope = effectScope();
|
||||||
|
|
||||||
const domRef = ref<HTMLElement | null>(null);
|
const themeStore = useThemeStore();
|
||||||
|
const darkMode = computed(() => themeStore.darkMode);
|
||||||
|
|
||||||
|
const domRef = ref<HTMLElement | null>(null);
|
||||||
const initialSize = { width: 0, height: 0 };
|
const initialSize = { width: 0, height: 0 };
|
||||||
const { width, height } = useElementSize(domRef, initialSize);
|
const { width, height } = useElementSize(domRef, initialSize);
|
||||||
|
|
||||||
let chart: echarts.ECharts | null = null;
|
let chart: echarts.ECharts | null = null;
|
||||||
|
const chartOptions: T = optionsFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether can render chart
|
||||||
|
*
|
||||||
|
* when domRef is ready and initialSize is valid
|
||||||
|
*/
|
||||||
function canRender() {
|
function canRender() {
|
||||||
return initialSize.width > 0 && initialSize.height > 0;
|
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** is chart rendered */
|
||||||
function isRendered() {
|
function isRendered() {
|
||||||
return Boolean(domRef.value && chart);
|
return Boolean(domRef.value && chart);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOptions(opts: ECOption) {
|
/**
|
||||||
|
* update chart options
|
||||||
|
*
|
||||||
|
* @param callback callback function
|
||||||
|
*/
|
||||||
|
async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) {
|
||||||
|
if (!isRendered()) return;
|
||||||
|
|
||||||
|
const updatedOpts = callback(chartOptions, optionsFactory);
|
||||||
|
|
||||||
|
Object.assign(chartOptions, updatedOpts);
|
||||||
|
|
||||||
if (isRendered()) {
|
if (isRendered()) {
|
||||||
chart?.clear();
|
chart?.clear();
|
||||||
chart?.setOption({ ...opts, backgroundColor: 'transparent' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
|
||||||
|
|
||||||
|
await hooks?.onUpdated?.(chart!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** render chart */
|
||||||
async function render() {
|
async function render() {
|
||||||
if (domRef.value) {
|
if (!isRendered()) {
|
||||||
const chartTheme = darkMode.value ? 'dark' : 'light';
|
const chartTheme = darkMode.value ? 'dark' : 'light';
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
chart = echarts.init(domRef.value, chartTheme);
|
chart = echarts.init(domRef.value, chartTheme);
|
||||||
|
|
||||||
setOptions(options);
|
chart.setOption({ ...chartOptions, backgroundColor: 'transparent' });
|
||||||
|
|
||||||
await hooks?.onRender?.(chart);
|
await hooks?.onRender?.(chart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** resize chart */
|
||||||
function resize() {
|
function resize() {
|
||||||
chart?.resize();
|
chart?.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** destroy chart */
|
||||||
async function destroy() {
|
async function destroy() {
|
||||||
if (!chart) return;
|
if (!chart) return;
|
||||||
|
|
||||||
@ -130,16 +167,18 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
|
|||||||
chart = null;
|
chart = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** change chart theme */
|
||||||
async function changeTheme() {
|
async function changeTheme() {
|
||||||
await destroy();
|
await destroy();
|
||||||
await render();
|
await render();
|
||||||
|
await hooks?.onUpdated?.(chart!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* render chart by size
|
* render chart by size
|
||||||
*
|
*
|
||||||
* @param w
|
* @param w width
|
||||||
* @param h
|
* @param h height
|
||||||
*/
|
*/
|
||||||
async function renderChartBySize(w: number, h: number) {
|
async function renderChartBySize(w: number, h: number) {
|
||||||
initialSize.width = w;
|
initialSize.width = w;
|
||||||
@ -152,14 +191,13 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// render chart
|
// resize chart
|
||||||
if (!isRendered()) {
|
if (isRendered()) {
|
||||||
await render();
|
resize();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize chart
|
// render chart
|
||||||
resize();
|
await render();
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.run(() => {
|
scope.run(() => {
|
||||||
@ -179,6 +217,6 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
domRef,
|
domRef,
|
||||||
setOptions
|
updateOptions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,18 @@ const local: App.I18n.Schema = {
|
|||||||
},
|
},
|
||||||
prdDep: 'Production Dependency',
|
prdDep: 'Production Dependency',
|
||||||
devDep: 'Development Dependency'
|
devDep: 'Development Dependency'
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
downloadCount: 'Download Count',
|
||||||
|
registerCount: 'Register Count',
|
||||||
|
schedule: 'Work and rest Schedule',
|
||||||
|
study: 'Study',
|
||||||
|
work: 'Work',
|
||||||
|
rest: 'Rest',
|
||||||
|
entertainment: 'Entertainment',
|
||||||
|
visit: 'Visit Count',
|
||||||
|
amount: 'Amount',
|
||||||
|
trade: 'Trade Count'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
@ -184,6 +184,18 @@ const local: App.I18n.Schema = {
|
|||||||
},
|
},
|
||||||
prdDep: '生产依赖',
|
prdDep: '生产依赖',
|
||||||
devDep: '开发依赖'
|
devDep: '开发依赖'
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
downloadCount: '下载量',
|
||||||
|
registerCount: '注册量',
|
||||||
|
schedule: '作息安排',
|
||||||
|
study: '学习',
|
||||||
|
work: '工作',
|
||||||
|
rest: '休息',
|
||||||
|
entertainment: '娱乐',
|
||||||
|
visit: '访问量',
|
||||||
|
amount: '成交额',
|
||||||
|
trade: '成交量'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
12
src/typings/app.d.ts
vendored
12
src/typings/app.d.ts
vendored
@ -369,6 +369,18 @@ declare namespace App {
|
|||||||
prdDep: string;
|
prdDep: string;
|
||||||
devDep: string;
|
devDep: string;
|
||||||
};
|
};
|
||||||
|
home: {
|
||||||
|
downloadCount: string;
|
||||||
|
registerCount: string;
|
||||||
|
schedule: string;
|
||||||
|
study: string;
|
||||||
|
work: string;
|
||||||
|
rest: string;
|
||||||
|
entertainment: string;
|
||||||
|
visit: string;
|
||||||
|
amount: string;
|
||||||
|
trade: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
form: {
|
form: {
|
||||||
userName: FormMsg;
|
userName: FormMsg;
|
||||||
|
4
src/typings/components.d.ts
vendored
4
src/typings/components.d.ts
vendored
@ -10,6 +10,7 @@ declare module 'vue' {
|
|||||||
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
||||||
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
|
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
|
||||||
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
||||||
|
CountTo: typeof import('./../components/custom/count-to.vue')['default']
|
||||||
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
|
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
|
||||||
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
|
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
|
||||||
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
|
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
|
||||||
@ -35,6 +36,9 @@ declare module 'vue' {
|
|||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
NGi: typeof import('naive-ui')['NGi']
|
||||||
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
|
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||||
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||||
|
27
src/views/home/components/gradient-bg.vue
Normal file
27
src/views/home/components/gradient-bg.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<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>
|
@ -1,9 +1,317 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
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';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||||
|
|
||||||
|
const { domRef: lineRef, updateOptions: updateLineOptions } = 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 mockLineData() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateLineOptions(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 updateLineLocale() {
|
||||||
|
updateLineOptions((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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { domRef: pieRef, updateOptions: updatePieOptions } = 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 mockPieData() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
updatePieOptions(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 updatePieLocale() {
|
||||||
|
updatePieOptions((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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => appStore.locale,
|
||||||
|
() => {
|
||||||
|
updateLineLocale();
|
||||||
|
updatePieLocale();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// init
|
||||||
|
init();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<NSpace vertical :size="16">
|
||||||
<NCalendar />
|
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||||
</div>
|
<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">
|
||||||
|
<NCard :bordered="false" class="card-wrapper">
|
||||||
|
<div ref="lineRef" class="h-360px overflow-hidden"></div>
|
||||||
|
</NCard>
|
||||||
|
</NGi>
|
||||||
|
<NGi span="24 s:24 m:8">
|
||||||
|
<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>
|
||||||
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
Loading…
Reference in New Issue
Block a user