|
|
<!--
|
|
|
* @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>
|