付费会员价格 #110

Open
khy wants to merge 1 commits from khy-two into master
29 changed files with 819 additions and 49 deletions
Showing only changes of commit 55fe8612c5 - Show all commits

View File

@ -23,6 +23,7 @@ export interface Sku {
firstBrokeragePrice?: number | string // 一级分销的佣金
secondBrokeragePrice?: number | string // 二级分销的佣金
salesCount?: number // 商品销量
paidMemberPrice?: number | string //付费会员价
}
export interface GiveCouponTemplate {
@ -58,6 +59,8 @@ export interface Spu {
status?: number // 商品状态
pointsStatus?: number // 商品使用积分状态
usePointsMax?: number // 单商品积分使用上限
isPaidMemberPrice?: number//是否设置付费会员价
paidMemberPrice?: number//会员价
}
// 获得 Spu 列表

View File

@ -0,0 +1,470 @@
<!-- 情况一添加/修改 -->
<template>
<template v-if="!isDetail && isBatch">
<el-input v-model="formData.paidMemberPrice"
placeholder="请输入会员价" label="会员价" style="width: 180px; margin-right: 10px;"/>
<el-button type="primary" @click="batchAdd">
批量添加
</el-button>
</template>
<el-table
v-if="!isDetail && !isBatch"
:data="isBatch ? skuList : formData!.skus!"
border
class="tabNumWidth"
max-height="500"
size="small"
>
<template v-if="formData!.specType && !isBatch">
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for="(item, index) in tableHeaders"
:key="index"
:label="item.label"
align="center"
min-width="120"
>
<template #default="{ row }">
<span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template>
</el-table-column>
</template>
<el-table-column align="center" label="图片" min-width="65">
<template #default="{ row }">
<el-image
v-if="row.picUrl"
:src="row.picUrl"
class="h-50px w-50px"
@click="imagePreview(row.picUrl)"
/>
</template>
</el-table-column>
<el-table-column align="center" label="售价" min-width="168">
<template #default="{ row }">
{{row.price}}
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="168">
<template #default="{ row }">
{{row.stock}}
</template>
</el-table-column>
<template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.firstBrokeragePrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.secondBrokeragePrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
</template>
<template v-if="formData!.isPaidMemberPrice === 1">
<el-table-column align="center" label="会员价" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.paidMemberPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
</template>
</el-table>
<!-- 情况二详情-->
<template v-if="isDetail && isBatch">
<el-input v-model="formData.paidMemberPrice"
placeholder="请输入会员价" label="会员价" style="width: 180px; margin-right: 10px;"/>
<el-button type="primary" @click="batchAdd">
批量添加
</el-button>
</template>
<el-table
v-if="isDetail"
ref="activitySkuListRef"
:data="formData!.skus!"
border
max-height="500"
size="small"
style="width: 99%"
@selection-change="handleSelectionChange"
>
<el-table-column v-if="isComponent" type="selection" width="45" />
<template v-if="formData!.specType && !isBatch">
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for="(item, index) in tableHeaders"
:key="index"
:label="item.label"
align="center"
min-width="80"
>
<template #default="{ row }">
<span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template>
</el-table-column>
</template>
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image
v-if="row.picUrl"
:src="row.picUrl"
class="h-50px w-50px"
@click="imagePreview(row.picUrl)"
/>
</template>
</el-table-column>
<el-table-column align="center" label="售价(元)" min-width="80">
<template #default="{ row }">
{{ row.price }}
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="80">
<template #default="{ row }">
{{ row.stock }}
</template>
</el-table-column>
<template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="80">
<template #default="{ row }">
{{ row.firstBrokeragePrice }}
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="80">
<template #default="{ row }">
{{ row.secondBrokeragePrice }}
</template>
</el-table-column>
</template>
<template v-if="formData!.isPaidMemberPrice === 1">
<el-table-column align="center" label="会员价" min-width="168">
<template #default="{ row }">
{{row.paidMemberPrice}}
</template>
</el-table-column>
</template>
</el-table>
</template>
<script lang="ts" setup>
import { PropType, Ref } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile'
import type { Property, Sku, Spu } from '@/api/mall/product/spu'
import { createImageViewer } from '@/components/ImageViewer'
import { RuleConfig } from '@/views/mall/product/spu/components/index'
import { PropertyAndValues } from './index'
import { ElTable } from 'element-plus'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'MarkingtingList' })
const message = useMessage() //
const props = defineProps({
propFormData: {
type: Object as PropType<Spu>,
default: () => {}
},
propertyList: {
type: Array as PropType<PropertyAndValues[]>,
default: () => []
},
ruleConfig: {
type: Array as PropType<RuleConfig[]>,
default: () => []
},
isBatch: propTypes.bool.def(false), //
isDetail: propTypes.bool.def(false), // sku
isComponent: propTypes.bool.def(false), // sku
isActivityComponent: propTypes.bool.def(false) // sku
})
const formData: Ref<Spu | undefined> = ref<Spu>() //
const skuList = ref<Sku[]>([
{
price: 0, //
marketPrice: 0, //
costPrice: 0, //
barCode: '', //
picUrl: '', //
stock: 0, //
weight: 0, //
volume: 0, //
firstBrokeragePrice: 0, //
secondBrokeragePrice: 0, //
paidMemberPrice: 0 //
}
]) //
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
zIndex: 9999999,
urlList: [imgUrl]
})
}
/** 批量添加 */
const batchAdd = () => {
validateProperty()
formData.value!.skus!.forEach((item) => {
console.log("item:",item,"formSkus:",formData.value!.paidMemberPrice)
item.paidMemberPrice = formData.value!.paidMemberPrice
// copyValueToTarget(item.paidMemberPrice, formData.value!.paidMemberPrice)
})
}
/** 校验商品属性属性值 */
const validateProperty = () => {
//
const warningInfo = '存在属性属性值为空,请先检查完善属性值后重试!!!'
for (const item of props.propertyList) {
if (!item.values || isEmpty(item.values)) {
message.warning(warningInfo)
throw new Error(warningInfo)
}
}
}
/** 删除 sku */
const deleteSku = (row) => {
const index = formData.value!.skus!.findIndex(
//
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
formData.value!.skus!.splice(index, 1)
}
const tableHeaders = ref<{ prop: string; label: string }[]>([]) //
/**
* 保存时每个商品规格的表单要校验下例如说销售金额最低是 0.01 这种
*/
const validateSku = () => {
validateProperty()
let warningInfo = '请检查商品各行相关属性配置,'
let validate = true //
for (const sku of formData.value!.skus!) {
//
for (const rule of props?.ruleConfig) {
const arg = getValue(sku, rule.name)
if (!rule.rule(arg)) {
validate = false //
warningInfo += rule.message
break
}
}
//
if (!validate) {
message.warning(warningInfo)
throw new Error(warningInfo)
}
}
}
const getValue = (obj, arg) => {
const keys = arg.split('.')
let value = obj
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key]
} else {
value = undefined
break
}
}
return value
}
const emit = defineEmits<{
(e: 'selectionChange', value: Sku[]): void
}>()
/**
* 选择时触发
* @param Sku 传递过来的选中的 sku 是一个数组
*/
const handleSelectionChange = (val: Sku[]) => {
emit('selectionChange', val)
}
/**
* 将传进来的值赋值给 skuList
*/
watch(
() => props.propFormData,
(data) => {
if (!data) return
formData.value = data
},
{
deep: true,
immediate: true
}
)
/** 生成表数据 */
const generateTableData = (propertyList: any[]) => {
//
const propertyValues = propertyList.map((item) =>
item.values.map((v: any) => ({
propertyId: item.id,
propertyName: item.name,
valueId: v.id,
valueName: v.name
}))
)
const buildSkuList = build(propertyValues)
// sku skus
if (!validateData(propertyList)) {
// sku
formData.value!.skus = []
}
for (const item of buildSkuList) {
const row = {
properties: Array.isArray(item) ? item : [item], // property
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
firstBrokeragePrice: 0,
secondBrokeragePrice: 0
}
// sku
const index = formData.value!.skus!.findIndex(
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
if (index !== -1) {
continue
}
formData.value!.skus!.push(row)
}
}
/**
* 生成 skus 前置校验
*/
const validateData = (propertyList: any[]) => {
const skuPropertyIds: number[] = []
formData.value!.skus!.forEach((sku) =>
sku.properties
?.map((property) => property.propertyId)
?.forEach((propertyId) => {
if (skuPropertyIds.indexOf(propertyId!) === -1) {
skuPropertyIds.push(propertyId!)
}
})
)
const propertyIds = propertyList.map((item) => item.id)
return skuPropertyIds.length === propertyIds.length
}
/** 构建所有排列组合 */
const build = (propertyValuesList: Property[][]) => {
if (propertyValuesList.length === 0) {
return []
} else if (propertyValuesList.length === 1) {
return propertyValuesList[0]
} else {
const result: Property[][] = []
const rest = build(propertyValuesList.slice(1))
for (let i = 0; i < propertyValuesList[0].length; i++) {
for (let j = 0; j < rest.length; j++) {
//
if (Array.isArray(rest[j])) {
result.push([propertyValuesList[0][i], ...rest[j]])
} else {
result.push([propertyValuesList[0][i], rest[j]])
}
}
}
return result
}
}
/** 监听属性列表,生成相关参数和表头 */
watch(
() => props.propertyList,
(propertyList: PropertyAndValues[]) => {
//
if (!formData.value!.specType) {
return
}
// 使
if (props.isBatch) {
skuList.value = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
firstBrokeragePrice: 0,
secondBrokeragePrice: 0
}
]
}
//
if (JSON.stringify(propertyList) === '[]') {
return
}
//
tableHeaders.value = []
//
propertyList.forEach((item, index) => {
// nameindex
tableHeaders.value.push({ prop: `name${index}`, label: item.name })
})
// sku
if (validateData(propertyList)) {
return
}
//
if (propertyList.some((item) => !item.values || isEmpty(item.values))) {
return
}
// table sku
generateTableData(propertyList)
},
{
deep: true,
immediate: true
}
)
const activitySkuListRef = ref<InstanceType<typeof ElTable>>()
const getSkuTableRef = () => {
return activitySkuListRef.value
}
// sku
defineExpose({ generateTableData, validateSku, getSkuTableRef })
</script>

