feat: 完善砍价活动管理

This commit is contained in:
puhui999 2023-08-06 18:12:07 +08:00
parent c327f79177
commit 4947716983
4 changed files with 564 additions and 0 deletions

View File

@ -0,0 +1,62 @@
import request from '@/config/axios'
import { Sku, Spu } from '@/api/mall/product/spu'
export interface BargainActivityVO {
id?: number
name?: string
startTime?: Date
endTime?: Date
status?: number
spuId?: number
userSize?: number // 达到该人数,才能砍到低价
bargainCount?: number // 最大帮砍次数
totalLimitCount?: number // 最大购买次数
stock?: number // 活动总库存
randomMinPrice?: number // 用户每次砍价的最小金额,单位:分
randomMaxPrice?: number // 用户每次砍价的最大金额,单位:分
successCount?: number // 砍价成功数量
products?: BargainProductVO[]
}
// 砍价活动所需属性
export interface BargainProductVO {
spuId: number
skuId: number
bargainFirstPrice: number // 砍价起始价格,单位分
bargainPrice: number // 砍价底价
stock: number // 活动库存
}
// 扩展 Sku 配置
export type SkuExtension = Sku & {
productConfig: BargainProductVO
}
export interface SpuExtension extends Spu {
skus: SkuExtension[] // 重写类型
}
// 查询砍价活动列表
export const getBargainActivityPage = async (params: any) => {
return await request.get({ url: '/promotion/bargain-activity/page', params })
}
// 查询砍价活动详情
export const getBargainActivity = async (id: number) => {
return await request.get({ url: '/promotion/bargain-activity/get?id=' + id })
}
// 新增砍价活动
export const createBargainActivity = async (data: BargainActivityVO) => {
return await request.post({ url: '/promotion/bargain-activity/create', data })
}
// 修改砍价活动
export const updateBargainActivity = async (data: BargainActivityVO) => {
return await request.put({ url: '/promotion/bargain-activity/update', data })
}
// 删除砍价活动
export const deleteBargainActivity = async (id: number) => {
return await request.delete({ url: '/promotion/bargain-activity/delete?id=' + id })
}

View File

