feat:【mall 商城】优惠劵-模版(100% ele)

This commit is contained in:
YunaiV
2025-10-18 11:45:57 +08:00
parent 97e692d2fc
commit 2288748ca8
8 changed files with 458 additions and 463 deletions

View File

@@ -5,46 +5,28 @@ import { requestClient } from '#/api/request';
export namespace MallCouponApi {
/** 优惠券 */
export interface Coupon {
/** 优惠券编号 */
id: number;
/** 优惠券名称 */
name: string;
/** 优惠券状态 */
status: number;
/** 优惠券类型 */
type: number;
/** 优惠券金额 */
price: number;
/** 使用门槛 */
usePrice: number;
/** 商品范围 */
productScope: number;
/** 商品编号数组 */
productSpuIds: number[];
/** 有效期类型 */
validityType: number;
/** 固定日期-生效开始时间 */
validStartTime: Date;
/** 固定日期-生效结束时间 */
validEndTime: Date;
/** 领取日期-开始天数 */
fixedStartTerm: number;
/** 领取日期-结束天数 */
fixedEndTerm: number;
/** 每人限领个数 */
takeLimitCount: number;
/** 是否设置满多少金额可用 */
usePriceEnabled: boolean;
/** 商品分类编号数组 */
productCategoryIds: number[];
id: number; // 优惠券编号
name: string; // 优惠券名称
status: number; // 优惠券状态
type: number; // 优惠券类型
price: number; // 优惠券金额
usePrice: number; // 使用门槛
productScope: number; // 商品范围
productSpuIds: number[]; // 商品编号数组
validityType: number; // 有效期类型
validStartTime: Date; // 固定日期-生效开始时间
validEndTime: Date; // 固定日期-生效结束时间
fixedStartTerm: number; // 领取日期-开始天数
fixedEndTerm: number; // 领取日期-结束天数
takeLimitCount: number; // 每人限领个数
usePriceEnabled: boolean; // 是否设置满多少金额可用
productCategoryIds: number[]; // 商品分类编号数组
}
/** 发送优惠券 */
export interface CouponSendReqVO {
/** 优惠券编号 */
templateId: number;
/** 用户编号数组 */
userIds: number[];
templateId: number; // 优惠券编号
userIds: number[]; // 用户编号数组
}
}

View File

@@ -5,54 +5,26 @@ import { requestClient } from '#/api/request';
export namespace MallCouponTemplateApi {
/** 优惠券模板 */
export interface CouponTemplate {
/** 模板编号 */
id: number;
/** 模板名称 */
name: string;
/** 状态 */
status: number;
/** 发放数量 */
totalCount: number;
/** 每人限领个数 */
takeLimitCount: number;
/** 领取方式 */
takeType: number;
/** 使用门槛 */
usePrice: number;
/** 商品范围 */
productScope: number;
/** 商品范围值 */
productScopeValues: number[];
/** 有效期类型 */
validityType: number;
/** 固定日期-生效开始时间 */
validStartTime: Date;
/** 固定日期-生效结束时间 */
validEndTime: Date;
/** 领取日期-开始天数 */
fixedStartTerm: number;
/** 领取日期-结束天数 */
fixedEndTerm: number;
/** 优惠类型 */
discountType: number;
/** 折扣百分比 */
discountPercent: number;
/** 优惠金额 */
discountPrice: number;
/** 折扣上限 */
discountLimitPrice: number;
/** 已领取数量 */
takeCount: number;
/** 已使用数量 */
useCount: number;
}
/** 优惠券模板状态更新 */
export interface StatusUpdate {
/** 模板编号 */
id: number;
/** 状态 */
status: 0 | 1;
id: number; // 模板编号
name: string; // 模板名称
status: number; // 状态
totalCount: number; // 发放数量
takeLimitCount: number; // 每人限领个数
takeType: number; // 领取方式
usePrice: number; // 使用门槛
productScope: number; // 商品范围
productScopeValues: number[]; // 商品范围值
validityType: number; // 有效期类型
validStartTime: Date; // 固定日期-生效开始时间
validEndTime: Date; // 固定日期-生效结束时间
fixedStartTerm: number; // 领取日期-开始天数
fixedEndTerm: number; // 领取日期-结束天数
discountType: number; // 优惠类型
discountPercent?: number; // 折扣百分比
discountPrice: number; // 优惠金额
discountLimitPrice?: number; // 折扣上限
takeCount: number; // 已领取数量
useCount: number; // 已使用数量
}
}
@@ -71,9 +43,11 @@ export function updateCouponTemplate(
}
/** 更新优惠劵模板的状态 */
export function updateCouponTemplateStatus(id: number, status: 0 | 1) {
const data: MallCouponTemplateApi.StatusUpdate = { id, status };
return requestClient.put('/promotion/coupon-template/update-status', data);
export function updateCouponTemplateStatus(id: number, status: number) {
return requestClient.put('/promotion/coupon-template/update-status', {
id,
status,
});
}
/** 删除优惠劵模板 */
@@ -102,11 +76,3 @@ export function getCouponTemplateList(ids: number[]) {
`/promotion/coupon-template/list?ids=${ids}`,
);
}
/** 导出优惠劵模板 Excel */
export function exportCouponTemplateExcel(params: PageParam) {
return requestClient.get('/promotion/coupon-template/export-excel', {
params,
responseType: 'blob',
});
}

