diff --git a/src/views/pages/MarketingAnalysis.vue b/src/views/pages/MarketingAnalysis.vue index 542fe95..a2b5572 100644 --- a/src/views/pages/MarketingAnalysis.vue +++ b/src/views/pages/MarketingAnalysis.vue @@ -266,39 +266,9 @@ /> -
-
-
-
Сегменты аудитории для рекламы
- -
- -
-
-
Сегмент
-
Доля
-
-
-
-
-
{{ seg.name }}
-
{{ seg.value }}%
-
-
-
-
-
-
-
-
-
- -
+
-
+
Возрастная структура
@@ -312,7 +282,7 @@
-
+
Доход и платёжеспособность
@@ -481,20 +451,6 @@
-
-
-
-
Тип запроса
-
Доля
-
-
-
-
{{ row.label }}
-
{{ row.value }}%
-
-
-
-
@@ -511,20 +467,6 @@
-
-
-
-
Фактор
-
Доля
-
-
-
-
{{ row.label }}
-
{{ row.value }}%
-
-
-
-
@@ -539,20 +481,6 @@
-
-
-
-
Причина
-
Доля
-
-
-
-
{{ row.label }}
-
{{ row.value }}%
-
-
-
-
@@ -677,7 +605,7 @@ /> -
+
Доли каналов
@@ -691,31 +619,6 @@
-
-
-
-
Стоимость привлечения
- -
-
-
-
Канал
-
Стоимость
-
Эффективность
-
-
-
-
{{ row.label }}
-
{{ row.cost }}
-
{{ row.status }}
-
-
-
-
-
@@ -1165,6 +1068,8 @@ function resetDashboard() { const PPT_BLUE = '#5B89FF'; const PPT_ORANGE = '#FF8E4F'; const PPT_GREEN = '#52C41A'; +const PPT_GRADIENT_BLUE_START = '#5B89FF'; +const PPT_GRADIENT_BLUE_END = '#82A7FF'; const roiValue = computed(() => report.value?.sections?.marketing_economics?.metrics?.roi ?? '—'); const cacValue = computed(() => report.value?.sections?.marketing_economics?.metrics?.customer_acquisition_cost_cac ?? '—'); @@ -1386,10 +1291,12 @@ const compactBarOptions = computed(() => ({ const barWidth = Math.abs(bar.x - bar.base); if (barWidth > 40) { ctx.fillStyle = '#ffffff'; + ctx.font = 'bold 12px Inter, system-ui, sans-serif'; ctx.textAlign = 'right'; - ctx.fillText(label, bar.x - 10, bar.y); + ctx.fillText(label, bar.x - 12, bar.y); } else { ctx.fillStyle = '#64748b'; + ctx.font = 'bold 11px Inter, system-ui, sans-serif'; ctx.textAlign = 'left'; ctx.fillText(label, bar.x + 10, bar.y); } @@ -1398,10 +1305,12 @@ const compactBarOptions = computed(() => ({ ctx.textAlign = 'center'; if (barHeight > 25) { ctx.fillStyle = '#ffffff'; - ctx.fillText(label, bar.x, bar.y + 14); + ctx.font = 'bold 14px Inter, system-ui, sans-serif'; + ctx.fillText(label, bar.x, bar.y + 16); } else { ctx.fillStyle = '#64748b'; - ctx.fillText(label, bar.x, bar.y - 10); + ctx.font = 'bold 12px Inter, system-ui, sans-serif'; + ctx.fillText(label, bar.x, bar.y - 12); } } } @@ -1509,8 +1418,8 @@ const ageBarData = computed(() => ({ backgroundColor: (context) => { const { chart } = context; const { ctx, chartArea } = chart; - if (!chartArea) return 'rgba(91,137,255,0.55)'; - return makeVerticalGradient(ctx, chartArea, 'rgba(91,137,255,0.85)', 'rgba(91,137,255,0.25)'); + if (!chartArea) return PPT_BLUE; + return makeVerticalGradient(ctx, chartArea, PPT_GRADIENT_BLUE_START, PPT_GRADIENT_BLUE_END); } } ] @@ -1576,8 +1485,8 @@ const incomeBarData = computed(() => ({ backgroundColor: (context) => { const { chart } = context; const { ctx, chartArea } = chart; - if (!chartArea) return 'rgba(82,196,26,0.55)'; - return makeVerticalGradient(ctx, chartArea, 'rgba(82,196,26,0.80)', 'rgba(82,196,26,0.25)'); + if (!chartArea) return PPT_GREEN; + return makeVerticalGradient(ctx, chartArea, PPT_GREEN, '#73D13D'); } } ] @@ -1832,8 +1741,8 @@ const decisionFactorsBarData = computed(() => { backgroundColor: (context) => { const { chart } = context; const { ctx, chartArea } = chart; - if (!chartArea) return 'rgba(91,137,255,0.55)'; - return makeVerticalGradient(ctx, chartArea, 'rgba(91,137,255,0.85)', 'rgba(91,137,255,0.25)'); + if (!chartArea) return PPT_BLUE; + return makeVerticalGradient(ctx, chartArea, PPT_GRADIENT_BLUE_START, PPT_GRADIENT_BLUE_END); } } ] @@ -1866,8 +1775,8 @@ const refusalReasonsBarData = computed(() => { backgroundColor: (context) => { const { chart } = context; const { ctx, chartArea } = chart; - if (!chartArea) return 'rgba(255,142,79,0.55)'; - return makeVerticalGradient(ctx, chartArea, 'rgba(255,142,79,0.85)', 'rgba(255,142,79,0.25)'); + if (!chartArea) return PPT_ORANGE; + return makeVerticalGradient(ctx, chartArea, PPT_ORANGE, '#FFBB96'); } } ] diff --git a/src/views/pages/marketing/MarketingAnalysis.vue b/src/views/pages/marketing/MarketingAnalysis.vue index 4dc2a43..9846fbb 100644 --- a/src/views/pages/marketing/MarketingAnalysis.vue +++ b/src/views/pages/marketing/MarketingAnalysis.vue @@ -957,16 +957,16 @@ const drawBarValues = (chart) => { // Горизонтальный: цифра справа темная ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937'; ctx.textAlign = 'left'; - ctx.fillText(label, bar.x + 8, bar.y); + ctx.fillText(label, bar.x + 8, bar.y); } else { // === ВЕРТИКАЛЬНЫЙ (КАК НА ФОТО) === const barHeight = Math.abs(bar.base - bar.y); - + // Если столбец достаточно высокий (>25px), рисуем цифру ВНУТРИ белым цветом if (barHeight > 25) { - ctx.fillStyle = '#ffffff'; + ctx.fillStyle = '#ffffff'; // bar.y - это верхушка столбца. Добавляем 15px, чтобы спустить цифру чуть ниже верха - ctx.fillText(label, bar.x, bar.y + 15); + ctx.fillText(label, bar.x, bar.y + 15); } else { // Если столбец мелкий, рисуем цифру СВЕРХУ темным цветом ctx.fillStyle = document.documentElement.classList.contains('dark') ? '#ffffff' : '#1f2937'; @@ -991,7 +991,7 @@ const photoStyleBarOptions = { }, plugins: { legend: { display: false }, // Легенда не нужна - tooltip: { + tooltip: { enabled: true, callbacks: { label: (ctx) => `${ctx.formattedValue}%` } } @@ -1003,7 +1003,7 @@ const photoStyleBarOptions = { grid: { display: true, drawBorder: false, color: 'rgba(0,0,0,0.05)' }, ticks: { font: { size: 10 } } }, - x: { + x: { grid: { display: false }, // Вертикальные линии убираем ticks: { font: { size: 12, weight: '600' }, @@ -1025,8 +1025,8 @@ const horizontalBarChartOptions = { }, plugins: { legend: { display: false } }, scales: { - x: { display: false, beginAtZero: true }, - y: { + x: { display: false, beginAtZero: true }, + y: { grid: { display: false }, ticks: { font: { size: 12, weight: '500' }, crossAlign: 'far' } } @@ -1058,8 +1058,8 @@ const conversionFunnelChartOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'bottom' } }, - scales: { - y: { beginAtZero: true, title: { display: true, text: 'Пользователи' } } + scales: { + y: { beginAtZero: true, title: { display: true, text: 'Пользователи' } } } }; @@ -1996,12 +1996,12 @@ const buildChannelsChartData = (acquisitionChannels) => { if (!shares || typeof shares !== 'object') return null; // Превращаем ключи "instagram_target" в "Instagram Target" - const labels = Object.keys(shares).map(key => + const labels = Object.keys(shares).map(key => key.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ') ); // Чистим значения от знака % для графика - const values = Object.values(shares).map(val => + const values = Object.values(shares).map(val => (typeof val === 'string') ? parseFloat(val.replace('%', '')) : val ); @@ -2149,16 +2149,16 @@ const mapChartsDataToSegments = (chartsData) => { segments.push({ type: 'chart', key: 'ageStructure', - title: 'АРЫСТАНБЕК', + title: 'Возрастная структура', chartType: 'bar', data: { labels, datasets: [{ data: values, - // !!! ЦВЕТ КАК НА ФОТО (СИНИЙ) !!! - backgroundColor: '#60a5fa', + // !!! ЦВЕТ КАК НА ФОТО (СИНИЙ) !!! + backgroundColor: '#60a5fa', borderRadius: 8, // Скругление - barPercentage: 0.65, + barPercentage: 0.65, unit: '%' }] }, @@ -2177,7 +2177,7 @@ const mapChartsDataToSegments = (chartsData) => { values = parsed.values; } else { labels = ['Низкий', 'Средний', 'Высокий']; - values = [20, 50, 30]; + values = [20, 50, 30]; } segments.push({