@fmdevui/fm-dev
Version:
Page level components developed based on Element Plus.
513 lines (509 loc) • 13.2 kB
JavaScript
;
var vue = require('vue');
require('../plugins/echarts.js');
var echarts = require('../../../node_modules/.pnpm/echarts@6.0.0/node_modules/echarts/lib/core/echarts.js');
const useChartOps = () => ({
/** */
chartHeight: "16rem",
/** 字体大小 */
fontSize: 13,
/** 字体颜色 */
fontColor: "#999",
/** 主题颜色 */
themeColor: "#00E4E5",
/** 颜色组 */
colors: [
"#00E4E5",
"#4ABEFF",
"#EDF2FF",
"#14DEBA",
"#FFAF20",
"#FA8A6C",
"#FFAF20"
]
});
const RESIZE_DELAYS = [50, 100, 200, 350];
const MENU_RESIZE_DELAYS = [50, 100, 200];
const RESIZE_DEBOUNCE_DELAY = 100;
function useChart(options = {}) {
const { initOptions, initDelay = 0, threshold = 0.1, autoTheme = true } = options;
const chartRef = vue.ref();
let chart = null;
let intersectionObserver = null;
let pendingOptions = null;
let resizeTimeoutId = null;
let resizeFrameId = null;
let isDestroyed = false;
let emptyStateDiv = null;
const clearTimers = () => {
if (resizeTimeoutId) {
clearTimeout(resizeTimeoutId);
resizeTimeoutId = null;
}
if (resizeFrameId) {
cancelAnimationFrame(resizeFrameId);
resizeFrameId = null;
}
};
const requestAnimationResize = () => {
if (resizeFrameId) {
cancelAnimationFrame(resizeFrameId);
}
resizeFrameId = requestAnimationFrame(() => {
handleResize();
resizeFrameId = null;
});
};
const debouncedResize = () => {
if (resizeTimeoutId) {
clearTimeout(resizeTimeoutId);
}
resizeTimeoutId = window.setTimeout(() => {
requestAnimationResize();
resizeTimeoutId = null;
}, RESIZE_DEBOUNCE_DELAY);
};
const multiDelayResize = (delays) => {
vue.nextTick(requestAnimationResize);
delays.forEach((delay) => {
setTimeout(requestAnimationResize, delay);
});
};
let menuOpenStopHandle = null;
let menuTypeStopHandle = null;
const setupMenuWatchers = () => {
vue.nextTick(requestAnimationResize);
setTimeout(() => multiDelayResize(MENU_RESIZE_DELAYS), 0);
};
const cleanupMenuWatchers = () => {
menuOpenStopHandle?.();
menuTypeStopHandle?.();
menuOpenStopHandle = null;
menuTypeStopHandle = null;
};
let themeStopHandle = null;
const setupThemeWatcher = () => {
if (autoTheme) {
}
};
const cleanupThemeWatcher = () => {
themeStopHandle?.();
themeStopHandle = null;
};
const createLineStyle = (color, width = 1, type) => ({
color,
width,
...type && { type }
});
const styleCache = {
axisLine: null,
splitLine: null,
axisLabel: null,
lastDarkValue: false
};
const clearStyleCache = () => {
styleCache.axisLine = null;
styleCache.splitLine = null;
styleCache.axisLabel = null;
styleCache.lastDarkValue = false;
};
const getAxisLineStyle = (show = true) => {
if (styleCache.lastDarkValue !== false) {
clearStyleCache();
}
if (!styleCache.axisLine) {
styleCache.axisLine = {
show,
lineStyle: createLineStyle("#EDEDED")
};
}
return styleCache.axisLine;
};
const getSplitLineStyle = (show = true) => {
if (styleCache.lastDarkValue !== false) {
clearStyleCache();
}
if (!styleCache.splitLine) {
styleCache.splitLine = {
show,
lineStyle: createLineStyle("#EDEDED", 1, "dashed")
};
}
return styleCache.splitLine;
};
const getAxisLabelStyle = (show = true) => {
if (styleCache.lastDarkValue !== false) {
clearStyleCache();
}
if (!styleCache.axisLabel) {
const { fontColor, fontSize } = useChartOps();
styleCache.axisLabel = {
show,
color: fontColor,
fontSize
};
}
return styleCache.axisLabel;
};
const getAxisTickStyle = () => ({
show: false
});
const getAnimationConfig = (animationDelay = 50, animationDuration = 1500) => ({
animationDelay: (idx) => idx * animationDelay + 200,
animationDuration: (idx) => animationDuration - idx * 50,
animationEasing: "quarticOut"
});
const getTooltipStyle = (trigger = "axis", customOptions = {}) => ({
trigger,
backgroundColor: "rgba(255, 255, 255, 0.9)",
borderColor: "#ddd",
borderWidth: 1,
textStyle: {
color: "#333"
},
...customOptions
});
const getLegendStyle = (position = "bottom", customOptions = {}) => {
const baseConfig = {
textStyle: {
color: "#333"
},
itemWidth: 12,
itemHeight: 12,
itemGap: 20,
...customOptions
};
switch (position) {
case "bottom":
return {
...baseConfig,
bottom: 0,
left: "center",
orient: "horizontal",
icon: "roundRect"
};
case "top":
return {
...baseConfig,
top: 0,
left: "center",
orient: "horizontal",
icon: "roundRect"
};
case "left":
return {
...baseConfig,
left: 0,
top: "center",
orient: "vertical",
icon: "roundRect"
};
case "right":
return {
...baseConfig,
right: 0,
top: "center",
orient: "vertical",
icon: "roundRect"
};
default:
return baseConfig;
}
};
const getGridWithLegend = (showLegend, legendPosition = "bottom", baseGrid = {}) => {
const defaultGrid = {
top: 15,
right: 15,
bottom: 8,
left: 0,
containLabel: true,
...baseGrid
};
if (!showLegend) {
return defaultGrid;
}
switch (legendPosition) {
case "bottom":
return {
...defaultGrid,
bottom: 40
};
case "top":
return {
...defaultGrid,
top: 40
};
case "left":
return {
...defaultGrid,
left: 120
};
case "right":
return {
...defaultGrid,
right: 120
};
default:
return defaultGrid;
}
};
const createIntersectionObserver = () => {
if (intersectionObserver || !chartRef.value) return;
intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && pendingOptions && !isDestroyed) {
requestAnimationFrame(() => {
if (!isDestroyed && pendingOptions) {
try {
if (!chart) {
chart = echarts.init(entry.target);
}
const event = new CustomEvent("chartVisible", {
detail: { options: pendingOptions }
});
entry.target.dispatchEvent(event);
pendingOptions = null;
cleanupIntersectionObserver();
} catch (error) {
console.error("\u56FE\u8868\u521D\u59CB\u5316\u5931\u8D25:", error);
}
}
});
}
});
},
{ threshold }
);
intersectionObserver.observe(chartRef.value);
};
const cleanupIntersectionObserver = () => {
if (intersectionObserver) {
intersectionObserver.disconnect();
intersectionObserver = null;
}
};
const isContainerVisible = (element) => {
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0 && rect.top < window.innerHeight && rect.bottom > 0;
};
const performChartInit = (options2) => {
if (!chart && chartRef.value && !isDestroyed) {
chart = echarts.init(chartRef.value);
setupMenuWatchers();
setupThemeWatcher();
}
if (chart && !isDestroyed) {
chart.setOption(options2);
pendingOptions = null;
}
};
const emptyStateManager = {
create: () => {
if (!chartRef.value || emptyStateDiv) return;
emptyStateDiv = document.createElement("div");
emptyStateDiv.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
color: ${"#B3B2B2"};
background: transparent;
z-index: 10;
`;
emptyStateDiv.innerHTML = `<span>\u6682\u65E0\u6570\u636E</span>`;
if (chartRef.value.style.position !== "relative" && chartRef.value.style.position !== "absolute") {
chartRef.value.style.position = "relative";
}
chartRef.value.appendChild(emptyStateDiv);
},
remove: () => {
if (emptyStateDiv && chartRef.value) {
chartRef.value.removeChild(emptyStateDiv);
emptyStateDiv = null;
}
},
updateStyle: () => {
if (emptyStateDiv) {
emptyStateDiv.style.color = "#999";
}
}
};
const initChart = (options2 = {}, isEmpty = false) => {
if (!chartRef.value || isDestroyed) return;
const mergedOptions = { ...initOptions, ...options2 };
try {
if (isEmpty) {
if (chart) {
chart.clear();
}
emptyStateManager.create();
return;
} else {
emptyStateManager.remove();
}
if (isContainerVisible(chartRef.value)) {
if (initDelay > 0) {
setTimeout(() => performChartInit(mergedOptions), initDelay);
} else {
performChartInit(mergedOptions);
}
} else {
pendingOptions = mergedOptions;
createIntersectionObserver();
}
} catch (error) {
console.error("\u56FE\u8868\u521D\u59CB\u5316\u5931\u8D25:", error);
}
};
const updateChart = (options2) => {
if (isDestroyed) return;
try {
if (!chart) {
initChart(options2);
return;
}
chart.setOption(options2);
} catch (error) {
console.error("\u56FE\u8868\u66F4\u65B0\u5931\u8D25:", error);
}
};
const handleResize = () => {
if (chart && !isDestroyed) {
try {
chart.resize();
} catch (error) {
console.error("\u56FE\u8868resize\u5931\u8D25:", error);
}
}
};
const destroyChart = () => {
isDestroyed = true;
if (chart) {
try {
chart.dispose();
} catch (error) {
console.error("\u56FE\u8868\u9500\u6BC1\u5931\u8D25:", error);
} finally {
chart = null;
}
}
cleanupMenuWatchers();
cleanupThemeWatcher();
emptyStateManager.remove();
cleanupIntersectionObserver();
clearTimers();
clearStyleCache();
pendingOptions = null;
};
const getChartInstance = () => chart;
const isChartInitialized = () => chart !== null;
vue.onMounted(() => {
window.addEventListener("resize", debouncedResize);
});
vue.onBeforeUnmount(() => {
window.removeEventListener("resize", debouncedResize);
});
vue.onUnmounted(() => {
destroyChart();
});
return {
chartRef,
initChart,
updateChart,
handleResize,
destroyChart,
getChartInstance,
isChartInitialized,
emptyStateManager,
getAxisLineStyle,
getSplitLineStyle,
getAxisLabelStyle,
getAxisTickStyle,
getAnimationConfig,
getTooltipStyle,
getLegendStyle,
useChartOps,
getGridWithLegend
};
}
function useChartComponent(options) {
const {
props,
generateOptions,
checkEmpty,
watchSources = [],
onVisible,
chartOptions = {}
} = options;
const chart = useChart(chartOptions);
const { chartRef, initChart, emptyStateManager } = chart;
const isEmpty = vue.computed(() => {
if (props.isEmpty) return true;
if (checkEmpty) return checkEmpty();
return false;
});
const updateChart = () => {
vue.nextTick(() => {
if (isEmpty.value) {
if (chart.getChartInstance()) {
chart.getChartInstance()?.clear();
}
emptyStateManager.create();
} else {
emptyStateManager.remove();
initChart(generateOptions());
}
});
};
const handleChartVisible = () => {
if (onVisible) {
onVisible();
} else {
updateChart();
}
};
const stopHandles = [];
const setupWatchers = () => {
if (watchSources.length > 0) {
const stopHandle = vue.watch(watchSources, updateChart, { deep: true });
stopHandles.push(stopHandle);
}
};
const cleanupWatchers = () => {
stopHandles.forEach((stop) => stop());
stopHandles.length = 0;
};
const setupLifecycle = () => {
vue.onMounted(() => {
updateChart();
if (chartRef.value) {
chartRef.value.addEventListener("chartVisible", handleChartVisible);
}
});
vue.onBeforeUnmount(() => {
if (chartRef.value) {
chartRef.value.removeEventListener("chartVisible", handleChartVisible);
}
cleanupWatchers();
emptyStateManager.remove();
});
};
setupWatchers();
setupLifecycle();
return {
...chart,
isEmpty,
updateChart,
handleChartVisible
};
}
exports.useChart = useChart;
exports.useChartComponent = useChartComponent;
exports.useChartOps = useChartOps;