newCoupon.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. <template>
  2. <!-- 优惠券管理 - 新增页面 -->
  3. <div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
  4. <div class="header">
  5. <el-button @click="goBack"> 返回 </el-button>
  6. <h2 class="title">{{ type == "add" ? "新建" : "编辑" }}优惠券</h2>
  7. </div>
  8. <el-form :model="couponModel" ref="ruleFormRef" :rules="rules" label-width="200px" class="formBox">
  9. <div class="content">
  10. <!-- 左侧内容区域 -->
  11. <div class="contentLeft">
  12. <!-- 优惠券名称 -->
  13. <el-form-item label="优惠券名称" prop="name">
  14. <el-input maxlength="50" v-model="couponModel.name" placeholder="请输入" clearable />
  15. </el-form-item>
  16. <!-- 面值 -->
  17. <el-form-item label="面值(¥)" prop="nominalValue">
  18. <el-input v-model="couponModel.nominalValue" maxlength="15" placeholder="请输入" clearable />
  19. </el-form-item>
  20. <!-- 开始领取时间 -->
  21. <el-form-item label="开始领取时间" prop="beginGetDate">
  22. <el-date-picker
  23. v-model="couponModel.beginGetDate"
  24. format="YYYY/MM/DD"
  25. value-format="YYYY-MM-DD"
  26. placeholder="请选择开始领取时间"
  27. :disabled-date="disabledStartDate"
  28. />
  29. </el-form-item>
  30. <!-- 结束领取时间 -->
  31. <el-form-item label="结束领取时间" prop="endGetDate">
  32. <el-date-picker
  33. v-model="couponModel.endGetDate"
  34. format="YYYY/MM/DD"
  35. value-format="YYYY-MM-DD"
  36. placeholder="请选择结束领取时间"
  37. :disabled-date="disabledEndDate"
  38. />
  39. </el-form-item>
  40. <!-- 有效期 -->
  41. <el-form-item label="有效期(天)" prop="specifiedDay">
  42. <el-input v-model="couponModel.specifiedDay" maxlength="5" placeholder="请输入" clearable />
  43. </el-form-item>
  44. <!-- 库存 -->
  45. <el-form-item label="库存(张)" prop="singleQty">
  46. <el-input v-model="couponModel.singleQty" maxlength="5" placeholder="请输入" clearable />
  47. </el-form-item>
  48. <!-- 用户领取规则 -->
  49. <el-form-item label="用户领取规则" prop="claimRule">
  50. <el-radio-group v-model="couponModel.claimRule" class="ml-4">
  51. <el-radio v-for="item in claimRuleOptions" :key="item.value" :value="item.value">
  52. {{ item.label }}
  53. </el-radio>
  54. </el-radio-group>
  55. </el-form-item>
  56. <!-- 用户是否需要收藏店铺领取 -->
  57. <el-form-item label="用户是否需要收藏店铺领取" prop="attentionCanReceived">
  58. <el-radio-group v-model="couponModel.attentionCanReceived" class="ml-4">
  59. <el-radio v-for="item in yesNoOptions" :key="item.value" :value="item.value">
  60. {{ item.label }}
  61. </el-radio>
  62. </el-radio-group>
  63. </el-form-item>
  64. </div>
  65. <!-- 右侧内容区域 -->
  66. <div class="contentRight">
  67. <!-- 是否有低消 -->
  68. <el-form-item label="是否有低消" prop="hasMinimumSpend">
  69. <el-radio-group v-model="couponModel.hasMinimumSpend" class="ml-4">
  70. <el-radio v-for="item in yesNoOptions" :key="item.value" :value="item.value">
  71. {{ item.label }}
  72. </el-radio>
  73. </el-radio-group>
  74. </el-form-item>
  75. <!-- 最低消费金额 -->
  76. <el-form-item label="最低消费金额(¥)" prop="minimumSpendingAmount" v-if="couponModel.hasMinimumSpend === 1">
  77. <el-input v-model="couponModel.minimumSpendingAmount" maxlength="15" placeholder="请输入" clearable />
  78. </el-form-item>
  79. <!-- 补充说明 -->
  80. <el-form-item label="补充说明" prop="supplementaryInstruction">
  81. <el-input
  82. maxlength="300"
  83. v-model="couponModel.supplementaryInstruction"
  84. :rows="4"
  85. type="textarea"
  86. placeholder="请输入"
  87. show-word-limit
  88. />
  89. </el-form-item>
  90. </div>
  91. </div>
  92. </el-form>
  93. <!-- 底部按钮区域 -->
  94. <div class="button-container">
  95. <el-button @click="() => handleSubmit('draft')"> 存草稿 </el-button>
  96. <el-button type="primary" @click="() => handleSubmit()"> 提交 </el-button>
  97. </div>
  98. </div>
  99. </template>
  100. <script setup lang="tsx" name="newCoupon">
  101. /**
  102. * 优惠券管理 - 新增页面
  103. * 功能:支持优惠券的新增操作
  104. */
  105. import { ref, reactive, watch, nextTick, onMounted } from "vue";
  106. import { ElMessage } from "element-plus";
  107. import { useRoute, useRouter } from "vue-router";
  108. import type { FormInstance } from "element-plus";
  109. import { getCouponDetail, addDiscountCoupon, editDiscountCoupon } from "@/api/modules/couponManagement";
  110. import { validatePositiveNumber, validatePositiveInteger, validateDateRange, validatePriceFormat } from "@/utils/eleValidate";
  111. import { localGet } from "@/utils";
  112. import { getVoucherDetail } from "@/api/modules/voucherManagement";
  113. // ==================== 响应式数据定义 ====================
  114. // 路由相关
  115. const router = useRouter();
  116. const route = useRoute();
  117. // 页面状态
  118. const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
  119. const id = ref<string>(""); // 页面ID参数
  120. // ==================== 表单验证规则 ====================
  121. const rules = reactive({
  122. name: [{ required: true, message: "请输入优惠券名称" }],
  123. nominalValue: [
  124. { required: true, message: "请输入面值" },
  125. {
  126. validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
  127. trigger: "blur"
  128. }
  129. ],
  130. beginGetDate: [
  131. { required: true, message: "请选择开始领取时间" },
  132. {
  133. validator: validateDateRange(
  134. () => couponModel.value.beginGetDate,
  135. () => couponModel.value.endGetDate,
  136. "开始领取时间不能早于当前时间",
  137. "结束领取时间不能早于当前时间",
  138. "开始领取时间必须早于结束领取时间",
  139. true,
  140. true
  141. ),
  142. trigger: "change"
  143. }
  144. ],
  145. endGetDate: [
  146. { required: true, message: "请选择结束领取时间" },
  147. {
  148. validator: validateDateRange(
  149. () => couponModel.value.beginGetDate,
  150. () => couponModel.value.endGetDate,
  151. "开始领取时间不能早于当前时间",
  152. "结束领取时间不能早于当前时间",
  153. "开始领取时间必须早于结束领取时间",
  154. true,
  155. true
  156. ),
  157. trigger: "change"
  158. }
  159. ],
  160. specifiedDay: [
  161. { required: true, message: "请输入有效期" },
  162. {
  163. validator: validatePositiveInteger("有效期必须为正整数", { required: false }),
  164. trigger: "blur"
  165. }
  166. ],
  167. singleQty: [
  168. { required: true, message: "请输入库存" },
  169. {
  170. validator: validatePositiveInteger("库存必须为正整数", { required: false }),
  171. trigger: "blur"
  172. }
  173. ],
  174. minimumSpendingAmount: [
  175. { required: true, message: "请输入最低消费金额" },
  176. {
  177. validator: validatePositiveNumber("最低消费金额必须为正数"),
  178. trigger: "blur"
  179. },
  180. {
  181. validator: validatePriceFormat("整数部分最多6位,小数部分最多2位"),
  182. trigger: "blur"
  183. }
  184. ]
  185. });
  186. // ==================== 选项配置 ====================
  187. // 用户领取规则选项
  188. const claimRuleOptions = [
  189. { label: "每日一领", value: "day" },
  190. { label: "每周一领", value: "week" },
  191. { label: "每月一领", value: "month" }
  192. ];
  193. // 是/否选项
  194. const yesNoOptions = [
  195. { label: "是", value: 1 },
  196. { label: "否", value: 0 }
  197. ];
  198. // ==================== 优惠券信息数据模型 ====================
  199. const couponModel = ref<any>({
  200. // 优惠券名称
  201. name: "",
  202. // 面值(元)
  203. nominalValue: "",
  204. // 开始领取时间
  205. beginGetDate: "",
  206. // 结束领取时间
  207. endGetDate: "",
  208. // 有效期
  209. specifiedDay: "",
  210. // 库存
  211. singleQty: "",
  212. // 用户领取规则:day-每日一领,week-每周一领,month-每月一领
  213. claimRule: "day",
  214. // 用户是否需要收藏店铺领取:1-是,0-否
  215. attentionCanReceived: 1,
  216. // 是否有低消:1-是,0-否
  217. hasMinimumSpend: 0,
  218. // 最低消费金额
  219. minimumSpendingAmount: "",
  220. // 补充说明
  221. supplementaryInstruction: ""
  222. });
  223. // ==================== 监听器 ====================
  224. // 初始化标志,用于防止初始化时触发验证
  225. const isInitializing = ref(true);
  226. /**
  227. * 监听开始领取时间变化
  228. * 当开始时间改变时,重新验证结束时间
  229. */
  230. watch(
  231. () => couponModel.value.beginGetDate,
  232. () => {
  233. if (isInitializing.value) return;
  234. if (couponModel.value.endGetDate) {
  235. nextTick(() => {
  236. ruleFormRef.value?.validateField("endGetDate");
  237. });
  238. }
  239. }
  240. );
  241. /**
  242. * 监听结束领取时间变化
  243. * 当结束时间改变时,重新验证开始时间
  244. */
  245. watch(
  246. () => couponModel.value.endGetDate,
  247. () => {
  248. if (isInitializing.value) return;
  249. if (couponModel.value.beginGetDate) {
  250. nextTick(() => {
  251. ruleFormRef.value?.validateField("beginGetDate");
  252. });
  253. }
  254. }
  255. );
  256. /**
  257. * 监听是否有低消变化
  258. * 当选择"否"时,清空最低消费金额
  259. */
  260. watch(
  261. () => couponModel.value.hasMinimumSpend,
  262. newVal => {
  263. if (isInitializing.value) return;
  264. if (newVal === 0) {
  265. couponModel.value.minimumSpendingAmount = "";
  266. nextTick(() => {
  267. ruleFormRef.value?.clearValidate("minimumSpendingAmount");
  268. });
  269. }
  270. }
  271. );
  272. // ==================== 事件处理函数 ====================
  273. /**
  274. * 组件挂载时初始化
  275. * 从路由参数中获取页面类型和ID
  276. */
  277. onMounted(async () => {
  278. id.value = (route.query.id as string) || "";
  279. type.value = (route.query.type as string) || "";
  280. if (type.value != "add") {
  281. let res: any = await getCouponDetail({ counponId: id.value });
  282. couponModel.value = { ...couponModel.value, ...res.data };
  283. // 根据最低消费金额设置是否有低消
  284. const amount = Number(couponModel.value.minimumSpendingAmount);
  285. if (!isNaN(amount) && amount > 0) {
  286. couponModel.value.hasMinimumSpend = 1;
  287. } else {
  288. couponModel.value.hasMinimumSpend = 0;
  289. }
  290. }
  291. await nextTick();
  292. ruleFormRef.value?.clearValidate();
  293. isInitializing.value = false;
  294. });
  295. /**
  296. * 返回上一页
  297. */
  298. const goBack = () => {
  299. router.go(-1);
  300. };
  301. // ==================== 表单引用 ====================
  302. const ruleFormRef = ref<FormInstance>(); // 表单引用
  303. /**
  304. * 提交数据(新增)
  305. * 验证表单,通过后调用相应的API接口
  306. */
  307. const handleSubmit = async (submitType?: string) => {
  308. // 组装提交参数
  309. let params: any = { ...couponModel.value };
  310. params.storeId = localGet("createdId");
  311. params.couponId = "";
  312. params.couponStatus = submitType ? 0 : 1;
  313. params.restrictedQuantity = 0;
  314. params.expirationDate = 0;
  315. params.getStatus = 1;
  316. params.type = 1;
  317. if (submitType) {
  318. if (!couponModel.value.name) {
  319. ElMessage.warning("请填写优惠券名称");
  320. return;
  321. }
  322. if (type.value == "add") {
  323. let res: any = await addDiscountCoupon(params);
  324. if (res && res.code == 200) {
  325. ElMessage.success("创建成功");
  326. }
  327. } else {
  328. params.couponId = id.value;
  329. let res: any = await editDiscountCoupon(params);
  330. if (res && res.code == 200) {
  331. ElMessage.success("修改成功");
  332. }
  333. }
  334. goBack();
  335. return;
  336. }
  337. // 验证表单
  338. ruleFormRef.value!.validate(async (valid: boolean) => {
  339. if (!valid) return;
  340. if (type.value == "add") {
  341. let res: any = await addDiscountCoupon(params);
  342. if (res && res.code == 200) {
  343. ElMessage.success("创建成功");
  344. }
  345. } else {
  346. params.couponId = id.value;
  347. let res: any = await editDiscountCoupon(params);
  348. if (res && res.code == 200) {
  349. ElMessage.success("修改成功");
  350. }
  351. }
  352. goBack();
  353. });
  354. };
  355. // ==================== 工具函数 ====================
  356. /**
  357. * 禁用开始领取时间的日期
  358. * 不能选择早于当前时间的日期
  359. */
  360. const disabledStartDate = (time: Date) => {
  361. const today = new Date();
  362. today.setHours(0, 0, 0, 0);
  363. return time.getTime() < today.getTime();
  364. };
  365. /**
  366. * 禁用结束领取时间的日期
  367. * 不能选择早于当前时间的日期,也不能选择早于开始领取时间的日期
  368. */
  369. const disabledEndDate = (time: Date) => {
  370. const today = new Date();
  371. today.setHours(0, 0, 0, 0);
  372. if (time.getTime() < today.getTime()) {
  373. return true;
  374. }
  375. if (couponModel.value.beginGetDate) {
  376. const startDate = new Date(couponModel.value.beginGetDate);
  377. startDate.setHours(0, 0, 0, 0);
  378. return time.getTime() < startDate.getTime();
  379. }
  380. return false;
  381. };
  382. </script>
  383. <style scoped lang="scss">
  384. /* 页面容器 */
  385. .table-box {
  386. display: flex;
  387. flex-direction: column;
  388. height: auto !important;
  389. min-height: 100%;
  390. }
  391. /* 头部区域 */
  392. .header {
  393. display: flex;
  394. align-items: center;
  395. padding: 20px;
  396. border-bottom: 1px solid #e4e7ed;
  397. }
  398. .title {
  399. flex: 1;
  400. margin: 0;
  401. font-size: 18px;
  402. font-weight: bold;
  403. text-align: center;
  404. }
  405. /* 内容区域布局 */
  406. .content {
  407. display: flex;
  408. flex: 1;
  409. column-gap: 20px;
  410. width: 98%;
  411. margin: 20px auto;
  412. /* 左侧内容区域 */
  413. .contentLeft {
  414. width: 50%;
  415. }
  416. /* 右侧内容区域 */
  417. .contentRight {
  418. width: 50%;
  419. }
  420. }
  421. /* 表单容器 */
  422. .formBox {
  423. display: flex;
  424. flex-direction: column;
  425. width: 100%;
  426. min-height: 100%;
  427. }
  428. /* 底部按钮容器 - 居中显示 */
  429. .button-container {
  430. display: flex;
  431. gap: 12px;
  432. align-items: center;
  433. justify-content: center;
  434. padding: 20px 0;
  435. margin-top: 20px;
  436. }
  437. </style>