Skip to content

Commit 95bb19d

Browse files
YunaiVgitee-org
authored andcommitted
!558 【功能完善】商城: 完善积分商城装修
Merge pull request !558 from puhui999/dev-crm
2 parents 7a6c694 + d9f4336 commit 95bb19d

File tree

9 files changed

+945
-15
lines changed

9 files changed

+945
-15
lines changed

src/api/mall/promotion/point/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import request from '@/config/axios'
2-
import { Sku, Spu } from '@/api/mall/product/spu'
2+
import { Sku, Spu } from '@/api/mall/product/spu' // 积分商城活动 VO
33

44
// 积分商城活动 VO
55
export interface PointActivityVO {
66
id: number // 积分商城活动编号
77
spuId: number // 积分商城活动商品
88
status: number // 活动状态
9+
stock: number // 积分商城活动库存
10+
totalStock: number // 积分商城活动总库存
911
remark?: string // 备注
1012
sort: number // 排序
1113
createTime: string // 创建时间
@@ -17,7 +19,6 @@ export interface PointActivityVO {
1719
marketPrice: number // 商品市场价,单位:分
1820

1921
//======================= 显示所需兑换积分最少的 sku 信息 =======================
20-
maxCount: number // 可兑换数量
2122
point: number // 兑换积分
2223
price: number // 兑换金额,单位:分
2324
}
@@ -44,6 +45,13 @@ export interface SpuExtension extends Spu {
4445
skus: SkuExtension[] // 重写类型
4546
}
4647

48+
export interface SpuExtension0 extends Spu {
49+
pointStock: number // 积分商城活动库存
50+
pointTotalStock: number // 积分商城活动总库存
51+
point: number // 兑换积分
52+
pointPrice: number // 兑换金额,单位:分
53+
}
54+
4755
// 积分商城活动 API
4856
export const PointActivityApi = {
4957
// 查询积分商城活动分页
@@ -56,6 +64,11 @@ export const PointActivityApi = {
5664
return await request.get({ url: `/promotion/point-activity/get?id=` + id })
5765
},
5866

67+
// 查询积分商城活动列表,基于活动编号数组
68+
getPointActivityListByIds: async (ids: number[]) => {
69+
return request.get({ url: `/promotion/point-activity/list-by-ids?ids=${ids}` })
70+
},
71+
5972
// 新增积分商城活动
6073
createPointActivity: async (data: PointActivityVO) => {
6174
return await request.post({ url: `/promotion/point-activity/create`, data })

src/components/AppLinkInput/data.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface AppLinkGroup {
55
// 链接列表
66
links: AppLink[]
77
}
8+
89
// APP 链接
910
export interface AppLink {
1011
// 链接名称
@@ -21,6 +22,8 @@ export const enum APP_LINK_TYPE_ENUM {
2122
ACTIVITY_COMBINATION,
2223
// 秒杀活动
2324
ACTIVITY_SECKILL,
25+
// 积分商城活动
26+
ACTIVITY_POINT,
2427
// 文章详情
2528
ARTICLE_DETAIL,
2629
// 优惠券详情
@@ -130,6 +133,11 @@ export const APP_LINK_GROUP_LIST = [
130133
path: '/pages/activity/seckill/list',
131134
type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL
132135
},
136+
{
137+
name: '积分商城活动',
138+
path: '/pages/activity/point/list',
139+
type: APP_LINK_TYPE_ENUM.ACTIVITY_POINT
140+
},
133141
{
134142
name: '签到中心',
135143
path: '/pages/app/sign'
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
2+
3+
/** 积分商城属性 */
4+
export interface PromotionPointProperty {
5+
// 布局类型:单列 | 三列
6+
layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
7+
// 商品字段
8+
fields: {
9+
// 商品名称
10+
name: PromotionPointFieldProperty
11+
// 商品简介
12+
introduction: PromotionPointFieldProperty
13+
// 商品价格
14+
price: PromotionPointFieldProperty
15+
// 市场价
16+
marketPrice: PromotionPointFieldProperty
17+
// 商品销量
18+
salesCount: PromotionPointFieldProperty
19+
// 商品库存
20+
stock: PromotionPointFieldProperty
21+
}
22+
// 角标
23+
badge: {
24+
// 是否显示
25+
show: boolean
26+
// 角标图片
27+
imgUrl: string
28+
}
29+
// 按钮
30+
btnBuy: {
31+
// 类型:文字 | 图片
32+
type: 'text' | 'img'
33+
// 文字
34+
text: string
35+
// 文字按钮:背景渐变起始颜色
36+
bgBeginColor: string
37+
// 文字按钮:背景渐变结束颜色
38+
bgEndColor: string
39+
// 图片按钮:图片地址
40+
imgUrl: string
41+
}
42+
// 上圆角
43+
borderRadiusTop: number
44+
// 下圆角
45+
borderRadiusBottom: number
46+
// 间距
47+
space: number
48+
// 秒杀活动编号
49+
activityIds: number[]
50+
// 组件样式
51+
style: ComponentStyle
52+
}
53+
54+
// 商品字段
55+
export interface PromotionPointFieldProperty {
56+
// 是否显示
57+
show: boolean
58+
// 颜色
59+
color: string
60+
}
61+
62+
// 定义组件
63+
export const component = {
64+
id: 'PromotionPoint',
65+
name: '积分商城',
66+
icon: 'ep:present',
67+
property: {
68+
layoutType: 'oneColBigImg',
69+
fields: {
70+
name: { show: true, color: '#000' },
71+
introduction: { show: true, color: '#999' },
72+
price: { show: true, color: '#ff3000' },
73+
marketPrice: { show: true, color: '#c4c4c4' },
74+
salesCount: { show: true, color: '#c4c4c4' },
75+
stock: { show: false, color: '#c4c4c4' }
76+
},
77+
badge: { show: false, imgUrl: '' },
78+
btnBuy: {
79+
type: 'text',
80+
text: '立即兑换',
81+
bgBeginColor: '#FF6000',
82+
bgEndColor: '#FE832A',
83+
imgUrl: ''
84+
},
85+
borderRadiusTop: 8,
86+
borderRadiusBottom: 8,
87+
space: 8,
88+
style: {
89+
bgType: 'color',
90+
bgColor: '',
91+
marginLeft: 8,
92+
marginRight: 8,
93+
marginBottom: 8
94+
} as ComponentStyle
95+
}
96+
} as DiyComponent<PromotionPointProperty>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<template>
2+
<div ref="containerRef" :class="`box-content min-h-30px w-full flex flex-row flex-wrap`">
3+
<div
4+
v-for="(spu, index) in spuList"
5+
:key="index"
6+
:style="{
7+
...calculateSpace(index),
8+
...calculateWidth(),
9+
borderTopLeftRadius: `${property.borderRadiusTop}px`,
10+
borderTopRightRadius: `${property.borderRadiusTop}px`,
11+
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
12+
borderBottomRightRadius: `${property.borderRadiusBottom}px`
13+
}"
14+
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
15+
>
16+
<!-- 角标 -->
17+
<div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
18+
<el-image :src="property.badge.imgUrl" class="h-26px w-38px" fit="cover" />
19+
</div>
20+
<!-- 商品封面图 -->
21+
<div
22+
:class="[
23+
'h-140px',
24+
{
25+
'w-full': property.layoutType !== 'oneColSmallImg',
26+
'w-140px': property.layoutType === 'oneColSmallImg'
27+
}
28+
]"
29+
>
30+
<el-image :src="spu.picUrl" class="h-full w-full" fit="cover" />
31+
</div>
32+
<div
33+
:class="[
34+
' flex flex-col gap-8px p-8px box-border',
35+
{
36+
'w-full': property.layoutType !== 'oneColSmallImg',
37+
'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
38+
}
39+
]"
40+
>
41+
<!-- 商品名称 -->
42+
<div
43+
v-if="property.fields.name.show"
44+
:class="[
45+
'text-14px ',
46+
{
47+
truncate: property.layoutType !== 'oneColSmallImg',
48+
'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
49+
}
50+
]"
51+
:style="{ color: property.fields.name.color }"
52+
>
53+
{{ spu.name }}
54+
</div>
55+
<!-- 商品简介 -->
56+
<div
57+
v-if="property.fields.introduction.show"
58+
:style="{ color: property.fields.introduction.color }"
59+
class="truncate text-12px"
60+
>
61+
{{ spu.introduction }}
62+
</div>
63+
<div>
64+
<!-- 积分 -->
65+
<span
66+
v-if="property.fields.price.show"
67+
:style="{ color: property.fields.price.color }"
68+
class="text-16px"
69+
>
70+
{{ spu.point }}积分
71+
{{ !spu.pointPrice || spu.pointPrice === 0 ? '' : `+${fenToYuan(spu.pointPrice)}元` }}
72+
</span>
73+
<!-- 市场价 -->
74+
<span
75+
v-if="property.fields.marketPrice.show && spu.marketPrice"
76+
:style="{ color: property.fields.marketPrice.color }"
77+
class="ml-4px text-10px line-through"
78+
>
79+
¥{{ fenToYuan(spu.marketPrice) }}
80+
</span>
81+
</div>
82+
<div class="text-12px">
83+
<!-- 销量 -->
84+
<span
85+
v-if="property.fields.salesCount.show"
86+
:style="{ color: property.fields.salesCount.color }"
87+
>
88+
已兑{{ (spu.pointTotalStock || 0) - (spu.pointStock || 0) }}件
89+
</span>
90+
<!-- 库存 -->
91+
<span v-if="property.fields.stock.show" :style="{ color: property.fields.stock.color }">
92+
库存{{ spu.pointTotalStock || 0 }}
93+
</span>
94+
</div>
95+
</div>
96+
<!-- 购买按钮 -->
97+
<div class="absolute bottom-8px right-8px">
98+
<!-- 文字按钮 -->
99+
<span
100+
v-if="property.btnBuy.type === 'text'"
101+
:style="{
102+
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
103+
}"
104+
class="rounded-full p-x-12px p-y-4px text-12px text-white"
105+
>
106+
{{ property.btnBuy.text }}
107+
</span>
108+
<!-- 图片按钮 -->
109+
<el-image
110+
v-else
111+
:src="property.btnBuy.imgUrl"
112+
class="h-28px w-28px rounded-full"
113+
fit="cover"
114+
/>
115+
</div>
116+
</div>
117+
</div>
118+
</template>
119+
<script lang="ts" setup>
120+
import { PromotionPointProperty } from './config'
121+
import * as ProductSpuApi from '@/api/mall/product/spu'
122+
import { PointActivityApi, PointActivityVO, SpuExtension0 } from '@/api/mall/promotion/point'
123+
import { fenToYuan } from '@/utils'
124+
125+
/** 积分商城卡片 */
126+
defineOptions({ name: 'PromotionPoint' })
127+
// 定义属性
128+
const props = defineProps<{ property: PromotionPointProperty }>()
129+
// 商品列表
130+
const spuList = ref<SpuExtension0[]>([])
131+
const spuIdList = ref<number[]>([])
132+
const pointActivityList = ref<PointActivityVO[]>([])
133+
134+
watch(
135+
() => props.property.activityIds,
136+
async () => {
137+
try {
138+
// 新添加的积分商城组件,是没有活动ID的
139+
const activityIds = props.property.activityIds
140+
// 检查活动ID的有效性
141+
if (Array.isArray(activityIds) && activityIds.length > 0) {
142+
// 获取积分商城活动详情列表
143+
pointActivityList.value = await PointActivityApi.getPointActivityListByIds(activityIds)
144+
145+
// 获取积分商城活动的 SPU 详情列表
146+
spuList.value = []
147+
spuIdList.value = pointActivityList.value.map((activity) => activity.spuId)
148+
if (spuIdList.value.length > 0) {
149+
spuList.value = await ProductSpuApi.getSpuDetailList(spuIdList.value)
150+
}
151+
152+
// 更新 SPU 的最低兑换积分和所需兑换金额
153+
pointActivityList.value.forEach((activity) => {
154+
// 匹配spuId
155+
const spu = spuList.value.find((spu) => spu.id === activity.spuId)
156+
if (spu) {
157+
spu.pointStock = activity.stock
158+
spu.pointTotalStock = activity.totalStock
159+
spu.point = activity.point
160+
spu.pointPrice = activity.price
161+
}
162+
})
163+
}
164+
} catch (error) {
165+
console.error('获取积分商城活动细节或 SPU 细节时出错:', error)
166+
}
167+
},
168+
{
169+
immediate: true,
170+
deep: true
171+
}
172+
)
173+
174+
/**
175+
* 计算商品的间距
176+
* @param index 商品索引
177+
*/
178+
const calculateSpace = (index: number) => {
179+
// 商品的列数
180+
const columns = props.property.layoutType === 'twoCol' ? 2 : 1
181+
// 第一列没有左边距
182+
const marginLeft = index % columns === 0 ? '0' : props.property.space + 'px'
183+
// 第一行没有上边距
184+
const marginTop = index < columns ? '0' : props.property.space + 'px'
185+
186+
return { marginLeft, marginTop }
187+
}
188+
189+
// 容器
190+
const containerRef = ref()
191+
// 计算商品的宽度
192+
const calculateWidth = () => {
193+
let width = '100%'
194+
// 双列时每列的宽度为:(总宽度 - 间距)/ 2
195+
if (props.property.layoutType === 'twoCol') {
196+
width = `${(containerRef.value.offsetWidth - props.property.space) / 2}px`
197+
}
198+
return { width }
199+
}
200+
</script>
201+
202+
<style lang="scss" scoped></style>

0 commit comments

Comments
 (0)