add: 优化优惠券问题

This commit is contained in:
gyq
2025-09-18 18:14:36 +08:00
parent 350a314feb
commit 9eb3b8e306
17 changed files with 1349 additions and 620 deletions

View File

@@ -14,7 +14,7 @@
label-width="160px"
class="dialog-form"
>
<el-form-item label="优惠券名称" prop="title">
<el-form-item :label="titleOptions.name" prop="title">
<el-input
v-model="form.title"
:maxlength="20"
@@ -30,6 +30,7 @@
placeholder="请输入使用门槛"
style="width: 240px"
input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.fullAmount = filterNumberInput(e))"
>
<template #prepend></template>
@@ -40,6 +41,7 @@
placeholder="请输入满减金额"
style="width: 240px"
input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.discountAmount = filterNumberInput(e))"
>
<template #prepend></template>
@@ -56,6 +58,7 @@
placeholder="请输入使用门槛"
style="width: 200px"
input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.fullAmount = filterNumberInput(e))"
>
<template #prepend></template>
@@ -63,10 +66,10 @@
</el-input>
</div>
</el-form-item>
<el-form-item label="指定门槛商品">
<el-form-item label="可用商品">
<el-radio-group v-model="goodsType">
<el-radio label="全部商品参与计算门槛" :value="1"></el-radio>
<el-radio label="部分商品参与计算门槛" :value="2"></el-radio>
<el-radio label="全部商品可用" :value="1"></el-radio>
<el-radio label="部分商品可用" :value="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="goodsType" v-if="goodsType == 2">
@@ -78,7 +81,7 @@
:max-collapse-tags="3"
collapse-tags
clearable
style="width: 100%"
style="width: 300px"
@change="selectFoodsConfirm"
></el-cascader>
</el-form-item>
@@ -113,6 +116,7 @@
placeholder="请输入使用门槛"
style="width: 200px"
input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.discountAmount = filterNumberInput(e))"
>
<template #prepend></template>
@@ -126,6 +130,7 @@
v-model="form.maxDiscountAmount"
placeholder="请输入金额"
style="width: 200px"
:maxlength="8"
@input="(e) => (form.maxDiscountAmount = filterNumberInput(e))"
>
<template #append>可用</template>
@@ -133,7 +138,33 @@
</div>
</el-form-item>
</div>
<div v-if="form.couponType == 4"></div>
<div v-if="form.couponType == 4 || form.couponType == 6">
<el-form-item label="可用商品">
<el-radio-group v-model="goodsType">
<el-radio label="全部商品可用" :value="1"></el-radio>
<el-radio label="部分商品可用" :value="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="goodsType" v-if="goodsType == 2">
<el-cascader
v-model="goodsTypeCascaderValue"
:options="goodsList"
:props="cascaderProps"
:show-all-levels="false"
:max-collapse-tags="3"
collapse-tags
clearable
style="width: 300px"
@change="selectFoodsConfirm"
></el-cascader>
</el-form-item>
<el-form-item label="使用规则">
<el-radio-group v-model="form.useRule">
<el-radio label="从最低价开始抵扣" value="price_asc"></el-radio>
<el-radio label="从最高价开始抵扣" value="price_desc"></el-radio>
</el-radio-group>
</el-form-item>
</div>
<div v-if="form.couponType == 6"></div>
<div class="title">指定设置</div>
<el-form-item label="选择门店" prop="useShopType" v-if="shopInfo.isHeadShop">
@@ -150,6 +181,7 @@
clearable
placeholder="请选择门店"
@change="shopsChange"
style="width: 300px"
>
<el-option
:label="item.shopName"
@@ -159,11 +191,11 @@
></el-option>
</el-select>
</el-form-item>
<div v-if="form.couponType != 2">
<div v-if="form.couponType != 2 && form.couponType != 4 && form.couponType != 6">
<el-form-item label="指定门槛商品" prop="goodsType">
<el-radio-group v-model="goodsType">
<el-radio label="全部商品参与计算门槛" :value="1"></el-radio>
<el-radio label="部分商品参与计算门槛" :value="2"></el-radio>
<el-radio label="全部商品可用" :value="1"></el-radio>
<el-radio label="部分商品可用" :value="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="goodsType == 2">
@@ -175,7 +207,7 @@
:max-collapse-tags="3"
collapse-tags
clearable
style="width: 100%"
style="width: 300px"
@change="selectFoodsConfirm"
></el-cascader>
</el-form-item>
@@ -200,6 +232,7 @@
v-model="form.validDays"
placeholder="请输入有效期"
style="width: 200px"
:maxlength="5"
v-if="form.validType == 'fixed'"
input-style="text-align: center;"
@input="validDaysInput"
@@ -229,6 +262,7 @@
placeholder="请输入隔天生效日期"
style="width: 300px"
input-style="text-align: center;"
:maxlength="5"
@input="daysToTakeEffectInput"
>
<template #prepend></template>
@@ -301,6 +335,7 @@
placeholder="请输入总发放数量"
style="width: 200px"
input-style="text-align: center;"
:maxlength="5"
@input="giveNumInput"
>
<template #append></template>
@@ -322,6 +357,7 @@
placeholder="请输入每人限领量"
style="width: 200px"
input-style="text-align: center;"
:maxlength="5"
@input="getLimitInput"
>
<template #append></template>
@@ -346,6 +382,7 @@
placeholder="需小于或等于每人限领量"
style="width: 255px"
input-style="text-align: center;"
:maxlength="5"
@input="useLimitInput"
>
<template #append>/每人1天</template>
@@ -420,12 +457,14 @@
import _ from "lodash";
import { dayjs } from "element-plus";
import { ref, reactive, onMounted } from "vue";
import { filterNumberInput } from "@/utils";
import { getBranchPage, getProductList, getCategoryList, addCoupon } from "@/api/coupon/index.js";
const shopInfo = ref("");
const dialogVisible = ref(false);
const titleOptions = reactive({
name: "优惠券名称",
title: "添加优惠券",
couponTypeList: [
{
@@ -566,7 +605,7 @@ const form = ref({
discountRate: "", // 折扣%
maxDiscountAmount: "", // 可抵扣最大金额 元
useRule: "price_asc", // 使用规则price_asc-价格低到高price_desc-高到低
discountNum: "", // 抵扣数量
discountNum: 1, // 抵扣数量
otherCouponShare: 1, // 与其它优惠共享0-否1-是
});
@@ -585,7 +624,7 @@ function reset() {
// 自定义校验使用门槛
const fullAmountValidate = (rule, value, callback) => {
if (form.value.fullAmount <= 0) {
if (form.value.fullAmount < 0) {
callback(new Error("请输入使用门槛"));
} else if (form.value.discountAmount <= 0) {
callback(new Error("请输入满减金额"));
@@ -594,7 +633,7 @@ const fullAmountValidate = (rule, value, callback) => {
}
};
const fullAmountValidate2 = (rule, value, callback) => {
if (form.value.fullAmount <= 0) {
if (form.value.fullAmount < 0 || form.value.fullAmount == "") {
callback(new Error("请输入使用门槛"));
} else {
callback();
@@ -812,6 +851,7 @@ function show(t, obj = null) {
}
});
let m = titleOptions.couponTypeList.find((item) => item.value == t);
titleOptions.name = `${m.label}名称`;
if (obj && obj.id) {
titleOptions.title = `编辑${m.label}`;
@@ -919,6 +959,9 @@ function convertTimeToDate(timeStr, options = {}) {
const time = 500;
const discountNumInput = _.debounce(function (value) {
form.value.discountNum = filterNumberInput(value, true);
if (form.value.discountNum == "") {
form.value.discountNum = 1;
}
}, time);
const discountRateInput = _.debounce(function (value) {
@@ -957,51 +1000,6 @@ const useLimitInput = _.debounce(function (value) {
}
}, time);
/**
* 过滤输入,只允许数字和最多两位小数
* @param {string} value - 输入框当前值
* @param {boolean} isIntegerOnly - 是否只允许正整数无小数点开启时最小值为1
* @returns {string} 过滤后的合法值
*/
function filterNumberInput(value, isIntegerOnly = false) {
// 第一步就过滤所有非数字和非小数点的字符(包括字母)
let filtered = value.replace(/[^\d.]/g, "");
// 整数模式处理
if (isIntegerOnly) {
// 移除所有小数点
filtered = filtered.replace(/\./g, "");
// 处理前导零
filtered = filtered.replace(/^0+(\d)/, "$1") || filtered;
// 空值处理(允许临时删除)
if (filtered === "") {
return "";
}
// 最小值限制
if (filtered === "0" || parseInt(filtered, 10) < 1) {
return "1";
}
return filtered;
}
// 小数模式处理
const parts = filtered.split(".");
if (parts.length > 1) {
filtered = parts[0] + "." + (parts[1].substring(0, 2) || "");
}
// 处理前导零
if (filtered.startsWith("0") && filtered.length > 1 && !filtered.startsWith("0.")) {
filtered = filtered.replace(/^0+(\d)/, "$1");
}
return filtered;
}
defineExpose({
show,
});

View File

@@ -1,100 +1,105 @@
<template>
<div class="flex justify-between box">
<div class="item">
<img :src="getIconPath(props.icon)" class="icon" />
<div class="info">
<div class="name">{{ props.name }}</div>
<div class="intro">
{{ props.intro }}
</div>
</div>
</div>
<div>
<el-switch v-model="isOpen" v-if="props.showSwitch" />
</div>
</div>
<div class="flex justify-between box">
<div class="item">
<img :src="getIconPath(props.icon)" class="icon" />
<div class="info">
<div class="name">{{ props.name }}</div>
<div class="intro">
{{ props.intro }}
</div>
</div>
</div>
<div>
<el-switch
:active-value="1"
:inactive-value="0"
v-model="isOpen"
v-if="props.showSwitch"
/>
</div>
</div>
</template>
<script setup>
import defaultIcon from "@/assets/logo.png";
const props = defineProps({
icon: {
type: String,
defautl: "",
},
name: {
type: String,
default: "",
},
intro: {
type: String,
default: "",
},
showSwitch: {
type: Boolean,
default: false,
},
icon: {
type: String,
defautl: "",
},
name: {
type: String,
default: "",
},
intro: {
type: String,
default: "",
},
showSwitch: {
type: Boolean,
default: false,
},
});
const isOpen = defineModel("isOpen", {
type: Boolean,
default: false,
type: [Boolean, String, Number],
default: false,
});
// 动态获取PNG图标路径
const getIconPath = (iconName) => {
try {
// 直接导入对应PNG文件
return new URL(`/src/assets/applocation/${iconName}.png`, import.meta.url).href;
} catch (error) {
console.warn(`图标 ${iconName}.png 不存在`);
return defaultIcon; // 图标不存在时使用默认图标
}
try {
// 直接导入对应PNG文件
return new URL(`/src/assets/applocation/${iconName}.png`, import.meta.url).href;
} catch (error) {
console.warn(`图标 ${iconName}.png 不存在`);
return defaultIcon; // 图标不存在时使用默认图标
}
};
</script>
<style scoped lang="scss">
.box {
padding: 10px;
border-radius: 4px;
background-color: #f8f8f8;
transition: all 0.1s ease-in-out;
padding: 10px;
border-radius: 4px;
background-color: #f8f8f8;
transition: all 0.1s ease-in-out;
}
.item {
display: flex;
align-items: center;
display: flex;
align-items: center;
// &:hover {
// cursor: pointer;
// background-color: #d5ebff;
// }
// &:hover {
// cursor: pointer;
// background-color: #d5ebff;
// }
.icon {
width: 48px;
height: 48px;
margin-right: 10px;
}
.icon {
width: 48px;
height: 48px;
margin-right: 10px;
}
.info {
.name {
font-size: 14px;
}
.info {
.name {
font-size: 14px;
}
.intro {
margin-top: 4px;
font-size: 14px;
color: #666;
line-height: 1.4em;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
white-space: normal;
}
}
.intro {
margin-top: 4px;
font-size: 14px;
color: #666;
line-height: 1.4em;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
white-space: normal;
}
}
}
</style>
</style>

View File

@@ -123,7 +123,7 @@ async function relevanceCouponAjax() {
try {
tableData.loading = true;
const res = await relevanceCoupon({
couponId: row.value.syncId ? row.value.syncId : row.value.id,
couponId: row.value.id,
type: tableData.type,
page: tableData.page,
size: tableData.pageSize,