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/assessment/market.vue

507 lines
18 KiB
Vue

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
* @Author: qinzhenpen qzp1807@126.com
* @Date: 2025-05-06 11:39:19
* @LastEditors: qinzhenpen qzp1807@126.com
* @LastEditTime: 2025-05-13 15:05:45
* @FilePath: \e-commerce-internet\src\views\assessment\market.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="market">
<el-scrollbar :height="`${height - 100}px`">
<TrainingIntro title="市场机会矩阵">
<template #background>
<p>通过商品组合分析优化资源分配,提升整体利润率。</p>
</template>
<template #goal>
<p>掌握波士顿矩阵相关指标计算公式,准确划分产品类型,并提出可行性建议。</p>
</template>
</TrainingIntro>
<div class="training-step-area">
<div class="caseBacktitle">
<img src="@/assets/images/步骤.png" alt="" />
<span>任务目标</span>
</div>
<p>市场机会矩阵(波士顿矩阵)‌按市场增长率和市场份额将商品分为“明星、现金牛、问题、瘦狗”四类,制定差异化运营策略(如淘汰低效品、扶持潜力品)。学生需要掌握波士顿矩阵中相关指标的计算公式,准确划分产品类型,并针对不同的产品类型提出可行性建议。‌</p>
<div class="caseBacktitle">
<img src="@/assets/images/步骤.png" alt="" />
<span>任务背景</span>
</div>
<p>某美妆电商平台的商品组合分析,该平台主要销售护肤品、彩妆、个护工具等品类,拥有200+SKU,目标是通过波士顿矩阵优化资源分配,提升整体利润率。</p>
<div class="df">
<div class="left">
<p>1. 销售额数据</p>
<el-table
:data="salesData"
border
style="width: 100%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="产品类别" align="center" prop="category"></el-table-column>
<el-table-column label="本年度销售额(万元)" align="center" prop="currentYear"></el-table-column>
<el-table-column label="上年度销售额(万元)" align="center" prop="lastYear"></el-table-column>
</el-table>
<p>2. 行业平均增长率</p>
<span>行业整体增长率为 10%(数据来自第三方调研机构)。</span>
</div>
<div class="right">
<p>3. 竞品市场份额</p>
<el-table
:data="competitorMarketShare"
border
style="width: 50%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="产品类别" align="center" prop="category"></el-table-column>
<el-table-column label="竞品同类产品总销售额(万元)" align="center" prop="totalSales"></el-table-column>
</el-table>
<p>4. 用户行为数据</p>
<el-table
:data="userBehaviorData"
border
style="width: 60%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="产品类别" align="center" prop="category"></el-table-column>
<el-table-column label="加购率(%)" align="center" prop="addToCartRate"></el-table-column>
<el-table-column label="转化率(%)" align="center" prop="conversionRate"></el-table-column>
<el-table-column label="复购率(%)" align="center" prop="repurchaseRate"></el-table-column>
</el-table>
</div>
</div>
<div class="caseBacktitle">
<img src="@/assets/images/步骤.png" alt="" />
<span>任务要求</span>
</div>
<div class="caseBacktitle">
<span>任务1:计算市场增长率</span>
</div>
<p>市场增长率=(本年度销售额一上年度销售额) / 上年度销售额 x 100%</p>
<el-table
:data="productSalesData"
border
style="width: 80%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="产品类别" align="center" prop="category" width="180"></el-table-column>
<el-table-column label="本年度销售额(万元)" align="center" prop="currentYear" width="200"></el-table-column>
<el-table-column label="上年度销售额(万元)" align="center" prop="lastYear" width="200"></el-table-column>
<el-table-column label="市场增长率(%)" align="center">
<template #default="scope">
<div class="df" style="align-items: center">
<span>(</span>
<el-input v-model="scope.row.currentYearValue" style="width: 10vw"></el-input>
<span>-</span>
<el-input v-model="scope.row.lastYearValue" style="width: 10vw"></el-input>
<span>)</span>
<span>/</span>
<el-input v-model="scope.row.value" style="width: 10vw"></el-input>
<span>=</span>
<el-input v-model="scope.row.growthValue" style="width: 10vw">
<template #suffix>%</template>
</el-input>
</div>
</template>
</el-table-column>
</el-table>
<div class="caseBacktitle">
<span>任务2:计算相对市场份额</span>
</div>
<p>相对市场份额=本品牌销售额 / 竞品同类产品总销售额 ×100%</p>
<el-table
:data="marketShareData"
border
style="width: 70%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="产品类别" align="center" prop="category" width="180"></el-table-column>
<el-table-column label="本品牌销售额(万元)" align="center" prop="brandSales" width="200"></el-table-column>
<el-table-column label="竞品同类产品总销售额(万元)" align="center" prop="competitorSales" width="240"></el-table-column>
<el-table-column label="相对市场份额(%)" align="center">
<template #default="scope">
<div class="df" style="align-items: center">
<el-input v-model="scope.row.brandSalesValue" style="width: 10vw"></el-input>
<span>/</span>
<el-input v-model="scope.row.competitorSalesValue" style="width: 10vw"></el-input>
<span>=</span>
<el-input v-model="scope.row.marketShareValue" style="width: 10vw">
<template #suffix>%</template>
</el-input>
</div>
</template>
</el-table-column>
</el-table>
<div class="caseBacktitle">
<span>任务3:汇总计算结果</span>
</div>
<el-table
:data="marketGrowthData"
border
style="width: 70%; margin-top: 10px"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column prop="category" label="产品类别" align="center" />
<el-table-column prop="growthRate" label="市场增长率(%)" align="center" />
<el-table-column prop="industryAvgGrowthRate" label="市场增长率行业均值" align="center" />
<el-table-column prop="marketShare" label="相对市场份额(%)" align="center" />
<el-table-column prop="industryAvgMarketShare" label="相对市场份额均值" align="center" />
</el-table>
<div class="caseBacktitle">
<span>任务4:产品分类</span>
</div>
<p>根据 市场增长率 和 相对市场份额 的计算结果,结合波士顿矩阵的标准:</p>
<template v-for="(item, index) in productClassification" :key="index">
<div class="online">
<span style="width: 22vw">{{ index + 1 }}.{{ item.label }}</span>
<el-select placeholder="请选择" @change="onChange" v-model="item.value" class="ml10 mt10">
<el-option v-for="item in option" :key="item" :value="item" :label="item"></el-option>
</el-select>
</div>
</template>
<div class="caseBacktitle">
<span>任务5:产品诊断</span>
</div>
<p>
通过原始数据的计算,明确了各产品的市场定位:
<br />
明星产品(精华液):高增长、高份额,需持续投入资源。
<br />
现金牛产品(面霜):低增长、高份额,需优化成本,支持其他产品。
<br />
问题产品(腮红):高增长、低份额,需评估潜力,精准投放。
<br />
瘦狗产品(口红):低增长、低份额,需快速止损,释放资源。
<br />
</p>
</div>
</el-scrollbar>
<div class="taskfooter">
<el-button @click="submitTask">提交任务</el-button>
<el-button @click="resetTask">重新实训</el-button>
</div>
</div>
</template>
<script setup>
import TrainingLegacyActions from "@/views/components/TrainingLegacyActions.vue";
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import TrainingIntro from '@/views/components/TrainingIntro.vue';
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
import * as analysisAPI from "@/api/market-demand-mining.js";
const { proxy } = getCurrentInstance();
const height = ref(800);
const salesData = [
{ category: "某品牌精华液", currentYear: 500, lastYear: 400 },
{ category: "基础保湿面霜", currentYear: 800, lastYear: 760 },
{ category: "小众矿物腮红", currentYear: 120, lastYear: 92 },
{ category: "传统软管装口红", currentYear: 90, lastYear: 92 },
];
const competitorMarketShare = [
{ category: "某品牌精华液", totalSales: 420 },
{ category: "基础保湿面霜", totalSales: 533 },
{ category: "小众矿物腮红", totalSales: 400 },
{ category: "传统软管装口红", totalSales: 1125 },
];
const userBehaviorData = [
{ category: "某品牌精华液", addToCartRate: 8.5, conversionRate: 3.8, repurchaseRate: 45 },
{ category: "基础保湿面霜", addToCartRate: 6.8, conversionRate: 3.2, repurchaseRate: 28 },
{ category: "小众矿物腮红", addToCartRate: 4.5, conversionRate: 1.8, repurchaseRate: 15 },
{ category: "传统软管装口红", addToCartRate: 1.2, conversionRate: 0.8, repurchaseRate: 5 },
];
const productSalesData = ref([
{
category: "某品牌精华液",
currentYear: 500,
currentYearValue: null,
lastYear: 400,
lastYearValue: null,
value: null,
growthRate: "25%",
growthValue: null,
key1: "mppjhyRw1Two",
key2: "ctrzkhRw1Two",
key3: "mppjhyRw1Three",
key4: "mppjhyRw1Four",
},
{
category: "基础保湿面霜",
currentYear: 800,
currentYearValue: null,
value: null,
lastYear: 760,
lastYearValue: null,
growthRate: "5.26%",
growthValue: null,
key1: "jcbsmsRw1One",
key2: "jcbsmsRw1Two",
key3: "jcbsmsRw1Three",
key4: "jcbsmsRw1Four",
},
{
category: "小众矿物腮红",
currentYear: 120,
currentYearValue: null,
value: null,
lastYear: 92,
lastYearValue: null,
growthRate: "30.43%",
growthValue: null,
key1: "xzkwshRw1One",
key2: "xzkwshRw1Two",
key3: "xzkwshRw1Three",
key4: "xzkwshRw1Four",
},
{
category: "传统软管装口红",
currentYear: 90,
currentYearValue: null,
lastYear: 92,
lastYearValue:null,
value: null,
growthRate: "-2.17%", // 负增长
growthValue: null,
key1: "ctrzkhRw1One",
key2: "ctrzkhRw1Two",
key3: "ctrzkhRw1Three",
key4: "ctrzkhRw1Four",
},
]);
const marketShareData = ref([
{
category: "某品牌精华液",
brandSales: 500,
brandSalesValue: null,
competitorSales: 420,
competitorSalesValue: null,
marketShare: "119.05%",
marketShareValue:null,
key1: "mppjhyRw2One",
key2: "mppjhyRw2Two",
key3: "mppjhyRw2Three",
},
{
category: "基础保湿面霜",
brandSales: 800,
brandSalesValue:null,
competitorSales: 533,
competitorSalesValue: null,
marketShare: "150.09%",
marketShareValue: null,
key1: "jcbsmsRw2One",
key2: "jcbsmsRw2Two",
key3: "jcbsmsRw2Thee",
},
{
category: "小众矿物腮红",
brandSales: 120,
brandSalesValue: null,
competitorSales: 400,
competitorSalesValue: null,
marketShare: "30.00%",
marketShareValue: null,
key1: "xzkwshRw2One",
key2: "xzkwshRw2Two",
key3: "xzkwshRw2Three",
},
{
category: "传统软管装口红",
brandSales: 90,
brandSalesValue:null,
competitorSales: 1125,
competitorSalesValue: null,
marketShare: "8.00%",
marketShareValue:null,
key1: "ctrzkhRw2One",
key2: "ctrzkhRw2Two",
key3: "ctrzkhRw2Three",
},
]);
const marketGrowthData = ref([
{
category: "某品牌精华液",
growthRate: "25%",
industryAvgGrowthRate: "10%",
marketShare: "119.05%",
industryAvgMarketShare: "100%",
},
{
category: "基础保湿面霜",
growthRate: "5.26%",
industryAvgGrowthRate: "10%",
marketShare: "150.09%",
industryAvgMarketShare: "100%",
},
{
category: "小众矿物腮红",
growthRate: "30.43%",
industryAvgGrowthRate: "10%",
marketShare: "30.00%",
industryAvgMarketShare: "100%",
},
{
category: "传统软管装口红",
growthRate: "-2.17%",
industryAvgGrowthRate: "10%",
marketShare: "8.00%",
industryAvgMarketShare: "100%",
},
]);
// 分类描述与绑定值数组
const productClassification = ref([
{
label: "明星产品:市场增长率 > 10%,相对市场份额 > 100%",
value: "", // 绑定的选中值
key1: "rw4One",
},
{
label: "现金牛产品:市场增长率 < 10%,相对市场份额 > 100%",
key1: "rw4Two",
value: "",
},
{
label: "问题产品:市场增长率 > 10%,相对市场份额 < 100%",
key1: "rw4Three",
value: "",
},
{
label: "瘦狗产品:市场增长率 < 0%,相对市场份额 < 10%",
key1: "rw4Four",
value: "",
},
]);
const option = ["某品牌精华液", "基础保湿面霜", "小众矿物腮红", "传统软管装口红"];
function getElementHeight(selector) {
const el = document.querySelector(selector);
if (el) {
// 获取元素的实际高度,包括 padding 但不包括 margin
return el.scrollHeight;
} else {
console.warn(`找不到元素:${selector}`);
return 0;
}
}
const submitTask = () => {
if (productSalesData.value.some((item) => !item.currentYearValue || !item.lastYearValue || !item.value || !item.growthValue)) {
proxy.$modal.msgWarning("请先完成任务1");
return;
}
if (marketShareData.value.some((item) => !item.brandSalesValue || !item.competitorSalesValue || !item.marketShareValue)) {
proxy.$modal.msgWarning("请先完成任务2");
return;
}
if (productClassification.value.some((item) => !item.value)) {
proxy.$modal.msgWarning("请先完成任务4");
return;
}
const answerMap = {};
productSalesData.value.forEach((item, index) => {
answerMap[item.key1] = item.currentYearValue;
answerMap[item.key2] = item.lastYearValue;
answerMap[item.key3] = item.value;
answerMap[item.key4] = Number(item.growthValue).toFixed(2);
});
marketShareData.value.forEach((item, index) => {
answerMap[item.key1] = item.brandSalesValue;
answerMap[item.key2] = item.competitorSalesValue;
answerMap[item.key3] = Number(item.marketShareValue).toFixed(2);
});
productClassification.value.forEach((item, index) => {
answerMap[item.key1] = item.value; // 直接映射 label 作为值
});
answerMap.userId = userStore.userInfo.userId;
analysisAPI.marketOpportunityMatrixSubmit(answerMap).then((res) => {
proxy.$modal.msgSuccess("提交成功");
});
};
const echo = () => {
analysisAPI.marketOpportunityMatrixEcho({ userId: userStore.userInfo.userId }).then((res) => {
const { data } = res;
productSalesData.value.forEach((item, index) => {
item.currentYearValue = data[item.key1];
item.lastYearValue = data[item.key2];
item.value = data[item.key3];
item.growthValue = data[item.key4];
});
marketShareData.value.forEach((item, index) => {
item.brandSalesValue = data[item.key1];
item.competitorSalesValue = data[item.key2];
item.marketShareValue = data[item.key3];
});
productClassification.value.forEach((item, index) => {
item.value = data[item.key1]; // 直接映射 label 作为值
});
});
};
//重新实训
const resetTask = () => {
analysisAPI.marketOpportunityMatrixReTrain({ userId: userStore.userInfo.userId }).then((res) => {
proxy.$modal.msgSuccess("重新实训成功");
productSalesData.value.forEach((item, index) => {
item.currentYear = "";
item.lastYear = "";
item.value = "";
item.growthValue = "";
});
marketShareData.value.forEach((item, index) => {
item.brandSales = "";
item.competitorSales = "";
item.marketShareValue = "";
});
productClassification.value.forEach((item, index) => {
item.value = "";
});
});
};
onMounted(() => {
// 监听窗口大小变化
window.addEventListener("resize", () => {
height.value = getElementHeight(".kano");
});
echo();
});
defineExpose({ submitTask, resetTask });
</script>
<style lang='scss' scoped>
.market {
height: calc(100vh - 230px);
background: #ffffff;
padding: 20px;
border-radius: 8px;
padding-bottom: 0;
.df {
display: flex;
gap: 30px;
.right {
width: 50%;
}
}
}
</style>