fix(图表): 修复组件卸载时图表内存泄漏问题

添加组件挂载状态跟踪,确保在组件卸载时正确清理图表实例
移除无用注释,修正描述文字
This commit is contained in:
2025-10-14 18:58:30 +08:00
parent 73c84f7b8d
commit a6f4c96f1f
4 changed files with 43 additions and 22 deletions

View File

@@ -263,6 +263,9 @@ async function CenterGetSecondOrderAnalysisReport(time) {
// Chart.js 实例 // Chart.js 实例
const chartInstances = {}; const chartInstances = {};
// 添加组件挂载状态跟踪
const isComponentMounted = ref(true);
// DOM 元素引用 // DOM 元素引用
const personalFunnelChartCanvas = ref(null); const personalFunnelChartCanvas = ref(null);
const contactTimeChartCanvas = ref(null); const contactTimeChartCanvas = ref(null);
@@ -288,7 +291,7 @@ const kpiDescriptions = {
}, },
avgDuration: { avgDuration: {
title: '平均通话时长', title: '平均通话时长',
description: '所有通话总时长 ÷ 拨打电话次数。' description: '所有通话总时长 ÷ 拨打电话次数。'
}, },
conversionRate: { conversionRate: {
title: '成交转化率', title: '成交转化率',
@@ -327,7 +330,8 @@ const createOrUpdateChart = (chartId, canvasRef, config) => {
if (chartInstances[chartId]) { if (chartInstances[chartId]) {
chartInstances[chartId].destroy(); chartInstances[chartId].destroy();
} }
if (canvasRef.value) { // 确保组件仍然挂载且canvas引用存在
if (isComponentMounted.value && canvasRef.value) {
const ctx = canvasRef.value.getContext('2d'); const ctx = canvasRef.value.getContext('2d');
chartInstances[chartId] = new Chart(ctx, config); chartInstances[chartId] = new Chart(ctx, config);
} }
@@ -335,6 +339,9 @@ const createOrUpdateChart = (chartId, canvasRef, config) => {
// Chart.js: 渲染销售漏斗图 // Chart.js: 渲染销售漏斗图
const renderPersonalFunnelChart = () => { const renderPersonalFunnelChart = () => {
// 确保组件仍然挂载
if (!isComponentMounted.value) return;
const config = { const config = {
type: 'bar', type: 'bar',
data: { data: {
@@ -359,6 +366,9 @@ const renderPersonalFunnelChart = () => {
// Chart.js: 渲染黄金联络时段图 // Chart.js: 渲染黄金联络时段图
const renderContactTimeChart = () => { const renderContactTimeChart = () => {
// 确保组件仍然挂载
if (!isComponentMounted.value) return;
if (!props.contactTimeData || !props.contactTimeData.gold_contact_success_rate) { if (!props.contactTimeData || !props.contactTimeData.gold_contact_success_rate) {
return; return;
} }
@@ -450,11 +460,13 @@ watch(() => props.contactTimeData, () => {
// --- 生命周期钩子 --- // --- 生命周期钩子 ---
onMounted(() => { onMounted(() => {
isComponentMounted.value = true;
renderPersonalFunnelChart(); renderPersonalFunnelChart();
renderContactTimeChart(); renderContactTimeChart();
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
isComponentMounted.value = false;
Object.values(chartInstances).forEach(chart => chart.destroy()); Object.values(chartInstances).forEach(chart => chart.destroy());
}); });
</script> </script>

View File

@@ -920,19 +920,6 @@ async function CenterGetSalesFunnel() {
const res = await getSalesFunnel(hasParams ? params : undefined) const res = await getSalesFunnel(hasParams ? params : undefined)
if(res.code === 200){ if(res.code === 200){
SalesFunnel.value = res.data SalesFunnel.value = res.data
/**
* "data": {
"user_name": "常琳",
"user_level": 1,
"sale_funnel": {
"线索总数": 11,
"有效沟通": 9,
"到课数据": 8,
"预付定金": 0,
"成功签单": 0
}
}
*/
} }
} }
// 黄金联络时间段 // 黄金联络时间段

View File

@@ -27,18 +27,25 @@ const contextPanelRef = ref(null);
const sentimentChartCanvas = ref(null); const sentimentChartCanvas = ref(null);
const chartInstances = {}; const chartInstances = {};
// 添加组件挂载状态跟踪
const isComponentMounted = ref(true);
// CHARTING // CHARTING
const createOrUpdateChart = (chartId, canvasRef, config) => { const createOrUpdateChart = (chartId, canvasRef, config) => {
if (chartInstances[chartId]) { if (chartInstances[chartId]) {
chartInstances[chartId].destroy(); chartInstances[chartId].destroy();
} }
if (canvasRef.value) { // 确保组件仍然挂载且canvas引用存在
if (isComponentMounted.value && canvasRef.value) {
const ctx = canvasRef.value.getContext('2d'); const ctx = canvasRef.value.getContext('2d');
chartInstances[chartId] = new Chart(ctx, config); chartInstances[chartId] = new Chart(ctx, config);
} }
}; };
const renderSentimentChart = (history) => { const renderSentimentChart = (history) => {
// 确保组件仍然挂载
if (!isComponentMounted.value) return;
if (!sentimentChartCanvas.value) return; if (!sentimentChartCanvas.value) return;
const ctx = sentimentChartCanvas.value.getContext('2d'); const ctx = sentimentChartCanvas.value.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 0, 120); const gradient = ctx.createLinearGradient(0, 0, 0, 120);
@@ -126,6 +133,21 @@ watch(() => props.selectedContact, (newContact) => {
}); });
} }
}, { immediate: true }); }, { immediate: true });
// LIFECYCLE HOOKS
onMounted(() => {
isComponentMounted.value = true;
});
onBeforeUnmount(() => {
isComponentMounted.value = false;
// 清理所有图表实例
Object.values(chartInstances).forEach(chart => {
if (chart) {
chart.destroy();
}
});
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -28,7 +28,7 @@ const props = defineProps({
}) })
// 组件状态跟踪 // 组件状态跟踪
let isComponentMounted = true const isComponentMounted = ref(true)
// Chart.js 实例 // Chart.js 实例
const chartInstances = {} const chartInstances = {}
@@ -44,13 +44,13 @@ const funnelData = reactive({
// 监听teamSalesFunnel变化并更新图表数据 // 监听teamSalesFunnel变化并更新图表数据
watch(() => props.teamSalesFunnel, (newVal) => { watch(() => props.teamSalesFunnel, (newVal) => {
if (newVal && Object.keys(newVal).length > 0 && isComponentMounted) { if (newVal && Object.keys(newVal).length > 0 && isComponentMounted.value) {
// 按照固定顺序提取数据 // 按照固定顺序提取数据
const order = ['线索', '加微', '到课', '定金', '成交'] const order = ['线索', '加微', '到课', '定金', '成交']
funnelData.data = order.map(key => newVal[key] || 0) funnelData.data = order.map(key => newVal[key] || 0)
// 确保在DOM更新后再更新图表 // 确保在DOM更新后再更新图表
nextTick(() => { nextTick(() => {
if (isComponentMounted) { if (isComponentMounted.value) {
renderPersonalFunnelChart() renderPersonalFunnelChart()
} }
}) })
@@ -94,19 +94,19 @@ const renderPersonalFunnelChart = () => {
// 生命周期钩子 // 生命周期钩子
onMounted(() => { onMounted(() => {
isComponentMounted = true isComponentMounted.value = true
// 处理初始传入的teamSalesFunnel数据 // 处理初始传入的teamSalesFunnel数据
if (props.teamSalesFunnel && Object.keys(props.teamSalesFunnel).length > 0) { if (props.teamSalesFunnel && Object.keys(props.teamSalesFunnel).length > 0) {
const order = ['线索', '加微', '到课', '定金', '成交'] const order = ['线索', '加微', '到课', '定金', '成交']
funnelData.data = order.map(key => props.teamSalesFunnel[key] || 0) funnelData.data = order.map(key => props.teamSalesFunnel[key] || 0)
} }
if (isComponentMounted) { if (isComponentMounted.value) {
renderPersonalFunnelChart() renderPersonalFunnelChart()
} }
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
isComponentMounted = false isComponentMounted.value = false
Object.values(chartInstances).forEach(chart => chart.destroy()) Object.values(chartInstances).forEach(chart => chart.destroy())
}) })
</script> </script>