Skip to content

Commit d5f9d31

Browse files
committed
营销:适配商城装修组件【拼团】(待重写)
1 parent 7e44b62 commit d5f9d31

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
2+
3+
/** 拼团属性 */
4+
export interface PromotionCombinationProperty {
5+
// 布局类型:单列 | 三列
6+
layoutType: 'oneCol' | 'threeCol'
7+
// 商品字段
8+
fields: {
9+
// 商品名称
10+
name: PromotionCombinationFieldProperty
11+
// 商品价格
12+
price: PromotionCombinationFieldProperty
13+
}
14+
// 角标
15+
badge: {
16+
// 是否显示
17+
show: boolean
18+
// 角标图片
19+
imgUrl: string
20+
}
21+
// 上圆角
22+
borderRadiusTop: number
23+
// 下圆角
24+
borderRadiusBottom: number
25+
// 间距
26+
space: number
27+
// 拼团活动编号
28+
activityId: number
29+
// 组件样式
30+
style: ComponentStyle
31+
}
32+
// 商品字段
33+
export interface PromotionCombinationFieldProperty {
34+
// 是否显示
35+
show: boolean
36+
// 颜色
37+
color: string
38+
}
39+
40+
// 定义组件
41+
export const component = {
42+
id: 'PromotionCombination',
43+
name: '拼团',
44+
icon: 'mdi:account-group',
45+
property: {
46+
activityId: undefined,
47+
layoutType: 'oneCol',
48+
fields: {
49+
name: { show: true, color: '#000' },
50+
price: { show: true, color: '#ff3000' }
51+
},
52+
badge: { show: false, imgUrl: '' },
53+
borderRadiusTop: 8,
54+
borderRadiusBottom: 8,
55+
space: 8,
56+
style: {
57+
bgType: 'color',
58+
bgColor: '',
59+
marginLeft: 8,
60+
marginRight: 8,
61+
marginBottom: 8
62+
} as ComponentStyle
63+
}
64+
} as DiyComponent<PromotionCombinationProperty>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<template>
2+
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
3+
<!-- 商品网格 -->
4+
<div
5+
class="grid overflow-x-auto"
6+
:style="{
7+
gridGap: `${property.space}px`,
8+
gridTemplateColumns,
9+
width: scrollbarWidth
10+
}"
11+
>
12+
<!-- 商品 -->
13+
<div
14+
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
15+
:style="{
16+
borderTopLeftRadius: `${property.borderRadiusTop}px`,
17+
borderTopRightRadius: `${property.borderRadiusTop}px`,
18+
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
19+
borderBottomRightRadius: `${property.borderRadiusBottom}px`
20+
}"
21+
v-for="(spu, index) in spuList"
22+
:key="index"
23+
>
24+
<!-- 角标 -->
25+
<div
26+
v-if="property.badge.show"
27+
class="absolute left-0 top-0 z-1 items-center justify-center"
28+
>
29+
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
30+
</div>
31+
<!-- 商品封面图 -->
32+
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
33+
<div
34+
:class="[
35+
'flex flex-col gap-8px p-8px box-border',
36+
{
37+
'w-[calc(100%-64px)]': columns === 2,
38+
'w-full': columns === 3
39+
}
40+
]"
41+
>
42+
<!-- 商品名称 -->
43+
<div
44+
v-if="property.fields.name.show"
45+
class="truncate text-12px"
46+
:style="{ color: property.fields.name.color }"
47+
>
48+
{{ spu.name }}
49+
</div>
50+
<div>
51+
<!-- 商品价格 -->
52+
<span
53+
v-if="property.fields.price.show"
54+
class="text-12px"
55+
:style="{ color: property.fields.price.color }"
56+
>
57+
¥{{ spu.price }}
58+
</span>
59+
</div>
60+
</div>
61+
</div>
62+
</div>
63+
</el-scrollbar>
64+
</template>
65+
<script setup lang="ts">
66+
import { PromotionCombinationProperty } from './config'
67+
import * as ProductSpuApi from '@/api/mall/product/spu'
68+
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
69+
70+
/** 拼团 */
71+
defineOptions({ name: 'PromotionCombination' })
72+
// 定义属性
73+
const props = defineProps<{ property: PromotionCombinationProperty }>()
74+
// 商品列表
75+
const spuList = ref<ProductSpuApi.Spu[]>([])
76+
watch(
77+
() => props.property.activityId,
78+
async () => {
79+
if (!props.property.activityId) return
80+
const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
81+
if (!activity?.spuId) return
82+
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
83+
},
84+
{
85+
immediate: true,
86+
deep: true
87+
}
88+
)
89+
// 手机宽度
90+
const phoneWidth = ref(375)
91+
// 容器
92+
const containerRef = ref()
93+
// 商品的列数
94+
const columns = ref(2)
95+
// 滚动条宽度
96+
const scrollbarWidth = ref('100%')
97+
// 商品图大小
98+
const imageSize = ref('0')
99+
// 商品网络列数
100+
const gridTemplateColumns = ref('')
101+
// 计算布局参数
102+
watch(
103+
() => [props.property, phoneWidth, spuList.value.length],
104+
() => {
105+
// 计算列数
106+
columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
107+
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
108+
const productWidth =
109+
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
110+
// 商品图布局:2列时,左右布局 3列时,上下布局
111+
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
112+
// 指定列数
113+
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
114+
// 不滚动
115+
scrollbarWidth.value = '100%'
116+
},
117+
{ immediate: true, deep: true }
118+
)
119+
onMounted(() => {
120+
// 提取手机宽度
121+
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
122+
})
123+
</script>
124+
125+
<style scoped lang="scss"></style>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<template>
2+
<ComponentContainerProperty v-model="formData.style">
3+
<el-form label-width="80px" :model="formData">
4+
<el-card header="拼团活动" class="property-group" shadow="never">
5+
<el-form-item label="拼团活动" prop="activityId">
6+
<el-select v-model="formData.activityId">
7+
<el-option
8+
v-for="activity in activityList"
9+
:key="activity.id"
10+
:label="activity.name"
11+
:value="activity.id"
12+
/>
13+
</el-select>
14+
</el-form-item>
15+
</el-card>
16+
<el-card header="商品样式" class="property-group" shadow="never">
17+
<el-form-item label="布局" prop="type">
18+
<el-radio-group v-model="formData.layoutType">
19+
<el-tooltip class="item" content="单列" placement="bottom">
20+
<el-radio-button label="oneCol">
21+
<Icon icon="fluent:text-column-one-24-filled" />
22+
</el-radio-button>
23+
</el-tooltip>
24+
<el-tooltip class="item" content="三列" placement="bottom">
25+
<el-radio-button label="threeCol">
26+
<Icon icon="fluent:text-column-three-24-filled" />
27+
</el-radio-button>
28+
</el-tooltip>
29+
</el-radio-group>
30+
</el-form-item>
31+
<el-form-item label="商品名称" prop="fields.name.show">
32+
<div class="flex gap-8px">
33+
<ColorInput v-model="formData.fields.name.color" />
34+
<el-checkbox v-model="formData.fields.name.show" />
35+
</div>
36+
</el-form-item>
37+
<el-form-item label="商品价格" prop="fields.price.show">
38+
<div class="flex gap-8px">
39+
<ColorInput v-model="formData.fields.price.color" />
40+
<el-checkbox v-model="formData.fields.price.show" />
41+
</div>
42+
</el-form-item>
43+
</el-card>
44+
<el-card header="角标" class="property-group" shadow="never">
45+
<el-form-item label="角标" prop="badge.show">
46+
<el-switch v-model="formData.badge.show" />
47+
</el-form-item>
48+
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
49+
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
50+
<template #tip> 建议尺寸:36 * 22 </template>
51+
</UploadImg>
52+
</el-form-item>
53+
</el-card>
54+
<el-card header="商品样式" class="property-group" shadow="never">
55+
<el-form-item label="上圆角" prop="borderRadiusTop">
56+
<el-slider
57+
v-model="formData.borderRadiusTop"
58+
:max="100"
59+
:min="0"
60+
show-input
61+
input-size="small"
62+
:show-input-controls="false"
63+
/>
64+
</el-form-item>
65+
<el-form-item label="下圆角" prop="borderRadiusBottom">
66+
<el-slider
67+
v-model="formData.borderRadiusBottom"
68+
:max="100"
69+
:min="0"
70+
show-input
71+
input-size="small"
72+
:show-input-controls="false"
73+
/>
74+
</el-form-item>
75+
<el-form-item label="间隔" prop="space">
76+
<el-slider
77+
v-model="formData.space"
78+
:max="100"
79+
:min="0"
80+
show-input
81+
input-size="small"
82+
:show-input-controls="false"
83+
/>
84+
</el-form-item>
85+
</el-card>
86+
</el-form>
87+
</ComponentContainerProperty>
88+
</template>
89+
90+
<script setup lang="ts">
91+
import { PromotionCombinationProperty } from './config'
92+
import { usePropertyForm } from '@/components/DiyEditor/util'
93+
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
94+
import { CommonStatusEnum } from '@/utils/constants'
95+
96+
// 拼团属性面板
97+
defineOptions({ name: 'PromotionCombinationProperty' })
98+
99+
const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
100+
const emit = defineEmits(['update:modelValue'])
101+
const { formData } = usePropertyForm(props.modelValue, emit)
102+
// 活动列表
103+
const activityList = ref<CombinationActivityApi.CombinationActivityVO>([])
104+
onMounted(async () => {
105+
const { list } = await CombinationActivityApi.getCombinationActivityPage({
106+
status: CommonStatusEnum.ENABLE
107+
})
108+
activityList.value = list
109+
})
110+
</script>
111+
112+
<style scoped lang="scss"></style>

0 commit comments

Comments
 (0)