You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/test/promotion.vue

703 lines
28 KiB
Vue

4 months ago
<!--
* @Author: qinzhenpen qzp1807@126.com
* @Date: 2025-04-30 16:34:04
* @LastEditors: qinzhenpen qzp1807@126.com
* @LastEditTime: 2025-05-16 17:32:57
* @FilePath: \e-commerce-internet\src\views\test\promotion.vue
* @Description: A/B testing component with ECharts boxplots using rawData1 directly
-->
<template>
<div class="promotion app-container">
<div class="handren">
<div class="left">
<el-button>A/B测试</el-button>
</div>
<div class="right">
<el-button @click="dialogVisible = true" type="primary" id="brtn">任务背景与目标</el-button>
<popModel :showModel="dialogVisible" title="任务背景:" @closePop="dialogVisible = false">
<template v-slot:content>
<p style="font-size: 1rem">
电商产品推广测试是电商运营中的重要环节,它有助于商家了解产品在市场上的表现,并据此优化推广策略。
<br />
<br />
测试目的
<br />
‌评估产品市场潜力‌:通过推广测试,商家可以评估产品在目标市场中的受欢迎程度,以及潜在的销售潜力。
<br />
‌优化推广策略‌:根据测试数据,商家可以调整推广方式、渠道选择、广告文案等,以提高推广效果。
<br />
‌提升用户体验‌:通过测试用户对产品的反应,商家可以发现并改进产品中的不足之处,从而提升用户体验。
<br />
<br />
任务目标:
<br />
<br />
使用A/B测试法,根据曝光量、点击量、转化量、广告费用、订单金额等数据,评估A版广告和B版广告优劣,根据分析结果进行问题定位,制定相应对策。
</p>
</template>
</popModel>
</div>
</div>
<section class="intro">
<p>在电商产品推广测试中,A/B测试(也称为分裂测试)是一种科学的方法,用于比较两个或多个版本的广告、网页或其他营销元素,以确定哪个版本在给定目标方面表现更好。</p>
<p>
<strong>A/B测试的步骤:</strong>
<br />
- 明确目标:确定A/B测试的目标,例如提升点击率、转化率或投资回报率。
<br />
- 选择变量:选择需要测试的变量,例如广告文案、图片、按钮颜色等。
<br />
- 创建实验组和对照组:将用户随机分配到实验组和对照组,确保样本量足够大。
<br />
- 施加影响:对实验组施加影响(如不同的广告创意),而对照组保持不变。
<br />
- 收集数据:收集实验组和对照组的数据,包括点击率、转化率、停留时间等指标。
<br />
- 分析数据:对收集的数据进行分析,以确定新运营策略是否优于旧运营策略。
<br />
- 优化策略:根据分析结果,优化运营策略,以提升运营效果。
</p>
</section>
<section class="case-study">
<div class="case-backtitle">
<img src="@/assets/images/步骤.png" alt="实验实训图标" />
<span>实验实训</span>
</div>
<p>假设京东某电子产品店铺进行新产品推广测试,设计以下A、B两个版本广告,5个月时间两个版本广告的点击量、曝光量、转化量、订单金额和广告费用数据如下所示:</p>
<el-table :data="rawData" border class="data-table">
<el-table-column prop="version" label="广告版本" align="center" />
<el-table-column prop="clicks" label="点击量" align="center" />
<el-table-column prop="exposure" label="曝光量" align="center" />
<el-table-column prop="conversions" label="转化量" align="center" />
<el-table-column prop="orderAmount" label="订单金额" align="center" />
<el-table-column prop="adCost" label="广告费用" align="center" />
</el-table>
</section>
<section class="analysis">
<h3>数据分析与可视化</h3>
<p class="formulas">
<span>点击率 = 点击量 / 曝光量</span>
<span>转化率 = 转化量 / 点击量</span>
<span>投资回报率 = (订单金额 - 广告费用) / 广告费用</span>
</p>
<p>请根据上述公式计算A、B两个版本的点击率、转化率、投资回报率指标数据(计算结果保留4位小数),根据计算结果绘制点击率对比、转化率对比、投资回报率对比、订单金额对比箱线图</p>
<el-table :data="rawData1" border class="data-table">
<el-table-column prop="version" label="广告版本" align="center" />
<el-table-column prop="clicks" label="点击率" align="center">
<template #default="{ row }">
<el-input v-model.number="row.clicks" placeholder="请输入点击率" />
</template>
</el-table-column>
<el-table-column prop="exposure" label="转化率" align="center">
<template #default="{ row }">
<el-input v-model.number="row.exposure" placeholder="请输入转化率" />
</template>
</el-table-column>
<el-table-column prop="conversions" label="投资回报率" align="center">
<template #default="{ row }">
<el-input v-model.number="row.conversions" placeholder="请输入投资回报率" />
</template>
</el-table-column>
<el-table-column prop="orderAmount" label="订单金额" align="center" />
</el-table>
<el-button @click="initializeChart">点击绘制箱线图</el-button>
<el-button @click="runResultShow = true">查看代码</el-button>
<el-button @click="go">跳转到Python在线编程环境</el-button>
<div ref="chartContainer" class="chart-container"></div>
</section>
<section class="statistical-test">
<h3>统计显著性检验(保留4位小数):</h3>
<el-table :data="testResults" border class="data-table" style="width: 50%">
<el-table-column prop="testName" label="t检验结果" align="center" />
<el-table-column prop="statistic" label="t统计量" align="center">
<template #default="{ row }">
<el-input v-model="row.statistic" placeholder="请输入t统计量" />
</template>
</el-table-column>
<el-table-column prop="pValue" label="p值" align="center">
<template #default="{ row }">
<el-input v-model="row.pValue" placeholder="请输入p值" />
</template>
</el-table-column>
</el-table>
</section>
<section class="results">
<h3>A/B测试结果分析</h3>
<el-input v-model="analysisText" :autosize="{ minRows: 11, maxRows: 11 }" type="textarea" placeholder="请输入A/B测试结果分析" />
</section>
<div class="taskfooter">
<el-button @click="submitTask">提交任务</el-button>
<el-button @click="resetTask">重新实训</el-button>
</div>
</div>
<pop-model :showModel="runResultShow" title="设置代码" @closePop="closeCode">
<template v-slot:content>
<div>
<codemirror :code="code"></codemirror>
</div>
</template>
</pop-model>
</template>
<script setup >
import * as analysisAPI from "@/api/market-demand-mining.js";
import * as echarts from "echarts";
import codemirror from "@/components/codemirror/index.vue";
import popModel from "@/views/components/popModal.vue";
const { proxy } = getCurrentInstance();
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
import jStat from "jstat";
import { ref } from "vue";
const runResultShow = ref(false);
const dialogVisible = ref(false);
// 图表显示隐藏
const code = ref(
`
import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
import seaborn as sns
# 模拟数据
data = {
'广告版本': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'],
'点击量': [120, 150, 130, 140, 160, 180, 200, 190, 210, 220],
'曝光量': [1000, 1200, 1100, 1300, 1400, 1500, 1600, 1700, 1800, 2000],
'转化量': [20, 25, 22, 24, 28, 30, 35, 32, 38, 40],
'订单金额': [2000, 2500, 2200, 2400, 2800, 3000, 3500, 3200, 3800, 4000],
'广告费用': [500, 600, 550, 650, 700, 750, 800, 780, 850, 900]
}
df = pd.DataFrame(data)
# 计算关键指标
df['点击率'] = df['点击量'] / df['曝光量']
df['转化率'] = df['转化量'] / df['点击量']
df['投资回报率'] = (df['订单金额'] - df['广告费用']) / df['广告费用']
# 数据可视化
plt.figure(figsize=(12, 8))
# 点击率
plt.subplot(2, 2, 1)
sns.boxplot(x='广告版本', y='点击率', data=df)
plt.title('点击率对比')
# 转化率
plt.subplot(2, 2, 2)
sns.boxplot(x='广告版本', y='转化率', data=df)
plt.title('转化率对比')
# 投资回报率
plt.subplot(2, 2, 3)
sns.boxplot(x='广告版本', y='投资回报率', data=df)
plt.title('投资回报率对比')
# 订单金额
plt.subplot(2, 2, 4)
sns.boxplot(x='广告版本', y='订单金额', data=df)
plt.title('订单金额对比')
plt.tight_layout()
plt.show()
# 提取A组和B组数据
a_group = df[df['广告版本'] == 'A']
b_group = df[df['广告版本'] == 'B']
# 点击率的t检验
t_stat_click, p_val_click = stats.ttest_ind(a_group['点击率'], b_group['点击率'])
print(f"点击率t检验结果: t统计量={t_stat_click}, p值={p_val_click}")
# 转化率的t检验
t_stat_conversion, p_val_conversion = stats.ttest_ind(a_group['转化率'], b_group['转化率'])
print(f"转化率t检验结果: t统计量={t_stat_conversion}, p值={p_val_conversion}")
# 投资回报率的t检验
t_stat_roi, p_val_roi = stats.ttest_ind(a_group['投资回报率'], b_group['投资回报率'])
print(f"投资回报率t检验结果: t统计量={t_stat_roi}, p值={p_val_roi}")
# 订单金额的t检验
t_stat_revenue, p_val_revenue = stats.ttest_ind(a_group['订单金额'], b_group['订单金额'])
print(f"订单金额t检验结果: t统计量={t_stat_revenue}, p值={p_val_revenue}")
# 设置显著性水平
alpha = 0.05
# 评估结果
def evaluate_result(p_value, metric):
if p_value < alpha:
print(f"{metric}差异显著,B组效果优于A组。")
else:
print(f"{metric}差异不显著,无法确定B组效果优于A组。")
evaluate_result(p_val_click, "点击率")
evaluate_result(p_val_conversion, "转化率")
evaluate_result(p_val_roi, "投资回报率")
evaluate_result(p_val_revenue, "订单金额")
# 基于结果优化策略
if p_val_click < alpha and p_val_conversion < alpha and p_val_roi < alpha:
print("B组在点击率、转化率和投资回报率方面均优于A组,建议全面推广B组广告版本。")
elif p_val_click < alpha:
print("B组在点击率方面优于A组,建议优化广告文案或图片以提高点击率。")
elif p_val_conversion < alpha:
print("B组在转化率方面优于A组,建议优化产品页面或购买流程以提高转化率。")
elif p_val_roi < alpha:
print("B组在投资回报率方面优于A组,建议调整广告预算分配以提高投资回报率。")
else:
print("B组效果不显著,建议重新设计测试或优化测试变量。")`
);
// Constants
const TABLE_HEADER_STYLE = {
color: "#fff",
fontSize: "14px",
backgroundColor: "#6A7DE9 !important",
};
const CHART_COLOR = "#4682B4";
const ALPHA = 0.05;
// Reactive State
const chartContainer = ref(null);
const chartLoading = ref(false);
const analysisText = ref("");
const rawData = ref([
{ version: "A (1月)", clicks: 123, exposure: 1042, conversions: 21, orderAmount: 2059, adCost: 512 },
{ version: "A (2月)", clicks: 159, exposure: 1208, conversions: 25, orderAmount: 2515, adCost: 632 },
{ version: "A (3月)", clicks: 131, exposure: 1137, conversions: 22, orderAmount: 2207, adCost: 552 },
{ version: "A (4月)", clicks: 144, exposure: 1321, conversions: 24, orderAmount: 2436, adCost: 653 },
{ version: "A (5月)", clicks: 167, exposure: 1423, conversions: 28, orderAmount: 2853, adCost: 709 },
{ version: "B (1月)", clicks: 183, exposure: 1505, conversions: 30, orderAmount: 3079, adCost: 751 },
{ version: "B (2月)", clicks: 209, exposure: 1609, conversions: 35, orderAmount: 3507, adCost: 819 },
{ version: "B (3月)", clicks: 194, exposure: 1719, conversions: 32, orderAmount: 3215, adCost: 784 },
{ version: "B (4月)", clicks: 218, exposure: 1851, conversions: 38, orderAmount: 3831, adCost: 853 },
{ version: "B (5月)", clicks: 220, exposure: 2032, conversions: 40, orderAmount: 4023, adCost: 921 },
]);
const rawData1 = ref([
{ version: "A (1月)", clicks: "", exposure: "", conversions: "", orderAmount: 2059 },
{ version: "A (2月)", clicks: "", exposure: "", conversions: "", orderAmount: 2515 },
{ version: "A (3月)", clicks: "", exposure: "", conversions: "", orderAmount: 2207 },
{ version: "A (4月)", clicks: "", exposure: "", conversions: "", orderAmount: 2436 },
{ version: "A (5月)", clicks: "", exposure: "", conversions: "", orderAmount: 2853 },
{ version: "B (1月)", clicks: "", exposure: "", conversions: "", orderAmount: 3079 },
{ version: "B (2月)", clicks: "", exposure: "", conversions: "", orderAmount: 3507 },
{ version: "B (3月)", clicks: "", exposure: "", conversions: "", orderAmount: 3215 },
{ version: "B (4月)", clicks: "", exposure: "", conversions: "", orderAmount: 3831 },
{ version: "B (5月)", clicks: "", exposure: "", conversions: "", orderAmount: 4023 },
]);
const testResults = ref([
{ testName: "点击率", statistic: "", pValue: "" },
{ testName: "转化率", statistic: "", pValue: "" },
{ testName: "投资回报率", statistic: "", pValue: "" },
{ testName: "订单金额", statistic: "", pValue: "" },
]);
// Computed Metrics for rawData (to display CTR, CVR, ROI in the first table)
function calculateMetrics(data) {
return data.map((item) => ({
...item,
ctr: item.exposure ? item.clicks / item.exposure : 0,
cvr: item.clicks ? item.conversions / item.clicks : 0,
roi: item.adCost ? (item.orderAmount - item.adCost) / item.adCost : 0,
}));
}
// Chart Logic
function initializeChart() {
if (!chartContainer.value) {
return;
}
chartLoading.value = true;
try {
const chart = echarts.init(chartContainer.value);
// Group data by version (A or B)
const groupedData = {
A: { clicks: [], exposure: [], conversions: [], orderAmount: [] },
B: { clicks: [], exposure: [], conversions: [], orderAmount: [] },
};
rawData1.value.forEach((item) => {
const version = item.version.startsWith("A") ? "A" : "B";
// Convert user input to numbers, default to 0 if empty or invalid
groupedData[version].clicks.push(Number(item.clicks) || 0);
groupedData[version].exposure.push(Number(item.exposure) || 0);
groupedData[version].conversions.push(Number(item.conversions) || 0);
groupedData[version].orderAmount.push(Number(item.orderAmount) || 0);
});
// Prepare boxplot data and calculate outliers
const boxData = {
clicks: [prepareBoxPlotData(groupedData.A.clicks), prepareBoxPlotData(groupedData.B.clicks)],
exposure: [prepareBoxPlotData(groupedData.A.exposure), prepareBoxPlotData(groupedData.B.exposure)],
conversions: [prepareBoxPlotData(groupedData.A.conversions), prepareBoxPlotData(groupedData.B.conversions)],
orderAmount: [prepareBoxPlotData(groupedData.A.orderAmount), prepareBoxPlotData(groupedData.B.orderAmount)],
};
const outlierData = {
clicks: [calcOutliers(groupedData.A.clicks), calcOutliers(groupedData.B.clicks)],
exposure: [calcOutliers(groupedData.A.exposure), calcOutliers(groupedData.B.exposure)],
conversions: [calcOutliers(groupedData.A.conversions), calcOutliers(groupedData.B.conversions)],
orderAmount: [calcOutliers(groupedData.A.orderAmount), calcOutliers(groupedData.B.orderAmount)],
};
// ECharts configuration with adjusted sizes
const option = {
title: [
{ text: "点击量对比", left: "25%", top: "0%", textStyle: { fontSize: 16 } },
{ text: "曝光量对比", left: "70%", top: "0%", textStyle: { fontSize: 16 } },
{ text: "转化量对比", left: "25%", top: "55%", textStyle: { fontSize: 16 } },
{ text: "订单金额对比", left: "70%", top: "55%", textStyle: { fontSize: 16 } },
],
grid: [
{ left: "10%", top: "10%", width: "35%", height: "35%" },
{ left: "55%", top: "10%", width: "35%", height: "35%" },
{ left: "10%", top: "60%", width: "35%", height: "35%" },
{ left: "55%", top: "60%", width: "35%", height: "35%" },
],
xAxis: [
{ gridIndex: 0, type: "category", data: ["A", "B"], name: "广告版本", nameLocation: "middle", nameGap: 30, nameTextStyle: { fontSize: 14 } },
{ gridIndex: 1, type: "category", data: ["A", "B"], name: "广告版本", nameLocation: "middle", nameGap: 30, nameTextStyle: { fontSize: 14 } },
{ gridIndex: 2, type: "category", data: ["A", "B"], name: "广告版本", nameLocation: "middle", nameGap: 30, nameTextStyle: { fontSize: 14 } },
{ gridIndex: 3, type: "category", data: ["A", "B"], name: "广告版本", nameLocation: "middle", nameGap: 30, nameTextStyle: { fontSize: 14 } },
],
yAxis: [
{ gridIndex: 0, type: "value", name: "点击量", nameLocation: "middle", nameGap: 50, nameTextStyle: { fontSize: 14 } },
{ gridIndex: 1, type: "value", name: "曝光量", nameLocation: "middle", nameGap: 50, nameTextStyle: { fontSize: 14 } },
{ gridIndex: 2, type: "value", name: "转化量", nameLocation: "middle", nameGap: 50, nameTextStyle: { fontSize: 14 } },
{ gridIndex: 3, type: "value", name: "订单金额", nameLocation: "middle", nameGap: 50, nameTextStyle: { fontSize: 14 } },
],
series: [
// Clicks boxplot
{
name: "点击量",
type: "boxplot",
xAxisIndex: 0,
yAxisIndex: 0,
data: boxData.clicks,
itemStyle: {
borderColor: CHART_COLOR,
color: CHART_COLOR,
},
boxWidth: 150, // Increased box width
tooltip: {
formatter: (param) => [`${param.name}:`, `Upper: ${param.data[4].toFixed(2)}`, `Q3: ${param.data[3].toFixed(2)}`, `Median: ${param.data[2].toFixed(2)}`, `Q1: ${param.data[1].toFixed(2)}`, `Lower: ${param.data[0].toFixed(2)}`].join("<br/>"),
},
},
// Clicks outliers
{
name: "outlier",
type: "scatter",
xAxisIndex: 0,
yAxisIndex: 0,
data: outlierData.clicks.map((outliers, i) => outliers.map((val) => [i, val])),
itemStyle: {
color: "#fff",
borderColor: "#000",
borderWidth: 2,
},
symbolSize: 12, // Increased outlier size
},
// Exposure boxplot
{
name: "曝光量",
type: "boxplot",
xAxisIndex: 1,
yAxisIndex: 1,
data: boxData.exposure,
itemStyle: {
borderColor: CHART_COLOR,
color: CHART_COLOR,
},
boxWidth: 150, // Increased box width
tooltip: {
formatter: (param) => [`${param.name}:`, `Upper: ${param.data[4].toFixed(2)}`, `Q3: ${param.data[3].toFixed(2)}`, `Median: ${param.data[2].toFixed(2)}`, `Q1: ${param.data[1].toFixed(2)}`, `Lower: ${param.data[0].toFixed(2)}`].join("<br/>"),
},
},
// Exposure outliers
{
name: "outlier",
type: "scatter",
xAxisIndex: 1,
yAxisIndex: 1,
data: outlierData.exposure.map((outliers, i) => outliers.map((val) => [i, val])),
itemStyle: {
color: "#fff",
borderColor: "#000",
borderWidth: 2,
},
symbolSize: 12, // Increased outlier size
},
// Conversions boxplot
{
name: "转化量",
type: "boxplot",
xAxisIndex: 2,
yAxisIndex: 2,
data: boxData.conversions,
itemStyle: {
borderColor: CHART_COLOR,
color: CHART_COLOR,
},
boxWidth: 150, // Increased box width
tooltip: {
formatter: (param) => [`${param.name}:`, `Upper: ${param.data[4].toFixed(2)}`, `Q3: ${param.data[3].toFixed(2)}`, `Median: ${param.data[2].toFixed(2)}`, `Q1: ${param.data[1].toFixed(2)}`, `Lower: ${param.data[0].toFixed(2)}`].join("<br/>"),
},
},
// Conversions outliers
{
name: "outlier",
type: "scatter",
xAxisIndex: 2,
yAxisIndex: 2,
data: outlierData.conversions.map((outliers, i) => outliers.map((val) => [i, val])),
itemStyle: {
color: "#fff",
borderColor: "#000",
borderWidth: 2,
},
symbolSize: 12, // Increased outlier size
},
// Order Amount boxplot
{
name: "订单金额",
type: "boxplot",
xAxisIndex: 3,
yAxisIndex: 3,
data: boxData.orderAmount,
itemStyle: {
borderColor: CHART_COLOR,
color: CHART_COLOR,
},
boxWidth: 150, // Increased box width
tooltip: {
formatter: (param) => [`${param.name}:`, `Upper: ${param.data[4].toFixed(2)}`, `Q3: ${param.data[3].toFixed(2)}`, `Median: ${param.data[2].toFixed(2)}`, `Q1: ${param.data[1].toFixed(2)}`, `Lower: ${param.data[0].toFixed(2)}`].join("<br/>"),
},
},
// Order Amount outliers
{
name: "outlier",
type: "scatter",
xAxisIndex: 3,
yAxisIndex: 3,
data: outlierData.orderAmount.map((outliers, i) => outliers.map((val) => [i, val])),
itemStyle: {
color: "#fff",
borderColor: "#000",
borderWidth: 2,
},
symbolSize: 12, // Increased outlier size
},
],
};
chart.setOption(option);
// Handle window resize
const resizeHandler = () => chart.resize();
window.addEventListener("resize", resizeHandler);
// Cleanup
onUnmounted(() => {
window.removeEventListener("resize", resizeHandler);
chart.dispose();
});
} catch (error) {
ElMessage.error("图表渲染失败,请稍后重试");
console.error("Chart initialization error:", error);
} finally {
chartLoading.value = false;
}
}
// Helper Functions
function prepareBoxPlotData(data) {
const sorted = data.slice().sort((a, b) => a - b);
const len = sorted.length;
const q1 = sorted[Math.floor(len / 4)];
const median = sorted[Math.floor(len / 2)];
const q3 = sorted[Math.floor((len * 3) / 4)];
const iqr = q3 - q1;
const lowerBound = q1 - 1.5 * iqr;
const upperBound = q3 + 1.5 * iqr;
const lower = Math.max(sorted[0], lowerBound);
const upper = Math.min(sorted[len - 1], upperBound);
return [lower, q1, median, q3, upper];
}
function calcOutliers(data) {
const sorted = data.slice().sort((a, b) => a - b);
const len = sorted.length;
const q1 = sorted[Math.floor(len / 4)];
const q3 = sorted[Math.floor((len * 3) / 4)];
const iqr = q3 - q1;
const lowerBound = q1 - 1.5 * iqr;
const upperBound = q3 + 1.5 * iqr;
return data.filter((val) => val < lowerBound || val > upperBound);
}
function performTTest(a, b) {
const result = jStat.ttest(a, b, 2); // Two-sample t-test
return {
statistic: result.tstat,
pValue: result.p,
};
}
// Reset Task
function resetTask() {
analysisAPI.productReleaseReTrain({ userId: userStore.userInfo.userId, module: "产品推广测试" }).then((res) => {
proxy.$modal.msgSuccess("重新实训成功");
rawData1.value = [
{ version: "A (1月)", clicks: "", exposure: "", conversions: "", orderAmount: 2059 },
{ version: "A (2月)", clicks: "", exposure: "", conversions: "", orderAmount: 2515 },
{ version: "A (3月)", clicks: "", exposure: "", conversions: "", orderAmount: 2207 },
{ version: "A (4月)", clicks: "", exposure: "", conversions: "", orderAmount: 2436 },
{ version: "A (5月)", clicks: "", exposure: "", conversions: "", orderAmount: 2853 },
{ version: "B (1月)", clicks: "", exposure: "", conversions: "", orderAmount: 3079 },
{ version: "B (2月)", clicks: "", exposure: "", conversions: "", orderAmount: 3507 },
{ version: "B (3月)", clicks: "", exposure: "", conversions: "", orderAmount: 3215 },
{ version: "B (4月)", clicks: "", exposure: "", conversions: "", orderAmount: 3831 },
{ version: "B (5月)", clicks: "", exposure: "", conversions: "", orderAmount: 4023 },
];
testResults.value = [
{ testName: "点击量", statistic: "", pValue: "" },
{ testName: "曝光量", statistic: "", pValue: "" },
{ testName: "转化量", statistic: "", pValue: "" },
{ testName: "订单金额", statistic: "", pValue: "" },
];
analysisText.value = "";
// 图表重置
chartLoading.value = true;
setTimeout(() => {
chartLoading.value = false;
initializeChart();
}, 1000);
});
}
function submitTask() {
if (rawData1.value.some((item) => !item.clicks || !item.exposure || !item.conversions)) {
return proxy.$modal.msgWarning("请先指标数据计算填写!");
}
if (testResults.value.some((item) => !item.statistic || !item.pValue)) {
return proxy.$modal.msgWarning("请先完成统计显著性检验!");
}
let params = {
userId: userStore.userInfo.userId,
clickRate: rawData1.value.map((item) => item.clicks).join(","),
conversionRate: rawData1.value.map((item) => item.exposure).join(","),
roiData: rawData1.value.map((item) => item.conversions).join(","),
pValue: testResults.value.map((item) => item.pValue).join(","),
orderAmount: testResults.value.map((item) => item.statistic).join(","),
context: analysisText.value,
};
analysisAPI.productPromotionTestSubmit(params).then((res) => {});
}
function closeCode() {
runResultShow.value = false;
}
// 回显数据
function loadTaskInfo() {
analysisAPI.productPromotionTestEcho({ userId: userStore.userInfo.userId }).then((res) => {
if (!res.data) return;
const clickRate = res.data.clickRate.split(",");
const conversionRate = res.data.conversionRate.split(",");
const roiData = res.data.roiData.split(",");
const pValue = res.data.pValue.split(",");
const orderAmount = res.data.orderAmount.split(",");
rawData1.value.forEach((item, index) => {
item.clicks = clickRate[index] || "";
item.exposure = conversionRate[index] || "";
item.conversions = roiData[index] || "";
});
// Update testResults with echoed data
testResults.value.forEach((item, index) => {
item.pValue = pValue[index];
item.statistic = orderAmount[index];
});
context.value = res.data.context;
chartLoading.value = false;
initializeChart();
});
}
const go=()=>{
window.open('https://jrdsj.sztzjy.com:8000/user/zy84cf311/lab')
}
// Lifecycle
onMounted(() => {
rawData.value = calculateMetrics(rawData.value);
const observer = new MutationObserver(() => {
if (chartContainer.value) {
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
loadTaskInfo();
});
</script>
<style lang="scss" scoped>
.promotion {
background: #ffffff;
.intro,
.case-study,
.analysis,
.statistical-test,
.results {
margin-bottom: 30px;
}
h3 {
font-size: 18px;
color: var(--text-color);
margin-bottom: 15px;
}
.case-backtitle {
display: flex;
align-items: center;
margin-bottom: 15px;
img {
width: 24px;
height: 24px;
margin-right: 8px;
}
span {
font-size: 16px;
font-weight: bold;
color: var(--text-color);
}
}
.data-table {
width: 100%;
max-width: 1000px;
margin: 20px 0;
display: block;
}
.formulas {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
span {
font-size: 14px;
color: var(--text-color);
}
}
.chart-container {
width: 100%;
max-width: 1500px;
height: 600px; /* Increased height for better visibility */
margin: 20px 0;
display: block;
}
.task-footer {
display: flex;
gap: 10px;
justify-content: flex-end;
}
@media (max-width: 768px) {
padding: 10px;
.data-table,
.chart-container {
max-width: 100%;
}
.chart-container {
height: 400px; /* Adjusted for smaller screens */
}
.formulas {
flex-direction: column;
gap: 10px;
}
}
}
</style>