|
|
@@ -0,0 +1,258 @@
|
|
|
+<template>
|
|
|
+ <div class="admin-page">
|
|
|
+ <Breadcrumb />
|
|
|
+
|
|
|
+ <a-card :bordered="false" class="page-card" :title="isEdit ? '编辑优惠码' : '新建优惠码'">
|
|
|
+ <a-spin :loading="pageLoading">
|
|
|
+ <a-form :model="form" layout="vertical" @submit-success="handleSubmit">
|
|
|
+ <a-row :gutter="24">
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="优惠码" required>
|
|
|
+ <a-input
|
|
|
+ v-model="form.code"
|
|
|
+ placeholder="如 SPRING2026"
|
|
|
+ :max-length="32"
|
|
|
+ allow-clear
|
|
|
+ :disabled="isEdit"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="名称(可选)">
|
|
|
+ <a-input v-model="form.name" placeholder="内部备注名称" :max-length="64" allow-clear />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="状态">
|
|
|
+ <a-radio-group v-model="form.state" type="button">
|
|
|
+ <a-radio :value="1">启用</a-radio>
|
|
|
+ <a-radio :value="0">停用</a-radio>
|
|
|
+ </a-radio-group>
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+
|
|
|
+ <a-divider orientation="left">折扣规则</a-divider>
|
|
|
+ <a-row :gutter="24">
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="折扣类型">
|
|
|
+ <a-radio-group v-model="form.discount_type" type="button">
|
|
|
+ <a-radio value="percent">百分比</a-radio>
|
|
|
+ <a-radio value="fixed">固定减免</a-radio>
|
|
|
+ </a-radio-group>
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item :label="form.discount_type === 'percent' ? '折扣比例(%)' : '减免金额(元)'">
|
|
|
+ <a-input-number
|
|
|
+ v-model="form.discount_value"
|
|
|
+ :min="form.discount_type === 'percent' ? 1 : 0.01"
|
|
|
+ :max="form.discount_type === 'percent' ? 100 : 99999"
|
|
|
+ :precision="form.discount_type === 'percent' ? 0 : 2"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="最低消费(元,0 表示不限)">
|
|
|
+ <a-input-number v-model="form.min_amount" :min="0" :precision="2" style="width: 100%" />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+
|
|
|
+ <a-divider orientation="left">使用范围</a-divider>
|
|
|
+ <a-row :gutter="24">
|
|
|
+ <a-col :xs="24" :md="12">
|
|
|
+ <a-form-item label="适用用户">
|
|
|
+ <a-radio-group v-model="form.user_scope" direction="vertical">
|
|
|
+ <a-radio :value="0">所有人可用</a-radio>
|
|
|
+ <a-radio :value="1">仅指定用户可用</a-radio>
|
|
|
+ </a-radio-group>
|
|
|
+ <a-textarea
|
|
|
+ v-if="form.user_scope === 1"
|
|
|
+ v-model="form.allowed_usernames"
|
|
|
+ class="scope-textarea"
|
|
|
+ placeholder="每行一个用户名,或用逗号分隔"
|
|
|
+ :auto-size="{ minRows: 3, maxRows: 6 }"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="12">
|
|
|
+ <a-form-item label="适用商品">
|
|
|
+ <a-radio-group v-model="form.goods_scope" direction="vertical">
|
|
|
+ <a-radio :value="0">全部商品可用</a-radio>
|
|
|
+ <a-radio :value="1">仅指定商品可用</a-radio>
|
|
|
+ </a-radio-group>
|
|
|
+ <a-select
|
|
|
+ v-if="form.goods_scope === 1"
|
|
|
+ v-model="form.allowed_goods_ids"
|
|
|
+ multiple
|
|
|
+ allow-search
|
|
|
+ placeholder="选择适用商品"
|
|
|
+ class="goods-select"
|
|
|
+ :loading="goodsLoading"
|
|
|
+ >
|
|
|
+ <a-option v-for="g in goodsOptions" :key="g.id" :value="g.id">
|
|
|
+ {{ g.name }}(¥{{ g.price }})
|
|
|
+ </a-option>
|
|
|
+ </a-select>
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+
|
|
|
+ <a-divider orientation="left">使用限制</a-divider>
|
|
|
+ <a-row :gutter="24">
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="总使用次数(0=不限)">
|
|
|
+ <a-input-number v-model="form.total_limit" :min="0" :precision="0" style="width: 100%" />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="每人限用次数">
|
|
|
+ <a-input-number v-model="form.per_user_limit" :min="1" :precision="0" style="width: 100%" />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ <a-col :xs="24" :md="8">
|
|
|
+ <a-form-item label="有效期">
|
|
|
+ <a-range-picker
|
|
|
+ v-model="validityRange"
|
|
|
+ show-time
|
|
|
+ format="YYYY-MM-DD HH:mm"
|
|
|
+ value-format="x"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </a-form-item>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+
|
|
|
+ <a-form-item>
|
|
|
+ <a-space>
|
|
|
+ <a-button type="primary" html-type="submit" :loading="submitting">保存</a-button>
|
|
|
+ <a-button @click="$router.push('/goodsManage/goods/couponList')">返回列表</a-button>
|
|
|
+ </a-space>
|
|
|
+ </a-form-item>
|
|
|
+ </a-form>
|
|
|
+ </a-spin>
|
|
|
+ </a-card>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
+import { useRoute, useRouter } from 'vue-router'
|
|
|
+import { adminCouponDetail, adminSaveCoupon } from '@/api/coupon'
|
|
|
+import { adminGetGoodsList } from '@/api/goods'
|
|
|
+import { Notification } from '@arco-design/web-vue'
|
|
|
+
|
|
|
+const route = useRoute()
|
|
|
+const router = useRouter()
|
|
|
+const isEdit = computed(() => Boolean(route.params.id))
|
|
|
+
|
|
|
+const pageLoading = ref(false)
|
|
|
+const submitting = ref(false)
|
|
|
+const goodsLoading = ref(false)
|
|
|
+const goodsOptions = ref([])
|
|
|
+const validityRange = ref([])
|
|
|
+
|
|
|
+const form = reactive({
|
|
|
+ code: '',
|
|
|
+ name: '',
|
|
|
+ discount_type: 'percent',
|
|
|
+ discount_value: 10,
|
|
|
+ user_scope: 0,
|
|
|
+ goods_scope: 0,
|
|
|
+ total_limit: 0,
|
|
|
+ per_user_limit: 1,
|
|
|
+ min_amount: 0,
|
|
|
+ state: 1,
|
|
|
+ allowed_usernames: '',
|
|
|
+ allowed_goods_ids: []
|
|
|
+})
|
|
|
+
|
|
|
+const loadGoods = async () => {
|
|
|
+ try {
|
|
|
+ goodsLoading.value = true
|
|
|
+ const res = await adminGetGoodsList({ current: 1, pagesize: 500, keyword: '' })
|
|
|
+ if (res?.code === 0) goodsOptions.value = res.data || []
|
|
|
+ } finally {
|
|
|
+ goodsLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadDetail = async () => {
|
|
|
+ if (!route.params.id) return
|
|
|
+ pageLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await adminCouponDetail({ id: route.params.id })
|
|
|
+ if (!res || res.code !== 0) {
|
|
|
+ return Notification.error({ title: '加载失败', content: res?.msg })
|
|
|
+ }
|
|
|
+ const d = res.data
|
|
|
+ form.code = d.code
|
|
|
+ form.name = d.name || ''
|
|
|
+ form.discount_type = d.discount_type
|
|
|
+ form.discount_value = Number(d.discount_value)
|
|
|
+ form.user_scope = Number(d.user_scope)
|
|
|
+ form.goods_scope = Number(d.goods_scope)
|
|
|
+ form.total_limit = Number(d.total_limit)
|
|
|
+ form.per_user_limit = Number(d.per_user_limit)
|
|
|
+ form.min_amount = Number(d.min_amount)
|
|
|
+ form.state = Number(d.state)
|
|
|
+ form.allowed_usernames = (d.allowedUsers || []).map((u) => u.username).filter(Boolean).join('\n')
|
|
|
+ form.allowed_goods_ids = (d.allowedGoods || []).map((g) => g.id).filter(Boolean)
|
|
|
+ if (d.start_time || d.end_time) {
|
|
|
+ validityRange.value = [d.start_time ? String(d.start_time) : null, d.end_time ? String(d.end_time) : null].filter(Boolean)
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ pageLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ if (!form.code?.trim()) {
|
|
|
+ return Notification.warning({ title: '请填写优惠码' })
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ submitting.value = true
|
|
|
+ const payload = {
|
|
|
+ id: route.params.id || null,
|
|
|
+ ...form,
|
|
|
+ code: form.code.trim().toUpperCase(),
|
|
|
+ start_time: validityRange.value?.[0] ? Number(validityRange.value[0]) : null,
|
|
|
+ end_time: validityRange.value?.[1] ? Number(validityRange.value[1]) : null
|
|
|
+ }
|
|
|
+ const res = await adminSaveCoupon(payload)
|
|
|
+ if (!res || res.code !== 0) {
|
|
|
+ return Notification.error({ title: '保存失败', content: res?.msg })
|
|
|
+ }
|
|
|
+ Notification.success({ title: '保存成功' })
|
|
|
+ router.push('/goodsManage/goods/couponList')
|
|
|
+ } catch (e) {
|
|
|
+ Notification.error({ title: '保存失败', content: e.message })
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await loadGoods()
|
|
|
+ await loadDetail()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.admin-page {
|
|
|
+ padding: 0 20px 20px;
|
|
|
+}
|
|
|
+.page-card {
|
|
|
+ border-radius: 12px;
|
|
|
+ max-width: 960px;
|
|
|
+}
|
|
|
+.scope-textarea {
|
|
|
+ margin-top: 12px;
|
|
|
+}
|
|
|
+.goods-select {
|
|
|
+ margin-top: 12px;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+</style>
|