View File

@@ -6,11 +6,11 @@ export namespace MallOrderApi {
/** 商品属性 */
export interface ProductProperty {
/** 属性的编号 */
propertyId?: null | number;
propertyId?: number;
/** 属性的名称 */
propertyName?: string;
/** 属性值的编号 */
valueId?: null | number;
valueId?: number;
/** 属性值的名称 */
valueName?: string;
}
@@ -18,35 +18,35 @@ export namespace MallOrderApi {
/** 订单项 */
export interface OrderItem {
/** 编号 */
id?: null | number;
id?: number;
/** 用户编号 */
userId?: null | number;
userId?: number;
/** 订单编号 */
orderId?: null | number;
orderId?: number;
/** 商品 SPU 编号 */
spuId?: null | number;
spuId?: number;
/** 商品 SPU 名称 */
spuName?: string;
/** 商品 SKU 编号 */
skuId?: null | number;
skuId?: number;
/** 商品图片 */
picUrl?: string;
/** 购买数量 */
count?: null | number;
count?: number;
/** 商品原价(总) */
originalPrice?: null | number;
originalPrice?: number;
/** 商品原价(单) */
originalUnitPrice?: null | number;
originalUnitPrice?: number;
/** 商品优惠(总) */
discountPrice?: null | number;
discountPrice?: number;
/** 商品实付金额(总) */
payPrice?: null | number;
payPrice?: number;
/** 子订单分摊金额(总) */
orderPartPrice?: null | number;
orderPartPrice?: number;
/** 分摊后子订单实付金额(总) */
orderDividePrice?: null | number;
orderDividePrice?: number;
/** 售后状态 */
afterSaleStatus?: null | number;
afterSaleStatus?: number;
/** 属性数组 */
properties?: ProductProperty[];
price?: number;
@@ -54,104 +54,104 @@ export namespace MallOrderApi {
/** 订单日志 */
export interface OrderLog {
/** 日志编号 */
id: number;
/** 日志内容 */
content?: string;
/** 创建时间 */
createTime?: Date;
/** 用户类型 */
userType?: number;
/** 用户编号 */
userId?: number;
}
/** 订单 */
export interface Order {
/** 订单编号 */
id?: null | number;
id?: number;
/** 订单流水号 */
no?: string;
/** 下单时间 */
createTime?: Date | null;
createTime?: Date;
/** 订单类型 */
type?: null | number;
type?: number;
/** 订单来源 */
terminal?: null | number;
terminal?: number;
/** 用户编号 */
userId?: null | number;
userId?: number;
/** 用户 IP */
userIp?: string;
/** 用户备注 */
userRemark?: string;
/** 订单状态 */
status?: null | number;
status?: number;
/** 购买的商品数量 */
productCount?: null | number;
productCount?: number;
/** 订单完成时间 */
finishTime?: Date | null;
finishTime?: Date;
/** 订单取消时间 */
cancelTime?: Date | null;
cancelTime?: Date;
/** 取消类型 */
cancelType?: null | number;
cancelType?: number;
/** 商家备注 */
remark?: string;
/** 支付订单编号 */
payOrderId?: null | number;
payOrderId?: number;
/** 是否已支付 */
payStatus?: boolean;
/** 付款时间 */
payTime?: Date | null;
payTime?: Date;
/** 支付渠道 */
payChannelCode?: string;
/** 商品原价(总) */
totalPrice?: null | number;
totalPrice?: number;
/** 订单优惠(总) */
discountPrice?: null | number;
discountPrice?: number;
/** 运费金额 */
deliveryPrice?: null | number;
deliveryPrice?: number;
/** 订单调价(总) */
adjustPrice?: null | number;
adjustPrice?: number;
/** 应付金额(总) */
payPrice?: null | number | string;
payPrice?: number;
/** 发货方式 */
deliveryType?: null | number;
deliveryType?: number;
/** 自提门店编号 */
pickUpStoreId?: number;
/** 自提核销码 */
pickUpVerifyCode?: string;
/** 配送模板编号 */
deliveryTemplateId?: null | number;
deliveryTemplateId?: number;
/** 发货物流公司编号 */
logisticsId?: number;
/** 发货物流单号 */
logisticsNo?: string;
/** 发货时间 */
deliveryTime?: Date | null;
deliveryTime?: Date;
/** 收货时间 */
receiveTime?: Date | null;
receiveTime?: Date;
/** 收件人名称 */
receiverName?: string;
/** 收件人手机 */
receiverMobile?: string;
/** 收件人邮编 */
receiverPostCode?: null | number;
receiverPostCode?: number;
/** 收件人地区编号 */
receiverAreaId?: null | number;
receiverAreaId?: number;
/** 收件人地区名字 */
receiverAreaName?: string;
/** 收件人详细地址 */
receiverDetailAddress?: string;
/** 售后状态 */
afterSaleStatus?: null | number;
afterSaleStatus?: number;
/** 退款金额 */
refundPrice?: null | number;
refundPrice?: number;
/** 优惠劵编号 */
couponId?: null | number;
couponId?: number;
/** 优惠劵减免金额 */
couponPrice?: null | number;
couponPrice?: number;
/** 积分抵扣的金额 */
pointPrice?: null | number;
pointPrice?: number;
/** VIP 减免金额 */
vipPrice?: null | number;
vipPrice?: number;
/** 订单项列表 */
items?: OrderItem[];
/** 下单用户信息 */
@@ -159,7 +159,7 @@ export namespace MallOrderApi {
/** 用户头像 */
avatar?: string;
/** 用户编号 */
id?: null | number;
id?: number;
/** 用户昵称 */
nickname?: string;
};
@@ -168,7 +168,7 @@ export namespace MallOrderApi {
/** 用户头像 */
avatar?: string;
/** 用户编号 */
id?: null | number;
id?: number;
/** 用户昵称 */
nickname?: string;
};
@@ -195,7 +195,7 @@ export namespace MallOrderApi {
/** 发货方式 */
expressType: string;
/** 物流公司编号 */
logisticsId: null | number;
logisticsId: number;
/** 物流编号 */
logisticsNo: string;
}
@@ -229,13 +229,6 @@ export namespace MallOrderApi {
/** 收件人详细地址 */
receiverDetailAddress: string;
}
export interface OrderExpressTrackRespDTO {
/** 发生时间 */
time: Date;
/** 快递状态 */
content: string;
}
}
/** 查询交易订单列表 */
@@ -264,9 +257,7 @@ export function getOrder(id: number) {
/** 查询交易订单物流详情 */
export function getExpressTrackList(id: number) {
return requestClient.get<MallOrderApi.OrderExpressTrackRespDTO[]>(
`/trade/order/get-express-track-list?id=${id}`,
);
return requestClient.get(`/trade/order/get-express-track-list?id=${id}`);
}
/** 订单发货 */
@@ -296,7 +287,7 @@ export function pickUpOrder(id: number) {
/** 订单核销 */
export function pickUpOrderByVerifyCode(pickUpVerifyCode: string) {
return requestClient.put('/trade/order/pick-up-by-verify-code', {
return requestClient.put('/trade/order/pick-up-by-verify-code', undefined, {
params: { pickUpVerifyCode },
});
}

View File

@@ -1,223 +0,0 @@
<script lang="ts" setup>
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { reactive, ref } from 'vue';
import { ContentWrap } from '@vben/common-ui';
import { CouponTemplateTakeTypeEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import * as CouponTemplateApi from '#/api/mall/promotion/coupon/couponTemplate';
import {
discountFormat,
remainedCountFormat,
takeLimitCountFormat,
validityTypeFormat,
} from '#/views/mall/promotion/coupon/formatter';
defineOptions({ name: 'CouponSelect' });
const props = defineProps<{
multipleSelection?: MallCouponTemplateApi.CouponTemplate[];
takeType: number; // 领取方式
}>();
const emit = defineEmits<{
(
e: 'update:multipleSelection',
v: MallCouponTemplateApi.CouponTemplate[],
): void;
(e: 'change', v: MallCouponTemplateApi.CouponTemplate[]): void;
}>();
const dialogVisible = ref(false); // 弹窗的是否展示
const dialogTitle = ref('选择优惠劵'); // 弹窗的标题
const formLoading = ref(false); // 表单的加载中1修改时的数据加载2提交的按钮禁用
const loading = ref(true); // 列表的加载中
const total = ref(0); // 列表的总页数
const list = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 字典表格数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
discountType: null,
canTakeTypes: [CouponTemplateTakeTypeEnum.USER.type], // 只获得直接领取的券
});
const queryFormRef = ref(); // 搜索的表单
const selectedCouponList = ref<MallCouponTemplateApi.CouponTemplate[]>([]); // 选择的数据
/** 查询列表 */
const getList = async () => {
loading.value = true;
try {
// 执行查询
queryParams.canTakeTypes = [props.takeType] as any;
const data = await CouponTemplateApi.getCouponTemplatePage(queryParams);
list.value = data.list;
total.value = data.total;
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef?.value?.resetFields();
handleQuery();
};
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true;
resetQuery();
};
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
const handleSelectionChange = (val: MallCouponTemplateApi.CouponTemplate[]) => {
if (props.multipleSelection) {
emit('update:multipleSelection', val);
return;
}
selectedCouponList.value = val;
};
const submitForm = () => {
dialogVisible.value = false;
emit('change', selectedCouponList.value);
};
</script>
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="65%">
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="82px"
>
<el-form-item label="优惠券名称" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
clearable
placeholder="请输入优惠劵名"
@keyup="handleQuery"
/>
</el-form-item>
<el-form-item label="优惠类型" prop="discountType">
<el-select
v-model="queryParams.discountType"
class="!w-240px"
clearable
placeholder="请选择优惠券类型"
>
<el-option
v-for="dict in getDictOptions(
DICT_TYPE.PROMOTION_DISCOUNT_TYPE,
'number',
)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<IconifyIcon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<IconifyIcon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column label="优惠券名称" min-width="140" prop="name" />
<el-table-column label="类型" min-width="80" prop="productScope">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE"
:value="scope.row.productScope"
/>
</template>
</el-table-column>
<el-table-column label="优惠" min-width="100" prop="discount">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE"
:value="scope.row.discountType"
/>
{{ discountFormat(scope.row) }}
</template>
</el-table-column>
<el-table-column label="领取方式" min-width="100" prop="takeType">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE"
:value="scope.row.takeType"
/>
</template>
</el-table-column>
<el-table-column
:formatter="validityTypeFormat"
align="center"
label="使用时间"
prop="validityType"
width="185"
/>
<el-table-column align="center" label="发放数量" prop="totalCount" />
<el-table-column
:formatter="remainedCountFormat"
align="center"
label="剩余数量"
prop="totalCount"
/>
<el-table-column
:formatter="takeLimitCountFormat"
align="center"
label="领取上限"
prop="takeLimitCount"
/>
<el-table-column align="center" label="状态" prop="status">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.COMMON_STATUS"
:value="scope.row.status"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">
确 定
</el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@@ -1,11 +1,17 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import {
CommonStatusEnum,
CouponTemplateTakeTypeEnum,
CouponTemplateValidityTypeEnum,
DICT_TYPE,
PromotionDiscountTypeEnum,
PromotionProductScopeEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
// 格式化函数移到组件内部实现
import { z } from '#/adapter/form';
import { getRangePickerDefaultProps } from '#/utils';
import {
@@ -40,69 +46,289 @@ export function useFormSchema(): VbenFormSchema[] {
fieldName: 'description',
label: '优惠券描述',
component: 'Textarea',
componentProps: {
placeholder: '请输入优惠券描述',
},
},
// TODO @霖:不同的优惠,不同的选择
{
fieldName: 'productScope',
label: '优惠类型',
label: '优惠类型',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE, 'number'),
},
rules: 'required',
defaultValue: PromotionProductScopeEnum.ALL.scope,
},
// TODO @puhui999 商品选择器优化
{
fieldName: 'productSpuIds',
label: '商品',
component: 'Input',
componentProps: {
placeholder: '请选择商品',
},
dependencies: {
triggerFields: ['productScope', 'productScopeValues'],
show: (model) =>
model.productScope === PromotionProductScopeEnum.SPU.scope,
trigger(values, form) {
// 当加载已有数据时,根据 productScopeValues 设置 productSpuIds
if (
values.productScope === PromotionProductScopeEnum.SPU.scope &&
values.productScopeValues
) {
form.setFieldValue('productSpuIds', values.productScopeValues);
}
},
},
rules: 'required',
},
// TODO @puhui999 商品分类选择器优化
{
fieldName: 'productCategoryIds',
label: '商品分类',
component: 'Input',
componentProps: {
placeholder: '请选择商品分类',
},
dependencies: {
triggerFields: ['productScope', 'productScopeValues'],
show: (model) =>
model.productScope === PromotionProductScopeEnum.CATEGORY.scope,
trigger(values, form) {
// 当加载已有数据时,根据 productScopeValues 设置 productCategoryIds
if (
values.productScope === PromotionProductScopeEnum.CATEGORY.scope &&
values.productScopeValues
) {
const categoryIds = values.productScopeValues;
// 单选时使用数组不能反显,取第一个元素
form.setFieldValue(
'productCategoryIds',
Array.isArray(categoryIds) && categoryIds.length > 0
? categoryIds[0]
: categoryIds,
);
}
},
},
rules: 'required',
},
{
fieldName: 'discountType',
label: '优惠类型',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'),
},
rules: 'required',
defaultValue: PromotionDiscountTypeEnum.PRICE.type,
},
{
fieldName: 'discountPrice',
label: '优惠券面额',
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
placeholder: '请输入优惠金额,单位:元',
addonAfter: '元',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['discountType'],
show: (model) =>
model.discountType === PromotionDiscountTypeEnum.PRICE.type,
},
rules: 'required',
},
{
fieldName: 'discountPercent',
label: '优惠券折扣',
component: 'InputNumber',
componentProps: {
min: 1,
max: 9.9,
precision: 1,
placeholder: '优惠券折扣不能小于 1 折,且不可大于 9.9 折',
addonAfter: '折',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['discountType'],
show: (model) =>
model.discountType === PromotionDiscountTypeEnum.PERCENT.type,
},
rules: 'required',
},
{
fieldName: 'discountLimitPrice',
label: '最多优惠',
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
placeholder: '请输入最多优惠',
addonAfter: '元',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['discountType'],
show: (model) =>
model.discountType === PromotionDiscountTypeEnum.PERCENT.type,
},
rules: 'required',
},
{
fieldName: 'usePrice',
label: '满多少元可以使用',
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
placeholder: '无门槛请设为 0',
addonAfter: '元',
controlsPosition: 'right',
class: '!w-full',
},
rules: 'required',
},
{
fieldName: 'takeType',
label: '领取方式',
component: 'Select',
component: 'RadioGroup',
componentProps: {
placeholder: '请选择领取方式',
options: getDictOptions(DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE, 'number'),
},
rules: 'required',
},
// TODO @xingu不同的有效期不同的类型
{
fieldName: 'validityType',
label: '有效期类型',
component: 'Select',
componentProps: {
placeholder: '请选择有效期类型',
options: getDictOptions(
DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE,
'number',
),
},
rules: 'required',
defaultValue: CouponTemplateTakeTypeEnum.USER.type,
},
{
fieldName: 'totalCount',
label: '发放数量',
component: 'InputNumber',
componentProps: {
min: 0,
placeholder: '请输入发放数量',
min: -1,
placeholder: '发放数量,没有之后不能领取或发放,-1 为不限制',
addonAfter: '张',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['takeType'],
show: (model) =>
model.takeType === CouponTemplateTakeTypeEnum.USER.type,
},
rules: 'required',
},
{
fieldName: 'takeLimitCount',
label: '领取上限',
label: '每人限领个数',
component: 'InputNumber',
componentProps: {
min: 0,
placeholder: '请输入领取上限',
min: -1,
placeholder: '设置为 -1 时,可无限领取',
addonAfter: '张',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['takeType'],
show: (model) => model.takeType === 1,
},
rules: 'required',
},
{
fieldName: 'status',
label: '优惠券状态',
fieldName: 'validityType',
label: '有效期类型',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
options: getDictOptions(
DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE,
'number',
),
},
defaultValue: CouponTemplateValidityTypeEnum.DATE.type,
rules: 'required',
},
{
fieldName: 'validTimes',
label: '固定日期',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
valueFormat: 'x',
},
dependencies: {
triggerFields: ['validityType'],
show: (model) =>
model.validityType === CouponTemplateValidityTypeEnum.DATE.type,
},
rules: 'required',
},
{
fieldName: 'fixedStartTerm',
label: '领取日期',
component: 'InputNumber',
componentProps: {
min: 0,
placeholder: '第 0 为今天生效',
addonBefore: '第',
addonAfter: '天',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['validityType'],
show: (model) =>
model.validityType === CouponTemplateValidityTypeEnum.TERM.type,
},
rules: 'required',
},
{
fieldName: 'fixedEndTerm',
component: 'InputNumber',
componentProps: {
min: 0,
placeholder: '请输入结束天数',
addonBefore: '至',
addonAfter: '天有效',
controlsPosition: 'right',
class: '!w-full',
},
dependencies: {
triggerFields: ['validityType'],
show: (model) =>
model.validityType === CouponTemplateValidityTypeEnum.TERM.type,
},
rules: 'required',
},
{
fieldName: 'productScopeValues',
component: 'Input',
dependencies: {
triggerFields: ['productScope', 'productSpuIds', 'productCategoryIds'],
show: () => false,
trigger(values, form) {
switch (values.productScope) {
case PromotionProductScopeEnum.CATEGORY.scope: {
const categoryIds = Array.isArray(values.productCategoryIds)
? values.productCategoryIds
: [values.productCategoryIds];
form.setFieldValue('productScopeValues', categoryIds);
break;
}
case PromotionProductScopeEnum.SPU.scope: {
form.setFieldValue('productScopeValues', values.productSpuIds);
break;
}
}
},
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
];
}
@@ -115,7 +341,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '优惠券名称',
component: 'Input',
componentProps: {
placeholder: '请输入优惠券名称',
placeholder: '请输入优惠劵名',
clearable: true,
},
},
@@ -152,9 +378,13 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
export function useGridColumns(
onStatusChange?: (
newStatus: number,
row: MallCouponTemplateApi.CouponTemplate,
) => PromiseLike<boolean | undefined>,
): VxeTableGridOptions['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'name',
title: '优惠券名称',
@@ -231,7 +461,15 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
field: 'status',
title: '状态',
minWidth: 100,
slots: { default: 'status' },
align: 'center',
cellRender: {
attrs: { beforeChange: onStatusChange },
name: 'CellSwitch',
props: {
activeValue: CommonStatusEnum.ENABLE,
inactiveValue: CommonStatusEnum.DISABLE,
},
},
},
{
field: 'createTime',

View File

@@ -2,13 +2,11 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate';
import { ref } from 'vue';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants';
import { $t } from '@vben/locales';
import { ElLoading, ElMessage, ElSwitch } from 'element-plus';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -48,7 +46,7 @@ async function handleDelete(row: MallCouponTemplateApi.CouponTemplate) {
text: $t('ui.actionMessage.deleting', [row.name]),
});
try {
await deleteCouponTemplate(row.id as number);
await deleteCouponTemplate(row.id!);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
@@ -56,33 +54,30 @@ async function handleDelete(row: MallCouponTemplateApi.CouponTemplate) {
}
}
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: MallCouponTemplateApi.CouponTemplate[];
}) {
checkedIds.value = records.map((item) => item.id!);
}
/** 优惠券模板状态修改 */
async function handleStatusChange(row: MallCouponTemplateApi.CouponTemplate) {
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用';
const loadingInstance = ElLoading.service({
text: `正在${text}优惠券模板...`,
async function handleStatusChange(
newStatus: number,
row: MallCouponTemplateApi.CouponTemplate,
): Promise<boolean | undefined> {
return new Promise((resolve, reject) => {
confirm({
content: `你要将${row.name}的状态切换为【${newStatus === CommonStatusEnum.ENABLE ? '启用' : '停用'}】吗?`,
})
.then(async () => {
// 更新优惠券模板状态
const res = await updateCouponTemplateStatus(row.id!, newStatus);
if (res) {
// 提示并返回成功
ElMessage.success($t('ui.actionMessage.operationSuccess'));
resolve(true);
} else {
reject(new Error('更新失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
try {
await updateCouponTemplateStatus(row.id as number, row.status as 0 | 1);
ElMessage.success(`${text}成功`);
} catch {
// 异常时,需要将 row.status 状态重置回之前的
row.status =
row.status === CommonStatusEnum.ENABLE
? CommonStatusEnum.DISABLE
: CommonStatusEnum.ENABLE;
} finally {
loadingInstance.close();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -90,7 +85,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
columns: useGridColumns(handleStatusChange),
height: 'auto',
keepSource: true,
proxyConfig: {
@@ -113,10 +108,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true,
},
} as VxeTableGridOptions<MallCouponTemplateApi.CouponTemplate>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
@@ -144,15 +135,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
]"
/>
</template>
<template #status="{ row }">
<ElSwitch
v-model:checked="row.status"
:checked-value="CommonStatusEnum.ENABLE"
:un-checked-value="CommonStatusEnum.DISABLE"
@change="handleStatusChange(row)"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[

View File

@@ -4,6 +4,8 @@ import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTe
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CouponTemplateTakeTypeEnum } from '@vben/constants';
import { convertToInteger, formatToFraction } from '@vben/utils';
import { ElMessage } from 'element-plus';
@@ -31,7 +33,7 @@ const [Form, formApi] = useVbenForm({
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
labelWidth: 120,
},
layout: 'horizontal',
schema: useFormSchema(),
@@ -46,8 +48,8 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
// 提交表单
const data =
(await formApi.getValues()) as MallCouponTemplateApi.CouponTemplate;
const formValues = (await formApi.getValues()) as any;
const data = await processSubmitData(formValues);
try {
await (formData.value?.id
? updateCouponTemplate(data)
@@ -73,17 +75,75 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock();
try {
formData.value = await getCouponTemplate(data.id);
// 设置到 values
await formApi.setValues(formData.value);
const processedData = await processLoadData(formData.value);
// 设置到表单
await formApi.setValues(processedData);
} finally {
modalApi.unlock();
}
},
});
/** 处理提交数据 */
async function processSubmitData(
formValues: any,
): Promise<MallCouponTemplateApi.CouponTemplate> {
return {
...formValues,
// 金额转换:元转分
discountPrice: convertToInteger(formValues.discountPrice),
discountPercent:
formValues.discountPercent === undefined
? undefined
: formValues.discountPercent * 10,
discountLimitPrice: convertToInteger(formValues.discountLimitPrice),
usePrice: convertToInteger(formValues.usePrice),
// 处理有效期时间
validStartTime:
formValues.validTimes && formValues.validTimes.length === 2
? formValues.validTimes[0]
: undefined,
validEndTime:
formValues.validTimes && formValues.validTimes.length === 2
? formValues.validTimes[1]
: undefined,
// 处理发放数量和限领数量
totalCount:
formValues.takeType === CouponTemplateTakeTypeEnum.USER.type
? formValues.totalCount
: -1,
takeLimitCount:
formValues.takeType === CouponTemplateTakeTypeEnum.USER.type
? formValues.takeLimitCount
: -1,
};
}
/** 处理加载的数据 */
async function processLoadData(
data: MallCouponTemplateApi.CouponTemplate,
): Promise<any> {
return {
...data,
// 金额转换:分转元
discountPrice: formatToFraction(data.discountPrice),
discountPercent:
data.discountPercent === undefined
? undefined
: data.discountPercent / 10,
discountLimitPrice: formatToFraction(data.discountLimitPrice),
usePrice: formatToFraction(data.usePrice),
// 处理有效期时间
validTimes:
data.validStartTime && data.validEndTime
? [data.validStartTime, data.validEndTime]
: [],
};
}
</script>
<template>
<Modal class="w-2/5" :title="getTitle">
<Modal :title="getTitle" class="w-2/5">
<Form class="mx-4" />
</Modal>
</template>