本教程来源于来团科技真实开发项目,将详细介绍如何在小程序端实现会员价格功能,包括:后台新会员价格结构分析,前端数据结构设计,动态计算会员价格等。
来团智慧商业小程序零代码开发平台,多行业适配。无需代码,拖拽式设计,轻松打造订货商城、会员制商城、分销商城及小程序官网。不仅能满足通用需求,还支持定制化,从页面布局到功能模块,随心定制,助您快速搭建专属商业小程序,抢占市场先机。
本教程将详细介绍如何在小程序端实现会员价格功能,包括:
后台现在支持三种会员价格模式:
| 字段 | 说明 | 可选值 |
|---|---|---|
is_enable_grade |
是否开启会员价格 | 0=关闭, 1=开启 |
is_alone_grade |
会员价格设置模式 | 0=暂无折扣, 1=默认设置, 2=单独设置 |
grade_price_type |
价格类型 | 10=折扣, 20=金额 |
alone_grade_price |
SKU级别的会员价格 | JSON格式: {"等级ID": 价格/折扣} |
问题:后台返回的商品价格已经是计算后的会员价,小程序无法区分"原价"和"优惠价"
解决方案:
后台API返回的商品详情包含:
{
"detail": {
"goods_id": 1,
"goods_name": "商品名称",
"is_enable_grade": 1,
"is_alone_grade": 2,
"grade_price_type": 10,
"alone_grade_equity": {},
"is_user_grade": true,
"user_grade": {
"grade_id": 10001,
"name": "黄金会员"
},
"goods_sku": {
"spec_sku_id": "1_2_3",
"goods_price": 80,
"line_price": 100
},
"goods_multi_spec": {
"spec_list": [
{
"spec_sku_id": "1_2_3",
"form": {
"goods_price": 80,
"alone_grade_price": {
"10001": "80",
"10002": "70"
}
}
}
]
}
}
}
关键发现:API返回的价格已经是会员优惠价,我们需要保存原始价格用于显示。
在 index.js 的 data 中添加两个新字段:
data: {
// ... 其他字段
goods_price: 0, // 商品价格(显示价格,可能已优惠)
original_price: 0, // 原始价格(未优惠)- 新增
userGradeId: 0, // 用户会员等级ID - 新增
// ... 其他字段
}
设计说明:
| 字段 | 作用 |
|---|---|
original_price |
保存用户看到的"划线价/原价",用于判断是否享受优惠,切换规格时作为计算基准 |
userGradeId |
记录当前用户的会员等级,切换规格时根据等级查找对应的会员价格 |
_initGoodsDetailData 方法/**
* 初始化商品详情数据
*/
_initGoodsDetailData(data) {
let _this = this;
let goodsDetail = data.detail;
// 富文本转码
if (goodsDetail.content.length > 0) {
wxParse.wxParse('content', 'html', goodsDetail.content, _this, 0);
}
// 商品价格/划线价/库存
data.goods_sku_id = goodsDetail.goods_sku.spec_sku_id;
data.goods_price = goodsDetail.goods_sku.goods_price; // 优惠价
data.original_price = goodsDetail.goods_sku.goods_price; // ★ 保存为原价
data.line_price = goodsDetail.goods_sku.line_price;
data.stock_num = goodsDetail.goods_sku.stock_num;
// ★ 保存用户会员等级ID
data.userGradeId = goodsDetail.user_grade
? goodsDetail.user_grade.grade_id
: 0;
// 商品封面图(确认弹窗)
data.skuCoverImage = goodsDetail.goods_image;
// 多规格商品封面图(确认弹窗)
if (goodsDetail.spec_type == 20 && goodsDetail.goods_sku['image']) {
data.skuCoverImage = goodsDetail.goods_sku['image']['file_path'];
}
// 初始化商品多规格
if (goodsDetail.spec_type == 20) {
data.goodsMultiSpec = _this._initManySpecData(goodsDetail.goods_multi_spec);
}
return data;
}
常见问题解答:
Q: 为什么
goods_price和original_price初始值相同?
A: 因为API返回的价格已经是优惠价,我们需要保存这个值作为"参考原价"。后续在 _calcGradePrice 中会重新计算真正的原价。
Q:
user_grade可能不存在吗?
A: 是的!未登录用户或非会员用户没有 user_grade,所以要加三元运算判断。
这是整个功能最复杂的部分,需要在规格切换时重新计算价格。
_calcGradePrice 方法/**
* 计算会员价格
* @param {Object} skuItem - SKU规格项
* @returns {Object} 价格信息 {price, originalPrice}
*/
_calcGradePrice(skuItem) {
let detail = this.data.detail;
let result = {
price: skuItem.form.goods_price,
originalPrice: skuItem.form.goods_price
};
// 步骤1: 检查是否启用会员价格
if (!detail.is_enable_grade || !detail.is_user_grade) {
return result;
}
// 步骤2: 获取用户会员等级ID
let userGradeId = this.data.userGradeId;
if (!userGradeId) {
return result;
}
// 步骤3: 暂无折扣模式
if (detail.is_alone_grade == 0) {
return result;
}
// 步骤4: 获取商品原始价格
result.originalPrice = skuItem.form.goods_price;
// 步骤5: 根据模式计算会员价格
if (detail.is_alone_grade == 2) {
// ===== 单独设置模式 =====
let aloneGradePrice = skuItem.form.alone_grade_price || {};
if (aloneGradePrice && aloneGradePrice[userGradeId] !== undefined) {
let priceValue = aloneGradePrice[userGradeId];
if (detail.grade_price_type == 10) {
// 折扣类型
let discountRatio = priceValue / 10;
result.price = (skuItem.form.goods_price * discountRatio).toFixed(2);
} else {
// 金额类型
result.price = parseFloat(priceValue).toFixed(2);
}
}
} else if (detail.is_alone_grade == 1) {
// ===== 默认设置模式 =====
let gradeEquities = detail.alone_grade_equity || {};
let priceValue = gradeEquities[userGradeId];
if (priceValue === undefined && detail.user_grade && detail.user_grade.equity) {
priceValue = detail.user_grade.equity.discount;
}
if (priceValue !== undefined) {
if (detail.grade_price_type == 10) {
// 折扣类型
let discountRatio = priceValue / 10;
result.price = (skuItem.form.goods_price * discountRatio).toFixed(2);
} else {
// 金额类型
result.price = parseFloat(priceValue).toFixed(2);
}
}
}
return result;
}
/**
* 更新商品规格信息
*/
_updateSpecGoods() {
let _this = this,
specSkuId = goodsSpecArr.join('_');
// 查找当前选中的SKU
let spec_list = _this.data.goodsMultiSpec.spec_list,
skuItem = spec_list.find((val) => {
return val.spec_sku_id == specSkuId;
});
if (typeof skuItem === 'object') {
// 核心修改:调用_calcGradePrice计算会员价格
let gradePriceInfo = _this._calcGradePrice(skuItem);
_this.setData({
goods_sku_id: skuItem.spec_sku_id,
goods_price: gradePriceInfo.price,
original_price: gradePriceInfo.originalPrice,
line_price: skuItem.form.line_price,
stock_num: skuItem.form.stock_num,
skuCoverImage: skuItem.form.image_id > 0
? skuItem.form.image_path
: _this.data.detail.goods_image
});
}
}
用户点击切换规格
↓
_updateSpecGoods() 被调用
↓
找到对应的SKU项
↓
_calcGradePrice(skuItem) 被调用
↓
判断会员价格模式
↓
├─ 暂无折扣 → 返回原价
│
├─ 单独设置 → 从SKU的alone_grade_price取值
│ ├─ 折扣类型 → 价格 × (折扣值 / 10)
│ └─ 金额类型 → 直接使用金额
│
└─ 默认设置 → 从商品配置取值
├─ 折扣类型 → 价格 × (折扣值 / 10)
└─ 金额类型 → 直接使用金额
↓
setData 更新页面显示
<!-- 商品价格区域 -->
<view class="money-box dis-flex flex-y-center">
<!-- 会员价(已优惠) -->
<view class="goods-price"
wx:if="{{ detail.is_user_grade && goods_price < original_price }}">
<text class="col-m f-26">¥</text>
<text class="goods-price_num col-m f-40">{{ goods_price }}</text>
</view>
<!-- 非会员价 -->
<view class="goods-price" wx:else>
<text class="col-m f-26">¥</text>
<text class="goods-price_num col-m f-40">{{ goods_price }}</text>
</view>
<!-- 划线价(原价) -->
<view class="line-price" wx:if="{{line_price > 0}}">
<text wx:if="{{ detail.is_user_grade && goods_price < original_price }}">
¥{{ original_price }}
</text>
<text wx:else>
¥{{ line_price }}
</text>
</view>
<!-- 会员价标签 -->
<view wx:if="{{ detail.is_user_grade && goods_price < original_price }}"
class="tag-grade-price">
<text>会员价</text>
</view>
</view>
| 场景 | 显示效果 |
|---|---|
| 会员登录 + 有会员价 | 显示优惠价,划线显示原价,标签"会员价" |
| 会员登录 + 无会员价 | 显示正常价格 |
| 未登录 | 显示正常价格 |
┌─────────────────────────────────────────┐
│ 场景: 会员登录且有会员价 │
│ ┌────┐ ┌─────────┐ ┌──────────┐ │
│ │¥80│ │划线¥100│ │ [会员价] │ │
│ └────┘ └─────────┘ └──────────┘ │
├─────────────────────────────────────────┤
│ 场景: 非会员或无会员价 │
│ ┌────┐ ┌─────────┐ │
│ │¥80│ │划线¥100│ │
│ └────┘ └─────────┘ │
└─────────────────────────────────────────┘
为了让小程序端能获取到正确的 alone_grade_price 数据,需要修改API层的计算逻辑。
/**
* 设置商品的会员价
* @param $user 用户信息
* @param $goods 商品信息(引用传递)
*/
private function setGoodsGradeMoney($user, &$goods)
{
// 会员等级状态检查
$gradeStatus = (!empty($user) && $user['grade_id'] > 0
&& !empty($user['grade']) && !$user['grade']['is_delete']);
if (!$gradeStatus || !$goods['is_enable_grade']) {
$goods['is_user_grade'] = false;
return;
}
$userGradeId = $user['grade_id'];
// 模式判断
switch ($goods['is_alone_grade']) {
case 0: // 暂无折扣
$goods['is_user_grade'] = false;
return;
case 2: // 单独设置 - 使用SKU级别的价格
foreach ($goods['sku'] as &$skuItem) {
$aloneGradePrice = $skuItem['alone_grade_price'] ?: [];
if (!empty($aloneGradePrice) && isset($aloneGradePrice[$userGradeId])) {
$priceValue = $aloneGradePrice[$userGradeId];
if ($goods['grade_price_type'] == 10) {
// 折扣类型
$discountRatio = $priceValue / 10;
$skuItem['goods_price'] = helper::bcmul(
$skuItem['goods_price'],
$discountRatio
);
} else {
// 金额类型
$skuItem['goods_price'] = $priceValue;
}
$goods['is_user_grade'] = true;
}
}
return;
case 1: // 默认设置 - 使用全局折扣
$priceValue = $goods['alone_grade_equity'][$userGradeId]
?? $user['grade']['equity']['discount'];
if ($goods['grade_price_type'] == 10) {
$discountRatio = $priceValue / 10;
foreach ($goods['sku'] as &$skuItem) {
$skuItem['goods_price'] = helper::bcmul(
$skuItem['goods_price'],
$discountRatio
);
}
} else {
foreach ($goods['sku'] as &$skuItem) {
$skuItem['goods_price'] = $priceValue;
}
}
$goods['is_user_grade'] = true;
return;
}
}
┌─────────────────────────────────────────────────────────────────┐
│ 数据流向 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户打开商品页 │
│ ↓ │
│ API返回数据(价格已是优惠价) │
│ ↓ │
│ _initGoodsDetailData 保存 original_price │
│ ↓ │
│ 用户选择规格 │
│ ↓ │
│ _updateSpecGoods → _calcGradePrice │
│ ↓ │
│ 根据会员配置重新计算价格 │
│ ↓ │
│ setData 更新显示 │
│ ↓ │
│ 用户看到原价和优惠价 │
│ │
└─────────────────────────────────────────────────────────────────┘
| 知识点 | 说明 |
|---|---|
| 数据保存 | 初始时保存原始价格用于后续比较 |
| 条件渲染 | wx:if 用于区分会员/非会员场景 |
| 方法封装 | _calcGradePrice 独立封装计算逻辑 |
| API配合 | 后端需要返回 SKU 级别的 alone_grade_price |
尝试自己实现以下功能:
在CSS中添加删除线效果:
.original-price {
text-decoration: line-through;
color: #999;
}
在wxml中使用:
<text class="original-price">¥{{ original_price }}</text>
计算并显示"节省¥XX":
// 在_calcGradePrice中添加
result.saveMoney = (result.originalPrice - result.price).toFixed(2);
在wxml中显示:
<view class="save-money" wx:if="{{saveMoney > 0}}">
节省¥{{saveMoney}}
</view>
当会员价库存不足时显示提示:
// 检查SKU库存
if (skuItem.form.stock_num <= 0) {
App.showError('该规格会员价已售罄');
}
| 文件路径 | 修改内容 |
|---|---|
wxapp/pages/goods/index.js |
添加 original_price、userGradeId,新增 _calcGradePrice 方法 |
wxapp/pages/goods/index.wxml |
修改价格显示区域,支持原价划线 |
source/application/api/model/Goods.php |
重写 setGoodsGradeMoney 方法 |
source/application/api/model/sharing/Goods.php |
重写 setGoodsGradeMoney 方法 |
检查以下内容:
goodsSpecArr 全局变量是否正确更新_updateSpecGoods 方法是否被调用setData 是否正确执行排查步骤:
userGradeId 是否有值alone_grade_price 数据格式是否正确grade_price_type 值是否正确(10=折扣,20=金额)检查逻辑:
// 确保有用户等级ID
if (!userGradeId) {
return result; // 非会员直接返回
}
本教程涵盖了小程序商城会员价格功能的核心实现,通过保存原始价格、动态计算会员价、灵活显示等步骤,实现了友好的会员价格展示效果。
来团科技GEO优化&AI搜索优化系统,是通过大模型内容投喂+训练,将企业品牌及产品信息在多平台AI生成的答案中获取优先展现,更精准触达潜在目标客户,让企业品牌出现在AI搜索里。让客户一搜就看到你,实现一问就有你,一查就信你,一看就找你的营销效果。
来团智慧商业小程序零代码开发平台,多行业适配。无需代码,拖拽式设计,轻松打造订货商城、会员制商城、分销商城及小程序官网。不仅能满足通用需求,还支持定制化,从页面布局到功能模块,随心定制,助您快速搭建专属商业小程序,抢占市场先机。
来团科技微名通不止是电子名片,更是你的商业连接器。比起传统名片,它更像你的 “迷你商业工具”:信息多、好携带、能互动,还不浪费纸张。不管是跑业务、拓人脉,还是展示企业,一张「微名通」电子名片,就能帮你把商机揣在手机里。
来团科技CRM客户管理系统,帮你把 “线索→成交→回款” 全流程管明白。这就是一套 “让销售省心、老板放心” 的客户管理工具,从获客到回款,帮你把生意攥在手里。