diff --git a/src/api/mall/product/spu.ts b/src/api/mall/product/spu.ts index b6bec97e..5ddaa92e 100644 --- a/src/api/mall/product/spu.ts +++ b/src/api/mall/product/spu.ts @@ -49,6 +49,16 @@ export interface Spu { recommendGood?: boolean // 是否优品 } +export interface SpuRespVO extends Spu { + price: number + salesCount: number + marketPrice: number + costPrice: number + stock: number + createTime: Date + status: number +} + // 获得 Spu 列表 export const getSpuPage = (params: PageParam) => { return request.get({ url: '/product/spu/page', params }) diff --git a/src/api/mall/promotion/seckill/seckillActivity.ts b/src/api/mall/promotion/seckill/seckillActivity.ts new file mode 100644 index 00000000..fc2d1871 --- /dev/null +++ b/src/api/mall/promotion/seckill/seckillActivity.ts @@ -0,0 +1,67 @@ +import request from '@/config/axios' +import { Sku, SpuRespVO } from '@/api/mall/product/spu' + +export interface SeckillActivityVO { + id: number + spuIds: number[] + name: string + status: number + remark: string + startTime: Date + endTime: Date + sort: number + configIds: string + orderCount: number + userCount: number + totalPrice: number + totalLimitCount: number + singleLimitCount: number + stock: number + totalStock: number + products: SeckillProductVO[] +} + +export interface SeckillProductVO { + spuId: number + skuId: number + seckillPrice: number + stock: number +} + +type SkuExtension = Sku & { + productConfig: SeckillProductVO +} + +export interface SpuExtension extends SpuRespVO { + skus: SkuExtension[] // 重写类型 +} + +// 查询秒杀活动列表 +export const getSeckillActivityPage = async (params) => { + return await request.get({ url: '/promotion/seckill-activity/page', params }) +} + +// 查询秒杀活动详情 +export const getSeckillActivity = async (id: number) => { + return await request.get({ url: '/promotion/seckill-activity/get?id=' + id }) +} + +// 新增秒杀活动 +export const createSeckillActivity = async (data: SeckillActivityVO) => { + return await request.post({ url: '/promotion/seckill-activity/create', data }) +} + +// 修改秒杀活动 +export const updateSeckillActivity = async (data: SeckillActivityVO) => { + return await request.put({ url: '/promotion/seckill-activity/update', data }) +} + +// 删除秒杀活动 +export const deleteSeckillActivity = async (id: number) => { + return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id }) +} + +// 导出秒杀活动 Excel +export const exportSeckillActivityApi = async (params) => { + return await request.download({ url: '/promotion/seckill-activity/export-excel', params }) +} diff --git a/src/api/mall/promotion/seckill/seckillConfig.ts b/src/api/mall/promotion/seckill/seckillConfig.ts new file mode 100644 index 00000000..aff72821 --- /dev/null +++ b/src/api/mall/promotion/seckill/seckillConfig.ts @@ -0,0 +1,54 @@ +import request from '@/config/axios' + +export interface SeckillConfigVO { + id: number + name: string + startTime: string + endTime: string + picUrl: string + status: number +} + +// 查询秒杀时段配置列表 +export const getSeckillConfigPage = async (params) => { + return await request.get({ url: '/promotion/seckill-config/page', params }) +} + +// 查询秒杀时段配置详情 +export const getSeckillConfig = async (id: number) => { + return await request.get({ url: '/promotion/seckill-config/get?id=' + id }) +} + +// 获得所有开启状态的秒杀时段精简列表 +export const getListAllSimple = async () => { + return await request.get({ url: '/promotion/seckill-config/list-all-simple' }) +} + +// 新增秒杀时段配置 +export const createSeckillConfig = async (data: SeckillConfigVO) => { + return await request.post({ url: '/promotion/seckill-config/create', data }) +} + +// 修改秒杀时段配置 +export const updateSeckillConfig = async (data: SeckillConfigVO) => { + return await request.put({ url: '/promotion/seckill-config/update', data }) +} + +// 修改时段配置状态 +export const updateSeckillConfigStatus = (id: number, status: number) => { + const data = { + id, + status + } + return request.put({ url: '/promotion/seckill-config/update-status', data: data }) +} + +// 删除秒杀时段配置 +export const deleteSeckillConfig = async (id: number) => { + return await request.delete({ url: '/promotion/seckill-config/delete?id=' + id }) +} + +// 导出秒杀时段配置 Excel +export const exportSeckillConfigApi = async (params) => { + return await request.download({ url: '/promotion/seckill-config/export-excel', params }) +} diff --git a/src/components/Dialog/src/Dialog.vue b/src/components/Dialog/src/Dialog.vue index 8fb71759..c1114d36 100644 --- a/src/components/Dialog/src/Dialog.vue +++ b/src/components/Dialog/src/Dialog.vue @@ -17,7 +17,7 @@ const props = defineProps({ }) const getBindValue = computed(() => { - const delArr: string[] = ['fullscreen', 'title', 'maxHeight'] + const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody'] const attrs = useAttrs() const obj = { ...attrs, ...props } for (const key in obj) { diff --git a/src/hooks/web/useCrudSchemas.ts b/src/hooks/web/useCrudSchemas.ts index a29f75ab..984e57c5 100644 --- a/src/hooks/web/useCrudSchemas.ts +++ b/src/hooks/web/useCrudSchemas.ts @@ -1,7 +1,7 @@ import { reactive } from 'vue' import { AxiosPromise } from 'axios' import { findIndex } from '@/utils' -import { eachTree, treeMap, filter } from '@/utils/tree' +import { eachTree, filter, treeMap } from '@/utils/tree' import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict' import { FormSchema } from '@/types/form' @@ -36,8 +36,11 @@ type CrudSearchParams = { type CrudTableParams = { // 是否显示表头 show?: boolean + // 列宽配置 + width?: number | string + // 列是否固定在左侧或者右侧 + fixed?: 'left' | 'right' } & Omit - type CrudFormParams = { // 是否显示表单项 show?: boolean diff --git a/src/types/table.d.ts b/src/types/table.d.ts index 3294234b..9cb4205b 100644 --- a/src/types/table.d.ts +++ b/src/types/table.d.ts @@ -1,6 +1,8 @@ export type TableColumn = { field: string label?: string + width?: number | string + fixed?: 'left' | 'right' children?: TableColumn[] } & Recordable diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index 0d68e362..b27cabdf 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -155,7 +155,7 @@ export const dateFormatter = (row, column, cellValue) => { * @returns 带时间00:00:00的日期 */ export function beginOfDay(param: Date) { - return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0, 0) + return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0) } /** @@ -164,7 +164,7 @@ export function beginOfDay(param: Date) { * @returns 带时间23:59:59的日期 */ export function endOfDay(param: Date) { - return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59, 999) + return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59) } /** diff --git a/src/views/mall/product/spu/components/SkuList.vue b/src/views/mall/product/spu/components/SkuList.vue index dcb2ccaf..da263686 100644 --- a/src/views/mall/product/spu/components/SkuList.vue +++ b/src/views/mall/product/spu/components/SkuList.vue @@ -1,7 +1,7 @@ + diff --git a/src/views/mall/promotion/seckill/activity/components/SpuAndSkuList.vue b/src/views/mall/promotion/seckill/activity/components/SpuAndSkuList.vue new file mode 100644 index 00000000..fc7ed15a --- /dev/null +++ b/src/views/mall/promotion/seckill/activity/components/SpuAndSkuList.vue @@ -0,0 +1,157 @@ + + diff --git a/src/views/mall/promotion/seckill/activity/components/SpuAndSkuSelectForm.vue b/src/views/mall/promotion/seckill/activity/components/SpuAndSkuSelectForm.vue new file mode 100644 index 00000000..c3de9a2b --- /dev/null +++ b/src/views/mall/promotion/seckill/activity/components/SpuAndSkuSelectForm.vue @@ -0,0 +1,272 @@ + + + diff --git a/src/views/mall/promotion/seckill/activity/components/index.ts b/src/views/mall/promotion/seckill/activity/components/index.ts new file mode 100644 index 00000000..ef92a41a --- /dev/null +++ b/src/views/mall/promotion/seckill/activity/components/index.ts @@ -0,0 +1,4 @@ +import SpuAndSkuSelectForm from './SpuAndSkuSelectForm.vue' +import SpuAndSkuList from './SpuAndSkuList.vue' + +export { SpuAndSkuSelectForm, SpuAndSkuList } diff --git a/src/views/mall/promotion/seckill/activity/index.vue b/src/views/mall/promotion/seckill/activity/index.vue new file mode 100644 index 00000000..de9f71db --- /dev/null +++ b/src/views/mall/promotion/seckill/activity/index.vue @@ -0,0 +1,86 @@ + + diff --git a/src/views/mall/promotion/seckill/activity/seckillActivity.data.ts b/src/views/mall/promotion/seckill/activity/seckillActivity.data.ts new file mode 100644 index 00000000..70524b91 --- /dev/null +++ b/src/views/mall/promotion/seckill/activity/seckillActivity.data.ts @@ -0,0 +1,261 @@ +import type { CrudSchema } from '@/hooks/web/useCrudSchemas' +import { dateFormatter } from '@/utils/formatTime' +import { getListAllSimple } from '@/api/mall/promotion/seckill/seckillConfig' + +// 表单校验 +export const rules = reactive({ + spuId: [required], + name: [required], + startTime: [required], + endTime: [required], + sort: [required], + configIds: [required], + totalLimitCount: [required], + singleLimitCount: [required], + totalStock: [required] +}) + +// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/ +const crudSchemas = reactive([ + { + label: '秒杀活动名称', + field: 'name', + isSearch: true, + form: { + colProps: { + span: 24 + } + }, + table: { + width: 120 + } + }, + { + label: '活动开始时间', + field: 'startTime', + formatter: dateFormatter, + isSearch: true, + search: { + component: 'DatePicker', + componentProps: { + valueFormat: 'YYYY-MM-DD HH:mm:ss', + type: 'daterange', + defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] + } + }, + form: { + component: 'DatePicker', + componentProps: { + type: 'date', + valueFormat: 'x' + } + }, + table: { + width: 300 + } + }, + { + label: '活动结束时间', + field: 'endTime', + formatter: dateFormatter, + isSearch: true, + search: { + component: 'DatePicker', + componentProps: { + valueFormat: 'YYYY-MM-DD HH:mm:ss', + type: 'daterange', + defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] + } + }, + form: { + component: 'DatePicker', + componentProps: { + type: 'date', + valueFormat: 'x' + } + }, + table: { + width: 300 + } + }, + { + label: '秒杀时段', + field: 'configIds', + form: { + component: 'Select', + componentProps: { + multiple: true, + optionsAlias: { + labelField: 'name', + valueField: 'id' + } + }, + api: getListAllSimple + }, + table: { + width: 300 + } + }, + { + label: '新增订单数', + field: 'orderCount', + isForm: false, + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '付款人数', + field: 'userCount', + isForm: false, + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '订单实付金额', + field: 'totalPrice', + isForm: false, + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '总限购数量', + field: 'totalLimitCount', + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '单次限够数量', + field: 'singleLimitCount', + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '秒杀库存', + field: 'stock', + isForm: false, + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '秒杀总库存', + field: 'totalStock', + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '秒杀活动商品', + field: 'spuId', + form: { + colProps: { + span: 24 + } + }, + table: { + width: 200 + } + }, + { + label: '创建时间', + field: 'createTime', + formatter: dateFormatter, + search: { + component: 'DatePicker', + componentProps: { + valueFormat: 'YYYY-MM-DD HH:mm:ss', + type: 'daterange', + defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] + } + }, + isForm: false, + table: { + width: 300 + } + }, + { + label: '排序', + field: 'sort', + form: { + component: 'InputNumber', + value: 0 + }, + table: { + width: 300 + } + }, + { + label: '状态', + field: 'status', + dictType: DICT_TYPE.COMMON_STATUS, + dictClass: 'number', + isForm: false, + isSearch: true, + form: { + component: 'Radio' + }, + table: { + width: 80 + } + }, + { + label: '备注', + field: 'remark', + form: { + component: 'Input', + componentProps: { + type: 'textarea', + rows: 4 + }, + colProps: { + span: 24 + } + }, + table: { + width: 300 + } + }, + { + label: '操作', + field: 'action', + isForm: false, + table: { + width: 120, + fixed: 'right' + } + } +]) +export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/src/views/mall/promotion/seckill/config/SeckillConfigForm.vue b/src/views/mall/promotion/seckill/config/SeckillConfigForm.vue new file mode 100644 index 00000000..e25f8fce --- /dev/null +++ b/src/views/mall/promotion/seckill/config/SeckillConfigForm.vue @@ -0,0 +1,66 @@ + + diff --git a/src/views/mall/promotion/seckill/config/index.vue b/src/views/mall/promotion/seckill/config/index.vue new file mode 100644 index 00000000..ac851a63 --- /dev/null +++ b/src/views/mall/promotion/seckill/config/index.vue @@ -0,0 +1,120 @@ + + diff --git a/src/views/mall/promotion/seckill/config/seckillConfig.data.ts b/src/views/mall/promotion/seckill/config/seckillConfig.data.ts new file mode 100644 index 00000000..49bf36af --- /dev/null +++ b/src/views/mall/promotion/seckill/config/seckillConfig.data.ts @@ -0,0 +1,79 @@ +import type { CrudSchema } from '@/hooks/web/useCrudSchemas' +import { dateFormatter } from '@/utils/formatTime' + +// 表单校验 +export const rules = reactive({ + name: [required], + startTime: [required], + endTime: [required], + picUrl: [required], + status: [required] +}) + +// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/ +const crudSchemas = reactive([ + { + label: '秒杀时段名称', + field: 'name', + isSearch: true + }, + { + label: '开始时间点', + field: 'startTime', + isSearch: false, + search: { + component: 'TimePicker' + }, + form: { + component: 'TimePicker', + componentProps: { + valueFormat: 'HH:mm:ss' + } + } + }, + { + label: '结束时间点', + field: 'endTime', + isSearch: false, + search: { + component: 'TimePicker' + }, + form: { + component: 'TimePicker', + componentProps: { + valueFormat: 'HH:mm:ss' + } + } + }, + { + label: '秒杀主图', + field: 'picUrl', + isSearch: false, + form: { + component: 'UploadImg' + } + }, + { + label: '状态', + field: 'status', + dictType: DICT_TYPE.COMMON_STATUS, + dictClass: 'number', + isSearch: true, + form: { + component: 'Radio' + } + }, + { + label: '创建时间', + field: 'createTime', + isForm: false, + isSearch: false, + formatter: dateFormatter + }, + { + label: '操作', + field: 'action', + isForm: false + } +]) +export const { allSchemas } = useCrudSchemas(crudSchemas)