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/product/price.vue

640 lines
21 KiB
Vue

2 months ago
<!--
* @Author: qinzhenpen qzp1807@126.com
* @Date: 2025-04-28 15:05:56
* @LastEditors: qinzhenpen qzp1807@126.com
* @LastEditTime: 2025-05-08 17:17:01
* @FilePath: \e-commerce-internet\src\views\product\price.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="price app-container">
<div class="handren">
<div class="left">
<el-button>PSM模型</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 />
在电商产品定价方面PSM分析技术通过进行价格敏感度测试基于问卷调查数据可视化模型分析给出合理定价区间
<br />
<br />
任务目标
<br />
<br />
使用PSM模型给一款洗面奶进行定价
<br />
<br />
①数据源价格区间问卷调查数据
<br />
<br />
②根据PSM模型分析得出合理定价区间
<br />
</p>
</template>
</popModel>
</div>
</div>
<div class="caseBacktitle">
<img src="@/assets/images/步骤.png" alt="" />
<span>知识学习</span>
</div>
<p>
PSM分析技术的理论基础
<br />
在开发新产品时厂商对产品价格的看法和消费者对产品价格的知觉经常会不一致因此厂商往往不愿意事先设定产品的价格而希望能通过调查从消费者那里得到产品价格的可接受范围
<br />
价格敏感度测试(Price Sensitivity Measure mentPSM不需要预先给定好价格而是让每位被访者自己表示他们的可接受价格范围
<br />
目的可用于新产品开发产品线的扩张产品线内产品项目的增加产品范围的扩张增加产品线等情况下确定产品价格的可接受值或范围
<br />
样本的选择对于产品的开发必须从所有可能使用新产品的消费总体中抽取有代表性的样本对于产品线或产品范围的扩张样本可以从原来的品牌使用者中抽取
<br />
</p>
<div class="caseBacktitle">
<img src="@/assets/images/步骤.png" alt="" />
<span>实验实训</span>
</div>
<p>假如淘宝某店铺计划推出一款针对大学生群体的洗面奶您对下列价格区间有何想法</p>
<el-table
:data="priceList1"
border
style="width: 60%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="价格区间" align="center" prop="label"></el-table-column>
<el-table-column label="太便宜,可能有陷阱" align="center" prop="value">
<template #default="{ row }">
<el-radio v-model="row.value" label="1" class="hidden-label"></el-radio>
</template>
</el-table-column>
<el-table-column label="很划算,肯定会购买" align="center" prop="value">
<template #default="{ row }">
<el-radio v-model="row.value" label="2" class="hidden-label"></el-radio>
</template>
</el-table-column>
<el-table-column label="有点贵,还可以接受" align="center" prop="value">
<template #default="{ row }">
<el-radio v-model="row.value" label="3" class="hidden-label"></el-radio>
</template>
</el-table-column>
<el-table-column label="太贵,直接放弃" align="center" prop="value">
<template #default="{ row }">
<el-radio v-model="row.value" label="4" class="hidden-label"></el-radio>
</template>
</el-table-column>
</el-table>
<p>洗面奶价格梯度表累计百分比用百分数表示保留两位小数</p>
<el-table
:data="priceList2"
border
style="width: 80%"
:header-cell-style="{
color: '#fff',
fontSize: '14px',
backgroundColor: '#6A7DE9!important',
}"
>
<el-table-column label="价格区间" align="center" prop="label"></el-table-column>
<el-table-column label="太便宜,可能有陷阱" align="center">
<el-table-column label="" align="center" prop="pricevalue1"></el-table-column>
<el-table-column label="累计总和" align="center" prop="toocheapsum1">
<template #default="{ row }">
<el-input v-model="row.toocheapsum1" placeholder="" />
</template>
</el-table-column>
<el-table-column label="累计百分比" align="center" prop="percentage1">
<template #default="{ row }">
<el-input v-model="row.percentage1" placeholder="">
<template #suffix>%</template>
</el-input>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="很划算,肯定会购买" align="center">
<el-table-column label="" align="center" prop="pricevalue2"></el-table-column>
<el-table-column label="累计总和" align="center" prop="toocheapsum2">
<template #default="{ row }">
<el-input v-model="row.toocheapsum2" placeholder="" />
</template>
</el-table-column>
<el-table-column label="累计百分比" align="center" prop="percentage2">
<template #default="{ row }">
<el-input v-model="row.percentage2" placeholder="">
<template #suffix>%</template>
</el-input>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="有点贵,还可以接受" align="center">
<el-table-column label="" align="center" prop="pricevalue3"></el-table-column>
<el-table-column label="累计总和" align="center" prop="toocheapsum3">
<template #default="{ row }">
<el-input v-model="row.toocheapsum3" placeholder="" />
</template>
</el-table-column>
<el-table-column label="累计百分比" align="center" prop="percentage3">
<template #default="{ row }">
<el-input v-model="row.percentage3" placeholder="">
<template #suffix>%</template>
</el-input>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="太贵,直接放弃" align="center">
<el-table-column label="" align="center" prop="pricevalue4"></el-table-column>
<el-table-column label="累计总和" align="center" prop="toocheapsum4">
<template #default="{ row }">
<el-input v-model="row.toocheapsum4" placeholder="" />
</template>
</el-table-column>
<el-table-column label="累计百分比" align="center" prop="percentage4">
<template #default="{ row }">
<el-input v-model="row.percentage4" placeholder="">
<template #suffix>%</template>
</el-input>
</template>
</el-table-column>
</el-table-column>
</el-table>
<p>太便宜和很划算向下累积有点贵和太贵向上累积画出每一个价格下开始觉得太便宜便宜划算开始觉得贵太贵的累积人数百分比曲线图</p>
<div class="cnetent">
<div class="left">
<el-button @click="draw" style="margin-bottom: 20px">绘制</el-button>
<div id="main" ref="main" style="width: 100%; height: 50vh"></div>
</div>
<div class="right">
<div style="display: flex">
<div class="online">
<span>最优价格点:</span>
<el-input style="width: 100px" type="text" v-model="formInline.optimalPrice" />
</div>
<div class="online ml20">
<span>可接受价格点:</span>
<el-input style="width: 100px" type="text" v-model="formInline.acceptablePrice" />
</div>
</div>
<div class="online mt20">
<span>可接受价格范围:</span>
<el-input style="width: 100px" type="text" v-model="formInline.range1" />
<span class="p10">----------</span>
<el-input style="width: 100px" type="text" v-model="formInline.range2" />
</div>
<p>PSM模型在产品定价过程中的作用以及不足</p>
<el-input v-model="textarea2" :autosize="{ minRows: 11, maxRows: 11 }" type="textarea" placeholder="" />
</div>
</div>
<div class="taskfooter">
<el-button @click="submitTask"></el-button>
<el-button @click="resetTask"></el-button>
</div>
</div>
</template>
<script setup>
import * as echarts from "echarts";
import * as analysisAPI from "@/api/market-demand-mining.js";
const { proxy } = getCurrentInstance();
import useUserStore from "@/store/modules/user";
import popModel from "@/views/components/popModal.vue";
const userStore = useUserStore();
const formInline = ref({
optimalPrice: "",
acceptablePrice: "",
range1: "",
range2: "",
});
const textarea2 = ref("");
const dialogVisible = ref(false);
const reTraining = () => {
proxy.$message.success("重新实训");
};
onMounted(() => {});
const main = ref(null);
const priceList1 = ref([
{
label: "15元以内",
value: "",
},
{
label: "15-20元",
value: "",
},
{
label: "20-30元",
value: "",
},
{
label: "30-50元",
value: "",
},
{
label: "50-70元",
value: "",
},
{
label: "70-100元",
value: "",
},
{
label: "100-200元",
value: "",
},
{
label: "200元以上",
value: "",
},
]);
const priceList2 = ref([
{
label: "15元以内",
pricevalue1: "126",
pricevalue2: "56",
pricevalue3: "0",
pricevalue4: "0",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "15-20元",
pricevalue1: "85",
pricevalue2: "102",
pricevalue3: "0",
pricevalue4: "0",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "20-30元",
pricevalue1: "36",
pricevalue2: "198",
pricevalue3: "5",
pricevalue4: "0",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "30-50元",
pricevalue1: "2",
pricevalue2: "85",
pricevalue3: "30",
pricevalue4: "0",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "50-70元",
pricevalue1: "0",
pricevalue2: "25",
pricevalue3: "98",
pricevalue4: "8",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "70-100元",
pricevalue1: "0",
pricevalue2: "2",
pricevalue3: "126",
pricevalue4: "35",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "100-200元",
pricevalue1: "0",
pricevalue2: "0",
pricevalue3: "159",
pricevalue4: "89",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
{
label: "200元以上",
pricevalue1: "0",
pricevalue2: "0",
pricevalue3: "109",
pricevalue4: "192",
toocheapsum1: "",
toocheapsum2: "",
toocheapsum3: "",
toocheapsum4: "",
percentage1: "",
percentage2: "",
percentage3: "",
percentage4: "",
},
]);
// 校验空值并只提示一次
function validateAndAlertOnce() {
let hasEmptyValue = false; // 标记是否发现空值
// 遍历数组检查空值
priceList2.value.forEach((item) => {
Object.keys(item).forEach((key) => {
if (item[key] === "") {
hasEmptyValue = true; // 发现空值,标记为 true
// 可选择替换空值(如果需要)
item[key] = ""; // 替换为空值时的默认值
}
});
});
// 只在发现空值时提示一次
if (hasEmptyValue) {
// alert("数据中存在空值,已自动替换为默认值 0。");
proxy.$modal.msgWarning("请填写完整数据!");
return false; // 返回 false表示存在空值
}
return true; // 没有空值,返回 true
}
const draw = () => {
const isValid = validateAndAlertOnce();
if (!isValid) return;
const minPicture = priceList2.value.map((item) => item.percentage1);
const calculatePrice = priceList2.value.map((item) => item.percentage2);
const minexpensive = priceList2.value.map((item) => item.percentage3);
const expensive = priceList2.value.map((item) => item.percentage4);
const priceRanges = ['15元以内',"15-20元", "20-30元", "30-50元", "50-70元", "70-100元", "100-200元", "200元以上"];
var myChart = echarts.init(main.value);
myChart.setOption({
title: {
text: "PSM价格数据分析",
left: "center",
},
tooltip: {
trigger: "axis",
},
legend: {
data: ["太便宜", "很划算", "有点贵", "太贵了"],
top: 30,
},
xAxis: {
type: "category",
name: "价格区间",
data: priceRanges,
},
yAxis: {
type: "value",
name: "销售数量 (件)",
min: 0,
max: 100,
},
series: [
{
name: "太便宜",
type: "line",
data: minPicture,
color: "#1f77b4",
symbol: "circle",
symbolSize: 8,
},
{
name: "太贵了",
type: "line",
data: expensive,
color: "#d62728",
symbol: "circle",
symbolSize: 8,
},
{
name: "有点贵",
type: "line",
data: minexpensive,
color: "#2ca02c",
symbol: "circle",
symbolSize: 8,
},
{
name: "很划算",
type: "line",
data: calculatePrice,
color: "#ff7f0e",
symbol: "circle",
symbolSize: 8,
},
],
grid: {
left: "2%",
right: "15%",
bottom: "10%",
containLabel: true,
},
graphic: [
{
type: "text",
left: 391, // 或 "60%" 等
top: 334,
z: 100, // 设置更高的图层层级
style: {
text: "P4",
font: "bold 14px sans-serif",
fill: "#2ca02c",
// 显示在线条墙上面
distance: 10,
},
},
{
type: "text",
left: 300, // 或 "60%" 等
top: 380,
z: 100, // 设置更高的图层层级
symbol: "circle",
style: {
text: "P1",
font: "bold 14px sans-serif",
fill: "#1f77b4",
// 显示在线条墙上面
distance: 10,
},
},
{
type: "text",
left: 350, // 或 "60%" 等
top: 388,
z: 100, // 设置更高的图层层级
symbol: "circle",
style: {
text: "P2",
font: "bold 14px sans-serif",
fill: "#d62728",
// 显示在线条墙上面
distance: 10,
},
},
{
type: "text",
left: 430, // 或 "60%" 等
top: 380,
z: 100, // 设置更高的图层层级
symbol: "circle",
style: {
text: "P3",
font: "bold 14px sans-serif",
fill: "#ff7f0e",
// 显示在线条墙上面
distance: 10,
},
},
],
});
};
const submitTask = () => {
const minPicture = priceList2.value.map((item) => item.percentage1);
const minPriceValue = priceList2.value.map((item) => item.toocheapsum1);
const calculatePrice = priceList2.value.map((item) => item.percentage2);
const calculatevalue = priceList2.value.map((item) => item.toocheapsum2);
const minexpensive = priceList2.value.map((item) => item.percentage3);
const minexpensivevalue = priceList2.value.map((item) => item.toocheapsum3);
const expensive = priceList2.value.map((item) => item.percentage4);
const tooSum = priceList2.value.map((item) => item.toocheapsum4);
// 判断数组是否为空
if (minPicture.includes("") || minPriceValue.includes("") || calculatePrice.includes("") || calculatevalue.includes("") || minexpensive.includes("") || minexpensivevalue.includes("") || expensive.includes("") || priceList1.value.some((item) => !item.value)) {
return proxy.$modal.msgWarning("请填写完整数据");
}
// 判断formInline的值是否为空
if (formInline.value.optimalPrice == "" || formInline.value.acceptablePrice == "" || formInline.value.range1 == "" || formInline.value.range2 == "") {
return proxy.$modal.msgWarning("请填写完整数据");
}
const params = {
acceptablePrice: formInline.value.acceptablePrice,
acceptablePriceRange: formInline.value.range1 + "," + formInline.value.range2,
optimalPrice: formInline.value.optimalPrice,
cheapPercentage: minPicture.join(","),
cheapSum: minPriceValue.join(","),
context: textarea2.value,
costEffectivePercentage: calculatePrice.join(","),
costEffectiveSum: calculatevalue.join(","),
expensivePercentage: minexpensive.join(","),
expensiveSum: minexpensivevalue.join(","),
tooExpensivePercentage: expensive.join(","),
tooSum: tooSum.join(","),
priceRange: JSON.stringify(priceList1.value),
displayStatus: 1,
};
analysisAPI.productPricingSubmit({ stuAnswer: params, userId: userStore.userInfo.userId }).then((res) => {
proxy.$modal.msgSuccess("提交成功");
});
};
// 回显接口
const loadTaskInfo = () => {
analysisAPI.productPricingEcho({ userId: userStore.userInfo.userId }).then((res) => {
if (!res.data) return;
formInline.value.optimalPrice = res.data.optimalPrice;
formInline.value.acceptablePrice = res.data.acceptablePrice;
formInline.value.range1 = res.data.acceptablePriceRange.split(",")[0];
formInline.value.range2 = res.data.acceptablePriceRange.split(",")[1];
textarea2.value = res.data.context;
priceList1.value = JSON.parse(res.data.priceRange !== "" ? res.data.priceRange : priceList1.value);
priceList2.value.forEach((item, index) => {
item.toocheapsum1 = res.data.cheapSum.split(",")[index];
item.toocheapsum2 = res.data.costEffectiveSum.split(",")[index];
item.toocheapsum3 = res.data.expensiveSum.split(",")[index];
item.percentage1 = res.data.cheapPercentage.split(",")[index];
item.percentage2 = res.data.costEffectivePercentage.split(",")[index];
item.percentage3 = res.data.expensivePercentage.split(",")[index];
item.percentage4 = res.data.tooExpensivePercentage.split(",")[index];
item.toocheapsum4 = res.data.tooSum.split(",")[index];
});
draw();
});
};
// 重新实训
const resetTask = () => {
analysisAPI.productPricingReTrain({ userId: userStore.userInfo.userId, module: "产品定价" }).then((res) => {
proxy.$modal.msgSuccess("重新实训成功");
// 清楚数据
formInline.value.optimalPrice = "";
formInline.value.acceptablePrice = "";
formInline.value.range1 = "";
formInline.value.range2 = "";
textarea2.value = "";
// 清除数据
priceList1.value.forEach((item) => {
item.value = "";
});
priceList2.value.forEach((item) => {
item.toocheapsum1 = "";
item.toocheapsum2 = "";
item.toocheapsum3 = "";
item.toocheapsum4 = "";
item.percentage1 = "";
item.percentage2 = "";
item.percentage3 = "";
item.percentage4 = "";
});
// loadTaskInfo();
});
};
onMounted(() => {
loadTaskInfo();
});
</script>
<style lang='scss' scoped>
.app-container {
background-color: #f5f5f5;
:deep(.hidden-label) {
.el-radio__label {
display: none;
}
}
.cnetent {
display: flex;
.left {
width: 50%;
height: 100%;
}
.right {
width: 50%;
height: 100%;
}
}
}
</style>