Skip to content

Commit 62e4da0

Browse files
committed
feat(projects): page home & perf useEcharts
1 parent 0fae993 commit 62e4da0

File tree

8 files changed

+511
-23
lines changed

8 files changed

+511
-23
lines changed

src/components/custom/count-to.vue

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<script setup lang="ts">
2+
import { computed, ref, watch } from 'vue';
3+
import { TransitionPresets, useTransition } from '@vueuse/core';
4+
5+
defineOptions({
6+
name: 'CountTo'
7+
});
8+
9+
const props = withDefaults(defineProps<Props>(), {
10+
startValue: 0,
11+
endValue: 2024,
12+
autoplay: true,
13+
decimals: 0,
14+
prefix: '',
15+
suffix: '',
16+
separator: ',',
17+
decimal: '.'
18+
});
19+
20+
interface Props {
21+
startValue?: number;
22+
endValue?: number;
23+
autoplay?: boolean;
24+
decimals?: number;
25+
prefix?: string;
26+
suffix?: string;
27+
separator?: string;
28+
decimal?: string;
29+
}
30+
31+
const source = ref(0);
32+
33+
const outputValue = useTransition(source, {
34+
disabled: false,
35+
duration: 1500,
36+
transition: TransitionPresets.easeOutCubic
37+
});
38+
39+
const value = computed(() => formatValue(outputValue.value));
40+
41+
function formatValue(num: number) {
42+
const { decimals, decimal, separator, suffix, prefix } = props;
43+
44+
let number = num.toFixed(decimals);
45+
number = String(number);
46+
47+
const x = number.split('.');
48+
let x1 = x[0];
49+
const x2 = x.length > 1 ? decimal + x[1] : '';
50+
const rgx = /(\d+)(\d{3})/;
51+
if (separator) {
52+
while (rgx.test(x1)) {
53+
x1 = x1.replace(rgx, `$1${separator}$2`);
54+
}
55+
}
56+
57+
return prefix + x1 + x2 + suffix;
58+
}
59+
60+
watch(
61+
[() => props.startValue, () => props.endValue],
62+
() => {
63+
if (props.autoplay) {
64+
source.value = props.endValue;
65+
}
66+
},
67+
{ immediate: true }
68+
);
69+
</script>
70+
71+
<template>
72+
<span>{{ value }}</span>
73+
</template>
74+
75+
<style scoped></style>