@ -0,0 +1,220 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="65%">
<Form
ref="formRef"
v-loading="formLoading"
:is-col="true"
:rules="rules"
:schema="allSchemas.formSchema"
class="mt-10px"
>
<template #spuId>
<el-button @click="spuSelectRef.open()">选择商品</el-button>
<SpuAndSkuList
ref="spuAndSkuListRef"
:rule-config="ruleConfig"
:spu-list="spuList"
:spu-property-list-p="spuPropertyList"
>
<el-table-column align="center" label="砍价起始价格(元)" min-width="168">
<template #default="{ row: sku }">
<el-input-number
v-model="sku.productConfig.bargainFirstPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template>
</el-table-column>
<el-table-column align="center" label="砍价底价(元)" min-width="168">
<template #default="{ row: sku }">
<el-input-number
v-model="sku.productConfig.bargainPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template>
</el-table-column>
<el-table-column align="center" label="活动库存" min-width="168">
<template #default="{ row: sku }">
<el-input-number v-model="sku.productConfig.stock" class="w-100%" />
</template>
</el-table-column>
</SpuAndSkuList>
</template>
</Form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
<SpuSelect ref="spuSelectRef" :isSelectSku="true" @confirm="selectSpu" />
</template>
<script lang="ts" setup>
import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivity'
import { BargainProductVO } from '@/api/mall/promotion/bargain/bargainActivity'
import { allSchemas, rules } from './bargainActivity.data'
import { SpuAndSkuList, SpuProperty, SpuSelect } from '@/views/mall/promotion/components'
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
import * as ProductSpuApi from '@/api/mall/product/spu'
import { convertToInteger, formatToFraction } from '@/utils'
defineOptions({ name: 'PromotionBargainActivityForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formRef = ref() // Ref
// ================= =================
const spuSelectRef = ref() // Ref
const spuAndSkuListRef = ref() // sku Ref
const spuList = ref<BargainActivityApi.SpuExtension[]>([]) // spu
const spuPropertyList = ref<SpuProperty<BargainActivityApi.SpuExtension>[]>([])
const ruleConfig: RuleConfig[] = [
{
name: 'productConfig.bargainFirstPrice',
rule: (arg) => arg > 0,
message: '商品砍价起始价格不能小于0 '
},
{
name: 'productConfig.bargainPrice',
rule: (arg) => arg > 0,
message: '商品砍价底价不能小于0 '
},
{
name: 'productConfig.stock',
rule: (arg) => arg > 1,
message: '商品活动库存不能小于1 '
}
]
const selectSpu = (spuId: number, skuIds: number[]) => {
formRef.value.setValues({ spuId })
getSpuDetails(spuId, skuIds)
}
/**
* 获取 SPU 详情
*/
const getSpuDetails = async (
spuId: number,
skuIds: number[] | undefined,
products?: BargainProductVO[]
) => {
const spuProperties: SpuProperty<BargainActivityApi.SpuExtension>[] = []
const res = (await ProductSpuApi.getSpuDetailList([spuId])) as BargainActivityApi.SpuExtension[]
if (res.length == 0) {
return
}
spuList.value = []
//
const spu = res[0]
const selectSkus =
typeof skuIds === 'undefined' ? spu?.skus : spu?.skus?.filter((sku) => skuIds.includes(sku.id!))
selectSkus?.forEach((sku) => {
let config: BargainProductVO = {
spuId: spu.id!,
skuId: sku.id!,
bargainFirstPrice: 1,
bargainPrice: 1,
stock: 1
}
if (typeof products !== 'undefined') {
const product = products.find((item) => item.skuId === sku.id)
if (product) {
//
product.bargainFirstPrice = formatToFraction(product.bargainFirstPrice)
product.bargainPrice = formatToFraction(product.bargainPrice)
}
config = product || config
}
sku.productConfig = config
})
spu.skus = selectSkus as BargainActivityApi.SkuExtension[]
spuProperties.push({
spuId: spu.id!,
spuDetail: spu,
propertyList: getPropertyList(spu)
})
spuList.value.push(spu)
spuPropertyList.value = spuProperties
}
// ================= end =================
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
await resetForm()
//
if (id) {
formLoading.value = true
try {
const data = (await BargainActivityApi.getBargainActivity(
id
)) as BargainActivityApi.BargainActivityVO
// ,
data.randomMinPrice = formatToFraction(data.randomMinPrice)
data.randomMaxPrice = formatToFraction(data.randomMaxPrice)
await getSpuDetails(data.spuId!, data.products?.map((sku) => sku.skuId), data.products)
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 重置表单 */
const resetForm = async () => {
spuList.value = []
spuPropertyList.value = []
await nextTick()
formRef.value.getElFormRef().resetFields()
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formRef.value.formModel as BargainActivityApi.BargainActivityVO
const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
products.forEach((item: BargainProductVO) => {
//
item.bargainFirstPrice = convertToInteger(item.bargainFirstPrice)
item.bargainPrice = convertToInteger(item.bargainPrice)
})
// ,
data.randomMinPrice = convertToInteger(data.randomMinPrice)
data.randomMaxPrice = convertToInteger(data.randomMaxPrice)
data.products = products
if (formType.value === 'create') {
await BargainActivityApi.createBargainActivity(data)
message.success(t('common.createSuccess'))
} else {
await BargainActivityApi.updateBargainActivity(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
</script>

View File

@ -0,0 +1,165 @@
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter2 } from '@/utils/formatTime'
// 表单校验
export const rules = reactive({
name: [required],
startTime: [required],
endTime: [required],
userSize: [required],
bargainCount: [required],
singleLimitCount: [required]
})
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive<CrudSchema[]>([
{
label: '砍价活动名称',
field: 'name',
isSearch: true,
isTable: false,
form: {
colProps: {
span: 24
}
}
},
{
label: '活动开始时间',
field: 'startTime',
formatter: dateFormatter2,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD',
type: 'daterange'
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'date',
valueFormat: 'x'
}
},
table: {
width: 120
}
},
{
label: '活动结束时间',
field: 'endTime',
formatter: dateFormatter2,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD',
type: 'daterange'
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'date',
valueFormat: 'x'
}
},
table: {
width: 120
}
},
{
label: '砍价人数',
field: 'userSize',
isSearch: false,
form: {
component: 'InputNumber',
labelMessage: '参与人数不能少于两人',
value: 2
}
},
{
label: '最大帮砍次数',
field: 'bargainCount',
isSearch: false,
form: {
component: 'InputNumber',
labelMessage: '参与人数不能少于两人',
value: 2
}
},
{
label: '总限购数量',
field: 'totalLimitCount',
isSearch: false,
form: {
component: 'InputNumber',
labelMessage: '用户最大能发起砍价的次数',
value: 0
}
},
{
label: '砍价的最小金额',
field: 'randomMinPrice',
isSearch: false,
isTable: false,
form: {
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
step: 0.1
},
labelMessage: '用户每次砍价的最小金额',
value: 0
}
},
{
label: '砍价的最大金额',
field: 'randomMaxPrice',
isSearch: false,
isTable: false,
form: {
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
step: 0.1
},
labelMessage: '用户每次砍价的最大金额',
value: 0
}
},
{
label: '砍价成功数量',
field: 'successCount',
isSearch: false,
isForm: false
},
{
label: '活动状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true,
isForm: false
},
{
label: '拼团商品',
field: 'spuId',
isSearch: false,
form: {
colProps: {
span: 24
}
}
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -0,0 +1,117 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button
v-hasPermi="['promotion:bargain-activity:create']"
plain
type="primary"
@click="openForm('create')"
>
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
</template>
</Search>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<Table
v-model:currentPage="tableObject.currentPage"
v-model:pageSize="tableObject.pageSize"
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
>
<template #spuId="{ row }">
<el-image
:src="row.picUrl"
class="w-30px h-30px align-middle mr-5px"
@click="imagePreview(row.picUrl)"
/>
<span class="align-middle">{{ row.spuName }}</span>
</template>
<template #action="{ row }">
<el-button
v-hasPermi="['promotion:bargain-activity:update']"
link
type="primary"
@click="openForm('update', row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi="['promotion:bargain-activity:delete']"
link
type="danger"
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</Table>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<BargainActivityForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { allSchemas } from './bargainActivity.data'
import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivity'
import BargainActivityForm from './BargainActivityForm.vue'
import { cloneDeep } from 'lodash-es'
import { createImageViewer } from '@/components/ImageViewer'
defineOptions({ name: 'PromotionBargainActivity' })
// tableObject
// tableMethods
// https://doc.iocoder.cn/vue3/crud-schema/
const { tableObject, tableMethods } = useTable({
getListApi: BargainActivityApi.getBargainActivityPage, //
delListApi: BargainActivityApi.deleteBargainActivity //
})
//
const { getList, setSearchParams } = tableMethods
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, false)
}
// TODO @puhui999使 element plus crud schema
/** 初始化 **/
onMounted(() => {
/**
TODO
后面准备封装成一个函数来操作 tableColumns 重新排列比如说需求是表单上商品选择是在后面的而列表展示的时候需要调到位置
封装效果支持批量操作给出 field 和需要插入的位置[{field:'spuId',index: 1}] 效果为把 field spuId column 移动到第一个位置
*/
//
const index = allSchemas.tableColumns.findIndex((item) => item.field === 'spuId')
const column = cloneDeep(allSchemas.tableColumns[index])
allSchemas.tableColumns.splice(index, 1)
//
allSchemas.tableColumns.unshift(column)
getList()
})
</script>