| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- <template>
- <!-- 优惠券管理 - 新建/编辑页面(参考 IssueCoupons;左:类型/名称/折扣或面值/低消/有效期,右:数量/补充说明) -->
- <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
- <div class="header">
- <el-button @click="goBack"> 返回 </el-button>
- <h2 class="title">
- {{ type == "add" ? "新建优惠券" : "编辑优惠券" }}
- </h2>
- </div>
- <el-form :model="couponModel" ref="ruleFormRef" :rules="rules" label-width="200px" class="formBox">
- <div class="content">
- <!-- 左侧:优惠券类型、名称、折扣/面值、低消、有效期(领取后天数) -->
- <div class="contentLeft">
- <!-- 优惠券类型 -->
- <el-form-item label="优惠券类型" prop="couponType">
- <el-radio-group v-model="couponModel.couponType" class="radio-group">
- <el-radio :value="2"> 折扣券 </el-radio>
- <el-radio :value="1"> 满减券 </el-radio>
- </el-radio-group>
- </el-form-item>
- <!-- 优惠券名称 -->
- <el-form-item label="优惠券名称" prop="name">
- <el-input maxlength="50" v-model="couponModel.name" placeholder="请输入" clearable />
- </el-form-item>
- <!-- 折扣力度(折扣券) -->
- <el-form-item v-if="couponModel.couponType === 2" label="折扣力度" prop="discountRate">
- <el-input v-model="couponModel.discountRate" maxlength="4" placeholder="请输入0.1-9.9之间的数字" clearable>
- <template #suffix> 折 </template>
- </el-input>
- </el-form-item>
- <!-- 面值(满减券) -->
- <el-form-item v-if="couponModel.couponType === 1" label="面值(¥)" prop="nominalValue">
- <el-input v-model="couponModel.nominalValue" maxlength="15" placeholder="请输入" clearable />
- </el-form-item>
- <!-- 是否有低消 -->
- <el-form-item label="是否有低消" prop="hasMinimumSpend">
- <el-radio-group v-model="couponModel.hasMinimumSpend" class="radio-group">
- <el-radio :value="1"> 是 </el-radio>
- <el-radio :value="0"> 否 </el-radio>
- </el-radio-group>
- </el-form-item>
- <!-- 最低消费金额(元可用) -->
- <el-form-item v-if="couponModel.hasMinimumSpend === 1" label="" prop="minimumSpendingAmount" class="no-label">
- <el-input v-model="couponModel.minimumSpendingAmount" maxlength="15" placeholder="请输入" clearable>
- <template #suffix> 元可用 </template>
- </el-input>
- </el-form-item>
- <!-- 有效期:长期有效 / 自定义(领取后有效天数) -->
- <el-form-item label="有效期" prop="validityMode">
- <el-radio-group v-model="couponModel.validityMode" class="radio-group">
- <el-radio :value="0"> 长期有效 </el-radio>
- <el-radio :value="1"> 自定义 </el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item v-if="couponModel.validityMode === 1" label="领取后有效期" prop="validDaysAfterReceive">
- <el-input
- v-model="couponModel.validDaysAfterReceive"
- maxlength="5"
- placeholder="请输入正整数"
- clearable
- inputmode="numeric"
- @input="onValidDaysAfterReceiveInput"
- >
- <template #suffix> 天 </template>
- </el-input>
- </el-form-item>
- </div>
- <!-- 右侧:数量、补充说明 -->
- <div class="contentRight">
- <!-- 数量:不限 / 自定义 -->
- <el-form-item label="数量" prop="quantityMode">
- <el-radio-group v-model="couponModel.quantityMode" class="radio-group">
- <el-radio :value="0"> 不限 </el-radio>
- <el-radio :value="1"> 自定义 </el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item v-if="couponModel.quantityMode === 1" label="请输入数量" prop="singleQty">
- <el-input
- v-model="couponModel.singleQty"
- maxlength="5"
- placeholder="请输入正整数"
- clearable
- inputmode="numeric"
- @input="onSingleQtyInput"
- >
- <template #suffix> 张 </template>
- </el-input>
- </el-form-item>
- <!-- 补充说明 -->
- <el-form-item label="补充说明" prop="supplementaryInstruction">
- <el-input
- maxlength="300"
- v-model="couponModel.supplementaryInstruction"
- :rows="4"
- type="textarea"
- placeholder="请输入"
- show-word-limit
- />
- </el-form-item>
- </div>
- </div>
- </el-form>
- <!-- 底部按钮区域 -->
- <div class="button-container">
- <!-- <el-button @click="() => handleSubmit('draft')"> 存草稿 </el-button> -->
- <el-button type="primary" @click="() => handleSubmit()">
- {{ type === "add" ? "新建优惠券" : "编辑优惠券" }}
- </el-button>
- </div>
- </div>
- </template>
- <script setup lang="tsx" name="newCoupon">
- /**
- * 优惠券管理 - 新建/编辑页面
- * 接口入参:longTermValid(1 长期有效 / 0 自定义天数)+ expirationDate(天);
- * unlimitedQty(1 不限 / 0 自定义张数)+ singleQty(张)
- */
- import { ref, reactive, watch, nextTick, onMounted } from "vue";
- import { ElMessage } from "element-plus";
- import { useRoute, useRouter } from "vue-router";
- import type { FormInstance } from "element-plus";
- import { getCouponDetail, addDiscountCoupon, editDiscountCoupon } from "@/api/modules/couponManagement";
- import { validatePositiveNumber, validatePositiveInteger, validatePriceFormat } from "@/utils/eleValidate";
- import { localGet } from "@/utils";
- const router = useRouter();
- const route = useRoute();
- const type = ref<string>("");
- const id = ref<string>("");
- // 折扣力度校验:0.1-9.9
- const validateDiscountRate = (_rule: any, value: any, callback: (err?: Error) => void) => {
- if (couponModel.value.couponType !== 2) {
- callback();
- return;
- }
- if (value === null || value === undefined || value === "") {
- callback(new Error("请输入折扣力度"));
- return;
- }
- const num = Number(value);
- if (isNaN(num) || num < 0.1 || num > 9.9) {
- callback(new Error("请输入0.1-9.9之间的数字"));
- return;
- }
- callback();
- };
- const rules = reactive({
- name: [{ required: true, message: "请输入优惠券名称" }],
- couponType: [{ required: true, message: "请选择优惠券类型" }],
- nominalValue: [
- {
- validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
- if (couponModel.value.couponType !== 1) {
- callback();
- return;
- }
- if (!value || value.toString().trim() === "") {
- callback(new Error("请输入面值"));
- return;
- }
- const next = validatePriceFormat("整数部分最多6位,小数部分最多2位");
- next(_rule, value, callback);
- },
- trigger: "blur"
- }
- ],
- discountRate: [{ validator: validateDiscountRate, trigger: "blur" }],
- validityMode: [{ required: true, message: "请选择有效期类型" }],
- validDaysAfterReceive: [
- {
- validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
- if (couponModel.value.validityMode !== 1) {
- callback();
- return;
- }
- if (value === null || value === undefined || value === "") {
- callback(new Error("请输入领取后有效期"));
- return;
- }
- const next = validatePositiveInteger("领取后有效期须为正整数", { required: false });
- next(_rule, value, callback);
- },
- trigger: ["blur", "change"]
- }
- ],
- quantityMode: [{ required: true, message: "请选择数量类型" }],
- singleQty: [
- {
- validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
- if (couponModel.value.quantityMode !== 1) {
- callback();
- return;
- }
- if (value === null || value === undefined || value === "") {
- callback(new Error("请输入数量"));
- return;
- }
- const next = validatePositiveInteger("数量须为正整数", { required: false });
- next(_rule, value, callback);
- },
- trigger: ["blur", "change"]
- }
- ],
- minimumSpendingAmount: [
- {
- validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
- if (couponModel.value.hasMinimumSpend !== 1) {
- callback();
- return;
- }
- if (value === null || value === undefined || value === "") {
- callback(new Error("请输入最低消费金额"));
- return;
- }
- const next = validatePositiveNumber("最低消费金额必须为正数");
- next(_rule, value, callback);
- },
- trigger: "blur"
- },
- {
- validator: (_rule: any, value: any, callback: (err?: Error) => void) => {
- if (couponModel.value.hasMinimumSpend !== 1 || !value) {
- callback();
- return;
- }
- const next = validatePriceFormat("整数部分最多6位,小数部分最多2位");
- next(_rule, value, callback);
- },
- trigger: "blur"
- }
- ]
- });
- // 1-满减券 2-折扣券(与 ticketManagement index couponTypeEnum 一致)
- // validityMode:0 长期有效 → longTermValid=1;1 自定义 → longTermValid=0 + expirationDate=天
- // quantityMode:0 不限 → unlimitedQty=1;1 自定义 → unlimitedQty=0 + singleQty=张数
- const couponModel = ref<any>({
- name: "",
- couponType: 2,
- nominalValue: "",
- discountRate: "",
- validityMode: 0,
- validDaysAfterReceive: "",
- quantityMode: 0,
- singleQty: "",
- claimRule: "day",
- hasMinimumSpend: 0,
- minimumSpendingAmount: "",
- supplementaryInstruction: ""
- });
- /** 仅允许数字,限制长度(正整数输入) */
- const sanitizeDigits = (raw: string, maxLen: number) =>
- String(raw ?? "")
- .replace(/\D/g, "")
- .slice(0, maxLen);
- const onValidDaysAfterReceiveInput = (val: string) => {
- couponModel.value.validDaysAfterReceive = sanitizeDigits(val, 5);
- };
- const onSingleQtyInput = (val: string) => {
- couponModel.value.singleQty = sanitizeDigits(val, 5);
- };
- const isInitializing = ref(true);
- watch(
- () => couponModel.value.validityMode,
- newVal => {
- if (isInitializing.value) return;
- if (newVal === 0) {
- couponModel.value.validDaysAfterReceive = "";
- nextTick(() => ruleFormRef.value?.clearValidate("validDaysAfterReceive"));
- }
- }
- );
- watch(
- () => couponModel.value.quantityMode,
- newVal => {
- if (isInitializing.value) return;
- if (newVal === 0) {
- couponModel.value.singleQty = "";
- nextTick(() => ruleFormRef.value?.clearValidate("singleQty"));
- }
- }
- );
- watch(
- () => couponModel.value.hasMinimumSpend,
- newVal => {
- if (isInitializing.value) return;
- if (newVal === 0) {
- couponModel.value.minimumSpendingAmount = "";
- nextTick(() => ruleFormRef.value?.clearValidate("minimumSpendingAmount"));
- }
- }
- );
- // 切换优惠券类型时清空另一类字段并清校验
- watch(
- () => couponModel.value.couponType,
- newVal => {
- if (isInitializing.value) return;
- if (newVal === 1) {
- couponModel.value.discountRate = "";
- nextTick(() => ruleFormRef.value?.clearValidate("discountRate"));
- } else {
- couponModel.value.nominalValue = "";
- nextTick(() => ruleFormRef.value?.clearValidate("nominalValue"));
- }
- }
- );
- onMounted(async () => {
- id.value = (route.query.id as string) || "";
- type.value = (route.query.type as string) || "add";
- if (type.value !== "add" && id.value) {
- const res: any = await getCouponDetail({ counponId: id.value });
- const data = res.data || {};
- const lt = data.longTermValid;
- const isLongByFlag = lt === 1 || lt === "1" || lt === true;
- const isLongLegacy =
- lt === undefined &&
- (() => {
- const sd = data.specifiedDay;
- const sdNum = Number(sd);
- return sd === null || sd === undefined || sd === "" || Number.isNaN(sdNum) || sdNum === 0;
- })();
- const isLongValidity = isLongByFlag || isLongLegacy;
- const expDays = data.expirationDate ?? data.specifiedDay;
- const uq = data.unlimitedQty;
- const isUnlimitedByFlag = uq === 1 || uq === "1" || uq === true;
- const isUnlimitedLegacy =
- uq === undefined &&
- (data.singleQty === null || data.singleQty === undefined || data.singleQty === "" || Number(data.singleQty) === 0);
- const isUnlimitedQty = isUnlimitedByFlag || isUnlimitedLegacy;
- couponModel.value = {
- ...couponModel.value,
- ...data,
- couponType: data.couponType ?? 1,
- discountRate: data.discountRate != null ? (Number(data.discountRate) / 10).toString() : "",
- hasMinimumSpend: Number(data.minimumSpendingAmount) > 0 ? 1 : 0,
- validityMode: isLongValidity ? 0 : 1,
- validDaysAfterReceive: isLongValidity ? "" : String(expDays ?? ""),
- quantityMode: isUnlimitedQty ? 0 : 1,
- singleQty: isUnlimitedQty ? "" : String(data.singleQty ?? "")
- };
- } else {
- couponModel.value.couponType = couponModel.value.couponType ?? 2;
- couponModel.value.validityMode = 0;
- couponModel.value.quantityMode = 0;
- couponModel.value.validDaysAfterReceive = "";
- couponModel.value.singleQty = "";
- }
- await nextTick();
- ruleFormRef.value?.clearValidate();
- isInitializing.value = false;
- });
- const goBack = () => {
- router.go(-1);
- };
- const ruleFormRef = ref<FormInstance>();
- const buildSubmitParams = (submitType?: string) => {
- const m = couponModel.value;
- const longTermValid = m.validityMode === 0 ? 1 : 0;
- const expirationDate = m.validityMode === 0 ? 0 : Number(m.validDaysAfterReceive) || 0;
- const unlimitedQty = m.quantityMode === 0 ? 1 : 0;
- const singleQty = m.quantityMode === 0 ? 0 : Number(m.singleQty) || 0;
- const params: any = {
- name: m.name,
- couponType: m.couponType,
- nominalValue: m.nominalValue,
- discountRate: m.discountRate,
- longTermValid,
- expirationDate,
- unlimitedQty,
- singleQty,
- claimRule: m.claimRule || "day",
- attentionCanReceived: 0,
- hasMinimumSpend: m.hasMinimumSpend,
- minimumSpendingAmount: m.minimumSpendingAmount,
- supplementaryInstruction: m.supplementaryInstruction,
- beginGetDate: "",
- endGetDate: "",
- storeId: localGet("createdId"),
- couponId: type.value === "edit" ? id.value : "",
- couponStatus: submitType ? 0 : 1,
- restrictedQuantity: 0,
- getStatus: 1,
- type: 1
- };
- return params;
- };
- const handleSubmit = async (submitType?: string) => {
- const params: any = buildSubmitParams(submitType);
- // 折扣券:discountRate 后端多为 1-100(如 85 表示 8.5 折),此处按 0.1-9.9 输入则乘 10
- if (params.couponType === 2 && params.discountRate !== "" && params.discountRate != null) {
- const rate = Number(params.discountRate);
- params.discountRate = !isNaN(rate) ? Math.round(rate * 10) : null;
- }
- if (params.couponType === 1) {
- params.discountRate = null;
- }
- if (!params.hasMinimumSpend) {
- params.minimumSpendingAmount = 0;
- }
- if (submitType) {
- if (!couponModel.value.name?.trim()) {
- ElMessage.warning("请填写优惠券名称");
- return;
- }
- if (type.value === "add") {
- const res: any = await addDiscountCoupon(params);
- if (res?.code == 200) {
- ElMessage.success("草稿保存成功");
- goBack();
- }
- } else {
- params.couponId = id.value;
- const res: any = await editDiscountCoupon(params);
- if (res?.code == 200) {
- ElMessage.success("草稿保存成功");
- goBack();
- }
- }
- return;
- }
- ruleFormRef.value!.validate(async (valid: boolean) => {
- if (!valid) return;
- if (type.value === "add") {
- const res: any = await addDiscountCoupon(params);
- if (res?.code == 200) {
- ElMessage.success("创建成功");
- goBack();
- }
- } else {
- params.couponId = id.value;
- const res: any = await editDiscountCoupon(params);
- if (res?.code == 200) {
- ElMessage.success("修改成功");
- goBack();
- }
- }
- });
- };
- </script>
- <style scoped lang="scss">
- .table-box {
- display: flex;
- flex-direction: column;
- height: auto !important;
- min-height: 100%;
- }
- .header {
- display: flex;
- align-items: center;
- padding: 20px;
- border-bottom: 1px solid #e4e7ed;
- }
- .title {
- flex: 1;
- margin: 0;
- font-size: 18px;
- font-weight: bold;
- text-align: center;
- }
- .content {
- display: flex;
- flex: 1;
- column-gap: 24px;
- width: 98%;
- margin: 20px auto;
- .contentLeft {
- width: 50%;
- }
- .contentRight {
- width: 50%;
- }
- }
- .formBox {
- display: flex;
- flex-direction: column;
- width: 100%;
- min-height: 100%;
- }
- .radio-group {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- &.block {
- flex-direction: column;
- gap: 4px;
- }
- }
- .claim-rule-item :deep(.el-form-item__label) {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- }
- .label-tip {
- margin-left: 4px;
- font-size: 12px;
- font-weight: normal;
- color: #909399;
- }
- .no-label :deep(.el-form-item__label) {
- width: 0 !important;
- padding: 0;
- margin: 0;
- }
- .button-container {
- display: flex;
- gap: 12px;
- align-items: center;
- justify-content: center;
- padding: 20px 0;
- margin-top: 20px;
- }
- </style>
|