mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
refactor(projects): perf code
This commit is contained in:
230
src/hooks/common/echarts.ts
Normal file
230
src/hooks/common/echarts.ts
Normal file
@ -0,0 +1,230 @@
|
||||
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 {
|
||||
BarSeriesOption,
|
||||
GaugeSeriesOption,
|
||||
LineSeriesOption,
|
||||
PictorialBarSeriesOption,
|
||||
PieSeriesOption,
|
||||
RadarSeriesOption,
|
||||
ScatterSeriesOption
|
||||
} from 'echarts/charts';
|
||||
import {
|
||||
DatasetComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
ToolboxComponent,
|
||||
TooltipComponent,
|
||||
TransformComponent
|
||||
} from 'echarts/components';
|
||||
import type {
|
||||
DatasetComponentOption,
|
||||
GridComponentOption,
|
||||
LegendComponentOption,
|
||||
TitleComponentOption,
|
||||
ToolboxComponentOption,
|
||||
TooltipComponentOption
|
||||
} from 'echarts/components';
|
||||
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
|
||||
| LineSeriesOption
|
||||
| PieSeriesOption
|
||||
| ScatterSeriesOption
|
||||
| PictorialBarSeriesOption
|
||||
| RadarSeriesOption
|
||||
| GaugeSeriesOption
|
||||
| TitleComponentOption
|
||||
| LegendComponentOption
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| ToolboxComponentOption
|
||||
| DatasetComponentOption
|
||||
>;
|
||||
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
LegendComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
ToolboxComponent,
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
ScatterChart,
|
||||
PictorialBarChart,
|
||||
RadarChart,
|
||||
GaugeChart,
|
||||
LabelLayout,
|
||||
UniversalTransition,
|
||||
CanvasRenderer
|
||||
]);
|
||||
|
||||
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 optionsFactory echarts options factory function
|
||||
* @param darkMode dark mode
|
||||
*/
|
||||
export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: ChartHooks = {}) {
|
||||
const scope = effectScope();
|
||||
|
||||
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();
|
||||
|
||||
const {
|
||||
onRender = instance => {
|
||||
const textColor = darkMode.value ? 'rgb(224, 224, 224)' : 'rgb(31, 31, 31)';
|
||||
const maskColor = darkMode.value ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.8)';
|
||||
|
||||
instance.showLoading({
|
||||
color: themeStore.themeColor,
|
||||
textColor,
|
||||
fontSize: 14,
|
||||
maskColor
|
||||
});
|
||||
},
|
||||
onUpdated = instance => {
|
||||
instance.hideLoading();
|
||||
},
|
||||
onDestroy
|
||||
} = hooks;
|
||||
|
||||
/**
|
||||
* whether can render chart
|
||||
*
|
||||
* when domRef is ready and initialSize is valid
|
||||
*/
|
||||
function canRender() {
|
||||
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
|
||||
}
|
||||
|
||||
/** is chart rendered */
|
||||
function isRendered() {
|
||||
return Boolean(domRef.value && chart);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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({ ...updatedOpts, backgroundColor: 'transparent' });
|
||||
|
||||
await onUpdated?.(chart!);
|
||||
}
|
||||
|
||||
/** render chart */
|
||||
async function render() {
|
||||
if (!isRendered()) {
|
||||
const chartTheme = darkMode.value ? 'dark' : 'light';
|
||||
|
||||
await nextTick();
|
||||
|
||||
chart = echarts.init(domRef.value, chartTheme);
|
||||
|
||||
chart.setOption({ ...chartOptions, backgroundColor: 'transparent' });
|
||||
|
||||
await onRender?.(chart);
|
||||
}
|
||||
}
|
||||
|
||||
/** resize chart */
|
||||
function resize() {
|
||||
chart?.resize();
|
||||
}
|
||||
|
||||
/** destroy chart */
|
||||
async function destroy() {
|
||||
if (!chart) return;
|
||||
|
||||
await onDestroy?.(chart);
|
||||
chart?.dispose();
|
||||
chart = null;
|
||||
}
|
||||
|
||||
/** change chart theme */
|
||||
async function changeTheme() {
|
||||
await destroy();
|
||||
await render();
|
||||
await onUpdated?.(chart!);
|
||||
}
|
||||
|
||||
/**
|
||||
* render chart by size
|
||||
*
|
||||
* @param w width
|
||||
* @param h height
|
||||
*/
|
||||
async function renderChartBySize(w: number, h: number) {
|
||||
initialSize.width = w;
|
||||
initialSize.height = h;
|
||||
|
||||
// size is abnormal, destroy chart
|
||||
if (!canRender()) {
|
||||
await destroy();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// resize chart
|
||||
if (isRendered()) {
|
||||
resize();
|
||||
}
|
||||
|
||||
// render chart
|
||||
await render();
|
||||
}
|
||||
|
||||
scope.run(() => {
|
||||
watch([width, height], ([newWidth, newHeight]) => {
|
||||
renderChartBySize(newWidth, newHeight);
|
||||
});
|
||||
|
||||
watch(darkMode, () => {
|
||||
changeTheme();
|
||||
});
|
||||
});
|
||||
|
||||
onScopeDispose(() => {
|
||||
destroy();
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
return {
|
||||
domRef,
|
||||
updateOptions
|
||||
};
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
import { ref, toValue } from 'vue';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { FormInst } from 'naive-ui';
|
||||
import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg';
|
||||
import { $t } from '@/locales';
|
||||
@ -50,11 +51,30 @@ export function useFormRules() {
|
||||
};
|
||||
}
|
||||
|
||||
/** create a rule for confirming the password */
|
||||
function createConfirmPwdRule(pwd: string | Ref<string> | ComputedRef<string>) {
|
||||
const confirmPwdRule: App.Global.FormRule[] = [
|
||||
{ required: true, message: $t('form.confirmPwd.required') },
|
||||
{
|
||||
asyncValidator: (rule, value) => {
|
||||
if (value.trim() !== '' && value !== toValue(pwd)) {
|
||||
return Promise.reject(rule.message);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
message: $t('form.confirmPwd.invalid'),
|
||||
trigger: 'input'
|
||||
}
|
||||
];
|
||||
return confirmPwdRule;
|
||||
}
|
||||
|
||||
return {
|
||||
patternRules,
|
||||
formRules,
|
||||
defaultRequiredRule,
|
||||
createRequiredRule
|
||||
createRequiredRule,
|
||||
createConfirmPwdRule
|
||||
};
|
||||
}
|
||||
|
||||
|
10
src/hooks/common/icon.ts
Normal file
10
src/hooks/common/icon.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useSvgIconRender } from '@sa/hooks';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
|
||||
export function useSvgIcon() {
|
||||
const { SvgIconVNode } = useSvgIconRender(SvgIcon);
|
||||
|
||||
return {
|
||||
SvgIconVNode
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user