CouponTemplateForm.vue 14 KB

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