Update MarketingAnalysis.vue
This commit is contained in:
@@ -2085,18 +2085,12 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
if (!chartsData || typeof chartsData !== 'object') return [];
|
||||
|
||||
const segments = [];
|
||||
|
||||
// Нормализация: работаем либо с полем sections (новый бэк), либо с корнем (если пришло без обертки)
|
||||
const sections = chartsData.sections || chartsData;
|
||||
|
||||
// ==========================================
|
||||
// 1. НОВАЯ ЛОГИКА (V2 JSON + Новый Дизайн)
|
||||
// ==========================================
|
||||
|
||||
// --- Блок: Спрос и поведение (Фото 1: Причины отказов, Фото 6: Факторы) ---
|
||||
// --- 1. СПРОС И ПОВЕДЕНИЕ ---
|
||||
const demandSection = sections.demand_and_behavior || sections.demandAndBehavior;
|
||||
if (demandSection) {
|
||||
// 1. Причины отказов (Фото 1: Вертикальные, цифры внутри, оранжевый)
|
||||
// Причины отказов (Оранжевые, как на фото 1)
|
||||
const refusals = demandSection.reasons_for_refusal || demandSection.reasonsForRefusal;
|
||||
if (refusals) {
|
||||
const { labels, values } = parsePercentageData(refusals);
|
||||
@@ -2110,17 +2104,17 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#fb923c', // Оранжевый
|
||||
borderRadius: 6,
|
||||
borderRadius: 8,
|
||||
barPercentage: 0.7,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions, // Спец. опции: без осей, цифры внутри
|
||||
options: photoStyleBarOptions, // <-- Стиль как на фото (цифры внутри)
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Факторы принятия решения (Фото 6 -> дизайн как 1,2,3)
|
||||
// Факторы принятия решения (Сиреневые, стиль фото 6)
|
||||
const factors = demandSection.decision_factors || demandSection.decisionFactors;
|
||||
if (factors) {
|
||||
const { labels, values } = parsePercentageData(factors);
|
||||
@@ -2134,21 +2128,21 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#818cf8', // Сиреневый
|
||||
borderRadius: 6,
|
||||
borderRadius: 8,
|
||||
barPercentage: 0.7,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions,
|
||||
options: photoStyleBarOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок: Целевая аудитория (Фото 3: Возраст, Фото 5: Доход) ---
|
||||
// --- 2. ЦЕЛЕВАЯ АУДИТОРИЯ ---
|
||||
const audienceSection = sections.target_audience || sections.targetAudience;
|
||||
if (audienceSection) {
|
||||
// 3. Возрастная структура (Фото 3: Вертикальные, цифры внутри, голубой)
|
||||
// ВОЗРАСТНАЯ СТРУКТУРА (ФОТО 3 - СИНИЙ ЦВЕТ)
|
||||
const ageStruct = audienceSection.age_structure || audienceSection.ageStructure;
|
||||
if (ageStruct) {
|
||||
const { labels, values } = parsePercentageData(ageStruct);
|
||||
@@ -2161,19 +2155,19 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#60a5fa', // Голубой
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.7,
|
||||
// !!! ЦВЕТ КАК НА ФОТО (СИНИЙ) !!!
|
||||
backgroundColor: '#60a5fa',
|
||||
borderRadius: 8, // Скругление
|
||||
barPercentage: 0.65,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions,
|
||||
options: photoStyleBarOptions, // Белые цифры внутри
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Доход (Фото 5 -> Дизайн как на фото 4: Вертикальные, компактные)
|
||||
// Если данных в income_by_age нет, проверяем metrics или используем заглушку, чтобы блок не пропал
|
||||
// Доход (Фото 5 - Мятный цвет)
|
||||
const incomeData = audienceSection.income_by_age || audienceSection.incomeByAge;
|
||||
if (incomeData || audienceSection.metrics?.above_average_income_share) {
|
||||
let labels = [], values = [];
|
||||
@@ -2182,7 +2176,6 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
labels = parsed.labels;
|
||||
values = parsed.values;
|
||||
} else {
|
||||
// Если с бэка пришел null, но нужно показать структуру (фоллбэк)
|
||||
labels = ['Низкий', 'Средний', 'Высокий'];
|
||||
values = [20, 50, 30];
|
||||
}
|
||||
@@ -2196,25 +2189,25 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: '#34d399', // Мятный/Зеленый
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.7,
|
||||
backgroundColor: '#34d399', // Мятный
|
||||
borderRadius: 8,
|
||||
barPercentage: 0.65,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions, // Дизайн с цифрами внутри
|
||||
options: photoStyleBarOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок: Каналы привлечения (Фото 4) ---
|
||||
// --- 3. КАНАЛЫ (Фото 4 - Разноцветные) ---
|
||||
const acquisitionSection = sections.acquisition_channels || sections.acquisitionChannels;
|
||||
if (acquisitionSection) {
|
||||
const channelShares = acquisitionSection.channel_shares || acquisitionSection.channelShares;
|
||||
if (channelShares) {
|
||||
const { labels, values } = parsePercentageData(channelShares);
|
||||
// Фильтруем нулевые значения, чтобы не засорять график
|
||||
// Убираем нули
|
||||
const filteredIndices = values.map((v, i) => v > 0 ? i : -1).filter(i => i !== -1);
|
||||
const finalLabels = filteredIndices.map(i => labels[i]);
|
||||
const finalValues = filteredIndices.map(i => values[i]);
|
||||
@@ -2229,41 +2222,38 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
labels: finalLabels,
|
||||
datasets: [{
|
||||
data: finalValues,
|
||||
backgroundColor: getMarketingColors(finalValues.length),
|
||||
borderRadius: 6,
|
||||
barPercentage: 0.6, // Чуть тоньше, как на фото 4
|
||||
backgroundColor: getMarketingColors(finalValues.length), // Разноцветные
|
||||
borderRadius: 8,
|
||||
barPercentage: 0.6,
|
||||
unit: '%'
|
||||
}]
|
||||
},
|
||||
options: verticalBarChartInsideOptions,
|
||||
options: photoStyleBarOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок: Сезонность (Line Chart) ---
|
||||
// --- 4. СЕЗОННОСТЬ (Line) ---
|
||||
const marketSection = sections.market_overview || sections.marketOverview;
|
||||
if (marketSection) {
|
||||
const seasonalData = marketSection.monthly_online_index || marketSection.monthlyOnlineIndex;
|
||||
if (seasonalData) {
|
||||
const labels = Object.keys(seasonalData);
|
||||
const values = Object.values(seasonalData);
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'seasonality',
|
||||
title: 'Динамика спроса (Сезонность)',
|
||||
title: 'Динамика спроса',
|
||||
chartType: 'line',
|
||||
data: {
|
||||
labels,
|
||||
labels: Object.keys(seasonalData),
|
||||
datasets: [{
|
||||
label: 'Индекс спроса',
|
||||
data: values,
|
||||
label: 'Индекс',
|
||||
data: Object.values(seasonalData),
|
||||
borderColor: '#6366f1',
|
||||
backgroundColor: 'rgba(99, 102, 241, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 4
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: seasonalityChartOptions,
|
||||
@@ -2272,228 +2262,19 @@ const mapChartsDataToSegments = (chartsData) => {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. СТАРАЯ ЛОГИКА (LEGACY SUPPORT) - БЕЗ ИЗМЕНЕНИЙ
|
||||
// ==========================================
|
||||
|
||||
// Handle case when chartsData is an array
|
||||
// --- LEGACY (Старый код, просто чтобы не ломалось, если придет старый JSON) ---
|
||||
if (Array.isArray(chartsData)) {
|
||||
if (chartsData.length > 0 && chartsData[0]?.name && (chartsData[0]?.channelsHeatmap || chartsData[0]?.reach || chartsData[0]?.followers)) {
|
||||
const reachChartData = buildCompetitorsBarChartData(chartsData, 'reach');
|
||||
const followersChartData = buildCompetitorsBarChartData(chartsData, 'followers');
|
||||
segments.push({
|
||||
type: 'competitors',
|
||||
competitors: chartsData,
|
||||
competitorsHeatmap: buildCompetitorsHeatmapData(chartsData),
|
||||
reachChart: reachChartData ? { chartType: 'bar', data: reachChartData, options: competitorsBarChartOptions, title: 'Охват конкурентов' } : null,
|
||||
followersChart: followersChartData ? { chartType: 'bar', data: followersChartData, options: competitorsBarChartOptions, title: 'Подписчики конкурентов' } : null
|
||||
});
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
// Handle case when chartsData is an audienceSegmentation object directly
|
||||
if (chartsData.genders || chartsData.ageGroups || chartsData.segments) {
|
||||
const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(chartsData);
|
||||
if (ageGroupsChartData || gendersChartData) {
|
||||
segments.push({
|
||||
type: 'audienceSegmentation',
|
||||
ageGroupsChartData,
|
||||
gendersChartData,
|
||||
segmentsChannelMatrix: buildSegmentsChannelMatrix(chartsData.segmentsChannelMatrix),
|
||||
keyTakeaways: chartsData.keyTakeaways || []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Audience Segmentation (nested property)
|
||||
if (chartsData.audienceSegmentation) {
|
||||
const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(chartsData.audienceSegmentation);
|
||||
if (ageGroupsChartData || gendersChartData) {
|
||||
segments.push({
|
||||
type: 'audienceSegmentation',
|
||||
ageGroupsChartData,
|
||||
gendersChartData,
|
||||
segmentsChannelMatrix: buildSegmentsChannelMatrix(chartsData.audienceSegmentation.segmentsChannelMatrix),
|
||||
keyTakeaways: chartsData.audienceSegmentation.keyTakeaways || []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Market Share Chart (in root of chartsData)
|
||||
if (chartsData.marketShareChart) {
|
||||
const marketShareChartData = Array.isArray(chartsData.marketShareChart) ? buildMarketShareChartData(chartsData.marketShareChart) : normalizeChartJsData(chartsData.marketShareChart, 'pie');
|
||||
if (marketShareChartData) {
|
||||
segments.push({
|
||||
type: 'marketShareChart',
|
||||
title: 'Доли рынка',
|
||||
chartType: 'pie',
|
||||
data: marketShareChartData,
|
||||
options: pieChartOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Competitors Analysis
|
||||
if (chartsData.competitors) {
|
||||
let competitorsArray = null;
|
||||
if (Array.isArray(chartsData.competitors)) {
|
||||
competitorsArray = chartsData.competitors;
|
||||
} else if (chartsData.competitors.competitors && Array.isArray(chartsData.competitors.competitors)) {
|
||||
competitorsArray = chartsData.competitors.competitors;
|
||||
}
|
||||
|
||||
if (competitorsArray && competitorsArray.length > 0) {
|
||||
const reachChartData = buildCompetitorsBarChartData(competitorsArray, 'reach');
|
||||
const followersChartData = buildCompetitorsBarChartData(competitorsArray, 'followers');
|
||||
segments.push({
|
||||
type: 'competitors',
|
||||
competitors: competitorsArray,
|
||||
competitorsHeatmap: buildCompetitorsHeatmapData(competitorsArray),
|
||||
reachChart: reachChartData ? { chartType: 'bar', data: reachChartData, options: competitorsBarChartOptions, title: 'Охват конкурентов' } : null,
|
||||
followersChart: followersChartData ? { chartType: 'bar', data: followersChartData, options: competitorsBarChartOptions, title: 'Подписчики конкурентов' } : null
|
||||
});
|
||||
}
|
||||
if (chartsData.competitors.swotCompetitors && typeof chartsData.competitors.swotCompetitors === 'object') {
|
||||
segments.push({
|
||||
type: 'swotCompetitors',
|
||||
data: {
|
||||
strengths: chartsData.competitors.swotCompetitors.strengths || [],
|
||||
weaknesses: chartsData.competitors.swotCompetitors.weaknesses || []
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Comparison Table
|
||||
if (chartsData.comparisonTable && Array.isArray(chartsData.comparisonTable) && chartsData.comparisonTable.length > 0) {
|
||||
segments.push({
|
||||
type: 'comparisonTable',
|
||||
data: chartsData.comparisonTable
|
||||
});
|
||||
}
|
||||
|
||||
// 5. SWOT Competitors
|
||||
if (chartsData.swotCompetitors && typeof chartsData.swotCompetitors === 'object') {
|
||||
segments.push({
|
||||
type: 'swotCompetitors',
|
||||
data: {
|
||||
strengths: chartsData.swotCompetitors.strengths || [],
|
||||
weaknesses: chartsData.swotCompetitors.weaknesses || []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 6. Seasonality (Old structure)
|
||||
if (chartsData.seasonality && typeof chartsData.seasonality === 'object') {
|
||||
// Если мы уже добавили сезонность в V2 логике, не дублируем
|
||||
const alreadyAdded = segments.some(s => s.key === 'seasonality');
|
||||
if (!alreadyAdded) {
|
||||
const chartData = buildSeasonalityChartData(chartsData.seasonality);
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'seasonality',
|
||||
title: 'Сезонность спроса',
|
||||
chartType: 'line',
|
||||
data: chartData,
|
||||
options: seasonalityChartOptions,
|
||||
chartHeight: 'height: 300px'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Market
|
||||
if (chartsData.market && typeof chartsData.market === 'object') {
|
||||
segments.push({
|
||||
type: 'market',
|
||||
data: chartsData.market
|
||||
});
|
||||
}
|
||||
|
||||
// 8. Conversion Funnel
|
||||
if (chartsData.conversionFunnel) {
|
||||
const chartData = isChartJsData(chartsData.conversionFunnel) ? normalizeChartJsData(chartsData.conversionFunnel, 'bar') : buildConversionFunnelChartData(chartsData.conversionFunnel);
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
if (chartsData.length > 0 && chartsData[0]?.name) {
|
||||
const reachData = chartsData.map(c => c.reach || 0);
|
||||
return [{
|
||||
type: 'chart',
|
||||
key: 'funnel',
|
||||
title: 'Воронка конверсии',
|
||||
title: 'Охват конкурентов',
|
||||
chartType: 'bar',
|
||||
data: chartData,
|
||||
options: conversionFunnelChartOptions,
|
||||
chartHeight: 'height: 400px'
|
||||
});
|
||||
data: { labels: chartsData.map(c => c.name), datasets: [{ data: reachData, backgroundColor: '#60a5fa' }] },
|
||||
options: photoStyleBarOptions
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Channels Potential (Legacy)
|
||||
if (chartsData.channelsPotential) {
|
||||
// Если V2 уже добавил каналы, пропускаем
|
||||
const alreadyAdded = segments.some(s => s.key === 'acquisitionChannels');
|
||||
if (!alreadyAdded) {
|
||||
const chartData = isChartJsData(chartsData.channelsPotential) ? normalizeChartJsData(chartsData.channelsPotential, 'bar') : buildChannelsPotentialChartData(chartsData.channelsPotential);
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'channelsPotential',
|
||||
title: 'Потенциал каналов продвижения',
|
||||
chartType: 'bar',
|
||||
data: chartData,
|
||||
options: channelsPotentialChartOptions,
|
||||
chartHeight: 'height: 300px',
|
||||
channels: Array.isArray(chartsData.channelsPotential) ? chartsData.channelsPotential : []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Audience charts (Legacy)
|
||||
if (chartsData.audienceAge) {
|
||||
const alreadyAdded = segments.some(s => s.key === 'ageStructure');
|
||||
if (!alreadyAdded) {
|
||||
const chartData = normalizeChartJsData(chartsData.audienceAge, 'bar');
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'audienceAge',
|
||||
title: 'Распределение по возрасту',
|
||||
chartType: 'bar',
|
||||
data: chartData,
|
||||
options: competitorsBarChartOptions,
|
||||
chartHeight: 'height: 350px'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chartsData.audienceGender) {
|
||||
const chartData = normalizeChartJsData(chartsData.audienceGender, 'pie');
|
||||
if (chartData) {
|
||||
segments.push({
|
||||
type: 'chart',
|
||||
key: 'audienceGender',
|
||||
title: 'Распределение по полу',
|
||||
chartType: 'pie',
|
||||
data: chartData,
|
||||
options: pieChartOptions,
|
||||
chartHeight: 'height: 350px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 10. SWOT
|
||||
if (chartsData.swot && typeof chartsData.swot === 'object') {
|
||||
segments.push({
|
||||
type: 'swot',
|
||||
data: {
|
||||
strengths: chartsData.swot.strengths || [],
|
||||
weaknesses: chartsData.swot.weaknesses || [],
|
||||
opportunities: chartsData.swot.opportunities || [],
|
||||
threats: chartsData.swot.threats || []
|
||||
}
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
return segments;
|
||||
|
||||
Reference in New Issue
Block a user