Skip to content

Commit

Permalink
feat(projects): page home & perf useEcharts
Browse files Browse the repository at this point in the history
  • Loading branch information
honghuangdc committed Jan 23, 2024
1 parent 0fae993 commit 62e4da0
Show file tree
Hide file tree
Showing 8 changed files with 511 additions and 23 deletions.
75 changes: 75 additions & 0 deletions src/components/custom/count-to.vue
@@ -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>
76 changes: 57 additions & 19 deletions 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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -70,58 +70,95 @@ echarts.use([

interface ChartHooks {
onRender?: (chart: echarts.ECharts) => void | Promise<void>;
onUpdated?: (chart: echarts.ECharts) => void | Promise<void>;
onDestroy?: (chart: echarts.ECharts) => void | Promise<void>;
}

/**
* use echarts
*
* @param options echarts options
* @param optionsFactory echarts options factory function
* @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 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 { 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;

Expand All @@ -130,16 +167,18 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | 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;
Expand All @@ -152,14 +191,13 @@ export function useEcharts(options: ECOption, darkMode: Ref<boolean> | ComputedR
return;
}

// render chart
if (!isRendered()) {
await render();
return;
// resize chart
if (isRendered()) {
resize();
}

// resize chart
resize();
// render chart
await render();
}

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

return {
domRef,
setOptions
updateOptions
};
}
12 changes: 12 additions & 0 deletions src/locales/langs/en-us.ts
Expand Up @@ -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: {
Expand Down
12 changes: 12 additions & 0 deletions src/locales/langs/zh-cn.ts
Expand Up @@ -184,6 +184,18 @@ const local: App.I18n.Schema = {
},
prdDep: '生产依赖',
devDep: '开发依赖'
},
home: {
downloadCount: '下载量',
registerCount: '注册量',
schedule: '作息安排',
study: '学习',
work: '工作',
rest: '休息',
entertainment: '娱乐',
visit: '访问量',
amount: '成交额',
trade: '成交量'
}
},
form: {
Expand Down
12 changes: 12 additions & 0 deletions src/typings/app.d.ts
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/typings/components.d.ts
Expand Up @@ -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']
Expand All @@ -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']
Expand Down
27 changes: 27 additions & 0 deletions src/views/home/components/gradient-bg.vue
@@ -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 comment on commit 62e4da0

@vercel
Copy link

@vercel vercel bot commented on 62e4da0 Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

soybean-admin – ./

soybean-admin-soybeanjs.vercel.app
soybean-admin-eta.vercel.app
soybean-admin-git-main-soybeanjs.vercel.app

Please sign in to comment.