View File

@ -1,4 +1,5 @@
import SkuList from './SkuList.vue'
import MarkingtingList from './MarkingtingList.vue'
import { Spu } from '@/api/mall/product/spu'
interface PropertyAndValues {
@ -51,4 +52,4 @@ const getPropertyList = (spu: Spu): PropertyAndValues[] => {
return properties
}
export { SkuList, PropertyAndValues, RuleConfig, getPropertyList }
export { MarkingtingList, SkuList, PropertyAndValues, RuleConfig, getPropertyList }

View File

@ -2,54 +2,84 @@
<template>
<el-form ref="formRef" :disabled="isDetail" :model="formData" :rules="rules" label-width="120px">
<el-form-item label="付费会员专属" >
<el-switch
v-model="formData.onlyPaidMemberView"
:active-value="1"
<el-switch
v-model="formData.onlyPaidMemberView"
:active-value="1"
:inactive-value="0" />
&nbsp;&nbsp;&nbsp;<el-text class="mx-1" size="small">开启后仅付费会员可以看见并购买此商品</el-text>
&nbsp;&nbsp;&nbsp;<el-text class="mx-1" size="small">开启后仅付费会员可以看见并购买此商品</el-text>
</el-form-item>
<el-form-item label="单独设置" >
<el-checkbox
v-model="formData.isPaidMemberPrice"
:true-value="1"
:false-value="0" >付费会员价</el-checkbox>
</el-form-item>
<template v-if="formData.isPaidMemberPrice === 1 ">
<el-form-item label="批量设置" >
<!-- <el-input v-model="formData.paidMemberPrice"-->
<!-- placeholder="请输入会员价" label="会员价" style="width: 180px; margin-right: 10px;"/>-->
<!-- <el-button type="primary" @click="batchAdd">-->
<!-- 批量添加-->
<!-- </el-button>-->
<MarkingtingList ref="skuListRef"
:is-batch="true"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
<el-form-item label="商品属性">
<MarkingtingList
ref="skuListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:property-list="propertyList"
:rule-config="ruleConfig"
/>
</el-form-item>
</template>
<el-form-item label="商品推荐">
<el-checkbox
v-model="formData.recommendHot"
:true-value="1"
:false-value="0"
:true-value="1"
:false-value="0"
name="productRecommend">
热卖单品
</el-checkbox>
<el-checkbox
v-model="formData.recommendBenefit"
:true-value="1"
:false-value="0"
<el-checkbox
v-model="formData.recommendBenefit"
:true-value="1"
:false-value="0"
name="productRecommend">
促销单品
</el-checkbox>
<el-checkbox
v-model="formData.recommendBest"
:true-value="1"
:false-value="0"
<el-checkbox
v-model="formData.recommendBest"
:true-value="1"
:false-value="0"
name="productRecommend">
精品推荐
</el-checkbox>
<el-checkbox
v-model="formData.recommendNew"
:true-value="1"
:false-value="0"
<el-checkbox
v-model="formData.recommendNew"
:true-value="1"
:false-value="0"
name="productRecommend">
首发新品
</el-checkbox>
<el-checkbox
v-model="formData.recommendGood"
:true-value="1"
:false-value="0"
<el-checkbox
v-model="formData.recommendGood"
:true-value="1"
:false-value="0"
name="productRecommend">
优品推荐
</el-checkbox>
</el-form-item>
</el-form>
<!-- 商品属性添加 Form 表单 -->
<ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
<!-- &lt;!&ndash; 商品属性添加 Form 表单 &ndash;&gt;-->
<!-- <ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" />-->
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
@ -59,11 +89,12 @@ import {
getPropertyList,
PropertyAndValues,
RuleConfig,
SkuList
MarkingtingList
} from '@/views/mall/product/spu/components/index'
import ProductAttributes from './ProductAttributes.vue'
import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
import type { Spu } from '@/api/mall/product/spu'
import {isEmpty} from "@/utils/is";
defineOptions({ name: 'ProductSpuSkuForm' })
@ -75,8 +106,18 @@ const props = defineProps({
type: Object as PropType<Spu>,
default: () => {}
},
isDetail: propTypes.bool.def(false) //
isBatch: propTypes.bool.def(false), //
isDetail: propTypes.bool.def(false), // sku
isComponent: propTypes.bool.def(false), // sku
isActivityComponent: propTypes.bool.def(false) // sku
})
const ruleConfig: RuleConfig[] = [
{
name: 'paidMemberPrice',
rule: (arg) => arg >= 0,
message: '会员价必须大于等于 0 '
}
]
const attributesAddFormRef = ref() //
const formRef = ref() // Ref
const propertyList = ref<PropertyAndValues[]>([]) //
@ -87,7 +128,12 @@ const formData = reactive<Spu>({
recommendBest: 0, //
recommendNew: 0, //
recommendGood: 0, //
onlyPaidMemberView: 0 //
onlyPaidMemberView: 0, //
isPaidMemberPrice: 0, //
paidMemberPrice: 0, //
specType: false, //
subCommissionType: false, //
skus: []
})
const rules = reactive({
// specType: [required],

View File

@ -146,6 +146,7 @@ const getDetail = async () => {
item.price = floatToFixed2(item.price)
item.marketPrice = floatToFixed2(item.marketPrice)
item.costPrice = floatToFixed2(item.costPrice)
item.paidMemberPrice = floatToFixed2(item.paidMemberPrice)
item.firstBrokeragePrice = floatToFixed2(item.firstBrokeragePrice)
item.secondBrokeragePrice = floatToFixed2(item.secondBrokeragePrice)
} else {
@ -153,11 +154,18 @@ const getDetail = async () => {
item.price = formatToFraction(item.price)
item.marketPrice = formatToFraction(item.marketPrice)
item.costPrice = formatToFraction(item.costPrice)
item.paidMemberPrice = formatToFraction(item.paidMemberPrice)
item.firstBrokeragePrice = formatToFraction(item.firstBrokeragePrice)
item.secondBrokeragePrice = formatToFraction(item.secondBrokeragePrice)
}
})
if (isDetail.value){
res.paidMemberPrice = floatToFixed2(res.paidMemberPrice)
} else {
res.paidMemberPrice = formatToFraction(res.paidMemberPrice)
}
formData.value = res
} finally {
formLoading.value = false
@ -186,6 +194,7 @@ const submitForm = async () => {
item.price = convertToInteger(item.price)
item.marketPrice = convertToInteger(item.marketPrice)
item.costPrice = convertToInteger(item.costPrice)
item.paidMemberPrice = convertToInteger(item.paidMemberPrice)
item.firstBrokeragePrice = convertToInteger(item.firstBrokeragePrice)
item.secondBrokeragePrice = convertToInteger(item.secondBrokeragePrice)
})

