diff --git a/src/api/coupon/index.js b/src/api/coupon/index.js index a26816c..4aa0dea 100644 --- a/src/api/coupon/index.js +++ b/src/api/coupon/index.js @@ -126,4 +126,20 @@ export function deleteRecord(params) { method: 'DELETE', 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' + }); } \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 3697fb7..08c7c70 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,8 +4,8 @@ * @param {string} cls * @returns {boolean} */ -export function hasClass(ele: HTMLElement, cls: string) { - return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)")); +export function hasClass(ele : HTMLElement, cls : string) { + return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)")); } /** @@ -13,8 +13,8 @@ export function hasClass(ele: HTMLElement, cls: string) { * @param {HTMLElement} ele * @param {string} cls */ -export function addClass(ele: HTMLElement, cls: string) { - if (!hasClass(ele, cls)) ele.className += " " + cls; +export function addClass(ele : HTMLElement, cls : string) { + if (!hasClass(ele, cls)) ele.className += " " + cls; } /** @@ -22,11 +22,11 @@ export function addClass(ele: HTMLElement, cls: string) { * @param {HTMLElement} ele * @param {string} cls */ -export function removeClass(ele: HTMLElement, cls: string) { - if (hasClass(ele, cls)) { - const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); - ele.className = ele.className.replace(reg, " "); - } +export function removeClass(ele : HTMLElement, cls : string) { + if (hasClass(ele, cls)) { + const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); + ele.className = ele.className.replace(reg, " "); + } } /** @@ -35,9 +35,9 @@ export function removeClass(ele: HTMLElement, cls: string) { * @param {string} path * @returns {Boolean} */ -export function isExternal(path: string) { - const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path); - return isExternal; +export function isExternal(path : string) { + const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path); + return isExternal; } /** @@ -46,15 +46,15 @@ export function isExternal(path: string) { * @param growthRate * @returns */ -export function formatGrowthRate(growthRate: number) { - if (growthRate === 0) { - return "-"; - } +export function formatGrowthRate(growthRate : number) { + if (growthRate === 0) { + return "-"; + } - const formattedRate = Math.abs(growthRate * 100) - .toFixed(2) - .replace(/\.?0+$/, ""); - return formattedRate + "%"; + const formattedRate = Math.abs(growthRate * 100) + .toFixed(2) + .replace(/\.?0+$/, ""); + return formattedRate + "%"; } /** * Parse the time to string @@ -62,83 +62,174 @@ export function formatGrowthRate(growthRate: number) { * @param {string} cFormat * @returns {string} */ -export function parseTime(time: string | number | Date | null, cFormat: string | undefined) { - if (arguments.length === 0) { - return null; - } - const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}"; - let date; - if (typeof time === "undefined" || time === null || time === "null") { - return ""; - } else if (typeof time === "object") { - date = time; - } else { - if (typeof time === "string" && /^[0-9]+$/.test(time)) { - time = parseInt(time); - } - if (typeof time === "number" && time.toString().length === 10) { - time = time * 1000; - } - date = new Date(time); - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - }; - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key: keyof typeof formatObj): string => { - let value = formatObj[key]; - // Note: getDay() returns 0 on Sunday - if (key === "a") { - return ["日", "一", "二", "三", "四", "五", "六"][value]; - } - if (result.length > 0 && value < 10) { - value = Number("0" + value.toString()); - } - return value.toString() || "0"; - }); - return time_str; +export function parseTime(time : string | number | Date | null, cFormat : string | undefined) { + if (arguments.length === 0) { + return null; + } + const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}"; + let date; + if (typeof time === "undefined" || time === null || time === "null") { + return ""; + } else if (typeof time === "object") { + date = time; + } else { + if (typeof time === "string" && /^[0-9]+$/.test(time)) { + time = parseInt(time); + } + if (typeof time === "number" && time.toString().length === 10) { + time = time * 1000; + } + date = new Date(time); + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + }; + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key : keyof typeof formatObj) : string => { + let value = formatObj[key]; + // Note: getDay() returns 0 on Sunday + if (key === "a") { + return ["日", "一", "二", "三", "四", "五", "六"][value]; + } + if (result.length > 0 && value < 10) { + value = Number("0" + value.toString()); + } + return value.toString() || "0"; + }); + return time_str; } // 下载文件 -export function downloadFile(obj: BlobPart, name: string, suffix: string, useUnix = true) { - const url = window.URL.createObjectURL(new Blob([obj])); - const link = document.createElement("a"); - link.style.display = "none"; - link.href = url; - const newFilename = useUnix ? (parseTime(new Date(), undefined) + "-") : '' + name.trim() - const fileName = newFilename + "." + suffix; - link.setAttribute("download", fileName); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); +export function downloadFile(obj : BlobPart, name : string, suffix : string, useUnix = true) { + const url = window.URL.createObjectURL(new Blob([obj])); + const link = document.createElement("a"); + link.style.display = "none"; + link.href = url; + const newFilename = useUnix ? (parseTime(new Date(), undefined) + "-") : '' + name.trim() + const fileName = newFilename + "." + suffix; + link.setAttribute("download", fileName); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); } /** * 判断主店同步是否启用 */ export function isSyncStatus() { - 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) { - return true - }else { - return false - } + 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) { + return true + } else { + return false + } } /** * 判断是否有某权限 */ -export function hasPermission(params: any) { - let $PermissionObj = JSON.parse(localStorage.getItem("permission") || '[]' ) - const obj = $PermissionObj.find((v: any) => v == params || v == params) - if (obj) { - return obj - } +export function hasPermission(params : any) { + let $PermissionObj = JSON.parse(localStorage.getItem("permission") || '[]') + const obj = $PermissionObj.find((v : any) => v == params || v == params) + if (obj) { + return obj + } 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); +} \ No newline at end of file diff --git a/src/views/marketing_center/buy_one/index.vue b/src/views/marketing_center/buy_one/index.vue index 3e45c0c..a252d13 100644 --- a/src/views/marketing_center/buy_one/index.vue +++ b/src/views/marketing_center/buy_one/index.vue @@ -38,7 +38,7 @@ diff --git a/src/views/marketing_center/components/couponDialog.vue b/src/views/marketing_center/components/couponDialog.vue index 733bf11..8a33509 100644 --- a/src/views/marketing_center/components/couponDialog.vue +++ b/src/views/marketing_center/components/couponDialog.vue @@ -14,7 +14,7 @@ label-width="160px" class="dialog-form" > - + @@ -40,6 +41,7 @@ placeholder="请输入满减金额" style="width: 240px" input-style="text-align: center;" + :maxlength="8" @input="(e) => (form.discountAmount = filterNumberInput(e))" > @@ -56,6 +58,7 @@ placeholder="请输入使用门槛" style="width: 200px" input-style="text-align: center;" + :maxlength="8" @input="(e) => (form.fullAmount = filterNumberInput(e))" > @@ -63,10 +66,10 @@ - + - - + + @@ -78,7 +81,7 @@ :max-collapse-tags="3" collapse-tags clearable - style="width: 100%" + style="width: 300px" @change="selectFoodsConfirm" > @@ -113,6 +116,7 @@ placeholder="请输入使用门槛" style="width: 200px" input-style="text-align: center;" + :maxlength="8" @input="(e) => (form.discountAmount = filterNumberInput(e))" > @@ -126,6 +130,7 @@ v-model="form.maxDiscountAmount" placeholder="请输入金额" style="width: 200px" + :maxlength="8" @input="(e) => (form.maxDiscountAmount = filterNumberInput(e))" > @@ -133,7 +138,33 @@ -
+
+ + + + + + + + + + + + + + + +
指定设置
@@ -150,6 +181,7 @@ clearable placeholder="请选择门店" @change="shopsChange" + style="width: 300px" > -
+
- - + + @@ -175,7 +207,7 @@ :max-collapse-tags="3" collapse-tags clearable - style="width: 100%" + style="width: 300px" @change="selectFoodsConfirm" > @@ -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" > @@ -301,6 +335,7 @@ placeholder="请输入总发放数量" style="width: 200px" input-style="text-align: center;" + :maxlength="5" @input="giveNumInput" > @@ -322,6 +357,7 @@ placeholder="请输入每人限领量" style="width: 200px" input-style="text-align: center;" + :maxlength="5" @input="getLimitInput" > @@ -346,6 +382,7 @@ placeholder="需小于或等于每人限领量" style="width: 255px" input-style="text-align: center;" + :maxlength="5" @input="useLimitInput" > @@ -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,8 @@ const fullAmountValidate = (rule, value, callback) => { } }; const fullAmountValidate2 = (rule, value, callback) => { - if (form.value.fullAmount <= 0) { + console.log(form.value.fullAmount); + if (form.value.fullAmount < 0 || form.value.fullAmount === "") { callback(new Error("请输入使用门槛")); } else { callback(); @@ -812,6 +852,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 +960,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 +1001,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, }); diff --git a/src/views/marketing_center/components/headerCard.vue b/src/views/marketing_center/components/headerCard.vue index ce69abe..0e2c36c 100644 --- a/src/views/marketing_center/components/headerCard.vue +++ b/src/views/marketing_center/components/headerCard.vue @@ -10,7 +10,7 @@
- +
@@ -38,7 +38,7 @@ const props = defineProps({ }); const isOpen = defineModel("isOpen", { - type: Boolean, + type: [Boolean, String, Number], default: false, }); @@ -97,4 +97,4 @@ const getIconPath = (iconName) => { } } } - \ No newline at end of file + diff --git a/src/views/marketing_center/components/relevanceDialog.vue b/src/views/marketing_center/components/relevanceDialog.vue index 7bdb2b8..cbc7f73 100644 --- a/src/views/marketing_center/components/relevanceDialog.vue +++ b/src/views/marketing_center/components/relevanceDialog.vue @@ -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, diff --git a/src/views/marketing_center/consume_ticket/components/dialogForm.vue b/src/views/marketing_center/consume_ticket/components/dialogForm.vue index 8ed4af6..23d8d03 100644 --- a/src/views/marketing_center/consume_ticket/components/dialogForm.vue +++ b/src/views/marketing_center/consume_ticket/components/dialogForm.vue @@ -14,16 +14,28 @@ class="dialog-form" > - - - - +
+ + + + + + + + + +
@@ -39,16 +51,6 @@ :key="item.id" /> - - - -
指定设置
@@ -80,6 +82,17 @@
+ + + + +
@@ -121,6 +134,7 @@ \ No newline at end of file +defineExpose({ open }); + +/** + * 过滤输入,只允许数字和最多两位小数 + * @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; +} + + + diff --git a/src/views/marketing_center/new_user_discount/index.vue b/src/views/marketing_center/new_user_discount/index.vue index df3f38c..f7337f3 100644 --- a/src/views/marketing_center/new_user_discount/index.vue +++ b/src/views/marketing_center/new_user_discount/index.vue @@ -1,151 +1,215 @@ diff --git a/src/views/marketing_center/product_redemption/index.vue b/src/views/marketing_center/product_redemption/index.vue index 4392593..5b760d1 100644 --- a/src/views/marketing_center/product_redemption/index.vue +++ b/src/views/marketing_center/product_redemption/index.vue @@ -13,7 +13,7 @@ - + + + + + diff --git a/src/views/marketing_center/wisdom_recharge/index.vue b/src/views/marketing_center/wisdom_recharge/index.vue new file mode 100644 index 0000000..4840797 --- /dev/null +++ b/src/views/marketing_center/wisdom_recharge/index.vue @@ -0,0 +1,331 @@ + + + + +