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

@ -126,4 +126,20 @@ export function deleteRecord(params) {
method: 'DELETE', method: 'DELETE',
params params
}); });
}
// 智慧充值 配置信息修改
export function shopRecharge(data) {
return request({
url: `${Market_BaseUrl + "/admin/shopRecharge"}`,
method: 'post',
data
});
}
// 智慧充值 配置信息获取
export function shopRechargeGet() {
return request({
url: `${Market_BaseUrl + "/admin/shopRecharge"}`,
method: 'get'
});
} }

View File

@ -4,8 +4,8 @@
* @param {string} cls * @param {string} cls
* @returns {boolean} * @returns {boolean}
*/ */
export function hasClass(ele: HTMLElement, cls: string) { export function hasClass(ele : HTMLElement, cls : string) {
return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)")); return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)"));
} }
/** /**
@ -13,8 +13,8 @@ export function hasClass(ele: HTMLElement, cls: string) {
* @param {HTMLElement} ele * @param {HTMLElement} ele
* @param {string} cls * @param {string} cls
*/ */
export function addClass(ele: HTMLElement, cls: string) { export function addClass(ele : HTMLElement, cls : string) {
if (!hasClass(ele, cls)) ele.className += " " + cls; if (!hasClass(ele, cls)) ele.className += " " + cls;
} }
/** /**
@ -22,11 +22,11 @@ export function addClass(ele: HTMLElement, cls: string) {
* @param {HTMLElement} ele * @param {HTMLElement} ele
* @param {string} cls * @param {string} cls
*/ */
export function removeClass(ele: HTMLElement, cls: string) { export function removeClass(ele : HTMLElement, cls : string) {
if (hasClass(ele, cls)) { if (hasClass(ele, cls)) {
const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
ele.className = ele.className.replace(reg, " "); ele.className = ele.className.replace(reg, " ");
} }
} }
/** /**
@ -35,9 +35,9 @@ export function removeClass(ele: HTMLElement, cls: string) {
* @param {string} path * @param {string} path
* @returns {Boolean} * @returns {Boolean}
*/ */
export function isExternal(path: string) { export function isExternal(path : string) {
const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path); const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
return isExternal; return isExternal;
} }
/** /**
@ -46,15 +46,15 @@ export function isExternal(path: string) {
* @param growthRate * @param growthRate
* @returns * @returns
*/ */
export function formatGrowthRate(growthRate: number) { export function formatGrowthRate(growthRate : number) {
if (growthRate === 0) { if (growthRate === 0) {
return "-"; return "-";
} }
const formattedRate = Math.abs(growthRate * 100) const formattedRate = Math.abs(growthRate * 100)
.toFixed(2) .toFixed(2)
.replace(/\.?0+$/, ""); .replace(/\.?0+$/, "");
return formattedRate + "%"; return formattedRate + "%";
} }
/** /**
* Parse the time to string * Parse the time to string
@ -62,83 +62,174 @@ export function formatGrowthRate(growthRate: number) {
* @param {string} cFormat * @param {string} cFormat
* @returns {string} * @returns {string}
*/ */
export function parseTime(time: string | number | Date | null, cFormat: string | undefined) { export function parseTime(time : string | number | Date | null, cFormat : string | undefined) {
if (arguments.length === 0) { if (arguments.length === 0) {
return null; return null;
} }
const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}"; const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}";
let date; let date;
if (typeof time === "undefined" || time === null || time === "null") { if (typeof time === "undefined" || time === null || time === "null") {
return ""; return "";
} else if (typeof time === "object") { } else if (typeof time === "object") {
date = time; date = time;
} else { } else {
if (typeof time === "string" && /^[0-9]+$/.test(time)) { if (typeof time === "string" && /^[0-9]+$/.test(time)) {
time = parseInt(time); time = parseInt(time);
} }
if (typeof time === "number" && time.toString().length === 10) { if (typeof time === "number" && time.toString().length === 10) {
time = time * 1000; time = time * 1000;
} }
date = new Date(time); date = new Date(time);
} }
const formatObj = { const formatObj = {
y: date.getFullYear(), y: date.getFullYear(),
m: date.getMonth() + 1, m: date.getMonth() + 1,
d: date.getDate(), d: date.getDate(),
h: date.getHours(), h: date.getHours(),
i: date.getMinutes(), i: date.getMinutes(),
s: date.getSeconds(), s: date.getSeconds(),
a: date.getDay() a: date.getDay()
}; };
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key: keyof typeof formatObj): string => { const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key : keyof typeof formatObj) : string => {
let value = formatObj[key]; let value = formatObj[key];
// Note: getDay() returns 0 on Sunday // Note: getDay() returns 0 on Sunday
if (key === "a") { if (key === "a") {
return ["日", "一", "二", "三", "四", "五", "六"][value]; return ["日", "一", "二", "三", "四", "五", "六"][value];
} }
if (result.length > 0 && value < 10) { if (result.length > 0 && value < 10) {
value = Number("0" + value.toString()); value = Number("0" + value.toString());
} }
return value.toString() || "0"; return value.toString() || "0";
}); });
return time_str; return time_str;
} }
// 下载文件 // 下载文件
export function downloadFile(obj: BlobPart, name: string, suffix: string, useUnix = true) { export function downloadFile(obj : BlobPart, name : string, suffix : string, useUnix = true) {
const url = window.URL.createObjectURL(new Blob([obj])); const url = window.URL.createObjectURL(new Blob([obj]));
const link = document.createElement("a"); const link = document.createElement("a");
link.style.display = "none"; link.style.display = "none";
link.href = url; link.href = url;
const newFilename = useUnix ? (parseTime(new Date(), undefined) + "-") : '' + name.trim() const newFilename = useUnix ? (parseTime(new Date(), undefined) + "-") : '' + name.trim()
const fileName = newFilename + "." + suffix; const fileName = newFilename + "." + suffix;
link.setAttribute("download", fileName); link.setAttribute("download", fileName);
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
} }
/** /**
* *
*/ */
export function isSyncStatus() { export function isSyncStatus() {
let userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || '{}')) let userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || '{}'))
if (userInfo.value.isHeadShop == 0 && userInfo.value.isEnableProdSync == 1 && userInfo.value.isEnableVipSync == 1 && userInfo.value.isEnableConsSync == 1) { if (userInfo.value.isHeadShop == 0 && userInfo.value.isEnableProdSync == 1 && userInfo.value.isEnableVipSync == 1 && userInfo.value.isEnableConsSync == 1) {
return true return true
}else { } else {
return false return false
} }
} }
/** /**
* *
*/ */
export function hasPermission(params: any) { export function hasPermission(params : any) {
let $PermissionObj = JSON.parse(localStorage.getItem("permission") || '[]' ) let $PermissionObj = JSON.parse(localStorage.getItem("permission") || '[]')
const obj = $PermissionObj.find((v: any) => v == params || v == params) const obj = $PermissionObj.find((v : any) => v == params || v == params)
if (obj) { if (obj) {
return obj return obj
} }
return false return false
} }
/**
*
* @param {string} value -
* @param {boolean} isIntegerOnly - 1
* @returns {string}
*/
export 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;
}
/**
*
* @param {string} timeStr - HH:mm:ss '00:53:00'
* @param {Object} options -
* @param {string} [options.customDate] - YYYY-MM-DD使
* @param {string} [options.format='YYYY-MM-DD HH:mm:ss'] -
* @returns {string|null} null
*/
export function convertTimeToDate(timeStr, options = {}) {
// 解构配置项,设置默认值
const { customDate, format = "YYYY-MM-DD HH:mm:ss" } = options;
// 1. 校验时分秒格式(必须为 HH:mm:ss允许数字1-2位
const timeRegex = /^\d{1,2}:\d{1,2}:\d{1,2}$/;
if (!timeRegex.test(timeStr)) {
console.error("时分秒格式错误,请使用 HH:mm:ss 格式(如 00:53:00");
return null;
}
// 2. 确定日期部分(自定义日期或当前日期)
let datePart;
if (customDate) {
// 校验自定义日期格式
if (!dayjs(customDate, "YYYY-MM-DD", true).isValid()) {
console.error("自定义日期格式错误,请使用 YYYY-MM-DD 格式(如 2024-05-20");
return null;
}
datePart = customDate;
} else {
// 使用当前日期格式YYYY-MM-DD
datePart = dayjs().format("YYYY-MM-DD");
}
// 3. 组合日期和时分秒,生成完整日期对象
const fullDateTime = `${datePart} ${timeStr}`;
const dateObj = dayjs(fullDateTime);
// 4. 校验完整日期是否有效(如避免 2024-02-30 这种无效日期)
if (!dateObj.isValid()) {
console.error("生成的日期无效,请检查日期或时分秒是否合理");
return null;
}
// 5. 按指定格式返回日期字符串
return dateObj.format(format);
}