View File

@ -39,6 +39,11 @@ public class ProductSkuRespDTO {
* 成本价单位
*/
private Integer costPrice;
/**
* 付费会员价格单位
*
*/
private Integer paidMemberPrice;
/**
* SKU 的条形码
*/

View File

@ -68,6 +68,11 @@ public class ProductSpuRespDTO {
* 库存
*/
private Integer stock;
/**
* 付费会员价格单位
*
*/
private Integer paidMemberPrice;
// ========== 物流相关字段 =========

View File

@ -48,4 +48,6 @@ public class ProductSkuRespVO {
@Schema(description = "属性数组")
private List<ProductSkuSaveReqVO.Property> properties;
@Schema(description = "商品价格,单位:分", example = "199")
private Integer paidMemberPrice;
}

View File

@ -50,6 +50,9 @@ public class ProductSkuSaveReqVO {
@Schema(description = "二级分销的佣金,单位:分", example = "19")
private Integer secondBrokeragePrice;
@Schema(description = "商品价格,单位:分", example = "199")
private Integer paidMemberPrice;
@Schema(description = "属性数组")
private List<Property> properties;

View File

@ -89,6 +89,14 @@ public class ProductSpuRespVO {
@Schema(description = "是否仅付费会员可见")
@ExcelProperty("是否仅付费会员可见")
private Integer onlyPaidMemberView;
@Schema(description = "是否为付费会员价")
@ExcelProperty("是否为付费会员价")
private Integer isPaidMemberPrice;
@Schema(description = " 商品价格,单位:分")
@ExcelProperty("商品价格,单位:分")
private Integer paidMemberPrice;
// ========== SKU 相关字段 =========
@Schema(description = "规格类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")

View File

@ -122,4 +122,12 @@ public class ProductSpuSaveReqVO {
@Schema(description = "是否仅付费会员可见")
@Valid
private Integer onlyPaidMemberView;
@Schema(description = "是否为付费会员价")
@Valid
private Integer isPaidMemberPrice;
@Schema(description = " 商品价格,单位:分")
@Valid
private Integer paidMemberPrice;
}

View File

@ -78,9 +78,20 @@ public class AppProductSpuController {
// 拼接返回
list.forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount()));
List<AppProductSpuRespVO> voList = BeanUtils.toBean(list, AppProductSpuRespVO.class);
// 处理 vip 价格
MemberLevelRespDTO memberLevel = getMemberLevel();
voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
//判断当前用户是否为付费会员
if (memberUserApi.validUserActivate(getLoginUserId())){
voList.forEach(vo -> {
Integer priceToUse = vo.getPaidMemberPrice() != null ? vo.getPaidMemberPrice() : vo.getPrice();
vo.setVipPrice(calculateVipPrice(priceToUse, memberLevel));
});
}
else {
voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
}
return success(voList);
}
@ -97,7 +108,16 @@ public class AppProductSpuController {
PageResult<AppProductSpuRespVO> voPageResult = BeanUtils.toBean(pageResult, AppProductSpuRespVO.class);
// 处理 vip 价格
MemberLevelRespDTO memberLevel = getMemberLevel();
voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
//判断当前用户是否为付费会员
if (memberUserApi.validUserActivate(getLoginUserId())){
voPageResult.getList().forEach(vo -> {
Integer priceToUse = vo.getPaidMemberPrice() != null ? vo.getPaidMemberPrice() : vo.getPrice();
vo.setVipPrice(calculateVipPrice(priceToUse, memberLevel));
});
}
else {
voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
}
return success(voPageResult);
}
@ -114,7 +134,16 @@ public class AppProductSpuController {
PageResult<AppProductSpuRespVO> voPageResult = BeanUtils.toBean(pageResult, AppProductSpuRespVO.class);
// 处理 vip 价格
MemberLevelRespDTO memberLevel = getMemberLevel();
voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
//判断当前用户是否为付费会员
if (memberUserApi.validUserActivate(getLoginUserId())){
voPageResult.getList().forEach(vo -> {
Integer priceToUse = vo.getPaidMemberPrice() != null ? vo.getPaidMemberPrice() : vo.getPrice();
vo.setVipPrice(calculateVipPrice(priceToUse, memberLevel));
});
}
else {
voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
}
return success(voPageResult);
}
@ -146,7 +175,13 @@ public class AppProductSpuController {
spuVO.setProductBrand(productBrandService.getBrandByIdStatus(spu.getBrandId(), CommonStatusEnum.ENABLE.getStatus()));
// 处理 vip 价格
MemberLevelRespDTO memberLevel = getMemberLevel();
spuVO.setVipPrice(calculateVipPrice(spuVO.getPrice(), memberLevel));
//判断当前用户是否为付费会员
if (memberUserApi.validUserActivate(getLoginUserId()) && spuVO.getPaidMemberPrice() != null){
spuVO.setVipPrice(calculateVipPrice(spuVO.getPaidMemberPrice(), memberLevel));
}
else {
spuVO.setVipPrice(calculateVipPrice(spuVO.getPrice(), memberLevel));
}
return success(spuVO);
}
@ -215,7 +250,16 @@ public class AppProductSpuController {
List<AppProductSpuRespVO> voList = BeanUtils.toBean(list, AppProductSpuRespVO.class);
// 处理 vip 价格
MemberLevelRespDTO memberLevel = getMemberLevel();
voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
//判断当前用户是否为付费会员
if (memberUserApi.validUserActivate(getLoginUserId())){
voList.forEach(vo -> {
Integer priceToUse = vo.getPaidMemberPrice() != null ? vo.getPaidMemberPrice() : vo.getPrice();
vo.setVipPrice(calculateVipPrice(priceToUse, memberLevel));
});
}
else {
voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel)));
}
return success(voList);
}

