1166 lines
33 KiB
Vue
1166 lines
33 KiB
Vue
<!-- 编辑弹窗 -->
|
||
<template>
|
||
<ele-modal
|
||
:width="1200"
|
||
:visible="visible"
|
||
:maskClosable="false"
|
||
:maxable="maxable"
|
||
:title="isUpdate ? '编辑优惠券' : '新增优惠券'"
|
||
:body-style="{ paddingBottom: '28px' }"
|
||
@update:visible="updateVisible"
|
||
@ok="save"
|
||
>
|
||
<a-form
|
||
ref="formRef"
|
||
:model="form"
|
||
:rules="rules"
|
||
:label-col="{ span: 6 }"
|
||
:wrapper-col="{ span: 18 }"
|
||
>
|
||
<!-- 基本信息 -->
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">基本信息</span>
|
||
</a-divider>
|
||
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item label="优惠券名称" name="name">
|
||
<a-input placeholder="请输入优惠券名称" v-model:value="form.name" />
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item label="优惠券类型" name="type">
|
||
<a-select
|
||
v-model:value="form.type"
|
||
placeholder="请选择优惠券类型"
|
||
@change="onTypeChange"
|
||
>
|
||
<a-select-option :value="10">
|
||
<div class="coupon-type-option">
|
||
<a-tag color="red">满减券</a-tag>
|
||
<span>满足条件减免金额</span>
|
||
</div>
|
||
</a-select-option>
|
||
<a-select-option :value="20">
|
||
<div class="coupon-type-option">
|
||
<a-tag color="orange">折扣券</a-tag>
|
||
<span>按比例折扣</span>
|
||
</div>
|
||
</a-select-option>
|
||
<a-select-option :value="30">
|
||
<div class="coupon-type-option">
|
||
<a-tag color="green">免费券</a-tag>
|
||
<span>免费使用</span>
|
||
</div>
|
||
</a-select-option>
|
||
<a-select-option :value="40">
|
||
<div class="coupon-type-option">
|
||
<a-tag color="blue">无门槛券</a-tag>
|
||
<span>无最低消费限制</span>
|
||
</div>
|
||
</a-select-option>
|
||
<a-select-option :value="50">
|
||
<div class="coupon-type-option">
|
||
<a-tag color="cyan">场地使用券</a-tag>
|
||
<span>指定场地使用权限</span>
|
||
</div>
|
||
</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<a-form-item label="优惠券描述" name="description">
|
||
<a-textarea
|
||
placeholder="请输入优惠券描述"
|
||
v-model:value="form.description"
|
||
:rows="3"
|
||
:maxlength="200"
|
||
show-count
|
||
/>
|
||
</a-form-item>
|
||
|
||
<!-- 优惠设置 -->
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">优惠设置</span>
|
||
</a-divider>
|
||
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item
|
||
v-if="form.type !== 40 && form.type !== 50"
|
||
label="最低消费金额"
|
||
name="minPrice"
|
||
>
|
||
<a-input-number
|
||
:min="0"
|
||
:precision="2"
|
||
placeholder="请输入最低消费金额"
|
||
v-model:value="form.minPrice"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>元</template>
|
||
</a-input-number>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<!-- 满减券设置 -->
|
||
<a-form-item
|
||
v-if="form.type === 10"
|
||
label="减免金额"
|
||
name="reducePrice"
|
||
>
|
||
<a-input-number
|
||
:min="0"
|
||
:precision="2"
|
||
placeholder="请输入减免金额"
|
||
v-model:value="form.reducePrice"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>元</template>
|
||
</a-input-number>
|
||
</a-form-item>
|
||
|
||
<!-- 折扣券设置 -->
|
||
<a-form-item v-if="form.type === 20" label="折扣率" name="discount">
|
||
<a-input-number
|
||
:min="0.1"
|
||
:max="99.9"
|
||
:precision="1"
|
||
placeholder="请输入折扣率"
|
||
v-model:value="form.discount"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>折</template>
|
||
</a-input-number>
|
||
</a-form-item>
|
||
|
||
<!-- 无门槛券设置 -->
|
||
<a-form-item
|
||
v-if="form.type === 40"
|
||
label="减免金额"
|
||
name="reducePrice"
|
||
>
|
||
<a-input-number
|
||
:min="0"
|
||
:precision="2"
|
||
placeholder="请输入减免金额"
|
||
v-model:value="form.reducePrice"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>元</template>
|
||
</a-input-number>
|
||
<div style="color: #999; font-size: 12px; margin-top: 4px">
|
||
无门槛券无需最低消费即可使用
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<!-- 场地使用券设置 -->
|
||
<div v-if="form.type === 50" class="venue-config">
|
||
<a-alert
|
||
message="场地使用券配置"
|
||
description="设置场地使用券的具体参数"
|
||
type="info"
|
||
show-icon
|
||
style="margin-bottom: 12px"
|
||
/>
|
||
<a-form-item label="可用次数" name="useCount">
|
||
<a-input-number
|
||
:min="-1"
|
||
placeholder="-1表示无限次"
|
||
v-model:value="form.useCount"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>次</template>
|
||
</a-input-number>
|
||
</a-form-item>
|
||
<a-form-item label="使用时长(分钟)" name="useDuration">
|
||
<a-input-number
|
||
:min="0"
|
||
placeholder="每次可用时长,0表示不限制"
|
||
v-model:value="form.useDuration"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>分钟</template>
|
||
</a-input-number>
|
||
</a-form-item>
|
||
</div>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<!-- 有效期设置 -->
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">有效期设置</span>
|
||
</a-divider>
|
||
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item label="到期类型" name="expireType">
|
||
<a-radio-group
|
||
v-model:value="form.expireType"
|
||
@change="onExpireTypeChange"
|
||
>
|
||
<a-radio :value="10">
|
||
<a-tag color="blue">领取后生效</a-tag>
|
||
<span style="margin-left: 8px">用户领取后开始计时</span>
|
||
</a-radio>
|
||
<a-radio :value="20">
|
||
<a-tag color="purple">固定时间</a-tag>
|
||
<span style="margin-left: 8px">指定有效期时间段</span>
|
||
</a-radio>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item
|
||
v-if="form.expireType === 10"
|
||
label="有效天数"
|
||
name="expireDay"
|
||
>
|
||
<a-input-number
|
||
:min="1"
|
||
placeholder="请输入有效天数"
|
||
v-model:value="form.expireDay"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>天</template>
|
||
</a-input-number>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<div v-if="form.expireType === 20" class="fixed-time-config">
|
||
<a-alert
|
||
message="固定时间配置"
|
||
description="设置优惠券的固定有效期时间段"
|
||
type="info"
|
||
show-icon
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item label="有效期开始时间" name="startTime">
|
||
<a-date-picker
|
||
placeholder="请选择开始时间"
|
||
v-model:value="form.startTime"
|
||
style="width: 100%"
|
||
show-time
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item label="有效期结束时间" name="endTime">
|
||
<a-date-picker
|
||
placeholder="请选择结束时间"
|
||
v-model:value="form.endTime"
|
||
style="width: 100%"
|
||
show-time
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</div>
|
||
|
||
<!-- 适用范围 -->
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">适用范围</span>
|
||
</a-divider>
|
||
|
||
<a-form-item label="适用范围" name="applyRange">
|
||
<a-radio-group
|
||
v-model:value="form.applyRange"
|
||
@change="onApplyRangeChange"
|
||
>
|
||
<a-radio :value="10">
|
||
<a-tag color="cyan">全部商品</a-tag>
|
||
<span style="margin-left: 8px">适用于所有商品</span>
|
||
</a-radio>
|
||
<a-radio :value="20">
|
||
<a-tag color="geekblue">指定商品</a-tag>
|
||
<span style="margin-left: 8px">仅适用于指定商品</span>
|
||
</a-radio>
|
||
<a-radio :value="30">
|
||
<a-tag color="purple">指定分类</a-tag>
|
||
<span style="margin-left: 8px">适用于指定商品分类</span>
|
||
</a-radio>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
|
||
<div v-if="form.applyRange === 30" class="apply-range-config">
|
||
<a-alert
|
||
message="分类限制配置"
|
||
description="选择优惠券适用的商品分类"
|
||
type="info"
|
||
show-icon
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<a-form-item label="限制分类">
|
||
<a-cascader
|
||
v-model:value="applyCateListValue"
|
||
:options="goodsCateList"
|
||
multiple
|
||
placeholder="请选择商品分类"
|
||
:fieldNames="{ label: 'title', value: 'categoryId' }"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
</div>
|
||
|
||
<div v-if="form.applyRange === 20" class="apply-range-config">
|
||
<a-alert
|
||
message="商品限制配置"
|
||
description="选择优惠券适用的具体商品"
|
||
type="info"
|
||
show-icon
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<a-form-item label="限制商品">
|
||
<a-select
|
||
mode="multiple"
|
||
v-model:value="applyItemListValue"
|
||
placeholder="请选择商品"
|
||
style="width: 100%"
|
||
:filter-option="false"
|
||
:show-search="true"
|
||
@search="searchGoods"
|
||
>
|
||
<a-select-option
|
||
v-for="(item, index) in goodsList"
|
||
:key="index"
|
||
:value="item.goodsId"
|
||
>
|
||
{{ item.name }}
|
||
</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
</div>
|
||
|
||
<!-- 发放设置 -->
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">发放设置</span>
|
||
</a-divider>
|
||
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item label="发放总数量" name="totalCount">
|
||
<a-input-number
|
||
:min="-1"
|
||
placeholder="请输入发放总数量"
|
||
v-model:value="form.totalCount"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>张</template>
|
||
</a-input-number>
|
||
<div style="color: #999; font-size: 12px; margin-top: 4px">
|
||
-1 表示无限制
|
||
</div>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-form-item label="每人限领数量" name="limitPerUser">
|
||
<a-input-number
|
||
:min="-1"
|
||
placeholder="请输入每人限领数量"
|
||
v-model:value="form.limitPerUser"
|
||
style="width: 100%"
|
||
>
|
||
<template #addonAfter>张</template>
|
||
</a-input-number>
|
||
<div style="color: #999; font-size: 12px; margin-top: 4px">
|
||
-1 表示无限制
|
||
</div>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<!-- 发放对象设置 -->
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-form-item label="发放对象" name="receiveTarget">
|
||
<a-select
|
||
v-model:value="form.receiveTarget"
|
||
placeholder="请选择发放对象"
|
||
@change="onReceiveTargetChange"
|
||
style="width: 100%"
|
||
>
|
||
<a-select-option :value="0">全部用户</a-select-option>
|
||
<a-select-option :value="1">仅会员</a-select-option>
|
||
<a-select-option :value="2">仅非会员</a-select-option>
|
||
<a-select-option :value="3">指定用户</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<!-- 指定用户选择 -->
|
||
<a-form-item
|
||
v-if="form.receiveTarget === 3"
|
||
label="指定用户"
|
||
name="receiveUserIds"
|
||
>
|
||
<a-select
|
||
mode="multiple"
|
||
v-model:value="receiveUserIdsValue"
|
||
placeholder="输入手机号/昵称搜索用户"
|
||
style="width: 100%"
|
||
:filter-option="false"
|
||
:show-search="true"
|
||
@search="onUserSearch"
|
||
>
|
||
<a-select-option
|
||
v-for="user in userList"
|
||
:key="user.userId"
|
||
:value="user.userId"
|
||
>
|
||
{{ user.nickname }}({{ user.phone }})
|
||
</a-select-option>
|
||
</a-select>
|
||
<div style="color: #999; font-size: 12px; margin-top: 4px">
|
||
已选择 {{ receiveUserIdsValue.length }} 个用户
|
||
</div>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<!-- 状态设置 -->
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">状态设置</span>
|
||
</a-divider>
|
||
|
||
<a-row :gutter="16">
|
||
<a-col :span="8">
|
||
<a-form-item label="启用状态" name="enabled">
|
||
<a-switch
|
||
v-model:checked="form.enabled"
|
||
:checked-value="1"
|
||
:un-checked-value="0"
|
||
checked-children="启用"
|
||
un-checked-children="禁用"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item label="显示状态" name="status">
|
||
<a-switch
|
||
v-model:checked="form.status"
|
||
:checked-value="0"
|
||
:un-checked-value="1"
|
||
checked-children="显示"
|
||
un-checked-children="隐藏"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item label="排序" name="sortNumber">
|
||
<a-input-number
|
||
:min="0"
|
||
placeholder="数字越小越靠前"
|
||
v-model:value="form.sortNumber"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<!-- 优惠券预览 -->
|
||
<div class="coupon-preview" v-if="form.name && form.type">
|
||
<a-divider orientation="left">
|
||
<span style="color: #1890ff; font-weight: 600">优惠券预览</span>
|
||
</a-divider>
|
||
<div class="coupon-card">
|
||
<div class="coupon-header">
|
||
<div class="coupon-type">
|
||
<a-tag :color="getCouponTypeColor()">{{
|
||
getCouponTypeName()
|
||
}}</a-tag>
|
||
</div>
|
||
<div class="coupon-value">
|
||
{{ getCouponValueText() }}
|
||
</div>
|
||
</div>
|
||
<div class="coupon-body">
|
||
<div class="coupon-name">{{ form.name }}</div>
|
||
<div class="coupon-desc">{{ form.description || '暂无描述' }}</div>
|
||
<div class="coupon-condition">
|
||
满{{ form.minPrice || 0 }}元可用
|
||
</div>
|
||
</div>
|
||
<div class="coupon-footer">
|
||
<div class="coupon-expire">
|
||
{{ getExpireText() }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</a-form>
|
||
</ele-modal>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, reactive, watch } from 'vue';
|
||
import { Form, message } from 'ant-design-vue';
|
||
import { assignObject } from 'ele-admin-pro';
|
||
import { addShopCoupon, updateShopCoupon } from '@/api/shop/shopCoupon';
|
||
import { ShopCoupon } from '@/api/shop/shopCoupon/model';
|
||
import { FormInstance } from 'ant-design-vue/es/form';
|
||
import { ShopGoods } from '@/api/shop/shopGoods/model';
|
||
import { listShopGoods } from '@/api/shop/shopGoods';
|
||
import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model';
|
||
import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory';
|
||
import { ShopUser } from '@/api/shop/shopUser/model';
|
||
import { listShopUser } from '@/api/shop/shopUser';
|
||
import dayjs from 'dayjs';
|
||
|
||
// 是否是修改
|
||
const isUpdate = ref(false);
|
||
const useForm = Form.useForm;
|
||
|
||
const props = defineProps<{
|
||
// 弹窗是否打开
|
||
visible: boolean;
|
||
// 修改回显的数据
|
||
data?: ShopCoupon | null;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'done'): void;
|
||
(e: 'update:visible', visible: boolean): void;
|
||
}>();
|
||
|
||
// 提交状态
|
||
const loading = ref(false);
|
||
// 是否显示最大化切换按钮
|
||
const maxable = ref(true);
|
||
// 表格选中数据
|
||
const formRef = ref<FormInstance | null>(null);
|
||
|
||
// 表单数据
|
||
const form = reactive<ShopCoupon>({
|
||
id: undefined,
|
||
name: '',
|
||
description: '',
|
||
type: 10,
|
||
reducePrice: undefined,
|
||
discount: undefined,
|
||
minPrice: '0',
|
||
expireType: 10,
|
||
expireDay: 30,
|
||
startTime: undefined,
|
||
endTime: undefined,
|
||
applyRange: 10,
|
||
applyRangeConfig: '',
|
||
isExpire: 0,
|
||
sortNumber: 100,
|
||
status: 0,
|
||
deleted: 0,
|
||
userId: undefined,
|
||
tenantId: undefined,
|
||
createTime: undefined,
|
||
updateTime: undefined,
|
||
totalCount: -1,
|
||
issuedCount: 0,
|
||
limitPerUser: 1,
|
||
enabled: '1',
|
||
receiveTarget: 0,
|
||
receiveUserIds: undefined,
|
||
venueType: undefined,
|
||
venueId: undefined,
|
||
useCount: -1,
|
||
useDuration: undefined
|
||
});
|
||
|
||
// 商品分类列表
|
||
const goodsCateList = ref<ShopGoodsCategory[]>([]);
|
||
// 商品列表
|
||
const goodsList = ref<ShopGoods[]>([]);
|
||
// 适用分类值
|
||
const applyCateListValue = ref<number[]>([]);
|
||
// 适用商品值
|
||
const applyItemListValue = ref<number[]>([]);
|
||
// 指定用户列表(搜索结果)
|
||
const userList = ref<ShopUser[]>([]);
|
||
// 已选用户ID列表
|
||
const receiveUserIdsValue = ref<number[]>([]);
|
||
|
||
/* 更新visible */
|
||
const updateVisible = (value: boolean) => {
|
||
emit('update:visible', value);
|
||
};
|
||
|
||
// 表单验证规则
|
||
const rules = reactive({
|
||
name: [
|
||
{
|
||
required: true,
|
||
message: '请输入优惠券名称',
|
||
trigger: 'blur'
|
||
},
|
||
{
|
||
min: 2,
|
||
max: 50,
|
||
message: '优惠券名称长度应在2-50个字符之间',
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
type: [
|
||
{
|
||
required: true,
|
||
message: '请选择优惠券类型',
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
reducePrice: [
|
||
{
|
||
validator: (_rule: any, value: any) => {
|
||
if ((form.type === 10 || form.type === 40) && (!value || value <= 0)) {
|
||
return Promise.reject('减免金额必须大于0');
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
discount: [
|
||
{
|
||
validator: (_rule: any, value: any) => {
|
||
if (form.type === 20 && (!value || value <= 0 || value >= 100)) {
|
||
return Promise.reject('折扣率必须在0-100之间');
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
minPrice: [
|
||
{
|
||
validator: (_rule: any, value: any) => {
|
||
if (form.type !== 40 && form.type !== 50 && !value) {
|
||
return Promise.reject('请输入最低消费金额');
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
expireType: [
|
||
{
|
||
required: true,
|
||
message: '请选择到期类型',
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
expireDay: [
|
||
{
|
||
validator: (_rule: any, value: any) => {
|
||
if (form.expireType === 10 && (!value || value <= 0)) {
|
||
return Promise.reject('有效天数必须大于0');
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
startTime: [
|
||
{
|
||
validator: (_rule: any, value: any) => {
|
||
if (form.expireType === 20 && !value) {
|
||
return Promise.reject('请选择有效期开始时间');
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
endTime: [
|
||
{
|
||
validator: (_rule: any, value: any) => {
|
||
if (form.expireType === 20 && !value) {
|
||
return Promise.reject('请选择有效期结束时间');
|
||
}
|
||
if (
|
||
form.expireType === 20 &&
|
||
value &&
|
||
form.startTime &&
|
||
dayjs(value).isBefore(dayjs(form.startTime))
|
||
) {
|
||
return Promise.reject('结束时间不能早于开始时间');
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
trigger: 'change'
|
||
}
|
||
],
|
||
totalCount: [
|
||
{
|
||
required: true,
|
||
message: '请输入发放总数量',
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
limitPerUser: [
|
||
{
|
||
required: true,
|
||
message: '请输入每人限领数量',
|
||
trigger: 'blur'
|
||
}
|
||
]
|
||
});
|
||
|
||
/* 优惠券类型改变 */
|
||
const onTypeChange = (value: number) => {
|
||
// 清空相关字段
|
||
if (value !== 10 && value !== 40) {
|
||
form.reducePrice = undefined;
|
||
}
|
||
if (value !== 20) {
|
||
form.discount = undefined;
|
||
}
|
||
// 切换到无门槛券或场地使用券时清空 minPrice
|
||
if (value === 40 || value === 50) {
|
||
form.minPrice = '0';
|
||
}
|
||
};
|
||
|
||
/* 到期类型改变 */
|
||
const onExpireTypeChange = (e: any) => {
|
||
const value = e.target.value;
|
||
if (value === 10) {
|
||
form.startTime = undefined;
|
||
form.endTime = undefined;
|
||
form.expireDay = 30;
|
||
} else {
|
||
form.expireDay = undefined;
|
||
}
|
||
};
|
||
|
||
/* 适用范围改变 */
|
||
const onApplyRangeChange = (e: any) => {
|
||
applyCateListValue.value = [];
|
||
applyItemListValue.value = [];
|
||
form.applyRangeConfig = '';
|
||
};
|
||
|
||
/* 发放对象改变 */
|
||
const onReceiveTargetChange = (value: number) => {
|
||
if (value !== 3) {
|
||
receiveUserIdsValue.value = [];
|
||
userList.value = [];
|
||
form.receiveUserIds = undefined;
|
||
}
|
||
};
|
||
|
||
/* 搜索用户(防抖)*/
|
||
let userSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
||
const onUserSearch = (keyword: string) => {
|
||
if (userSearchTimer) clearTimeout(userSearchTimer);
|
||
if (!keyword || !keyword.trim()) return;
|
||
userSearchTimer = setTimeout(async () => {
|
||
try {
|
||
const res = await listShopUser({ keywords: keyword.trim() });
|
||
userList.value = res || [];
|
||
} catch (e) {
|
||
console.error('搜索用户失败:', e);
|
||
userList.value = [];
|
||
}
|
||
}, 300);
|
||
};
|
||
|
||
/* 搜索商品 */
|
||
const searchGoods = async (value: string) => {
|
||
if (value && value.trim()) {
|
||
try {
|
||
const res = await listShopGoods({ keywords: value.trim() });
|
||
goodsList.value = res || [];
|
||
console.log('搜索到的商品:', goodsList.value);
|
||
} catch (e) {
|
||
console.error('搜索商品失败:', e);
|
||
goodsList.value = [];
|
||
}
|
||
}
|
||
};
|
||
|
||
/* 获取优惠券类型颜色 */
|
||
const getCouponTypeColor = () => {
|
||
const colorMap: Record<number, string> = {
|
||
10: 'red',
|
||
20: 'orange',
|
||
30: 'green',
|
||
40: 'blue',
|
||
50: 'cyan'
|
||
};
|
||
return colorMap[form.type ?? 10] || 'blue';
|
||
};
|
||
|
||
/* 获取优惠券类型名称 */
|
||
const getCouponTypeName = () => {
|
||
const nameMap: Record<number, string> = {
|
||
10: '满减券',
|
||
20: '折扣券',
|
||
30: '免费券',
|
||
40: '无门槛券',
|
||
50: '场地使用券'
|
||
};
|
||
return nameMap[form.type ?? 10] || '优惠券';
|
||
};
|
||
|
||
/* 获取优惠券价值文本 */
|
||
const getCouponValueText = () => {
|
||
switch (form.type) {
|
||
case 10:
|
||
return `¥${form.reducePrice || 0}`;
|
||
case 20:
|
||
return `${form.discount || 0}折`;
|
||
case 30:
|
||
return '免费';
|
||
case 40:
|
||
return `¥${form.reducePrice || 0} 无门槛`;
|
||
case 50: {
|
||
const count = form.useCount === -1 ? '无限' : (form.useCount || 0) + '次';
|
||
return form.useDuration ? `${count}/${form.useDuration}分钟` : count;
|
||
}
|
||
default:
|
||
return '优惠';
|
||
}
|
||
};
|
||
|
||
/* 获取有效期文本 */
|
||
const getExpireText = () => {
|
||
if (form.expireType === 10) {
|
||
return `领取后${form.expireDay || 0}天内有效`;
|
||
} else {
|
||
const start = form.startTime
|
||
? dayjs(form.startTime).format('YYYY.MM.DD')
|
||
: '';
|
||
const end = form.endTime ? dayjs(form.endTime).format('YYYY.MM.DD') : '';
|
||
return start && end ? `${start} - ${end}` : '请设置有效期';
|
||
}
|
||
};
|
||
|
||
const { resetFields } = useForm(form, rules);
|
||
|
||
/* 保存编辑 */
|
||
const save = () => {
|
||
if (!formRef.value) {
|
||
return;
|
||
}
|
||
formRef.value
|
||
.validate()
|
||
.then(() => {
|
||
loading.value = true;
|
||
const formData = {
|
||
...form
|
||
};
|
||
|
||
// 处理时间字段转换
|
||
if (formData.startTime && dayjs.isDayjs(formData.startTime)) {
|
||
formData.startTime = formData.startTime.format('YYYY-MM-DD HH:mm:ss');
|
||
}
|
||
if (formData.endTime && dayjs.isDayjs(formData.endTime)) {
|
||
formData.endTime = formData.endTime.format('YYYY-MM-DD HH:mm:ss');
|
||
}
|
||
|
||
// 处理适用范围配置
|
||
if (form.applyRange === 20 && applyItemListValue.value.length) {
|
||
formData.couponApplyItemList = applyItemListValue.value.map((pk) => ({
|
||
pk,
|
||
type: 0
|
||
}));
|
||
} else {
|
||
formData.couponApplyItemList = [];
|
||
}
|
||
|
||
if (form.applyRange === 30 && applyCateListValue.value.length) {
|
||
formData.couponApplyCateList = applyCateListValue.value.map(
|
||
(cateId) => ({
|
||
cateId,
|
||
cateLevel: 0
|
||
})
|
||
);
|
||
} else {
|
||
formData.couponApplyCateList = [];
|
||
}
|
||
|
||
// 处理发放对象(指定用户时序列化为JSON数组)
|
||
if (form.receiveTarget === 3 && receiveUserIdsValue.value.length > 0) {
|
||
formData.receiveUserIds = JSON.stringify(receiveUserIdsValue.value);
|
||
} else {
|
||
formData.receiveUserIds = undefined;
|
||
}
|
||
|
||
// 无门槛券强制 minPrice 为 0
|
||
if (form.type === 40) {
|
||
formData.minPrice = '0';
|
||
}
|
||
// 场地使用券不需要 minPrice
|
||
if (form.type === 50) {
|
||
formData.minPrice = '0';
|
||
}
|
||
|
||
const saveOrUpdate = isUpdate.value ? updateShopCoupon : addShopCoupon;
|
||
saveOrUpdate(formData)
|
||
.then((msg) => {
|
||
loading.value = false;
|
||
message.success(msg);
|
||
updateVisible(false);
|
||
emit('done');
|
||
})
|
||
.catch((e) => {
|
||
loading.value = false;
|
||
message.error(e.message);
|
||
});
|
||
})
|
||
.catch(() => {});
|
||
};
|
||
|
||
/* 获取商品列表 */
|
||
const getGoodsList = async () => {
|
||
try {
|
||
const res = await listShopGoods({});
|
||
goodsList.value = res || [];
|
||
console.log('获取到的商品列表:', goodsList.value);
|
||
} catch (e) {
|
||
console.error('获取商品列表失败:', e);
|
||
goodsList.value = [];
|
||
}
|
||
};
|
||
|
||
/* 获取商品分类列表 */
|
||
const getGoodsCateList = async () => {
|
||
try {
|
||
const res = await listShopGoodsCategory();
|
||
goodsCateList.value = res || [];
|
||
console.log('获取到的商品分类列表:', goodsCateList.value);
|
||
} catch (e) {
|
||
console.error('获取商品分类列表失败:', e);
|
||
goodsCateList.value = [];
|
||
}
|
||
};
|
||
|
||
watch(
|
||
() => props.visible,
|
||
async (visible) => {
|
||
if (visible) {
|
||
await getGoodsList();
|
||
await getGoodsCateList();
|
||
|
||
if (props.data) {
|
||
assignObject(form, props.data);
|
||
|
||
// 处理时间字段转换
|
||
if (props.data.startTime) {
|
||
form.startTime = dayjs(props.data.startTime);
|
||
}
|
||
if (props.data.endTime) {
|
||
form.endTime = dayjs(props.data.endTime);
|
||
}
|
||
|
||
// 处理适用范围数据
|
||
if (
|
||
props.data.couponApplyCateList &&
|
||
props.data.couponApplyCateList.length > 0
|
||
) {
|
||
applyCateListValue.value = props.data.couponApplyCateList
|
||
.map((item) => item.cateId)
|
||
.filter((v): v is number => v != null);
|
||
}
|
||
if (
|
||
props.data.couponApplyItemList &&
|
||
props.data.couponApplyItemList.length > 0
|
||
) {
|
||
applyItemListValue.value = props.data.couponApplyItemList
|
||
.map((item) => item.pk)
|
||
.filter((v): v is number => v != null);
|
||
}
|
||
|
||
// 处理发放对象-指定用户回显
|
||
if (props.data.receiveTarget === 3 && props.data.receiveUserIds) {
|
||
try {
|
||
receiveUserIdsValue.value = JSON.parse(
|
||
props.data.receiveUserIds
|
||
) as number[];
|
||
} catch (e) {
|
||
receiveUserIdsValue.value = [];
|
||
}
|
||
} else {
|
||
receiveUserIdsValue.value = [];
|
||
}
|
||
|
||
isUpdate.value = true;
|
||
} else {
|
||
// 重置为默认值
|
||
Object.assign(form, {
|
||
id: undefined,
|
||
name: '',
|
||
description: '',
|
||
type: 10,
|
||
reducePrice: undefined,
|
||
discount: undefined,
|
||
minPrice: '0',
|
||
expireType: 10,
|
||
expireDay: 30,
|
||
startTime: undefined,
|
||
endTime: undefined,
|
||
applyRange: 10,
|
||
applyRangeConfig: '',
|
||
isExpire: 0,
|
||
sortNumber: 100,
|
||
status: 0,
|
||
deleted: 0,
|
||
userId: undefined,
|
||
tenantId: undefined,
|
||
createTime: undefined,
|
||
updateTime: undefined,
|
||
totalCount: -1,
|
||
issuedCount: 0,
|
||
limitPerUser: 1,
|
||
enabled: '1',
|
||
receiveTarget: 0,
|
||
receiveUserIds: undefined,
|
||
venueType: undefined,
|
||
venueId: undefined,
|
||
useCount: -1,
|
||
useDuration: undefined
|
||
});
|
||
|
||
isUpdate.value = false;
|
||
}
|
||
} else {
|
||
applyCateListValue.value = [];
|
||
applyItemListValue.value = [];
|
||
receiveUserIdsValue.value = [];
|
||
userList.value = [];
|
||
resetFields();
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.coupon-type-option {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.ant-tag {
|
||
margin-right: 8px;
|
||
}
|
||
|
||
span {
|
||
color: #666;
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
.fixed-time-config,
|
||
.apply-range-config,
|
||
.venue-config {
|
||
background: #fafafa;
|
||
padding: 16px;
|
||
border-radius: 6px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.coupon-preview {
|
||
margin-top: 24px;
|
||
|
||
.coupon-card {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
color: white;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 100px;
|
||
height: 100px;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 50%;
|
||
transform: translate(30px, -30px);
|
||
}
|
||
|
||
.coupon-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
|
||
.coupon-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.coupon-body {
|
||
.coupon-name {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.coupon-desc {
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.coupon-condition {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
.coupon-footer {
|
||
margin-top: 16px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||
|
||
.coupon-expire {
|
||
font-size: 12px;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||
margin: 24px 0 16px 0;
|
||
|
||
.ant-divider-inner-text {
|
||
padding: 0 16px 0 0;
|
||
}
|
||
}
|
||
|
||
:deep(.ant-form-item) {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
:deep(.ant-radio) {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
|
||
.ant-radio-inner {
|
||
margin-right: 8px;
|
||
}
|
||
}
|
||
|
||
:deep(.ant-select-selection-item) {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
:deep(.ant-input-number) {
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.ant-alert) {
|
||
.ant-alert-message {
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
</style>
|