View File

@ -38,7 +38,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="giveNum" label="总发放数量" width="100"> <el-table-column prop="giveNum" label="总发放数量" width="100">
<template #default="scope"> <template #default="scope">
{{ scope.row.giveNum == -10086 ? "无限" : scope.row.giftNum }} {{ scope.row.giveNum == -10086 ? "无限" : scope.row.giveNum }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="giftNum" label="已领取" width="180"> <el-table-column prop="giftNum" label="已领取" width="180">

View File

@ -14,7 +14,7 @@
label-width="160px" label-width="160px"
class="dialog-form" class="dialog-form"
> >
<el-form-item label="优惠券名称" prop="title"> <el-form-item :label="titleOptions.name" prop="title">
<el-input <el-input
v-model="form.title" v-model="form.title"
:maxlength="20" :maxlength="20"
@ -30,6 +30,7 @@
placeholder="请输入使用门槛" placeholder="请输入使用门槛"
style="width: 240px" style="width: 240px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.fullAmount = filterNumberInput(e))" @input="(e) => (form.fullAmount = filterNumberInput(e))"
> >
<template #prepend></template> <template #prepend></template>
@ -40,6 +41,7 @@
placeholder="请输入满减金额" placeholder="请输入满减金额"
style="width: 240px" style="width: 240px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.discountAmount = filterNumberInput(e))" @input="(e) => (form.discountAmount = filterNumberInput(e))"
> >
<template #prepend></template> <template #prepend></template>
@ -56,6 +58,7 @@
placeholder="请输入使用门槛" placeholder="请输入使用门槛"
style="width: 200px" style="width: 200px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.fullAmount = filterNumberInput(e))" @input="(e) => (form.fullAmount = filterNumberInput(e))"
> >
<template #prepend></template> <template #prepend></template>
@ -63,10 +66,10 @@
</el-input> </el-input>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="指定门槛商品"> <el-form-item label="可用商品">
<el-radio-group v-model="goodsType"> <el-radio-group v-model="goodsType">
<el-radio label="全部商品参与计算门槛" :value="1"></el-radio> <el-radio label="全部商品可用" :value="1"></el-radio>
<el-radio label="部分商品参与计算门槛" :value="2"></el-radio> <el-radio label="部分商品可用" :value="2"></el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item prop="goodsType" v-if="goodsType == 2"> <el-form-item prop="goodsType" v-if="goodsType == 2">
@ -78,7 +81,7 @@
:max-collapse-tags="3" :max-collapse-tags="3"
collapse-tags collapse-tags
clearable clearable
style="width: 100%" style="width: 300px"
@change="selectFoodsConfirm" @change="selectFoodsConfirm"
></el-cascader> ></el-cascader>
</el-form-item> </el-form-item>
@ -113,6 +116,7 @@
placeholder="请输入使用门槛" placeholder="请输入使用门槛"
style="width: 200px" style="width: 200px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="8"
@input="(e) => (form.discountAmount = filterNumberInput(e))" @input="(e) => (form.discountAmount = filterNumberInput(e))"
> >
<template #prepend></template> <template #prepend></template>
@ -126,6 +130,7 @@
v-model="form.maxDiscountAmount" v-model="form.maxDiscountAmount"
placeholder="请输入金额" placeholder="请输入金额"
style="width: 200px" style="width: 200px"
:maxlength="8"
@input="(e) => (form.maxDiscountAmount = filterNumberInput(e))" @input="(e) => (form.maxDiscountAmount = filterNumberInput(e))"
> >
<template #append>可用</template> <template #append>可用</template>
@ -133,7 +138,33 @@
</div> </div>
</el-form-item> </el-form-item>
</div> </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 v-if="form.couponType == 6"></div>
<div class="title">指定设置</div> <div class="title">指定设置</div>
<el-form-item label="选择门店" prop="useShopType" v-if="shopInfo.isHeadShop"> <el-form-item label="选择门店" prop="useShopType" v-if="shopInfo.isHeadShop">
@ -150,6 +181,7 @@
clearable clearable
placeholder="请选择门店" placeholder="请选择门店"
@change="shopsChange" @change="shopsChange"
style="width: 300px"
> >
<el-option <el-option
:label="item.shopName" :label="item.shopName"
@ -159,11 +191,11 @@
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </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-form-item label="指定门槛商品" prop="goodsType">
<el-radio-group v-model="goodsType"> <el-radio-group v-model="goodsType">
<el-radio label="全部商品参与计算门槛" :value="1"></el-radio> <el-radio label="全部商品可用" :value="1"></el-radio>
<el-radio label="部分商品参与计算门槛" :value="2"></el-radio> <el-radio label="部分商品可用" :value="2"></el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="goodsType == 2"> <el-form-item v-if="goodsType == 2">
@ -175,7 +207,7 @@
:max-collapse-tags="3" :max-collapse-tags="3"
collapse-tags collapse-tags
clearable clearable
style="width: 100%" style="width: 300px"
@change="selectFoodsConfirm" @change="selectFoodsConfirm"
></el-cascader> ></el-cascader>
</el-form-item> </el-form-item>
@ -200,6 +232,7 @@
v-model="form.validDays" v-model="form.validDays"
placeholder="请输入有效期" placeholder="请输入有效期"
style="width: 200px" style="width: 200px"
:maxlength="5"
v-if="form.validType == 'fixed'" v-if="form.validType == 'fixed'"
input-style="text-align: center;" input-style="text-align: center;"
@input="validDaysInput" @input="validDaysInput"
@ -229,6 +262,7 @@
placeholder="请输入隔天生效日期" placeholder="请输入隔天生效日期"
style="width: 300px" style="width: 300px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="5"
@input="daysToTakeEffectInput" @input="daysToTakeEffectInput"
> >
<template #prepend></template> <template #prepend></template>
@ -301,6 +335,7 @@
placeholder="请输入总发放数量" placeholder="请输入总发放数量"
style="width: 200px" style="width: 200px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="5"
@input="giveNumInput" @input="giveNumInput"
> >
<template #append></template> <template #append></template>
@ -322,6 +357,7 @@
placeholder="请输入每人限领量" placeholder="请输入每人限领量"
style="width: 200px" style="width: 200px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="5"
@input="getLimitInput" @input="getLimitInput"
> >
<template #append></template> <template #append></template>
@ -346,6 +382,7 @@
placeholder="需小于或等于每人限领量" placeholder="需小于或等于每人限领量"
style="width: 255px" style="width: 255px"
input-style="text-align: center;" input-style="text-align: center;"
:maxlength="5"
@input="useLimitInput" @input="useLimitInput"
> >
<template #append>/每人1天</template> <template #append>/每人1天</template>
@ -420,12 +457,14 @@
import _ from "lodash"; import _ from "lodash";
import { dayjs } from "element-plus"; import { dayjs } from "element-plus";
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted } from "vue";
import { filterNumberInput } from "@/utils";
import { getBranchPage, getProductList, getCategoryList, addCoupon } from "@/api/coupon/index.js"; import { getBranchPage, getProductList, getCategoryList, addCoupon } from "@/api/coupon/index.js";
const shopInfo = ref(""); const shopInfo = ref("");
const dialogVisible = ref(false); const dialogVisible = ref(false);
const titleOptions = reactive({ const titleOptions = reactive({
name: "优惠券名称",
title: "添加优惠券", title: "添加优惠券",
couponTypeList: [ couponTypeList: [
{ {
@ -566,7 +605,7 @@ const form = ref({
discountRate: "", // % discountRate: "", // %
maxDiscountAmount: "", // maxDiscountAmount: "", //
useRule: "price_asc", // 使price_asc-price_desc- useRule: "price_asc", // 使price_asc-price_desc-
discountNum: "", // discountNum: 1, //
otherCouponShare: 1, // 0-1- otherCouponShare: 1, // 0-1-
}); });
@ -585,7 +624,7 @@ function reset() {
// 使 // 使
const fullAmountValidate = (rule, value, callback) => { const fullAmountValidate = (rule, value, callback) => {
if (form.value.fullAmount <= 0) { if (form.value.fullAmount < 0) {
callback(new Error("请输入使用门槛")); callback(new Error("请输入使用门槛"));
} else if (form.value.discountAmount <= 0) { } else if (form.value.discountAmount <= 0) {
callback(new Error("请输入满减金额")); callback(new Error("请输入满减金额"));
@ -594,7 +633,7 @@ const fullAmountValidate = (rule, value, callback) => {
} }
}; };
const fullAmountValidate2 = (rule, value, callback) => { const fullAmountValidate2 = (rule, value, callback) => {
if (form.value.fullAmount <= 0) { if (form.value.fullAmount < 0 || form.value.fullAmount == "") {
callback(new Error("请输入使用门槛")); callback(new Error("请输入使用门槛"));
} else { } else {
callback(); callback();
@ -812,6 +851,7 @@ function show(t, obj = null) {
} }
}); });
let m = titleOptions.couponTypeList.find((item) => item.value == t); let m = titleOptions.couponTypeList.find((item) => item.value == t);
titleOptions.name = `${m.label}名称`;
if (obj && obj.id) { if (obj && obj.id) {
titleOptions.title = `编辑${m.label}`; titleOptions.title = `编辑${m.label}`;
@ -919,6 +959,9 @@ function convertTimeToDate(timeStr, options = {}) {
const time = 500; const time = 500;
const discountNumInput = _.debounce(function (value) { const discountNumInput = _.debounce(function (value) {
form.value.discountNum = filterNumberInput(value, true); form.value.discountNum = filterNumberInput(value, true);
if (form.value.discountNum == "") {
form.value.discountNum = 1;
}
}, time); }, time);
const discountRateInput = _.debounce(function (value) { const discountRateInput = _.debounce(function (value) {
@ -957,51 +1000,6 @@ const useLimitInput = _.debounce(function (value) {
} }
}, time); }, 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({ defineExpose({
show, show,
}); });

View File

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

View File

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

View File

@ -14,16 +14,28 @@
class="dialog-form" class="dialog-form"
> >
<el-form-item label="赠券门槛" prop="fullAmount"> <el-form-item label="赠券门槛" prop="fullAmount">
<el-input <div class="center">
v-model="form.fullAmount" <el-input
placeholder="请输入赠券门槛" v-model="form.fullAmount"
style="width: 200px" placeholder="请输入赠券门槛"
input-style="text-align: center;" style="width: 240px"
@input="(e) => (form.fullAmount = filterNumberInput(e))" input-style="text-align: center;"
> @input="(e) => (form.fullAmount = filterNumberInput(e))"
<template #prepend></template> >
<template #append></template> <template #prepend></template>
</el-input> <template #append></template>
</el-input>
<el-tooltip
class="box-item"
effect="dark"
content="每单消费满此金额后赠送券 "
placement="top-start"
>
<el-icon size="18">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</el-form-item> </el-form-item>
<el-form-item label="优惠券" prop="coupon"> <el-form-item label="优惠券" prop="coupon">
<div class="center"> <div class="center">
@ -39,16 +51,6 @@
:key="item.id" :key="item.id"
/> />
</el-select> </el-select>
<el-input
v-model="couponGiveNum"
placeholder="请输入"
style="width: 250px"
input-style="text-align: center;"
@input="couponGiveNumInput"
>
<template #prepend>每次赠送</template>
<template #append></template>
</el-input>
</div> </div>
</el-form-item> </el-form-item>
<div class="title">指定设置</div> <div class="title">指定设置</div>
@ -80,6 +82,17 @@
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="每次赠送">
<el-input
v-model="couponGiveNum"
placeholder="请输入"
style="width: 200px"
input-style="text-align: center;"
@input="couponGiveNumInput"
>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="每人限量" prop="getLimit"> <el-form-item label="每人限量" prop="getLimit">
<div class="column"> <div class="column">
<div class="center"> <div class="center">
@ -121,6 +134,7 @@
<script setup> <script setup>
import _ from "lodash"; import _ from "lodash";
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { filterNumberInput } from "@/utils";
import { addConsumerCoupon, couponPage, updateConsumerCouponById } from "@/api/coupon/index.js"; import { addConsumerCoupon, couponPage, updateConsumerCouponById } from "@/api/coupon/index.js";
const shopInfo = ref(""); const shopInfo = ref("");
const dialogVisible = ref(false); const dialogVisible = ref(false);
@ -350,55 +364,13 @@ onMounted(() => {
couponPageAjax(); couponPageAjax();
}); });
/**
* 过滤输入只允许数字和最多两位小数
* @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;
}
// input // input
const time = 500; const time = 500;
const couponGiveNumInput = _.debounce(function (value) { const couponGiveNumInput = _.debounce(function (value) {
couponGiveNum.value = filterNumberInput(value, true); couponGiveNum.value = filterNumberInput(value, true);
if (couponGiveNum.value == "") {
couponGiveNum.value = 1;
}
}, time); }, time);
const giveNumInput = _.debounce(function (value) { const giveNumInput = _.debounce(function (value) {

View File

@ -20,7 +20,11 @@
}} }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="fullAmount" label="赠券门槛" width="180" /> <el-table-column prop="fullAmount" label="赠券门槛" width="180">
<template #default="scope">
{{ `${scope.row.fullAmount}元赠送` }}
</template>
</el-table-column>
<el-table-column prop="getLimit" label="每人限量" width="180"> <el-table-column prop="getLimit" label="每人限量" width="180">
<template #default="scope"> <template #default="scope">
<div v-if="scope.row.getLimit == -10086">无限</div> <div v-if="scope.row.getLimit == -10086">无限</div>
@ -76,7 +80,6 @@
<template #default="scope"> <template #default="scope">
<el-button <el-button
type="primary" type="primary"
size="small"
link link
@click="DialogFormRef.show(couponType, scope.row)" @click="DialogFormRef.show(couponType, scope.row)"
> >
@ -87,7 +90,7 @@
@confirm="deleteHandle(scope.row)" @confirm="deleteHandle(scope.row)"
> >
<template #reference> <template #reference>
<el-button type="danger" link size="small">删除</el-button> <el-button type="danger" link>删除</el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
</template> </template>

View File

@ -38,7 +38,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="giveNum" label="总发放数量" width="100"> <el-table-column prop="giveNum" label="总发放数量" width="100">
<template #default="scope"> <template #default="scope">
{{ scope.row.giveNum == -10086 ? "无限" : scope.row.giftNum }} {{ scope.row.giveNum == -10086 ? "无限" : scope.row.giveNum }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="giftNum" label="已领取" width="180"> <el-table-column prop="giftNum" label="已领取" width="180">

View File

@ -56,7 +56,7 @@ const menus = ref([
{ name: "弹窗广告", icon: "tcgg", pathName: "", intro: "设置弹窗广告" }, { name: "弹窗广告", icon: "tcgg", pathName: "", intro: "设置弹窗广告" },
{ name: "超级会员", icon: "cjhy", pathName: "superVip", intro: "用户会员管理设置" }, { name: "超级会员", icon: "cjhy", pathName: "superVip", intro: "用户会员管理设置" },
{ name: "新客立减", icon: "xklj", pathName: "newUserDiscount", intro: "首单下单减免金额" }, { name: "新客立减", icon: "xklj", pathName: "newUserDiscount", intro: "首单下单减免金额" },
{ name: "智慧充值", icon: "zhcz", pathName: "", intro: "允许客户充值并使用余额支付" }, { name: "智慧充值", icon: "zhcz", pathName: "wisdom_recharge", intro: "允许客户充值并使用余额支付" },
{ name: "分销", icon: "zhcz", pathName: "", intro: "允许客户充值并使用余额支付" }, { name: "分销", icon: "zhcz", pathName: "", intro: "允许客户充值并使用余额支付" },
{ {
name: "消费返现", name: "消费返现",

View File

@ -1,149 +1,169 @@
<template> <template>
<div> <div>
<el-dialog title="购买会员方案" v-model="show" @close="reset" width="60%"> <el-dialog title="添加方案" v-model="show" @closed="reset" width="730px">
<el-form :model="form" label-width="120px"> <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="周期名称" required> <el-form-item label="减免金额" prop="amount">
<el-input v-model="form.name" placeholder="周期名称" /> <el-input
</el-form-item> v-model="form.amount"
<el-form-item label="周期价格" required> placeholder="请输入减免金额"
<el-input v-model="form.price" placeholder="周期价格" type="number" /> style="width: 270px"
</el-form-item> :maxlength="8"
<el-form-item label="赠送成长值" required> @input="amountInput"
<el-input v-model="form.reward" placeholder="开通后立刻获得经验" type="number" /> >
</el-form-item> <template #append></template>
</el-input>
<el-form-item label="周期时间" required> </el-form-item>
<div class="flex"> <el-form-item label="概率" prop="probability">
<el-input-number <div class="column">
style="width: 300px" <div class="item">
v-model="form.circleTime" <el-input
placeholder="周期时间" v-model="form.probability"
type="number" placeholder="请输入概率"
/> style="width: 270px"
<el-select class="ml-2" v-model="form.circleUnit" placeholder=""> @input="probabilityInput"
<el-option label="年" value="年" /> >
<el-option label="月" value="月" /> <template #append>%</template>
<el-option label="周" value="周" /> </el-input>
<el-option label="天" value="天" /> </div>
</el-select> <div class="item">
</div> <span class="tips">所有概率相加必须等于100%</span>
</el-form-item> </div>
</el-form> </div>
<div style="text-align: right; margin-top: 20px"> </el-form-item>
<el-button @click="close">取消</el-button> </el-form>
<el-button type="primary" @click="submit">{{ isedit ? "更新" : "提交" }}</el-button> <div style="text-align: right; margin-top: 20px">
</div> <el-button @click="close">取消</el-button>
</el-dialog> <el-button type="primary" @click="submit">{{ isedit ? "更新" : "提交" }}</el-button>
</div> </div>
</el-dialog>
</div>
</template> </template>
<script setup> <script setup>
import _ from "lodash";
import { ref, toRaw } from "vue"; import { ref, toRaw } from "vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
// //
const show = ref(false); const show = ref(false);
//
const couponDialogVisible = ref(false);
// //
const formRef = ref(null);
const form = ref({ const form = ref({
name: "", amount: "",
price: 0, probability: "",
reward: 0, });
couponList: [], // const rules = ref({
circleTime: 1, // amount: [{ required: true, message: "请输入减免金额", trigger: "blur" }],
circleUnit: "月", // probability: [{ required: true, message: "请输入概率", trigger: "blur" }],
}); });
// const time = 500;
const couponList = ref([ const amountInput = _.debounce(function (value) {
{ id: 1, name: "满100减10", value: "10元" }, form.value.amount = filterNumberInput(value);
{ id: 2, name: "满200减30", value: "30元" }, }, time);
{ id: 3, name: "满500减100", value: "100元" }, const probabilityInput = _.debounce(function (value) {
]); form.value.probability = filterNumberInput(value, true);
if (form.value.probability >= 100) {
// form.value.probability = 100;
const selectedCoupons = ref([]); }
}, time);
//
function openCouponDialog() {
couponDialogVisible.value = true;
}
//
function closeCouponDialog() {
couponDialogVisible.value = false;
}
//
function confirmCouponSelection() {
form.value.couponList = [...selectedCoupons.value];
closeCouponDialog();
}
//
function handleCouponSelection(selection) {
selectedCoupons.value = selection;
}
// //
function reset() { function reset() {
form.value = { form.value = {
name: "", amount: "",
price: 0, probability: "",
reward: 0, };
couponList: [],
circleTime: "",
};
} }
const emits = defineEmits(["submitSuccess"]);
// //
const emits = defineEmits(["submitSuccess"]);
function submit() { function submit() {
if (!form.value.name) { formRef.value.validate(async (valid) => {
ElMessage.error("请输入方案名称"); try {
return; if (valid) {
} emits("submitSuccess", form.value, dataIndex);
if (form.value.price <= 0) { close();
ElMessage.error("请输入有效的价格"); }
return; } catch (err) {
} console.log(err);
if (!form.value.circleTime) { }
ElMessage.error("请选择会员周期"); });
return;
}
const ispass = form.value.couponList.every((item) => item.num && item.coupon.id);
if (!ispass) {
ElMessage.error("请选择优惠券并输入数量");
return;
}
console.log("提交表单数据:", form.value);
emits("submitSuccess", form.value, dataIndex);
// API
close();
} }
let isedit = ref(false); let isedit = ref(false);
let dataIndex = null; let dataIndex = null;
function open(data, index) { function open(data = null, index = 0) {
data = toRaw(data); if (data) {
console.log("data", data); form.value = { ...data };
console.log("index", index); dataIndex = index;
if (data) { isedit.value = true;
form.value = data; } else {
isedit.value = true; dataIndex = null;
dataIndex = index; isedit.value = false;
} else { }
isedit.value = false; console.log(data);
dataIndex = null; show.value = true;
}
console.log(data);
show.value = true;
} }
function close() { function close() {
show.value = false; show.value = false;
reset(); reset();
} }
defineExpose({ open, close, reset, submit }); defineExpose({ open });
</script>
/**
* 过滤输入只允许数字和最多两位小数
* @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;
}
</script>
<style scoped lang="scss">
.column {
display: flex;
flex-direction: column;
.item {
flex: 1;
}
}
</style>

View File

@ -1,151 +1,215 @@
<template> <template>
<div class="m-4 bg-white p-4"> <div class="m-4 bg-white p-4">
<HeaderCard <HeaderCard
name="新客立减" name="新客立减"
intro="首单下单减免金额" intro="首单下单减免金额"
icon="new_user_discount" icon="new_user_discount"
showSwitch showSwitch
v-model:isOpen="isEnable" v-model:isOpen="basicForm.isEnable"
></HeaderCard> ></HeaderCard>
<el-form ref="form" :model="basicForm"> <div style="padding-top: 40px">
<el-form-item label="减免方式"> <el-form
<div> ref="formRef"
<el-radio-group v-model="basicForm.discountType"> :rules="rules"
<el-radio value="FIXED">固定金额</el-radio> :model="basicForm"
<el-radio value="RANDOM">随机立减</el-radio> label-width="120px"
</el-radio-group> label-position="right"
<div v-if="basicForm.discountType === 'FIXED'"> >
<el-input type="number" v-model="basicForm.discountValue" placeholder="请输入金额"> <el-form-item label="活动时间">
<template #append></template> <div style="width: 300px">
</el-input> <el-date-picker
</div> v-model="validityScope"
</div> type="daterange"
</el-form-item> range-separator="至"
<!-- 随机立减 --> start-placeholder="开始时间"
<div v-if="basicForm.discountType == 'RANDOM'"> end-placeholder="结束时间"
<el-form-item label="会员周期列表"> @change="validityScopeChange"
<el-button type="primary" @click="refDialogPlans.open()">添加方案</el-button> />
</el-form-item> </div>
<el-form-item label=""> </el-form-item>
<el-table :data="basicForm.randomDiscountList" border style="width: 60%"> <el-form-item label="减免方式">
<el-table-column prop="amount" label="减免金融(元)" align="center" /> <div>
<el-table-column prop="probability" label="概率(%" align="center" /> <el-radio-group v-model="basicForm.discountType">
<el-radio value="FIXED">固定金额</el-radio>
<el-radio value="RANDOM">随机立减</el-radio>
</el-radio-group>
<div v-if="basicForm.discountType === 'FIXED'">
<el-input
v-model="basicForm.discountAmount"
placeholder="请输入金额"
:maxlength="8"
@input="discountAmountInput"
>
<template #append></template>
</el-input>
</div>
</div>
</el-form-item>
<!-- 随机立减 -->
<div v-if="basicForm.discountType == 'RANDOM'">
<el-form-item label="随机减免方案">
<el-button type="primary" @click="refDialogPlans.open()">
添加方案
</el-button>
</el-form-item>
<el-form-item prop="randomDiscountList">
<el-table :data="basicForm.randomDiscountList" border style="width: 60%">
<el-table-column prop="amount" label="减免金融(元)" align="center" />
<el-table-column prop="probability" label="概率(%" align="center" />
<el-table-column label="操作" align="center"> <el-table-column label="操作" align="center">
<template #default="scope"> <template #default="scope">
<el-button type="text" @click="refDialogPlans.open(scope.row, scope.$index)"> <el-button
编辑 type="primary"
</el-button> link
<el-button type="text" style="color: red" @click="deletePlan(scope.row)"> @click="refDialogPlans.open(scope.row, scope.$index)"
删除 >
</el-button> 编辑
</template> </el-button>
</el-table-column> <el-button
</el-table> type="danger"
</el-form-item> link
</div> @click="
<div> basicForm.randomDiscountList.splice(scope.$index, 1)
<el-form-item label="可使用类型"> "
<el-checkbox-group v-model="basicForm.useType"> >
<el-checkbox 删除
v-model="item.value" </el-button>
:label="item.value" </template>
v-for="item in useTypeList" </el-table-column>
:key="item.value" </el-table>
> </el-form-item>
{{ item.label }} </div>
</el-checkbox> <div>
</el-checkbox-group> <el-form-item label="可使用类型" prop="useTypeList">
</el-form-item> <el-checkbox-group v-model="basicForm.useTypeList">
</div> <el-checkbox
</el-form> v-model="item.value"
:label="item.label"
<div class="flex mt-10 justify-center gap-10"> :value="item.value"
<el-button style="width: 100px" type="primary" @click="basicSubmit" size="large"> v-for="item in useTypeList"
保存 :key="item.value"
</el-button> ></el-checkbox>
<el-button @click="close" style="width: 100px" size="large">取消</el-button> </el-checkbox-group>
</div> </el-form-item>
</div>
<DialogPlans ref="refDialogPlans" @submitSuccess="submitSuccess"></DialogPlans> </el-form>
</div> </div>
<div class="flex mt-10 justify-center gap-10">
<el-button style="width: 100px" type="primary" @click="basicSubmit" size="large">
保存
</el-button>
<el-button @click="close" style="width: 100px" size="large">取消</el-button>
</div>
<DialogPlans ref="refDialogPlans" @submitSuccess="submitSuccess"></DialogPlans>
</div>
</template> </template>
<script setup> <script setup>
import { dayjs } from "element-plus";
import _, { cloneDeep } from "lodash";
import shopApi from "@/api/account/shop"; import shopApi from "@/api/account/shop";
import consumeDiscountApi from "@/api/market/consumeDiscount"; import consumeDiscountApi from "@/api/market/consumeDiscount";
import HeaderCard from "../components/headerCard.vue"; import HeaderCard from "../components/headerCard.vue";
import DialogPlans from "./components/dialog-plans.vue"; import DialogPlans from "./components/dialog-plans.vue";
import { ref, reactive, watch, toRaw, getCurrentInstance, onMounted } from "vue"; import { ref, reactive, watch, toRaw, getCurrentInstance, onMounted } from "vue";
import { ElMessage } from "element-plus"; import { ElMessage, ElNotification } from "element-plus";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { filterNumberInput, convertTimeToDate } from "@/utils";
const inputStyle = { const inputStyle = {
width: "340px", width: "340px",
}; };
const router = useRouter(); const router = useRouter();
//
const isEnable = ref(false);
const refDialogPlans = ref(); const refDialogPlans = ref();
const configs = [ const configs = [
{ name: "basic", label: "会员基础设置" }, { name: "basic", label: "会员基础设置" },
{ name: "lv", label: "会员等级设置" }, { name: "lv", label: "会员等级设置" },
{ name: "order", label: "购买会员订单" }, { name: "order", label: "购买会员订单" },
]; ];
const activeTab = ref("basic"); const activeTab = ref("basic");
const useTypeList = ref([ const useTypeList = ref([
{ label: "堂食", value: "dine-in" }, { label: "堂食", value: "dine-in" },
{ label: "外带", value: "take-out" }, { label: "外带", value: "take-out" },
{ label: "外卖", value: "take-away" }, { label: "外卖", value: "take-away" },
{ label: "快递", value: "post" },
]); ]);
const formRef = ref(null);
const basicForm = reactive({ const basicForm = reactive({
isEnable: 0, isEnable: 1,
discountType: "RANDOM", discountType: "RANDOM",
discountAmount: 0.0, discountAmount: 0.0,
randomDiscountList: [], randomDiscountList: [],
useType: [], useTypeList: [],
shopId: 0, shopId: 0,
startTime: "",
endTime: "",
}); });
function deletePlan(row) {
const index = basicForm.configList.indexOf(row); const validityScope = ref([]);
if (index > -1) { function validityScopeChange(e) {
basicForm.configList.splice(index, 1); if (e && e.length) {
} basicForm.startTime = dayjs(e[0]).format("YYYY-MM-DD 00:00:00");
basicForm.endTime = dayjs(e[1]).format("YYYY-MM-DD 23:59:59");
} else {
basicForm.startTime = "";
basicForm.endTime = "";
}
} }
function submitSuccess(plans, index) {
if (!basicForm.configList) { const rules = reactive({
basicForm.configList = []; useTypeList: [
} {
if (index !== null && index !== undefined) { required: true,
basicForm.configList[index] = plans; message: "请选择可使用类型",
return; trigger: "change",
} },
basicForm.configList.push(plans); ],
randomDiscountList: [
{
validator: (rule, value, callback) => {
if (basicForm.randomDiscountList.length == 0) {
callback(new Error("请添加方案"));
} else {
callback();
}
},
trigger: "change",
},
],
});
const time = 500;
const discountAmountInput = _.debounce(function (e) {
basicForm.discountAmount = filterNumberInput(e);
}, time);
function submitSuccess(plans, index = null) {
if (index !== null && index !== undefined) {
basicForm.randomDiscountList[index] = plans;
} else {
basicForm.randomDiscountList.push(plans);
}
} }
// //
function basicSubmit() { function basicSubmit() {
const data = toRaw(basicForm); formRef.value.validate(async (vaild) => {
// if (data.openType == "PAY") { try {
// data.conditionList = null; if (vaild) {
// } console.log(basicForm);
// if (data.openType == "CONDITION") { await consumeDiscountApi.editConfig(basicForm);
// data.configList = null; ElNotification({
// } title: "注意",
data.conditionList = useTypes.value message: "保存成功",
.filter((v) => v.checked) type: "success",
.map((v) => { });
return { }
code: v.code, } catch (err) {
value: v.value, console.log(err);
}; }
}); });
consumeDiscountApi.editConfig(data).then((res) => {
ElMessage.success("保存成功");
});
// ElMessage.success("");
} }
// //
@ -156,135 +220,144 @@ const selectedLevel = ref(null);
// //
const couponList = ref([ const couponList = ref([
{ id: 1, name: "满100减10" }, { id: 1, name: "满100减10" },
{ id: 2, name: "满200减30" }, { id: 2, name: "满200减30" },
]); ]);
let activeLevelId = ref(null); let activeLevelId = ref(null);
// //
function addLevel() { function addLevel() {
const nowLastVip = levels.value[levels.value.length - 1]; const nowLastVip = levels.value[levels.value.length - 1];
let name = "VIP1"; let name = "VIP1";
if (levels.value.length) { if (levels.value.length) {
name = "VIP" + (levels.value.length + 1); name = "VIP" + (levels.value.length + 1);
} }
if (nowLastVip && !nowLastVip.id) { if (nowLastVip && !nowLastVip.id) {
ElMessage.error("请先保存当前等级"); ElMessage.error("请先保存当前等级");
return; return;
} }
const newLevel = { const newLevel = {
name, name,
experienceValue: 0, experienceValue: 0,
discount: 1, discount: 1,
logo: "", logo: "",
costRewardPoints: 1, costRewardPoints: 1,
isCostRewardPoints: 1, isCostRewardPoints: 1,
isCycleReward: 0, isCycleReward: 0,
cycleTime: 1, cycleTime: 1,
cycleUnit: "月", cycleUnit: "月",
cycleRewardPoints: 1, cycleRewardPoints: 1,
cycleRewardCouponList: [], cycleRewardCouponList: [],
}; };
console.log(newLevel); console.log(newLevel);
levels.value.push(newLevel); levels.value.push(newLevel);
selectedLevel.value = newLevel; selectedLevel.value = newLevel;
activeLevelId.value = levels.value.length - 1; activeLevelId.value = levels.value.length - 1;
} }
// //
function editLevel(level) { function editLevel(level) {
selectedLevel.value = level; selectedLevel.value = level;
} }
// //
async function removeLevel(index) { async function removeLevel(index) {
const item = levels.value[index]; const item = levels.value[index];
const { id } = item; const { id } = item;
if (!id) { if (!id) {
// //
levels.value.splice(index, 1); levels.value.splice(index, 1);
const newLevel = levels.value[index - 1]; const newLevel = levels.value[index - 1];
selectedLevel.value = newLevel; selectedLevel.value = newLevel;
activeLevelId.value = index - 1; activeLevelId.value = index - 1;
ElMessage.success("删除成功"); ElMessage.success("删除成功");
return; return;
} }
ElMessageBox.confirm("确定要删除吗?", "提示", { ElMessageBox.confirm("确定要删除吗?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning", type: "warning",
}).then(async () => { }).then(async () => {
const res = await consumeDiscountApi.levelDel({ id: id }); const res = await consumeDiscountApi.levelDel({ id: id });
if (res) { if (res) {
levels.value.splice(index, 1); levels.value.splice(index, 1);
ElMessage.success("删除成功"); ElMessage.success("删除成功");
} }
const newLevel = levels.value[index - 1]; const newLevel = levels.value[index - 1];
selectedLevel.value = newLevel; selectedLevel.value = newLevel;
activeLevelId.value = index - 1; activeLevelId.value = index - 1;
}); });
} }
// //
async function saveLevel(level) { async function saveLevel(level) {
const isPass = level.cycleRewardCouponList.every((item) => item.num && item.coupon.id); const isPass = level.cycleRewardCouponList.every((item) => item.num && item.coupon.id);
if (!isPass) { if (!isPass) {
ElMessage.error("请选择优惠券并输入数量"); ElMessage.error("请选择优惠券并输入数量");
return; return;
} }
const res = level.id const res = level.id
? await consumeDiscountApi.levelEdit(level) ? await consumeDiscountApi.levelEdit(level)
: await consumeDiscountApi.levelAdd(level); : await consumeDiscountApi.levelAdd(level);
if (res) { if (res) {
ElMessage.success("保存成功"); ElMessage.success("保存成功");
} }
levelRefresh(); levelRefresh();
} }
const shops = ref([]); const shops = ref([]);
async function levelRefresh() { async function levelRefresh() {
consumeDiscountApi.levelList().then((res) => { consumeDiscountApi.levelList().then((res) => {
if (res && res.length) { if (res && res.length) {
levels.value = res; levels.value = res;
if (res.length != 0) { if (res.length != 0) {
selectedLevel.value = res[activeLevelId.value]; selectedLevel.value = res[activeLevelId.value];
} }
} }
}); });
} }
async function init() { async function init() {
consumeDiscountApi.getConfig().then((res) => { consumeDiscountApi.getConfig().then((res) => {
Object.assign(basicForm, res); Object.assign(basicForm, res);
}); if (!res.useTypeList) {
basicForm.useTypeList = [];
}
if (!res.randomDiscountList) {
basicForm.randomDiscountList = [];
}
if (res.startTime) {
validityScope.value = [res.startTime, res.endTime];
}
});
} }
onMounted(() => { onMounted(() => {
init(); init();
}); });
// //
function totalCount(arr) { function totalCount(arr) {
return arr.reduce((total, item) => { return arr.reduce((total, item) => {
return total + item.num * 1; return total + item.num * 1;
}, 0); }, 0);
} }
// //
function close() { function close() {
router.back(); router.back();
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-tabs--border-card) { :deep(.el-tabs--border-card) {
border: none; border: none;
} }
:deep(.el-tabs--border-card > .el-tabs__header) { :deep(.el-tabs--border-card > .el-tabs__header) {
border: none; border: none;
padding: 4px; padding: 4px;
} }
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item) { :deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item) {
border: none; border: none;
} }
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active) { :deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active) {
border: none; border: none;
} }
</style> </style>

View File

@ -13,7 +13,7 @@
<el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="名称" width="180" /> <el-table-column prop="title" label="名称" width="180" />
<el-table-column prop="fullAmount" label="使用门槛" width="180"> <el-table-column prop="fullAmount" label="使用门槛" width="180">
<template #default="scope">{{ scope.row.discountAmount }}</template> <template #default="scope">{{ scope.row.fullAmount }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="validStartTime" label="有效期" width="320"> <el-table-column prop="validStartTime" label="有效期" width="320">
<template #default="scope"> <template #default="scope">
@ -27,7 +27,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="giveNum" label="总发放数量" width="100"> <el-table-column prop="giveNum" label="总发放数量" width="100">
<template #default="scope"> <template #default="scope">
{{ scope.row.giveNum == -10086 ? "无限" : scope.row.giftNum }} {{ scope.row.giveNum == -10086 ? "无限" : scope.row.giveNum }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="giftNum" label="已领取" width="180"> <el-table-column prop="giftNum" label="已领取" width="180">

View File

@ -12,9 +12,11 @@
<el-table :data="tableData.list" border stripe v-loading="tableData.loading"> <el-table :data="tableData.list" border stripe v-loading="tableData.loading">
<el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="规则名称" width="180" /> <el-table-column prop="title" label="规则名称" width="180" />
<el-table-column prop="discountRate" label="折扣率" width="180" /> <el-table-column prop="discountRate" label="折扣率" width="180">
<el-table-column prop="fullAmount" label="使用门槛" width="180"> <template #default="scope">{{ scope.row.discountRate }}%</template>
<template #default="scope">{{ scope.row.fullAmount }}</template> </el-table-column>
<el-table-column prop="discountAmount" label="使用门槛" width="180">
<template #default="scope">{{ scope.row.discountAmount }}</template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="maxDiscountAmount" prop="maxDiscountAmount"

View File

View File

@ -0,0 +1,218 @@
<template>
<div>
<el-dialog
v-model="show"
:title="editorIndex ? '编辑充值面额' : '添加充值面额'"
width="700"
@closed="reset"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
label-position="right"
>
<el-form-item label="充值面额" prop="amount">
<el-input
v-model="form.amount"
placeholder="请输入充值面额"
style="width: 270px"
:maxlength="8"
@input="amountInput"
>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="赠送金额">
<el-input
v-model="form.rewardAmount"
placeholder="请输入赠送金额"
style="width: 270px"
:maxlength="8"
@input="rewardAmountInput"
>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="赠送积分">
<el-input
v-model="form.rewardPoints"
placeholder="请输入赠送积分"
style="width: 270px"
@input="rewardPointsInput"
></el-input>
</el-form-item>
<el-form-item label="赠送优惠券" prop="couponInfoList">
<div class="item">
<div
class="center"
v-for="(item, index) in form.couponInfoList"
:key="index"
>
<el-select v-model="item.id">
<el-option
v-for="val of props.couponList"
:key="val.id"
:value="val.id"
:label="val.title"
></el-option>
</el-select>
<el-input v-model="item.num" @input="numInput($event, index)">
<template #append>数量</template>
</el-input>
<el-button
type="danger"
text
@click="form.couponInfoList.splice(index, 1)"
>
删除
</el-button>
</div>
<div class="center">
<el-button type="primary" link icon="CirclePlus" @click="addItemHandle">
新增券
</el-button>
</div>
</div>
</el-form-item>
<el-form-item></el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="show = false"> </el-button>
<el-button type="primary" @click="submitHandle"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import _, { cloneDeep } from "lodash";
import { onMounted } from "vue";
import { filterNumberInput } from "@/utils";
const show = ref(false);
const props = defineProps({
couponList: {
type: Array,
default: [],
},
});
const editorIndex = ref(null);
const resetForm = ref("");
const formRef = ref(null);
const form = ref({
amount: "",
rewardAmount: "",
rewardPoints: "",
couponInfoList: [],
});
const rules = ref({
amount: [{ required: true, message: "请输入充值面额", trigger: "blur" }],
couponInfoList: [
{
validator: (rule, value, callback) => {
let flag = true;
form.value.couponInfoList.map((item) => {
if (!item.id) {
flag = false;
}
});
if (!flag) {
callback(new Error("请选择优惠券"));
} else {
callback();
}
},
trigger: "change",
},
],
});
//
function addItemHandle() {
form.value.couponInfoList.push({
id: "",
num: 1,
});
}
//
const emits = defineEmits(["success"]);
function submitHandle() {
formRef.value.validate((valid) => {
if (valid) {
emits("success", {
data: { ...form.value },
index: editorIndex.value,
});
show.value = false;
}
});
}
const time = 500;
const amountInput = _.debounce(function (e) {
form.value.amount = filterNumberInput(e);
}, time);
const rewardAmountInput = _.debounce(function (e) {
form.value.rewardAmount = filterNumberInput(e);
}, time);
const rewardPointsInput = _.debounce(function (e) {
form.value.rewardPoints = filterNumberInput(e, true);
}, time);
const numInput = _.debounce(function (e, index) {
form.value.couponInfoList[index].num = filterNumberInput(e, true);
if (form.value.couponInfoList[index].num < 1) {
form.value.couponInfoList[index].num = 1;
}
}, time);
function reset() {
editorIndex.value = null;
form.value = cloneDeep(resetForm.value);
form.value.couponInfoList = [];
}
//
function open(obj = null, index = null) {
console.log(obj, index);
if (obj && obj.amount) {
form.value = cloneDeep(obj);
editorIndex.value = index;
}
show.value = true;
}
defineExpose({
open,
});
onMounted(() => {
resetForm.value = { ...form.value };
});
</script>
<style scoped lang="scss">
.item {
width: 100%;
display: flex;
gap: 14px;
flex-direction: column;
}
.center {
flex: 1;
display: flex;
align-items: center;
gap: 14px;
}
</style>

View File

@ -0,0 +1,331 @@
<template>
<div class="container">
<div class="content">
<HeaderCard
name="智慧充值"
intro="允许客户充值并使用余额支付"
icon="zhcz"
showSwitch
v-model:isOpen="form.isEnable"
></HeaderCard>
<div style="padding-top: 14px">
<el-tabs v-model="tabsValue">
<el-tab-pane label="基础设置" :name="1">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120"
label-position="right"
>
<el-form-item label="充值面额" required>
<el-button type="primary" @click="AddDialogRef.open()">
添加面额
</el-button>
</el-form-item>
<el-form-item prop="rechargeDetailList">
<el-table :data="form.rechargeDetailList" border stripe>
<el-table-column label="ID" prop="id"></el-table-column>
<el-table-column
label="充值金额(元)"
prop="amount"
></el-table-column>
<el-table-column
label="赠送金额"
prop="rewardAmount"
></el-table-column>
<el-table-column
label="赠送积分"
prop="rewardPoints"
></el-table-column>
<el-table-column label="赠送优惠券" prop="couponInfoList">
<template #default="scope">
<div class="column">
<div v-for="item in scope.row.couponInfoList">
<el-tag type="primary" disable-transitions>
{{ couponListFilter(item.id) }}x{{
item.num
}}
</el-tag>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button
type="primary"
link
@click="AddDialogRef.open(scope.row, scope.$index)"
>
编辑
</el-button>
<el-button
type="danger"
link
@click="
form.rechargeDetailList.splice(scope.$index, 1)
"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item
label="选择门店"
prop="useType"
v-if="shopInfo.isHeadShop"
>
<el-radio-group v-model="form.useType">
<el-radio label="全部门店" value="all"></el-radio>
<el-radio label="指定门店可用" value="part"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择门店" v-if="form.useType == 'part'">
<el-select
v-model="form.shopIdList"
multiple
clearable
placeholder="请选择门店"
style="width: 300px"
>
<el-option
:label="item.shopName"
:value="item.id"
v-for="item in branchList"
:key="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="自定义金额">
<div class="column">
<div class="center">
<el-switch
v-model="form.isCustom"
:active-value="1"
:inactive-value="0"
/>
<span class="tips">自定义金额不参与赠送优惠</span>
</div>
</div>
</el-form-item>
<el-form-item label="充值并下单">
<div class="column">
<div class="center">
<el-switch
v-model="form.isOrder"
:active-value="1"
:inactive-value="0"
/>
<span class="tips">开启后订单支付页面显示充值选项</span>
</div>
</div>
</el-form-item>
<el-form-item label="充值说明" prop="remark">
<div class="column">
<div class="item">
<el-input
type="textarea"
:rows="4"
:maxlength="250"
v-model="form.remark"
placeholder="填写内容"
></el-input>
</div>
<div class="item textarea-num">
{{ form.remark.length }}/250字内单文本
</div>
</div>
</el-form-item>
</el-form>
<div class="footer">
<el-button type="primary" size="large" @click="submitHandle">
保存
</el-button>
<el-button size="large" @click="back">取消</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="充值记录" :name="2">
<ChargeList />
</el-tab-pane>
</el-tabs>
</div>
</div>
<AddDialog ref="AddDialogRef" :couponList="couponList" @success="addSuccess" />
</div>
</template>
<script setup>
import HeaderCard from "../components/headerCard.vue";
import AddDialog from "./components/addDialog.vue";
import ChargeList from "@/views/user/charge/index.vue";
import { couponPage, getBranchPage, shopRecharge, shopRechargeGet } from "@/api/coupon/index.js";
import { useRouter } from "vue-router";
import { ElNotification } from "element-plus";
const router = useRouter();
const AddDialogRef = ref(null);
const tabsValue = ref(1);
const formRef = ref(null);
const form = ref({
isEnable: 1,
id: "",
shopIdList: "",
useType: "all",
isCustom: 0,
isOrder: 0,
remark: "",
rechargeDetailList: [],
});
const rules = ref({
rechargeDetailList: [
{
validator: (rule, value, callback) => {
if (form.value.rechargeDetailList.length == 0) {
callback(new Error("请添加面额"));
} else {
callback();
}
},
trigger: "change",
},
],
remark: [{ required: true, message: "请输入充值说明", trigger: "blur" }],
});
function submitHandle() {
formRef.value.validate(async (valid) => {
try {
if (valid) {
await shopRecharge(form.value);
ElNotification({
title: "注意",
message: "保存成功",
type: "success",
});
}
} catch (err) {
console.log(err);
}
});
}
//
const shopInfo = ref("");
//
const branchList = ref([]);
async function getBranchPageAjax() {
try {
const res = await getBranchPage();
branchList.value = res.records;
} catch (err) {
console.log(err);
}
}
function addSuccess(data) {
console.log("addSuccess===", data);
if (data.index == null) {
form.value.rechargeDetailList.push(data.data);
} else {
form.value.rechargeDetailList[data.index] = data.data;
}
}
//
const couponList = ref([]);
async function couponPageAjax() {
try {
const res = await couponPage({
page: 1,
size: 500,
});
couponList.value = res.records;
} catch (err) {
console.log(err);
}
}
// id
function couponListFilter(id) {
if (id) {
let obj = couponList.value.find((item) => item.id == id);
return obj.title;
} else {
return "";
}
}
//
function getLocalShopInfo() {
shopInfo.value = JSON.parse(localStorage.getItem("userInfo"));
}
function back() {
router.back();
}
//
async function shopRechargeGetAjax() {
try {
const res = await shopRechargeGet();
res.rechargeDetailList.map((item) => {
item.couponInfoList.map((val) => {
val.id = val.coupon.id;
});
});
form.value = res;
console.log(form.value);
} catch (err) {
console.log(err);
}
}
onMounted(async () => {
await couponPageAjax();
getLocalShopInfo();
getBranchPageAjax();
shopRechargeGetAjax();
});
</script>
<style scoped lang="scss">
.container {
padding: 14px;
.content {
padding: 14px;
background-color: #fff;
border-radius: 8px;
}
}
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
.item {
flex: 1;
}
}
.center {
display: flex;
align-items: center;
gap: 10px;
}
.tips {
color: #666;
}
.footer {
display: flex;
justify-content: center;
}
.textarea-num {
color: #999;
display: flex;
justify-content: flex-end;
}
</style>