View File

@ -53,6 +53,9 @@ public class AppProductSpuDetailRespVO {
@Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级计算出折扣后价格
private Integer vipPrice;
@Schema(description = "付费会员价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer paidMemberPrice;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
private Integer stock;
@ -87,6 +90,9 @@ public class AppProductSpuDetailRespVO {
@Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级计算出折扣后价格
private Integer vipPrice;
@Schema(description = "付费会员价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer paidMemberPrice;
@Schema(description = "图片地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
private String picUrl;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -41,6 +42,9 @@ public class AppProductSpuRespVO {
@Schema(description = "VIP 价格,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "968") // 通过会员等级计算出折扣后价格
private Integer vipPrice;
@Schema(description = "付费会员价,单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer paidMemberPrice;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
private Integer stock;

View File

@ -86,6 +86,11 @@ public class ProductSkuDO extends BaseDO {
*/
private Integer secondBrokeragePrice;
/**
* 付费会员价格单位
*/
private Integer paidMemberPrice;
// ========== 营销相关字段 =========
// ========== 统计相关字段 =========

View File

@ -121,6 +121,12 @@ public class ProductSpuDO extends BaseDO {
*/
private Integer onlyPaidMemberView;
/**
* 是否为付费会员价
*/
private Integer isPaidMemberPrice;
// ========== SKU 相关字段 =========
/**
@ -148,6 +154,11 @@ public class ProductSpuDO extends BaseDO {
* 基于其对应的 {@link ProductSkuDO#getCostPrice()} sku单价最低的商品的
*/
private Integer costPrice;
/**
* 付费会员价格单位
* 基于其对应的 {@link ProductSkuDO#getPaidMemberPrice()} ()} sku单价最低的商品的
*/
private Integer paidMemberPrice;
/**
* 库存
*

View File

@ -113,6 +113,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
spu.setMarketPrice(getMinValue(skus, ProductSkuSaveReqVO::getMarketPrice));
// sku 单价最低的商品的成本价格
spu.setCostPrice(getMinValue(skus, ProductSkuSaveReqVO::getCostPrice));
// sku 单价最低的商品的付费会员价格
spu.setPaidMemberPrice(getMinValue(skus, ProductSkuSaveReqVO::getPaidMemberPrice));
// skus 库存总数
spu.setStock(getSumValue(skus, ProductSkuSaveReqVO::getStock, Integer::sum));
// 若是 spu 已有状态则不处理

View File

@ -23,6 +23,9 @@ public class AppProductSkuBaseRespVO {
@Schema(description = "销售价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer price;
@Schema(description = "付费会员价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer paidMemberPrice;
@Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer stock;

View File

@ -61,6 +61,10 @@ public class AppTradeOrderSettlementRespVO {
private Integer skuId;
@Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer price;
@Schema(description = "付费会员价(总),单位使用:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer paidMemberPrice;
@Schema(description = "图片地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
private String picUrl;

View File

@ -252,7 +252,7 @@ public interface TradeOrderConvert {
return respVO;
}
AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, MemberAddressRespDTO address);
AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, MemberAddressRespDTO address);
List<AppOrderExpressTrackRespDTO> convertList02(List<ExpressTrackRespDTO> list);

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.trade.service.price;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
@ -57,6 +58,9 @@ public class TradePriceServiceImpl implements TradePriceService {
@Resource
private TradeDiscountActivityPriceCalculator discountActivityPriceCalculator;
@Resource
private MemberUserApi memberUserApi;
@Override
public TradePriceCalculateRespBO calculateOrderPrice(TradePriceCalculateReqBO calculateReqBO) {
// 1.1 获得商品 SKU 数组
@ -64,9 +68,11 @@ public class TradePriceServiceImpl implements TradePriceService {
// 1.2 获得商品 SPU 数组
List<ProductSpuRespDTO> spuList = checkSpuList(skuList);
//判断是否为付费会员
Boolean valid = memberUserApi.validUserActivate(calculateReqBO.getUserId());
// 2.1 计算价格
TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper
.buildCalculateResp(calculateReqBO, spuList, skuList);
.buildCalculateResp(calculateReqBO, spuList, skuList,valid);
priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO));
// 2.2 如果最终支付金额小于等于 0则抛出业务异常
if (calculateReqBO.getPointActivityId() == null // 积分订单允许支付金额为 0

View File

@ -97,7 +97,7 @@ public class TradePriceCalculateRespBO {
/**
* 商品原价单位
*
* 基于 {@link OrderItem#getPrice()} * {@link OrderItem#getCount()} 求和
* 基于 {@link OrderItem#getPrice()} || {@link OrderItem#getPaidMemberPrice()} * {@link OrderItem#getCount()} 求和
*
* 对应 taobao trade.total_fee 字段
*/
@ -206,10 +206,15 @@ public class TradePriceCalculateRespBO {
* VIP 减免金额单位
*/
private Integer vipPrice;
/**
* 付费会员价格()单位
*/
private Integer paidMemberPrice;
/**
* 应付金额单位
*
* = {@link #price} * {@link #count}
* = {@link #price} || {@link #paidMemberPrice}
* * {@link #count}
* - {@link #couponPrice}
* - {@link #pointPrice}
* - {@link #discountPrice}

View File

@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
@ -43,6 +44,9 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
@Resource
private CouponApi couponApi;
@Resource
private MemberUserApi memberUserApi;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 只有普通订单才允许使用优惠劵
@ -90,9 +94,18 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
orderItem.setCouponPrice(divideCouponPrices.get(i));
TradePriceCalculatorHelper.recountPayPrice(orderItem);
if (memberUserApi.validUserActivate(param.getUserId()) && orderItem.getPaidMemberPrice() != null){
TradePriceCalculatorHelper.recountPayPriceisPaidMember(orderItem);
}
else {
TradePriceCalculatorHelper.recountPayPrice(orderItem);
}
}
if (memberUserApi.validUserActivate(param.getUserId()) ){
TradePriceCalculatorHelper.recountAllPriceisPaidMember(result);
}else {
TradePriceCalculatorHelper.recountAllPrice(result);
}
TradePriceCalculatorHelper.recountAllPrice(result);
}

View File

@ -142,10 +142,16 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
// 更新 SKU 的优惠金额
orderItem.setVipPrice(vipPrice);
}
// 4. 分摊优惠
TradePriceCalculatorHelper.recountPayPrice(orderItem);
TradePriceCalculatorHelper.recountAllPrice(result);
//判断是否为会员且具有会员价
if(memberUserApi.validUserActivate(param.getUserId()) && orderItem.getPaidMemberPrice() != null){
TradePriceCalculatorHelper.recountPayPriceisPaidMember(orderItem);
TradePriceCalculatorHelper.recountAllPriceisPaidMember(result);
}
else {
TradePriceCalculatorHelper.recountPayPrice(orderItem);
TradePriceCalculatorHelper.recountAllPrice(result);
}
});
}

View File

@ -78,9 +78,18 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
TradePriceCalculateRespBO.OrderItem orderItem = orderItems.get(i);
orderItem.setPointPrice(dividePointPrices.get(i));
orderItem.setUsePoint(divideUsePoints.get(i));
TradePriceCalculatorHelper.recountPayPrice(orderItem);
if(memberUserApi.validUserActivate(param.getUserId()) && orderItem.getPaidMemberPrice() != null ){
TradePriceCalculatorHelper.recountPayPriceisPaidMember(orderItem);
}
else{
TradePriceCalculatorHelper.recountPayPrice(orderItem);
}
}
if(memberUserApi.validUserActivate(param.getUserId())){
TradePriceCalculatorHelper.recountAllPriceisPaidMember(result);
}else {
TradePriceCalculatorHelper.recountAllPrice(result);
}
TradePriceCalculatorHelper.recountAllPrice(result);
}
private boolean isDeductPointEnable(MemberConfigRespDTO config) {

View File

@ -29,7 +29,7 @@ import static java.util.Collections.singletonList;
public class TradePriceCalculatorHelper {
public static TradePriceCalculateRespBO buildCalculateResp(TradePriceCalculateReqBO param,
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList) {
List<ProductSpuRespDTO> spuList, List<ProductSkuRespDTO> skuList,Boolean valid) {
// 创建 PriceCalculateRespDTO 对象
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO();
result.setType(getOrderType(param)).setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>());
@ -53,8 +53,15 @@ public class TradePriceCalculatorHelper {
orderItem.setSpuId(sku.getSpuId()).setSkuId(sku.getId())
.setCount(item.getCount()).setCartId(item.getCartId()).setSelected(item.getSelected());
// sku 价格
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0).setVipPrice(0);
//判断当前订单的用户是否为付费会员
if(valid && sku.getPaidMemberPrice() !=null){
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPaidMemberPrice() * item.getCount())
.setPaidMemberPrice(sku.getPaidMemberPrice())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0).setVipPrice(0);
}else {
orderItem.setPrice(sku.getPrice()).setPayPrice(sku.getPrice() * item.getCount())
.setDiscountPrice(0).setDeliveryPrice(0).setCouponPrice(0).setPointPrice(0).setVipPrice(0);
}
// sku 信息
orderItem.setPicUrl(sku.getPicUrl()).setProperties(sku.getProperties())
.setWeight(sku.getWeight()).setVolume(sku.getVolume());
@ -69,7 +76,12 @@ public class TradePriceCalculatorHelper {
// 创建它的 Price 属性
result.setPrice(new TradePriceCalculateRespBO.Price());
recountAllPrice(result);
if(valid){
recountAllPriceisPaidMember(result);
}
else {
recountAllPrice(result);
}
recountAllGivePoint(result);
return result;
}
@ -96,6 +108,36 @@ public class TradePriceCalculatorHelper {
return TradeOrderTypeEnum.NORMAL.getType();
}
/**
* 基于订单项重新计算 price 总价
*(付费会员
* @param result 计算结果
*/
public static void recountAllPriceisPaidMember(TradePriceCalculateRespBO result) {
// 先重置
TradePriceCalculateRespBO.Price price = result.getPrice();
price.setTotalPrice(0).setDiscountPrice(0).setDeliveryPrice(0)
.setCouponPrice(0).setPointPrice(0).setVipPrice(0).setPayPrice(0);
// 再合计 item
result.getItems().forEach(item -> {
if (!item.getSelected()) {
return;
}
if(item.getPaidMemberPrice() != null){
price.setTotalPrice(price.getTotalPrice() + item.getPaidMemberPrice() * item.getCount());
}
else{
price.setTotalPrice(price.getTotalPrice() + item.getPrice() * item.getCount());
}
price.setDiscountPrice(price.getDiscountPrice() + item.getDiscountPrice());
price.setDeliveryPrice(price.getDeliveryPrice() + item.getDeliveryPrice());
price.setCouponPrice(price.getCouponPrice() + item.getCouponPrice());
price.setPointPrice(price.getPointPrice() + item.getPointPrice());
price.setVipPrice(price.getVipPrice() + item.getVipPrice());
price.setPayPrice(price.getPayPrice() + item.getPayPrice());
});
}
/**
* 基于订单项重新计算 price 总价
*
@ -130,6 +172,32 @@ public class TradePriceCalculatorHelper {
result.setGivePoint(getSumValue(result.getItems(), item -> item.getSelected() ? item.getGivePoint() : 0, Integer::sum));
}
/**
* 重新计算单个订单项的支付金额
*(付费会员)
* @param orderItem 订单项
*/
public static void recountPayPriceisPaidMember(TradePriceCalculateRespBO.OrderItem orderItem) {
if(orderItem.getPaidMemberPrice() != null){
orderItem.setPayPrice(orderItem.getPaidMemberPrice() * orderItem.getCount()
- orderItem.getDiscountPrice()
+ orderItem.getDeliveryPrice()
- orderItem.getCouponPrice()
- orderItem.getPointPrice()
- orderItem.getVipPrice()
);
}
else {
orderItem.setPayPrice(orderItem.getPrice() * orderItem.getCount()
- orderItem.getDiscountPrice()
+ orderItem.getDeliveryPrice()
- orderItem.getCouponPrice()
- orderItem.getPointPrice()
- orderItem.getVipPrice()
);
}
}
/**
* 重新计算单个订单项的支付金额
*

View File

@ -65,4 +65,13 @@ public interface MemberUserApi {
*/
void validateUser(Long id);
/**
* 校验用户是否为付费会员
*
* @param id 用户编号
* @return 用户信息
*/
Boolean validUserActivate(Long id);
}

View File

@ -56,4 +56,9 @@ public class MemberUserRespDTO {
*/
private Integer point;
/**
* 是否绑过卡,是否开通过会员(0未开通,1试用,2有效期,3永久,4过期)
*/
private Integer activate;
}

View File

@ -55,4 +55,14 @@ public class MemberUserApiImpl implements MemberUserApi {
}
}
@Override
public Boolean validUserActivate(Long id) {
MemberUserDO user = userService.getUser(id);
if (user.getActivate() == 0 || user.getActivate() == 4){
return false;
}else {
return true;
}
}
}