Commit 5ccca7ff by 芋道源码 Committed by Gitee

!543 【功能优化】商城:秒杀装修重构

Merge pull request !543 from 卢越/master
parents 90656ddb d5f1e504
...@@ -18,6 +18,7 @@ export interface SeckillActivityVO { ...@@ -18,6 +18,7 @@ export interface SeckillActivityVO {
singleLimitCount?: number singleLimitCount?: number
stock?: number stock?: number
totalStock?: number totalStock?: number
seckillPrice?: number
products?: SeckillProductVO[] products?: SeckillProductVO[]
} }
...@@ -43,6 +44,11 @@ export const getSeckillActivityPage = async (params) => { ...@@ -43,6 +44,11 @@ export const getSeckillActivityPage = async (params) => {
return await request.get({ url: '/promotion/seckill-activity/page', params }) return await request.get({ url: '/promotion/seckill-activity/page', params })
} }
// 查询拼团活动列表,基于活动编号数组
export const getSeckillActivityListByIds = (ids: number[]) => {
return request.get({ url: `/promotion/seckill-activity/list-by-ids?ids=${ids}` })
}
// 查询秒杀活动详情 // 查询秒杀活动详情
export const getSeckillActivity = async (id: number) => { export const getSeckillActivity = async (id: number) => {
return await request.get({ url: '/promotion/seckill-activity/get?id=' + id }) return await request.get({ url: '/promotion/seckill-activity/get?id=' + id })
......
...@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util' ...@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 秒杀属性 */ /** 秒杀属性 */
export interface PromotionSeckillProperty { export interface PromotionSeckillProperty {
// 布局类型:单列 | 三列 // 布局类型:单列 | 三列
layoutType: 'oneCol' | 'threeCol' layoutType: 'oneColBigImg' | 'oneColSmallImg' | 'twoCol'
// 商品字段 // 商品字段
fields: { fields: {
// 商品名称 // 商品名称
name: PromotionSeckillFieldProperty name: PromotionSeckillFieldProperty
// 商品简介
introduction: PromotionSeckillFieldProperty
// 商品价格 // 商品价格
price: PromotionSeckillFieldProperty price: PromotionSeckillFieldProperty
// 市场价
marketPrice: PromotionSeckillFieldProperty
// 商品销量
salesCount: PromotionSeckillFieldProperty
// 商品库存
stock: PromotionSeckillFieldProperty
} }
// 角标 // 角标
badge: { badge: {
...@@ -18,6 +26,19 @@ export interface PromotionSeckillProperty { ...@@ -18,6 +26,19 @@ export interface PromotionSeckillProperty {
// 角标图片 // 角标图片
imgUrl: string imgUrl: string
} }
// 按钮
btnBuy: {
// 类型:文字 | 图片
type: 'text' | 'img'
// 文字
text: string
// 文字按钮:背景渐变起始颜色
bgBeginColor: string
// 文字按钮:背景渐变结束颜色
bgEndColor: string
// 图片按钮:图片地址
imgUrl: string
}
// 上圆角 // 上圆角
borderRadiusTop: number borderRadiusTop: number
// 下圆角 // 下圆角
...@@ -25,10 +46,11 @@ export interface PromotionSeckillProperty { ...@@ -25,10 +46,11 @@ export interface PromotionSeckillProperty {
// 间距 // 间距
space: number space: number
// 秒杀活动编号 // 秒杀活动编号
activityId: number activityIds: number[]
// 组件样式 // 组件样式
style: ComponentStyle style: ComponentStyle
} }
// 商品字段 // 商品字段
export interface PromotionSeckillFieldProperty { export interface PromotionSeckillFieldProperty {
// 是否显示 // 是否显示
...@@ -43,13 +65,23 @@ export const component = { ...@@ -43,13 +65,23 @@ export const component = {
name: '秒杀', name: '秒杀',
icon: 'mdi:calendar-time', icon: 'mdi:calendar-time',
property: { property: {
activityId: undefined, layoutType: 'oneColBigImg',
layoutType: 'oneCol',
fields: { fields: {
name: { show: true, color: '#000' }, name: { show: true, color: '#000' },
price: { show: true, color: '#ff3000' } introduction: { show: true, color: '#999' },
price: { show: true, color: '#ff3000' },
marketPrice: { show: true, color: '#c4c4c4' },
salesCount: { show: true, color: '#c4c4c4' },
stock: { show: false, color: '#c4c4c4' }
}, },
badge: { show: false, imgUrl: '' }, badge: { show: false, imgUrl: '' },
btnBuy: {
type: 'text',
text: '立即秒杀',
bgBeginColor: '#FF6000',
bgEndColor: '#FE832A',
imgUrl: ''
},
borderRadiusTop: 8, borderRadiusTop: 8,
borderRadiusBottom: 8, borderRadiusBottom: 8,
space: 8, space: 8,
......
...@@ -2,30 +2,31 @@ ...@@ -2,30 +2,31 @@
<ComponentContainerProperty v-model="formData.style"> <ComponentContainerProperty v-model="formData.style">
<el-form label-width="80px" :model="formData"> <el-form label-width="80px" :model="formData">
<el-card header="秒杀活动" class="property-group" shadow="never"> <el-card header="秒杀活动" class="property-group" shadow="never">
<el-form-item label="秒杀活动" prop="activityId"> <SeckillShowcase v-model="formData.activityIds" />
<el-select v-model="formData.activityId">
<el-option
v-for="activity in activityList"
:key="activity.id"
:label="activity.name"
:value="activity.id"
/>
</el-select>
</el-form-item>
</el-card> </el-card>
<el-card header="商品样式" class="property-group" shadow="never"> <el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="布局" prop="type"> <el-form-item label="布局" prop="type">
<el-radio-group v-model="formData.layoutType"> <el-radio-group v-model="formData.layoutType">
<el-tooltip class="item" content="单列" placement="bottom"> <el-tooltip class="item" content="单列大图" placement="bottom">
<el-radio-button value="oneCol"> <el-radio-button value="oneColBigImg">
<Icon icon="fluent:text-column-one-24-filled" /> <Icon icon="fluent:text-column-one-24-filled" />
</el-radio-button> </el-radio-button>
</el-tooltip> </el-tooltip>
<el-tooltip class="item" content="三列" placement="bottom"> <el-tooltip class="item" content="单列小图" placement="bottom">
<el-radio-button value="oneColSmallImg">
<Icon icon="fluent:text-column-two-left-24-filled" />
</el-radio-button>
</el-tooltip>
<el-tooltip class="item" content="双列" placement="bottom">
<el-radio-button value="twoCol">
<Icon icon="fluent:text-column-two-24-filled" />
</el-radio-button>
</el-tooltip>
<!--<el-tooltip class="item" content="三列" placement="bottom">
<el-radio-button value="threeCol"> <el-radio-button value="threeCol">
<Icon icon="fluent:text-column-three-24-filled" /> <Icon icon="fluent:text-column-three-24-filled" />
</el-radio-button> </el-radio-button>
</el-tooltip> </el-tooltip>-->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="商品名称" prop="fields.name.show"> <el-form-item label="商品名称" prop="fields.name.show">
...@@ -34,12 +35,36 @@ ...@@ -34,12 +35,36 @@
<el-checkbox v-model="formData.fields.name.show" /> <el-checkbox v-model="formData.fields.name.show" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="商品简介" prop="fields.introduction.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.introduction.color" />
<el-checkbox v-model="formData.fields.introduction.show" />
</div>
</el-form-item>
<el-form-item label="商品价格" prop="fields.price.show"> <el-form-item label="商品价格" prop="fields.price.show">
<div class="flex gap-8px"> <div class="flex gap-8px">
<ColorInput v-model="formData.fields.price.color" /> <ColorInput v-model="formData.fields.price.color" />
<el-checkbox v-model="formData.fields.price.show" /> <el-checkbox v-model="formData.fields.price.show" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="市场价" prop="fields.marketPrice.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.marketPrice.color" />
<el-checkbox v-model="formData.fields.marketPrice.show" />
</div>
</el-form-item>
<el-form-item label="商品销量" prop="fields.salesCount.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.salesCount.color" />
<el-checkbox v-model="formData.fields.salesCount.show" />
</div>
</el-form-item>
<el-form-item label="商品库存" prop="fields.stock.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.stock.color" />
<el-checkbox v-model="formData.fields.stock.show" />
</div>
</el-form-item>
</el-card> </el-card>
<el-card header="角标" class="property-group" shadow="never"> <el-card header="角标" class="property-group" shadow="never">
<el-form-item label="角标" prop="badge.show"> <el-form-item label="角标" prop="badge.show">
...@@ -47,10 +72,36 @@ ...@@ -47,10 +72,36 @@
</el-form-item> </el-form-item>
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show"> <el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px"> <UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
<template #tip> 建议尺寸:36 * 22 </template> <template #tip> 建议尺寸:36 * 22</template>
</UploadImg> </UploadImg>
</el-form-item> </el-form-item>
</el-card> </el-card>
<el-card header="按钮" class="property-group" shadow="never">
<el-form-item label="按钮类型" prop="btnBuy.type">
<el-radio-group v-model="formData.btnBuy.type">
<el-radio-button value="text">文字</el-radio-button>
<el-radio-button value="img">图片</el-radio-button>
</el-radio-group>
</el-form-item>
<template v-if="formData.btnBuy.type === 'text'">
<el-form-item label="按钮文字" prop="btnBuy.text">
<el-input v-model="formData.btnBuy.text" />
</el-form-item>
<el-form-item label="左侧背景" prop="btnBuy.bgBeginColor">
<ColorInput v-model="formData.btnBuy.bgBeginColor" />
</el-form-item>
<el-form-item label="右侧背景" prop="btnBuy.bgEndColor">
<ColorInput v-model="formData.btnBuy.bgEndColor" />
</el-form-item>
</template>
<template v-else>
<el-form-item label="图片" prop="btnBuy.imgUrl">
<UploadImg v-model="formData.btnBuy.imgUrl" height="56px" width="56px">
<template #tip> 建议尺寸:56 * 56</template>
</UploadImg>
</el-form-item>
</template>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never"> <el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="上圆角" prop="borderRadiusTop"> <el-form-item label="上圆角" prop="borderRadiusTop">
<el-slider <el-slider
...@@ -92,6 +143,7 @@ import { PromotionSeckillProperty } from './config' ...@@ -92,6 +143,7 @@ import { PromotionSeckillProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { usePropertyForm } from '@/components/DiyEditor/util'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity' import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import SeckillShowcase from '@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
// 秒杀属性面板 // 秒杀属性面板
defineOptions({ name: 'PromotionSeckillProperty' }) defineOptions({ name: 'PromotionSeckillProperty' })
...@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionSeckillProperty }>() ...@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit) const { formData } = usePropertyForm(props.modelValue, emit)
// 活动列表 // 活动列表
const activityList = ref<SeckillActivityApi.SeckillActivityVO>([]) const activityList = ref<SeckillActivityApi.SeckillActivityVO[]>([])
onMounted(async () => { onMounted(async () => {
const { list } = await SeckillActivityApi.getSeckillActivityPage({ const { list } = await SeckillActivityApi.getSeckillActivityPage({
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE
......
<template>
<div class="flex flex-wrap items-center gap-8px">
<div
v-for="(seckillActivity, index) in Activitys"
:key="seckillActivity.id"
class="select-box spu-pic"
>
<el-tooltip :content="seckillActivity.name">
<div class="relative h-full w-full">
<el-image :src="seckillActivity.picUrl" class="h-full w-full" />
<Icon
v-show="!disabled"
class="del-icon"
icon="ep:circle-close-filled"
@click="handleRemoveActivity(index)"
/>
</div>
</el-tooltip>
</div>
<el-tooltip content="选择活动" v-if="canAdd">
<div class="select-box" @click="openSeckillActivityTableSelect">
<Icon icon="ep:plus" />
</div>
</el-tooltip>
</div>
<!-- 拼团活动选择对话框(表格形式) -->
<SeckillTableSelect
ref="seckillActivityTableSelectRef"
:multiple="limit != 1"
@change="handleActivitySelected"
/>
</template>
<script lang="ts" setup>
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { propTypes } from '@/utils/propTypes'
import { oneOfType } from 'vue-types'
import { isArray } from '@/utils/is'
import SeckillTableSelect from '@/views/mall/promotion/seckill/components/SeckillTableSelect.vue'
// 活动橱窗,一般用于装修时使用
// 提供功能:展示活动列表、添加活动、删除活动
defineOptions({ name: 'SeckillShowcase' })
const props = defineProps({
modelValue: oneOfType<number | Array<number>>([Number, Array]).isRequired,
// 限制数量:默认不限制
limit: propTypes.number.def(Number.MAX_VALUE),
disabled: propTypes.bool.def(false)
})
// 计算是否可以添加
const canAdd = computed(() => {
// 情况一:禁用时不可以添加
if (props.disabled) return false
// 情况二:未指定限制数量时,可以添加
if (!props.limit) return true
// 情况三:检查已添加数量是否小于限制数量
return Activitys.value.length < props.limit
})
// 拼团活动列表
const Activitys = ref<SeckillActivityApi.SeckillActivityVO[]>([])
watch(
() => props.modelValue,
async () => {
const ids = isArray(props.modelValue)
? // 情况一:多选
props.modelValue
: // 情况二:单选
props.modelValue
? [props.modelValue]
: []
// 不需要返显
if (ids.length === 0) {
Activitys.value = []
return
}
// 只有活动发生变化之后,才会查询活动
if (
Activitys.value.length === 0 ||
Activitys.value.some((seckillActivity) => !ids.includes(seckillActivity.id!))
) {
Activitys.value = await SeckillActivityApi.getSeckillActivityListByIds(ids)
}
},
{ immediate: true }
)
/** 活动表格选择对话框 */
const seckillActivityTableSelectRef = ref()
// 打开对话框
const openSeckillActivityTableSelect = () => {
seckillActivityTableSelectRef.value.open(Activitys.value)
}
/**
* 选择活动后触发
* @param activityVOs 选中的活动列表
*/
const handleActivitySelected = (
activityVOs: SeckillActivityApi.SeckillActivityVO | SeckillActivityApi.SeckillActivityVO[]
) => {
Activitys.value = isArray(activityVOs) ? activityVOs : [activityVOs]
emitActivityChange()
}
/**
* 删除活动
* @param index 活动索引
*/
const handleRemoveActivity = (index: number) => {
Activitys.value.splice(index, 1)
emitActivityChange()
}
const emit = defineEmits(['update:modelValue', 'change'])
const emitActivityChange = () => {
if (props.limit === 1) {
const seckillActivity = Activitys.value.length > 0 ? Activitys.value[0] : null
emit('update:modelValue', seckillActivity?.id || 0)
emit('change', seckillActivity)
} else {
emit(
'update:modelValue',
Activitys.value.map((seckillActivity) => seckillActivity.id)
)
emit('change', Activitys.value)
}
}
</script>
<style lang="scss" scoped>
.select-box {
display: flex;
width: 60px;
height: 60px;
border: 1px dashed var(--el-border-color-darker);
border-radius: 8px;
align-items: center;
justify-content: center;
cursor: pointer;
}
.spu-pic {
position: relative;
}
.del-icon {
position: absolute;
top: -10px;
right: -10px;
z-index: 1;
width: 20px !important;
height: 20px !important;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment