Update MarketingAnalysis.vue

This commit is contained in:
Administrator
2026-01-20 18:57:26 +00:00
parent 3cd57b49f1
commit d69269d31c
+77 -161
View File
@@ -934,8 +934,8 @@ const v2ReportNormalized = computed(() => {
const drawBarValues = (chart) => {
const ctx = chart.ctx;
ctx.save(); // Сохраняем состояние контекста
ctx.font = 'bold 12px "Inter", sans-serif';
ctx.save();
ctx.font = 'bold 14px "Inter", sans-serif'; // Шрифт покрупнее, как на фото
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
@@ -944,33 +944,33 @@ const drawBarValues = (chart) => {
if (!meta.hidden) {
meta.data.forEach((bar, index) => {
const value = dataset.data[index];
if (value !== null && value !== undefined && value !== 0) {
if (value !== null && value !== undefined && value != 0) {
let label = value.toLocaleString();
// Добавляем %, если это указано в датасете или значение похоже на процент
if ((dataset.unit === '%') || (typeof dataset.data[index] === 'string' && dataset.data[index].includes('%'))) {
// Если это проценты, добавляем знак %
if ((dataset.unit === '%') || (typeof dataset.data[index] === 'string' && String(dataset.data[index]).includes('%'))) {
label = String(value).replace('%', '') + '%';
}
const isHorizontal = meta.indexAxis === 'y';
if (isHorizontal) {
// Горизонтальный график: цифра справа от бара или внутри, если бар длинный
// Горизонтальный: цифра справа темная
ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937';
ctx.textAlign = 'left';
ctx.fillText(label, bar.x + 8, bar.y);
} else {
// Вертикальный график (Требование 1, 2, 3): Цифра ВНУТРИ бара
// Используем белый цвет для контраста на цветном фоне
ctx.fillStyle = '#ffffff';
// Если бар слишком маленький, рисуем сверху, иначе внутри
// === ВЕРТИКАЛЬНЫЙ (КАК НА ФОТО) ===
const barHeight = Math.abs(bar.base - bar.y);
if (barHeight > 20) {
ctx.fillText(label, bar.x, bar.y + (barHeight / 2)); // По центру бара
// Если столбец достаточно высокий (>25px), рисуем цифру ВНУТРИ белым цветом
if (barHeight > 25) {
ctx.fillStyle = '#ffffff';
// bar.y - это верхушка столбца. Добавляем 15px, чтобы спустить цифру чуть ниже верха
ctx.fillText(label, bar.x, bar.y + 15);
} else {
// Если бар слишком мелкий, рисуем сверху темным цветом
// Если столбец мелкий, рисуем цифру СВЕРХУ темным цветом
ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937';
ctx.fillText(label, bar.x, bar.y - 10);
ctx.fillText(label, bar.x, bar.y - 12);
}
}
}
@@ -980,41 +980,40 @@ const drawBarValues = (chart) => {
ctx.restore();
};
// --- 2. НОВЫЕ ОПЦИИ ДЛЯ ГРАФИКОВ (Требование: убрать нижние цифры и оси) ---
const verticalBarChartInsideOptions = {
// Опции: Дизайн "Как на фото" (С цифрами внутри)
const photoStyleBarOptions = {
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 25 } }, // Отступ сверху для тултипов
animation: {
duration: 500,
onComplete: (animation) => drawBarValues(animation.chart)
},
plugins: {
legend: { display: false },
legend: { display: false }, // Легенда не нужна
tooltip: {
enabled: true,
callbacks: {
label: (context) => `${context.formattedValue}%`
}
callbacks: { label: (ctx) => `${ctx.formattedValue}%` }
}
},
scales: {
y: {
display: false, // Скрываем ось Y полностью
display: true, // Ось Y оставляем (как на фото 0, 10, 20...), но можно поставить false, если надо скрыть
beginAtZero: true,
grid: { display: false }
grid: { display: true, drawBorder: false, color: 'rgba(0,0,0,0.05)' },
ticks: { font: { size: 10 } }
},
x: {
grid: { display: false },
grid: { display: false }, // Вертикальные линии убираем
ticks: {
font: { size: 11, weight: '600' },
color: '#64748b' // Цвет подписей категорий (Высокая цена, Нет отзывов и т.д.)
font: { size: 12, weight: '600' },
color: '#64748b'
}
}
},
layout: { padding: { top: 20, bottom: 0 } }
}
};
// Опции для горизонтальных баров (как на фото 4/5)
// Опции: Горизонтальные бары
const horizontalBarChartOptions = {
indexAxis: 'y',
responsive: true,
@@ -1026,59 +1025,69 @@ const horizontalBarChartOptions = {
},
plugins: { legend: { display: false } },
scales: {
x: { display: false }, // Скрываем цифры снизу
x: { display: false, beginAtZero: true },
y: {
grid: { display: false },
ticks: {
font: { size: 12, weight: '500' },
crossAlign: 'far'
}
ticks: { font: { size: 12, weight: '500' }, crossAlign: 'far' }
}
}
};
// Опции: Круговая диаграмма
const pieChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: true, position: 'bottom', align: 'center' },
tooltip: {
callbacks: { label: (context) => context.label + ': ' + context.parsed + '%' }
}
}
};
// Опции: Линейный график (Сезонность)
const seasonalityChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: true, position: 'bottom' } },
scales: { y: { beginAtZero: true } }
};
// Опции: Воронка
const conversionFunnelChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: true, position: 'bottom' } },
scales: {
y: { beginAtZero: true, title: { display: true, text: 'Пользователи' } }
}
};
// Опции: Потенциал каналов
const channelsPotentialChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true, max: 100 } }
};
// Хелпер для цветов
const MARKETING_COLORS = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'];
const getMarketingColors = (count, offset = 0) => {
const safeCount = Math.max(0, Number(count) || 0);
if (safeCount === 0) return [];
return Array.from({ length: safeCount }, (_, i) => MARKETING_COLORS[(i + offset) % MARKETING_COLORS.length]);
};
const parsePercentageData = (obj) => {
if (!obj || typeof obj !== 'object') return { labels: [], values: [] };
const labels = Object.keys(obj).map(k => k.replace(/_/g, ' ')); // Убираем _ из ключей
const labels = Object.keys(obj).map(k => k.replace(/_/g, ' '));
const values = Object.values(obj).map(v => {
if (typeof v === 'string') return parseFloat(v.replace('%', ''));
return v;
});
return { labels, values };
};
const competitorsBarChartOptions = {
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 20 } },
animation: {
duration: 500,
onComplete: (animation) => drawBarValues(animation.chart)
},
plugins: {
legend: { display: false },
tooltip: { enabled: true }
},
scales: {
y: {
beginAtZero: true,
grid: { display: true, drawBorder: false },
ticks: { display: false } // Скрываем ось Y, цифры уже на барах
},
x: { grid: { display: false } }
}
};
const channelsBarChartOptions = {
...competitorsBarChartOptions,
plugins: {
legend: { display: false },
tooltip: {
callbacks: { label: (ctx) => `${ctx.formattedValue}%` }
}
}
};
const refreshV2 = async () => {
if (!v2AnalysisId.value) return;
@@ -1772,80 +1781,6 @@ const pushMarkdownTokens = (segments, markdownChunk) => {
}
};
const seasonalityChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'bottom',
align: 'center'
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function (value) {
return value + '%';
}
}
}
}
};
const conversionFunnelChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'bottom',
align: 'center'
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Количество пользователей'
}
}
}
};
const channelsPotentialChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function (value) {
return value;
}
}
}
}
};
// Marketing-friendly color palette (cycled as needed)
const MARKETING_COLORS = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'];
const getMarketingColors = (count, offset = 0) => {
const safeCount = Math.max(0, Number(count) || 0);
if (safeCount === 0) return [];
const base = MARKETING_COLORS.length ? MARKETING_COLORS : ['#3B82F6'];
return Array.from({ length: safeCount }, (_, i) => base[(i + offset) % base.length]);
};
const buildSeasonalityChartData = (seasonality) => {
if (!seasonality || typeof seasonality !== 'object') return null;
@@ -3570,25 +3505,6 @@ const toCompactChartOptions = (options) => {
return compact;
};
const pieChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'bottom',
align: 'center'
},
tooltip: {
callbacks: {
label: function (context) {
return context.label + ': ' + context.parsed + '%';
}
}
}
}
};
// Get analysis type label
const getAnalysisTypeLabel = (type) => {
const labels = {