CouponTemplateForm.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <Dialog v-model="dialogVisible" :title="dialogTitle">
  3. <el-form
  4. ref="formRef"
  5. v-loading="formLoading"
  6. :model="formData"
  7. :rules="formRules"
  8. label-width="140px"
  9. >
  10. <el-form-item label="优惠券名称" prop="name">
  11. <el-input v-model="formData.name" placeholder="请输入优惠券名称" />
  12. </el-form-item>
  13. <el-form-item label="优惠券类型" prop="discountType">
  14. <el-radio-group v-model="formData.discountType">
  15. <el-radio
  16. v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
  17. :key="dict.value"
  18. :label="dict.value"
  19. >
  20. {{ dict.label }}
  21. </el-radio>
  22. </el-radio-group>
  23. </el-form-item>
  24. <el-form-item
  25. v-if="formData.discountType === PromotionDiscountTypeEnum.PRICE.type"
  26. label="优惠券面额"
  27. prop="discountPrice"
  28. >
  29. <el-input-number
  30. v-model="formData.discountPrice"
  31. placeholder="请输入优惠金额,单位:元"
  32. class="!w-400px mr-2"
  33. :precision="2"
  34. :min="0"
  35. />
  36. </el-form-item>
  37. <el-form-item
  38. v-if="formData.discountType === PromotionDiscountTypeEnum.PERCENT.type"
  39. label="优惠券折扣"
  40. prop="discountPercent"
  41. >
  42. <el-input-number
  43. v-model="formData.discountPercent"
  44. placeholder="优惠券折扣不能小于 1 折,且不可大于 9.9 折"
  45. class="!w-400px mr-2"
  46. :precision="1"
  47. :min="1"
  48. :max="9.9"
  49. />
  50. </el-form-item>
  51. <el-form-item
  52. v-if="formData.discountType === PromotionDiscountTypeEnum.PERCENT.type"
  53. label="最多优惠"
  54. prop="discountLimitPrice"
  55. >
  56. <el-input-number
  57. v-model="formData.discountLimitPrice"
  58. placeholder="请输入最多优惠"
  59. class="!w-400px mr-2"
  60. :precision="2"
  61. :min="0"
  62. />
  63. </el-form-item>
  64. <el-form-item label="满多少元可以使用" prop="usePrice">
  65. <el-input-number
  66. v-model="formData.usePrice"
  67. placeholder="无门槛请设为 0"
  68. class="!w-400px mr-2"
  69. :precision="2"
  70. :min="0"
  71. />
  72. </el-form-item>
  73. <el-form-item label="领取方式" prop="takeType">
  74. <el-radio-group v-model="formData.takeType">
  75. <el-radio :key="1" :label="1">直接领取</el-radio>
  76. <el-radio :key="2" :label="2">指定发放</el-radio>
  77. </el-radio-group>
  78. </el-form-item>
  79. <el-form-item v-if="formData.takeType === 1" label="发放数量" prop="totalCount">
  80. <el-input-number
  81. v-model="formData.totalCount"
  82. placeholder="发放数量,没有之后不能领取或发放,-1 为不限制"
  83. class="!w-400px mr-2"
  84. :precision="0"
  85. :min="-1"
  86. />
  87. </el-form-item>
  88. <el-form-item v-if="formData.takeType === 1" label="每人限领个数" prop="takeLimitCount">
  89. <el-input-number
  90. v-model="formData.takeLimitCount"
  91. placeholder="设置为 -1 时,可无限领取"
  92. class="!w-400px mr-2"
  93. :precision="0"
  94. :min="-1"
  95. />
  96. </el-form-item>
  97. <el-form-item label="有效期类型" prop="validityType">
  98. <el-radio-group v-model="formData.validityType">
  99. <el-radio
  100. v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE)"
  101. :key="dict.value"
  102. :label="dict.value"
  103. >
  104. {{ dict.label }}
  105. </el-radio>
  106. </el-radio-group>
  107. </el-form-item>
  108. <el-form-item
  109. v-if="formData.validityType === CouponTemplateValidityTypeEnum.DATE.type"
  110. label="固定日期"
  111. prop="validTimes"
  112. >
  113. <el-date-picker
  114. v-model="formData.validTimes"
  115. style="width: 240px"
  116. value-format="YYYY-MM-DD HH:mm:ss"
  117. type="datetimerange"
  118. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
  119. />
  120. </el-form-item>
  121. <el-form-item
  122. v-if="formData.validityType === CouponTemplateValidityTypeEnum.TERM.type"
  123. label="领取日期"
  124. prop="fixedStartTerm"
  125. >
  126. <el-input-number
  127. v-model="formData.fixedStartTerm"
  128. placeholder="0 为今天生效"
  129. class="mx-2"
  130. :precision="0"
  131. :min="0"
  132. />
  133. <el-input-number
  134. v-model="formData.fixedEndTerm"
  135. placeholder="请输入结束天数"
  136. class="mx-2"
  137. :precision="0"
  138. :min="0"
  139. />
  140. 天有效
  141. </el-form-item>
  142. <el-form-item label="活动商品" prop="productScope">
  143. <el-radio-group v-model="formData.productScope">
  144. <el-radio
  145. v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
  146. :key="dict.value"
  147. :label="dict.value"
  148. >
  149. {{ dict.label }}
  150. </el-radio>
  151. </el-radio-group>
  152. </el-form-item>
  153. <el-form-item
  154. v-if="formData.productScope === PromotionProductScopeEnum.SPU.scope"
  155. prop="productSpuIds"
  156. >
  157. <div class="flex items-center gap-1 flex-wrap">
  158. <div class="select-box spu-pic" v-for="(spu, index) in productSpus" :key="spu.id">
  159. <el-image :src="spu.picUrl" />
  160. <Icon icon="ep:circle-close-filled" class="del-icon" @click="handleRemoveSpu(index)" />
  161. </div>
  162. <div class="select-box" @click="openSpuTableSelect">
  163. <Icon icon="ep:plus" />
  164. </div>
  165. </div>
  166. </el-form-item>
  167. <el-form-item
  168. v-if="formData.productScope === PromotionProductScopeEnum.CATEGORY.scope"
  169. prop="productCategoryIds"
  170. >
  171. <ProductCategorySelect v-model="formData.productCategoryIds" multiple />
  172. </el-form-item>
  173. </el-form>
  174. <template #footer>
  175. <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
  176. <el-button @click="dialogVisible = false">取 消</el-button>
  177. </template>
  178. </Dialog>
  179. <SpuTableSelect ref="spuTableSelectRef" multiple @change="handleSpuSelected" />
  180. </template>
  181. <script lang="ts" setup>
  182. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  183. import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
  184. import * as ProductSpuApi from '@/api/mall/product/spu'
  185. import {
  186. CouponTemplateValidityTypeEnum,
  187. PromotionDiscountTypeEnum,
  188. PromotionProductScopeEnum
  189. } from '@/utils/constants'
  190. import SpuTableSelect from '@/views/mall/product/spu/components/SpuTableSelect.vue'
  191. import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
  192. defineOptions({ name: 'CouponTemplateForm' })
  193. const { t } = useI18n() // 国际化
  194. const message = useMessage() // 消息弹窗
  195. const dialogVisible = ref(false) // 弹窗的是否展示
  196. const dialogTitle = ref('') // 弹窗的标题
  197. const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  198. const formType = ref('') // 表单的类型:create - 新增;update - 修改
  199. const formData = ref({
  200. id: undefined,
  201. name: undefined,
  202. discountType: PromotionDiscountTypeEnum.PRICE.type,
  203. discountPrice: undefined,
  204. discountPercent: undefined,
  205. discountLimitPrice: undefined,
  206. usePrice: undefined,
  207. takeType: 1,
  208. totalCount: undefined,
  209. takeLimitCount: undefined,
  210. validityType: CouponTemplateValidityTypeEnum.DATE.type,
  211. validTimes: [],
  212. validStartTime: undefined,
  213. validEndTime: undefined,
  214. fixedStartTerm: undefined,
  215. fixedEndTerm: undefined,
  216. productScope: PromotionProductScopeEnum.ALL.scope,
  217. productSpuIds: [],
  218. productCategoryIds: []
  219. })
  220. const formRules = reactive({
  221. name: [{ required: true, message: '优惠券名称不能为空', trigger: 'blur' }],
  222. discountType: [{ required: true, message: '优惠券类型不能为空', trigger: 'change' }],
  223. discountPrice: [{ required: true, message: '优惠券面额不能为空', trigger: 'blur' }],
  224. discountPercent: [{ required: true, message: '优惠券折扣不能为空', trigger: 'blur' }],
  225. discountLimitPrice: [{ required: true, message: '最多优惠不能为空', trigger: 'blur' }],
  226. usePrice: [{ required: true, message: '满多少元可以使用不能为空', trigger: 'blur' }],
  227. takeType: [{ required: true, message: '领取方式不能为空', trigger: 'change' }],
  228. totalCount: [{ required: true, message: '发放数量不能为空', trigger: 'blur' }],
  229. takeLimitCount: [{ required: true, message: '每人限领个数不能为空', trigger: 'blur' }],
  230. validityType: [{ required: true, message: '有效期类型不能为空', trigger: 'change' }],
  231. validTimes: [{ required: true, message: '固定日期不能为空', trigger: 'change' }],
  232. fixedStartTerm: [{ required: true, message: '开始领取天数不能为空', trigger: 'blur' }],
  233. fixedEndTerm: [{ required: true, message: '开始领取天数不能为空', trigger: 'blur' }],
  234. productScope: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }],
  235. productSpuIds: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }],
  236. productCategoryIds: [{ required: true, message: '分类范围不能为空', trigger: 'blur' }]
  237. })
  238. const formRef = ref() // 表单 Ref
  239. const productSpus = ref<ProductSpuApi.Spu[]>([]) // 商品列表
  240. /** 打开弹窗 */
  241. const open = async (type: string, id?: number) => {
  242. dialogVisible.value = true
  243. dialogTitle.value = t('action.' + type)
  244. formType.value = type
  245. resetForm()
  246. // 修改时,设置数据
  247. if (id) {
  248. formLoading.value = true
  249. try {
  250. const data = await CouponTemplateApi.getCouponTemplate(id)
  251. formData.value = {
  252. ...data,
  253. discountPrice: data.discountPrice !== undefined ? data.discountPrice / 100.0 : undefined,
  254. discountPercent:
  255. data.discountPercent !== undefined ? data.discountPercent / 10.0 : undefined,
  256. discountLimitPrice:
  257. data.discountLimitPrice !== undefined ? data.discountLimitPrice / 100.0 : undefined,
  258. usePrice: data.usePrice !== undefined ? data.usePrice / 100.0 : undefined,
  259. validTimes: [data.validStartTime, data.validEndTime]
  260. }
  261. // 获得商品范围
  262. await getProductScope()
  263. } finally {
  264. formLoading.value = false
  265. }
  266. }
  267. }
  268. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  269. /** 提交表单 */
  270. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  271. const submitForm = async () => {
  272. // 校验表单
  273. if (!formRef) return
  274. const valid = await formRef.value.validate()
  275. if (!valid) return
  276. // 提交请求
  277. formLoading.value = true
  278. try {
  279. const data = {
  280. ...formData.value,
  281. discountPrice:
  282. formData.value.discountPrice !== undefined ? formData.value.discountPrice * 100 : undefined,
  283. discountPercent:
  284. formData.value.discountPercent !== undefined
  285. ? formData.value.discountPercent * 10
  286. : undefined,
  287. discountLimitPrice:
  288. formData.value.discountLimitPrice !== undefined
  289. ? formData.value.discountLimitPrice * 100
  290. : undefined,
  291. usePrice: formData.value.usePrice !== undefined ? formData.value.usePrice * 100 : undefined,
  292. validStartTime:
  293. formData.value.validTimes && formData.value.validTimes.length === 2
  294. ? formData.value.validTimes[0]
  295. : undefined,
  296. validEndTime:
  297. formData.value.validTimes && formData.value.validTimes.length === 2
  298. ? formData.value.validTimes[1]
  299. : undefined
  300. } as CouponTemplateApi.CouponTemplateVO
  301. if (formData.value.productCategoryIds?.length > 0) {
  302. // 改个名字?加个字段?
  303. data.productSpuIds = formData.value.productCategoryIds
  304. }
  305. if (formType.value === 'create') {
  306. await CouponTemplateApi.createCouponTemplate(data)
  307. message.success(t('common.createSuccess'))
  308. } else {
  309. await CouponTemplateApi.updateCouponTemplate(data)
  310. message.success(t('common.updateSuccess'))
  311. }
  312. dialogVisible.value = false
  313. // 发送操作成功的事件
  314. emit('success')
  315. } finally {
  316. formLoading.value = false
  317. }
  318. }
  319. /** 重置表单 */
  320. const resetForm = () => {
  321. formData.value = {
  322. id: undefined,
  323. name: undefined,
  324. discountType: PromotionDiscountTypeEnum.PRICE.type,
  325. discountPrice: undefined,
  326. discountPercent: undefined,
  327. discountLimitPrice: undefined,
  328. usePrice: undefined,
  329. takeType: 1,
  330. totalCount: undefined,
  331. takeLimitCount: undefined,
  332. validityType: CouponTemplateValidityTypeEnum.DATE.type,
  333. validTimes: [],
  334. validStartTime: undefined,
  335. validEndTime: undefined,
  336. fixedStartTerm: undefined,
  337. fixedEndTerm: undefined,
  338. productScope: PromotionProductScopeEnum.ALL.scope,
  339. productSpuIds: [],
  340. productCategoryIds: []
  341. }
  342. formRef.value?.resetFields()
  343. productSpus.value = []
  344. }
  345. /** 获得商品范围 */
  346. const getProductScope = async () => {
  347. switch (formData.value.productScope) {
  348. case PromotionProductScopeEnum.SPU.scope:
  349. // 获得商品列表
  350. productSpus.value = await ProductSpuApi.getSpuDetailList(formData.value.productSpuIds)
  351. break
  352. case PromotionProductScopeEnum.CATEGORY.scope:
  353. formData.value.productCategoryIds = formData.value.productSpuIds
  354. formData.value.productSpuIds = []
  355. break
  356. default:
  357. break
  358. }
  359. }
  360. /** 活动商品 按钮 */
  361. const spuTableSelectRef = ref()
  362. const openSpuTableSelect = () => {
  363. spuTableSelectRef.value.open(productSpus.value)
  364. }
  365. /** 选择商品后触发 */
  366. const handleSpuSelected = (spus: ProductSpuApi.Spu[]) => {
  367. productSpus.value = spus
  368. formData.value.productSpuIds = spus.map((spu) => spu.id) as []
  369. }
  370. /** 选择商品后触发 */
  371. const handleRemoveSpu = (index: number) => {
  372. productSpus.value.splice(index, 1)
  373. formData.value.productSpuIds.splice(index, 1)
  374. }
  375. </script>
  376. <style scoped lang="scss">
  377. .select-box {
  378. display: flex;
  379. align-items: center;
  380. justify-content: center;
  381. border: 1px dashed var(--el-border-color-darker);
  382. border-radius: 8px;
  383. width: 60px;
  384. height: 60px;
  385. }
  386. .spu-pic {
  387. position: relative;
  388. }
  389. .del-icon {
  390. position: absolute;
  391. z-index: 1;
  392. width: 20px !important;
  393. height: 20px !important;
  394. right: -10px;
  395. top: -10px;
  396. }
  397. </style>