src/hooks/chart/use-echarts.ts

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
2-
import type { ComputedRef, Ref } from 'vue';
1+
import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
32
import * as echarts from 'echarts/core';
43
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
54
import type {
@@ -31,6 +30,7 @@ import type {
3130
import { LabelLayout, UniversalTransition } from 'echarts/features';
3231
import { CanvasRenderer } from 'echarts/renderers';
3332
import { useElementSize } from '@vueuse/core';
33+
import { useThemeStore } from '@/store/modules/theme';
3434

3535
export type ECOption = echarts.ComposeOption<
3636
| BarSeriesOption
@@ -70,58 +70,95 @@ echarts.use([
7070

7171
interface ChartHooks {
7272
onRender?: (chart: echarts.ECharts) => void | Promise<void>;
73+
onUpdated?: (chart: echarts.ECharts) => void | Promise<void>;
7374
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
7475
}
7576

7677
/**
7778
* use echarts
7879
*
79-
* @param options echarts options
80+
* @param optionsFactory echarts options factory function
8081
* @param darkMode dark mode
8182
*/
82-
export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedRef<boolean>, hooks?: ChartHooks) {
83+
export function useEcharts<T extends ECOption>(
84+
optionsFactory: () => T,
85+
hooks: ChartHooks = {
86+
onRender(chart) {
87+
chart.showLoading();
88+
},
89+
onUpdated(chart) {
90+
chart.hideLoading();
91+
}
92+
}
93+
) {
8394
const scope = effectScope();
8495

85-
const domRef = ref<HTMLElement | null>(null);
96+
const themeStore = useThemeStore();
97+
const darkMode = computed(() => themeStore.darkMode);
8698

99+
const domRef = ref<HTMLElement | null>(null);
87100
const initialSize = { width: 0, height: 0 };
88101
const { width, height } = useElementSize(domRef, initialSize);
89102

90103
let chart: echarts.ECharts | null = null;
104+
const chartOptions: T = optionsFactory();
91105

106+
/**
107+
* whether can render chart
108+
*
109+
* when domRef is ready and initialSize is valid
110+
*/
92111
function canRender() {
93-
return initialSize.width > 0 && initialSize.height > 0;
112+
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
94113
}
95114

115+
/** is chart rendered */
96116
function isRendered() {
97117
return Boolean(domRef.value && chart);
98118
}
99119

100-
function setOptions(opts: ECOption) {
120+
/**
121+
* update chart options
122+
*
123+
* @param callback callback function
124+
*/
125+
async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) {
126+
if (!isRendered()) return;
127+
128+
const updatedOpts = callback(chartOptions, optionsFactory);
129+
130+
Object.assign(chartOptions, updatedOpts);
131+
101132
if (isRendered()) {
102133
chart?.clear();
103-
chart?.setOption({ ...opts, backgroundColor: 'transparent' });
104134
}
135+
136+
chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
137+
138+
await hooks?.onUpdated?.(chart!);
105139
}
106140

141+
/** render chart */
107142
async function render() {
108-
if (domRef.value) {
143+
if (!isRendered()) {
109144
const chartTheme = darkMode.value ? 'dark' : 'light';
110145

111146
await nextTick();
112147

113148
chart = echarts.init(domRef.value, chartTheme);
114149

115-
setOptions(options);
150+
chart.setOption({ ...chartOptions, backgroundColor: 'transparent' });
116151

117152
await hooks?.onRender?.(chart);
118153
}
119154
}
120155

156+
/** resize chart */
121157
function resize() {
122158
chart?.resize();
123159
}
124160

161+
/** destroy chart */
125162
async function destroy() {
126163
if (!chart) return;
127164

@@ -130,16 +167,18 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
130167
chart = null;
131168
}
132169

170+
/** change chart theme */
133171
async function changeTheme() {
134172
await destroy();
135173
await render();
174+
await hooks?.onUpdated?.(chart!);
136175
}
137176

138177
/**
139178
* render chart by size
140179
*
141-
* @param w
142-
* @param h
180+
* @param w width
181+
* @param h height
143182
*/
144183
async function renderChartBySize(w: number, h: number) {
145184
initialSize.width = w;
@@ -152,14 +191,13 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
152191
return;
153192
}
154193

155-
// render chart
156-
if (!isRendered()) {
157-
await render();
158-
return;
194+
// resize chart
195+
if (isRendered()) {
196+
resize();
159197
}
160198

161-
// resize chart
162-
resize();
199+
// render chart
200+
await render();
163201
}
164202

165203
scope.run(() => {
@@ -179,6 +217,6 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
179217

180218
return {
181219
domRef,
182-
setOptions
220+
updateOptions
183221
};
184222
}

src/locales/langs/en-us.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ const local: App.I18n.Schema = {
184184
},
185185
prdDep: 'Production Dependency',
186186
devDep: 'Development Dependency'
187+
},
188+
home: {
189+
downloadCount: 'Download Count',
190+
registerCount: 'Register Count',
191+
schedule: 'Work and rest Schedule',
192+
study: 'Study',
193+
work: 'Work',
194+
rest: 'Rest',
195+
entertainment: 'Entertainment',
196+
visit: 'Visit Count',
197+
amount: 'Amount',
198+
trade: 'Trade Count'
187199
}
188200
},
189201
form: {

src/locales/langs/zh-cn.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ const local: App.I18n.Schema = {
184184
},
185185
prdDep: '生产依赖',
186186
devDep: '开发依赖'
187+
},
188+
home: {
189+
downloadCount: '下载量',
190+
registerCount: '注册量',
191+
schedule: '作息安排',
192+
study: '学习',
193+
work: '工作',
194+
rest: '休息',
195+
entertainment: '娱乐',
196+
visit: '访问量',
197+
amount: '成交额',
198+
trade: '成交量'
187199
}
188200
},
189201
form: {

src/typings/app.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,18 @@ declare namespace App {
369369
prdDep: string;
370370
devDep: string;
371371
};
372+
home: {
373+
downloadCount: string;
374+
registerCount: string;
375+
schedule: string;
376+
study: string;
377+
work: string;
378+
rest: string;
379+
entertainment: string;
380+
visit: string;
381+
amount: string;
382+
trade: string;
383+
};
372384
};
373385
form: {
374386
userName: FormMsg;

src/typings/components.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare module 'vue' {
1010
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
1111
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
1212
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
13+
CountTo: typeof import('./../components/custom/count-to.vue')['default']
1314
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
1415
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
1516
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
@@ -35,6 +36,9 @@ declare module 'vue' {
3536
NDropdown: typeof import('naive-ui')['NDropdown']
3637
NForm: typeof import('naive-ui')['NForm']
3738
NFormItem: typeof import('naive-ui')['NFormItem']
39+
NGi: typeof import('naive-ui')['NGi']
40+
NGrid: typeof import('naive-ui')['NGrid']
41+
NGridItem: typeof import('naive-ui')['NGridItem']
3842
NInput: typeof import('naive-ui')['NInput']
3943
NInputNumber: typeof import('naive-ui')['NInputNumber']
4044
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
4+
defineOptions({
5+
name: 'GradientBg'
6+
});
7+
8+
const props = withDefaults(defineProps<Props>(), {
9+
startColor: '#56cdf3',
10+
endColor: '#719de3'
11+
});
12+
13+
interface Props {
14+
startColor?: string;
15+
endColor?: string;
16+
}
17+
18+
const gradientStyle = computed(() => `linear-gradient(to bottom right, ${props.startColor}, ${props.endColor})`);
19+
</script>
20+
21+
<template>
22+
<div class="px-16px pt-8px pb-4px rd-8px text-white" :style="{ backgroundImage: gradientStyle }">
23+
<slot></slot>
24+
</div>
25+
</template>
26+
27+
<style scoped></style>

0 commit comments

Comments
 (0)