diff --git a/src/components/custom/count-to.vue b/src/components/custom/count-to.vue new file mode 100644 index 000000000..7d8dcc950 --- /dev/null +++ b/src/components/custom/count-to.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/hooks/chart/use-echarts.ts b/src/hooks/chart/use-echarts.ts index b74bfd1cc..f05913300 100644 --- a/src/hooks/chart/use-echarts.ts +++ b/src/hooks/chart/use-echarts.ts @@ -1,5 +1,4 @@ -import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue'; -import type { ComputedRef, Ref } from 'vue'; +import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue'; import * as echarts from 'echarts/core'; import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts'; import type { @@ -31,6 +30,7 @@ import type { import { LabelLayout, UniversalTransition } from 'echarts/features'; import { CanvasRenderer } from 'echarts/renderers'; import { useElementSize } from '@vueuse/core'; +import { useThemeStore } from '@/store/modules/theme'; export type ECOption = echarts.ComposeOption< | BarSeriesOption @@ -70,58 +70,95 @@ echarts.use([ interface ChartHooks { onRender?: (chart: echarts.ECharts) => void | Promise; + onUpdated?: (chart: echarts.ECharts) => void | Promise; onDestroy?: (chart: echarts.ECharts) => void | Promise; } /** * use echarts * - * @param options echarts options + * @param optionsFactory echarts options factory function * @param darkMode dark mode */ -export function useEcharts(options: ECOption, darkMode: Ref | ComputedRef, hooks?: ChartHooks) { +export function useEcharts( + optionsFactory: () => T, + hooks: ChartHooks = { + onRender(chart) { + chart.showLoading(); + }, + onUpdated(chart) { + chart.hideLoading(); + } + } +) { const scope = effectScope(); - const domRef = ref(null); + const themeStore = useThemeStore(); + const darkMode = computed(() => themeStore.darkMode); + const domRef = ref(null); const initialSize = { width: 0, height: 0 }; const { width, height } = useElementSize(domRef, initialSize); let chart: echarts.ECharts | null = null; + const chartOptions: T = optionsFactory(); + /** + * whether can render chart + * + * when domRef is ready and initialSize is valid + */ function canRender() { - return initialSize.width > 0 && initialSize.height > 0; + return domRef.value && initialSize.width > 0 && initialSize.height > 0; } + /** is chart rendered */ function isRendered() { 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()) { chart?.clear(); - chart?.setOption({ ...opts, backgroundColor: 'transparent' }); } + + chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' }); + + await hooks?.onUpdated?.(chart!); } + /** render chart */ async function render() { - if (domRef.value) { + if (!isRendered()) { const chartTheme = darkMode.value ? 'dark' : 'light'; await nextTick(); chart = echarts.init(domRef.value, chartTheme); - setOptions(options); + chart.setOption({ ...chartOptions, backgroundColor: 'transparent' }); await hooks?.onRender?.(chart); } } + /** resize chart */ function resize() { chart?.resize(); } + /** destroy chart */ async function destroy() { if (!chart) return; @@ -130,16 +167,18 @@ export function useEcharts(options: ECOption, darkMode: Ref | ComputedR chart = null; } + /** change chart theme */ async function changeTheme() { await destroy(); await render(); + await hooks?.onUpdated?.(chart!); } /** * render chart by size * - * @param w - * @param h + * @param w width + * @param h height */ async function renderChartBySize(w: number, h: number) { initialSize.width = w; @@ -152,14 +191,13 @@ export function useEcharts(options: ECOption, darkMode: Ref | ComputedR return; } - // render chart - if (!isRendered()) { - await render(); - return; + // resize chart + if (isRendered()) { + resize(); } - // resize chart - resize(); + // render chart + await render(); } scope.run(() => { @@ -179,6 +217,6 @@ export function useEcharts(options: ECOption, darkMode: Ref | ComputedR return { domRef, - setOptions + updateOptions }; } diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index 2a83b11bb..b8117628a 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -184,6 +184,18 @@ const local: App.I18n.Schema = { }, prdDep: 'Production 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: { diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 70c9637bf..f8eae156d 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -184,6 +184,18 @@ const local: App.I18n.Schema = { }, prdDep: '生产依赖', devDep: '开发依赖' + }, + home: { + downloadCount: '下载量', + registerCount: '注册量', + schedule: '作息安排', + study: '学习', + work: '工作', + rest: '休息', + entertainment: '娱乐', + visit: '访问量', + amount: '成交额', + trade: '成交量' } }, form: { diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts index e132a8256..d949792ba 100644 --- a/src/typings/app.d.ts +++ b/src/typings/app.d.ts @@ -369,6 +369,18 @@ declare namespace App { prdDep: 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: { userName: FormMsg; diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index df75b79f1..d63e270a3 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -10,6 +10,7 @@ declare module 'vue' { AppProvider: typeof import('./../components/common/app-provider.vue')['default'] BetterScroll: typeof import('./../components/custom/better-scroll.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'] ExceptionBase: typeof import('./../components/common/exception-base.vue')['default'] FullScreen: typeof import('./../components/common/full-screen.vue')['default'] @@ -35,6 +36,9 @@ declare module 'vue' { NDropdown: typeof import('naive-ui')['NDropdown'] NForm: typeof import('naive-ui')['NForm'] 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'] NInputNumber: typeof import('naive-ui')['NInputNumber'] NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] diff --git a/src/views/home/components/gradient-bg.vue b/src/views/home/components/gradient-bg.vue new file mode 100644 index 000000000..a5840846f --- /dev/null +++ b/src/views/home/components/gradient-bg.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/views/home/index.vue b/src/views/home/index.vue index e830d7f46..f7d70e29b 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -1,9 +1,317 @@ - +